Changes touching this path
- day 0: loot hosts loot
f4c30e75 · dbf3dbe6… - evidence: crew minted and verified (#86)
1fada823 · dbf3dbe6…diff - normalize working tree to LF: byte-stable co-located bridge (.gitattributes -text)
e58fdda6 · dbf3dbe6…diff - S2: reconcile the verb surface — read-only status, columnar log, eager change ids (#145)
Reconciles the daily verb surface to ADR 0030, consuming S0's durable
change_id (#143) and S1's implicit snapshot (#144).
- status is now READ-ONLY: it recomputes the pending delta live and never
persists a snapshot; -m is dropped (naming is describe's job). It shows the
working change's durable change_id alongside a live, non-durable version id
(a plaintext content fingerprint, distinct by construction from the sealed
snapshot id — Seam #1).
- new is the finalize/sign boundary and eagerly mints + prints the *next*
change's durable handle, so a fresh change has a name from birth. Adds the
convenience `new -m`. init mints the first change's handle too.
- log/status go columnar: change · version · message · vis · author, with the
change id as reverse-hex LETTERS and the version id as hex DIGITS (ADR 0029)
so the two ids disambiguate at a glance. The working change is rendered once,
as a live row shared with status (log agrees with status).
- status porcelain/JSON gain an `@` change-identity header carrying change_id
+ version id (hex); the ~ path rows and status chars stay a frozen contract.
Engine: snapshot_assigning carries an eagerly-minted handle onto a fresh
change's first version; working_preview computes the live, non-durable version
id + emptiness without sealing or recording; mint_next_change_id gates on
authorship. Store persists a per-dock next-change handle.
bd5d6a8f · dbf3dbe6…diff - Merge pull request #159 from Connor-Miller/s2-verb-surface
Git-Author: Connor Miller <53197564+Connor-Miller@users.noreply.github.com>
42d3411bdiff - R4: buoy's frozen machine contract gets a tested encoder home (ADR 0025, #180)
13bba560 · dbf3dbe6…diff - review fixes: retire with_repo, collapse forwarders, reconcile_* renames, fork-view naming (#177/#178/#182 + code-review)
b071df66 · dbf3dbe6…diff - Spawn DevX (#232): loot lanes observability, --ticket lane spawn, wayfinder claim-to-lane
f0a885c2 · dbf3dbe6…diff - Workspace accepts its clock; lane flag-gate goes pure so cmd_* tests never touch a real .loot (#322)
20ac82e1 · dbf3dbe6…diff - TS SDK slice 6: physical mode openRepo over the shared LootRepo interface (#428)
The second backend: openRepo(path) drives an on-disk .loot/ checkout by
shelling out to the installed loot binary, returning the IDENTICAL LootRepo
the in-memory mode defines — so calling code is backend-agnostic. The binary
owns all crypto/codec; physical mode adds none (no WASM).
- CLI (sanctioned tiny machine-output addition, #428): `loot surface
--porcelain`/`--json` emit the current readable tree as path+visibility
(loot-core `verdict::surface_{porcelain,json}`), so `list()` never scrapes
human text. An empty repo is an empty tree, not an error.
- sdk/src/physical.ts: openRepo → PhysicalRepo via child_process. list()
parses `surface --json`; read() streams the materialized file (a real byte
stream) → NotFound on ENOENT; edit/remove write the working copy
(capture-first) and record a client-side overlay so status/diff report kinds
(added/modified/removed) against a committed baseline captured at open +
refreshed on push — loot folds a described change into the current tree, so
surface alone can't tell add from modify. describe/push shell out
(`describe -m`, `new`); guards map to --allow-demote. Errors map to the
shared taxonomy (missing binary → setup error; parent-moved → ConflictError;
non-repo → NotFoundError).
- Seam: `runReadContract` extracted to sdk/test/read-contract.ts and now runs
VERBATIM against BOTH backends (connectRelay and openRepo) — the proof they
are interchangeable behind one interface. physical.behavior.test.ts adds a
write round-trip + error surface. 32 SDK tests; loot-core/loot-cli green.
Deferred (documented): physical private-visibility authoring (a .lootattributes
rule) and pull-with-remote behavior — public content covers the AC and the
in-memory backend covers private.
Refs #428, #422.
9d12a388 · dbf3dbe6…diff - CLI: thread typed engine errors through Workspace so slugs travel (#430 follow-up)
#430 laid the CliError plumbing but the taxonomy stayed inert on live verb
paths: workspace.rs methods returned Result<_, String>, stringifying RepoError
before the verb's `?`, so every engine variant collapsed to code "error" at the
--json boundary. Convert that boundary: impl Workspace / Snapshotted / lanes
methods return Result<_, CliError>; engine (self.repo.*) sites use
.map_err(CliError::from) so code() is preserved; the MisSeal/SealWip guards
propagate the typed RepoError instead of .to_string(). Non-RepoError sources
(io, format!, bare strings) stay code "error", messages byte-for-byte intact.
Workspace::open/init stay Result<_, String> (open_repo() stamps the CLI-level
no_repo slug), keeping the loot-first ripple to two boundary conversions. New
emit_snapshot test proves a real RepoError::UnsupportedFormat reaches --json
stderr as {"code":"unsupported_format"} on `loot apply <garbage>`, with the
non-json `loot: <message>` line unchanged.
Also: share loot_core::verdict::json_string (drop error.rs's byte-identical
twin) and collapse the emit_snapshot run_with_code/run_stderr_with_code helpers
into one run_streams. Known residual: the ferry.rs bridge path still flattens
engine errors to "error" (deliberate scope boundary — git2 errors ride its
String contract). cargo test --workspace green.
627897cb · dbf3dbe6…diff - Rename the Public visibility tier to Internal (ADR 0041 §2, #480)
6969626d · dbf3dbe6…diff - Publish: surface the `published` visibility token across CLI/porcelain/JSON/WASM/SDK (#481 refinement 1)
082ad335 · dbf3dbe6…diff - Stop shipping grantee nicknames on the wire (ADR 0015, #521)
55ba71eb · dbf3dbe6…diff - a lane carries its own hold, and custody crosses back whole at finalize (#811)
941cafb9 · dbf3dbe6…diff - an empty holder list means unknown, not nobody: a pulled Restricted path stops reading as a permanent local edit, and one engine rule answers for every content comparison (#1005)
65b6946c · dbf3dbe6…diff - a verb that takes no positionals stops being able to say so by claiming the opposite, because `NO_ARGS` now sits beside `NO_PATHS` and a census reads every verb invocation shape out of USAGE to find the ones still spelling nothing as open-ended — which is how `loot heads junk` was accepted 88 minutes after the same hole was closed at `loot cat`, and beside it `HEAD~n` ends where the revset lexer already ended it so a range can be typed at all (#1562)
fb140e48 · dbf3dbe6…diff - tag and buoy stop disagreeing about whether a landmark is live, and the disagreement turns out to have been built by a sentence that was true of the resolver and read as true of the verb: both module docs said buoy is untouched by construction, which holds for loot_core buoy resolve and is false of the verb the operator runs, so the fix is entirely in what buoy consults and resolve is not changed by one line. a retirement annotates and never withdraws - that was already decided rather than open, since ADR 0025's #1508 amendment calls a retirement advisory, and four recorded facts each independently forbid withdrawal: there is no trustworthy order to withdraw by because newest is topological precisely since authored_at is advisory and never an ordering input; the attestation log is keyed by change, key and name so re-attesting after a retirement is a no-op, which would make retire the one permanently destructive verb on an append-only lane by a route that deletes no byte; buoy trusts peer-registry union self, so under withdrawal any registered peer could silently un-name somebody else's landmark; and the result would be the mutable ref that ADRs 0022 and 0025 exist to reject. so buoy now reports the retirement it already resolved through, in all three formats, additively - the B row and the human answer line are byte-identical to their pre-fix selves and the porcelain contract pin stayed green through a revert that reddened three others, which is the additive claim proving itself. an un-retired control runs in the same test so both-mention-retirement cannot pass on a verb that always mentions it, and four independent reverts were each proved red including one dropping the trust gate, which surfaced a stranger's validly signed retirement. no record is deleted or rewritten, so #1508's AC3 stands and its pin passes unchanged; the false sentence is narrowed at both copies rather than deleted, naming resolve as its subject (#1611)
ac8ee01f · dbf3dbe6…diff - ahead and behind against a remote without pushing to find out, and the thesis constraint is met as a property of the REQUEST rather than as advice: the question carries an EMPTY PAYLOAD and every comparison is local. a relay is asked POST /haves with a zero length body, a forge POST /ref with a signed envelope over zero bytes, so a repo of one path emits BYTE IDENTICAL bytes to a repo of ten thousand and the question has no room to encode paths, object addresses, or even our own heads. the two endpoints that could are forbidden on this path and ADR 0021 now records why: /wants sends our object addresses, which are per-content identifiers, so a status in a loop hands the relay a per-path edit-frequency profile of ciphertext it cannot open - ADR 0083 refusal at a higher call rate - and /offer sends our head ids, which a push may do because a push is a CHOSEN act and status is not. the pin asserts the recorded path-and-body list SET EQUAL to exactly /info and /haves both empty, asserts the two recordings equal across two very different positions, and carries a positive control that a real push through the same stub records a NON-EMPTY body; mutated into the rejected design it goes red with /wants carrying a literal 32 byte address. offline is split POSITIVELY, the site gate SKIPPED-OFFLINE rule: only reqwest own is_connect and is_timeout may be called offline, and anything that ARRIVED - a refusal, a 404, a proxy page, a truncated body - is unusable, because those are different facts and collapsing them is how a guess gets reported as a measurement. the relay answered and holds nothing is a THIRD thing and reads as declared with a count. every unknown count renders dash or null and NEVER zero, so a machine that never reached the network cannot emit a number, and log --unpushed REFUSES rather than printing an empty listing, because a listing has no row meaning I could not ask and an empty one reads as everything is pushed. the asymmetry is stated rather than faked: unpushed is exact, being the same change lane a push would send through the same ancestor_closure, while unpulled is zero exactly when every declared head is held here and UNKNOWN otherwise, since a head declaration says what the tips are and not how deep they run. no FORMAT_MAJOR bump and the reason is recorded: ADR 0019 marker exists to prevent misparse of a durable or on-wire artifact, an unasked status emits byte identical porcelain, and the R row lives behind a flag that did not exist when the shape froze. no revset predicate either, because loot-revset is handed a GraphView and a KeyOracle and nothing else (#868), so a predicate answering over HTTP would put a network round trip inside revset::select and therefore inside grep and format-patch too (#1522)
2237a331 · dbf3dbe6…diff - the two shapes that graduated without a record get their amendments, telemetry status and off carry the contract field, and the comma-joined column gets one home: #1971 carries the ADR 0088 section 4 follow-ups and asks for each premise to be re-verified first, so each was read against the tree — ADR 0023 held no amendment for count-objects (#1523) or telemetry (#1658), telemetry status and off printed no contract field and the string dash where V3 says null, and the Verdict entry in CONTEXT.md still typed 17 of the 68 beside a roster where the dispatch table derives 32 emitting verbs and 51 prose-only ones; the ticket comment correcting item 2 is the later word and is taken as such, telemetry on printing report JSON for the same deliberate reason as show. ADR 0023 gains two dated amendments describing what shipped: the one-metric-per-mark census rows, and the name-keyed telemetry rows with the divergences frozen beside them. status and off lead their JSON with contract now and spell an unresolvable spool null, rendered by status_shape and off_shape, which take values rather than reading the config so all three renderings are assertable; show and on stay exempt because what they print is the report the sender posts, and the exemption is pinned over Report::to_json rather than over a verb, that string being what the endpoint receives. The shared flag column item 3 asks for is verdict::joined_col, the comma join with the dash when empty, and what defines membership is that spelling rather than a roster, so the columns spelled that way are its callers: blame, delta_shape, explain, heads, log, evolog, attestation_shape, refit_shape, seek, tag, both pipeline listings and lanes route through it and no frozen shape pin moved. The cross-shape census is declined with its cost recorded in ADR 0088 section 4, since a census over values needs a pub on each shape that #1553 refuses, one over a spawned binary reads nothing for a verb whose arguments it cannot supply, and its exemption table would be a hand-maintained list asserted as complete. Red under mutation, counts read each time: joined_col joining with a tab (loot-cli 1335 passed and 8 failed), the empty set printing an empty field instead of the dash (1324 passed and 19 failed, and loot-core 647 passed and 1 failed naming lanes_porcelain_rows_are_the_frozen_contract), the contract field taken off status and off (5 passed and 3 failed), the spool null spelled as the string dash again (7 passed and 1 failed), and a contract field added to the report the sender posts (6 passed and 2 failed), each restored. No migration, no wire or format byte moves and no host behaviour moves, but two leaves of client JSON move, so this rides the next release and owes no deploy. The workspace suite is green (4107 passed over 132 binaries, 8 ignored) (#1971)
4ef9c62b · 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.