Changes touching this path
- the forge gains the runner routes, part 1 of 2 of this ticket: the owner registers, retires and lists runner rows through /runners/add, /runners/retire and /runners/list, and a runner claims a job and answers it through /jobs/claim and /jobs/verdict, each with its client half in the new loot_net::runners. a runner key is refused at push and propose by absence and admitted at the claim by its row, and since the live operator door lists the owner alone a route a runner calls passes a delegated door, server::authenticate_runner, which admits a live row only while its registering key still passes the operator list, asked every request, so an owner the operator stops listing takes their runners with them. a verdict is accepted from the job claimant whatever the lease says and refused from anyone else, including a runner whose expired claim another has since taken, which narrows ADR 0091 section 7 and keeps its reason; the compare runs inside the store write, FOR UPDATE in the driver, and the signed pass is written in that same write into the attestation table, after the route checks it is present iff the job passed, verifies, and is this runner pass over this version under this job role. retirement is now terminal per key, because the 2157 upsert rewrote retired_at and a forge envelope carries no freshness, so a replayed add could bring a stopped key back; an account key, the owner own included, is refused as a runner; the claim filters by the runner trust floor through Trust::admits, one predicate for both ends; detail is capped at 16 KiB, a log tail at 256 KiB stored by its hash, a lease at 24 hours, a scope must reach .lootpipeline, and a wake-up URL must be https to a host that is not literally private, with the resolved-address check left to the sender in 2159. JobOutcome, the pass roles and the pipeline file name move to loot_net::pipeline and are re-exported, and the forge act tags become one repr u8 enum so a duplicated tag cannot compile. the job.log_oid cross-reference to 1720 is written in migration 0018 as a comment, so its checksum is unmoved. measured on a throwaway Postgres 18 on the desktop and not the VPS: the claim is an index scan of job_claimable at 0.14 to 0.17 ms over 50000 jobs, 4.0 ms at worst over 20000 inadmissible candidates, and one polling runner gets about 139 claims a second while 32 level off near 2150. red under mutation, counts read each time: fourteen named mutations through the routes and the reference store each went red at 0 passed and 1 failed or 1 passed and 1 failed with the pg stamp skipping, one of them only after a first attempt proved too weak to be a mutation, and two driver-only mutations on Postgres 18 went red at 0 passed and 1 failed, each restored green. bash ci/local.sh is green against Postgres 18 (4243 passed over 135 binaries, 10 ignored). no migration and no format byte moves, and the forge needs a deploy for the routes to exist (#2158)
8dc22d7d · dbf3dbe6… - loot runners lists the runner rows a forge holds for this repo, part 2 of 2 of this ticket: name, key, the step kinds each may claim, live or retired, and when the forge last heard from it, owner-only because it is the owner configuration, retired rows included because who was allowed to run a version is what an audit asks. last seen needed a column, migration 0021 runner.last_seen_at, written by the runner door on every admitted request and throttled in its WHERE clause to one write per runner per 60 seconds, so a runner polling every second costs one write a minute; a registration never sets or clears it, so a runner registered and not started reads never seen, and deriving it from job rows was rejected because an idle runner finding no work leaves none. the first spelling of that throttle, last_seen_at <= $3 - $4, failed on Postgres 18 at the first touch, which would have made every runner request a 500 on the live forge while every in-memory test passed; the edge is computed in Rust now, and landed part 1 never wrote the column. the shape is born frozen under ADR 0023: porcelain pubkey state capabilities last_seen and the name last because it may hold a space, JSON with contract, remote and one object per runner, absent times as null, both from one value and pinned byte for byte. the verb census cost, counted: MACHINE_OUTPUT gains runners, the README block and its sentence move to 85 verbs, verb lines 133 to 134 at three main.rs sites and one ADR 0066 claim, dispatched verbs 84 to 85, dispatch table 83 to 84, machine output 32 to 33 with its named list, and two ADR 0066 lines that are dated records were left alone as the census skips them; the site verb list gains runners and its gate is green. the verb runs end to end through the spawned binary against a real forge. red under mutation, counts read each time: the touch unthrottled (1 passed and 1 failed), the door never touching (0 passed and 1 failed), the name not last (0 and 1), an absent time spelled as a dash in JSON (0 and 1), and a registration allowed to set last seen (1 and 1) once its pin moved into the store, where the defence lives, after a first attempt at the route proved unobservable; on Postgres 18 the throttle removed from the SQL went red at 0 passed and 1 failed after a green unmutated arm, each restored green. bash ci/local.sh is green against Postgres 18 (4258 passed over 136 binaries, 10 ignored). migration 0021 rides the forge binary, so the forge owes a deploy (#2158)
6dd6ae69 · dbf3dbe6…diff - the kinds trailer on /ingest and /propose and the job creation it drives, with the wake-up sender; the runner-deposit reap is not built. loot_net::forge::HeadDeclaration and ProposalClaim gain kinds: a push declares the kinds of the on main steps of its live heads and a proposal the kinds of the on change steps of its tip, read by pipeline::declared_kinds from the .lootpipeline each version holds, as one trailing byte in Kind::ALL order that is not written when nothing is declared, so a payload declaring nothing is byte-identical to the one an older client signs; a decoder reads the byte iff bytes remain, and a bit this build cannot name is dropped. FORMAT_MINOR is not bumped, per the 2026-09-22 comment on the ticket that overrides step 1 of its body (a minor bump makes persist_codec rewrite every store graph file, the #2180 finding), and adding_the_kinds_moves_neither_format_constant pins both constants; a new client payload decodes on a frozen copy of the old decoder and an old client payload decodes here as declaring nothing. /ingest makes a main job per declared head and declared kind, less those on file, inside the ref-moving transaction (IngestTxn::jobs on both stores, with a conformance case run on memory and on Postgres), and /propose makes a change job per declared kind for the tip once the proposal row stands; member is the author of the version classified by AccessClass::of and approved is a verifying pipeline/approve from a key require_pusher admits, both read once at creation. after the commit each live runner whose row covers a created job kind and carries a URL is woken through jobs::Wake, whose HttpWake calls loot_net::runners::send_wake: the URL checked as registration checks it, the send refused if any resolved address is one registration would refuse (refused_wake_address, which both ask), the checked address pinned, no redirect followed, the request bounded by WAKE_TIMEOUT_SECS. the ticket said the site TypeScript client learns the field, and no code under site or sdk encodes an /ingest or /propose payload, so nothing there changed. the reap is recorded as unbuilt in the ADR 0091 #2159 amendment: grant_inbox is keyed by recipient alone while runner, ref_head, proposal and job are bound to one repo under migration 0017, so a reap run per repo cannot see an address live in another repo the same runner key serves, and how to scope it is a decision left to the reap ticket; nothing writes a runner deposit before #2130. measured: the trailer is one byte when a kind is declared and none otherwise, and ingest::job_cost pins that a push declaring nothing makes no call for jobs and that job creation reads per declared head, not per kind. red under mutation, counts read each time, each 0 passed and 1 failed on a filter selecting its one pin unless said otherwise: the trailer written before the heads, the trailer read unconditionally, the trailer always written, FORMAT_MINOR at 1, every kind made rather than the declared ones (through the push route, the proposal handler and the binary end to end, each), a wake-up sent to every row with a URL, a wake-up sent before the commit, member never set, approved by any attester, the memory ingest dropping jobs, the memory ingest overwriting them, the Postgres ingest dropping them (through bash ci/local.sh, 461 passed and 1 failed in loot-forge), only the first resolved address checked, the binary declaring the kinds of every trigger, the push declaring nothing, approved read once per kind, and the no-kind early return removed; each restored to green. no migration, no format byte and no published wording moves; the forge binary changes, so a forge makes jobs only once it is redeployed, and a forge older than this still accepts a client that declares kinds. the workspace suite is green under bash ci/local.sh against Postgres 18 (4345 passed over 137 binaries, 12 ignored) (#2159)
09cefc35 · dbf3dbe6…diff - the review-sweep fix-up over #2303, #2159 and #2172. the wake-up sender: its client is built with no_proxy, so a proxy named in the environment is not used, pinned in a test binary of its own that sets the proxy variables and sees the request reach the pinned address and not the proxy; refused_wake_address now refuses every address that is not globally routable unicast, an ipv6 address carrying an ipv4 one (mapped, compatible, nat64 64:ff9b::/96, 6to4) judged by the one it carries, so 64:ff9b::a9fe:a9fe is refused, with the refused blocks copied from the iana special-purpose registries at the code and a table of example addresses pinning the rule; name resolution runs on a thread waited on for at most WAKE_TIMEOUT_SECS with the request given what is left, and the wake-ups in flight are bounded by MAX_WAKES_IN_FLIGHT, a resolver that outlives its wait keeping its place until it returns. the ingest kinds trailer is now a byte per head in head order, still absent when no head declares a kind, so each head is given jobs for its own kinds only and no job is made for a kind a head has no step of; the pre-2159 decoder still reads the new payload, the one-byte union shape was in no release, and neither format constant moves, pinned against the marker put_version writes rather than its digits. MetadataStore::ingest answers the jobs its commit wrote (store::Ingested) and a push wakes runners for those alone, so a triple a racing request filed first wakes nobody here; the proposal job half answers no error once the proposal is committed and logs instead. the job_cost no-kind pin now states the property, that the job half calls the store for nothing when nothing is declared, instead of a list of methods. the seek busy marker: one that does not parse is believed until its mtime is past the window, so the instant between its creation and its clock no longer admits two holders, and a marker that cannot be created for a reason other than one standing there is retried and then refused naming the state rather than counted as held; the held-marker comment is scoped to a round that claimed it. the census wire half plants each oracle into every payload sent builds and asks each payload for moving bytes, calls the now-public verbs::sync::forge_declaration instead of a copy, records that the fixture declares no kinds, and records the measured flake rate of its minimum reading (200 runs: 40 of 1600 store builds and 15 of 1600 wire builds above the floor, none red). false sentences fixed: jobs.rs says what a false proposal declaration costs the owner and what the trust floor does and does not stop, ADR 0091 section 7 and its 2159 amendment are corrected with a 2307 amendment and the reads-not-writes scope of the job cost measurement, ADR 0090 and ADR 0004 gain corrections, CONTEXT.md names the per-head trailer, the census module doc states the property instead of naming places, and an in_progress rewrap leftover is rewrapped. red under mutation, counts read each time, each restored to green: no_proxy dropped (0 passed and 1 failed), the 2159 refusal rule restored (2 and 2), embedded forms unrecognised (3 and 1), 6to4 unrecognised (3 and 1), the resolver thread not holding its place (0 and 1), the ceiling ignored (0 and 1), the resolve wait unbounded (0 and 1), the trailer encoding the union (25 and 2), the forge unioning kinds across heads (1 and 3), the memory ingest answering every handed job (4 and 2), the push waking for planned jobs (5 and 1), the proposal refusing after a failed job write (3 and 2), the job half reading with nothing declared (3 and 1), an unparseable marker read as idle (1 and 1), a failed create counted as held (1 and 1), the census reading the bundle alone (9 and 4), every payload a copy of the bundle (10 and 3), the ingest payload emptied (11 and 2). no migration, no format byte and no published wording moves; the forge binary changes, so the sender and per-head jobs are live once the forge is redeployed. the workspace suite is green (4360 passed over 138 binaries, 12 ignored), and green under bash ci/local.sh against Postgres 18 (#2307)
665b3cec · dbf3dbe6…diff - 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)
ff3b789d · dbf3dbe6…diff - 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)
bb95f4fb · dbf3dbe6…diff
Renames are not followed. loot's tree maps a path to an address, so a rename is a delete and an add. This list is the history of the name, not of the bytes.