Repository navigation
Add baley install with Claude artifact ownership and application - #253
Merged
Merged
Conversation
…ME and CLAUDE_CONFIG_DIR Install, the guard and the doctor each need the same answer for where the stubs, the settings file, the registration and the executable live, so the paths come from one pure derivation instead of three.
…at says partial until every artifact is written Install starts as a command the owner can run and read: it refuses a host Baley does not support, seeds the catalog, and reports each artifact as not written until the later steps give it a write decision.
…gistration and executable The guard derives those paths from HOME and CLAUDE_CONFIG_DIR, so they are protected whether or not install has run, and the versions folder is write-only. When the paths cannot be placed the guard fails closed, as it does when Baley's own folders cannot be found, and the denial names the cause it met.
… back through an install view Ownership evidence has to survive between runs, so the record holds Baley's entries whole and the view keeps only the newest record per host. The view set rises to 8, which makes an older binary read-only on the user project.
… latest one A re-run that changed nothing must change nothing in the ledger either, so the record step compares the whole payload against the install view before it appends. A fresh machine has no user project yet, and reading the latest record there answers none instead of failing.
A name or a folder proves nothing about who wrote a file. A stub is Baley's only when its bytes equal this binary's render or a hash the install record holds for that identity, and one conflict plans no write at all, so the stubs Baley could place are not written beside one it cannot.
… after the writes The applier writes each planned stub with the digest of the bytes the decision read, so a file changed in between is refused, not overwritten. A stub whose replacement failed keeps the older entry in the record, because the file still holds those bytes and the next run must not take them for foreign. The receipt says which stubs were written, replaced or left alone, and a record that could not be written makes the outcome partial.
…cord A run keeps the ownership hashes of the record it read, so if another run recorded in between, appending its facts would roll those hashes back and a retry would refuse files Baley did write. The record step now takes the sequence of the record the run read and, inside the transaction, returns the store's stale-input error when the stored record has moved and the payload differs. A racing identical record still only completes the command, and a sequential run is unchanged. I kept the check in the step that appends, using the sequence the view already stores, instead of a lock around the whole install, which would need a new seam.
Two installs planned from the same record could finish in either order, and the one that wrote the stubs could be refused while the record kept the old hashes. The install now takes an exclusive lock in Baley's own home before it reads anything and holds it through the record, so a second install is refused with a try-again line. The stale-record check stays as a second guard.
An owner's settings file that is not a JSON object, or cannot be read, is refused with install-settings-conflict. Reading it as an empty document would let install write over what the owner had.
…ecord's evidence The record says what Baley wrote last time, so only those whole values leave the settings document. Matching by pattern would delete an owner's own entries that happen to look like Baley's.
…e and name every gap One composition over the owner's document adds the hook, the deny rules and the sandbox block. A document that cannot be merged, or holds a guard hook or registration Baley did not write, is refused. A setting that weakens protection stays in the file and becomes a gap with its fix, because removing it would be the owner's call.
…e missing failIfUnavailable true would stop sandboxed Bash in every session until bwrap and socat exist, so the hook and deny rules go in and the sandbox waits. The doctor and install now word the missing-program fix from one function.
…he receipt The install decision now judges the settings file beside the stubs, and a refusal from either writes nothing. The settings write runs first, so a link or a changed file stops the install before any stub lands. A gap, a replaced owner value and the sandbox's reach are named in the receipt, and any gap makes the install partial.
Claude Code is the only writer of .claude.json, so install reads it and asks for the claude mcp commands that place this binary's entry. Only an entry equal to this binary's or to the one the record holds is Baley's. An entry that sets environment entries is not Baley's by its command alone, for the install and the doctor alike, since it can point Baley at another ledger.
claude mcp add-json refuses a name that exists, so replacing Baley's older entry is a remove then an add, and a remove deletes what is there. The file is read and judged again just before it, so an entry the owner put there in the meantime is never removed. A failed claude command leaves the install partial with the command to run by hand, and the record keeps the older entry while the file still holds it.
…le path A development build has no binary at ~/.local/bin/baley, and wiring Claude Code to run that path would break every session. The doctor's own look at the executable decides, so a binary placed by hand or a link to a build counts. The stubs, deny rules and sandbox block run nothing and are written anyway.
A leftover http or sse entry that still held Baley's command and args was judged unchanged, or removable as an older Baley entry, though Claude Code does not launch Baley through it. same_server now requires a stdio type, or none, so install, the doctor and composition agree.
… write placed it The receipt said Baley's secure value was now in the settings file from the plan alone, so a refused or failed write still claimed the file had changed.
The record and the deny rules hold folders as text, so a path with bytes that are not UTF-8 reached an expect and panicked mid-install. The settings decision now refuses it first, naming the path.
The doctor built its map from the running binary with every artifact unknown, so it could never see an installed one. It now takes the paths the guard and install share and the latest install record: an artifact is placed only when the record shows install wrote it, and the executable is the stable path. An unreadable record is a fault, not a machine where install never ran.
The script placed the binary and stopped, so the Claude wiring still needed a second command. It now runs install through the stable path and exits with install's status.
…e hook baley install renders the hook from host_artifacts::hook::render, so the hand-written copy and the test that held it to the renderer have nothing left to guard.
…esign Section 5, the sandbox text, the update and doctor sections and figures 7 and 8 still said install was future work. They now say what install writes and where, how it proves a file is Baley's, when it refuses, what a partial install leaves, and what the view set rise costs older binaries.
…design 0012 The records section had an install.recorded table that matched no code, a view that mixed install records with update outcomes, and build rows that still said the writer, the script's call and the guard's placements were unbuilt. They now follow what install records, what the view holds and what is built.
…tall proves ownership The guard design still told the reader the hook file is hand-written, the protected list waits for the installer and the doctor reads an unknown placement. The upkeep design left ownership to a build that has landed. Both now describe the derived placements, the hook install writes and the record-based ownership proof.
…the render nor the record The UPK-R2 row ended by saying an entry or hook the record does not show refuses the install, which contradicts its own earlier sentence and design 0012: an entry equal to this binary's render is Baley's with no record. Its env wording now matches 0012 as well: an entry that sets env entries is not Baley's, an empty env object still is.
…ings file The receipt and design 0012 said the sandbox applied to every Claude Code session on the machine. It is written to one user's settings file, under CLAUDE_CONFIG_DIR when that is set, so another user's sessions and a session run with a different CLAUDE_CONFIG_DIR do not read it.
The next sentence says the installer ends with baley install's exit status, which is 0, 1, 2 or 3, so exit 1 is only what the script itself returns before that last step.
… file The receipt and design 0012 said the sandbox misses sessions run with a different CLAUDE_CONFIG_DIR. A different value that names the same folder reads the same file and is covered. The line now says which file a session reads decides it.
…read the placements Designs 0001 and 0003 still called the install record, the install view, the live guard's placements and the install-time catalog seed future work, and gave the view set as 7. They now say what baley install records, that the view set is 8 and what that costs older binaries, and where the guard, the doctor and the seed get their placements.
…model The model had no edge from Baley to Claude Code for the files and registration install writes, and the system design still said the script does not call install. The model, its exported views, their captions and the trust note now describe install as built.
…ll writes The roadmap still listed t14 as in progress after #250 merged and left install to t15 in three places. The readme told readers the script does not call install. Both now say what install writes, where, and when a run is partial, and the roadmap hands t16 and t17 the settings file, the record and the placements.
…l promise The README said install changes none of your files when an artifact is not provably Baley's, with no exception. Issue 251 is one: the baley entry can change between install's check and the remove.
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 free
to 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.
Build 3 T15.
baley install [--host claude-code]now writes Claude Code's wiring at the stable path~/.local/bin/baley: the skill stubs, the guard hook, the deny rules and sandbox block merged into the user settings file, and last, after every file is written, the user-scope MCP registration throughclaude mcp add-json. Every placement comes from one reading ofHOMEandCLAUDE_CONFIG_DIR, and install, the guard and the doctor all use it. Before install replaces anything it proves the artifact is Baley's, by the current render or by the hash aninstall.recordedrecord holds, and anything else under Baley's name is refusedinstall-ownership-conflictwith no file changed. The owner's own settings keys are left alone, an older Baley hook or deny entry is removed only on the record's evidence, an owner value that weakens Baley's protection is replaced and named in the receipt, and the sandbox block is held back whenbwraporsocatis missing. The registration is judged from Claude Code's own file, and only a stdio entry counts as Baley's. While nothing runnable sits at the stable path, the hook and the registration are held back. Install appends a record only when its facts differ from the latest one and never from an older plan, holds one install lock so two runs can't overlap, and prints a receipt that says partial until every artifact is written. The newinstallview (view set 8) reads the record back, the guard protects the installed placements, andbaley doctorjudges each artifact against the record.install.shnow ends by runningbaley installand exits with its status, and the trackedhooks/hooks.jsonis gone. Designs 0001, 0002, 0003, 0010, 0012 and 0015, the C4 model, the roadmap and the README describe install as built.Each acceptance check failed before its behaviour existed and passes now. nextest ran 3565 of 3565 after the last change outside comments, clippy with
-D warnings,cargo fmt --checkandcargo denypass,baley-coreandbaley-storestill don't depend on rusqlite,Cargo.lockis unchanged, the touched docs' Mermaid blocks parse, and the C4 model validates. Verification passed 16 of 16 items, one after a fix: comments and the roadmap still described install as future work. The reviews found problems that are all fixed here: a stale install run could overwrite a newer install record, two installs could overlap, a non-stdio server was taken as Baley's registration, a security replacement was reported as applied after the settings write failed, a Baley folder that isn't UTF-8 panicked, and the receipt said the sandbox reaches only sessions with the sameCLAUDE_CONFIG_DIR.Left open: install has not run against a live Claude Code, which T17 does. Between install's recheck of
mcpServers.baleyandclaude mcp remove, another writer can change the entry, so the remove can delete one install didn't judge (#251). With nothing runnable at the stable path, install withholds the new hook but still removes a recorded older hook (#252). The two-process overlap has only been seen in a manualflockrun, and real write failures, crash durability and the UTF-8 refusal run throughbaley installitself have no test. The doctor doesn't name a non-stdiotypeas the reason a registration differs. Design 0015's static test counts are stale and belong to #54.