From 655dc55383f7e5128aecbec728efcf74b76d7071 Mon Sep 17 00:00:00 2001 From: tornidomaroc-web Date: Wed, 7 Oct 2026 07:33:38 +0000 Subject: [PATCH] docs(#137): PR A applied to production and proven live: 25 grant lines flipped exactly, six policies gone, nothing else moved, definer functions and every refusal witnessed inside a rolled-back transaction; public-grants required Co-Authored-By: Claude Fable 5.1 --- docs/PROGRESS.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md index 3de432d..c07034f 100644 --- a/docs/PROGRESS.md +++ b/docs/PROGRESS.md @@ -451,6 +451,20 @@ bodies total 37,095 bytes. **This retires the Option C frozen-tail invariant by existed only to police a boundary inside an unreviewable single line, and the append-only rule above supersedes it. No bespoke hash is needed for future updates: the diff is the proof. +### 2026-10-07 - #137 PR A applied to production after the merge and proven on the live database: the 25 grant lines flipped exactly, the six policies gone, everything else byte-identical, the definer functions and every refusal witnessed inside a transaction that rolled itself back; `public-grants` now a required check + +**Row #137 is not edited: the owner's brief for this step allowed Section 7 additions only. Its text still reads "NOT BUILT", which is now true of PR B and PR C and no longer of PR A; the block below supersedes that sentence until the row is rewritten when #137 closes. Outside this file: nothing in the repository; on production, the one file `20261007_public_grants_dead_writes_and_anon.sql`, run in the SQL editor by the agent on the owner's written instruction of 2026-10-07 (the editor's "Potential issue detected" dialog for the DROP POLICY statements confirmed by the agent under that instruction); and on GitHub, `public-grants` added to the required status checks of `main`. Rows 42, 69 and 81 are not touched, and Section 7 takes no deletions.** + +**ORDER AS RECORDED.** PR #236 merged as `7265eef` at 07:21:33Z (final head `816b4bf`; CI green on it, `public-grants` 131 of 131). The live baseline was re-read at ~07:10Z before the merge (previous block). The file was loaded into the editor from the merged commit itself (`raw.githubusercontent.com/.../7265eef/...`, SHA-256 `d616675b9c2fc64f...`, equal to `git show 7265eef:` of the file) and run once at ~07:23:50Z: "Success. No rows returned". Vercel deployed `7265eef` (success; nothing in the deploy applies migrations) and Railway built the ingestion image (success). The `typecheck` workflow's first run on `main` at `7265eef` (run 37586832596) had `public-grants` green, and only then was the protection changed. + +**THE LIVE READ AFTER APPLY (~07:25Z, `scripts/sql/read-public-grants.sql`, 13 cells, 149 lines; 159 before: the ten lines of the six dropped policies, two of which spanned three lines).** Compared in the page, line by line, against the fixture read of the green `public-grants` run (the expected side): **two live lines differ, both the platform objects the audit recorded** (`rls_auto_enable()` and the `supabase_admin`/`realtime` default ACL with grant options), **and one expected line is absent, the image's version of that same `realtime` row**. Every other line is identical: `1.relations`, the eleven touched tables now `{postgres=arwdDxtm/postgres,anon=rDxtm/postgres,authenticated=arwdDxtm/postgres,service_role=arwdDxtm/postgres}` for `profiles`, `knowledge_bases`, `documents`, `conversations`, `messages`, `chunks`, `quizzes`, `quiz_items` (the baseline minus exactly `a`, `w`, `d` on `anon`) and `{postgres=arwdDxtm/postgres,anon=rDxtm/postgres,authenticated=rDxtm/postgres,service_role=arwdDxtm/postgres}` for `waitlist`, `usage_counters`, `study_events` (minus `a`, `w`, `d` on both API roles), `subscriptions` and `account_deletion_orphans` unchanged, every owner `postgres`, RLS on, not forced; `2.privs`, the fourteen flipped lines now `SELECT=true INSERT=false UPDATE=false DELETE=false` and the other 25 unchanged (`service_role` all `true` everywhere); `3.policies`, exactly the fourteen repository policies minus `Anyone can join waitlist`: 13 policies, one per table except `chunks` 3, `waitlist` 0, `account_deletion_orphans` 0, each body hash-identical to the baseline's; `4.defacl` the six `public` rows unchanged (default privileges are PR C); `5.functions`, `6.definers`, `7.triggers`, `8.views`, `9.colacl`, `10.sequences`, `11.roles`, `12.schema` unchanged. **Each changed grant flipped exactly as intended and nothing else moved.** + +**THE REAL PATHS, ON THE REAL TABLES, AS THE ROLES PostgREST USES, WITH NOTHING LEFT WRITTEN.** One `DO` block in the editor, read-only row counts before and after. Inside it, as `postgres`: a throwaway `auth.users` row (a random id, an `@example.invalid` address) whose `handle_new_user` trigger made its profile (`1`); then `set local role authenticated` with `request.jwt.claims` carrying that id (`auth.uid()` matches: `true`): `increment_usage('query')` → `1`, again → `2`, `increment_usage('upload')` → `1`, `record_study_event('question_asked')` → a uuid; the student's own `usage_counters` row readable (`1`) and own `study_events` row readable (`1`): the definer functions write and the SELECT policies serve, exactly as `src/lib/rate-limit.ts`, `src/lib/study-events.ts`, the dashboard and the streak use them. Then, each in its own sub-block: `INSERT waitlist` → `42501 permission denied for table waitlist`; `INSERT usage_counters` → `42501 permission denied for table usage_counters`; `UPDATE usage_counters` → `42501 ...`; `INSERT study_events` → `42501 permission denied for table study_events`; `DELETE study_events` → `42501 ...`. Then `set local role anon`: `INSERT waitlist` → `42501 permission denied for table waitlist` (the audit's §1.3 hole, closed); `INSERT knowledge_bases`, `UPDATE documents`, `DELETE messages`, `INSERT profiles` → each `42501 permission denied for table ...`; `increment_usage` and `record_study_event` as `anon` → `P0001 not authenticated` (the functions' own guard, as before). The block ends with `raise exception` carrying that text, so the whole transaction, the throwaway account included, rolled back. **Row counts before (07:29:38Z) and after (07:31:24Z): `waitlist` 1, `usage_counters` 16, `study_events` 31, `auth.users` 13, `profiles` 13, policies in `public` 13: identical.** No real account was used; the three protected addresses were not touched. + +**`public-grants` REQUIRED.** After the first green run on `main` (`7265eef`), `PATCH /repos/.../branches/main/protection/required_status_checks` with `{"strict": false, "contexts": ["tsc", "db-types", "entitlement-read", "entitlement-rls", "public-grants"]}` (the four existing contexts plus the new one; `strict` kept `false`). Read back: contexts as sent, `enforce_admins` `true`, required reviews `0`, linear history `false`, force pushes and deletions `false`: nothing else in the protection changed. The branch `fix/137-grants-pr-a` was deleted locally and on origin. + +**WHAT CLOSES AND WHAT DOES NOT.** PR A of #137 is done: built, merged, applied, read back and proven. Row #137 stays open for PR B (the unused `authenticated` verbs: `knowledge_bases`, `conversations`, `messages`, `quiz_items` UPDATE and DELETE; `chunks`, `quizzes` UPDATE) and PR C (default privileges, `profiles`, EXECUTE from PUBLIC). PR B needs no new audit: its six lines are the only changes to the harness's `INTENDED` matrix, and the harness already exercises every verb `authenticated` keeps, so an over-reach goes red on its first run. + ### 2026-10-07 - #137 PR A built: INSERT, UPDATE and DELETE revoked from both API roles on `waitlist`, `usage_counters` and `study_events` and from `anon` on every other table; the open waitlist INSERT policy and the five hand-made duplicates dropped; the grants matrix proven in CI as `public-grants`; the production baseline re-read and the exact rollback recorded before anything is applied **Row #137 is not edited (still open; PR A is one of its three steps). Outside this file: `supabase/migrations/20261007_public_grants_dead_writes_and_anon.sql` (new, two REVOKEs and six DROP POLICY IF EXISTS), its `apply` line in `supabase/migration-order.txt`, `scripts/verify-public-grants.mjs` (new, the proof), and the job `public-grants` in `.github/workflows/typecheck.yml` (new, its own job, 15 minutes, not yet required). Nothing is applied to production by this PR; the apply is the step after merge, by hand in the SQL editor, by the agent on the owner's written instruction of 2026-10-07. PR B (the unused `authenticated` verbs) and PR C (default privileges, `profiles`, EXECUTE from PUBLIC) are not built. No auth config, template, env, function body or other table. Rows 42, 69 and 81 are not touched, and Section 7 takes no deletions.**