Changes touching this path
- 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…
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.