Changes touching this path

  • Forge storage drivers: Postgres metadata tier, S3 blob tier, and one conformance suite both must pass (#516) f65970e6 · dbf3dbe6…
  • loot-forge: the anonymous read surface is 11 barrier views and a forge_read role confined to them (#610) bb193149 · dbf3dbe6…diff
  • a forge storage error carries the server's own words, so a failed migration stops reporting 'db error' (#776) pg::storage formatted a postgres::Error with Display, which looks like it preserves the cause but is the literal string "db error" -- the message, SQLSTATE, detail and constraint all hang off as_db_error() and were dropped. connect_failed and migrate.rs's connect error had the same shape. That is worse than cosmetic. ForgeError::Storage is the ONLY ERROR-level variant, so the class the forge logs loudest carried nothing actionable; and --migrate funnels through it, so a failed migration reported 'storage: db error' with an empty ledger -- indistinguishable from the migration you had just written being wrong. Found applying ADR 0043's migration 0004 against a cluster that had defaulted to WIN1252: the real message named the offending U+26A0 byte sequence and the encoding, and was visible only in the postgres log on the box. It pointed the investigation at the new migration instead of at the cluster. pg::describe now unwraps as_db_error() into message/detail/hint/constraint plus SQLSTATE, and walks the source chain for non-DbError failures where Display is equally terse. All three sites route through it. It deliberately omits the statement and bound parameters: parameters carry tenant content and this string reaches logs. The server's message plus SQLSTATE names the fault without naming the data. before: cannot reach the metadata tier to migrate: db error after: cannot reach the metadata tier to migrate: database "nonexistent_db" does not exist [SQLSTATE 3D000] The regression test provokes a REAL server error rather than constructing one, because the bug was that the detail lives where Display does not look -- a hand-built ForgeError::Storage would have passed throughout this file's history. Verified to fail against the old one-liner. 9d4f91a6 · dbf3dbe6…diff
  • the forge's metadata pool caps every connection it opens, so a burst queues for one instead of being refused by Postgres (#611) f2d8ee80 · dbf3dbe6…diff
  • the metadata tier speaks TLS when the URL asks for it, and refuses rather than falling back to plaintext (#689) adf57547 · dbf3dbe6…diff
  • an embargo past the end of the storable clock stops reading back as due-now on Postgres, because reveal_at is admitted against one stated bound at the wire and in every backend instead of cast into a bigint that files it negative, and the agreement harness gains the row it could not carry until that decision was made (#1449) 13fd52b5 · dbf3dbe6…diff
  • RLS arrives on the trigger #702 named rather than on a schedule, because #1725 made it fire the same night: forge_member_writer holds SELECT, INSERT and DELETE on repo_member and SELECT on account_key and repo, which are base tables and not views, and its owner-scoping lived entirely in three SQL statements the site writes. It also held a bare unpredicated SELECT on repo, so it could enumerate every namespace on the forge including the metadata-private ones. Migration 0014 enables RLS on eight tables - the schema header's own repo-scoped group plus proposal, which 0011 added to that group after this ticket was written - and the verdict on the forge role is that it is UNCONSTRAINED, said out loud rather than decorated: all eight of its policies are USING (true) WITH CHECK (true), named forge_ingest_is_unconstrained so the catalogue itself prints the admission, because it writes every tenant from one process with no request-scoped identity and a predicate would be either inert or an outage. What ENABLE does buy is exactly this ticket's condition 2, the accident it was deferred against: a role no policy names now reads zero rows, so an ALTER DEFAULT PRIVILEGES that hands a reader every future table is answered by the tables themselves. The per-transaction ingest binding that would make the forge role genuinely constrainable is filed as #1732. FORCE ROW LEVEL SECURITY was proved rather than assumed, and is deliberately NOT set: on a real cluster a postgres-owned table with RLS enabled AND forced and zero policies still reads 2 of 2 as postgres, where a NOBYPASSRLS role reads 1 of 2 at the same moment, and a non-superuser-owned table reads 2 without FORCE and 0 with it - so the mechanism works, it is inert against a superuser owner, and setting it would be precisely the decorative control this ticket forbids. The red proofs are the output that matters and one of them corrected the method: a policy must be proved by REPLACING its predicate with true, never by dropping the policy, because enabled-with-no-policy is deny-all and returns zero, which proves nothing about the predicate - the first draft did exactly that and it is recorded so the next reader does not repeat it. With the predicate widened instead, the cross-owner INSERT lands and owner B's roster gains a collaborator its owner never named. One thing only a real run could find: membership.pg.test.ts's foreign-key case went red because the policy now stands in FRONT of the constraint, so an unscoped statement never reaches the foreign key the test is about, and left alone it would have quietly become an RLS test named for the wrong mechanism - so it is fixed and the RLS half split into its own case. The site half ships in the same change because it must: membership/db.ts gains withOwner, owner/db.ts's shape minus READ ONLY, and without it 0014 empties the membership surface (#702) c562196e · dbf3dbe6…diff
  • the forge role is bound to one repo per transaction: migration 0017 replaces the permissive forge_ingest_is_unconstrained policies with forge_is_bound_to_one_repo, keyed on loot.repo_owner and loot.repo_name, which Pg::with_repo sets before any other statement, so an unbound statement reads no row of those tables and has every write to them refused. every MetadataStore method that reaches a row-secured table now runs in with_repo, held in source by a census test and in behaviour by the conformance suite run as forge, and the ref CAS is still the first statement that locks ref. tests/rls.rs proves the binding refuses cross-repo reads and writes and goes red when any one table predicate is replaced with true, and the recorded-decision test that pinned the unconstrained role is removed. pg::tests now starts its sessions as forge. ADR 0078 decision 3 is amended with the census, the deploy order and what the binding does not constrain, and CONTEXT.md, the schema header, ADR 0077 and the evidence doc follow (#1732) af1c95ee · 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
  • migration 0017 and ADR 0078 now state the deploy window setup-forge.js leaves by migrating before it swaps the container: until the new binary is up the old one reads empty repos and has pushes refused, nothing is corrupted, and the order is kept because migrating first is what the deploy script relies on for every release (#573); the checksum pin stays green and went red on a one-token statement edit, and 0017 says statements rather than bytes. CONTEXT.md and ADR 0078 stop saying no CI runs the live suites, the rls.rs header names the key the workflow sets and says the cluster tests are the ones that return early, and the 2026-09-08 evidence entry gets a dated follow-up rather than a rewrite. binding_census now walks every file under src/pg instead of a hand-kept pair, reads fn items of any visibility, follows SQL held in a module-level const and drops cfg(test) items and files, and Pg::with_conn, with_txn and with_repo are private to pg so the compiler keeps other modules from issuing statements through them. a planted pub(crate) method in a new pg/planted.rs reading such a const went red under the new census (1 passed, 1 failed) and stayed green under the old one, and removing each widened shape turned the census red. the source_walk message on flags.rs drops its stale six-lines count, and a new assertion holds that flags.rs calls into the module on more lines than it has consumers, which went red when that premise was broken. the table counts in CONTEXT.md and pg/mod.rs point at the constants that hold those sets rather than restating a number, pg/mod.rs points at the path include instead of listing its includers, the Admitted doc says within and within_leaf read through the leaf spec they are handed, the test-main.yml provisioning comment names the grant repair it drops as a no-op on a fresh cluster, and the rewrap leftovers are fixed. the bare workspace suite is green, and the loot-forge suite is green against a throwaway cluster with the CI env block (#1953) f2cbbb4c · dbf3dbe6…diff
  • the forge test harnesses no longer race each other migrating a scratch database: a migration that adds one of the narrow roles creates it inside a check-then-act DO block and then states the attributes with ALTER ROLE, which writes a pg_authid row no database owns, so two harness threads each migrating their own scratch database write that row at once and postgres answers one of them tuple concurrently updated, the flake #1744 saw once in CI as migration 0003 failing on a fresh database. it reproduces three ways against a throwaway cluster: six concurrent loot-forge --migrate processes over six fresh databases failed 4 of 18 with that error, each leaving a ledger stopped at version 2 with the server log naming migration 0003; the statement alone, looped from sessions in separate databases, failed 2 of 4 where 18 single statements collided 0 times; and the two migrating pg::tests cases run as a pair went red in 5 of 8 runs. the new pg::migration_serial takes a session-level pg_advisory_lock on the admin url database and gives it back when the returned value drops, so the disconnect is the unlock and a test that panicked holding it releases it too. it is a lock on the cluster rather than the MIGRATING mutex the harnesses under tests/ each carried, because the row belongs to the cluster and the lib tests and each tests/*.rs are their own binary; every harness application of a migration now holds it, namely PrefixDb::new, the new apply_migration that apply and the new reapply share, the member read gate restore, and the rls case that makes a role bypass row security, where a second binary migrating during that window would have had migration 0014 refuse it. with the lock the pair is 0 of 8 red and four concurrent processes of it are 0 of 12; a process mutex in its place is 6 of 12 red, no lock in PrefixDb::new is 2 of 6 red with the census naming that function (1 passed, 1 failed), the lock dropped from the member_class harness is named too (0 passed, 1 failed), and the pg_advisory_xact_lock spelling reddens the new lock case (0 passed, 1 failed) and 4 of 6 runs. every_scratch_database_migration_holds_the_cluster_lock walks src/ and tests/ for the statements that apply a migration, holds the set of functions equal to what it expects and names migrate as the one deploy path exempt, since it already serialises runs on one database with LOCK TABLE schema_migration; it went red with ROOTS emptied and with that exemption emptied (0 passed, 1 failed each) and it needs no cluster, unlike the cases it reads. the_migration_lock_is_one_lock_for_the_whole_cluster pins that an advisory lock is per database, which is why the lock is taken on the url unmodified. PrefixDb migration panics print the server sentence through pg::describe now, where the original report read db error. docs/evidence/forge-datastores.md carries the run and answers the suspicion its #1744 section left open, the forge suite is green against the cluster (469 passed over nine binaries, no skip note) and the workspace suite is green (3832 passed) (#1956) 8aebb4de · 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.