Changes touching this path
- 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)
49f7a113 · dbf3dbe6… - 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)
414ba6b2 · 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 - 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)
38e8b55a · 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 - 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.