Changes touching this path

  • day 0: loot hosts loot f4c30e75 · dbf3dbe6…
  • evidence: crew minted and verified (#86) 1fada823 · dbf3dbe6…diff
  • catch up: GB1 loot ferry lands (PR #114) 6816f50d · dbf3dbe6…diff
  • normalize working tree to LF: byte-stable co-located bridge (.gitattributes -text) e58fdda6 · dbf3dbe6…diff
  • S1: implicit auto-snapshot on mutating verbs + demotion guard (#144) (#156) Flip the snapshot trigger from explicit to implicit (ADR 0030): every mutating verb captures the working tree first, so edits are never lost between commands and no manual `loot status` is needed. Read-only verbs still never snapshot. - Add `implicit_snapshot` + `SnapshotOpts` (--allow-demote repeatable, --no-snapshot/--ignore-working-copy) and wire it into `new`, `describe`, `grant` (both forms), `maroon`, `migrate`. A `positionals` helper strips the `--allow-demote <path>` value so it is never read as a verb positional. - `loot new` now captures pending edits before finalizing via `Workspace::finalize_capturing`, dropping an empty/tip-duplicate capture so a bare `new` mints no empty signed change. `working_message` preserves a described name across an implicit capture. - Demotion guard (#62) rides the implicit snapshot and is now a typed `RepoError::Demotion { paths }` (matchable, not a Backend string); the message is verb-agnostic ("re-run with --allow-demote"). - Docs: CONTEXT.md working-change + .lootattributes notes; USAGE. Verified end-to-end on the built binary (new/grant capture without status; demotion aborts + --allow-demote overrides; --no-snapshot skips) plus 6 new tests. Full workspace test suite + clippy green (no new warnings). Git-Author: Connor Miller <53197564+Connor-Miller@users.noreply.github.com> 8dc00304diff
  • S0: stable-change-id data model + FORMAT_MAJOR 6 (#143) Implement the durable change-id data model per ADR 0029 — the keystone the jj-ergonomics trio (map #142) builds on. Two ids per change: the existing content-derived **version id** (`ChangeNode.id`, unchanged role: dedup, DAG edges, sync addressing) and a NEW random 16-byte **change id**, a durable handle stable across a working change's re-snapshots. - `ChangeNode` gains additive `change_id: Option<[u8; 16]>`; never folded into any hash. - The Workspace mints a fresh change id when a change begins and carries it across every re-snapshot (`snapshot_allowing` reads the prior working node's id before dropping it; `record_carrying` carries, `record` mints when authored). Keyless/bridge/legacy changes stay `None`. - Finalize signs over `version_id ‖ change_id` (new `change_signing_message`); `verify_authored_change` checks the same. A legacy change (`change_id = None`) signs over the version id alone, so pre-v6 signatures still verify unchanged. All four CLI finalize paths widened (finalize_working, sign_change, resolve). - Wire/durable codecs carry the change id after author+sig, gated on major >= 6 (`put_change_id`/`read_change_id`); idempotent on re-receipt. - FORMAT_MAJOR 5 -> 6; legacy decodes as `None`, no backfill. v6 goldens added, v5 kept as decode-compat. Parents, dedup, sync addressing, convergence: unchanged (they key on the version id). No display/verb changes — that is S2. Tests: change id stable across re-snapshots while version id rewrites; keyless mints none; v6 signed-over-both-ids verifies through apply; relabelling the change id after signing is rejected; v6 bundle/graph round-trip; v<=5 loads as legacy. Verified end-to-end via the CLI (alice bundles a v6 signed change, bob applies and verifies). f253ce09 · dbf3dbe6…diff
  • Merge pull request #157 from Connor-Miller/s0-stable-change-id-format-6 S0 — Stable-id data model + FORMAT_MAJOR 6 (keystone) (#143) Git-Author: Connor Miller <53197564+Connor-Miller@users.noreply.github.com> 419d6996diff
  • S4: operation log + undo (#146) Implement ADR 0031: an append-only, repo-wide, local-only operation log (`.loot/ops`) backing `loot undo` / `loot op log` / `loot op restore`, the safety net that makes ADR 0030's implicit auto-snapshot safe to trust. Core (`loot_core::oplog`): - `Operation` captures the resulting **view** — change-graph heads, each dock's working/tip pointers, the conflicts set, and the ambient-dock pointer — as raw pointer-file bytes, so restore is a pure pointer reset that never touches the object store or the append-only graph (nothing is ever deleted). - `record` appends one op per view-changing command; `undo` steps the view back one op and appends a compensating op (the log grows on undo, so redo lands); `op restore <n>` jumps to any op. A 1-based ordinal is the durable ref; a `pos` field walks the history back one step at a time. - Barriers: `push`/`grant`/`maroon`/`pull-grants` are recorded non-undoable; undo refuses to cross one and names the real remedy (keyring/manifest are one-way state a view reset cannot retract). CLI: - `loot undo`, `loot op log`, `loot op restore <n>` verbs + USAGE. - One `record_op` per view-changing command; read-only verbs record nothing. loot has no standalone snapshot op — S2 made `status` read-only, so every capture rides a mutating verb and that verb is the one op. - undo/restore reload from the restored files and re-materialize the ambient dock (writing the restored tree, pruning what the step removed). The oplog never enters a bundle (bundle serializes changes/objects/keys, never reads `.loot/ops`) — asserted by test. Tests cover append-on-undo, walk-back, barrier refusal, absent-pointer round-trip, and full CLI undo/redo. clippy clean. ce2f96a7 · dbf3dbe6…diff
  • Merge pull request #162 from Connor-Miller/s4-oplog-undo Git-Author: Connor Miller <53197564+Connor-Miller@users.noreply.github.com> 4204625fdiff
  • Liveness: one loot-core home for live/superseded/divergent/parked + the head partition (#216); CONTEXT.md vocabulary; rides along: cargo-dist release config (dist-workspace.toml, release workflow, 0.1.0 manifests) from the install-prototype session (loot-site map, #206) 3630469b · dbf3dbe6…diff
  • Spawn DevX (#232): loot lanes observability, --ticket lane spawn, wayfinder claim-to-lane f0a885c2 · dbf3dbe6…diff
  • Workspace accepts its clock; lane flag-gate goes pure so cmd_* tests never touch a real .loot (#322) 20ac82e1 · dbf3dbe6…diff
  • Add loot verify: object-store integrity check (#19) - rehash every loose object against its address, report corrupt and missing by address, exit 1 on problems; load-free so a corrupt store can still be diagnosed aeb147b3 · dbf3dbe6…diff
  • loot verify: name each missing object's referencing change+path, and add --accept-loss lost-ledger (#335) - missing report carries provenance (MissingObject/MissingRef); .loot/lost records operator-acknowledged unrecoverable losses so verify gates CI again while new damage still fails; primary-only, store-locked 57350aa4 · dbf3dbe6…diff
  • Build the mis-seal gate: secret-name refusal + first-seal summary (#343) 12d2ea42 · dbf3dbe6…diff
  • Grant expiry: optional expires_at on GrantEntry and tag-3 wire (#20) a01c3d53 · dbf3dbe6…diff
  • loot grants --quarantined / --trust: review and trust quarantined senders (#12) 86c46a0d · dbf3dbe6…diff
  • Build loot burn: destroy + tombstone, no resurrection (#344) 0d1cc5af · dbf3dbe6…diff
  • loot id rotate: new keypair, expiry-preserving re-grant wave, archived old key (#16) 2b16256f · dbf3dbe6…diff
  • ADR 0039 build: pure-projection review + carry-at-land (#362) Review mode (`ferry --with-wip` / `loot-first review`) is now a pure projection: no ingest, no dock reconcile, no mirror-main advance, no spine rewrite - it mints the provisional commit from the lane's own anchor marks and pushes only review/<position>. A lane behind git main reviews normally; REFUSE_REVIEW_STALE_ANCHOR is deleted with the fold it guarded (#292/#302), and the #349 review-mode trigger is structurally gone. Reconcile lives only at the signing verbs, and its diverged-line shape is now the carry (DagRepo::carry_line): a self-authored suffix replays onto landed main as superseding versions - same change id, same subject, single parent, stale original kept as predecessor - so landed history stays exactly one commit per change with no ferry: 1412f811 · dbf3dbe6…diff
  • Build the seal-WIP guard (#418): refuse a bare sync verb that seals live described WIP Graduated from #356's "Prevent + hint" resolution (map #354). After ADR 0039 (#362) made review a pure projection, the only way to strand a described working change as a PR-less signed line is a *deliberate* bare sync verb. Guard that path at its source. - New typed refusal RepoError::SealWip { subject, verb } in loot-core, a sibling of MisSeal/Demotion, on the ADR 0030/0038 guard+override pattern. Overridable with --seal-wip on both verbs. - ferry seal path: thread seal_wip through ferry::run -> reconcile_onto; the Merge-with-wip arm (the ONLY place a bare ferry seals — land pre-finalizes, adopt folds through fold_line_in) refuses unless overridden. reconcile_onto now returns the sealed subject so the caller reports the seal from the seam that decided it, not a post-state heuristic. - adopt seal path: thread seal_wip through adopt_harbor; refuse before fold_line_in when a described line diverged. The clean fast-forward and redundant-capture drop return earlier, so a no-op catch-up never trips. - Fires ONLY on a described line the sync would actually fold: an un-described one stays the #275 refusal; a break-glass ferry / no-op sync (no live described WIP) is untouched. - On override the verb prints the follow-up-round recovery recipe (SEAL_WIP_RECOVERY, one shared seam); the review "nothing to review" and land "not in the pr-map" paths print it too when sealed_unlanded_anchor() detects a signed line ahead of mirror main with no PR. - tag's ferry passes seal_wip=false — it is not a finalizer, so it refuses rather than silently seal live WIP while cutting a release. - docs/agents/concurrent.md §"One seal path remains" rewritten to describe the guard; CONTEXT.md gains the "Seal-WIP guard" vocabulary entry. Tests: guard at both seams (refuse without / seal with --seal-wip), un-described stays #275, no-wip catch-up untripped, full ferry::run break-glass + no-op pass untripped, sealed_unlanded_anchor signal. Full suite green (loot-core 337, loot-cli 243, loot-first 69); the describe_contention CPU-load flake passes in isolation. 42409371 · dbf3dbe6…diff
  • Extract loot-codec + wasm core; de-risk the in-memory SDK boundary (#423) Slice 1 (the tracer bullet) is a multi-session build; this lands its riskiest integration — the Rust→WASM crypto/codec boundary — proven end-to-end, per the #381 "verify spikes before building" mandate. Spike result (ADR 0040): zstd-sys will not build for wasm32 (needs clang for its C), while aes-gcm/blake3/ed25519/getrandom-js do. So: - Extract `loot-codec`: a no-fs, wasm-buildable crate holding the byte format, sync-bundle codec, sealed content (AES-GCM + blake3), and the leaf types (Oid/Visibility/RepoError/ChangeNode). loot-core depends on it and re-exports every item at its original path — a pure relocation, proven by the unchanged, still-green workspace (767 tests pass). - zstd is an optional loot-codec feature (default on for the native host; off for wasm). A new zstd-free `sealed::decrypt` single-sources the AES-GCM step so native `open` and the wasm core share decrypt code; public content is inflated host-side in JS. - `loot-wasm`: wasm-bindgen exports (bundle decode, decrypt, blake3 address) over a pure native-testable `core`, plus the minimal diskless identity (generate/fromSeed/publicKey) on ed25519 directly — not loot-identity, whose OpenSSH/passphrase machinery is native-only (#383). - Golden-parity harness (loot-wasm/tests/parity.rs) runs identical assertions natively and under `wasm-pack test --node`, freezing native-computed vectors; both green. Remaining for slice 1 (next sessions): the TS package, fetch transport + client-side path-scoping, host-side zstd, and the live-relay behavior suite. Refs #423, #421. ADR 0040. 491cf780 · dbf3dbe6…diff
  • loot cherry-pick + revert: apply/invert a change's delta to the current line (#392, #393) 0db6666c · dbf3dbe6…diff
  • Publish mechanism: the `published` keyword + @world grantee (ADR 0041, #481) a6758352 · dbf3dbe6…diff
  • Authenticate the purge lane: a purge is a signed request (#503) A wire purge carried only an oid and yielded an unauthored tombstone, and stow honored it before storing objects -- so any authenticated pusher could destroy any oid across every tenant, needing no key, only the address. ADR 0038 already called a purge event a request asking cooperating relays and peers to destroy their copy. Cooperating meant nothing: loot honored anyone. The signature now says who is asking; a per-receiver policy decides whether to honor. Peers honor registered senders and quarantine strangers; relays honor their push allowlist. Local burn is untouched and needs no key -- only propagation requires a signature, so a keyless repo burns locally and refuses to propagate, explicitly. No global entitlement predicate exists: ChangeNode.tree is a full manifest and loot duplicate copies a tree address-for-address, so authored-a-change- referencing-this-oid is satisfiable by any cloner. Both withdrawn rules have regression tests. Format 9 to 10. A v10 reader parses a legacy purge lane and drops it, so no unsigned request is honored while the rest of a v9 bundle still decodes; an older client hard-fails on v10. Destruction is structurally unreachable without verification: HonoredBurn has a private field and authorize_burn is its only constructor. Both halves are signed, with domain-separated schemes -- without tags a maroon signature re-encodes byte-for-byte as a burn tombstone at path length 31. Maroon entitlement is an exact Manifest grantor join, and a grant only records a grantor when it actually installed a key, so a crafted grant cannot plant one for content you already hold. Closes #503. 913c5cc6 · dbf3dbe6…diff
  • store: report object count and total bytes from the layout owner (#632) 1bc2e66a · dbf3dbe6…diff
  • loot-core testkit: extracted engine modules own their tests again (#660) 3412c046 · dbf3dbe6…diff
  • burn::Burn: one home for the burn/purge concept (#662) 8e7834a7 · dbf3dbe6…diff
  • a publication survives a merge, an ingest and a resolve, so a catch-up stops silently reverting one (#783) The reported symptom was consent fatigue: every capture in the repo refused with "refusing to publish README.md", including changes touching nothing published. The cause was the opposite of the symptom -- README was not published any more, and the gate was correctly reporting a new @world transition on a publication that had been reverted. The ticket's hypothesis (an adopt/ferry fold re-sealing README from git-side content) was not the mechanism. Reading the real graph, #750's own publication change never published README at all, while it did publish both licenses. The difference is that the licenses were NEW paths, so the snapshot seal minted them @world, while README already existed and went through the merge -- where converge picked between two addresses holding identical plaintext under identical Visibility and its tie-break took the unpublished one. @world is address-neutral, outside every signature and ignored by open, key-travel and compression. That inertness is what made publication cheap; it is also why nothing carries the marker structurally. It survives only where the code writing a tree entry chooses to carry it, and four writers did not: converge, the git bridge's ingest, resolve (so a bounce quietly unpublished the conflicted path) and absorb's span rebuild. The snapshot seal learned this at #481 and nothing generalized it, so Repo::put_sealed now is the generalization. converge gains an is_published question on KeyOracle and compares publish-status as part of a path's content, which makes the existing 3-way answer publish flips correctly in both directions: a publication since the fork is an edit and wins over a stale untouched side, and a deliberate unpublish is equally an edit and still lands. On identical bytes with no base to judge from, the published side wins -- widening was consented to once, and reverting it on no evidence is the only outcome that loses something silently. The other three carry, and carry ONLY. A re-seal never originates a publication: ingest takes its policy from a git commit, which reaches loot without passing a consent seam, so a break-glass commit adding a `published` rule seals plain and leaves the transition for the capture gate to ask about in the usual place. Sealing more tightly than the rule says is always safe; more loosely is what this subsystem exists to prevent. Ask 2 -- should an unpublish need consent? No. A prompt would make consent MORE frequent, which is the failure the ticket is actually about: a gate that fires routinely teaches operators and agents to pass publication flags reflexively. The fix is fewer ways to unpublish, not another prompt, so dropping the rule is now the only one. For the same reason the gate still asks the anchor OBJECT's marker and not the anchor's .lootattributes: policy and marker disagree both when a marker was lost and when a rule has just begun publishing a path, nothing there can tell them apart, and trusting policy would turn the standing rule into a way to publish unasked. A future marker-losing path would make the gate nag again, and that is the failure worth having. Ask 4 -- the forge's published index cannot disagree with main: this repo pushes to relay.millerbyte.com and has never pushed to the forge, so the index holds no README row. Had the push happened inside the window, it would have received the unpublished object. ADR 0041 gains a 2026-08-04 amendment; CONTEXT.md's Published entry records the carry/originate split. ec50222e · dbf3dbe6…diff
  • what a peer declares in a negotiation is a type now, so the head list that strands a pull cannot be passed (#799) #40's fault-injection harness found a peer holding 3 of 6 files whose head had advanced to the sender's tip, with the negotiation reporting up to date. The ticket read that as "an interrupted pull cannot resume." It does resume: `pull_via_interrupted_fetch_resumes` has been proving that since #217, and `pull_via` declares `negotiation_have()` at all three of its call sites. The harness resumed by declaring `bob.repo.heads()` instead. That is the defect — not the sync path, but the fact that `have` accepted any `Vec<Oid>` and the obvious thing to reach for was the wrong one. `Repo::heads`' own doc said "what a peer passes as have", so the trait was teaching it. `loot clone` was doing it too, correct only because a freshly inited repo has no heads. What a peer may declare is now `Have`, and `negotiation_have()` is the only thing that makes one — the #217 filter is the check the type certifies. `Have::nothing()` is the one other constructor, for transports probing a relay and clients with no repo to ask yet; declaring less can only over-send, and over-declaring is the strandable direction. Threaded through the receiver-declares half only: SyncTransport, loot_net::{offer,fetch,pull} and the forge's signed twins. The sender side keeps `&[Oid]` deliberately. `have` means one thing in both directions — what the recipient of the bundle holds — so there is no second concept to name; what differs is provenance, and a type cannot carry a guarantee across a network. Wrapping it would advertise a check that does not happen. That reasoning lives on `Have`'s doc, which is the only place it is written. Candidate 1 from the ticket — refuse to advance the head over an incomplete closure — is not built, because ADR 0024 already priced and rejected it under "cross-batch atomicity is deliberately not provided". A confirmation note says so there, so the next reader does not re-derive it. The new `tests/sync_resume.rs` asserts the head advance rather than treating it as the failure, and covers the fault the suite genuinely missed: a bundle that is short but valid, where every batch succeeds, the pull returns Ok, and the operator is told it worked while holding half the files. The next pull heals it, which is what is pinned. Acceptance criterion 3 ("am I up to date" must answer no while any object is absent) is deliberately not built: `closure_complete` is unsatisfiable on the forge path, where a reader legitimately never receives ciphertext it holds no grant for, so the warning would fire forever on every forge repo with restricted content. That is #803. `an_interrupted_transfer_resumes_to_convergence` runs with its ignore deleted. 271dd5cf · dbf3dbe6…diff
  • a relay is measured, not capped, and the only bytes it may forget are torn writes (#817) f52d53e4 · dbf3dbe6…diff
  • a lineage catch-up brings the objects and keys its nodes reference, and a ferry that dies mid-projection names the oid in hex and the recovery (#778) The filed hypothesis was that a change node can become visible before the objects it references are durable. It is wrong: save_to already writes objects before the graph that names them, and that order is deliberate and commented. The real defect needs no race at all. The object store and the keyring are loaded exactly once, at load_from, while the shared graph is deliberately re-read mid-process by ingest_shared_lineage (the 265 catch-up primitive). So any peer that finalizes after this process opened contributes a node whose bytes and keys this process will never hold. A land guarantees a long window there: it opens the workspace, runs the pre-land tests, and only then ferries, so the ferry aborts mid-projection while holding the harbor lock. ingest_shared_lineage now refreshes objects and custody after it re-reads the graph, and load_from reads in the reverse of save_to write order, so a reader never blends a newer graph with an older object scan. Also: an Oid renders as hex instead of a 32-element decimal byte array (Debug full width, error strings short), and a mid-ferry abort now names its recovery with flags checked against the verbs that parse them. hex moved from loot-core to loot-codec because RepoError is defined in loot-codec and the dependency runs one way only; loot-core re-exports it, so every call site is unchanged. 0ba6d577 · dbf3dbe6…diff
  • the change graph gets one read view, and the two forwarding bands over it retire (#868) 826c3339 · dbf3dbe6…diff
  • a key's provenance is stated at the one door, and a grant is refused at the two that bypass it (#864) 82e06601 · dbf3dbe6…diff
  • the change graph gets one read view, and the two forwarding bands over it retire (#868) (conflict resolution: crates/loot-core/src/lib.rs) e9327007 · dbf3dbe6…diff
  • the reader names its own holes: the completeness filter and Have retire, and pull reports what never arrived (#803) f15d576e · dbf3dbe6…diff
  • the store spells a path one way: tree keys go slash-separated at every boundary, and a windows store loads nested on posix (#988) d2889f60 · dbf3dbe6…diff
  • a relay's deposit stops standing in for the forge's: the push-time dedupe moves off the Manifest onto a per-remote deposit ledger, so relay-first Restricted custody reaches the forge inbox the browser reads (#1052) 499739af · dbf3dbe6…diff
  • custody at rest seals to your own key behind an optional passphrase, unlock is a session file or an env var, and no non-interactive caller starts prompting (#1138) c8ffee3b · dbf3dbe6…diff
  • the keyring and escrow share one codec so a custody decision is written once, the locked refusal has one home instead of five drifted ones, and the terminal predicates each say why they differ (#1174) 33aca9d9 · dbf3dbe6…diff
  • content whose embargo has already lifted stops reading as sealed, because promoting due keys out of escrow is now the construction of the reader every content read hangs off instead of a doc-comment obligation six callers hoisted by hand, and deleting four of those hoists left all 2438 tests green while loot surface told an author to request a grant from herself (#1464) e59e46b3 · dbf3dbe6…diff
  • a named landmark stops needing a format major to carry its message, because a landmark IS an attestation under a reserved role envelope the signature already covers — and that is also what keeps a retirement from eating the thing it retires, since the attestation log is keyed by (change, attester, role) and writing both under one role made the retirement silently REPLACE the attestation rather than record beside it (#1508) 2d06014f · dbf3dbe6…diff
  • a repo open stops materializing half a million manifest entries no verb asked for: ChangeNode.tree becomes a Manifest holding either the decoded map or a proved-decodable byte range into one shared Arc, so topology decodes eagerly because reachability needs it while a change's path manifest decodes only when something reads it - which is where the cost actually was, since framing the 37.9 MB graph without building the maps takes 6.8 ms against 507 ms to build them, and that 507 ms was the bulk of the 1050 ms every verb paid, including loot whoami whose whole job is to print 93 bytes out of id.pub. Deref carries the ~270 existing node.tree read sites unchanged and Deferred is module-private, so a deferred manifest cannot exist over bytes Manifest::walk has not already refused with the same framing the eager pass uses, which is what earns an infallible &TreeManifest return rather than a Result; and an unmaterialized manifest is pinned never to read as an empty one, the rule read_graph_required already states for an absent graph file. Verbs also declare what they read now: RepoNeed rides beside CustodyNeed through the already-pub(crate) open_at_clocked_needing, so no new public door is added and the sealing door custody_lock pins by string and call-site count is untouched, with whoami, conflicts and manifest narrowing to POSITION_ONLY, defaulting to EVERYTHING everywhere else, and a withheld graph or store aborting loudly rather than reading as empty. whoami 555 to 42 ms against a 40 ms control, conflicts 557 to 41, manifest 557 to 42, log -n 5 626 to 144, status 704 to 227, lanes 4069 to 717, peak RSS for whoami 148 to 7.1 MB, and twelve verbs byte-identical against main including 414 KB of loot log. Three of this ticket's premises were wrong and are corrected rather than worked around: the keyring it named as the second cost measures 1.1 ms and is left alone, read_graph_outline did not fit because it discards the parents reachability needs, and the object-store half it called the boolean that costs a gigabyte had already landed in #1545 - while the stat cache stays unbuilt, which is the whole of what status and lanes still spend (#1536) b923abb1 · dbf3dbe6…diff
  • the last two readers of the deleted argv shape ask the door instead, and the blocker this ticket named was not the one holding them out: the static lifetime was never in the way, because OPT_IN_HALVES is a const whose flag fields are already static, so the valued union is a const fn fold at an arity derived from the three class lists and a half without a class becomes a build failure rather than a runtime one - what actually kept the perf binaries from naming the door is that loot-net is a dev-dependencies row in their manifest, deliberately since #847, because a real edge would link axum, reqwest and tokio into the gate binary every land builds. so the door moves once more, to loot-core, which every binary-shipping crate already names directly and which is pure std, making this #1628's own criterion applied one crate further rather than a second door - loot-net keeps a re-export shim so no caller changed. the recursive delete is demonstrated rather than argued: --size 64 pinned-checkout scratch bound the scratch slot to 64 and a file planted under a temp tree at 64/counters-repo is gone after a run driven by the real production path, proved non-vacuous by aiming the same call elsewhere and watching the assertion fail; it now binds the checkout the operator typed. the gate's poisoning is worse than this ticket described and its example argv is corrected rather than repeated, since that one is already refused by the strict single-position parse - the argv that genuinely passes needs the forged operand followed by another flag, and before this it wrote a 925 byte measured record to a file named --reps in the working directory while the land-and-stash pairing guard passed, because the stash it checked had been forged out of the message operand. after, it exits 2 naming the pairing and never measures. both call sites were extracted from main first, following #1628's remedy, so a pin can reach what main asks rather than only the door's traversal - and every pin was reverted to prove it red except one that pins fresh rather than the binding, which is documented as such and proven non-vacuous separately. the exemption list is emptied and re-adding a name now costs deleting a working census, since the roll call asserts set equality (#1682) d7e849b0 · dbf3dbe6…diff
  • a move becomes a recorded fact instead of a later guess, and the premise this ticket rested on was false: #98's object reuse is keyed by PATH, so a moved path is absent from the outgoing tree at its new key, falls through to put_sealed and gets a fresh address - the same object under a different key was not a fact waiting to be read off the tree, it had to be made true by extending the reuse across the move, and everything else follows from that. The rule is that a move is recorded only when the path's sealed object survives it, so a move whose content also changed in the same capture window shares no object, records nothing and is two rows, while a move and then an edit across two captures of one change composes and reads as one row saying the content also changed. Empty content and any ambiguity are refused for the same reason the whole design exists: zero bytes equal all zero bytes, and choosing between two vanished twins would make a signed fact depend on iteration order. On the boundary the render side compares nothing at all - resolve reads keys and never an address, a visibility or a byte, pinned by running it twice over trees that agree on every key and differ on every address at three unopenable tiers, with a positive control beside it so agreement is not evidence it answers nothing. The capture side compares once: it opens a vanished object with this identity's own keys, fail-closed so an unopenable object is never read or hashed, and pairs only against an addition at an identical visibility and publication tier, with the digest living for one call, never an address, never stored and never on the wire. What a relay newly learns is that two keys held equal ciphertext, which recording a move states in plaintext anyway since tree paths are plaintext at every tier, so it is inherent to the feature rather than the ADR 0004 oracle - written into that ADR rather than left in a message. Renames ride the label seam change_id and authored_at already use: covered by the finalize signature so a relay cannot rewrite a move, never folded into the version id so two peers reaching one tree by different routes still agree on its address, which is why every existing id and signature is unchanged and a v11 store reads with no move recorded. That was tested rather than argued, against a real store built by the shipped binary, where the old move still renders as a delete and an add in the same repo the new one renders as a rename, because inferring the old one would be the equality oracle arriving through the compatibility door. Three further things were wrong and are corrected rather than worked around: ADR 0019 says an additive change bumps the minor and has never described this project, since the minor is still zero and all five additive changes took the major; plan_moves own doc comment claimed the tier is checked before the digest, three lines above code that does the lookup first, when the property actually holds by the stronger route that nothing unopenable enters the index at all; and the empty-content refusal was written on both sides of the pairing, where each made the other unprovable and removing either left the test green - a duplicated guard is not belt and braces, it is two guards neither of which can be shown to be doing anything (#1539) 3c7e029b · dbf3dbe6…diff
  • loot count-objects lands, and the constraint it is built under is that its number has to be the number gc acts on rather than a second opinion about it: the unreachable count and the reclaim figure are gc OWN pruner and sweeper called with dry_run, over a keep set extracted out of gc as DagRepo::gc_keep_set the way 1505 pulled fold_onto out of converge, so a census that disagrees with a collection is not a thing that can be written, and the pin measures a store, really collects it, then asserts the two halves and the sum all match. One premise of the ticket was wrong in the direction that saved work: nothing reported how large a store is as a VERB, but the totals half was already factored, since RepoStore::objects_size has been the perf record load-bearing counter since 632, so this adds no third walk of the object directory at all, it adds a caller. The extraction surfaced something gc had been getting away with: its keep set was position-dependent, because the primary own working change is the one root in neither the shared graph file nor the lane registry and verify has been adding it by hand since 1309. gc could not notice, since it refuses from anywhere but the primary, where those oids ride in the loaded graph anyway; a read-only census can run from a lane, so the root moved into the shared function and the answer stopped depending on who asks. Machine output is this verb product rather than a garnish, which is why the porcelain is one row per metric keyed by a leading mark instead of one wide row: ADR 0023 makes a column unaddable, and a census is the shape most likely to be asked for one more number. The anti-vacuity pin is the same fixture reading 1 and then 0 with nothing changed but a change naming the orphan, and the read-only pin fingerprints every file under .loot rather than trusting the op log, which would never have shown a pruned object. On this lane own store it reads 7910 objects at 146.7 MiB with 2330 of them unreachable at 36.9 MiB, which is the 1524 baseline (#1523) f10ba1d9 · dbf3dbe6…diff
  • every conflict-stopping verb gets a named way out, and building it corrected the ticket in both directions rather than just adding flags: merge does not stop on a conflict at all, since merge_tips records the triples and fold_onto then signs the merge node and materializes the merged tree, so an abort there is a view step off signed history rather than the forgetting of a stop; while cherry-pick, revert and squash do the opposite and return before writing anything of their own, so their stop leaves even the paths that merged cleanly unapplied. the five named verbs are also not the whole set: converge, pull, adopt and ferrys carry bounce reach the same fold and the same ingest, and the stale-disk settle files a conflict on the way past any capturing verb with no operation behind it at all, so the covered set is now a census rather than an assumption. the state is one position-owned artifact at .loot/position/in-progress carrying the verb, its operand, and the op-log index the view sat at before the verb ran, declared Refuse on malformed content because a torn record read as absent answers nothing to abort over a tree that really is mid-operation, which is the single failure the record exists to prevent. it is a real artifact rather than a derivation because conflicts-are-non-empty-and-the-newest-op-is-a-stopping-verb answers no the instant the last resolve lands, which is exactly when continue must still work. nothing here re-implements a rollback: the inventory row is Captured at ordinal 7, so abort is that one index handed to op restore, which already restores the view, re-materializes the tree, refuses over edits made after the stop, and appends the compensating operation the op-log criterion asked for; force is declared on the five verbs and refused without abort. continue closes the stop rather than replaying it, because the re-run was built first and measured to reproduce the identical conflict: these verbs are capture-first and a resolve on the pre-dock home position never writes the tree, so the closure states instead that the operation applied nothing. status and conflicts name the operation through one derivation, and the pin asserts the identical line in both (#1506) 21b9faf1 · dbf3dbe6…diff
  • loot verify --unreachable lands the dangling report and closes the packing family, and the thing it does NOT do is walk: it answers from gc_keep_set, so the whole new computation is a SET DIFFERENCE over the scan the integrity check was already doing - no third reachability walk and no second directory pass either. 1523 refused to write the second one and this refuses to write the third, which matters because a report that could disagree with the collection it names as the remedy is worse than no report. the two directions deliberately take DIFFERENT root sets and that is the subtle half: verify own node list stays the roots for MISSING, because it carries the referencing change and path that a bare oid set cannot, while the keep set is the roots for UNREACHABLE, because it additionally holds the loaded graph and it DROPS burned oids under ADR 0038 - so using it for missing would hide deliberate destruction, and reporting a burned object as missing damage is exactly the inversion burn exists to avoid. the primary own working change is seeded once in each, by the code that already seeded it. the exit rule is structural rather than a renderer discipline: is_clean does not read unreachable at all, so garbage alone cannot fail a verify however the report is rendered, and the process pin deletes an object the listing did NOT name, chosen by subtraction, so its missing half is a positive control on its unreachable half rather than a second assertion of the same thing. size is the SPAN length for a packed object and the file length for a loose one, never plaintext, which loot cannot know without keys - and the two halves are reported APART rather than summed, because that is what keeps each one checkable against its own collector: the packed half is GcReport packed_orphans by construction and the loose half is the census unreachable figure, and the census must go on excluding packed garbage to keep the promise it is pinned to. ObjectScan now carries how each present object is stored, which costs ZERO extra syscalls because both arms already held both facts at the instant they graded an address. one residual is documented rather than hidden: an address stored both loose and packed, the window inside a repack, is attributed once to its loose copy. and the empty case prints that every stored object is referenced by a change - the exact sentence 1525 had to qualify at gc as scope drift - where here it is NOT drift, because the enumeration behind it is scan_objects and that sees packed spans too; the asymmetry is pinned as a test rather than asserted in prose. the verbs census moved to 345 and its sentence moved with it, the first run in four where that did not go stale (#1526) 061af7f3 · dbf3dbe6…diff
  • shallow clone lands with the cut on the RECEIVING side, and that is why no wire moves and no format major does either: the fetch body is a format-marked pair of oid runs, so a depth field would be a new wire shape and therefore a bump, where taking the whole change lane and KEEPING n generations needs no new field, no new endpoint and no server change - a shallow client works against every relay and forge already deployed, including older ones. the price is stated rather than glossed: the change lane metadata crosses once in full on the first round, and what is saved is the object bodies, which is where a history bytes are, exact from the second round onward. the no-false-absence guard lives in THREE places and none of them is a verb - assemble, which every CLI open lands in, measures the frontier; apply_bundle_reaching, the only thing that can move one, re-measures it; and the dispatcher states it on stderr after BOTH the success and the refusal arm, because a refusal is a false absence WORST shape. it cannot be bypassed by a verb that forgets to ask: there is no path from the CLI to history that skips assemble, and the one way to open without measuring is to declare RepoNeed without graph, which makes the first history read PANIC - so the declaration that would silence the notice is the same one that aborts the verb. it rides stderr rather than the shape, so json and porcelain stay byte identical under ADR 0023, and a complete position emits nothing at all. depth never reaches the remote AT ALL, pinned three ways: every recorded request re-encoded through the real codec is exactly header plus 32 bytes per id with no room for a depth or a path, the union of every have and wants is a SUBSET of what the relay itself named in a prior answer, and the aimed one is that two positions cloning the same history at the same depth and differing ONLY in their sparse view emit BYTE IDENTICAL requests. the test relay recorder had to start capturing IDS rather than counts, because a privacy claim about a request cannot be checked against a length. two findings came out of the sweep rather than the design. one mutation stayed GREEN and refuted a claim already written into four files - that shallowness is stable because the frontier id rides the declared closure - since a declared have IS a closure claim and the held tips therefore already subtract everything behind the cut; every occurrence is now the narrow true sentence with the refutation beside it. and a count assertion caught a silent no-op: the obvious deepen posture, the closure minus the frontier, comes back with an EMPTY change lane REPORTING SUCCESS, because the remaining ids are still descendants of the cut - a deepen must declare NOTHING, and the posture is now derived from the bound so the wrong pair cannot be spelled. the body-deferring filter is NOT attempted and is the one criterion left: it needs a lazy object read on every get, surface and diff path plus a policy for what happens offline, and half-building it would put a FIFTH kind of not-here into a store that already distinguishes four (#1527) 2cccbe27 · dbf3dbe6…diff
  • loot notes lands as a SECOND WRITER ON THE ATTESTATION LANE rather than as a record type of its own, so AC3 and AC6 arrive FREE and the wire does not move: a note is an attestation carrying a reserved role, the sibling of #1508 landmark record in the namespace the TAG field was shaped to admit, so every shipped reader parses the bytes, FORMAT_MAJOR does not move and NO RELAY OR FORGE REDEPLOY IS OWED. the visibility rule is the decision and it is DERIVED rather than chosen: the attestation lane carries no key and no grant list and rides the bundle beside the change it names, so a note audience is EVERY HOLDER OF THE CHANGE including a relay holding no content key - on a wholly-Internal change that audience can already open every byte, so no text ABOUT it can tell them anything new, and that implication FAILS THE INSTANT one path is Restricted or Embargoed. so the verb REFUSES on a change recording any path sealed narrower than Internal and names the paths, while Published PASSES because it is Internal plus world, a WIDER audience. inherit was rejected on its own terms - a Change HAS NO SINGLE VISIBILITY, that is the founding sentence of the glossary, so inheriting means a key granted to the INTERSECTION of holders which goes stale on every grant and maroon - and own-visibility was rejected because it is the only option that can OVER-disclose, making the safe answer an operator choice every time on an append-only already-delivered lane. an allow-leak hatch was declined because it would consent to nothing enforceable. the gate is the unconditional FIRST STATEMENT of the write path above a match exhaustive over the three writing ops, so a fourth does not COMPILE until it has an arm, and the limit is stated rather than implied: the check is at WRITE time, a path demoted afterwards leaves the note standing, and burn is the verb for that. seventeen mutations, each red with the count read, and both risky pins carry POSITIVE CONTROLS - the version-id pin mints a real version three lines below the assertion that notes did not, and the visibility pin has a peer who provably CANNOT open the sealed object reading the note in the clear beside it, each half the control for the other. a smoke run caught a defect this change had introduced rather than inherited: a multi-line note broke the attested-by line at column zero, so the cut is made AT THE DOOR, which covers a multi-line tag message that was latent and never exercised (#1519) 0ec3069f · dbf3dbe6…diff
  • the precondition that was prose becomes a TYPE AT THE SEAM, and both halves take that answer rather than only the cheap one. the vouching type moves into the crate that holds the door, with four private fields, and the door takes IT instead of a bare path set - so the only thing that can widen the set in a shipped build sits DOWNSTREAM OF ITS OWN GET. and that is not a second read, it IS the read the producer was already doing: the live arm was already exactly that expression, so the engine now RECORDS the open rather than the CLI asserting afterwards that one happened. one get either way. the same-change half, which already had a mechanism but one crate away from the door it protected, moves across the seam and gains two clauses NOTHING checked anywhere before - the reader and the clock - and it compares rather than records, returning the empty set on mismatch rather than an error, because a bisect midpoint must still pay the walk in full. the plan can no longer hold a set and a measurement that disagree, for the simple reason that it no longer has two fields. a THIRD precondition falls out structurally rather than being written down: only the live arm can vouch, so a plan built from a both-recorded spec now arrives EMPTY rather than full and unearned. the door stays public and that is now a DECISION with its reasoning in three places rather than an omission - making it private, as its neighbour three lines below already is, would close the hole for one crate while the public trait keeps surfacing content, so an embedder would lose the skip and nothing else. and the cross-key door is split out to take NO vouching parameter at all, because a move labels and never vouches: the hazard stops being expressible instead of being filtered by a dead guard. the mutations bought a REAL HOLE rather than confirming the work. two of them survived the first round, because every test built its vouch THROUGH the door, so the insert-side reader and clock checks were SHADOWED by the door-side ones - one identity open could fill another identity set, and an open at one clock could back a set labelled another. an isolating test was added, and both redden now. the counters are MEASURED rather than argued: an identical probe over surface and status at three sizes, run against a byte-identical copy of the unmodified tree, reads the same on every gated counter - with the honest caveat that the allocations column is VACUOUS, since that probe installs no counting allocator, so it is not claimed. and the single claim resting on the type system alone is NAMED as such: that no other crate can widen the set is held by the compiler privacy rules and not by any test, because this workspace has no compile-fail harness to assert it with (#1710) aca3a188 · dbf3dbe6…diff
  • the tree writers no longer remove the file they just wrote on a recorded case-only rename: materialize_target and materialize_promoted wrote the target tree and then removed each old path the new tree no longer holds by name, so where the filesystem folds case, as this windows machine and a default macos volume do, the removal took off disk what the write had just put there, and loot undo over such a rename reported pruned 1 path off disk, left the directory empty and made the next status read the recorded path as deleted, which loot move and converge did too, each reproduced by hand with the pre-change binary. both writers now prune through loot_core::disk_entry::prune_unwritten, the one home for the file and directory-entry rule apply-patch has taken since #2007 and #2014: an old path that is the directory entry of a path just written is renamed onto the written spelling, which keeps the bytes and gives the last name component the recorded case, the old name of a hard link is still removed by name, NotFound is a path that was not on disk rather than a path pruned, and any other removal failure is an error naming the path, where materialize_promoted discarded the error and materialize_target counted a failed removal as pruned either way. the identity helpers move out of apply_patch.rs into the new module, drop_empty_parents is shared with the view prune, and the prune makes no filesystem call beyond its removals unless an old path and a written path fold alike. the new pins went red with the entry rule removed (cli 1 passed, 3 failed; core 4 passed, 1 failed), with the old path kept and not renamed (cli 1 passed, 3 failed; core 4 passed, 1 failed) and with the removal error discarded and counted anyway (core 3 passed, 2 failed; the cli pins stay green, since none of them fails a removal). the perf gate dry run reads every counter the same before and after and says no move, ADR 0022 carries the amendment, ADR 0082 section 4 says where the rule lives, CONTEXT.md names it beside the apply-patch rule and in the re-pointing entry, and the workspace suite is green (#2011) de9fb5b0 · dbf3dbe6…diff
  • a .lootattributes rule whose pattern ends in a separator refuses the capture instead of sealing its subtree internal, and the decision is recorded with the widening it declines. the trailing-separator subtree affordance is .lootignore dialect - build/ prunes there, and #1859 repaired it there - while an attributes pattern is compared against the whole relative path, which never ends in a separator, so docs/ restricted=connor matched NO path and every path under docs/ fell through to Internal, the ANYONE-granted tier a relay reads by design. reproduced through the spawned binary first, which is what the ticket asked: loot new sealed docs/plan.md internal at exit 0 with one first-seal-summary line as the only signal, the #1108 shape exactly, and loot explain said the rule was tried and none matched. the ticket offered two answers and they are not equally safe, so the rejected one is measured rather than argued: reading the separator as .lootignore does MOVES what a path resolves to, and first-match-wins means not always narrower - with it honoured, docs/ internal above docs/secret/** restricted=connor resolves docs/secret/keys.md internal, and a_recorded_inert_pattern_moves_no_paths_tier goes red (27 passed, 4 failed over the file). silent exactly where the mis-seal gate is blind, a first seal of a basename that is not secret-shaped; an already-recorded path would trip the demotion guard instead, loud and just as unasked-for; either way the operator file would mean something new because the binary changed. so the line is RECORDED, not re-read: Attributes::parse keeps every rule exactly as it always parsed it, so no path changes tier and ferry ingest, which reads the rules and never the faults, is untouched, and the line becomes a MalformedRule that Workspace::malformed_attrs_gate already raises RepoError::MalformedAttrs over - one variant and one slug for both faults, because the taxonomy grows where a driver must act differently (#1582) and the tree one consumer of the slug does not, so each fault renders its own remedy beside its own line rather than one sentence teaching a reader the mistake they did not make. read off the compiled glob, so the docs-backslash spelling a Windows shell completes is the same fault and is quoted in the spelling it is compared in, the one loot explain prints for that line (#1859). scoped to the trailing separator and not to a pattern that matches nothing, since a bare docs internal is a fine rule for a file named docs. VISIBILITY blast radius measured rather than accepted: every .lootattributes in the estate - this repo and its lanes, scripts, millerbyte, the test and perf-scratch repos - plus every recorded revision of this repo own, holds no trailing-separator pattern, so no position next capture refuses and no repo changes tier, and the_shipped_attributes_holds_no_rule_the_gate_refuses keeps the shipped file that way. mutations: the recording dropped reddens the unit pins, the seam pin and the spawned pin (28 passed, 3 failed; 4 passed, 1 failed; 0 passed, 1 failed, the last naming the capture that succeeded); the separator honoured instead reddens four including the no-widening pin; a docs/ line added to the shipped .lootattributes reddens its census (0 passed, 1 failed). the path compared never ending in a separator is measured too, since the argv door rebase strips one and loot explain docs/ asks about docs. ADR 0038 carries the #2030 amendment and CONTEXT.md both halves, the attributes refusal and the .lootignore entry stating its affordance does not reach the other file. no migration, and no forge, relay or site byte moves, so this owes no deploy. the workspace suite is green (3859 passed over 119 binaries, 7 ignored) (#2030) 3e849712 · dbf3dbe6…diff
  • the attestation lane has one machine row, and it says whether the listing is raw or folded. loot manifest and loot notes show gain --porcelain/--json through loot_cli::attestation_shape, one row for a lane record spelled once over the record rather than per verb: the change and the attester key as full hex, a closed kind word (role, landmark-note, landmark-retire, note, reserved), a note generation, an open flag set (removed, unprintable) and the payload last, marked R for manifest listing every record as held and F for notes show listing the trusted live register, with folded in --json; manifest also emits its grant book as G rows ahead of them, by the absorb precedent of #1837. what a role carries is read through a new loot_codec::reserved::ReservedRecord::parse that reads the envelope tag once, and render::role_display now matches on it without a wildcard instead of trying the landmark parser and then the note parser - the door #1850 asks for, built only as far as this row needs; a scratch variant fails to compile at both matches. only the notes show leaf takes the flags, and the writing leaves refuse them naming it. the prose of both verbs is byte-identical, measured against the v0.4.20 primary binary on one fixture, where both verbs refused a machine flag with unknown_flag, so no frozen row moved. PROSE_ONLY_CEILING comes down 53 to 51 as the #1519 entry paid, ADR 0066 tiers move 28 to 30 and 52 to 50 with the #1519 amendment marked paid, ADR 0023 gains the amendment recording the rows, flags and JSON fields, and CONTEXT.md the Attestation entry. red first, four ways: every row marked R reddens the shape pin, the notes pin and the spawned pin (6 passed, 2 failed; 2 passed, 1 failed); notes show building its rows from the raw lane reddens the notes pin and the spawned pin (7 passed, 1 failed; 2 passed, 1 failed); manifest dropping its grant book reddens the manifest pin (7 passed, 1 failed); and the door misreading the note tag reddens the codec pin (4 passed, 1 failed), five cli pins (4 passed, 5 failed) and two spawned pins (1 passed, 2 failed). no migration, no wire or format byte moves, and no forge or relay byte moves, so this owes no deploy. the workspace suite is green (3911 passed over 123 binaries, 7 ignored) (#1846) 5f1a824d · dbf3dbe6…diff
  • adopt and ferry stop re-reading the whole shared graph once per absent tip, and the correctness question came before the perf one: ingest_shared_lineage is the one seam that re-reads that file mid-process (#265) because another session may have written it, so nothing may be memoised across a verb, and what lands is a LineagePool the verb owns rather than a cache the repo holds. a pool is one read of the file, handed to the tips of a single pass and dropped with it, and it is never trusted to say no: a tip it cannot name refills it from disk, which is the authoritative answer the per-tip loop used to take every time, so a change another session lands mid-pass is still found. WHAT IS GIVEN UP IS NAMED RATHER THAN LEFT TO BE FOUND: a node the pool holds and the file no longer does is spliced where a fresh read would have called it absent, which needs a prune inside the window between a pass reading the graph and that same pass reaching a tip. objects and custody catch up once per pool at its first arrival instead of once per tip, on the ordering the per-tip version already rested on, a node the pool names having been in the graph file before that scan ran and its objects written before its graph entry. measured in store_file_reads, a count that is bit-exact under load, and not in wall clock, because another session had the desktop. a ferry pass over trailered commits whose changes landed outside the lineage-filtered load read 42 store files over 2 such commits and 48 over 5 before, and reads 40 at both after; resolving an adopt prefix over the same two shapes read 6 and 12 before and reads 3 at both after, the prefix search having been a whole read of the same file on its own. red under mutation, counts read each time: ferry put back to a read per commit failed its pin (0 passed and 1 failed, 43 against 49), the adopt resolution put back to a read per tip failed its own (0 passed and 1 failed, 6 against 12), a pool believed on a miss failed the later-arrival pin (1 passed and 1 failed) and a pool that never catches up failed the every-tip-brings-its-bytes pin (1 passed and 1 failed), each restored. the hand-written workspace width in verbs/mod.rs moves to 389, which its own derived census demanded. no migration, no wire or format byte moves and no host behaviour moves, so this owes no deploy. the workspace suite is green (4162 passed over 132 binaries, 8 ignored) (#2224) 96a35669 · dbf3dbe6…diff
  • a deposit plan builds the tree it ships once instead of once per custody lane, and the first act had to be an instrument that could see the difference: no counter a land can read moved over a repeated whole-graph pass, so Work::GraphSorts now tallies ChangeGraph::in_order and is gated at 0% beside the object pair and store_file_reads, reading 8 on the gated fixture and one string across the drift pin repetitions. THE TICKET ASKED FOR THE TALLY TO REUSE Work::TreeWalks INSIDE finalized_tree and for ADR 0073 to re-decide that exclusion, and that was refused rather than followed: tree_walks counts a whole-tree FILESYSTEM walk of a working tree and this is a graph pass, and the in-process tier links loot-core and never loot-cli while nothing in loot-core outside its own tests calls the deposit lanes, so a tally there would have read zero on that tier anyway and the pin would have stayed green while its stated reason went false. the exclusion therefore stands unmoved on its own measurement, and the ADR records the trigger that did not fire rather than a re-decision it did not force. measured in the counter and not in wall clock: a forge plan over a fixture carrying an embargoed path, a Restricted path and the Internal default read 3 sorts before and 1 after, a relay plan 2 before and 1 after, the three lanes now taking the finalized tree as an argument plan_deposits builds once. the tips membership test inside that build became a set lookup rather than a Vec scan, which no counter can see and which is named as such rather than claimed. red under mutation, counts read each time: the shared build removed so each lane derives its own again failed the new pin (0 passed and 1 failed, graph_sorts 3 against 1, and with the forge arm relaxed the relay arm failed at 2 against 1), the tally dropped from in_order failed the anti-vacuity pin (10 passed and 1 failed) and the new pin (0 passed and 1 failed), and graph_sorts dropped from gate::COUNTERS failed four at once (7 passed and 4 failed), each restored to 11 and 1 passed. the two generated membership sentences in CONTEXT.md and HUNT-PERF.md are pasted by hand as their pins demanded, the hand-written workspace width in verbs/mod.rs moves to 390, the visibility census gains the two argument bindings the by-reference lanes create, and a count in loot-count that was wrong in the commit that wrote it is replaced by the property. no migration, no wire or format byte moves and no host behaviour moves, so this owes no deploy, though the gate records a new metric from the next land. the workspace suite is green (4174 passed over 132 binaries, 8 ignored) (#2225) fe6a089f · dbf3dbe6…diff
  • a push now carries an attestation recorded over a change the remote already holds, so loot tag after loot push reaches a relay and a forge instead of being left behind under a success line: a local attestation ledger (.loot/attestation-ledger) records per remote what each push delivered and is read by a push and by no open or save, written by RepoStore::record_attestations_sent as a read-merge-write under the shared-store lock; the push sends the attestations over the held changes of the remote that the ledger has not recorded beside the send set and prints how many, the bundle builder keeps a late attestation only over a change inside the have closure of the recipient whoever handed it in, the forge /ingest keeps one over any change its repo holds through a new changes_held store read that costs no query when every attestation rides its change, and /info gains an additive late_attestations field so a forge that does not advertise it is sent none, has nothing recorded as sent, and the push warns how many it left behind. the land gate store_file_reads is 24 on its workload with no move, where a first cut that read the ledger on every open measured 26 and was refused; an open reads 20 store files, 21 with that cut. the #48 bound holds on the wire: a push carrying one late tag sent 256 B at a relay and 306 B at a forge over both 2 and 24 held tags. the ticket recipe, whose fresh clone lacked late-tag2 through the 0.4.24 binary, shows it through a lane build. red under mutation, counts read each time, each restored green: the open reading the ledger again (0 passed and 1 failed), the ledger write overwriting instead of merging (0 and 1), the ledger ignored (0 and 2), the late lane dropped (0 and 2), the privacy filter removed (1 and 1), the forge back to in-this-bundle (2 and 1, and end to end 1 and 1), the forge keeping any change (2 and 1), a relay push recording nothing (0 and 2), a push recording to a forge that does not keep them (1 and 1), the /info flag ignored (1 and 1). no format constant, codec byte or migration moves; the forge change is live once the forge is redeployed. the pull half is not built: a pull still carries an attestation only with a change it sends. the workspace suite is green under bash ci/local.sh against Postgres 18 (4425 passed over 139 binaries, 13 ignored; site live suites 7 files passed) (#2251) 414ba6b2 · dbf3dbe6…diff

Renames are not followed. loot's tree maps a path to an address, so a rename is a delete and an add. This list is the history of the name, not of the bytes.