Changes touching this path

  • verbs/: seam scaffold + the repo family (init/clone/config) (#661) 28849eaa · dbf3dbe6…
  • verbs/grants: the mailbox family (grants/pull-grants/purges) (#661) faaacc8f · dbf3dbe6…diff
  • emit_snapshot calls verbs in-process; test-support feature retired (#661) 8033f451 · dbf3dbe6…diff
  • the relay's grant mailbox authorizes before it parses, and a read addresses only the caller's own (#621) 4b349b72 · dbf3dbe6…diff
  • the grant lane is fixed-width and acked, so a deposit frames no length and a read destroys nothing (#818) Closes #818 and #816, deliberately together. Both are hard wire breaks on the same two endpoints, and #818 said so itself: landed separately it buys a second relay-before-clients redeploy for a shape improvement. Landed as one, the grant lane breaks once and comes out uniform. #818 — the deposit body is `[recipient pubkey 32][grantor envelope...]`. `split_deposit` is still the only thing that runs before authorization, and it is now a constant-size slice: no length field, no UTF-8, no hex decode. This was never a vulnerability — the old length was attacker-controlled but correctly bounds-checked, and #621 already moved `Frame::decode` below the gate. What it was is a shape every future reader had to re-verify, and a "the grantee is a key" rule enforced by a validator that a later well-meaning change could relax. A fixed-width field cannot be relaxed. The mailbox index stays hex-keyed on disk, so the relay now mints that hex instead of parsing the caller's spelling. #816 — `/pull-grants` returns the due blobs and keeps them; a new `POST /grants/ack` names the content addresses the caller durably applied and drops exactly those. Handing bytes to a socket was never proof of receipt: the connection can drop, or `apply_sealed_grant` can fail after they arrive, and the grant was then gone from both sides. `loot pull-grants` acks only what it applied or quarantined — anything that hit a `skipping` branch stays pending, or this would be drain-on-read wearing an extra round trip. An ack is scoped to the acker's own mailbox, and since blobs are content-addressed a shared file is unlinked only when no index entry anywhere still refers to it. This also shrinks #621's accepted replay cost: a replayed read envelope now re-reads bytes the holder already had instead of destroying them. Not a format major, in either half — store and bundle formats are untouched and the on-disk mailbox index is unchanged, so a relay carrying pending grants keeps them across the redeploy. Recorded as the second amendment to ADR 0015 (2026-08-07), with CONTEXT.md's grant-allowlist, grant-discovery and forge entries corrected to match; the forge lost one of its four named differences from a relay, because the relay adopted it here. a5e2ffcf · dbf3dbe6…diff
  • a lane carries its own hold, and custody crosses back whole at finalize (#811) 941cafb9 · 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
  • a push deposits this identity's own keys: a standing self-grant carries Restricted custody to the second machine, so a same-identity clone opens what it authored (#980) 684f37cb · 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
  • the forge serves the peek it always served the fetch for: /grants/peek mounted on both mounts, and a peek now says how much of its count is standing self-custody instead of inviting the ADR 0057 alarm (#1114) cc003626 · dbf3dbe6…diff
  • the peek stops telling a fresh second machine there is nothing to receive, the glossary catches up to #1114, one predicate decides standing self-custody, and the shared warn gets its provenance back (#1120) e9544471 · dbf3dbe6…diff
  • pull-grants stops naming the relay for a host that may be a forge: kept there, left pending there, and the ack failure names the URL it missed (#1122) 382441ba · 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
  • deleting a named read on Workspace stops being free, because the 377 places that reached past it into the engine and the store now go through those faces instead — repo() no longer exists, store() is module-private, and the three extracted children share their fixtures by name rather than through the parent's private test module (#1411) c454db04 · dbf3dbe6…diff
  • loot seek answers for a loot repository on a relay or a forge without a working tree and without its bodies, and does so beside #2043 rather than behind it: a loot host, named by a loot+ URL, by an http URL whose info endpoint answers, or by a bare word the ambient repo config resolves as a remote name, is read through a body-less cache position, an ordinary loot store under the cache home that copies the ambient repo keypair, or the one --identity-from names, and never mints one, because a fresh key is 403 at a forge and reads only Published on a relay. each invocation refreshes it with the metadata half of a pull only, the closure-declared fetch every bounded pull starts with, ingested at depth one and with no wants round, through the routed transport a pull already uses, and collects a forge standing self-grants as clone does. a key rides beside its ciphertext, so a body-less change carries no key: a listing gates on the tier the tree records, Internal listed and Restricted or a live Embargo counted sealed, and the real key gate decides after a search or a read has fetched the listable bodies in one batched round, only those the store does not hold. the store already tolerates withheld bodies and this verb never reads one it has not fetched, so the fifth kind of absence #2043 would name is never classified here; what #2043 still owes stays its own. the workspace gains the two thin seams, pull_metadata_via and fetch_objects_via, and a holds check the engine now exports. ADR 0090 records the decision the map left to #2043, in docs/adr where a decision lives: the cache directory is this verb alone, the private key copy is refused for a sealed source and a failed make leaves none behind, offline is a refusal never a stale answer, and grants are collected without acking so a peer grant is never consumed into a cache. the two-axis review before landing put the pull doc back on its function, made the listing gate Internal-only because an embargo key has no bundle lane even after its instant, refused a multi-head remote without --at, excluded burned objects from a prefetch, probed the host once per invocation, made --identity-from a plain word read from the process directory, split collect_grants out of pull-grants with the ack a choice, folded the loot loop into loot_answer beside git_answer with one row push, and pinned the refresh closure declaration, the cache object store through the binary, and a remote name resolving to the same cache. the unit tests pin the seams over the in-memory relay, the tier gate, the object fetch that brings only what was asked and declares the closure, a refresh that sees a later change at depth one, and the cache over a spawned relay carrying the source identity; the spawned-binary pin pushes the fixture to a relay spawned in the test, holds the remote listing to the local one by path and visibility with the sealed path withheld and counted, checks the cache identity is the repo's, reads a body verbatim, refuses from outside any repo naming --identity-from, and answers with it. red under mutation: the metadata pull asking for every object (0 passed, 1 failed), held bodies fetched again (0 passed, 1 failed), the object fetch declaring nothing (0 passed, 1 failed), an embargoed path listable without its body (0 passed, 1 failed), the cache minting an identity (0 passed, 1 failed), a second open making a second position (0 passed, 1 failed), and the refresh skipped (0 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 is green (3972 passed over 123 binaries, 7 ignored) (#2092) 669baef4 · 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.