Changes touching this path

  • the relay contract is asserted once and both backends run it, the mailbox choreography moves behind a blanket impl a backend cannot override, and the fs and bucket acks stop disagreeing about which of them was right (#1164) eb659a91 · dbf3dbe6…
  • a peek costs one batched blob read instead of one per pending grant, the shared contract counts the round trips so neither backend can regress it, and the standing split is why the reads cannot leave (#1165) 204263a6 · dbf3dbe6…diff
  • a bucket-backed relay reaps, because the anchor is when the bytes last landed in the store and the store is what answers it, and the reap itself moves behind the seam so the standing exemption is one rule both backends run (#1166) 39e1e1eb · dbf3dbe6…diff
  • loot-relayd finds its verb wherever the operator put it and refuses rather than guessing past a flag it does not know, and the reap checks the answers it is about to zip instead of trusting an order nothing enforced (#1222) 76efc87f · dbf3dbe6…diff
  • the anchor refresh and the deposit race become shared contract rules every relay backend must answer, the reap report has one renderer instead of a drifted hand-copy, and a rule that is written but never asked is now a test failure (#1223) d31bf81e · dbf3dbe6…diff
  • the reap window becomes a struct a transposition cannot compile against, the reap-cost rule says at most three like the ADR it pins, the hand-rolled base64 gets its reason, and ADR 0070 admits its refinement in its Status block (#1263) ff6d4344 · dbf3dbe6…diff
  • loot gc --pack lands the write half, and the claim ADR 0083 deferred to this ticket is taken and comes back NEGATIVE: there is no size win and there cannot be one. a packed object is byte identical to the loose file it replaces, so a pack weighs its bodies plus 11 plus 44 per entry and the census total always RISES - measured through the CLI on a throwaway fixture, 12 objects in 11 buckets, 12094 to 12743 bytes. the groupings that WOULD compress are exactly the ones the ADR rejects as leaks, so the absence is the PRICE OF THE ANTI-LEAK RULE rather than an optimization nobody wrote, and the verb says so at the number instead of leaving a true pair that reads as a loss. the repack IS the collector: a rewritten bucket carries forward only the keep set, so an unreachable entry is simply not copied and there is no delete-a-span path to get wrong. what that leaves is a plain gc unable to collect packed garbage, and the hazard is the SILENCE rather than the retention, because nothing to prune - every stored object is referenced by a change is true of the files the pruner walked and reads as a claim about the store, printed at the operator; so a plain gc now OBSERVES it, one header and table read per pack, and prints what it could not reach beside the flag that reaches it. the census asymmetry is untouched: reclaimable_bytes stays a dry run of the plain collector, because a census agreeing with the collection is the whole reason it is trustworthy. the ranged read is implemented and it reintroduced a hazard the ticket never named - a table sized from a corruptible u32 is u32::MAX times 44, 188 GiB of prealloc - bounded now by the file, and the mutation removing that bound ABORTS the test process rather than failing it. burn still refuses a packed address and the remedy now exists and is pinned, since gc_keep_set already drops burned oids so the ordinary collector rewrites the bucket without one and no burn-shaped branch exists in the packer; it is deliberately NOT wired into destroy_objects, which runs from save taking a shared reference, because a bucket rewrite moves every OTHER member offset and the live index spans for the burned object innocent bucket-mates would go stale instantly - a destruction path that repairs itself by breaking unrelated reads is not the fix. crash safety is temp, read back through this module own reader, rename, caller unlinks, and the invariant at every instant is that every address being carried forward is readable from at least one complete file at a stable name, both of them in between; an interrupted repack is not a new failure mode but one of 817 torn writes, since the stage name is skipped by read_all and collected by the existing sweeper. two pins the mutation sweep caught as weak are fixed, one vacuous because a repo that put its own objects holds them in memory where the re-point deliberately does not reach, and one mis-named because its injected failure fires BEFORE the ordering it claimed to prove; the read-back-before-rename ordering has NO fault injection pin and that is said rather than invented, argued structurally from write_bucket having exactly one rename as its last act. and the verbs census attribution went stale a THIRD run running, caught in review here rather than after landing (#1525) bbbe984d · dbf3dbe6…diff
  • the blob-date window starts at a second the store clock has reached too, so a deposit at a second boundary stops reading as a neighbour date, and the assertion stops naming a cause it cannot tell apart. the rule brackets each of its three deposits with SystemTime::now either side, while a first deposit of new bytes is stamped by the clock behind the file mtime, which trails that one: land inside the trail and the blob is dated one second BELOW a window one second wide, printing the number the neighbour deposit would print, which is why Test - landed main went red at two commits of the 2026-09-16 run with every recorded pair collapsed to before == after. reached deliberately first, as the ticket asked, rather than fixing an unreproduced cause: with a_distinct_second spun to the boundary instead of polled at 20ms, the rule goes red on this windows desktop in the exact CI shape, 9 of 10 runs on the first pass and 10 of 10 on the paired one (0 passed, 1 failed, at position 1 and at position 4), and under that identical amplifier with the fix in it is 10 of 10 green. the fix is NOT a tolerance, which the ticket forbids and is right to: the deposits are a second apart, so a one-second slop would admit the neighbour and delete the property the rule exists for. the window start is a clock read blocked until it is 50ms into its second, so a trailing clock cannot still be in the one before, and that covers the strictly-increasing arm of the same rule and the one-sided arm of a_repeat_deposit_moves_the_anchor_forward, whose soundness rested on the refresh path happening to stamp from SystemTime::now. the granularity is recorded rather than left to be re-derived: a throwaway probe outside the tree stamped a boundary write in the previous second in 232 of 300 rounds, worst trail 1014us, and no mtime came out ahead of the read after the write, which is why only the lower end takes the margin; restore.rs already records 15.6ms for the clock behind a windows file stamp, the reason for the difference was NOT determined, and the margin is set past the coarser of the two. linux is not measured here and the doc says so, since that is the platform CI failed on. the reap rules that compare an idle number carry SLOP 5, which a one-second trail is already inside, and the bucket arm cannot undershoot at all because the fake stamps from this process own clock. with the fix undone and no amplifier the rule is 10 of 10 GREEN here, which is how 51 lands went by. no migration, and nothing outside a shared test module moves, so this owes no deploy. the workspace suite is green over three consecutive runs (3863 passed over 120 binaries, 7 ignored) (#2038) a4942de6 · 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.