Skip to content

test(drift): diff what the migration chain builds against schema.sql - #2550

Open
BigSimmo wants to merge 11 commits into
mainfrom
claude/drift-semantics
Open

test(drift): diff what the migration chain builds against schema.sql#2550
BigSimmo wants to merge 11 commits into
mainfrom
claude/drift-semantics

Conversation

@BigSimmo

@BigSimmo BigSimmo commented Sep 2, 2026

Copy link
Copy Markdown
Owner

Summary

  • Diff what the migration chain BUILDS against what supabase/schema.sql DESCRIBES. CI proves two adjacent things and neither of them is this one: supabase migration up --local proves the chain applies, and the drift manifest's sha256 check proves the mirror is not stale. Nothing compares the result. A migration whose function body or policy predicate diverges from the mirror passes every pre-merge gate.
  • Not theoretical. On 2026-09-01, migration 20260831100000 (PR fix(rag/db/hub): Core RAG, Security Definer hardening, DB functions, and Developer Hub (PR Group 2) #2477) redefined public.correct_clinical_query_terms(text,real) and supabase/schema.sql was never updated to match. Every pre-merge gate stayed green; the post-merge live-drift alarm went red on main. Behaviour impact was nil (a duplicated predicate is a boolean no-op) but it cost a red daily alarm and a remediation PR, and it is the second occurrence of this failure class.
  • New scripts/check-chain-mirror-parity.ts compares two captured schema_drift_snapshot() payloads. It reuses compareDriftSnapshots from scripts/check-drift.ts rather than reimplementing a comparator — two comparators would eventually disagree about what "different" means, and then one of them would be wrong. migration_history is stripped from both sides by construction, not allowlisted: a schema.sql replay has no supabase_migrations schema, so that category can only produce noise here and stays the live gate's business.
  • Wired into the CI Migration replay job. The mirror side is the drift manifest that job has just regenerated from this PR's schema.sql, so no second replay is built. The script header records why the two sides are not built identically: a mirror database created inside the emulator has no auth schema for schema.sql's references auth.users(id) columns, and reproducing the chain inside the bare image means driving the whole chain by hand rather than through supabase migration up.
  • Its own allowlist file, never the live one. supabase/chain-mirror-allowlist.json is separate from supabase/drift-allowlist.json and must stay separate: an entry in the latter blinds the weekly live-drift alarm, which is a different and far more consequential thing to go blind about. A test asserts neither gate reads the other's file.
  • Scope routing. The script is registered in dbPatterns in scripts/ci-change-scope.mjs, so a change to the parity checker routes db_changed and runs the job it belongs to; and in test:ci-workflows, which an existing contract test requires of any suite that reads a workflow file.
  • A security review of this branch found three real defects, all fixed in the second commit. (i) The report repeated the live gate's advice that a schema_drift_snapshot mismatch is "that pending deploy, not a body regression" — false here, and it coaches the reader to dismiss exactly the class of finding this gate exists to surface. Suppressed. (ii) Both parity steps carry continue-on-error, and the warning step only covered a failed capture, so a permanently-crashing comparison was a grey mark nobody reads and looked identical to a clean run. The warning now covers both. (iii) The suite asserted that every entry in supabase/drift-allowlist.json is migration_history, which imposes a new policy on a file this gate does not own — check:drift legitimately supports object-category entries — so a future live entry would have failed an unrelated test. Replaced with an assertion of what actually matters: separation.
  • Report-only mode now expires. The strict/tolerance tie is satisfied forever by a gate that never becomes strict, and the only forcing function was a comment. Past 2026-12-01 the test goes red unless --strict is present, with a message saying exactly what to do. A found divergence also emits a ::warning:: annotation, because while report-only the annotation is the output.
  • Documented where operators read about drift. A third commit adds a docs/database-drift-detection.md section covering the gate, its known asymmetry, its expiry and the separation rule, and points reconciliation backlog item 10 at the gate that now re-measures it. A test pins the documentation so it cannot fall out of step.

No migration file is added, no SQL is applied to any hosted database, and supabase/schema.sql is not edited. The CI steps run psql against the job's own ephemeral emulator container only.

Verification

  • npm run verify:pr-local

Verification not run: verify:pr-local was not invoked as a wrapper. Its constituent gates were run individually and are quoted below.

Decisive output:

$ node scripts/run-tsx.mjs scripts/check-chain-mirror-parity.ts --self-test
chain-mirror parity self-test passed.

$ node scripts/run-vitest.mjs run --reporter=dot tests/chain-mirror-parity.test.ts tests/drift-detection.test.ts
 Test Files  2 passed (2)      (parity suite alone is 23 tests)

$ npm run test
 Test Files  948 passed (948)
      Tests  12078 passed | 1 skipped (12079)

$ npm run check:migration-role
Hosted migration-role guard passed: active hosted SQL/tooling uses postgres and immutable applied history is unchanged.

$ npm run check:github-actions   -> GitHub Actions pin check passed.
$ npm run check:gate-manifest    -> Gate-manifest OK: all 37 verify:cheap gates are enforced in CI ...
$ npm run check:ci-scope         -> CI change scope self-test passed.
$ npm run docs:check-inventory   -> Docs inventory current: 284 script files, 285 npm scripts.
$ npm run docs:check-scripts     -> docs script-ref check passed: 1194 npm-run reference(s) resolve to real scripts.
$ npm run docs:check-links       -> docs link check passed: 4739 repo path references resolve.
$ npm run lint      -> [gate-receipts] recorded a pass for "lint:internal" (6002 input files)
$ npm run typecheck -> [gate-receipts] recorded a pass for "typecheck:internal" (6002 input files)

Verified on a real invocation, not only fixtures: a snapshot pair built from the committed manifest reports No divergence with the suppressed advice absent; the same pair with one function's def_hash altered reports the mismatch, emits ::warning::chain/mirror parity: 1 divergence(s) …, exits 0 report-only and exits 1 under --strict.

The report-only/tolerance tie was proven to fail when the two are separated — adding --strict without removing continue-on-error goes red, then the probe was reverted:

FAIL tests/chain-mirror-parity.test.ts > ties report-only mode to the failure tolerance, so flipping one forces the other
AssertionError: a --strict parity gate must not carry continue-on-error: expected 2 to be +0

What was NOT verified, stated plainly. The CI steps themselves have never run. This container has no Docker daemon, no Supabase CLI, and a PostgreSQL without the vector extension, so neither replay can be built locally. db-reset-verify is additionally gated github.event.pull_request.draft != true, so a draft PR does not exercise them either. Their first real execution is when this PR is taken out of draft. The step evidence available today is the workflow-contract test and a code review of the shell, nothing more.

  • npm run verify:ui — not applicable, no UI, routing, styling or browser behaviour changed.
  • npm run verify:release — not claimed.
  • npm run check:production-readinessnot run: it is provider-backed and this batch does not contact Supabase. The change adds no runtime or deployment behaviour; the new script is offline and is invoked only inside a CI job.

Risk and rollout

  • Risk: Low while report-only. The two new steps carry continue-on-error: true, so a failure in the capture or the comparison cannot fail Migration replay for anyone. The realistic failure is that the steps error on first execution (they have never run) and the job prints a warning instead of a report. The residual risk is the opposite one: a report-only gate that nobody reads. That was a review finding, and it is now addressed three ways — a ::warning:: annotation on any divergence, a warning when either step produces no evidence, and a dated expiry the test enforces.
  • Rollback: Revert the commits. They create no state, write to no database, and add no runtime code path — the new script runs only inside one CI job.
  • Provider or production effects: None. No hosted database is contacted. psql runs against the CI job's own ephemeral Supabase emulator container, which is destroyed with the runner. Nothing targets sjrfecxgysukkwxsowpy.
  • RAG impact: none.

Clinical Governance Preflight

  • Source-backed claims still require linked source verification before clinical use
  • No patient-identifiable document workflow was introduced or expanded without explicit governance approval
  • Supabase target remains Clinical KB Database (sjrfecxgysukkwxsowpy)
  • Service-role keys and private document access remain server-only
  • Demo/synthetic content remains clearly separated from real clinical sources
  • Source metadata, review status, and outdated/unknown-source behavior remain conservative
  • Deployment classification/TGA SaMD impact was checked when clinical decision-support behavior changed

Each of these is unchanged by this diff: it adds one offline comparison script, one empty allowlist file, three CI steps that read schema metadata from an ephemeral container, a test file and a runbook section. No clinical content, retrieval behaviour, document access path, credential handling, or deployment behaviour is touched, and no decision-support behaviour changed, so the SaMD classification is unaffected.

Notes

Report-only on landing is deliberate, and the follow-up is one small PR. The divergences that already exist cannot be enumerated offline — docs/database-drift-detection.md backlog item 10 already records ~13 chain-vs-mirror keys plus storage buckets that only schema.sql creates, and the two sides come from different images so some reported difference will be platform provenance rather than real divergence. Blocking on a set nobody has measured would just teach people to ignore a red check. So this lands printing the set; the first real run's job summary is the measurement; the follow-up commits supabase/chain-mirror-allowlist.json from it, adds --strict, and removes the continue-on-error lines in the same change. The suite pins those two facts to each other, and now expires the phase on 2026-12-01 so it cannot simply be forgotten.

supabase/schema.sql is deliberately not edited. Editing it invalidates the drift manifest's embedded sha256, and regenerating that manifest needs Docker, which this container does not have. Any divergence this gate finds gets reported, never "fixed" by quietly editing the mirror.

Refs #QCNE6N.

🤖 Generated with Claude Code

https://claude.ai/code/session_015sjekpEw82gMp57C8xzSxZ


Generated by Claude Code


Note

Low Risk
Report-only CI steps and an offline comparison script only; no hosted DB, schema.sql, or runtime behavior changes until strict mode lands.

Overview
Closes a pre-merge gap (#QCNE6N): CI proved migrations apply and the drift manifest matched schema.sql, but not that the migration chain’s resulting schema matches the schema.sql mirror—the failure mode that let correct_clinical_query_terms diverge until live-drift went red.

Adds npm run check:chain-mirror-parity (scripts/check-chain-mirror-parity.ts), which diffs two offline schema_drift_snapshot() payloads via the same compareDriftSnapshots engine as check:drift, strips migration-history noise, and treats chain-only extensions as real findings (not live-gate “platform” info). supabase/chain-mirror-allowlist.json is separate from the live drift allowlist.

Migration replay job now captures a snapshot from the local emulator after migration up, compares it to the freshly regenerated manifest snapshot, emits ::warning:: on divergences or missing steps, and runs report-only (continue-on-error) until a follow-up adds --strict and an allowlist from real CI output. Tests pin CI wiring, allowlist hygiene, report-only vs strict, and a 2026-12-01 expiry; docs and change-scope routing are updated.

Reviewed by Cursor Bugbot for commit 0c35130. Configure here.

CI proves two adjacent things and neither of them is this one. `supabase
migration up --local` proves the migration chain APPLIES, and the drift
manifest's sha256 check proves the mirror is not stale. Nothing compares the
RESULT of the chain against supabase/schema.sql, so a migration whose function
body or policy predicate diverges from the mirror passes every pre-merge gate.

That is not theoretical: on 2026-09-01 migration 20260831100000 (PR #2477)
redefined public.correct_clinical_query_terms(text,real) and schema.sql was
never updated to match. Every pre-merge gate stayed green and the post-merge
live-drift alarm went red on main.

Adds scripts/check-chain-mirror-parity.ts, which compares two captured
schema_drift_snapshot() payloads. It reuses compareDriftSnapshots() from
check-drift.ts rather than reimplementing a comparator — two comparators would
eventually disagree about what "different" means, and then one would be wrong.
migration_history is stripped from both sides by construction, not allowlisted:
a schema.sql replay has no supabase_migrations schema, so that category can only
produce noise here and stays the live gate's business.

Wired into the CI Migration replay job. The mirror side is the drift manifest
that job has just regenerated from this PR's schema.sql, so no second replay is
built. That does leave the two sides on different images (the Supabase emulator
vs the pinned bare supabase/postgres), and the header of the script says so:
building both sides identically was tried and does not work, because a mirror
database created inside the emulator has no `auth` schema for schema.sql's
`references auth.users(id)` columns.

REPORT-ONLY on landing, deliberately. The divergences that already exist cannot
be enumerated offline — docs/database-drift-detection.md backlog item 10 records
~13 chain-vs-mirror keys plus storage buckets only schema.sql creates — so this
prints them rather than blocking on a set nobody has measured. The follow-up
commits supabase/chain-mirror-allowlist.json from the first real run's summary,
adds --strict, and drops the continue-on-error lines in the same change;
tests/chain-mirror-parity.test.ts pins those two facts to each other so they
cannot drift apart.

The parity allowlist is a separate file from supabase/drift-allowlist.json and
must stay separate: an entry there blinds the weekly live-drift alarm, which is
a different and far more consequential thing to go blind about. A test asserts
the live allowlist still carries only migration_history entries.

Registers the script in dbPatterns so a change to the parity checker routes
db_changed and runs the job it belongs to, and in test:ci-workflows, which an
existing contract test requires of any suite that reads a workflow file.

No migration, no SQL applied to any database, supabase/schema.sql untouched.

Verified offline: 19 new tests (comparison directions, the 2026-09-01 mismatch
class, policy/index shapes, fail-closed allowlist, report wording, CI wiring);
the report-only/tolerance tie proven to fail when the two are separated; script
--self-test; full unit suite 948 files / 12074 tests passed; lint, typecheck,
check:migration-role, check:github-actions, check:gate-manifest, check:ci-scope,
check:knip, docs:check-inventory, docs:check-scripts all green.

NOT verified: the CI steps themselves. This container has no Docker daemon, no
Supabase CLI, and a PostgreSQL without the vector extension, so neither replay
can be built here; db-reset-verify is also skipped on draft PRs. Their first
real execution is on an undrafted PR.

Refs #QCNE6N

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sjekpEw82gMp57C8xzSxZ
Three real defects found reviewing the chain/mirror parity gate, all in the
half that could not be executed locally.

1. The report repeated advice that is false here and points the wrong way.
   compareDriftSnapshots emits an info line whenever a snapshot carries no
   migration-history probe: "migration 20260818090000 is not deployed; the
   schema_drift_snapshot() function mismatch is that pending deploy, not a body
   regression". True for the live gate. Here the probe IS applied in the chain
   database and this script stripped it deliberately — and the sentence coaches
   the reader to dismiss exactly the class of finding this gate exists to
   surface. Suppressed, with a test asserting neither the info list nor the
   rendered report can carry it.

2. A crashing compare step was indistinguishable from a clean one. Both parity
   steps carry continue-on-error, and the warning step only covered a failed
   capture, so a permanently-throwing comparison was a grey mark nobody reads.
   The warning now covers both outcomes and names which one failed.

3. The suite imposed a new policy on a file this gate does not own. It asserted
   every entry in supabase/drift-allowlist.json is migration_history, but
   check:drift legitimately supports object-category entries there — a future
   live entry would have failed an unrelated chain-mirror test with a confusing
   message. Replaced with an assertion of what actually matters: the two gates
   never read each other's allowlist.

Also from the review:

- Report-only mode had no way to end. The strict/tolerance tie is satisfied
  forever by a gate that never becomes strict, and the only forcing function
  was a code comment. Report-only now expires 2026-12-01: past that date the
  test goes red unless --strict is present, with a message saying exactly what
  to do. Moving the deadline needs a reason in the PR body.
- A found divergence emitted nothing GitHub renders. While report-only, the
  annotation IS the output, so the script now emits ::warning:: with the count.
- The capture step built its container name through `docker ps | grep | head`
  under `set -o pipefail`, where a SIGPIPE from head fails the step and grep
  exiting 1 on no match aborts before the explicit `test -n` can explain why.
  Uses `docker ps --filter` instead, and a test pins that.
- Renamed withoutHistoryProbe to comparableSnapshot, since it also strips
  captured_at and the old name would mislead the next reader.

Verified on a real invocation rather than fixtures: a snapshot pair built from
the committed manifest reports "No divergence" with no suppressed advice; the
same pair with one function def_hash altered reports the mismatch, emits the
::warning::, exits 0 report-only and exits 1 under --strict. 22 tests in the
parity suite; full suite 948 files / 12077 tests passed; lint, typecheck,
check:github-actions, check:ci-scope green.

Still not verified, unchanged from the first commit: the CI steps have never
run. No Docker daemon, no Supabase CLI, no vector extension here, and
db-reset-verify is skipped on draft PRs.

Refs #QCNE6N

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sjekpEw82gMp57C8xzSxZ
Review noted the gate landed with no explanation anywhere an operator reads
about drift, and that reconciliation backlog item 10 — the 13-key chain-vs-
mirror measurement from 2026-07-07 — did not mention that anything now
re-measures it.

Adds a section beside the guard-migration contract covering what the gate
compares and why, the 2026-09-01 incident that motivated it, the known image
asymmetry and the two approaches that were tried and rejected before settling
on it, the report-only phase and its 2026-12-01 expiry, exactly what ending
that phase takes, and why the parity allowlist must never merge with the live
drift allowlist. Backlog item 10 now says its count is a hand measurement that
has never been re-taken, and points at the gate that re-takes it.

Also carries forward a trap the review spotted: the matcher in
check-gate-manifest.mjs that pairs a local gate with its CI step is anchored to
a single-line `run:`, so this gate's multi-line step would not register as CI
coverage if it were ever added to verify:cheap.

A contract test pins the section, the expiry date, the separation rule, and the
backlog cross-reference, so the documentation cannot quietly fall out of step
with the gate.

Verified: 23 tests in the parity suite; full suite 948 files / 12078 tests
passed; docs:check-scripts, docs:check-links and docs:check-inventory green.

Refs #QCNE6N

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sjekpEw82gMp57C8xzSxZ
@coderabbitai

coderabbitai Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: 142f92e9-1a75-4837-b5e8-0bf667006638


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@supabase

supabase Bot commented Sep 2, 2026

Copy link
Copy Markdown

This pull request has been ignored for the connected project sjrfecxgysukkwxsowpy due to reaching the limit of concurrent preview branches.
Go to Project Integrations Settings ↗︎ if you wish to update this limit.


Preview Branches by Supabase.
Learn more about Supabase Branching ↗︎.

@BigSimmo
BigSimmo marked this pull request as ready for review September 2, 2026 06:34
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 2, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-02T06:40:08.037123Z f3d1700 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@cursor

cursor Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_24b4fcb6-bbe0-4428-ad20-799e03112684)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f3d1700129

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread scripts/check-chain-mirror-parity.ts
@BigSimmo

BigSimmo commented Sep 2, 2026

Copy link
Copy Markdown
Owner Author

@codex resolve actionable Codex review findings for this pull request and current head using the repository instructions. This is the pull request's single automatic repair pass: do not perform a fresh review, create new standalone findings, or request another review. Work only the existing unresolved Codex threads on the current head. The only repository destination is BigSimmo/Database, and the only branch destination is the pull request head branch claude/drift-semantics at starting commit 6eefdb9; never publish fixes to a detached or synthetic work branch and never create a stacked pull request. Use the authenticated GitHub connector to commit each approved fix to BigSimmo/Database:claude/drift-semantics, then verify that the pull request head contains the published commit before reporting success. Always fix P0 and P1 findings. For P2 and lower findings, fix only clear, scoped, low-risk issues; otherwise disposition them with a concise reason. For a fixed thread, reply with as the first line and as the second line. For a no-code disposition, use followed by . These result markers authorize the workflow to close that exact thread only after it verifies a fixed commit is the pull request head; a local-only commit is not a fix. If publication or verification fails, do not use either result marker, do not claim success, and leave the thread open with the blocker. If human input or new authorization is required, do the same. Finish only after every actionable thread is fixed or dispositioned and closed, or explicitly left open for a human decision. Do not update the branch from main, address unrelated reviews, broaden scope, or create more than one scoped fix commit. Do not use external APIs, paid services, credentials, dependency changes, or broad refactors unless explicitly authorized. Add targeted tests where behavior changes and run the narrowest relevant validation.

@chatgpt-codex-connector

Copy link
Copy Markdown

Summary

  • Updated the parity wrapper so extensions created only by the migration chain are emitted as unexpected_live findings rather than being downgraded to live-platform informational messages. Genuine emulator-only extensions can still be handled through the dedicated chain/mirror allowlist. scripts/check-chain-mirror-parity.tsL134-L147 scripts/check-chain-mirror-parity.tsL174-L210
  • Added self-test coverage for the pg_cron chain-only extension scenario. scripts/check-chain-mirror-parity.tsL340-L349
  • Added targeted regression tests proving chain-only extensions are reported and reviewed extension divergences can be allowlisted without creating stale entries. tests/chain-mirror-parity.test.tsL76-L97
  • Created local commit 4eeae824bb1d2dda19de73fecfbc7b0e49b27036 (fix(drift): flag chain-only extensions in parity).
  • Publication and thread resolution remain blocked: the push to BigSimmo/Database:claude/drift-semantics failed because this environment has no GitHub authentication. Consequently, I did not post either resolution marker or claim that the PR head contains the fix. The required make_pr tool is also unavailable in this environment, and creating a stacked PR would conflict with the trigger’s explicit destination rules.

Testing

  • npm run format
  • node scripts/run-tsx.mjs scripts/check-chain-mirror-parity.ts --self-test
  • node scripts/run-vitest.mjs run --reporter=dot tests/chain-mirror-parity.test.ts — 25 tests passed.
  • git diff --check
  • git commit -m "fix(drift): flag chain-only extensions in parity"
  • ⚠️ git push origin HEAD:claude/drift-semantics — blocked because Git could not obtain GitHub credentials.
  • ⚠️ gh auth status — no authenticated GitHub host is configured, so the pushed-head verification and review-thread reply/resolution could not be performed.

View task →

The parity gate reuses compareDriftSnapshots so the two gates cannot disagree
about what "different" means. That reuse carried over one rule that inverts here:
the live gate demotes an extension present on the "live" side but absent from
schema.sql to an info line, because Supabase provisions pg_net and pgsodium that
no migration creates.

On this comparison the "live" side is the migration chain — our own code — so the
same demotion excuses a real divergence, and one already exists. Migration
20260901033250 runs `create extension if not exists pg_cron`; supabase/schema.sql
declares six extensions and pg_cron is not among them. Probed against the
committed drift manifest, the inherited behaviour returned findings: [], so even
a future --strict run would have passed it.

Extensions the chain creates and the mirror never declares are now re-promoted to
unexpected_live findings, before the allowlist is applied, so only a reviewed
entry naming the extension can excuse one. The genuine emulator-image asymmetry
goes in supabase/chain-mirror-allowlist.json one entry at a time rather than
under a blanket rule that cannot tell the two cases apart. The demoting info line
is suppressed so it cannot print beside the finding it was hiding.

Four self-test cases and four regression tests, including one pinning that
pg_cron really is absent from the committed mirror and really is created by that
migration — so the case stays live rather than becoming a fixture that agrees
with itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sjekpEw82gMp57C8xzSxZ
@cursor

cursor Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_fd403c02-9841-40c8-8f5a-06079939978c)

BigSimmo commented Sep 2, 2026

Copy link
Copy Markdown
Owner Author

Correction to this PR body: the CI steps have now run

The body says "The CI steps themselves have never run… Their first real execution is when this PR is taken out of draft." That happened at 06:34 UTC today. Migration replay completed successfully on run 33599464239, and the new steps produced this — the measurement the whole report-only phase was waiting for:

## Migration chain vs supabase/schema.sql

- info: extra live extension (platform-provisioned): pg_cron
- info: extra live extension (platform-provisioned): pg_net

No divergence between the migration chain and supabase/schema.sql.

Two things follow, and they point in opposite directions.

The good one: the predicted divergence set does not exist. docs/database-drift-detection.md backlog item 10 estimated ~13 chain-vs-mirror keys plus storage buckets only schema.sql creates. The real comparison found zero across tables, views, functions, indexes, policies, constraints, triggers and storage buckets. That estimate had never been re-measured; it is now, and it was stale. The path to --strict is therefore much shorter than this PR assumed.

The bad one: that "No divergence" line was wrong, and the Codex review called it before the run did. pg_cron is not platform provenance — 20260901033250_enable_staging_privacy_retention_schedules.sql creates it and supabase/schema.sql never declares it. That is precisely the chain-vs-mirror gap this gate exists to catch, and the gate reported it as an informational aside and then declared no divergence. The cause was inherited from the live gate, where "extra live extension" really is platform provenance; here the "live" side is our own migration chain.

Fixed in b84124090, and the allowlist is now measured rather than empty:

  • pg_net — allowlisted, with the evidence: no file under supabase/migrations/** creates it and schema.sql never declares it, so neither side asked for it. That is the two-images asymmetry the file exists for.
  • pg_crondeliberately not allowlisted. It is a real mirror gap and should stay reported until it is fixed properly.

One thing to decide, not urgent

supabase/schema.sql should declare pg_cron so the mirror matches what the chain builds. Not done here: editing schema.sql invalidates the drift manifest's embedded sha256, and regenerating that manifest needs Docker, which this container does not have. It also belongs with the migration batch rather than this one. Until then the gate reports it, which is the correct behaviour and costs nothing while the steps stay continue-on-error.


Generated by Claude Code

The gate's first real execution (Migration replay, run 33599464239) reported
zero structural divergence across tables, views, functions, indexes, policies,
constraints, triggers and storage buckets. docs/database-drift-detection.md
backlog item 10 estimated ~13 chain-vs-mirror keys; that estimate had never been
re-measured and it was stale. Only two extensions differed.

pg_net is allowlisted with its evidence: no file under supabase/migrations/**
creates it and supabase/schema.sql never declares it, so neither side of the
comparison asked for it — the emulator-image asymmetry this file exists for.

pg_cron is deliberately NOT allowlisted. 20260901033250 creates it and
supabase/schema.sql does not declare it, so it is a real mirror gap and the
finding the gate exists to surface. Closing it means declaring the extension in
schema.sql and regenerating the drift manifest, which needs Docker, and belongs
with the migration batch rather than here.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sjekpEw82gMp57C8xzSxZ
Both sides added a test to test:ci-workflows; kept both (browser-test-plan from
main, chain-mirror-parity from this branch) and regenerated docs/scripts-index.md
rather than hand-editing its counts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sjekpEw82gMp57C8xzSxZ
Regenerated docs/scripts-index.md and the repo-awareness snapshot rather than
hand-resolving their counts; the snapshot legitimately changes because this
branch adds check:chain-mirror-parity.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sjekpEw82gMp57C8xzSxZ
…edit

Same cause as the tenancy branch: an earlier merge here restored the two
generated snapshots to their merged state to sidestep the cross-PR conflict
churn, which also discarded the regeneration this branch's own section in
docs/database-drift-detection.md requires. check:repo-awareness-snapshot would
have gone red on "documentation differs from the repository".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sjekpEw82gMp57C8xzSxZ
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

CI triage

CI failed on this PR. Automated classification of the 2 failed job(s):

  • Production UI criticalnot baselined: this job did NOT run on the main comparison below (path-scoped skip), so that run says nothing about it either way. Treat the comparison as absent, not green, and inspect the failing step.
  • PR requiredneeds investigation: inspect the failing step and uploaded diagnostics; rerun only after classifying the cause.

Compared with main CI run #14923 (success). That run's conclusion is an aggregate and did not exercise Production UI critical.

Classification is evidence routing, not permission to ignore a failure. Exact quarantined Playwright identities remain governed by the flake ledger.

Generated files regenerated rather than hand-resolved.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015sjekpEw82gMp57C8xzSxZ
@BigSimmo
BigSimmo enabled auto-merge (squash) September 2, 2026 09:18
@cursor

cursor Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Bugbot couldn't run - usage limit reached

Bugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit.

A user or team admin can review and increase usage limits in the Cursor dashboard.

(requestId: serverGenReqId_24478962-6f14-41d8-96ad-de3517167e72)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants