Changes touching this path

  • a contributor can offer work to a repo they do not own: loot propose over a three-path family - posture, propose and withdraw - and none of them is the ref-moving endpoint, which stays single because ADR 0075 refused putting the contribution path one flag away from the path that advances someone's tip. The bytes ride /stow unchanged, since the forge's stow literally calls the same push, so the batches are byte-identical and there is no new transport. One premise had to be WIDENED and the reason is a real replay: the ticket says sign the claim exactly as the head declaration does, but a head declaration names no repo and does not have to, because require_pusher catches a replay at another namespace - while a proposal is BY DEFINITION a write by a non-owner, so every repo with an open door admits the same signer, and a tip-and-base-only envelope is replayable by any observer at every one of them, in the contributor's name. The claim therefore carries the repo path and the server compares it against the URL's namespace, proved by RUNNING the replay and watching the second namespace admit it rather than by arguing it. The disclosure is an ORDERING rule and the posture route exists only to make it one: a client that learned metadata_public from the act's own answer could only ever speak afterwards, so posture comes first, the disclosure is printed second, and only then the ref read, the stow and the claim. It is pinned twice - once by a transport that records every call into the SAME trace as the output lines, so the assertion compares positions rather than presence, and once by byte offsets in real CLI stdout against a real forge - and a shut door still discloses first and sends nothing at all. Withdrawal is the tip change's AUTHOR and only them, where visibility admits three readers, because seeing is not acting and the carrier's remedies are the author's withdrawal or the owner's decline; it does not consult the door, since a shut door must not strand what is already offered, and absent and concealed refuse in byte-identical sentences. The lane refusal keeps every one of its old assertions, including that nothing reaches the forge before it fires, and gains the sentence naming the alternative. Twelve breaks were each run and read before being restored byte-identical, and the ones worth naming are the replay above, deleting the author check so a presenter can withdraw work they did not write, adding the door to the withdraw path so a shut door strands an offer already disclosed, and declaring the tip as a head so the CAS counter moves. The ref-moving path is asserted untouched rather than described as untouched: a proposal leaves generation and head set alone, and the SAME fixture then lands a change the ordinary way and the generation does move, which is the control (#1651) 83c5afa5 · dbf3dbe6…
  • a proposal row gains an owner, because it had none: /propose asked whether the signer may propose here and never whether this ROW was theirs to write, so both stores upserted unconditionally with the state reset to open and only landed excluded - which made the ban-and-quota handle rewritable by any account holder who could present the same change id, and made the author's withdrawal reversible by a stranger. It was proved by running the hijack rather than by reading, with a throwaway witness that printed the takeover happening: state Withdrawn with the presenter as proposer before, state Open with the hijacker as proposer after. The rule is the party whose act would be overwritten and nobody else, and it is refusal FIVE because it needs the change id the byte refusals produce. Open lets the PROPOSER amend, which is the carrier re-presenting an updated stack and is the legitimate case the rule cannot simply forbid, so it carries a positive control. Withdrawn lets the AUTHOR alone reopen, because a withdrawal is the one-actor decision withdraw already makes and letting the carrier undo it reaches that decision from behind. Declined lets EITHER party, and that asymmetry is argued rather than assumed: the decline is the OWNER's act and an owner cannot propose, so a stricter rule would leave a declined change id with no exit at all, which is precisely ADR 0075's honourably re-proposed arm. Landed is untouched and stays the store's own invariant rather than being restated. Every party is read from the row ON FILE and never from the presented tip, which the caller chose. The refusal is kept from becoming an existence oracle twice over: concealed and visible-but-not-yours collapse into one sentence that names no term of the row, asserted byte-identical across two keys and both settings of metadata_public with a loop proving it names no proposer, author, tip or state - and the structural half is the stronger one, since both entitled parties are already standing readers, so the write gate is a PROPER SUBSET of the read gate, pinned with the owner as the control because the owner reads and is refused writing. One instruction was subtly wrong and is recorded rather than followed: withdraw pairs ABSENT with concealed, but /propose has no absent refusal at all since an unused change id is ADMITTED, so the assertable pair is concealed against not-yours and the residue - that being refused says the change id is taken - is stated in the amendment instead of being claimed away. The disclosure stops overclaiming: it said nothing has left this machine yet while a SIGNED posture read had already gone and a test welded that sentence, so it now names what went and what it told the forge. And burn stops calling a proposal a push, which is the rule disclosure.rs states two files away, with is_disclosure and the tier token both untouched and a test asserting the prose moved where the token did not (#1746) db0f3142 · dbf3dbe6…diff
  • the skew this ticket reported never happened, and the trap that produced the report is the finding: a land pushes with the loot.exe sitting beside the running loot-first.exe, which is the primary's last cargo build --release, not the tree being landed - the only build a land performs is the perf gate binary in the position. So the land that RAISES FORMAT_MAJOR pushes with the major before it, and reading the constant out of main's source is not reading it out of the pusher. Verified by timeline rather than argued: both pushes ran at 23:26 and 00:08 while the primary's binaries date from 01:28 the next morning, so the pusher predated all three lands and wrote major 11 to a relay serving 11. Two further claims in the ticket were false and are refuted by the tree rather than reasoned away. stow DOES version-check, at the frame decoder before a single object is stored - handle_stow to RelayStore::stow to DagRepo::stow_from, whose first statement is Frame::decode and whose UnsupportedFormat maps through NetError::Engine to a 400 - and it was already pinned by a green test when the ticket was filed, so the acceptance criterion asking for it was asking for something that shipped. Which means the observed 200 and its byte count are themselves proof the marker was readable, since StowReport is constructed only after stow returns Ok and put_version writes the compile-time constant unconditionally. What survives is worth landing on its own merits and not as this incident's cause. The fail-open branch stops being silent: relay_can_read_our_writes returns Checked or Skipped carrying the probe's own failure string, push returns that beside the report, and cmd_push prints a warning to stderr where the refusal would have stood - a value on the result rather than a log line, because the broken pairing was a promise asserted by the path that fires and inverted by the path that does not, and a caller may decline to say a returned thing but cannot fail to be told it. The new test is the first that watches the wire at all: every prior skew pin simulated one by moving the CLIENT to FORMAT_MAJOR plus one, a value production never has, and none of them observed a byte - this one stubs a relay that reports a chosen major, asserts the request log is exactly the probe with zero bytes sent, and carries its positive control in the same function so that no bytes recorded and this harness cannot record bytes stop being the same observation. Two mutations were each verified present in the file before being run red. And the doc comment on relay_push now names the trap rather than the code, because the code was correct every time (#1768) 25a5a122 · dbf3dbe6…diff
  • sweep 1 fix-up over #1826, #1837 and #1839: a live change only a superseded head reaches is listed in the fork view, in a third section of its own, rather than in no section at all. LogGraph::retain_heads leaves such a change with an empty reachable_from, and history() partitioned the fork on exactly one head and more than one, so after a move --onto the ancestor the old tip alone reaches was printed nowhere - reproduced first through the spawned binary with two live lines (0 passed, 1 failed), the fork and the non-empty superseded head both holding as controls. GraphHistory gains unreached, the renderer prints it under behind a superseded tip after the shared ancestry, drawn_rows leads with it because such a change can only be a child of a lane or shared row, the path filter, retain_versions and all_rows walk it, and both machine shapes carry the row with no new column. HEAD on an untracked position with no working change now counts the heads Liveness::is_superseded rejects and answers the sole counted head by name, because Position::anchor answers the first graph head, which can be the superseded one; measured at the other two sites the ticket named, propose already reads is_live and IntegrationPreview deliberately counts the whole partition with retire for the superseded ones, so the liveness doc now points at the callers of the predicate rather than listing two readers. the move --onto usage line gets its space back; refit_shape spells its path columns through treepath::store_key with a backslash pin, and the #2033 census records the tree-path axis it is blind to by design; the shallow clause has one spelling, workspace::parents_not_held, read by the notice and the push refusal, with the pin the suite lacked; carried is carried_count, Reanchored.change is change_id, the squash and absorb op-log lines say re-anchored, frontier_beyond_the_host is private, the one-flag-member sentences drop one, ADR 0089 names both readers of .loot/shallow and both frontier spellings, and the one raw exit ADR 0089 rests on is held to one by a census in tutorial.rs. own > 0 for the move --onto stale head is pinned off the loot heads row. red first, six mutations in one round over disjoint pins: the unreached filter blinded, drawn_rows reordered, the HEAD filter blinded, store_key undone and a second raw exit added redden five pins with the sync shallow control green (1 passed, 5 failed) and the spawned fork pin (2 passed, 1 failed); the noun-verb swap survived tests/shallow (10 passed, 0 failed), which is why the clause has a pin now (0 passed, 1 failed under the swap). ADR 0066 keeps its gated 28 and points at the gate instead of at tickets. no migration, no wire or format byte moves, and no forge or relay byte moves, so this owes no deploy. the workspace suite is green (3895 passed over 122 binaries, 7 ignored) (#2046) 0b55a20f · dbf3dbe6…diff
  • the owner can finally see what was offered to them, and a read request had to stop being a withdrawal in disguise: propose::list had no caller outside its own tests and propose::read only handle_propose_withdraw, so ADR 0075's store half was built and unreachable and an owner could see nothing. /propose/read and /propose/list join the contribution family with loot propose --show and --list on the CLI side, flags rather than a verb because a new verb trips seven censuses here and a flag does not. the finding the ticket did not anticipate is that a read naming repo and change id would have encoded BYTE-IDENTICALLY to a withdrawal, and the route lives in the URL rather than in the bytes the envelope signs, so a captured read envelope from the tip's author - the one party withdraw admits - could have been re-posted to /propose/withdraw and ended their own offer, a non-destructive act becoming a destructive one with no forgery at all. every new payload in this family now leads with an act tag and the replay is run in both directions rather than argued. the reads dispatch before run and touch ProposeTransport nowhere, because that seam exists to make the propose-time ordering assertable and a read has no ordering to assert, and --show resolves its handle before the remote and before the key so a mistyped id is answered by the refusal for the thing mistyped. terminal rows are listed rather than filtered at the last hop, an empty list says visible to you rather than claiming the repo has none, and an underivable tip is rendered beside its row rather than as a refusal. the propose-time disclosure gains the presenting key, which ADR 0075's #2162 amendment decided is world-visible on a metadata-public repo and which the old sentence omitted while naming the author. two enums share one vocabulary by WORD rather than by numbering, pinned total and injective, because loot-forge depends on loot-net and never the reverse. three censuses refused and each was right: the dispatch table lost a row to a multi-line verb declaration, a source-walk anchor spelling [Route; 16] went stale and is now count-free per the rule its own header already states, and the selector census demanded --show declare its kind, which is evolog's - one proposal by construction, borrowing evolog's resolver rather than growing a fourth. red under mutation, counts read each time: the act tag dropped (118 passed and 1 failed), the handler telling concealed from absent (422 passed and 1 failed), the list filtering terminal rows (422 passed and 1 failed) and the disclosure reverted to naming only the author (11 passed and 1 failed), each restored to 119, 423, 423 and 12. no migration and FORMAT_MAJOR does not move, but two routes are new, so an old forge 404s them and this owes a forge deploy before the flags answer against the live host. the workspace suite is green (4156 passed over 132 binaries, 8 ignored) (#2174) 09b5ad09 · dbf3dbe6…diff
  • a withdrawal binds the tip it was written for, and the format minor deliberately does NOT move: a withdrawal names a proposal by its durable change id, which is stable across revisions on purpose, so a captured envelope replayed after a REVISED re-proposal ended an offer its author never withdrew - and the author is the only party withdraw admits, so the captured envelope is exactly the one that works. the signed bytes now carry the tip, compared inside the retry loop against the row that round actually read rather than once outside it, which is the same window #1959 closed for the author check. the replay is run end to end: the author withdraws at one tip, revises and offers again, and the byte-identical envelope is refused with the proposal left open, while a withdrawal naming the version actually on file still works, so the refusal is the binding and not a wall. ⚠⚠ the ticket instructed a FORMAT_MINOR bump and following it would have been a mistake, which is the finding worth more than the field: the marker's minor is ONE GLOBAL CONSTANT stamped onto every artifact rather than a per-payload version, and persist_codec's is_canonical compares a persisted store against the major and minor pair to decide whether the graph file must be REWRITTEN, so bumping it to announce one optional field on one wire message would force a rewrite of every local store on every machine. every read_version caller discards the minor, so the bump buys no reader anything the trailer's presence does not already give, and a test pins both constants unmoved with that reasoning beside it. the same plan sits on #2159 for its kinds trailer and is flagged there rather than left. an older client sends no tip and is accepted unbound, exactly as safe as before the field existed and no less, since an attacker cannot cause that shape and can only replay one already signed - so the exposure shrinks to withdrawals old clients signed rather than widening. both interop directions are pinned: an old payload decodes here reporting no binding rather than inventing one, and a new payload is byte-identical to an old one up to the trailer, which is what every deployed forge reads. red under mutation, counts read each time: the tip comparison made unreachable so the replay succeeds (430 passed and 1 failed, restored to 431). ⚠ three rounds of missed call sites, each invisible to the scope before it - a package lib run compiles neither integration tests nor other packages, and only the full suite answered. no migration, FORMAT_MAJOR and FORMAT_MINOR both unmoved, and the forge behaviour moves, so this rides the next deploy. the workspace suite is green (4169 passed over 132 binaries) (#2180) 7f2cbeca · dbf3dbe6…diff
  • 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 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) b45cbbf3 · 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
  • 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) facefe81 · dbf3dbe6…diff
  • 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) 6af9b14f · 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
  • 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) c8d5a6ea · dbf3dbe6…diff
  • 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) 01737140 · dbf3dbe6…diff
  • 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) f168d689 · dbf3dbe6…diff
  • 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) eceef7eb · 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.