Changes touching this path

  • the forge learns what a proposal IS, with no verb and no route: migration 0011 adds one table keyed by the tip's durable change id and scoped per repo, with no server-minted id, no base column since the declared base is CHECKED rather than stored in generation_expected's own shape, no extent table because a stack is derived and cannot be declared, and no index because both reads are answered by the primary key. The extent is the walk from the tip along parents to the landed frontier, and landed means the closure of ref_head over parents rather than everything the repo holds - a /stow having written repo_change is deliberately NOT landed, which is the difference between the two admission refusals doing their job and failing open. Refusal one is that the walk must terminate at landed changes, and it is an anti-abuse boundary rather than tidiness: an unrooted stack lets any party donate arbitrary unrelated history into someone else's namespace, unbounded, on a host whose quota producer still has none. Refusal two is that every change in the STACK and not merely the tip carries change_id, author and signature, all three of which are NULLable today because a legacy or unauthored change has none - no durable id is unidentifiable, no author is unattributable, no signature is not a contribution under ADR 0041. Each refusal was proved by deleting it and watching the donated or unsigned row be ADMITTED, and a third breakage is worth recording because it does not fail open: swapping the repo-scoped graph lookup for the global one still refuses eventually, but only after the walk has read another tenant's change_node row and it then blames the wrong thing. The ref_head pin is the decision itself and is asserted at three levels - a before-and-after on the declared head set with controls proving the proposal really is on file and its tip really could have been a head, a check that the migration text never names ref_head guarded by a non-vacuity read of its own prose, and a database-level count over a cluster only the shipped code ever wrote, where proposal tips that are a head of their own repo is zero. The reaper was CHECKED rather than assumed and the answer is the ticket's most important output: #493 is decided and not built, and its live set as specified roots on ref_head plus live publications plus un-acked grant blobs - so an OPEN proposal, which is outside ref_head by construction because that is the decision, is reachable from none of them and would be swept, deleting a contributor's offered work while the maintainer was reading it. ADR 0075's terminal sentence is right and its open half needs proposal.tip as a root, argued once in an amendment and filed as #1720. Visibility is a pure predicate and a concealed proposal reads as ABSENT rather than forbidden; no view was added at all, so the invariant that no forge_read view names grant_log or repo_member survives untouched and the reader holds nothing on the base table. The third viewer, the presenting proposer, is a deliberate widening over ADR 0075's letter of two and is recorded as an amendment rather than a comment: the presenter supplied the whole stack so existence discloses nothing they do not hold, and excluding them makes ADR 0075's own named feature - carrying a colleague's signed change - produce a row its creator cannot see (#1649) bc73a1dd · dbf3dbe6…
  • the view invariant stops being a name filter and becomes a property of the schema: grant_log_is_refused_in_every_form already enumerated pg_views rather than a literal list, but filtered it to the forge_read name prefix, so the eleven forge_owner views migration 0009 creates sat outside it entirely and 0009's own loudest paragraph - that the owner read surface carries no key material, not published_key, not grant_log, not grant_inbox - was prose with no assertion under it. It now holds over EVERY view in public, and the contrast is COMMITTED rather than transcribed: a new case plants a leaking owner view in a scratch database, asserts the exact violation sentence, runs the pre-1727 query beside it and REQUIRES it to still read zero, then drops the view and requires green - so the case cannot later degrade into one that would have passed before. The ticket was wrong about one table, and implementing it literally would have shipped a permanently red test: published_key is not named by no view at all, since forge_read_published_key has named it since migration 0003 as the anonymous tier's key path, gated two hops back on metadata_public plus a live publication. It becomes an exact set instead, which is strictly stronger than the absence the ticket asked for, because it fires both on an owner or member view GAINING key material and on the read view LOSING its definition. repo_member is expressed the same way and is correct before and after #1726 with no edit, because both sides are computed from the same live catalogue - today both are empty, after #1726 both become the member family - and it is not vacuous today either, since the planted owner view puts a name on the left and none on the right. The vacuity guard runs first and is what makes the rest mean anything, because every assertion here is an absence and an absence is true of an empty result: it asserts the read and owner families are both non-empty and that one view from each is present BY NAME, proved by pointing it at a second database migrated only to 0008, where it answers that the family this ticket exists to cover is the one that is missing. The counts are verified from the catalogue rather than from the ticket - eleven owner views, fifteen read views, zero member views, twenty-six in public - and the opt-in limit is stated rather than fixed, in the register #1734 set hours earlier, naming both tests, the env vars they need, the zero LOOT_FORGE occurrences under the workflows directory, and #1735 for the build that would change it (#1727) 19f9c87a · dbf3dbe6…diff
  • the web can now list a repo's proposals, and a pin holds it to the same rule the CLI answers with: migration 0020 adds forge_read_proposal, forge_owner_proposal and forge_member_proposal, each one join onto its family's existing gate view, so none re-derives a gate and each inherits how that gate fails closed, and each family's role is granted its own view and nothing else. propose::visible_to in Rust and these views in SQL are two implementations of one disclosure rule, so tests/proposal_read.rs plants one fixture in a scratch Postgres, pairs each Rust reader with the web reader the site would use for the same person - anonymous, an owner through the account holding a rotated namespace key, an accountless namespace key, a member through its roster row - and demands the whole row set match, terminal rows included, with a vacuity guard that every class but the anonymous reader of a private repo sees rows on both sides, and an executable red that rebuilds the anonymous view without its gate and requires the pin to name that class and repo. THE KEY ARM IS PINNED RATHER THAN SKIPPED: visible_to admits a proposer and a tip author by key and no family names a key, so the web is narrower than the CLI by exactly the rows a key presented or authored and never wider, and the test fails if the difference is ever anything else. the family set is derived in migrate.rs from the earlier grants rather than listed, so a new barrier family meets the text guard and must carry its own proposal view or change it with a reason. a cleared gate answers no rows rather than raising, through the views that inherit it. the plan at 10000 proposals over 100 repos reaches proposal through proposal_pkey on repo_id for the owner and anonymous views, which were measured, with no sequential scan of proposal and no per-row author lookup, since the views carry no author; the member view joins proposal the same way through its gate and its plan was not taken. member_read's row-security set gains proposal, which a member view now reads, and drops the counts beside it; 0011, 0015, ADR 0075 and visible_to's doc now point at the second implementation. red under mutation, counts read each time, against a PG18 throwaway cluster: the owner view dropping terminal rows (0 passed and 1 failed), the member view reaching the owner gate (0 passed and 1 failed), the anonymous view ungated (0 passed and 1 failed, naming the four rows the ungated view served), the member view casting without nullif (0 passed and 1 failed on 22P02), the member grant dropped (0 passed and 1 failed), and the anonymous view reading repo in the text guard (0 passed and 1 failed), each restored to 1 passed. numbered 0020 because #2250 landed 0019 while this was built; the merge that follows resolves migrate.rs against it and carries the suite. the forge owes a deploy for the migration before the site can read these views (#2175) 232d49ae · 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.