Changes touching this path

  • the collaborator can read someone else's namespace: migration 0015 adds the fourth role forge_member and eleven forge_member views, one per relation 0009 serves, so the shared prefix-parameterised TreeReader reads either family unchanged - and the gate is strictly stronger than 0009's, because the app names only the account the Clerk session resolved to while the membership join sits INSIDE the barrier view, so the application cannot name a repo it is not a member of even by mistake. metadata_public is absent as both column and predicate, and repo_member is named exactly once, by the gate. Two premises did not survive, and the first is a defect in the decision itself: ADR 0077's and the ticket's literal current_setting of loot.reader_tenant cast to bigint DOES NOT fail closed. It fails closed for an UNSET parameter and not for a CLEARED one, because set_config with NULL leaves the empty string behind and casting that to bigint RAISES - which 0009 never met because decoding an empty string as hex does not. Proved by removing the fold from the shipped migration and reading invalid input syntax for type bigint, so the gate ships as nullif and then the cast, malformed values still raise deliberately, and withMember validates a positive decimal with the pin on both sides. The second is a defect in #1727, which landed earlier tonight: its exact-set spelling, that the views naming repo_member equal the forge_member family, is satisfiable ONLY by a family of one, because the ten chained views name the GATE view and never the base table - so a second member view gaining the roster, which is precisely the disclosure ADR 0077 forbids, read as CORRECTNESS under the old spelling. It is re-spelled to exactly forge_member_repo whenever the family exists, its vacuity guard extended to require the member family non-empty, and its positive control now plants four breaks where it planted two. The row-level-security interaction is proved rather than assumed, as three readings on one database in one moment: the seven enabled tables this family reads really carry relrowsecurity, read as a set; the role handed a direct SELECT on repo reads ZERO rows, which is 0014's own named accident and shows the policy system live for that role; and the same role on the same connection at the same moment reads its repo through the gate view and gets one. The mechanism is then named from the catalogue and red-proved by setting security_invoker, which takes the read to insufficient_privilege, and the failure text names the non-superuser deployment case so a future deploy diagnoses itself. The isolation break is ADR 0077's own named careless shape rather than an invented one - the gate keeps its repo_member join and loses only the correlation to repo, which passes every text guard in migrate.rs - and seven of eight tests fire on it. Rows are planted from tests and from the site, outside account_tier.rs's scan of src, the way rls.rs already recorded, so the tripwire is untouched and green. forge_member ships NOLOGIN and provisioning is a different repo, so the class answers 404 rather than 500 and is not an existence oracle (#1726) e76ca863 · dbf3dbe6…
  • the second sweep's fix-up, and the guard at its centre could never have failed: it asserted that a view statement contains the member family prefix, while the statement BEGINS with create or replace view forge_member and then the view's own name - so the needle matched the name it was reading - and the two assertions beside it were absences that an UNGATED view also passes. The replacement computes the TRANSITIVE set of views reaching the gate, matching whole identifiers against each view's body rather than its header, and the contrast is committed rather than transcribed: a synthetic family per gate carrying an ungated base-table view, a shadow-prefix trap, and a one-hop and a two-hop view, asserting the OLD needle green on the first two while the new check refuses them, with the chained views as positive controls - a spelling that demands a direct FROM the gate fails that control, which is exactly why it is not the fix. The same shape had left the owner family unguarded since #1043, and both were red-proved on one planted break, deleting the join from a chained view and reading back that it reaches its gate by no chain at all. Beyond the ticket, migration 0003 had NO reach check whatsoever, only the outer-join tripwire, so the ANONYMOUS family was the least guarded of the three rather than the most; it gains the same check, red-proved the same way, and all three migration files are restored byte-identical with md5 verified. The night's dominant defect was counting: two different roles were each called the fourth role, ADR 0077 called the same thing a third family and a fourth family 284 lines apart, and ADR 0059's heading said FOUR and they are named rather than counted from here on, directly above a sentence listing five. Roughly twenty sites now list NAMES instead, because a corrected ordinal drifts again on the next role while a list of names cannot, and the arithmetic is settled against a live cluster rather than by reading - pg_views reads fifteen, eleven and eleven, so thirty-seven views in THREE families, which makes ADR 0077's own amendment the half that was wrong. The sentence that had been wrong three separate times is made CODE-DERIVED and stops being prose anyone must remember: removal now asks whether the member read tier is configured, the same question the pool itself asks, so the web half follows the deployment - while the sync half stays prose and is NAMED as the one line left to remember, because nothing in that process can observe the access class. The tenant validator is fixed in the CODE rather than in its claim, since the claim is what later readers rely on and narrowing the regex to eighteen digits would reject legal ids: set_config was measured accepting a nineteen-digit value with the first member query raising out of range, so a range check joins the pattern and the maximum itself is asserted as the positive control (#1739) 74afeacc · dbf3dbe6…diff
  • the forge live-database suites now run in CI: test-main.yml stands up a postgres:18 service, provisions it the way setup-forge.js does including its default privileges, migrates it with the commit own loot-forge --migrate, sets every LOOT_FORGE_TEST database url the Rust and site suites read, and runs cargo test and then the site pg tests against it. LOOT_FORGE_TEST_REQUIRE_DATABASE turns a database test skip into a failure through pg::live_opt_in and the site skippingLive helper, and with it set and no url 91 Rust tests and all six site pg files fail, while two half-suites that used to skip silently now say so. The default-privilege tripwires run in CI by decision, and fail on a cluster without provisioning. A new migration that neutralises the roster policy passed the bare suite and failed rls.rs and membership.pg.test.ts against the cluster. It is a canary on landed main and not a pre-land gate, and the prose that said no workflow sets these urls now says that instead (#1735) c0931f78 · 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.