Changes touching this path

  • day 0: loot hosts loot f4c30e75 · dbf3dbe6…
  • evidence: crew minted and verified (#86) 1fada823 · dbf3dbe6…diff
  • normalize working tree to LF: byte-stable co-located bridge (.gitattributes -text) e58fdda6 · dbf3dbe6…diff
  • 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
  • loot edit: amend a finalized change; supersession travels as signed predecessors (ADR 0032, #171) Implement the amend model: `loot edit <change-id>` reopens a finalized tip change as the working change - a sibling (parent = its parent, tree carried address-for-address, durable handle kept) whose `predecessors` names the reopened version - so once `loot new` signs the amend, the claim that X-prime replaces X is signed data that travels, not a local-only abandon. - Format: FORMAT_MAJOR 6 -> 7 (ADR 0019). ChangeNode.predecessors: Vec<Oid> rides the bundle + durable graph after the change id, canonically sorted, empty = ordinary; folded into the version-id computation (a no-op amend still mints a distinct version) AND into the finalize signature (version_id || change_id || predecessors) - ingest trusts received ids, so stripping/forging a supersession claim on the wire must break the signature directly. v7 reads v<=6 as predecessors-empty; goldens updated, v5/v6 kept decode-compat. - Liveness (amends ADR 0029's definition): superseded - named as a predecessor by any in-graph same-cid version, regardless of that supersessor's own abandoned/superseded state - joins abandoned as a live-view filter in divergence detection, versions_of_change, and log/status rows. Abandon means kill, never revert. - Converge: converge_heads drops superseded heads before collapsing forks (a solo amend lands at peers as a clean replacement, never content-merged with the version it replaced); dock merge adopts an amend of our tip as a fast-forward and treats the mirror case as a no-op (supersedes() requires the claim to sit ON the other line). - The verb: a named Workspace mutation; refuses on an in-progress or uncaptured working tree (the documented ADR 0030 exception - edit replaces the working change and never implicit-captures), on a divergent handle (abandon first), and on descendants (tip-only v1). One undoable op (ADR 0031); output through the render String seam. - dock switch: an idle dock no longer parks a tip-duplicate working child on its tip (the finalize_capturing duplicate-drop now runs there too) - the stray polluted the tip descendants and, post-0032, would have content-merged against amends. Tests: engine liveness + canonical hashing + signature strip/forge; codec round-trips + v7 goldens; workspace edit e2e / guards / undo / dock-merge FF / converge drop. Live-verified on the built binary (edit -> amend -> new; guards; undo). 366 tests + clippy clean. 8176f2e0 · dbf3dbe6…diff
  • Fix reconcile-merge resurrection of long-deleted files (#288): a change tree is a manifest, not an ancestry overlay Live incident 2026-07-16: the #281 land's `ferry --with-wip` reconcile minted merge d3ca4b8 carrying tools/loot-first.ps1 (deleted in the #218 tail) and crates/loot-first/src/ledger.rs (moved in #232) — deleted months earlier on every line involved; neither merge parent held them. Published to origin/main, cleaned up by PR #286. Root cause (loot-core, not the bridge, and not the merge base): every recorded change carries a FULL path->address manifest — snapshot, ingest_change and merge_tips all record whole trees, and deletion is absence from the child's manifest — but ChangeGraph::tree_at/current_tree computed a tip's tree by unioning every ANCESTOR's tree child-wins (delta semantics no production node ever had). Every path ever deleted anywhere in the ancestry re-entered the computed tree forever. merge_tips fed those polluted trees to the converge classifier, which saw the same stale address on both sides (untouched) and kept it; projection then faithfully published the merge manifest. The suspicion that the merge base predated the deletions was wrong — common_ancestor_tree always returned the ancestor's exact manifest; the resurrection needed no base at all (both POLLUTED inputs re-raised the paths, base or no base). The live history had exactly two ever-deleted files, and the merge resurrected exactly those two — confirming the union mechanism. Fix: tree_at returns the change's own manifest; current_tree unions the HEAD manifests only (preserving the pre-dock multi-head view). Repro tests at all three layers, each proven red under the old semantics: change_graph (tree_at_honors_a_deletion_instead_of_unioning_the_ancestry), engine (merge_tips_does_not_resurrect_a_path_deleted_before_the_fork), and the incident-shaped ferry test (reconcile_merge_does_not_resurrect_files_deleted_on_the_spine): spine deletes a file, lines fork after it, a git-native commit lands concurrently, the reconcile merges — the deleted path must appear in neither the merged loot manifest nor the projected merge commit's git tree nor on disk. Two tests that encoded the union semantics were corrected to record full manifests (an empty/partial tree in a seeded change means delete-all, which is what those tests accidentally said). ADR 0028 gains an amendment. Out of scope, noted there: the classifier still has no deletion-vs-base rule, so a path freshly deleted on ONE side since the fork is still re-adopted from the other side of a reconcile merge — follow-up ticket to come. 77f95feb · dbf3dbe6…diff
  • Workspace accepts its clock; lane flag-gate goes pure so cmd_* tests never touch a real .loot (#322) 20ac82e1 · dbf3dbe6…diff
  • loot embargo-status <path>: report embargoed/revealed/not-embargoed (#15) cb471c93 · 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
  • 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
  • TS SDK slice 2: author & push a signed public change (#424) The write half of the in-memory loop: a pre-registered key edits files and pushes a signed full-tree change to the relay, read back to confirm. All composition (change-id fold, dual signatures, bundle encode, /stow envelope) stays in Rust/WASM (#381); TS owns only the capture-first overlay. Prefactor: - Move the change-id fold into loot-codec (change_id module: compute_change_id_raw / change_signing_message / mint_change_id / canonical_predecessors); loot-core's compute_change_id(&Change) delegates. Behavior-preserving (loot-core 296 tests unchanged). - loot-codec: seal_uncompressed (the wasm author path can't run zstd). - loot-identity: public Identity::from_seed(&[u8;32]) (the flagged #383/#424 ctor, native side). loot-wasm: - Identity.sign + wrapEnvelope ([0x01][pubkey][sig][bundle], sig over bundle). - ChangeBuilder: carry(unchanged) / put(edited, seals uncompressed public) / finish() -> {envelope, changeId, versionId} — folds, signs the finalize message, encodes the Sync frame, wraps the envelope, all in Rust. - Golden parity (Seam 2) extended: frozen pubkey, signature, envelope bytes, and change-id fold — green natively and under wasm-pack test --node. sdk/: - Capture-first overlay: edit/remove mutate an in-RAM overlay that IS the pending change; describe names it; status/diff report it; push composes via ChangeBuilder (carry unchanged paths, put edited, skip removed) and POSTs the envelope to /stow, returning the durable change-id. - Seam 1: a write behavior suite drives a real relay whose allow-list holds the SDK key; the SDK authors the first change and reads it back through a fresh connection (4 tests). Full SDK suite: 9 green. Slice 2 authors PUBLIC content stored UNCOMPRESSED (valid + readable; zstd's C won't build for wasm, and fzstd is decompress-only). Deferred: unauthorized error mapping (slice 3), private/grant writes (slice 4). Refs #424, #421. 2defc2f5 · dbf3dbe6…diff
  • Rename the Public visibility tier to Internal (ADR 0041 §2, #480) 6969626d · dbf3dbe6…diff
  • Sweep the rust-1.96 clippy debt; document the land-holds-the-binary hazard (#667, #681) 26cfbaa9 · dbf3dbe6…diff
  • a change records when it was authored, so a projected commit stops reading '6 years ago' on GitHub (ADR 0043) loot changes carried no timestamp, so the git bridge fabricated one: BASE_EPOCH + generation, one second per ancestor depth from a 2020 epoch. All 526 commits sat inside seven minutes of September 2020. Earlier repairs (#626's floor, the missing-generation refusal) fixed ORDERING and never touched the absolute date, which is why this kept coming back. ADR 0028 inherited the no-timestamp constraint rather than choosing it. The real reason is upstream: a version id is blake3 over authored content, and a clock inside that hash gives two peers different ids for identical content, destroying the dedup and convergence of ADR 0001/0004. So authored_at rides the label seam ADR 0029 already cut for change_id -- covered by the finalize signature (no relay can restamp it) but never folded into the version id. The wasm golden vectors prove the separation held: FROZEN_VERSION_ID, FROZEN_OBJ_ADDR and FROZEN_SIGN are byte-identical, and only the version marker and one presence byte moved. Advisory, and never an ordering input: a self-reported clock is a claim, not evidence (ADR 0025). in_order/ids_topo, buoy and path_touch.ordinal are all untouched, the forge indexes nothing on it, and the projection floors it past every git parent so ancestry holds whoever's clock is wrong. Format v11: additive for readers (a v<=10 change decodes as None and an absent timestamp adds nothing to the signed message, so every existing signature still verifies), breaking for writers, so loot-cli and loot-forge go to 0.4.0 in lockstep. Forge migration 0004 stores it as bigint, not timestamptz -- signed data must round-trip bit-exact or pullers reject the change. Only new work gets real dates. Every commit on main predates v11, main is push-fast-forward-only, and backfilling would fabricate the very claim this replaces. Perf-Baseline: reset the change body grew one presence byte, plus eight where a timestamp is present, so bundle_bytes/store_bytes/wire_bytes step once at the v11 boundary eaa56d99 · dbf3dbe6…diff
  • a push deposits self-grants for the finalized tree it ships rather than the unsigned working manifest, and a parent arriving after its child no longer seats as a phantom head (#1125) bc7c27df · dbf3dbe6…diff
  • a repo stops going unopenable by every verb at once somewhere past 2100 changes, because the open's topo pass and the two loads shaped like it now share one explicit-stack walk instead of spending a call frame per ancestor, and that walk is pinned to emit exactly what the recursion emitted so the derived heads and the graph file's byte order cannot move (#1585) a463423e · dbf3dbe6…diff
  • the per-dock load stops materializing the change graph twice, because reachable_from now owns its pool and moves each reachable node out of it instead of deep-copying a complete path manifest per change and dropping the original, and it moves them in a second pass over the ids the shared topo walk emits so that walk keeps yielding borrows and the graph file byte order stays the one #1585 pinned (#1547) ea3859e4 · 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
  • converge retires a superseded head durably instead of only in memory, and the investigation had to come before the fix because the ticket named a symptom whose two halves have different answers. The stray head b0cffc66 is a superseded version under ADR 0032 of change txmplwto, #1042's forge-push work: same parent, 19 minutes earlier than the version that landed, so it is an AMEND SIBLING and not a child, which is why nothing that walks parents ever excluded it. It carries nothing a converge could merge, established three ways rather than asserted - the replacement was the primary's sole head at ops 975 to 979 and is on landed main; own reads 0 over the liveness-filtered walk, and since a head is always in its own exclusive line, own 0 can only mean the head itself is not live; and Liveness::partition puts it in stale, which converge abandons without merging. lane rm and lane gc structurally COULD NOT have taken it, which retires the ticket's own framing: both verbs are the same two steps, delete the lane directory and delete the lane entry, and neither opens the primary's .loot/heads - a lane's own head set lives in the lane's file, which rm removes with the directory, so #532's owner scoping never came into play. The SEATING is not reproduced and that is reported rather than papered over: today's lane new puts one head in and one head out against a copy of the real store, the derive_all_heads fallback produces 213 tips rather than 2, a local edit-and-new leaves one head, and an apply of a superseding bundle drops the predecessor and persists - four hypotheses refuted by running them. The PERSISTENCE is reproduced and is the live bug: converge abandons stale heads in memory, but the only persist on that arm is the settle that runs when the tip MOVES, so when the survivor is already the tip - which is exactly this ticket's orientation, a primary on landed main beside a superseded original - nothing is written and the drop dies with the Workspace. Measured on a metadata copy of the real store, converge printed already on one line, nothing to converge and left the head file at 64 bytes with the stray head intact, so loot log's own advice to run pull was a no-op for this whole class, and converge and heads reported the same head set while disagreeing about it. Removing the persist fails the new test on the REOPEN with both heads back while every assertion above the reopen still passes, which is precisely why this lasted 490 ops. A second and separate hazard was found on the way and is fixed with it: derive_all_heads, the fallback when the head file is absent or empty, excluded only nodes named as a parent, so every amended-then-landed original was a permanent derived tip - verbatim what the artifact's own doc says must not happen, and the case #1135 refused for a MALFORMED file while the empty one still fell through. Predecessors are covered now, and on a copy of the real store with the head file removed it derives 31 tips where it derived 213. The reap decision is that lane rm and lane gc should NOT grow head reaping: the head was never theirs, a lane's finalized head is a signed change already in the shared store, and stacking an irreversible head deletion behind an irreversible directory delete is two irreversibles - a head may be retired automatically only under a predicate proving it is not the sole reference to anything live, and that predicate's consumer is converge, which also holds the merge base, the conflict classifier and undo, none of which lane gc has. Neither change can destroy work: abandon retires a head ENTRY, the graph is written as a union with disk, and gc roots from that file - after the fixed converge the head file went 64 bytes to 32 and the graph stayed 41,598,284 bytes with the change still present. The ticket's claim that loot log exits 255 is stale and is recorded as such: it exits 0 today and renders both heads (#1477) 8448e586 · 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
  • 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
  • the ordered bulk load becomes a PRIVATE CONSTRUCTOR rather than a deleted guard: from_parents_first consumes a WHOLE ITERATOR from an empty graph, so parents-first is a property of one sequence handed over ONCE rather than of an interleaving on a live graph, and a caller holding only SOME nodes in order HAS NO DOOR TO SPELL THAT WITH. insert keeps its scan, stays correct under any order, and its doc now names the three callers that need it. the precondition is CHECKED rather than stated - a debug_assert running exactly the scan release no longer pays - and the new door is pinned against INSERT ITSELF on a fork, merge and two-tip DAG rather than against a typed-out literal, so the two agree by construction. the ticket own instrument premise was WRONG AND THE TREE ALREADY SAID SO: it named repo_open, but measure.rs and the fixture name #1548 BY NUMBER where they explain that --open is ONE CHANGE DEEP, so the quadratic term is ZERO there, and that --graph-load was ADDED BY #1571 BECAUSE #1547 AND #1548 BOTH READ FLAT AGAINST IT. measured anyway to confirm: 2.02 against 2.11 ms, overlapping, flat. against the instrument that can see it the change MOVES - 10.367 to 9.228 ms at depth 1024, minus 11.0% with DISJOINT SPREADS, and 61.986 to 38.404 ms at 4096, minus 38.0%, the win growing with depth exactly as the sum over k predicts. so the park clause does not fire, and the sizing guess of single-digit percent below resolution was wrong in the direction that matters. a NEW correctness finding keeps the scan where the ticket only argued from a missing instrument: the persist rewrite is NOT parents-first, because its two loops are two parents-first sequences CONCATENATED, so a parent held only in memory arrives after a child already read off a shallow on-disk graph - that caller needs the scan on correctness grounds and the persist win is explicitly NOT CLAIMED. record and record_unauthored are left alone too, since each is ONE insert per call and never a loop, so there is no quadratic term there to remove (#1548) 4e248905 · dbf3dbe6…diff
  • BOTH READINGS WERE RIGHT and the contradiction was a DENOMINATOR: #1536 made a repo open stop materializing half a million manifest entries, which is the very call graph_load times, so the same absolute saving of one to two milliseconds at depth 1024 was 0.9% of a 189 ms open before that landed and 11% of a 10.4 ms open after it. measured across that one commit rather than argued - 155.89 ms against 10.36 ms at the SAME workload id, 15x from a change that touched neither the scan nor the fixture - and corroborated by the scaling signature, since before it the depth ratios are 2.01 and 2.06, LINEAR, with the manifest term burying everything, and after they are 2.25 and 2.69, superlinear, with the quadratic term finally EXPOSED. the fixture never moved. the general rule is now written where the next run will read it: A PERCENTAGE IS A RATIO, so a landing that shrinks the DENOMINATOR flips a verdict without touching the change, and same flags is necessary and not sufficient - record the absolute beside the ratio and the growth beside both. re-measured at the depths the ticket comments ASKED FOR and never got, 8192, 16384 and 32768, interleaved, thirty runs, all exit 0: minus 55.7%, minus 81.5% and minus 91.3%, disjoint at every depth, and the claim is about SHAPE rather than percentage - per doubling the old engine grows 2.70, 5.06 and 4.32 while the new one grows 2.12 and 2.02, which is the sum-over-k term leaving and a linear load remaining. nothing is claimed at 1024 and the old claim is WITHDRAWN, the point replaced by a dated spread carrying all thirty raw readings. #1547 figures are marked the same way, because they are pre-#1536 too and equally un-reproducible - their percentages stand, since both sides were taken on one engine, but their absolutes are marked NOT A BASELINE rather than silently re-used, and the ratio is deliberately NOT re-derived because that is a measurement nobody has taken. and the refcount preference is answered honestly rather than defended: the land DID reverse a recorded preference without saying so, the two shapes answer DISJOINT halves, and on the callers that keep the scan - the ingest paths, the two-sequence rewrite and a third site nobody had listed - the refcount REMAINS THE BETTER ANSWER (#1860) e3c5456c · dbf3dbe6…diff
  • a newline in a path does not LOOK WRONG, it FORGES ROWS - a name spelled notes.md then newline then percent then four numbers injects a SECOND DISCLOSURE TALLY, which is the number an agent reads before it asks for content - so porcelain WITHHOLDS an unframeable name rather than escaping it, refusing it, or leaving it to a sentence: path becomes dash, flags gains unprintable, and --json carries the name escaped. the three alternatives are rejected WITH REASONS AT THE SITE - documenting JSON as the only safe channel is the trade ADR 0082 already refused for CRLF, since a porcelain consumer has no way to DETECT it; escaping mints a second spelling of one path inside one tool, the #988 class whose answer was ADR 0051 ONE spelling; and refusing denies the OTHER rows, disagrees with --stat, and lets anyone who can name a file BLIND the machine channel. a tab is deliberately NOT withheld, because the path is last and the documented split recovers it - the rule is exactly as wide as the frame it protects - and with both withholdings flagged, a bare dash with NEITHER flag is now unambiguously a file NAMED dash. flags order is frozen as the encoder own, sealed first because it is the member a reader must not miss, and later members APPEND so every combination without a new one keeps todays bytes. and the run caught itself: #1554 re-created the class #1553 had fixed FIVE HOURS EARLIER, four pub items with zero out-of-crate callers, one of them a pub wrapper returning exactly what #1553 had narrowed to pub(crate) - the narrowing undone THROUGH A NEW DOOR - so all four are narrowed and the rule gains its worked example for the FUNCTION half, the half nothing enforces. the frozen shape description is corrected in four places, including a TypeScript doc that omitted the bare restricted token a peer-received path really carries, which would have mis-parsed in a consumer. the residue attribution in #1860 doc is WITHDRAWN rather than explained, restated as unexplained with what measuring it would take, because that document own thesis is that unmeasured causal claims about the instrument are the defect. eight mutations with counts read, four compile_fail probes with positive controls, and ONE MUTATION WAS CAUGHT BEING TOO WEAK - un-backticking a single table row left the pin GREEN, so it proved nothing and was redone against all four mentions (#1870) 3dfefe4f · dbf3dbe6…diff
  • two readers stop answering a visibility question out of a book the enforcement path never opens - and the ticket OWN worry about which ROW is REFUTED before anything is built on it: path_in_history consults current_tree FIRST and returns outright, with the reverse-topo history walk only a fallback for a path the live heads no longer carry. the keeps-every-change-whose-tree-contains-the-path shape belongs to change_has_path and filter_history_to_path, which is what log --path filters on and which embargo-status never touches. so the verb was already answering about the CURRENT recording, and its defect was purely WHICH BOOK rather than which row - which makes the fix cleaner than the ticket allowed for, since the tree entry is still read, being what LOCATES the object, and only the visibility moved. embargo-status now asks a new seal_visibility, the refusing twin of visibility_of, sharing held_but_unreadable with embargo_reveal_at, which is re-expressed over it so there is ONE read door. the tree entry answers ONLY when the seal cannot be produced, and then the output SAYS SO, naming the source it read, the guarantee it does not carry, and the verb that tells a missing object from a corrupt one. seal-only was rejected with its reason recorded: this verb exists for why is this file not visible after a pull, which is exactly the case where the object legitimately is not here - a withheld forge object, a mid-pull absence - so refusing there would delete the verb purpose. healthy output is byte-identical to before. conflict_side loses its fallback rather than gaining a guard: one object read returns both the seal visibility and the open verdict, and sealed_to_us is extracted so the two doors cannot drift about which errors mean sealed-to-you. the sizing is stated rather than flattered - NO observable answer changes on any state reachable today, because old and new both propagate the rot, and what changed is REACHABILITY: the mutation restoring the pre-fix shape reproduces the defect exactly, a truncated object rendering as internal. ADR 0012 gains a row for each site, and its visibility_of row is CORRECTED, having claimed conflict_side as a live caller. the wire-format option is neither taken nor touched: folding visibility into the change id is a FORMAT_MAJOR decision, the ticket ruled it outside an AFK warrant, and the enforcement question is written up to be filed rather than begun. seven mutations, every pin asserting a VALUE - a reveal instant, an oid, an error code - and never merely that something failed, which is the trap #1578 pin fell into. and the vacuous-filter trap fired once and was caught by reading the NAMES rather than the word: a filter on embargo printed 22 passed, and none of those were the three new render tests, whose names do not contain it (#1581) 74526297 · dbf3dbe6…diff
  • a prose pass narrows claims that read wider than the code, and LineDelta::rendered goes crate-private. ADR 0007, CONTEXT.md and the object_and_key_at doc say the reveal gate is asked at every read through the grant key door, not at every read, since grant_sealed keeps its escrow fallback by design; the Refit enum count, the family list in CONTEXT.md and the refit.rs header, and a stale claim that every planner runs the draft check, now point at the enum and the wildcard-free minted_edges match instead of a count; role_display gets back the blank doc line rustdoc folded into the last bullet, verified in the built HTML, and a record type added to ReservedRecord replaces a third record type; the Landmark entry records the one-line rendering from #1519; the revset named door and the hunkpick PATCH constant state the exceptions a reader finds (grep writing its own refusal, restore spelling -p out under the main.rs census); the ChangeGraph insert doc stops saying the callers do not insert parents-first, names DagRepo::apply_sync for an apply_bundle DagRepo does not have, and says why the ordered ingest_shared_lineage splice stays on insert; ADR 0023 now says what #1870 wrote in the delta_shape header, that the reveal_at split is about the field and not the number, which embargoed@ can carry. nineteen assertion messages lose the run of spaces a rewrap left inside the literal. LineDelta::rendered and Rendered become pub(crate), with a compile_fail probe and a positive control: the probe was red before the narrowing (2 passed, 1 failed), and making the method pub again reddens it (3 passed, 1 failed). items 2, 8 and 14 need no fix here: the #1515 raise is real because #1516 lowered the ceiling between the two raises, #1968 dropped the width narrative, and #1860 withdrew the figures. cargo doc warnings are unchanged for loot-core, loot-codec and loot-cli. 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 (3913 passed over 123 binaries, 7 ignored) (#1848) eb966bdb · dbf3dbe6…diff
  • every place that repeats a stop now says what --continue will and will not do, and an F row carries the role as it was signed. resume::continue_clause is the one wording, read through ways_out, once_resolved and stop_advice by the stops, by the in-progress note status and conflicts print (which said only --continue once loot conflicts is empty), by the wrong-verb refusal (which said --continue finishes it), by apply-patch (whose own copy never said its clean paths stay unwritten), and by the apply and merge stops, which now name both ways out without the disclaimer, the reason written at stop_advice. a census in resume.rs refuses a production line of loot-cli outside that module that spells --continue, USAGE lines carrying USAGE_NOTE excepted, and its first run caught the move --before USAGE line paraphrasing the note without its disclaimer, which every_stopping_verb_documents_one_resume_sentence misses because it reads one line per verb. the note fold carries each live record role as signed (loot_core::note::LiveNote), so notes show --json role is the signed bytes for a generation spelled +2 where it was re-encoded as 2; every other row byte is pinned unchanged through the spawned binary, ADR 0023 records the amendment, and Note::parse is not made stricter, since this repo lane held no records to measure and refusing would reclassify signed records held elsewhere. manifest prose names an attester through attestation_shape::attester_name, the naming its JSON used, so the own key stops printing as hex there; unprintable is one const in delta_shape; ADR 0066 says notes stands for notes show; the ChangeGraph insert doc states its rule instead of a caller list; Pathspec::matches names the crate-private policy items instead of linking them, which removes five cargo doc warnings. declined: compile_fail,E0624, measured inert on the pinned stable toolchain (a probe edited to E0599 stayed green) and honoured only under RUSTC_BOOTSTRAP=1, the reason written at the probe. red first: the new pins failed before the fix (resume 21 passed, 2 failed; attestation_machine_shape 3 passed, 1 failed; apply_patch 12 passed, 1 failed); with the fixes undone in two rounds, the in-progress note, apply-patch copy, apply advice, re-encoded role and hex naming redden the census (1267 passed, 1 failed), apply_patch (12 passed, 1 failed), attestation_machine_shape (2 passed, 2 failed) and resume (21 passed, 2 failed), and the fold re-encoding, the old wrong-verb sentence and the old merge sentence redden the codec pin (158 passed, 1 failed), the census (1267 passed, 1 failed), attestation_machine_shape (3 passed, 1 failed) and resume (22 passed, 1 failed). no migration, no wire or format byte moves, and no forge or relay byte moves, so this owes no deploy. the workspace suite ran 3917 tests over 122 binaries with 7 ignored, and all passed but concurrent_stage_of_same_address_does_not_tear, an os error 5 in untouched persist_codec code that passed on a loot-core rerun (637 passed) and three times alone (#2052) d5fad7c8 · 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

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.