Ashlar.Manifest: the project contract and the envelope that constrains it - #364
Merged
Conversation
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.yamlashlar.policy.yamlThat 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
sandbox,selfExtend/self_extend,never, orpolicyis 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.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.mayAddaccepts onlybrick— a brick adds capability inside the envelope; a tool or capability widens it.proposing,self-extending) must declaregatesRequired. An extension path with no gates is not a gate.Scope
Contract + loaders + 27 tests only. Nothing consumes them yet — wiring to
ashlar init(emitting both documents with asealeddefault) 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)
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 viaReplaceLineEndings("\n"), so the tests hold regardless of checkout line endings.🤖 Generated with Claude Code