Skip to content

Add baley install with Claude artifact ownership and application - #253

Merged
crenshawdev merged 35 commits into
mainfrom
robot-visions
Oct 10, 2026
Merged

crenshawdev merged 35 commits into
mainfrom
robot-visions

Conversation

@crenshawdev

Copy link
Copy Markdown
Owner

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 through claude mcp add-json. Every placement comes from one reading of HOME and CLAUDE_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 an install.recorded record holds, and anything else under Baley's name is refused install-ownership-conflict with 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 when bwrap or socat is 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 new install view (view set 8) reads the record back, the guard protects the installed placements, and baley doctor judges each artifact against the record. install.sh now ends by running baley install and exits with its status, and the tracked hooks/hooks.json is 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 --check and cargo deny pass, baley-core and baley-store still don't depend on rusqlite, Cargo.lock is 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 same CLAUDE_CONFIG_DIR.

Left open: install has not run against a live Claude Code, which T17 does. Between install's recheck of mcpServers.baley and claude 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 manual flock run, and real write failures, crash durability and the UTF-8 refusal run through baley install itself have no test. The doctor doesn't name a non-stdio type as the reason a registration differs. Design 0015's static test counts are stale and belong to #54.

…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.
…ll as future work

No plan's files covered these, so after install landed they still said the
guard protects only the baley.toml files, that install adds the provider
hosts, and that there is no install view, and the roadmap's Build 3 list
left out #251 and #252.
@crenshawdev
crenshawdev merged commit 191ca9a into main Oct 10, 2026
5 checks passed
@crenshawdev
crenshawdev deleted the robot-visions branch October 10, 2026 18:57
Sign up for free to 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.

1 participant