Changes touching this path

  • day 0: loot hosts loot f4c30e75 · dbf3dbe6…
  • evidence: crew minted and verified (#86) 1fada823 · dbf3dbe6…diff
  • hard embargo engine/wire lands (#14, format v5) 7784bcac · dbf3dbe6…diff
  • normalize working tree to LF: byte-stable co-located bridge (.gitattributes -text) e58fdda6 · dbf3dbe6…diff
  • pull_via over a SyncTransport seam: the pull pipeline gets an interface and tests; negotiation uses complete heads (#217) 68836d58 · dbf3dbe6…diff
  • Relay: explicit 64 MiB body limit; push batches byte-capped (#309) 0ac2fa23 · dbf3dbe6…diff
  • Workspace accepts its clock; lane flag-gate goes pure so cmd_* tests never touch a real .loot (#322) 20ac82e1 · dbf3dbe6…diff
  • Grant expiry: optional expires_at on GrantEntry and tag-3 wire (#20) a01c3d53 · dbf3dbe6…diff
  • loot-net: hint a relay redeploy when a format-version rejection means the relay is behind (#431) 7d2564ee · dbf3dbe6…diff
  • loot remote info + relay GET /info endpoint (#10) 0d05d373 · dbf3dbe6…diff
  • Rename the Public visibility tier to Internal (ADR 0041 §2, #480) 6969626d · dbf3dbe6…diff
  • Say what a relay can actually read (#520) 11055d72 · dbf3dbe6…diff
  • Let the relay report the port it bound, so no test hardcodes one (#569) 2b7db5d3 · dbf3dbe6…diff
  • relay: count the bytes a push received and report them in the push response (#633) 2dc9f961 · dbf3dbe6…diff
  • Sweep the rust-1.96 clippy debt; document the land-holds-the-binary hazard (#667, #681) 26cfbaa9 · dbf3dbe6…diff
  • push negotiates the change lane against the relay /haves so a small land ships its delta, not 7 MB of re-sent history (#728) 2df34147 · dbf3dbe6…diff
  • the relay expands a pulling client's declared heads, so a one-behind pull ships its delta and not the whole change lane (#734) e3f7604c · 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
  • 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
  • the reader names its own holes: the completeness filter and Have retire, and pull reports what never arrived (#803) f15d576e · dbf3dbe6…diff
  • info names the build, not only the wire format: a deploy becomes verifiable from outside (#922) ffe01788 · 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 format-skew refusal tells the caller the host is behind and their repo is untouched, and scopes the redeploy to whoever operates the host (#1136) 74f719c6 · dbf3dbe6…diff
  • the format-skew rationale states the measured position: an allowlist of one refuses a stranger before any format check, so skew changes which refusal they read, and the land verdict's relay=FAILED was the detector that was always there (#1200) 8a623333 · dbf3dbe6…diff
  • the skew this ticket reported never happened, and the trap that produced the report is the finding: a land pushes with the loot.exe sitting beside the running loot-first.exe, which is the primary's last cargo build --release, not the tree being landed - the only build a land performs is the perf gate binary in the position. So the land that RAISES FORMAT_MAJOR pushes with the major before it, and reading the constant out of main's source is not reading it out of the pusher. Verified by timeline rather than argued: both pushes ran at 23:26 and 00:08 while the primary's binaries date from 01:28 the next morning, so the pusher predated all three lands and wrote major 11 to a relay serving 11. Two further claims in the ticket were false and are refuted by the tree rather than reasoned away. stow DOES version-check, at the frame decoder before a single object is stored - handle_stow to RelayStore::stow to DagRepo::stow_from, whose first statement is Frame::decode and whose UnsupportedFormat maps through NetError::Engine to a 400 - and it was already pinned by a green test when the ticket was filed, so the acceptance criterion asking for it was asking for something that shipped. Which means the observed 200 and its byte count are themselves proof the marker was readable, since StowReport is constructed only after stow returns Ok and put_version writes the compile-time constant unconditionally. What survives is worth landing on its own merits and not as this incident's cause. The fail-open branch stops being silent: relay_can_read_our_writes returns Checked or Skipped carrying the probe's own failure string, push returns that beside the report, and cmd_push prints a warning to stderr where the refusal would have stood - a value on the result rather than a log line, because the broken pairing was a promise asserted by the path that fires and inverted by the path that does not, and a caller may decline to say a returned thing but cannot fail to be told it. The new test is the first that watches the wire at all: every prior skew pin simulated one by moving the CLIENT to FORMAT_MAJOR plus one, a value production never has, and none of them observed a byte - this one stubs a relay that reports a chosen major, asserts the request log is exactly the probe with zero bytes sent, and carries its positive control in the same function so that no bytes recorded and this harness cannot record bytes stop being the same observation. Two mutations were each verified present in the file before being run red. And the doc comment on relay_push now names the trap rather than the code, because the code was correct every time (#1768) 25a5a122 · dbf3dbe6…diff
  • a relay with a push allowlist now gates its reads on it too: every relay read route was unauthenticated while every bundle carries the key for every Internal object (ADR 0011), so an allowlisted relay.millerbyte.com served this private repo to a stranger key in plaintext (#2388). a route is a read when its answer comes from the store, and the store now sits behind Hold, whose only doors are a gated read and a gated write, with the router built from one match over Route that has no wildcard, so a later route cannot answer from the store ungated; the reads today are /negotiate, /offer, /fetch, /wants and /haves, /info stays the one open probe, and the grant lane is unchanged. with no list a relay stays open and unsigned, and loot serve and loot-relayd now warn at startup that anyone who can reach it can clone everything, Internal content included. /info advertises authenticated_reads and a client signs its reads iff it is advertised, so a new client reads an old relay unsigned and an old client gets a 401 naming the upgrade; loot seek records the posture and the SDK signs its reads the same way. no format constant moves. ADR 0011 and 0015 amendments, CONTEXT.md, the forge spec, sdk README and the site cli, guides and concepts pages say it. red under mutation, counts read: gate never on (the stranger clone succeeds), gate admitting any valid key, the 401 without the upgrade wording, /haves filed as open, /info not advertising, client never signs, client signing whenever a list exists, the startup warning dropped, the SDK never signing; each restored to green. workspace suite 4539 passed over 141 binaries, 13 ignored; SDK 145 of 145; site gate green (777). owes a release before loot serve users get it; the live relay stays stopped (#2389) f6b5ecc7 · 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.