Changes touching this path
- day 0: loot hosts loot
f4c30e75 · dbf3dbe6… - evidence: crew minted and verified (#86)
1fada823 · dbf3dbe6…diff - catch up: GB1 loot ferry lands (PR #114)
6816f50d · dbf3dbe6…diff - normalize working tree to LF: byte-stable co-located bridge (.gitattributes -text)
e58fdda6 · dbf3dbe6…diff - Workspace accepts its clock; lane flag-gate goes pure so cmd_* tests never touch a real .loot (#322)
20ac82e1 · dbf3dbe6…diff - loot id rotate: new keypair, expiry-preserving re-grant wave, archived old key (#16)
2b16256f · 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 - SDK/WASM cross-session grant delivery: pull queues, accepting applies (#508)
e0bb183e · dbf3dbe6…diff - a browser-minted identity reaches the CLI: loot id recover decodes the 24-word phrase to the exact seed, and a shared vector holds both implementations to the same bytes (#926)
fd153402 · 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 - the unlock session holds the seed sealed under this repo's own ciphertext instead of the passphrase you typed, so a synced config directory leaks a window and not a secret you reuse elsewhere, and the passphrase is zeroized from the moment loot owns it (#1173)
1943d2c7 · dbf3dbe6…diff - loot id phrase cuts the key for the recovery door loot id recover already opened, issuing the 24 words for any identity ever minted with no derivation change, and refusing outright when stdout is not a terminal because those words are the private key
8bd665a5 · dbf3dbe6…diff - loot whoami stops handing you a paste-ready peer add for a name and key it cannot vouch for, because binding a peer's nickname to the wrong key seals every later grant to the wrong identity (#1390)
bffbe094 · dbf3dbe6…diff - the relay mailbox takes the door the relay already writes objects through, and the keypair archive reports a held file rather than waiting it out: #2026 routed the object store loose rename through store::rename_retrying and left two sites its census named, and the question here was where that helper should live. it does not move. the mailbox needed no export at all, because store::atomic_write is already public and loot-net has called it for every loose object since storage::write_loose_object was written, so save_index and write_blob now stage through that door and both ends of their renames are waited out, the reader holding the index it replaces and the scanner holding the staging file, and the fixed index.tmp that two concurrent writers shared is gone with it. the wait is spent on the host too, where rename has no sharing window and the refusals that predicate answers to are permanent, and that is accepted rather than gated on cfg(windows): a second spelling of one predicate is the shape #565 is the record of, and the relay has paid this on every loose object all along. loot-identity cannot reach the helper and the helper cannot come to it, since that crate depends on no workspace crate but loot-codec, which is the no-fs wasm core a filesystem retry loop is defined not to live in, so the choice was a second copy or a report. it reports, and on its own terms: a store staging rename waits out a scanner passing over a file microseconds old, where what is held here is a long-lived keypair file whose holder no budget outlasts, and a rotation is one operator-driven act where a refusal costs a re-run. what makes that safe is the order, so id.pub now moves before id and a refusal leaves the private key in the active slot, where archiving id first left a repo whose key had moved and whose re-run met NoKeypair; the refusal names both paths. the census loses its two mailbox rows and its blind-instrument guard stops reading a file that renames today, asserting instead that every crate directory the tree declares is in the walk. red under mutation, counts read each time: the index save put back to a bare rename (loot-net mailbox 20 passed and 1 failed), the blob write put back (20 passed and 1 failed, refused with os error 32 on the source), the archive order restored (loot-identity 3 passed and 1 failed, the private key gone), the paths dropped from the refusal (3 passed and 1 failed), a retry loop given to the archive (3 passed and 1 failed, the rotation waiting the hold out and returning Ok), a bare rename put back where the census cannot see it (store_rename_census 3 passed and 1 failed) and the walk cut to three crates (2 passed and 2 failed). ADR 0016 records the archive decision and store.rs records where the helper stays. no migration, no wire or format byte moves and no schema moves, but the relay binary changes on its own write path, so this owes a relay redeploy. the workspace suite is green (4083 passed over 130 binaries, 8 ignored) (#2112)
8b194a68 · dbf3dbe6…diff - a refused keypair archive puts back the half it moved, and the cut that blanks amendment records stops blanking half the document: #2112 made the order safe and left the re-run broken, because the suffix is a tag the two halves share and the search wants both names free, so a refusal at the second rename orphaned the public half under rotated-ts and sent the next run to the next suffix, handing back a path nothing had written. archive_keypair now undoes its own first rename before it returns, so the run after it meets the repo the refused one met and archives a pair under a tag of its own, which is what a re-run at a later second needs, and where the put-back is itself refused the refusal names where the public half was left; the archived public path comes back as None where there was no id.pub to move, so what it hands back is where a file is and not a name it picked. the amendment cut now asks for a date, the date being the whole reason an entry is not read: ADR 0066 opens with an undated Amended inline header and the reader blanked from there to the end of that quote, 482 lines of 953, taking the index of the amendments and the claims around it with no guarded phrase lost and nothing to say so. its control is new and reads the other way, walking every blanked line up to the header it stands under and spelling that header itself rather than reading the constant, so a widening from either end arrives red naming the lines and the guarded phrases on them, where the old control was green over half a document and the per-row found check fires only when the cut takes the last copy of a phrase. ADR 0066 stops pointing the reader at the newest entry, which is blanked, and says instead that the figures in that block are the ones held against the code, and what makes a block an entry. use_items stops naming one cost for two callers: a false item is refused in the resolver only where it also spells the module name, and is blanked in the call walk, which is the #1946 direction in the reading written to close it. the judgement items: both sides unterminated over a line they share and an empty file in each direction are pinned through the binary, the empty-lines arm in the marker test is gone with its reason moved onto the expect that ends_clean makes true, a name bound to two types three lines apart in the apply path is split, and the walk guard in store_rename_census reads the workspace members instead of the crates directory it already walks, asserted both ways. red under mutation, counts read each time: the put-back removed (loot-identity 48 passed and 1 failed, the refusal leaving id.pub.rotated-7 orphaned), the public path returned unconditionally (48 passed and 1 failed), the date requirement removed (loot-cli bin 0 passed and 1 failed, naming line 15 and 103 lines), that same defect with the new control stood down (1 passed and 0 failed, which is what shipped), the cut never leaving the quote (0 passed and 1 failed, naming line 197 and every guarded phrase the rows carry), the member list read short (store_rename_census 3 passed and 1 failed), a member declared outside the walk (3 passed and 1 failed), the empty-side carve-out removed from ends_clean (patch_trailing_newline 3 passed and 1 failed on the expect), and the marker withheld from a kept line (3 passed and 1 failed). ADR 0016 records the put-back in a new entry rather than rewriting the one that was wrong. the deploy sentence #2112 landed is narrower than that change, and that correction is a record posted on the ticket rather than code here. no migration, no wire or format byte moves and no relay or forge behaviour moves, so this owes no deploy, though the client rotation path moves and rides the next release. the workspace suite is green (4086 passed over 130 binaries, 8 ignored) (#2169)
b17b2955 · dbf3dbe6…diff - the stale narrowing moves to the enum that defines the set, and the plaintext census stops asking its constants what the store holds: pinned.rs still said the opener refuses a directory git cannot name a revision for, which is the vocabulary of the round before #637 gave that door a loot arm, and #1895 had just pointed the corrected loot-perf-cli header at this paragraph as its argument, so the module header now names no arm and sends the reader to Names, the door doc says what the accepted set widens with, and the struct doc and the refusal beside it follow. the binary header stops calling the door the only constructor of the type a measurement is taken against, a claim about a set the module can grow with one function and nothing derived behind it, and states privacy instead, which the compiler holds: the fields are private to pinned, so a value cannot be made out there at all. the census clock window opened at the first redaction, which runs after the fixture has built every repo, so the seconds it redacts sat below the lower edge and only the slack held them in; it now opens at the span the fixture records itself writing in, frozen once so both worlds redact through one window, and the slack is stated as paying for skew between two clock reads rather than for build time. the same census read green over a fixture whose varied path never reached the store, every planted control still catching its oracle because an oracle is planted from the recorded constants; the premise is read back per build through the repo door now and compared on the bytes that come out. the keypair archive record stops making a later second part of why a re-run works, since ts is a whole second and the put-back frees the tag, so a re-run inside the refused second takes that tag back, which is what the test exercises now; the branch where the put-back is itself refused stays unexercised and that deviation is recorded at the code with what makes it unreachable from a fixture. adr 0066 stops answering how many today with the figures in one block, because the verb-line figure the census holds stands outside it, and says instead that what decides is surviving the cut. red under mutation, counts read each time: the fixture slowed by seventy seconds between the worlds under the old window (census 4 passed and 2 failed, the value rows naming one unix second against eight redaction bytes), the window start taken at the reading again (6 passed and 1 failed), the capture stopped from recording the varied path before the new reading existed (6 passed and 0 failed, every control green) and after it (6 passed and 1 failed, naming b.txt), the put-back removed (loot-identity 48 passed and 1 failed), the suffix search started past the refused tag (46 passed and 3 failed, the resume test naming rotated-7-2), and the amendment date requirement removed, re-measured on the landed tree (loot-cli bin 0 passed and 1 failed, naming line 15 and 111 lines where the landed record says 103, that document being 952 lines before this land and not 953). no migration, no wire or format byte moves and no host behaviour moves, so this owes no deploy. the workspace suite is green (4093 passed over 132 binaries, 8 ignored) (#2179)
19bc2ec4 · dbf3dbe6…diff - every land now states which versions it published that the lander did not write, and refuses nothing - ADR 0092's detection half, part 1 of 2 of this ticket. a version is foreign when its author is not in the lander's own key set, which is new: loot_identity::own_signing_keys reads the active id.pub plus every id.pub.rotated archive that loot id rotate writes, exposed as Workspace::own_signing_keys beside author_pubkey rather than instead of it, because the two answer different questions. carry_line's Foreign asks may I RE-AUTHOR this and only the current key can sign, while the gate asks did I WRITE this and authorship is historical, so a rotated operator's own change is foreign to the carry and theirs to the gate and both are right - each site now names its own question and points at the other, since unifying them breaks a caller in either direction. the judged set is the ancestor closure of the landed change minus the closure of the main it was judged against, read BEFORE the pass because by verdict time main already includes the landed change and the difference would be empty on every land, and it is the whole closure rather than carry_line's first-parent chain because a stack carrying a merge of its own would otherwise have one side waved through. an absent author is foreign, which is carry_line's answer too, so the two predicates disagree only about which keys are mine. the count rides the verdict line as foreign= and a block names the versions, since the count says that and the next question is which. foreign= sits immediately BEFORE pusher= and not after it, which I got wrong first: pusher= carries an arbitrary percent-encoded path and is designed as the tail, workflow.md says the path comes last, and five pusher tests read it with ends_with - appending after it broke exactly those, while inserting before it breaks only a fixed-index reader and the repo has none, and those five pass unmodified. the key set reads only id.pub.rotated and never the private id.rotated spelling, which holds a secret key and has no business answering a question about authorship. a machine that pulled rather than rotated holds no archive, so there your own pre-rotation work reads as foreign, which fails toward asking for an approval nobody owed and never toward waiving one, and the block and workflow.md both say so. red under mutation, counts read each time: the key set ignoring the archive (52 passed and 1 failed), the filter admitting the private archive spelling (52 passed and 1 failed), and an unauthored change read as the lander's own (202 passed and 1 failed), each restored to 53, 53 and 203. ADR 0092's status now says which sections are built, because section 5 - the gating column, review/approve and the refusals - is part 2 and until it lands detection reports and nothing refuses, which is that section's decided default anyway. no migration, no wire or format byte moves, and nothing on a host moves, so this owes no deploy. the workspace suite is green (4189 passed over 133 binaries) (#2178)
8cb77a56 · 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.