Change Ledger
One row per change, not per commit. The pusher declares which versions are live; this walks their ancestry, newest first, and bands each change by what this server can actually read. Nothing here is merged, rewritten, or picked for you.
1 declared head · 1295 changes walked
- loot ticket new, loot ticket show and loot tickets ship as slice 1 of the native tracker (spec §12, map #2422): a new ticket module in loot-cli holds the layout (tickets/<id>, tickets/public/<id>, tickets/<group>/<id>, where an 8-letter k-z segment is always an id and never a space), the one file format of header lines, a blank line and a verbatim body, ids of 16 random bytes shown as 8 letters and minted again on a collision with any ticket at the read position, and causal order keyed on the space-stripped path, found by walking the change graph from the read position in descending generation to the change where each key first appeared, with a key only the working tree holds sorting after every recorded one. a read folds the tree at the harbor tip, this position and the working tree, so a lane sees a ticket a sibling landed without adopt and its own uncaptured one. new writes meta, the body and one waits-on edge per --on as plain files in the working tree and prints the id alone on stdout; the body comes only through --body-file, a file or - for stdin, with a leading byte-order mark dropped and CRLF made LF, and --title refuses a tab or a line break. show --json and tickets --porcelain are the spec §7 shapes, recorded in an ADR 0023 amendment; the JSON leads with contract as ADR 0023 has every --json do, the open form and the sealed {contract, id, sealed: true} alike, the spec field list naming the payload after it. a ticket this identity cannot open is an S row, and a field filter keeps it. --restricted-to and --published are left to #2428. red under eleven named mutations over sixteen runs, each 0 passed and 1 failed and restored: position keyed on the spaced path, the harbor tip skipped, a colliding id kept, the byte-order mark kept (once through the binary), a tab allowed in a header, an unopenable meta dropped, an id-shaped segment read as a space, labels folded add-only, a field filter dropping a sealed ticket, and contract left off the sealed form and off the open form. the verb census moves: README 87 verbs, 137 usage lines, ADR 0066 at 87 dispatched and 35 with machine output, MACHINE_OUTPUT, the placeholder census and the site verb list. cargo test green in two runs, 4576 passed with 13 ignored, and the site gate green at 819 passed (#2424)live head
- ↳ supersedes
bc3f8d0a· not stored here - Kept, not rewritten: a carry replays a suffix as a new version and the one it replaced stays addressable. That is the difference between a carry and a rebase.
1 readable · 12 sealed · 13 internal - ↳ supersedes
- every compiled-in and current-tense loot host moves to loot.build (map #2412, ADR 0099): loot --help names security@loot.build and https://loot.build/trust, ALPHA_PROMISE_URL is https://loot.build/install and the telemetry DEFAULT_ENDPOINT https://loot.build/api/telemetry, so the next release carries them; the site install one-liners, R2_BASE and HOP_BASE (https://dl.loot.build, https://loot.build/dl), the metrics SITE_SCOPE and own-host referrer, the disclosure links on trust, terms, privacy and known-issues, and the install, quickstart and guides pages follow, with the privacy and terms markers moved to 25 September 2026 and re-pinned since readers see the new address; README, CONTEXT.md (the forge door now forge.loot.build), the release checklist, site CI and test fixtures follow, and the two specs carry a note instead of a partial rewrite. the destination census gains build in WEB_TLDS, without which security@loot.build would be invisible to its bare-host clause, and exempts the two shipped lines that tld makes read as hosts (state.build, r.build()) by their exact text; ADR 0074 section 9 says twelve-entry to match. 37 lines still name millerbyte.com outside ADR bodies, evidence, research and scratch, each kept on purpose: dated history, millerbyte.com the site and its @millerbyte/ui tokens, the two noted specs, and relay-era tooling with no successor host, filed as #2438. census red with build dropped from WEB_TLDS, restored; site gate 819 passed and within budget; workspace suite 4559 passed over 142 binaries, 13 ignored (#2418)
- ↳ supersedes
b6948497· not stored here - Kept, not rewritten: a carry replays a suffix as a new version and the one it replaced stays addressable. That is the difference between a carry and a rebase.
1 readable · 44 sealed · 45 internal - ↳ supersedes
- the reap of runner deposits ADR 0091 section 4 owed is built, which closes the last half of this ticket: a runner mailbox is now PER REPO (migration 0026 runner_inbox, keyed repo_id and runner_pubkey and blob_address under 0017 binding), because the question that stopped the first attempt was that grant_inbox is keyed by recipient alone while liveness is recorded per repo, so a reap for one repo could drop a key another repo jobs still need and the standing walk, deduped by its own ledger, would never deposit it again. the operator chose scoping the mailbox over a reader that crosses repos or refusing one runner key in two repos, and the third option turned out not to be enforceable where it would have had to be anyway, since runner is keyed repo_id and pubkey and repo-bound so a forge serving one repo cannot see that a key is a runner elsewhere; a global unique index could, at the cost of making its refusal an existence oracle about another tenant repo. it is a second table rather than a repo column on grant_inbox because row security is per table and grant_inbox mailbox is the caller own verified envelope pubkey, which is what closes the read-anyone-mailbox hole, while an owner filing a key for a runner they registered is a different fact through a different door. WHAT IS LIVE IS THREE ROOTS, and a change ships its whole tree so a root manifest entries ARE its objects and there is no ancestor walk: the declared heads, open proposal tips, and the versions of UNFINISHED JOBS - the third is not redundant, since a push moves the heads and a withdrawal closes a proposal while a job made for that version still stands, and a pin holds it alone. history is deliberately not a root, because any change references it is true of every superseded object forever, so a reap rooted there keeps everything and does nothing, which is the failure section 4 describes. the door is loot-forge --reap-runner-deposits --repo owner-hex/repo, which lists and destroys nothing until --apply, the shape reap-grants has, because the row is the only copy of that key the runner gets; it names its repo and reaps no other, refuses --dev, and refuses a missing or malformed --repo. the decision of what is dead is one implementation over the trait and the store write is pure, the split ingest keeps. red under mutation, counts read each time: ten mutations through the policy and the reference store each went red at 1 passed and 1 failed with the pg stamp skipping, each restored to 2 passed - the three roots removed one at a time, a terminal proposal and a finished job counted as roots, a listing that destroys, history as the root, the mailbox read across repos, a deposit that is not idempotent, and a drop that ignores which runner it is for; the history mutation was TOO WEAK on its first attempt and is recorded as such, since an empty version list emptied the live set rather than widening it and so duplicated the heads mutation, and re-done as rooting at every change the repo holds it goes red one line further down at the assertion it is for. on a throwaway Postgres 18 the two mutations only the driver SQL can carry went red at 0 passed and 1 failed after an unmutated green arm, the DELETE no longer naming the runner and the INSERT no longer idempotent, each restored. bash ci/local.sh is green against Postgres 18 (4565 passed over 142 binaries, 13 ignored) with all three new conformance cases running on the real database, and the forge suite is green after the sweep (638 passed). NOT EXERCISED AGAINST A REAL DEPOSIT: nothing writes a runner deposit until loot runner add, so every row this has run against is one a test filed, and the first run over a mailbox a push filled is owed to that ticket. migration 0026 rides the forge binary so the forge owes a deploy, and nothing is broken until then because no code path writes the table yet (#2159)
- ↳ supersedes
a40b8a13· not stored here - Kept, not rewritten: a carry replays a suffix as a new version and the one it replaced stays addressable. That is the difference between a carry and a rebase.
nothing readable · 17 sealed · 17 internal - ↳ supersedes
- ADR 0099 records the move of loot from millerbyte.com to loot.build (map #2412): the site and one-liner at the apex with www redirecting, artifacts at dl.loot.build, the forge at forge.loot.build, security@loot.build through Cloudflare Email Routing with no catch-all, the relay not recreated, and the production Clerk instance on loot.build; and a hard cutover, the operator decision, in which the new names serve beside the old, a release carries them, every clone re-points its forge remote, and the old names are removed with no alias or redirect kept. it states what that breaks for anything made before it, read from the code: an opted-in old binary POSTs telemetry to the old name silently, so the old names must end NXDOMAIN rather than answered by the millerbyte.com wildcard, which would hand those reports to Vercel; old --help links; installers and manifests pinned to dl.millerbyte.com; the crates.io placeholders published with the old homepage, which only a new publish by the operator fixes; and it records the measured costs of the move (a 19.2 s first unlock on the new origin, no forge change because a signed request does not bind the host). ADRs 0037, 0041, 0045, 0049, 0061, 0064, 0066, 0067, 0074 and 0097, whose decisions name a loot host, each gain an amendment pointing here, and a banner where they had none; their bodies keep the names they were written with (#2414)nothing readable · 11 sealed · 11 internal
- ADR 0098 records the decisions on #2411: the web writes tickets as signed changes the browser builds, seals and pushes, one per action and stow-first, and the forge becomes a second writer to main for ticket-only changes, those whose every path is under tickets/ and whose signer is a key on the owner account. the forge refuses an ingest that would drop a head it does not descend from; a land folds ticket-only forge changes in before the gate and fails closed on any other forge-only change; a web write racing a land is folded by the sync as a ticket-only merge with no gate, pinned by a test that no gate input reads tickets/; the CLI reads web tickets through a read-only forge overlay; a web change subject is content-free (ticket <id>: <verb>); and tickets never reach git main or GitHub. docs/specs/loot-tickets.md gains dated amendments: created-at on c/ and r/, event position keyed on the space-stripped path so a move keeps order, loot tickets --state replacing --closed, no new --id, the BOM strip on --body-file -, the web write path, criterion 2 met in dependency waves, and the map #2422 slicing. ADR 0028 gains the ticket-free projection and ADR 0063 the gate-less ticket-only sync fold. CONTEXT.md amends Ticket and gains Second writer, Ticket-only change and Stow-first publish. the doc link, line-ending, hygiene corpus and runbook suites are green (22 passed), and the link gate went red under a broken link in the new ADR (1 failed, 2 passed) and was restored. docs only, no code moves (#2423)nothing readable · 5 sealed · 5 internal
- ADR 0097 records the grilling on #2182: a proposal's participants learn what happened through a pull first, a cross-repo list of my proposals and their activity on loot propose and the private shell plus one ambient line after verbs that already reach the forge, measured against a locally kept last-seen marker; and then through an opt-in email, off by default per account, with separate per-event toggles, an own-actions toggle and a per-repo watch for owners and members. the forge stores no address: Clerk already holds one for every account the proposal door admits, and a sender worker outside the forge looks it up at send time and holds the only Clerk and mail credentials. an email never says more than the world can read, naming and quoting a metadata-public repo and sending a nameless nudge for a private one, enforced by the sender reading excerpts only through the anonymous views. the forge writes a text-free outbox row in the same transaction as each act; delivery is coalesced per proposal under an hourly ceiling with one-click unsubscribe, through a transactional provider on a sending subdomain, Postmark behind a swappable interface. the grant inbox was ruled out as the channel because it is a pull, and browser push is rejected for now. CONTEXT.md gains Proposal notification. graduates as #2405, #2406, #2407, #2408 and #2409. docs only, no code moves (#2182)nothing readable · 2 sealed · 2 internal
- the private shell unlocks without waiting for its mailbox, and the mailbox pull becomes incremental: measured live, forge /pull-grants took 2.78 s of a 4 s /private load, because the whole mailbox of 1,640 standing self-grants was downloaded before the shell counted as unlocked. /pull-grants now takes an optional 17-byte cursor in its signed payload (a tag byte, then the inbox seq high-water mark and the forge clock at the answer, issued by the server and opaque to the browser, which may only send the all-zero one) and answers rows with seq past it or a reveal_at past its time, due on the forge clock, followed by the next cursor after the counted frames; the Postgres read takes a share lock on grant_inbox so a deposit that took a lower seq but commits later cannot fall behind every future cursor, and an empty payload answers byte-identically to before, so the CLI, the SDK and the relay client are unchanged and either deploy order works. the site keeps the sealed grant blobs and the cursor in IndexedDB beside the vault (version 2, the seed record kept), builds the keyring from them, shows the shell unlocked and pulls only the delta in the background; a file whose key is missing waits for an in-flight pull, the bars say keys are updating, and Lock clears every store with the vault. the gate shows Unlocking while a silent unlock runs rather than flashing the unlock card. forge tests on memory and Postgres and site tests, each red under a named mutation and restored; bash ci/local.sh green against Postgres 18 (4559 passed), site gate 819 passed. owes a forge deploy and a site deploy (#2403)nothing readable · 20 sealed · 20 internal
- the metrics shipper stops failing every tick once its backlog passes 4,095 rows: insertEvents sent one multi-row INSERT for every row a tick read at 16 parameters a row, and Postgres counts bind parameters in a 16-bit field, so past 65,535 node-postgres wrapped the count and the server refused the statement (bind message has 19296 parameter formats but 0 parameters, found by the #2396 site deploy post-deploy tick). eventInsertStatements now splits the rows into statements under MAX_BIND_PARAMETERS, each numbered from $1, run in the same one transaction so the log position still commits with every row or none. pinned at 5,000 rows: split under the limit, every row once in order, placeholders never past a statement own values, red under a single-statement mutation (1 failed, 3 passed) and restored. site gate 808 passed; bash ci/local.sh green against Postgres 18 including the metrics database suite. owes a site deploy (#2401)nothing readable · 3 sealed · 3 internal
- the private shell stops dying a minute after /account: it authenticated with Clerk 60-second __session cookie while no Clerk JS ran on /private to refresh it, so every private call 401d and a reload wiped the vault. per the operator grill (ADR 0096, reversing #930 dec. 4), Clerk JS now runs on the private routes, loaded on demand behind PrivateGate so public pages carry none, under the strict nonce CSP widened only for Clerk (its frontend API host read out of the publishable key, img.clerk.com, the Turnstile and protect hosts, and worker-src blob: for its session timers) with the request nonce passed to ClerkProvider; every private call goes through one privateFetch that sends a Bearer from getToken and retries once with a fresh token, and /api/private/* no longer accepts the cookie; the vault is wiped only when Clerk reports signed out or a different user, or on Lock, and an HTTP status never wipes it; signed-out /private renders SignIn in place and returns to the requested URL, and the private bar gains Sign out beside Lock. the vault, private shell and CSP comments and CONTEXT.md now say what is true: the wrap key can decrypt, so any script on this origin can recover the seed, and the wrap protects it at rest; the boundary is this origin script set, loot code plus Clerk. tests for the CSP, bearer lane, retry and wipe triggers and the built server, each red under a named mutation and restored; site gate 804 passed, every surface lighter; a CSP-enforcing signed-out load renders SignIn with no violation and /why loads no Clerk; workspace suite 4549 passed. the operator live check closes the ticket (#2396)nothing readable · 30 sealed · 30 internal
- the forge ingest stops writing a row per statement: write_rows issued one INSERT per object, tree entry, edge, repo change and path touch, about a million round trips for the 620-change push of this 1,600-path repo that timed out at the client 270 s wait in #2388. each table now takes one INSERT SELECT FROM UNNEST ON CONFLICT DO NOTHING per call, tree_entry one per manifest the call itself inserted (a manifest row is only ever written with its entries in one transaction, the memory store already keeps first-arrival entries, and the comment says what would break that), with the conflict clauses, refuse_burned first, the G5 order, per-change parent and predecessor ordinals and path_touch and object order kept. a Statements trait lets a counting wrapper stand in for the sealed GenericClient, and a pin holds 16 and 160 entries to the same 9 statements, red under per-row tree_entry, objects, path_touch and parents and under rewriting a held manifest, each restored. measured on a throwaway Postgres 18, 600 changes of 1,600 paths: 80.0 and 82.5 s before, 13.5 and 14.3 s after. workspace suite 4549 passed over 142 binaries, 13 ignored, and bash ci/local.sh green against Postgres 18 (4549 passed, site live suites 67). no migration; owes a forge deploy (#2391)nothing readable · 3 sealed · 3 internal
- 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)
- ↳ supersedes
81216feb· not stored here - Kept, not rewritten: a carry replays a suffix as a new version and the one it replaced stays addressable. That is the difference between a carry and a rebase.
nothing readable · 23 sealed · 23 internal - ↳ supersedes
- a land from a lane syncs origin from the primary: origin is now the forge (#2388) and loot push refuses to push a lane to a forge (ADR 0075, unchanged), so the post-land sync that pushed in the landing position was refused on every lane land. from a lane the sync now runs loot adopt then loot push in the primary checkout, after reading the primary in-process and refusing by name, before touching it, when it holds a non-empty working change or carries finalized changes landed main does not contain (so nothing unreviewed is published to origin), failing closed when that cannot be read; a primary land still pushes in position. the harbor is held until the sync finishes so two lands cannot write the primary at once, the #1776 format check still runs first, and the verdict keeps relay= and its tokens, a primary that holds work spelling REFUSED with a block that says what to clear; the block headings and fix text now say origin, and workflow.md and the land-change skill say what the land now does. nine new tests over the spawner and real primaries, each red under a named mutation and restored; loot-first 239 lib tests, workspace suite 4542 passed over 141 binaries, 13 ignored. live only once the primary release binaries are rebuilt (#2390)
- ↳ supersedes
724e2baa· not stored here - Kept, not rewritten: a carry replays a suffix as a new version and the one it replaced stays addressable. That is the difference between a carry and a rebase.
nothing readable · 4 sealed · 4 internal - ↳ supersedes
- the landing page stops pointing visitors at the relay and stops saying it cannot read what it holds: the check-it-yourself cell ran loot clone https://relay.millerbyte.com and said every sealed object stays sealed in your hands, but a relay holds the key to every Internal object (ADR 0011) and that clone handed this private repo to anyone who ran it (#2388), and the built-with-loot plate said the relay that hosts it physically cannot read the private code it stores. the cell now links to the repo own public tree page on the forge, from the owner and repo the receipt reads (so it renders only when there is a demo to name them), where anyone sees every path and everything but the published files locked, and the plate says the forge that hosts it holds no key to the private code, only to what is published (ADR 0041). FALSIFY and its reasoning are gone; a keyless forge clone earns a 403 under ADR 0061, which is why the check is a page and not a command. site gate green (777 passed), / is 259 bytes lighter; owes a site deploy (#2392)nothing readable · 3 sealed · 3 internal
- test scratch directories are named by one shared helper, common::scratch, from the process id and a per-process counter rather than the clock, so two tests asking for one tag at once are no longer handed one directory that the second caller empties and refills (the #2258 mechanism): 19 test files under crates/*/tests named a temp_dir path from SystemTime nanoseconds in 21 statements and now call fresh_dir or fresh_path, loot-cli files reaching it through mod common and loot-first exit_codes compiling the same std-only file by path, and revset_everywhere and plaintext_equality_census, which already counted, call it too. scratch_dirs.rs holds the #2258 pin, moved from revset_everywhere and aimed at the shared helper, red under the clock naming in 13 runs of 13 (0 passed and 1 failed, 1 to 16 of 400 directories handed twice), and a guard that walks every .rs file under crates/*/tests and refuses a temp_dir statement that reads the clock, red over the unconverted tree naming the 19 files (1 passed and 1 failed), red with buoy_exit put back (1 and 1) and red when the walk finds nothing (0 and 1), each restored green. no other test changes what it asserts. the workspace suite is green (4533 passed over 140 binaries, 13 ignored, from 4532 over 139: the guard is new and the pin moved into its own binary). test-only, so it owes no deploy (#2358)nothing readable · 25 sealed · 25 internal
- loot apply-patch names each applied path by the tier its seal records, and loot new --no-snapshot lists the paths the signed change first seals: the apply-patch report printed the .lootattributes resolution, a Visibility, which cannot spell published, so a path this repo publishes read internal while loot show said published, and it now reads each row through Workspace::tiers_as_sealed, the #2219 read generalized from first_seals_as_sealed to any paths of a recorded change, rendered through render::tier_label. the first-seal summary took its set of paths from the disk walk of seal_gate, so under --no-snapshot a .lootattributes the earlier capture first sealed and the disk had since lost was left off; finalize_capturing_over now returns the paths the signed change holds and the anchor the gates judged does not (Workspace::first_seals_of), paths alone, since the tier is the seal. both pinned red first (0 passed and 1 failed each): published, internal and restricted rows through the apply-patch verb, and a --no-snapshot summary listing the lost rules file and neither a path the disk gained since nor one the anchor holds. red with each piece undone, counts read each time, each restored green: the marker ignored, published claimed for every row, the rule resolution printed (0 and 1 each on the apply-patch pin), the set taken from the disk gate, the anchor filter dropped, the anchor read after the finalize (0 and 1 each on the summary pin). reproduced through the 0.4.24 binary before and a lane build after. CONTEXT.md and the Workspace width move with it. no format, wire or migration byte moves, so this owes no deploy. the workspace suite is green (4532 passed over 139 binaries, 13 ignored) (#2353)nothing readable · 8 sealed · 8 internal
- review sweep 15 over the proposal conversation and the published tier: acting on one proposal decides its parties by account as seeing it does, so the withdrawal and decline gates ask propose::sees, the withdrawal author check and the re-proposal parties in admit ask speaks_for, and a rotated tip author whose original key is retired withdraws their proposal with the account live key on a metadata-private repo, where by key it was told the proposal did not exist, and on a public one, where it was told it did not author the tip; a rotated proposer offers a declined proposal again. the #2378 rotation pin now retires both original keys on a Rotated fixture that files the offer before the rotation. the comment-page EXPLAIN pin asks for Sort anywhere in the plan, where its line-start form could never see a nested Sort node. an unread seal answers not published in rows_of and with_sealed_publish_status, the direction that never overstates, where it fell back to the rule; show renders an unread object by address as sealed, so the listing carries the pin. propose.rs, ADR 0095 and CONTEXT.md state which gates are by account as a property, a read of or act on one proposal, and cite #2006 for list, CONTEXT.md scopes the #2352 tier sentence and points at #2381, and append_page refuses a comment repeated within one page. red with each piece undone, counts read each time, each restored green: the withdrawal seeing by key, the withdrawal author by key, admit parties by key (0 passed and 1 failed each), a retired key speaking for nobody (0 and 1 on the rotation pin), a misshapen page index on a throwaway Postgres 18, comment_id before created_at and created_at descending (0 and 1 each, 1 passed restored), the listing falling back to the rule (0 and 1), a repeat within one page taken (0 and 1); the rows_of fallback mutation stays green, since no verb reaches it. bash ci/local.sh is green against Postgres 18 (4530 passed over 139 binaries, 13 ignored). no format constant, codec byte or migration moves; the forge rules change, so this owes a forge deploy (#2382)nothing readable · 9 sealed · 9 internal
- loot show, the deletion rows of loot status and a surface listing name the published tier off the marker the object carries rather than off the .lootattributes on disk, so a published object whose rule was deleted no longer prints internal and a plain one given a rule since no longer prints published: the delta seam reads each row marker in Workspace::rows_of, off the side that carries the row, and keeps the rule for a live side (ADR 0079 section 1) and for a seal it cannot read, while the clobber guards take ResolvedDelta::classes and pay no read; surface, rehome and the pull auto-surface list through the new Workspace::with_sealed_publish_status. the status machine listing is the working tree on disk and keeps the rule. measured over a 400-path tree with the counters, every added read is a memo hit: object_gets moves by one per row with a recorded side (show of a half-edited change 1602 to 1802, status with a third deleted 804 to 938, surface --json 802 to 1203) and object_disk_reads is flat on each (601, 401, 401), a clean status is flat on both, and a hyperfine A/B over 2000 paths reads within noise (show 98.2 vs 97.3 ms, status 93.1 vs 92.6, surface --porcelain 165.4 vs 168.3, status with deletions 130.8 vs 129.8, load 0 to 6 percent). surface_object_reads counts the listing marker read apart from the opens, and the restore pin reads the seals its capture wrote. both directions pinned red first (0 passed and 2 failed with the fix undone), each through the verb: show and status deletion rows, and surface over trees ferried from git commits that delete and add a published rule. red with each piece undone, counts read each time, each restored green: the delta seam on the rule (1 passed and 1 failed), marker or rule, the overstating repair (1 and 1), the new side asked for a deletion (1 and 1), the listing on the rule (1 and 1), the listing as marker or rule (1 and 1), the guard paying the reads (0 and 2 in surface_object_reads). the published values in diff and surface machine output change for such rows and no key or column moves (ADR 0023). no format, wire or migration byte moves, so this owes no deploy. the workspace suite is green (4527 passed over 140 binaries, 13 ignored) (#2352)nothing readable · 12 sealed · 12 internal
- review sweep 14 over the proposal conversation: the proposer and the tip author are accounts where #2374 left them keys, so a live key of either account is a party at a shut door and sees the proposal on a metadata-private repo, through propose::speaks_for, now beside visible_to, and propose::sees, which read and the comment routes ask one row at a time while list keeps the key arm by key. migration 0025 adds proposal_comment_page on (repo_id, change_id, created_at, comment_id), so a thread page is a range scan the driver statement comment_page reads, where every page sorted the whole thread, and a pg pin reads its plan with the sequential scan and the sort priced out. /propose/decline takes the comment routes body limit, --show says the decline reason prints on the forge word and refuses a thread page that brings nothing, stands still or repeats a comment, and the reason disclosure says a re-proposal clears it. the comment.rs door sentence names may_propose_in and lists no members, PROPOSAL_COLUMNS replaces four copies, and ADR 0095, CONTEXT.md and the code docs say the party rule, the index, the decline limit and that the reason is unverified pending the call ADR 0095 records. red with each piece undone, counts read each time, each restored green: a party by key and seeing by key (0 passed and 1 failed each on the rotated-party pin), the index dropped on a throwaway Postgres 18 (0 and 1), a refusal quoting the envelope printed uncaged (0 and 1), the reason printed without the forge word, the decline route uncapped, a cursor that does not move taken and the re-proposal unsaid (0 and 1 each). bash ci/local.sh is green against Postgres 18 (4525 passed over 139 binaries, 13 ignored). no format constant or codec byte moves; migration 0025 rides the forge binary and the forge rules change, so this owes a forge deploy (#2378)nothing readable · 14 sealed · 14 internal
- loot propose --show resolves the whole change id loot propose --list prints for a proposal whose stack this position never pulled: --list prints the id as k-z letters, and --show matched letters only against the local graph, so the handle copied off --list refused unless the stack was held. a whole change id, in hex digits or in letters, now decodes on its own through hex::decode_letters_array, the inverse of hex::letters, before the local resolver, which still takes a shorter prefix or a selector; the resolver doc said --list prints the hex id and now states the property it relies on. nothing is fetched or pulled as a side effect, and propose has no porcelain or json output, so no machine shape moves. pinned through the spawned binary: a fresh repo with its own key reads a metadata-public repo, copies the handle off --list, loot evolog refuses that handle, so this position holds no change by it, and --show reads the proposal. red first (0 passed and 1 failed), and red with each piece undone, counts read each time, each restored green: the letters arm dropped (0 and 1), the decoder nibbles swapped (0 and 1 on the codec pin, and 0 and 1 through the binary, where the forge answers that no such proposal is visible). the workspace suite is green (4521 passed over 139 binaries, 13 ignored). client-only, so it owes no deploy and is live once the CLI is released (#2339)nothing readable · 4 sealed · 4 internal
- a decline may carry a reason signed with it, kept on the proposal row and shown as the thread closing event: ProposalDeclination gains an optional reason as a trailer on its signed bytes, so a decline with none is the bytes #2176 signs and neither format constant moves, and a reason a carrier swaps or strips no longer verifies. migration 0024 adds proposal.decline_reason with a CHECK holding it to a declined row, never empty and at most COMMENT_BODY_MAX bytes, and replaces the three 0020 proposal views with the column appended, which a text guard holds equal to 0020 once the column is taken out. propose::decline refuses an empty or oversized reason whole and a repeated decline with other words, close_proposal writes the reason only with a decline, and a re-proposal clears it with closed_at. POST /propose/read carries the reason as a trailer after the thread continuation, absent from an older forge, which would drop a reason unread, so loot propose --decline <tip> --reason <text> asks the read first and refuses a reason such a forge would drop, then prints who can read it before signing; --show prints the decline under the thread with its reason caged through render sanitize. frozen before_2336 codecs pin old client against new forge and new client against old forge both ways, and tests/proposal_read.rs compares the column for each reader class with an executable red that blanks it in one view (1 class disagreeing). red with each piece undone, counts read each time, each restored green: the reason left out of the signed bytes (0 passed and 1 failed), the read trailer not decoded (0 and 1 on each of two pins), the route dropping the reason, the read serving none (0 and 1 each), no cap and an empty reason admitted (0 and 1 each), a repeated decline changing the reason (0 and 1), a re-proposal keeping it and a reason written with any terminal state in the memory store (1 and 1 each, the pg stamp of the case green without a database), the CLI signing for a forge that would drop it, the reason printed uncaged (0 and 1 each), --reason admitted beside no decline and no disclosure before signing (0 and 1 each), the migration cap moved and a view joined to another family gate (0 and 1 each). ADR 0095 amended, CONTEXT.md and the forge spec updated. bash ci/local.sh is green against Postgres 18 (4519 passed over 139 binaries, 13 ignored). migration 0024 rides the forge binary and the read and decline routes change, so this owes a forge deploy (#2336)nothing readable · 23 sealed · 23 internal
- review sweep 13 over the proposal conversation: loot propose --show verifies a comment against the repo and change id of the proposal it reads, since one change proposed to two repos shares its tip and a forge could serve one repo comment as said in another, and cages the body, the path and a refusal quoting the envelope through render sanitize, so ESC or a bare CR from a stranger prints as its control picture. an author is an account: a live key of the account whose key wrote a comment edits and withdraws it after a rotation, the key that signs an edit becomes its author so readers still verify it, and the author withdraws without the visibility check, so a lost view does not strand their words. the door arm calls may_propose_in and a party is asked live_account_in, its second half, where a copy stood. the comment routes read at most COMMENT_REQUEST_MAX, refusing a larger request before its signature is checked. /propose/read serves a thread a page of THREAD_PAGE comments at a time with a cursor, a trailer on both payloads, and --show follows it. an anchor must be spelled as a tree spells a path and is asked of tree_entry by its key through manifest_holds; Manifest::address_of reads manifest bytes the forge does not hold. a runner key under no account is refused moderation, and ADR 0095 now says any live key on the owner account moderates. finalized_before_review sends a lane to lane_landing_route rather than a land with no PR to name. ADR 0095 records the account rule, the paged read, the unbuilt withdrawal warning of its section 6 and the rewritten #2332 pin; ferry.rs says the #2370 pins run in-process; a pin holds the 0022 CHECK equal to COMMENT_BODY_MAX. red with each piece undone, counts read each time, each restored green: the envelope not bound to its proposal (0 passed and 1 failed in loot-net and in the CLI), the body or the path printed uncaged (0 and 1 each), authorship by key, and an edit keeping the old key as author (0 and 1 each), the author asked to see before withdrawing (0 and 1), the door arm a copy (0 and 1), no per-route limit (0 and 1), the forge thread unbounded and the CLI stopping at one page (0 and 1 each), anchors matched by Path over the whole tree (0 and 1), a runner key moderating (0 and 1), the lane land its own (0 and 1), the cap moved on one side (0 and 1). bash ci/local.sh is green against Postgres 18 (4498 passed over 139 binaries, 13 ignored). no format constant or migration moves; the proposal read gains trailers and the forge behaviour changes, so this owes a forge deploy (#2374)nothing readable · 23 sealed · 23 internal
- the web can read a proposal thread with exactly the audience of its proposal: migration 0023 adds forge_read_proposal_comment, forge_owner_proposal_comment and forge_member_proposal_comment, each granted to the role of its own family and each one inner join onto that family proposal view from 0020 on both halves of the proposal key, since one change can be proposed to more than one repo; they serve every comment column but the envelope, which no rendering surface verifies, so a withdrawn comment is its tombstone with no body. tests/proposal_read.rs now compares threads as well: for every reader class the thread the route serves against each view, tombstones, an edit, a path anchor and a reply included, with a vacuity guard, the key arm pinned as the only difference, a cleared gate answering no rows, every view read whole as its own role after a withdrawal through the store, a change proposed to a public and a private repo read with no repo filter, and an executable red that rebuilds the anonymous comment view onto the proposal base table; migrate.rs text guards pin one comment view per family, its grant, its reach, the whole-key join and no envelope. red with each piece undone, counts read each time, each restored green: the anonymous and owner views joined on change_id alone (1 passed and 6 failed), the owner view alone so (6 and 1, caught only by the unfiltered read), the member view reaching the anonymous proposal view (1 and 6, and 2 and 1 on the text guards), a withdrawal that keeps the body with its CHECK dropped (6 and 1), and the envelope served, a cross-family grant and a view without security_barrier (2 and 1 each on the text guards). the timeline and the Markdown renderer are not built: an attestation row carries no time and no reader role has a view of it, the decline reason is #2336, and no site surface renders a thread yet (#2165). ADR 0095 amended and CONTEXT.md updated; setup-forge checks reader views by prefix since scripts@1b2b9ed, so the member view needs no edit there. bash ci/local.sh is green against Postgres 18 (4482 passed over 139 binaries, 13 ignored). no format constant or codec byte moves; migration 0023 rides the forge binary, so this owes a forge deploy (#2332)nothing readable · 10 sealed · 10 internal
- a proposal has a conversation on the forge, stored and written and read, and no CLI flag posts to it yet: migration 0022 adds proposal_comment, keyed under the proposal row so a thread outlives a decline and a re-proposal, holding the envelope its author signed and its body, both set to NULL by a withdrawal that leaves a tombstone saying author or owner, with CHECKs that refuse a withdrawn row holding either, row security bound by the 0017 policy and the rls census widened. POST /propose/comment, /propose/comment/edit and /propose/comment/withdraw are tagged acts over propose::comment, where the rules live: a signer must see the proposal and is otherwise told what an absent one is told; the owner class writes, the accountless namespace key included; anyone else needs a live account key, so a bare keypair or a retired key is refused; members, the proposer and the tip author write through a shut door, anyone else only through an open one; a comment binds the tip on file when it arrives; a path must be in that tree and a reply must name a comment on the proposal; the body is at most 16 KiB of UTF-8, refused and not cut; only the author edits, re-signing the same id, anchors unmoved and bound to the BLAKE3 of the envelope it replaces; the author or the owner class withdraws, and members and proposers do not moderate. the comment id is signed by its author, so the same envelope again is one comment and a withdrawn one is not re-posted back. the thread rides POST /propose/read as a trailer and loot propose --show prints each body only when its envelope verifies against the author and anchors beside it. the flags that post, edit and withdraw a comment, and the disclosure printed before posting, are not built, since ADR 0095 names no flags; ADR 0095 amended and CONTEXT.md updated. red with each piece undone, counts read each time, each restored green: a bare keypair admitted, the door not asked, visibility not asked (each 0 passed and 1 failed on the writer-set pin), a member moderating, the owner editing another comment (0 and 1 each), anchors free to move (0 and 1), a withdrawal that writes nothing (0 and 1), no cap, and the body cut to the cap instead of refused (0 and 1 each), no tip binding (0 and 1), an edit not bound to its prior (0 and 1), an edit past the writer rule (0 and 1), the read sending no thread and an unverified body printed (0 and 1 each); on a throwaway Postgres 18, a withdrawal that kept the words with the CHECKs dropped left the conformance case green (1 passed) and turned the pin that reads the whole row red (0 and 1). bash ci/local.sh is green against Postgres 18 (4476 passed over 139 binaries, 13 ignored). no format constant or codec byte moves; migration 0022 rides the forge binary, so this owes a forge deploy (#2331)nothing readable · 23 sealed · 23 internal
- review sweep 12 over the lane projection guard: items 1 and 2 did not reproduce through the binaries in a scratch repo with its own lanes, since a position graph is its own lineage-filtered load (ADR 0022), so after a lane signed K a primary plain ferry projected nothing and minted no mark for K and the lane ferry still refused, and a lane with nothing of its own caught up while a sibling seal sat unmarked; the guard question stands and both scenarios are pinned. a lane ferry --seal-wip over described work refuses before the reconcile, where it sealed into the catch-up and then refused, leaving a sealed line no PR carries (reproduced on the landed binary). the reap never says landed, since the push that follows a land pass can still be refused, and says projected for a marked version, which a mark is and on main is not. the ferry verb landing false is pinned through the spawned binary. a refused pass carries its notes. the primary recovery recipe names --allow-primary for the verbs that need it there. the #2314 and #2366 lane refusals print one route rule, lane_landing_route, which sends a lane to its PR land only when the PR carries the tip with nothing captured on top. ADR 0039 amendment, ADR 0092 (--skip-gates), CONTEXT.md and concurrent.md corrected. red with each piece undone, counts read each time, each restored green: the verb flag flipped (0 passed and 1 failed), the seal-wip refusal dropped (75 and 1), the reap saying landed (73 and 3), the route ignoring captured work (75 and 1), the route ignoring the PR (75 and 1), notes dropped (75 and 1), the lane recipe on the primary (75 and 1), the guard off (71 and 5), every lane pass refused (73 and 3). the scripts MAINTAIN ask is its own scripts commit. no format constant, codec byte or migration moves. the workspace suite is green (4450 passed over 140 binaries, 13 ignored) (#2370)nothing readable · 8 sealed · 8 internal
- the require gate of the land is proved through a real land: three orchestrator tests run loot-first land under --skip-tests over a mirror main a plain ferry projected and a change signed under a second keyring, applied by bundle and merged in by the lander so it rides the second parent of the merge, with the fake forge standing in for GitHub. an unapproved land refuses naming that version and its loot attest, moves no main and pushes nothing, and lands once the owner attests it through that verb; a require line only on the stack copy of .lootpipeline gates nothing, and a stack that deletes the file main requires from is still gated. red with each piece undone, counts read each time over the five require pins, each restored green: the requirement read at the stack tip (2 passed and 3 failed, the read-from-main census among them), the check wrapped in a skip-tests condition (3 and 2, both censuses green), the owner attestations not consulted (4 and 1), the judged main taken from the lane anchor (3 and 2), the first-parent chain in place of the closure (3 and 2). the ADR 0092 amendment says the gap is closed and by what, and the break-glass census says what it cannot see. no format constant, codec byte or migration moves. the workspace suite is green (4445 passed over 138 binaries, 13 ignored) (#2317)nothing readable · 3 sealed · 3 internal
- a plain loot ferry in a lane refuses to project anything no land has carried, where it projected the lane unlanded line onto the mirror main every position shares so the next land from any of them pushed it unreviewed: the pass asks what it would project before the ingest and again at the gate seam after the reconcile, since --seal-wip can seal into the line, and loot-first land marks its own pass with RunOpts landing; a lane catch-up that projects nothing runs as before, and the refusal names the land of the PR that carries the lane tip or else the fresh-lane route of #962. the review reap says landed only from a land pass, on main for a projected version otherwise, and signed, no land has carried it for an unprojected one, where it said landed for any signed version. the seal-wip recovery recipe is chosen by position: the primary keeps its follow-up round and a lane gets the fresh-lane route, one const shared with the #2314 refusal. reproduced through the 0.4.24 binaries in a scratch repo with its own lanes (the advised ferry projected 2 lane changes and printed landed) and checked through the lane-built binary (refused, mirror main unmoved, lane catch-up still runs, reap and recipe reworded). the lane-simulated lands in the ferry tests now run as land passes, and 2 primary reap pins read on main. pinned by six new tests; red with each piece undone, counts read each time, each restored green over the ferry tests: the guard off (69 passed and 3 failed), a land pass refused too (61 and 11), every lane pass refused (70 and 2), the seam ask dropped (71 and 1), the PR route dropped (71 and 1), the old reap word (69 and 3), the recipe always the primary one (71 and 1) or always the lane one (71 and 1). ADR 0039 amended, CONTEXT.md, concurrent.md and the land-change skill updated. no format constant, codec byte or migration moves. the workspace suite is green (4442 passed over 139 binaries, 13 ignored) (#2366)nothing readable · 11 sealed · 11 internal
- review missing-mark refusal chooses its remedy by position: in a lane it no longer advises a plain loot ferry, which projects the lane unlanded line onto the mirror main every position shares so the next land from any of them pushes it to git main whether or not a PR reviewed it, and names the fresh-lane route of #962 instead; the primary keeps the plain-ferry advice ADR 0039 kept. reproduced through the 0.4.24 binaries in a scratch repo with its own lanes: after adopt --seal-wip and a follow-up edit the review refused with the old advice, and the advised ferry projected 2 lane changes onto mirror main; the lane-built binary prints the lane remedy, the named route opens a review in a fresh lane, and the primary still prints the old line. pinned by a_lane_after_seal_wip_is_not_told_to_run_a_plain_ferry and a_lane_over_its_own_bare_seal_gets_the_lane_advice_too, the primary pin now also refusing the lane text. red with each piece undone, counts read each time, each restored green: every position given the primary advice (1 passed and 2 failed), every position given the lane advice (2 and 1). no format constant, codec byte or migration moves. the workspace suite is green (4436 passed over 139 binaries, 13 ignored) (#2314)nothing readable · 2 sealed · 2 internal
- loot init quickstart block names the identity just created in its restricted= example, as the tip line above it already did, where it printed restricted=alice whatever the identity was called, so pasting it as told would seal .env away from its author and the lockout gate refused it: render::lootattributes_quickstart takes the identity name and init passes its own. pinned by init_quickstart_restricts_to_the_identity_the_tip_names, which inits as bee and holds the two lines to the same rule. red with each piece undone, counts read each time, each restored green: the block before the fix (0 passed and 1 failed), init passing alice to the block (0 and 1), the tip naming alice (0 and 1). the pages that write restricted=alice beside loot init --identity alice are right and unchanged, as are the captured runs under docs/evidence. no format constant, codec byte or migration moves, and init has no machine output. the workspace suite is green (4434 passed over 139 binaries, 13 ignored) (#2278)nothing readable · 3 sealed · 3 internal
- the deferring manifest walk checks every stored key and Restricted holder name for UTF-8 as the eager decoder does, so a graph file holding one it refuses is refused naming the file at the open, at the adopt and ferry pool read and at the save, where the open passed it and the first read of that manifest panicked; measured first, the check shares one pass over each key with the backslash test it replaces and the open got faster, one loot-perf-gate --features count per arm, interleaved, load idle: --graph-load 9.47 to 9.54 ms before and 8.93 to 9.04 after at depth 1024, --manifest-breadth 33.9 to 35.9 against 32.3 to 32.9 at 872 paths, store_file_reads 24 on both, and over a copy of this repo graph file (93.5 MB, 1,634 nodes) the deferring decode 16.6 to 17.1 ms before and 8.8 to 9.3 after, where handing every key to the validator read 24.9 to 25.8. Manifest::decodes, the splice check of #2282 and the refusal left to the save by #2287 go, since the read now refuses what they caught. through the binary, loot log and loot status over such a file panic on 0.4.24 and refuse on the lane build. red with each piece undone, counts read each time, each restored green: the key check dropped (0 passed and 2 failed), the holder check dropped (0 and 2), a valid non-ASCII key refused (1 and 1), the refusal not naming the file (1 and 1), the open alone not naming it (0 and 1). no format constant, codec byte or migration moves. the workspace suite is green (4433 passed over 138 binaries, 13 ignored) (#2275)nothing readable · 6 sealed · 6 internal
- the review-sweep fix-up over #2355 and #2258: the forge restore note says what the push does for the host it is talking to, so a forge without late_attestations, the live forge until redeployed, is told none is sent and that its left-behind count includes attestations it may still hold, where it was told they are sent again; and the restore mark is lowered only by a push that sends what it owes to a forge known to keep it, so a push that cannot deliver leaves the restore visible to the next one that can. the unknown-host rule of #2355 is pinned through push itself, against a front that refuses /info before a real relay. red with each fix undone, counts read each time, each restored green: the relay arm recording what it carried (2 passed and 1 failed), the note saying sent again to a forge without the flag (2 and 1), a push that does not deliver lowering the mark (2 and 1). record_attestations_sent is renamed record_attestations_kept for what it records. stated rather than overstated: the #2258 scratch naming reproduces the recorded message and whether it caused that sighting is not established, and its pin is red only where the clock is coarse; CONTEXT.md names the restore limit as pushes bringing the generation back up to the mark; RemoteSync bound states the property of its callers and that it records no row but a forge push still records the mark; ADR 0018 counts no bullets; a comment in store.rs is indented to its block. no format constant, codec byte or migration moves, and the ledger stays push-only: store_file_reads is 24 on the gate workload with no move. the workspace suite is green under bash ci/local.sh against Postgres 18 (4434 passed over 139 binaries, 13 ignored; site live suites 7 files passed) (#2360)nothing readable · 9 sealed · 9 internal
- a_scanner_holding_the_staging_file_does_not_fail_a_save waits for its scanner to finish a first scan before it starts the save, instead of starting it the moment the scanner thread is spawned: under 48 cpu-burn and 16 file-churn threads on 24 logical CPUs, measured at 100 percent, an instrumented run showed the scanner had scanned zero times in 48 of 48 rounds, its first instruction 113 to 518 ms after the spawn while the whole save took 2 to 8 ms, so every round missed the same way and the round count was never what failed. the wait is the condition, bounded by the scanner reaching its first scan whatever that scan returned, so a scanner that cannot read the directory still fails at the guard rather than hanging; the 8 rounds stay, now covering only the race a polling scanner has by construction, and 42 of 64 rounds caught the file under the same load. no reason for 8 was recorded when #2026 chose it, and it is not raised. interleaved under that load the old test passed 1 of 10 runs alone (9 failed at its guard) and the fixed one 10 of 10; the loot-core suite failed at the guard under load with the old test (689 passed and 1 failed) and passed 3 of 3 with the fix (690 passed each). the guard and the property stay live, counts read each time, each restored green: the scanner looking for a suffix nothing has (0 passed and 1 failed at the guard), the scanner reading a directory that is absent (0 and 1 at the guard, no hang), and a bare rename in save_objects_loose (0 and 1, os error 32 out of the save). not an instance of the #2258 naming shape: its fixtures come from testkit::tmp and its staging names from stage_tmp_name, both pid plus a per-process counter. test-only; no migration, format byte or wire byte moves, so this owes no deploy. the workspace suite is green (4431 passed over 139 binaries, 13 ignored) (#2269)nothing readable · 2 sealed · 2 internal
- revset_everywhere names each scratch directory by a per-process counter rather than the clock, so two tests asking for the same row no longer share one copy: on Windows SystemTime ticks in 100 ns steps and the tests here reach scratch(archive) together, so a second caller could empty the first caller copy and fill it with another fixture store, which answers no change matching word for word. reproduced by overwriting one copy with another fixture by hand, and by naming scratch by process id alone (5 passed and 5 failed, the historic test failing at loot archive with the #2258 message). pinned by concurrent_scratch_callers_never_share_a_directory, red under the old clock naming in 18 runs of 18 (0 passed and 1 failed, 9 to 14 of 400 directories handed twice), restored green. ci/test-main.sh runs the workspace suite with --no-fail-fast and runs the site live suites after a red workspace suite, recording a failed suite and exiting non-zero naming it, while a failed provisioning step still stops the run; the bare step-or-record form was measured exiting 0 over a failed step, so a suite step runs in a subshell with set -e. the published_surfaces pin reads the --no-fail-fast line (5 passed and 1 failed with the flag removed). no migration, format byte or wire byte moves. the workspace suite is green under bash ci/local.sh against Postgres 18 (4431 passed over 138 binaries, 13 ignored; site live suites 7 files passed) (#2258)nothing readable · 7 sealed · 7 internal
- the attestation ledger records what a remote is known to keep rather than what a push put on the wire, the review-sweep fix-up over #2251: a push records rows only for a host whose /info answered, as a relay or as a forge advertising late_attestations, and a host whose /info did not answer is still sent them but has nothing recorded; against a forge without the flag it records what rode with its change, so its left-behind warning counts the late ones and no longer grows; the ledger keeps per forge the tip generation the last recorded push committed at, and a push that reads a lower one sends every attestation over the changes that forge holds again and says it may have been restored, with deleting .loot/attestation-ledger documented as the recovery for a relay and for a restore hidden by later pushes; a ledger that will not read or write warns and never fails a push or skips its deposits, and a record over an unreadable one starts it afresh. the forge misread as a relay the sweep named fails at its unsigned /wants before sending, pinned; the rule stands without it. red first with each fix undone, counts read each time, each restored green: an unanswered /info read as keeping them (0 passed and 2 failed), a forge without the flag recording nothing (0 and 1, left behind 2 where 1), a restored forge unnoticed (0 and 1), an unreadable ledger fatal (0 and 1), an unwritable ledger fatal (0 and 1), the first-seal summary defaulting an unknown change to no rows (0 and 1). also stated: the privacy filter rests on the declared have, a forge holding a head without its ancestry drops a late attestation it is recorded as keeping, a ledger is one clone, late_attestations states the property rather than naming verbs, RemoteSync bound records nothing and its docs are current, the first-seal summary refuses an unknown change and names its tree clone, and the Route doc states where hosting is decided. the ledger stays push-only: store_file_reads is 24 on the gate workload with no move. no format constant, codec byte or migration moves. the workspace suite is green under bash ci/local.sh against Postgres 18 (4430 passed over 139 binaries, 13 ignored; site live suites 7 files passed) (#2355)nothing readable · 15 sealed · 15 internal
- a push now carries an attestation recorded over a change the remote already holds, so loot tag after loot push reaches a relay and a forge instead of being left behind under a success line: a local attestation ledger (.loot/attestation-ledger) records per remote what each push delivered and is read by a push and by no open or save, written by RepoStore::record_attestations_sent as a read-merge-write under the shared-store lock; the push sends the attestations over the held changes of the remote that the ledger has not recorded beside the send set and prints how many, the bundle builder keeps a late attestation only over a change inside the have closure of the recipient whoever handed it in, the forge /ingest keeps one over any change its repo holds through a new changes_held store read that costs no query when every attestation rides its change, and /info gains an additive late_attestations field so a forge that does not advertise it is sent none, has nothing recorded as sent, and the push warns how many it left behind. the land gate store_file_reads is 24 on its workload with no move, where a first cut that read the ledger on every open measured 26 and was refused; an open reads 20 store files, 21 with that cut. the #48 bound holds on the wire: a push carrying one late tag sent 256 B at a relay and 306 B at a forge over both 2 and 24 held tags. the ticket recipe, whose fresh clone lacked late-tag2 through the 0.4.24 binary, shows it through a lane build. red under mutation, counts read each time, each restored green: the open reading the ledger again (0 passed and 1 failed), the ledger write overwriting instead of merging (0 and 1), the ledger ignored (0 and 2), the late lane dropped (0 and 2), the privacy filter removed (1 and 1), the forge back to in-this-bundle (2 and 1, and end to end 1 and 1), the forge keeping any change (2 and 1), a relay push recording nothing (0 and 2), a push recording to a forge that does not keep them (1 and 1), the /info flag ignored (1 and 1). no format constant, codec byte or migration moves; the forge change is live once the forge is redeployed. the pull half is not built: a pull still carries an attestation only with a change it sends. the workspace suite is green under bash ci/local.sh against Postgres 18 (4425 passed over 139 binaries, 13 ignored; site live suites 7 files passed) (#2251)nothing readable · 28 sealed · 28 internal
- loot new prints each first-seal tier off the seal the path was sealed under, so a path the same finalize had just published no longer reads internal: the summary printed the .lootattributes resolution seal_gate hands back, a Visibility, which cannot spell published because a published object is Internal plus the @world marker (#481). the tier is now read back off the signed change, seal_visibility and published_marker_to_carry over the address the finalized tree holds at each path, rendered through a new render::tier_label that restore now shares. reading rather than folding the standing rule in, as with_publish_status does for surface, is what makes it right under --no-snapshot, where the finalize signs an earlier capture and the rules may have moved in either direction; a row the finalized tree does not hold is left out, and an unreadable seal prints as a refusal and never as a tier. both directions pinned red first (0 passed and 2 failed): every Tier plus published on a fresh capture, and under --no-snapshot a published seal whose rule was deleted, a restricted seal whose rule was deleted and an internal seal given a publish rule since. red under mutation, counts read each time, each restored green: the row printed again (0 and 2), the marker ignored (0 and 2), the marker taken from the standing rule (1 and 1), seal or standing rule, the overstating repair (1 and 1, late.txt read published), the Visibility taken from the row (1 and 1), internal spelled published (0 and 2), an unreadable seal falling back to the row (2 and 1), a path the change does not hold kept (2 and 1). reproduced through the 0.4.24 binary before and a lane build after. CONTEXT.md, the census row for new_with and the Workspace width move with it. no migration, format byte or wire byte moves, so this owes no deploy. the workspace suite is green (4407 passed over 139 binaries, 13 ignored) (#2219)
- ↳ supersedes
bbc85147· not stored here - Kept, not rewritten: a carry replays a suffix as a new version and the one it replaced stays addressable. That is the difference between a carry and a rebase.
nothing readable · 8 sealed · 8 internal - ↳ supersedes
- the review-sweep fix-up over #2308, #2310, #2311, #2312 and #2313. the lock note beside a failed test is said only on a Windows land: loot-hygiene failed_tests now takes whether the run was on Windows, since os error 5 and 32 are an I/O error and a broken pipe on macOS and Linux, and loot-first passes cfg!(windows); both answers are pinned on this host, red under mutation with counts read each time (recognised everywhere 2 passed and 1 failed, nowhere 0 and 3, loot-first passing false 1 and 4). the plain pre-land test finding now prints the re-run-first recovery the merged pass prints, for each gate row that runs programs and rides the merged pass, derived from the row and pinned whole (no triage 3 and 2, ignoring the merged pass or the row judgement 4 and 1 each, the order swapped 4 and 1). the timing line noun is a ServerKind enum whose relay and forge lines are pinned byte for byte (a misspelled noun 7 and 1), and the loot-s3 put refusal calls the idle duration the idle budget and keeps floor for the rate (the old word 18 and 1). tutorial.rs qualifies the #2311 halt as run on Windows and reasoned elsewhere, and a reap failure no longer prints could not run; ADR 0075 names the graph read by MetadataStore::repo_change_graph and its callers rather than a list that missed propose::admit; sync.rs states the store property the scoped forge offer rests on and what would break it, and its round-zero comments speak of the remote; land-change SKILL.md, workflow.md and ADR 0055 carry the re-run-first order and the Windows-only note. the (idle, uplink_floor) data clump in loot-s3 is left as it is. no migration, format byte or wire byte moves; the loot-first change is live once the primary release binaries are rebuilt. the workspace suite is green (4406 passed over 139 binaries, 13 ignored) (#2348)
- ↳ supersedes
28702d06· not stored here - Kept, not rewritten: a carry replays a suffix as a new version and the one it replaced stays addressable. That is the difference between a carry and a rebase.
nothing readable · 13 sealed · 13 internal - ↳ supersedes
- the review-sweep fix-up over #2325, #2207 and #2208. a proposal landed close is bound to the tip it was decided for, on both stores: ingest decided the landing outside the transaction and each store closed the row on state open alone, so a revision through record_proposal between the decision and the transaction, which leaves the row open and moves no ref and so is invisible to generation_expected, was closed landed at a tip nobody landed. the rows proposals_this_push_lands hands the store already carry the tip decided on, and the Postgres close now adds tip_version = $4 and the memory close compares the tip, the binding a decline has; no other writer closes a row this way. a new conformance case, a revision between the landing decision and the ingest stays open, runs on each store: red first on the memory store (1 passed and 1 failed, the pg half skipping without a database) and on Postgres 18 with the tip term defeated (106 passed and 1 failed), green with it (107 passed). CONTEXT.md, store.rs, pg/meta.rs, memory.rs, ingest.rs and ADR 0075 stop calling the CAS the whole of the soundness. false sentences fixed: ADR 0012 eleventh amendment now says the ledger ran after the lanes until #2208, and the fifteenth names the plan pin as the fixture where the timed walk reads one seal, measured by hand; the plan pin now deposits every row it plans at another forge as well as the Restricted row at a relay, so dropping the remote from the dedupe empties the plan rather than dropping pitch.md alone (20 passed and 3 failed, all six rows gone), restored green (23 passed); Route::ALL is written by a routes macro from the enum variant list, so a variant added to Route is in it with no second edit, checked by adding a probe variant, and the http.rs sentence states that. the --decline refusal says the handle reads as a change id, and the Judged census doc says it sees only the Spawner. no migration, format byte or wire byte moves; the landed close is live once the forge is redeployed. the workspace suite is green under bash ci/local.sh against Postgres 18 (4404 passed over 138 binaries, 13 ignored; site live suites 7 files passed) (#2347)
- ↳ supersedes
58169e04· not stored here - Kept, not rewritten: a carry replays a suffix as a new version and the one it replaced stays addressable. That is the difference between a carry and a rebase.
nothing readable · 15 sealed · 15 internal - ↳ supersedes
- ADR 0075 re-decides the proposal ingest fallback across history sizes on a throwaway Postgres 18: a push with ten proposals open grows from about 5 ms at 1,000 changes to about 520 ms at 300,000 while a push with none open stays flat at 2 to 3 ms, and the growth is the repo graph read, which past a planner step, between about 1,450 and 1,550 changes on a forge of about 288,000 edges, scans the whole global change_parent table and so follows the forge edge count as well as the repo history (55 to 123 ms at 30,000 changes as the forge grew to about 893,000 edges). the knowingly-wrong arm, a graph read per proposal, read about ten times the ten-open arm at each size from 3,000 up. the fallback is retired: offer, fetch and propose read make the same read, and since #2175 the web reads state from the column the fallback would leave open. the no-proposal cost, 0.11 to 0.48 ms and flat in history, is recorded and not closed, since skipping the read needs a new column or a cache. store.rs notes the plan step at repo_change_graph. docs only: no code, no migration, no deploy owed. the workspace suite is green (4400 passed over 139 binaries, 13 ignored) (#2312)
- ↳ supersedes
88457ee9· not stored here - Kept, not rewritten: a carry replays a suffix as a new version and the one it replaced stays addressable. That is the difference between a carry and a rebase.
nothing readable · 3 sealed · 3 internal - ↳ supersedes
- the push-time deposit plan asks the deposit ledger before its standing lanes ask any seal, so a push that ships nothing no longer pays a cold store read per held path for rows already deposited: plan_standing narrows the shipped tree by the ledger in place, one retain applied once, and hands the restricted and internal lanes that narrowed tree, which keeps the loot-core lanes remote-blind and plans the same deposits because each lane decides an entry on that entry and what this position holds at its address. measured cold on 66 standing rows at a forge, counts read each time: 66 disk reads with the ledger empty, as before; 50 with 16 rows delivered, where it read 66; 0 gets and 0 disk reads with every row delivered, where it read 66 of each. the plan is pinned field for field on an empty, a partly full and a full ledger holding rows at another remote and for another recipient, green before and after. red under mutation, each restored to green: the old lanes-first order (22 passed and 1 failed, the cost pin at 66 against 50), the dedupe ignoring the remote (20 and 3, pitch.md dropped), ignoring the recipient (21 and 2, f00.md dropped), no dedupe (19 and 4). the timed lane is not narrowed, its dedupe being per recipient. custody.rs, ADR 0012 in a fifteenth amendment and CONTEXT.md record what it costs now. no migration, format byte or wire byte moves, so this owes no deploy. the workspace suite is green (4402 passed over 139 binaries, 13 ignored) (#2208)nothing readable · 5 sealed · 5 internal
- CONTEXT.md gets its Proposal entry checked against the tree. the two sentences the ticket names had already been made true by later lands, so they stay in the present tense and gain the ticket that built them: ingest closing a proposal row from the head set it declares was built by #2177 (3ea3edc) and moved onto propose::landed_by_declaration by #2235 (6844205), and the land reading the required roles off the .lootpipeline on main was built by #2178 (cd10e16). the rest of the entry and one neighbour were not true, and are corrected: the entry said the land refuses without its verdicts, where it refuses on what that .lootpipeline declares and is advisory with no require line there, which is now said; the ingest transaction is no longer called pure writes, which #2273 narrowed in store.rs and ADR 0075 to the landing decision reading nothing inside it; until #2162 becomes when #2162 was decided, since that land was docs only and the producers came with #2174, #2176 and #2177, and the owner read routes and the web views of #2175 are named; an owner cannot propose, which nothing enforces for a new row, becomes the reason the declined arm exists; may_push is named as the rule require_pusher decides, since no function carries that name; the reaper sentence names #493, where an inserted paragraph had left that reaper without its antecedent; and Foreign authorship said loot-first reads no change author, false since #2178, now in the past tense. docs only: no code, migration, format byte or wire byte moves, so this owes no deploy. CONTEXT.md holds 0 CR bytes, and the tests that read it are green, published_surfaces (6 passed), opt_in_halves (4 passed) and work_counters (12 passed); the full suite was not run (#2207)
- ↳ supersedes
69e232db· not stored here - Kept, not rewritten: a carry replays a suffix as a new version and the one it replaced stays addressable. That is the difference between a carry and a rebase.
nothing readable · 2 sealed · 2 internal - ↳ supersedes
- the browser SDK budget refusal prints how long the wait lasted beside the limit, as the native refusal has since #2079, and names it as read off the page clock: the wait runs from the moment its timer was armed, the whole request for the answer and the time since the last bytes for a silence mid-answer, so a frozen or throttled tab shows as a wait well past the limit. budgetRanOut and transport_text_from point at each other. a forge loot-forge serves writes the received and answered lines a relay writes under LOOT_NET_TIMING=1, opening loot-net: forge, through loot_net::timing_layer, which the relay now attaches through too; with the flag unset serve_until passes no sink and the forge serves the router app builds with no layer added. pinned by a stalling loopback peer across a blocked page loop and across a silence after a streamed answer, and by a forge with a timing sink. red first (7 passed and 2 failed). red under mutation, counts read each time, each restored to green: the wait printed as the limit (8 and 1), the wait timed from the first arm (8 and 1), the clock label dropped (7 and 2), the forge passing no sink (0 and 1), the forge named relay (0 and 1), the arrival line naming relay whatever the server (0 and 1), the layer ignoring its sink (0 and 1 on the forge, 2 and 1 on the relay). CONTEXT.md and workflow.md record it. no migration, no format byte and no wire byte moves; the SDK refusal text changes and the forge writes lines only under a flag the scripts repo never sets, so this owes no deploy. the workspace suite is green (4398 passed over 139 binaries, 13 ignored), the sdk suite 141 passed over 13 files, tsc clean (#2313)
- ↳ supersedes
2e421180· not stored here - Kept, not rewritten: a carry replays a suffix as a new version and the one it replaced stays addressable. That is the difference between a carry and a rebase.
nothing readable · 8 sealed · 8 internal - ↳ supersedes
- every /private page rendered blank live because the private CSP, script-src self and wasm-unsafe-eval with no unsafe-inline, refused the two inline scripts TanStack writes into every page, its scroll restoration and the per-page $tsr SSR payload; each private document now gets a per-request nonce, minted by a request middleware in the new src/start.ts, stamped by the router on the scripts it writes through ssr.nonce read from that middleware context, and named in the policy the middleware sets on the response, so script-src still carries no unsafe-inline and injected markup cannot run. the start instance restates TanStack default CSRF middleware for server functions, which defining one would otherwise drop. the routes keep the nonce-less policy as the fail-closed fallback, /api/private/* and public pages carry no nonce, and /private now renders under vite dev too. a new test starts the built server and checks every inline script on three private pages carries the nonce its own response names, that nonces differ per response and that a public page has none; red under mutation, counts read each time: the router never given the nonce (3 failed, 2 passed), the middleware writing a policy without it (4 and 1), a constant nonce (1 and 7), unsafe-inline beside the nonce (1 and 7), the path test matching /privateer (1 and 7), each restored to green. ADR 0094 and CONTEXT.md are amended; 777 site tests pass; no migration and no forge change, site deploy only (#2335)
- ↳ supersedes
a44b1f70· not stored here - Kept, not rewritten: a carry replays a suffix as a new version and the one it replaced stays addressable. That is the difference between a carry and a rebase.
nothing readable · 9 sealed · 9 internal - ↳ supersedes
- the review-sweep fix-up over #2307, #2176 and #2193. a seek busy marker is believed while its clock is within the window of the reader clock on either side, so the round that claims first with the later clock is no longer taken over, and a marker that does not parse is dated by its mtime against that same now. loot propose --decline takes the version it declines, the full tip --show prints or a selector naming a version held here, following the grammar that already reads a version where a change id is expected; a change id is refused, the CLI finds the row standing at that tip and refuses when none visible does, so a revision made after the owner read the proposal is not declined. two act flags on loot propose refuse through ProposeAct::from_args, where the first one checked ran. the format-constant pin compares the marker of each trailer and act payload with the one a client from before #2159 wrote, recorded in the frozen codec copy, so a bump of either constant reddens it; a push declaring each head its own kinds is pinned through a two-head repo; the decline cost pin asserts the whole call sequence with the gate calls measured alone; the forge door tests ask every route in Route::ALL, now public, that the router mounts. each GATES row declares what its finding is read off (Judged): a finding off files gets a merged remedy that goes to loot edit at once, with no re-run and no file lock, and the pre-land pass rather than each finding says nothing has been signed or published, so the merged pass no longer prints it beside a signed merge; the #2193 census reaches each spawn after passing ones and holds every Judged declaration against the spawner. false sentences fixed in ADR 0055, 0075, 0090 and 0091, workflow.md, npm_not_started and the census doc, including a network-specific NAT64 prefix, which the wake-up rule cannot recognise; the forge refusals of withdraw and decline spell the tip as a version id; the cmd_propose doc sits on cmd_propose; the wake slot test waits for its places to come back. red under mutation, counts read each time, each restored to green: a marker ahead of the clock read as dead (2 passed and 1 failed), the unparseable marker dated by the system clock (2 and 1), the decline signing the tip the forge holds (4 and 1), a change id accepted (4 and 1), two acts not refused (4 and 1), FORMAT_MINOR at 1 (1 and 2), the push unioning kinds (0 and 1), a second read in the decline (0 and 1), /propose/read mounted without the door (0 and 2), the pre-land state line dropped (2 and 1), the files remedy replaced by the run remedy (2 and 1), the second perf spawn failure a finding (2 and 1), the site row declared Files (2 and 1). no migration, no format byte and no wire byte moves; the forge refusal wording changes, so that is live once the forge is redeployed, the decline argument once the CLI is released, and a land runs the primary release loot-first, so the gate changes are live only once those binaries are rebuilt. the workspace suite is green (4392 passed over 139 binaries, 12 ignored), and green under bash ci/local.sh against Postgres 18 (#2325)
- ↳ supersedes
5171f1ac· not stored here - Kept, not rewritten: a carry replays a suffix as a new version and the one it replaced stays addressable. That is the difference between a carry and a rebase.
nothing readable · 22 sealed · 22 internal - ↳ supersedes
- ADR 0095 records the grilling on #2181: a proposal gets a conversation and not only a reason on its decline, and each comment is an envelope signed by its author and stored on the forge with the proposal rather than as a note on the attestation lane, since ADR 0084 refuses a note on a sealed change, a contributor has no door into the owner's lane and a late attestation does not travel (#2251). a thread is metadata-tier plaintext with exactly its proposal's audience, disclosed before posting; the parties write always and anyone the proposal door admits besides, never a bare keypair; a comment binds the tip version, optionally a path, not yet a line; the author edits in place under an edited marker or withdraws, which drops the text and leaves a tombstone, and the owner alone moderates, withdraw-only; threads are flat with an optional reply_to, live with the row and stay open in every state; one Rust writer route with CLI flags and browser-signed posts, read through #2174 and a pinned view family; Markdown sanitized with no HTML and no images, capped at 16 KiB, images later and never external; the timeline reads recorded acts where they live; and the owner approves from the web through an owner-only door on top of #2251 with a record byte-identical to loot attest. CONTEXT.md gains Proposal comment. graduates as #2331, #2332, #2333, #2334 and #2336. docs only, no code moves (#2181)
- ↳ supersedes
6e33c395· not stored here - Kept, not rewritten: a carry replays a suffix as a new version and the one it replaced stays addressable. That is the difference between a carry and a rebase.
nothing readable · 2 sealed · 2 internal - ↳ supersedes
- the tutorial interrupt handler halts the writers the tutorial started before it sweeps the tree: a step loot subprocess is spawned, registered and reaped under a lock the handler takes and never releases, so the handler kills and reaps it and the walk blocks at the lock rather than fail its step and exit beside the handler, and the relay thread runs under loot_net::serve_until, a new stop that closes the listener and lets the requests in flight finish, which the handler asks for and waits on within 2 s. reproduced on Windows before the fix: with nothing injected, 7 of 15 runs interrupted as the clone started ended wrong (exit 1 with no handler line, a refusal, or the removal line over a tree holding config/loot/telemetry, which a loot process writes through create_dir_all), and 15 of 15 ended right after it; the relay thread held by temporary sleeps in its start-up or in a stow across a held handler put the relay store back under the removal line 5 of 5 each, right after; a subprocess in a process group of its own, standing in for kill -INT on unix, held the tree 5 of 5, right after. the halt adds under a millisecond at an idle prompt. serve_on, and so loot serve, keeps plain axum::serve. pinned by an interrupt mid-clone over three concurrent runs, a relay halt that leaves nothing serving, and a halt that ends a subprocess nothing signals. red under mutation, counts read each time: the handler without its claim and halt (6 of 6 runs red, 15 of 18 inner runs wrong), the relay started without a stop (0 passed and 1 failed), the halt skipping the relay (0 and 1), the halt without its kill (0 and 1), the holder name selecting nothing (0 and 1), each restored to green. no migration, no format byte and no published wording moves, so this owes no deploy. the workspace suite is green (4397 passed over 138 binaries, 13 ignored) (#2311)
- ↳ supersedes
aa8d13ce· not stored here - Kept, not rewritten: a carry replays a suffix as a new version and the one it replaced stays addressable. That is the difference between a carry and a rebase.
nothing readable · 4 sealed · 4 internal - ↳ supersedes
- the private repo surface becomes a workbench: /private/<owner>/<repo> is one layout route that mounts a VS Code-like shell once behind one gate (activity bar, side panel, preview and pinned tabs, status bar, Ctrl+P quick open) and keeps it across navigation, each child route naming its document with OpenDocument rather than rendering a page; a view is one module plus its routes, WorkbenchView in components/workbench/view.ts is the contract and VIEWS the one list the shell derives from, and the shell names no view. two views ship, Files (explorer over one new /api/private/manifest read, file editor with gutter, minimap, #L links and per-file decrypt, folder overview) and History (ledger panel, ledger, path and two-sided diff documents moved from the old pages), History built second against the contract and needing no shell edit, which is the test of the operator requirement that new views such as proposals and tickets be easy to add. /api/private/repo answers the header without a tree read, /api/private/tree and the page chrome it fed are deleted, every URL #1043 served still resolves. ADR 0094 and CONTEXT.md record it; 768 site tests pass, each new test red under a named mutation and restored; no migration, no format byte, no forge change, site deploy only (#2327)
- ↳ supersedes
afbb0e01· not stored here - Kept, not rewritten: a carry replays a suffix as a new version and the one it replaced stays addressable. That is the difference between a carry and a rebase.
nothing readable · 61 sealed · 61 internal - ↳ supersedes
- a forge push stows the delta past the forge heads rather than the whole change delta: push round zero now asks a forge on /ref, the route the closing declaration already reads, where RemoteSync::haves answered None for a forge and so negotiated nothing, and stow_batches, the round-zero and round-one planning lifted out of push_with, scopes the object offer and the first /stow batch by the closure of those heads, as it already did for a relay (#728) and as loot propose already does. it strands no object: a forge answers /wants from its object rows, and a change row exists there only once every object its tree names has a row, because prepare resolves against those rows, /stow defers what it cannot resolve, /ingest refuses it and a declared head must be held, and no MetadataStore method removes an object row today; so the scoped offer is asked for exactly what the unscoped one is, pinned against a real forge, and an interrupted push moves no ref, so the next push offers its change again, pinned by stowing one batch of three and reading the other two asked for. given up, and recorded at the code: a forge holding a head without its ancestry, the shallow push ADR 0089 section 6 refuses at the client, is no longer re-sent the changes it lacks. the cost is a /ref round trip ahead of /wants; a forge whose /ref cannot be read is planned the full delta as before, and the unscoped plan an older client sends is still taken, since no forge line, route or wire byte moves. read on the real binary against a real forge, 16 paths, one change pushed over a history the forge holds: /stow bodies of 8,873 B over 7 changes and 16,657 B over 15 before, 2,097 B at both after, and /wants bodies of 835 B and 1,091 B before, 611 B at both after; on the bundles, 24 paths, 13,405 B over 9 changes and 23,981 B over 17 before, 2,861 B over 1 change at both after. red first (14 passed and 1 failed). red under mutation, counts read each time, unit then wire: the forge not asked (2 passed and 1 failed, 0 and 1), the bundle unscoped (2 and 1, 0 and 1), the offer unscoped (3 and 0, 0 and 1), the own heads taken as the forge heads (1 and 2, 0 and 1), and an unread /ref taken as an empty head set (2 and 1, 1 and 0, a reading that plans the same full delta), each restored to green. CONTEXT.md and ADR 0024 record it. no migration, no format byte and no published wording moves and the forge is untouched, so this owes no deploy. the workspace suite is green (4390 passed over 139 binaries, 12 ignored) (#2308)
- ↳ supersedes
40fe786d· not stored here - Kept, not rewritten: a carry replays a suffix as a new version and the one it replaced stays addressable. That is the difference between a carry and a rebase.
nothing readable · 5 sealed · 5 internal - ↳ supersedes
- a gate row whose program could not be started now refuses as not run rather than as a finding, the cargo re-run after a first run that reached diagnostics excepted as #2071 decided: impl From<String> for Refusal is gone, so each GATES row names the kind of each refusal where it builds it. spawn::require takes its refusal type from the caller, so the npm gates build a NotRun in the spawn-failure closure and a Finding in the non-zero one, and the lockfile and perf gates carry their spawn failures the same way; gates::guard_with now answers gates::Refused, the CliError plus unstarted, read off the runner outcomes (true when each gate that did not clear never started), which loot new and loot push convert with the same ? and the user-gates row maps to NotRun. an npm step that could not be spawned refuses in its own words, naming the gate, the step, npm as this platform spawns it and the operating system reason, in place of the step failure advice; the offline degrade does not cover it, since it classifies the output of an npm ci that ran. the merged pass gives any NotRun regate_not_run_remedy, which names no program and no longer claims that nothing was built or run, that no gate judged the tree, or that the cause is the machine rather than the tree, each false for some row that now reaches it; a program that never started is re-run nowhere. pinned by a census over GATES that drives each row with every spawn failing (a row that reached the spawner refuses NotRun naming the program and the reason, spawns nothing more, and on the merged pass gets a remedy with neither the ADR 0055 story nor the program) and by its positive control with every step failing (still a finding, still the story on the merged pass); the finding prose of each spawning row was captured before the change and is byte-identical after it. red first against the unchanged code (1 passed and 1 failed). red under mutation, counts read each time, each restored to green: the user-gates row always a finding (1 and 1), always not run (1 and 1, the control), the npm install spawn failure a finding (1 and 1), that spawn retried once (1 and 1), the npm local step spawn failure a finding (0 and 1), the lockfile spawn failure a finding (1 and 1), the perf build spawn failure a finding (1 and 1), the merged NotRun given the cargo clean remedy (1 and 1), given the ADR 0055 story (1 and 1), the npm refusal not naming its program (1 and 1), unstarted read as any rather than each (0 and 1), unstarted forced false (0 and 1), a malformed declaration read as unstarted (0 and 1). ADR 0055 gains a 2193 amendment and the land-change skill no longer names the From default. no migration, no format byte and no published wording moves, so this owes no deploy, but a land runs the primary release loot-first, so this is live only once those binaries are rebuilt. the workspace suite is green (4385 passed over 138 binaries, 12 ignored) (#2193)
- ↳ supersedes
bc463cfe· not stored here - Kept, not rewritten: a carry replays a suffix as a new version and the one it replaced stays addressable. That is the difference between a carry and a rebase.
nothing readable · 6 sealed · 6 internal - ↳ supersedes
- loot-s3 gives an object-store request a budget that grows with its body in place of one 60 s deadline over the whole transfer. a get now reads its answer read by read, so the 60 s client timeout, renamed IDLE_BUDGET, bounds silence and not length: an answer that keeps arriving is read however long it takes, and one that stops fails after IDLE_BUDGET and names how many bytes it got. a request that sends a body is held, as its own request timeout, to upload_budget: IDLE_BUDGET plus the whole seconds of the body at UPLINK_FLOOR_BYTES_PER_SECOND, a copy of the native uplink floor of 256 KiB a second. reqwest blocking sends the body inside the one wait for the answer to begin, so an upload can have a deadline and not a silence limit, as in loot-net (#2079), and that reasoning is recorded at the code. the formula is copied from loot_net::wire::transfer_budget with pointers both ways rather than moved to loot-core or reached through a new loot-net edge: the copy drops the rounding that keys a connection pool and the cap a loot host enforces, bounds the send alone, and a shared home would tie the rate for a client link to a loot host to the rate for a server link to its bucket. no rate for that link was measured, since no script in the scripts repo measures one; the floor is the native choice, not a measurement. a timeout on each wait names its budget and what it covers. pinned over loopback peers: a large get that stalls mid-body, a get whose answer keeps arriving past the idle budget, a put the store reads slowly past it, a large put the store stops reading, and the formula on its own. red first (13 passed and 5 failed). red under mutation, counts read each time: the get read by one bytes() wait (17 passed and 2 failed), no deadline on a request with a body (17 and 2), the body earning nothing (16 and 3), the body capped at 64 MiB (18 and 1), part seconds rounded up (17 and 2), a deadline on every request (18 and 1), a read timeout not recognised (17 and 2), the put deadline not named (17 and 2), each restored to green (19 passed). no migration, no format byte and no published wording moves; loot-forge links loot-s3, so this is live once the forge is redeployed, and loot-relayd links it too but the relay the scripts repo deploys is loot serve, which does not. the workspace suite is green (4370 passed over 138 binaries, 12 ignored) (#2309)
- ↳ supersedes
cf9ba2d5· not stored here - Kept, not rewritten: a carry replays a suffix as a new version and the one it replaced stays addressable. That is the difference between a carry and a rebase.
nothing readable · 3 sealed · 3 internal - ↳ supersedes
- the repo owner can decline a proposal: propose::decline beside withdraw, POST /propose/decline, and loot propose --decline <change-id>, the flag named as its siblings on the verb each spell their act. a declination is signed over the repo, the change id and the tip, and tagged Act::ProposalDecline so it can never be read as a bound withdrawal or a read; on the wire it is a bound withdrawal plus the tag byte, and neither format constant moves. the owner is the key require_pusher admits, the rule jobs already asks for an approval; a member reads the proposal and is refused in words naming the author withdrawal as the other route, and a key the read gate conceals the row from gets one sentence whether the row exists or not. may_propose is not asked, so a shut door does not stop a decline; a repeated decline is the same event and is not re-dated, a landed row refuses by name, and a withdrawn row refuses as the withdrawal refuses a declined one. a declination replayed after a revised re-proposal is refused by the tip binding, the same stack re-proposed is declined again, and one signed for another repo of the same owner is refused by the repo binding. the CLI signs the tip the forge reports at the moment of declining and prints it. uncontended, a decline is one close_proposal and no graph read, pinned on the store call log. it touches no job made for the declined tip, which ADR 0091 leaves undecided. ADR 0075 records the build and what a decline does to the bytes, with CONTEXT.md and the forge spec. red under mutation, counts read each time, each 0 passed and 1 failed and then 1 passed restored: the owner check skipped, the visibility gate skipped, the repo binding removed, the tip binding removed, a declined row refusing re-proposal, a landed row declinable, a withdrawn row declinable, the decline walking the stack, the route asking may_propose, the act tag dropped, the tip left out of the signed bytes, and the declination written under its own minor. no migration and no format byte move; the forge binary changes and an older forge answers 404 on /propose/decline, so this owes a forge deploy before the flag works against the live host. the workspace suite is green (4376 passed over 139 binaries, 12 ignored), and green under bash ci/local.sh against Postgres 18 (#2176)
- ↳ supersedes
6ffacfde· not stored here - Kept, not rewritten: a carry replays a suffix as a new version and the one it replaced stays addressable. That is the difference between a carry and a rebase.
nothing readable · 13 sealed · 13 internal - ↳ supersedes