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 - Liveness: one loot-core home for live/superseded/divergent/parked + the head partition (#216); CONTEXT.md vocabulary; rides along: cargo-dist release config (dist-workspace.toml, release workflow, 0.1.0 manifests) from the install-prototype session (loot-site map, #206)
3630469b · dbf3dbe6…diff - loot-first in Rust: land policy behind a Forge seam (#218) — new loot-first bin crate (ledger/forge/policy/orchestrator), in-process Workspace reads via loot-cli lib split, typed pr-map owner + shared ferry::WipState; every policy decide-tested against a fake forge. Build+test only; shadow-run, real land, and ps1 deletion remain operator-gated.
58c28888 · dbf3dbe6…diff - Extract loot-codec + wasm core; de-risk the in-memory SDK boundary (#423)
Slice 1 (the tracer bullet) is a multi-session build; this lands its
riskiest integration — the Rust→WASM crypto/codec boundary — proven
end-to-end, per the #381 "verify spikes before building" mandate.
Spike result (ADR 0040): zstd-sys will not build for wasm32
(needs clang for its C), while aes-gcm/blake3/ed25519/getrandom-js do.
So:
- Extract `loot-codec`: a no-fs, wasm-buildable crate holding the byte
format, sync-bundle codec, sealed content (AES-GCM + blake3), and the
leaf types (Oid/Visibility/RepoError/ChangeNode). loot-core depends on
it and re-exports every item at its original path — a pure relocation,
proven by the unchanged, still-green workspace (767 tests pass).
- zstd is an optional loot-codec feature (default on for the native host;
off for wasm). A new zstd-free `sealed::decrypt` single-sources the
AES-GCM step so native `open` and the wasm core share decrypt code;
public content is inflated host-side in JS.
- `loot-wasm`: wasm-bindgen exports (bundle decode, decrypt, blake3
address) over a pure native-testable `core`, plus the minimal diskless
identity (generate/fromSeed/publicKey) on ed25519 directly — not
loot-identity, whose OpenSSH/passphrase machinery is native-only (#383).
- Golden-parity harness (loot-wasm/tests/parity.rs) runs identical
assertions natively and under `wasm-pack test --node`, freezing
native-computed vectors; both green.
Remaining for slice 1 (next sessions): the TS package, fetch transport +
client-side path-scoping, host-side zstd, and the live-relay behavior
suite.
Refs #423, #421. ADR 0040.
491cf780 · dbf3dbe6…diff - loot-revset: query-algebra crate for selecting sets of changes (#394)
cdec28d1 · dbf3dbe6…diff - loot remote info + relay GET /info endpoint (#10)
0d05d373 · dbf3dbe6…diff - Forge service skeleton: /ingest, the sync surface, and CLI forge remotes (#502)
A new `loot-forge` crate and binary, sibling to the relay and never a mutation
of it: `loot-net` is a dependency of `loot-cli`, so a storage driver in the
relay would ship inside the released CLI binary (ADR 0041:53-57). The ban runs
one way -- the forge may use the other crates; none of them may gain a driver.
Every endpoint is envelope-authenticated, where a relay authenticates only
/stow. The one declared exception is GET /info: a client reads the capability
probe before it knows how to authenticate.
POST /ingest = envelope_sign(bundle || head_declaration), one signature over
both, so a genuine bundle can never be replayed under someone else's head
claim. CAS first; nothing is written before the generation compare. A lost CAS
answers with a 409 BODY carrying the current generation and head set -- behind,
not bounced -- so the client re-carries with no extra round trip.
/stow keeps relay parity and moves no ref, and consumes no generation either,
since a client reads the generation once and then stows N batches. It DEFERS a
change whose objects have not all arrived; /ingest REFUSES one, because a
declared head whose objects are missing is a repo no pull can complete. That
asymmetry is what keeping /stow is for.
Bundles are filtered PER OBJECT by the caller's access class, closing the leak
the ticket names: bundle_impl ships a key for every ANYONE-granted object, i.e.
every Internal object. Change metadata rides the second #494 axis and gates the
whole walk -- a ChangeNode carries the full tree, so shipping one while
withholding its bytes still discloses every path, address, message and author.
Validation before any write: author signatures, and a tree whose visibility
contradicts its objects is rejected. vis_tag is derived from the SealedObject
and never trusted from the tree, which is what makes content-addressed upserts
safe.
Forge purge policy: burn iff burner == object.introduced_by, recorded at first
arrival as introducing.author else the envelope pubkey; maroon read off
grant_log, global and exact. Both are judged before any object is stored and
outside the CAS transaction.
Repos are pubkey-addressed at /k/<pubkey>/<repo>, auto-created on first push,
and may_push holds iff the signer owns the namespace. resolve_remote is
untouched -- still no default host. CORS from day one.
Storage sits behind three narrow traits with in-process reference
implementations the whole suite runs against; the Postgres DDL they mirror is
checked in at docs/sql/forge-schema.sql and its driver is #516.
CLI: /info capability detection, the live head declaration off the Liveness
view, and a refusal to push a lane -- v1 ingests the primary position only.
verify_authored_change moves to loot-codec so the forge runs the identical gate
without linking the fs-hardwired engine.
Recorded in ADR 0041, the spec and CONTEXT.md: the forge stores no content key
but a published one, because filing the ANYONE key lane would let the server
read every Internal object, and the keystone is that only Published is
server-readable. The cost is stated rather than hidden -- a fresh clone of your
own Internal content from a forge is ciphertext you cannot open until #488
lands.
Closes #502.
300e06ff · dbf3dbe6…diff - Give the forge an operator channel, with one redaction rule (#522)
f9ca9f91 · dbf3dbe6…diff - loot tutorial: remove the temp tree on Ctrl-C (#513)
fa318ba8 · dbf3dbe6…diff - Forge storage drivers: Postgres metadata tier, S3 blob tier, and one conformance suite both must pass (#516)
f65970e6 · dbf3dbe6…diff - loot-perf: the harness crate with fixture, measurement, and record seams (#634)
ef9fd595 · dbf3dbe6…diff - loot-forge drains on SIGTERM and SIGINT instead of waiting out SIGKILL (#657)
0485a488 · dbf3dbe6…diff - loot-perf: the CLI tier — hyperfine-timed push, receiver-counted wire bytes, raw samples and load on the record (#635)
3588811d · dbf3dbe6…diff - the converge seam merges with a diff3 three-way, so two disjoint edits compose instead of one silently dropping the other (#790)
2fd16e7f · dbf3dbe6…diff - the metadata tier speaks TLS when the URL asks for it, and refuses rather than falling back to plaintext (#689)
adf57547 · dbf3dbe6…diff - the relay store goes behind a RelayStorage seam, the SigV4 driver moves to its own crate, and an object-store relay is a second deployable the CLI never ships (#38)
330df915 · dbf3dbe6…diff - 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…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-revset stops being a finished parser wired to nothing, because `loot log <revset>` and `loot shortlog` now read one door that tries the historic selector shape FIRST and only parses a non-selector word as an expression — which is what keeps `loot log <change-id>` working, since the crate reads bare words as hex while a change id is the letters k-z, so the grammar was never the strict superset its own docs and this ticket both claimed (#1495)
dd223bf0 · dbf3dbe6…diff - the harness stops measuring only what a run produces and starts measuring what it does, because the three work counters this run built and threw away are now one family in loot-count that a single during region reads whole, with logical object opens and disk reads kept as separate numbers since 2,000 of the first can cost zero of the second, and the family ships compiled out rather than as cfg(test) one-offs because cfg(test) reaches neither an integration test nor loot-perf and would foreclose the gate #1602 exists to add, at a cost measured at zero bytes in the released binary (#1600)
482a72c1 · dbf3dbe6…diff - the no-false-absence guard 1527 landed was BYPASSABLE, and the bypass was neither of the two escape routes its doc ruled out: buoy and bisect return from main before the notice single call site, so assemble measured the frontier and threw it away unprinted, and a plain loot buoy on a shallow position exits 2 with no buoy - a REFUSAL-SHAPED false absence, which the comment four lines up calls the shape an operator most needs the line for. the repair is SHAPE rather than a third bullet: main is now a two statement wrapper and the whole former body is dispatch(), which is the only expression in main that produces an exit code, so every exit code the binary emits is that call return value and a return anywhere inside it - any depth, any verb, ahead of the table or behind it - returns INTO the notice. a new early return is not a new escape route, it is the SAME route, and because nothing is enumerated nothing has to be maintained. that matters because the old doc argued unbypassability by ENUMERATING escape routes and closing each, which reads as exhaustive, is not, and failed on the first route nobody listed - the same shape as a hand written count beside a derived number, which this run watched go stale three times. the residual is STATED rather than enumerated away: a process that never returns through main, process::exit or abort or panic, which is a property of process TERMINATION and not of the verb table, so nothing a verb does BY RETURNING can miss the notice. a census pinning exactly one raw exit was considered and refused, because that walker own consumer count is itself a derived-and-pasted census and adding a consumer churns it. the failed enumeration is kept as HISTORY, marked as the thing that failed, with the shape argument replacing it, and a THIRD site carrying the same claim was found in CONTEXT.md glossary which the ticket never named. FRONTIER_WIDTH stops being a process global read by verbs that hold the authority: both callers now ask their own workspace, and the public spelling no longer compiles from outside the module, which is the narrowing made structural rather than promised. three pins that asserted nothing are gone. the re-encoding privacy pin is DELETED because no byte seam is reachable - the transport is a TYPED seam, so the test re-encoded its own recorded id vectors and asserted the codec is linear in their length, true for every possible input - and its doc now records what it did against what it claimed, with the criterion standing on the other two pins, which carry positive controls. the partition pin is read out of the RENDERED sentence now rather than from literal triples the constructor never saw. two mutations make the case by experiment: moving the notice back to the end of dispatch reddens the new pin while log and show stay GREEN, and re-adding the old partition body under a scratch name PASSES, so vacuity is demonstrated rather than argued. an eighth pin was written and then deleted, because a mutation proved the existing assertion already sits on the live case (#1828)
b5eb05df · dbf3dbe6…diff - loot-hygiene (#2128, map #2099 ticket 4): the land text gates move from loot-first into a crate with no dependency that builds for wasm32, re-exported as loot_first::hygiene so every gate row, checkout test and AGENTS.md path reads unchanged; a new crate rather than loot-codec because three consumers now read it, loot-first, loot-cli and loot-wasm, and the wasm one is the fact ADR 0076 §2 no-new-crate reasoning did not have. Corpus::from_entries files an in-memory tree under exactly the allowlist the directory walk applies, top-level files plus SOURCE_ROOTS filtered by TEXT_EXTENSIONS minus SKIP_DIRS and SKIP_PATHS, through one filing rule both constructors share, and a link resolves against the entry set with dot and dotdot folded, so a Worker with no directory answers what the land answers. CORPUS_CHECKS is the table a .lootpipeline check step names, keyed by the land gate names and pinned to them, holding the floor-free halves: the land refusals still refuse a vacuous walk first because a mis-rooted land must not read as clean, but a three-file repo is not mis-rooted, so a check answers about the files it was handed. loot pipeline now runs check steps in-process over one corpus walk per run, a finding is failed and exits 2, an unknown name cannot start and names the set and exits 1, and the ? deferral #2127 shipped this morning is retired the same day with the ADR 0023 amendment saying so. loot-wasm links the crate and a wasm-bindgen test runs the three checks from entries under node. Measured: the site wasm is 475667 bytes before and after, unchanged, because nothing exported calls the crate; native the three checks over a 1120-file synthetic corpus take 0.8 ms in release and 28.0 ms under wasm in node; through the release binary on this repo, roughly 1355 covered files and 20 MB by an approximate count, the three checks cost 160 ms warm against a 73 ms listing and 6.5 s on a cold page cache. Two lands worth of censuses read off their refusals: the destination allowlist row for the moved file, and no other. Five pins broken once by a named mutation, red at 0 passed 1 failed then green at 1 passed: from_entries skipping widened, link resolution made true, the table key misspelled, a finding made a pass, an unknown check made a pass; the fifth sweep needed a second pass because its first restore string matched twice and left the mutation applied, caught by the full run that followed. 66 test binaries green across loot-hygiene, loot-cli and loot-first. No wire, format or store byte moves, no migration (#2128)
858575e1 · 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.