ci: verify skipped migrations target no public schema (register #40) - #62
Merged
Merged
Conversation
The db-types gate skips 002_storage.sql, and the skip is keyed on the
FILENAME, not on the file's content. Nothing stopped a `public`-schema
object from being added to a skipped file: it would be absent from the
generated types while db-types -- a REQUIRED status check -- stayed
green. Making the check required on 2026-07-17 raised the stakes on that
hole rather than lowering them, because a false green now carries
authority to gate merges.
Worse, gen-db-types.sh PRINTED the claim on every run:
"Skipped migrations create no objects in the public schema, so they
cannot affect the generated types."
Nothing executed it. That is prose wearing a gate's uniform -- the same
shape as register #23 (a written migration assumed applied) and register
#39 ("Enforcement WAS prose only and prose already failed once").
Each skipped file is now statically verified before Postgres starts:
every statement must match a form whose target is EXPLICITLY qualified to
an allowlisted schema (auth, storage). The notice no longer asserts the
claim; it states what was verified, and it keeps saying what this gate
still does not prove.
The predicate is an ALLOWLIST, not a `public`-token denylist, because a
denylist is backwards in both directions:
- it MISSES the dangerous case -- `create table foo (id int)` resolves
to `public` through search_path and contains no `public` token at
all; an allowlist reds it by construction;
- it FIRES on a harmless one -- 002_storage.sql line 1 is
`insert into storage.buckets (id, name, public)`, where `public` is
the bucket-visibility COLUMN. A token scan would have redded a
provably clean tree on its first run.
HONEST LIMIT, stated in the script's own register: this is a fail-closed
STATIC heuristic over SQL text, not a proof of application. It has a
documented undecidable case -- a $$-quoted body, dynamic SQL, or an
unterminated literal/comment is REJECTED rather than guessed at, because
refusing to guess is what keeps the result meaningful. The only sound
proof is execution, and execution is blocked for exactly the reason the
skip exists: the storage schema is absent on bare Postgres. Provisioning
a stub of it would let the gate claim a FULL application backed by a
fake schema this repo does not own -- a louder false green than the one
being closed.
Behaviour on the current tree is unchanged: 002_storage.sql passes (its
three statements are one `insert into storage.buckets` and two
`create policy ... on storage.objects`, all storage-qualified), the same
13/14 migrations apply, and database.types.ts is byte-identical. Placed
beside the manifest/disk cross-check and BEFORE `docker run`, so a bad
skipped file reds on a text read in seconds instead of after Postgres
startup and a partial application.
Register #40 only; no migration, no DB, no live access, no app code.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
tornidomaroc-web
added a commit
that referenced
this pull request
Jul 21, 2026
…64) Records three register moves from one purpose: the db-types REQUIRED check can no longer pass on a wrong result, nor red on a third party's traffic. Logged under Option C — a single-purpose current entry, with docs-PR provenance closed in the header lines, not changelog prose. Four edit regions, nothing else: - Line 8: PREPEND a gate-integrity current entry (#40 closed, #43 opened+closed, #44 opened) and advance the "Last updated" stamp to 2026-07-21. The prior #60/#58 current entry is NOT demoted or rewrapped — byte-identical. - Lines 9-10: advance Active-branch + Main-tip to the merged state. The Main-tip provenance chain now names 3378dd8 (PR #63) on top of d8c8fde (PR #62) on 71e1c9d (PR #60) via the PR #61 docs merge b62cbd5 — this is where PR #62's zero-mention lag is closed, in provenance, not in changelog prose. - Register #40 row: CONVERT IN PLACE to CLOSED (PR #62, merge d8c8fde), per the #38 template for a closed row that keeps its history. The lead flips to ✅ and preserves the original marker after "Was:". The "Where addressed" cell records WHICH branch was taken — the content-aware skip; `storage` was NOT provisioned — so the untaken alternative is not later mistaken for unfinished work, and carries the honest limit (a fail-closed STATIC heuristic with a documented undecidable case, not a proof of application). Columns 2 and 3 are byte-identical: both remain historically accurate. - Register rows #43 and #44 APPENDED, strictly sequential. #43 (closed, PR #63) carries its residual IN the row per the #38 precedent — Docker Hub is also anonymous-quota'd, and a SUPABASE_CLI_VERSION bump can silently fall back to an ECR pull. #44 (open, unfixed) records the npx integrity gap so the class is not considered "handled" because #43 closed. Frozen-history invariant proven, both hashes before == after: primary (Option C region, old entry's opening paren -> EOL8) sha256 d0c031b5a7ee2623e7b0443e337a612411563cea5329235adcc57118e69776fd secondary (first "Prior update" -> EOL8) sha256 22019b90c7ae5884e7531abb7916eb095c41745ed2b80cb01b6ab91a117aaf20 The insert is 2615 bytes and the frozen region shifted right by exactly that (byte 32 -> 2647); "Prior update" markers stay 15, so nothing was demoted. The historical "#40" mention inside the frozen region (now byte 11030) still describes #40 as OPEN and is deliberately UNCHANGED: it was true when written, and editing it would break the invariant. Not touched: §1 (Phase 7 stays ⬜ NOT STARTED — #40 was bucketed Phase 7 and closed early, the phase is unchanged); register #39 (its repo↔live parity claim is unaffected by either fix and the row is marked non-closable); PIVOT_PLAN.md. Docs-only; no code, no migration, no DB. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes register #40 — "latent hole in the
db-typesgate".The hole
supabase/migration-order.txtskips002_storage.sql, and the skip is keyed on the filename, not on the file's content. Nothing stopped apublic-schema object from being added to a skipped file: it would be silently absent from the generated types whiledb-types— a required status check — stayed green.gen-db-types.shalso printed the claim, on every run, with nothing executing it:That is prose wearing a gate's uniform — the same shape as #23 (a written migration assumed applied) and #39 ("Enforcement WAS prose only and prose already failed once").
The fix
Each skipped file is now statically verified before Postgres starts: every statement must match a form whose target is explicitly qualified to an allowlisted schema (
auth,storage). The notice no longer asserts the claim — it states what was verified, and keeps saying what this gate still does not prove.Allowlist, not a
public-token denylist, because a denylist is backwards in both directions:create table foo (id int)resolves topublicthroughsearch_pathand contains nopublictoken at all;002_storage.sql:1isinsert into storage.buckets (id, name, public), wherepublicis the bucket-visibility column. A token scan would have redded a provably clean tree on its first run.Honest limit (stated in the script's own register)
A fail-closed static heuristic over SQL text, not a proof of application. It has a documented undecidable case: a
$$-quoted body, dynamic SQL, or an unterminated literal/comment is rejected rather than guessed at. The only sound proof is execution, and execution is blocked for exactly the reason the skip exists — thestorageschema is absent on bare Postgres. Provisioning a stub of it would let the gate claim a full application backed by a fake schema this repo does not own: a louder false green than the one being closed.Evidence
Real file passes (
002_storage.sql— oneinsert into storage.buckets, twocreate policy ... on storage.objects, all storage-qualified):Fail-closed path fires, through the real script, before
docker run(scratch file, not committed):Also exercised: explicit
create table public.foo→ red;do $$ ... $$→ red with "apply it or split it";create table fooinside a--comment → pass; the same text inside a string literal → pass.Scope
Register #40 only. No migration, no DB, no live access, no app code.
database.types.tsis byte-identical; the same 13/14 migrations apply. Placed beside the manifest/disk cross-check so a bad skipped file reds on a text read in seconds rather than after Postgres startup and a partial application.🤖 Generated with Claude Code