Skip to content

Ashlar.Manifest: the project contract and the envelope that constrains it - #364

Merged
IanFrelinger merged 1 commit into
masterfrom
claude/manifest-contract
Aug 23, 2026
Merged

Ashlar.Manifest: the project contract and the envelope that constrains it#364
IanFrelinger merged 1 commit into
masterfrom
claude/manifest-contract

Conversation

@IanFrelinger

Copy link
Copy Markdown
Owner

First slice of the Studio direction: Ashlar.Manifest, the contract both the CLI and the future GUI edit, with loaders that enforce the self-extension safety boundary rather than merely documenting it.

The design decision this encodes

If agents may propose edits to the project file, the project file cannot hold the safety envelope — an application that sets its own self-extension mode has no mode. So it is two documents:

ashlar.yamlwhat the application isagents may propose changes
ashlar.policy.yamlwhat it may becomeoperator-owned; the app cannot read, propose, or modify it

That asymmetry is the entire safety model for runtime self-extension, and it is the same discipline the tool sandbox already enforces (the root comes from the host, never from the thing being confined), applied one level up.

What the loaders enforce — all fail closed

  • A manifest declaring sandbox, selfExtend/self_extend, never, or policy is rejected, not silently ignored — ignoring the key would let a proposed edit appear to widen the envelope and succeed. Both spellings are refused so a near-miss is not a silent pass.
  • The never-list (modify_gate, widen_sandbox, access_signing_keys, truncate_ledger, grant_capability) is compiled in. A policy omitting any entry fails to load rather than yielding a permissive gate. The entries still appear in the file so a reader sees the whole envelope in one place.
  • selfExtend.mayAdd accepts only brick — a brick adds capability inside the envelope; a tool or capability widens it.
  • A mode that can admit extensions (proposing, self-extending) must declare gatesRequired. An extension path with no gates is not a gate.
  • Empty or unparseable documents are rejections, never an absent set of constraints.

Scope

Contract + loaders + 27 tests only. Nothing consumes them yet — wiring to ashlar init (emitting both documents with a sealed default) and the runtime admission controller are the next slices. New project, no existing code touched beyond solution membership and a test-project reference.

Verification (dev container)

kernel build 0 Warning(s), 0 Error(s)
Tests.Kernel 216/216 (189 existing + 27 new)
cert-gate 178/178 (unchanged)

One post-rebase fix worth noting: the rebase checkout rewrote the test file CRLF, and two fixtures manipulating raw string literals with "\n" matches silently missed. The fixture constants now normalize via ReplaceLineEndings("\n"), so the tests hold regardless of checkout line endings.

🤖 Generated with Claude Code

First slice of the Studio work: the manifest is the keystone both the CLI and the GUI
edit, and everything else (deploy, self-extension) is downstream of it.
Detailing it surfaced a problem that shaped the design. If agents may propose edits to
the project file, the project file cannot hold the safety envelope — an application that
sets its own self-extension mode has no mode. So it is two documents:
ashlar.yaml what the application IS. Agents may propose changes.
ashlar.policy.yaml what it may BECOME. Operator-owned; the application
cannot read, propose, or modify it.
That asymmetry is the whole safety model, and the loaders enforce it rather than merely
documenting it:
- A manifest declaring sandbox, selfExtend/self_extend, never, or policy is REJECTED,
not silently ignored. Ignoring it would let a proposed edit look like it widened the
envelope and appear to succeed; the author, human or agent, is told plainly that the
envelope is not theirs to set. Both spellings are refused so a near-miss is not a
silent pass.
- The never-list is COMPILED IN (PolicyLoader.RequiredNeverEntries), not configured. A
policy omitting any entry fails to load rather than yielding a permissive gate. The
entries are still written in the file so a reader sees the whole envelope in one place.
- selfExtend.mayAdd accepts only 'brick'. A brick adds capability inside the envelope;
a tool or capability widens it, so neither is ever self-addable.
- A mode that can admit extensions (proposing, self-extending) must declare
gatesRequired. An extension path with no gates is not a gate.
- Everything fails closed: an empty or unparseable policy is a rejection, never an
absent set of constraints.
This is the same discipline as the tool sandbox — the root comes from the host, never
from the thing being confined — applied one level up.
27 tests pin each rule. Note the YAML key convention is camelCase throughout (selfExtend,
mayAdd, gatesRequired), matching apiVersion/kind/metadata; the design sketches had mixed
snake_case, which is corrected here.
Not yet done: nothing consumes these loaders. Wiring them to the init command and the runtime
admission controller is the next slice.
Verified in the dev container: kernel build 0/0, Tests.Kernel 216/216, cert gate 178/178.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Amended after the rebase onto the squashed master: the checkout rewrote the test file
with CRLF per the repo's normalization, and two fixtures that manipulated the raw string
literals with "\n"-matching Replace calls silently missed and failed. The fixture
constants now normalize with ReplaceLineEndings("\n") at initialization, so the tests
hold regardless of how git checked the source out. Third CRLF incident this project;
same lesson — never let a test's correctness depend on the line endings of its own
source file.
@IanFrelinger
IanFrelinger merged commit 26a50f1 into masterAug 23, 2026
6 checks passed
@IanFrelinger
IanFrelinger deleted the claude/manifest-contract branch August 23, 2026 14:46
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@IanFrelinger@PlzTouchGrass