S368 Runbook — ID-115 api-Schema Cutover (Cluster A) + ID-68 identity-guard De-ID (R vs S)
S368 Runbook — ID-115 api-Schema Cutover (Cluster A) + ID-68 identity-guard De-ID (R vs S)
Section titled “S368 Runbook — ID-115 api-Schema Cutover (Cluster A) + ID-68 identity-guard De-ID (R vs S)”Type: Ratifiable execution runbook (design-only authoring; nothing applied). Authored: 2026-06-16/17, read-only Planner dispatch. Main checkout
/Users/liamj/Documents/development/knowledge-hub, branchfix/ci-baseline-s368, tip696608b5b(brief said7203ecdd6 == main; branch advanced — orchestrator re-confirms base before execution). STRUCTURE (per owner reframe): Two operations were conflated in the original brief and are now SEPARATED:
- PART 1 — ID-115
api-schema cutover (S13). Clears CI Cluster A (types-parity + 9 integration + 2 eval) by applying the already-authoredapimigrations to the remotes. NO squash needed. Independent of the de-ID.- PART 2 — ID-68 identity-guard de-ID. Clears identity-guard RED. TWO costed options for the owner: Option R (documented
git filter-reporedaction — zero schema risk, ratified) vs Option S (freshmigration squash— clean public baseline). Liam ratifies every GO-gate; R-vs-S is an owner decision. KEY EMPIRICAL UPDATE (this dispatch): the owner’s correction — “ID-115 made REVOKE a non-issue for a fresh squash” — is CONFIRMED at the ACL layer but BLOCKED at the guard-regex layer. Measured against live prod: 114/114 public functions have non-defaultproacl→ pg_dump DOES emit per-function ACLs (the S176 baseline’s 3-REVOKE problem is GONE). BUT pg_dump emitsREVOKE ALL … FROM PUBLIC, andcheck-revoke-guard.tsonly acceptsREVOKE EXECUTE … FROM <list containing anon>(line 566). So Option S still needs a guard-aware normalisation pass (small, mechanical — NOT the full REVOKE re-injection my prior draft assumed). Costing revised below: S risk drops from “decisive blocker” to “one scripted normalisation step”, ~near-parity with R on schema risk — the deciding axis becomes the fleet-reclone + reference-DB gap-hunt, not REVOKEs.
0. PLAN-STATE VERDICT
Section titled “0. PLAN-STATE VERDICT”| Verdict | |
|---|---|
| PART 1 (api cutover) | Execution-ready except GAP A. ID-115 TECH §(d)/(f)/(h) + impl3 fully specify per-remote atomic apply, exposed-schema mirror, gen types --schema public,api. GAP A (greening BLOCKER): the 3 RPCs the failing CI expects in api — _test_delete_broken_auth_user (integration-9), find_duplicate_pairs (eval FC-44), get_content_win_rate (eval FC-51) — are NOT in scripts/generate-api-views.ts RPC_NAMES (verified absent). Test/eval clients default to db.schema='api' (post-S9) → .rpc('find_duplicate_pairs') → absent api.find_duplicate_pairs → PGRST202. All 3 EXIST in public with correct non-default ACLs (verified) — they are wrappable; the generator just doesn’t list them. Apply-to-prod ALONE does not green these jobs; extend the generator surface + re-run + apply. |
| PART 2 (de-ID) | A decision, not a single procedure. Documented/ratified mechanism (Option R) = git filter-repo --replace-text + --path-rename + the already-landed superseding migration, NEVER deleting an applied migration. Option S (fresh squash) is separable + undocumented-as-ID-68; with the REVOKE blocker now reduced to a normalisation step, it is viable but carries the reference-DB gap-hunt + storage/policy reconciliation precedent. Both green identity-guard. |
The conflation to keep separate (mempalace prior-investigation, verbatim substance):
- (A) S176 squash (~Apr 2026) is DONE + independent of ID-68. 43 migrations → baseline
supabase/migrations/20260416102457_pre_squash_reconciliation.sql(8,600 LOC). Today’s tree = that baseline + ~137 append-only deltas (141 files at this HEAD; the drawer’s “138” predates bl-328 + the 2 id115 api migrations). Only documented “collapse history into a clean baseline” op; finished. The S176 baseline has 0REVOKE EXECUTEbecause it predates OPS-43 (the functions were still default-ACL at squash time). - (B) ID-68 migration-history de-ID is NOT a squash. Content-redaction + file rename + append-only superseding migration, chain intact (Option R).
PART 1 — ID-115 api-Schema Cutover (clears Cluster A) — NO SQUASH
Section titled “PART 1 — ID-115 api-Schema Cutover (clears Cluster A) — NO SQUASH”Root cause: 20260616120000_id115_api_schema_create.sql + 20260616120100_id115_api_views_and_rpcs.sql are on main but not applied to the remotes (prod half-flipped: exposed-schemas → api, api objects empty). Fix = the S13 cutover: apply api migrations atomically per remote, flip exposed-schemas, regen types, fix the CI command. No history rewrite.
P1.0 — Close GAP A first (no GO; reversible)
Section titled “P1.0 — Close GAP A first (no GO; reversible)”Extend scripts/generate-api-views.ts so the 3 missing RPCs get api INVOKER wrappers:
_test_delete_broken_auth_user→ a_test_*/EXTRA-DEFINER lane (it’s a test helper — confirm it should be exposed at all, or fix the integration test to stop routing it throughapi).find_duplicate_pairs+get_content_win_rate→SURFACE_RPCSorEXTRA_DEFINER_RPCS(both verified present inpublicwith non-default ACLs; both INVOKER).- Per-RPC FIRST confirm the
publicoverload signature against the local catalog — the generator introspectspg_procper name and ABORTS on a name with no matching public fn. If any is a genuinely-dead.rpc()with a fallback, fix the test/eval side instead. - Re-run
bun scripts/generate-api-views.ts→bun scripts/check-api-view-coverage.ts→supabase db reset --local(foreground) →bun run testgreen + the 3 RPCs present asapi.*.
P1.1 — STAGING (turayklvaunphgbgscat) — full E2E + CI repurpose (no GO)
Section titled “P1.1 — STAGING (turayklvaunphgbgscat) — full E2E + CI repurpose (no GO)”cat supabase/.temp/project-ref→ MUST be staging. It currently readsrovrymhhffssilaftdwd= PROD — relinksupabase link --project-ref turayklvaunphgbgscat(CLAUDE.md drift hazard, LIVE now).- Apply: bring staging current (api schema + GAP-A wrappers).
supabase db pushforeground only (interactive prompt — never background, it hangs).dangerouslyDisableSandbox: truefor DB/CLI. config.toml:[api] schemas = ["api"]already set (:15); staging auth hook already →hook_restrict_signup_to_allowed_domain(:83-85, §PC-39(i) staging slice DONE) — confirm.- Exposed-schemas →
apiin lockstep with the apply (INV-19): Management APIPATCH /v1/projects/turayklvaunphgbgscat/postgrestbodydb_schema:"api", or Dashboard → Settings → API. - Verify (matrix items 1/7/8 TOGETHER): PGRST106 boundary (Accept-Profile: public → 406; api default → 200, INV-1/2); exposed-schemas ==
api;schema-parity.yml(dispatch) staging vs prod in sync onpublic. - Smoke: login → dashboard read → content read+write → one MCP tool → one client RPC ≠ PGRST106 (app smoke Liam Vercel-gated). Run full E2E here to classify e2e-4 (below).
P1.2 — PROD (rovrymhhffssilaftdwd) — completes the half-flip [GO-GATE P1]
Section titled “P1.2 — PROD (rovrymhhffssilaftdwd) — completes the half-flip [GO-GATE P1]”- GO-GATE P1 — irreversible prod DDL. Liam GO.
- Relink + verify ref == prod.
- Dump-diff prod
publicvs the migration set FIRST (schema-parity-style) — reconcile migration if non-zero, else proceed. - Apply api schema + views/RPCs to prod (
db pushforeground). Exposed-schemas alreadyapi(manual) — confirm, don’t re-flip. config.tomlprod block + Vercel-prod deploy already carryapirouting (on main).- Verify + smoke as P1.1-5/6. Half-flip hazard: if api stays empty after apply, every prod
api.*query 404s — the smoke “content read+write + client RPC → 200” is the catch; re-apply if it fails.
P1.3 — PLATFORM PROD DB + PLATFORM STAGING [GO-GATE P2]
Section titled “P1.3 — PLATFORM PROD DB + PLATFORM STAGING [GO-GATE P2]”- GO-GATE P2 — irreversible Platform-prod DDL. Highest blast radius (provisioning source-of-truth behind
seed-tenant-from-bundle.ts/seed-platform-from-staging.ts). - Resolve the Platform-prod ref from secrets (impl3: “lives in secrets; resolve at cutover”); link +
catverify; read its current exposed-schemas state BEFORE any change (impl3). - Apply api migrations; exposed-schemas →
api; provisioning/config.toml+ Platform deploy. - Verify + smoke against the Platform surface.
- Platform STAGING (S14 bake target) — same apply, lower stakes; can precede P1.3 as rehearsal.
P1.4 — Types + CI (no GO)
Section titled “P1.4 — Types + CI (no GO)”- Regen (both schemas, deterministic
publicthenapi):/opt/homebrew/bin/supabase gen types typescript --project-id rovrymhhffssilaftdwd --schema public,api > supabase/types/database.types.ts; thentsc+bun build(matrix item 12).database.types.tsis an agent-denied-read path — regen + commit only. - Fix the CI types-parity command (the types-parity red).
.github/workflows/ci.yml:1185-1194runs--schema public(single) and diffs the committed file (which carriesapi) → guaranteed fail. Change:1187--schema public→--schema public,api+ the::error::hint:1194. Commit WITH the regenerated types.
P1.5 — S14 provisioning bake (no GO)
Section titled “P1.5 — S14 provisioning bake (no GO)”Bake --schema public,api + db.schema='api' into provisioning (seed-tenant-from-bundle.ts + preview-branch + Platform + Platform-staging + Platform-prod); confirm the api-view coverage drift check runs in provisioning CI. Per impl3 S6: catalog-verify the 5 named api wrappers → mark S6 done.
PART 2 — ID-68 identity-guard De-ID — OPTION R vs OPTION S (owner picks)
Section titled “PART 2 — ID-68 identity-guard De-ID — OPTION R vs OPTION S (owner picks)”P2.0 — Why identity-guard is RED (precise mechanism)
Section titled “P2.0 — Why identity-guard is RED (precise mechanism)”identity-guard.yml runs on pull_request + push, git grep-ing the full tree for the secret-held KH_CLIENT_NAME_DENYLIST tokens minus a PENDING_DEID path-exclusion list (:95). The RED is one real client-named site outside that list: supabase/migrations/20260609093000_id68_21_drop_superseded_signup_hook.sql:21 = DROP FUNCTION IF EXISTS public.hook_restrict_signup_to_phew_domain(jsonb); (its CREATE counterpart is the FILENAME 20260424202806_capture_phew_domain_hook.sql).
Do NOT “fix” by adding the path to PENDING_DEID. Guard header :40-42: “Never ADD a path here to silence a red run — that is the exact re-introduction this guard exists to reject. New entries require an ID-68 spec amendment.” Both options below remove the leak so the path is enforced forever after.
OPTION R — Documented git filter-repo redaction (zero schema risk; RATIFIED)
Section titled “OPTION R — Documented git filter-repo redaction (zero schema risk; RATIFIED)”Authoritative docs: PRE-FLIP-DEID-PLAN.md (decisions B+E), ID-68 TECH.md §PC-35/§PC-39(i), executable runbook ${KH_PRIVATE_DOCS_DIR}/src/content/docs/runbooks/id68-filter-repo-purge.md (§4) + id68-purge-redaction-map-draft.md. Mechanism = content/message redaction + a one-file --path-rename + the already-landed generic superseding migration; applied migrations NEVER removed.
Procedure (condensed from the runbook; execute from it — this is the GO map):
- Mirror backup (MANDATORY FIRST — AC-E5):
git clone --mirror …knowledge-hub.git→ tagpre-id68-purge→ store OUTSIDE every tree → journal the absolute location (retains un-redacted history → client-confidential). - Path inventory:
bun scripts/generate-purge-path-inventory.ts --out ~/id68-purge-paths.txt(re-derive at purge time). The auth-hook migration is NEVER in the removal set (replay-break) — emitted as comment-only advisory. - Finalise the redaction map (OQ-H = Liam ratifies):
phew.org.uk→client.example,Phew Design Limited→Example Client Ltd,support@phew.org.uk,kh.phew.org.uk, the old hook name, the datacentre literal, the historical ICO number. - Execute on a FRESH MIRROR (two passes): pass 1
--invert-paths --paths-from-file; pass 2--force --replace-text --replace-message+ (Branch A)--path-rename 20260424202806_capture_phew_domain_hook.sql:…_capture_signup_domain_hook.sql(14-digit version prefix UNCHANGED → migration-history table stays keyed-consistent).- Migration handling — BOTH branches keep the file applied. Branch A redacts content + renames + replay proof: from a clone of the rewritten mirror,
supabase link --project-ref turayklvaunphgbgscat(STAGING, never prod) →supabase db resetreplays clean + signup gate still passes;supabase migration repairif name drift. Branch B = HEAD-scrub-only, each accepted residual journalled as an Inv 36 carve-out.
- Migration handling — BOTH branches keep the file applied. Branch A redacts content + renames + replay proof: from a clone of the rewritten mirror,
- Verification clone BEFORE force-push (AC-E2):
--analyze+git rev-list --objects --all | grep -f paths= zero +git log --all -S'<literal>'per map entry = zero. - Force-push (
git push --mirror): liftmainbranch protection viagh api …/branches/main/protection, push, restore the exact prior payload (re-verify identity-guard still required). GitHub retains pre-rewrite objects inrefs/pull/*+ cached views → close superseded PRs → GitHub Support GC before the{68.9}flip. - Fleet re-clone (every SHA changed): salvage
.env.local/supabase/.temp/first; delete + re-clone main; delete all.claude/worktrees/*;supabase linkre-verify; re-index gitnexus + ccc; accept orphaned branches/PRs/SHA-citations (journalled, not fixed).
Option R cost/risk: HIGH irreversibility (it IS the {68.10} purge — runs LAST, after Gates 1-4/6-8), ZERO schema/data risk (no migration deleted; replay preserved; backup-first). Migration-history leak removed by content redaction, not by collapsing the chain.
Scope note: {68.10} is the FULL-tree purge. If the owner wants identity-guard green NOW (before the whole flip is ready), R can run migration-slice-only as an interim (redact just 20260424202806/20260609093000/20260419212103:48 via scoped --replace-text+--path-rename on a mirror, same backup+replay-proof+force-push+re-clone discipline) — but this STILL triggers the full fleet re-clone (every SHA changes), so it is not cheaper than doing {68.10} once. Recommend: do {68.10} once when the flip gates are ready.
OPTION S — Fresh migration squash (clean public baseline; REVOKE blocker now REDUCED)
Section titled “OPTION S — Fresh migration squash (clean public baseline; REVOKE blocker now REDUCED)”A fresh supabase migration squash (the Docker local-replay tool — NOT db dump) collapses 141→1; the client-named CREATE+DROP vanish (baseline reflects only final state) → identity-guard green by construction + a single clean public baseline. A local Docker dry-squash was run this session: 141→1, ZERO schema deltas (tables/columns/NOT NULL/CHECK/FK/indexes/function-body md5/RLS/triggers/enums) — squash IS schema-faithful. (The dry-squash baseline artefact is NOT recoverable from /tmp/$TMPDIR/worktrees — it was ephemeral; the measurements below were taken against the LIVE prod catalog instead, which is the authoritative source for pg_dump’s ACL-emission behaviour.)
The REVOKE question — MEASURED, not assumed (owner’s correction)
Section titled “The REVOKE question — MEASURED, not assumed (owner’s correction)”Owner’s premise: ID-115’s explicit REVOKE/GRANT discipline made the functions’ ACLs non-default, so a fresh dump now emits them (vs the S176 baseline’s 3) → revoke-guard passes.
Measured against live prod rovrymhhffssilaftdwd (this dispatch):
- 114 public functions; 114 have non-default
proacl(non-NULL); 0 default; 22 SECURITY DEFINER. → pg_dump emits an explicit ACL block for ALL 114 (pg_dump omits a function’s ACL only whenproacl IS NULL). The owner is RIGHT that the REVOKEs are no longer absent. The S176 3-REVOKE problem is GONE (S176 predates OPS-43; OPS-4320260502143049issued 131 REVOKEs across 102 fns + tightenedpg_default_acl; subsequent migrations kept ACLs non-default). - Sample (6 of 7):
hybrid_search/claim_next_job/get_workspace_counts/merge_entities/find_duplicate_pairs/get_content_win_rate→public_has_execute = false, granteesauthenticated, service_role(anon excluded).set_config→public_has_execute = true(the lone intended anon-exec, INV-20).
THE REMAINING OBSTACLE (guard-regex layer, not ACL layer): pg_dump renders a non-default ACL with PUBLIC-revoked as REVOKE ALL ON FUNCTION public.<fn>(…) FROM PUBLIC; + per-role GRANT EXECUTE … TO authenticated, service_role;. But scripts/check-revoke-guard.ts:
REVOKE_FUNCTION_RE(:115) matches onlyREVOKE\s+EXECUTE\s+ON\s+FUNCTION→ pg_dump’sREVOKE ALL …form does not match the regex at all.matchRevokeForCreate(:566):if (!r.grantees.some((g) => g.toLowerCase() === 'anon')) continue;→ requires the grantee list to literally containanon; pg_dump emitsFROM PUBLIC(noanon, because once PUBLIC is revoked anon’s inherited grant is gone). Fails theanoncheck even if the form matched.
→ A fresh squash baseline WOULD still trip revoke-guard — not because REVOKEs are missing (they’re emitted for all 114), but because they’re in REVOKE ALL … FROM PUBLIC form, which the guard’s strict REVOKE EXECUTE … FROM <…anon…> matcher rejects.
Option S costing — REVISED (blocker → normalisation step)
Section titled “Option S costing — REVISED (blocker → normalisation step)”The fix is NOT REVOKE re-injection (my prior draft was wrong — the REVOKEs exist). It is one of two mechanical steps, both small:
- (S-fix-a) Normalise the dump’s REVOKE form in the baseline — a scripted post-process turning each
REVOKE ALL ON FUNCTION public.<fn>(…) FROM PUBLIC;into the guard-acceptedREVOKE EXECUTE ON FUNCTION public.<fn>(…) FROM PUBLIC, anon;(semantically identical for these fns — they have no other PUBLIC privilege; ALL≡EXECUTE for a function, and naminganonis a no-op once PUBLIC is gone). One pass over the generated baseline, deterministic. - (S-fix-b) OR teach the guard the dump form — extend
REVOKE_FUNCTION_RE+ the grantee check to acceptREVOKE ALL … FROM PUBLICas satisfying the requirement (since revoking PUBLIC strictly dominates revoking anon). A guard-side change, lands in the same PR as the baseline. This is arguably the more correct fix (the guard’sanon-literal requirement is an OPS-43-era artefact; PUBLIC-revoke is stronger), but it widens what the guard accepts — review carefully so it cannot mask a genuinely-missing REVOKE in a future hand-written migration.
Residual Option S burden (S176 precedent, verbatim — NOT eliminated by the REVOKE finding): S176 had to recover “a missing table, 9 missing columns, 4 MCP-created functions, 46 ALTER FUNCTION signature fixes, 3 storage-bucket INSERTs, 6 storage RLS policies” + ~7 reconcile migrations (20260419095200..134609). A schema-only dump omits storage buckets, storage RLS policies, and dashboard-wired auth hooks — manual re-add. S176 cross-referenced LIVE PROD to find gaps and iterated until clean replay; the same gap-hunt is required. The dry-squash’s ZERO-delta was against a reference DB; the markdown_batch CHECK drift (20260615233000) surfaces if the reference DB is wrong.
Option S No-Go checklist (ALL required): owner GO + staging creds (foreground, dangerouslyDisableSandbox: true) + verified reference DB + revoke-guard normalisation plan (S-fix-a or S-fix-b) + storage-bucket/RLS/auth-hook re-add list + git clone --mirror backup + staging supabase db reset replay proof. (KH_CLIENT_NAME_DENYLIST is unset locally → de-ID validates only in CI.)
P2.X — Recommendation (REVISED)
Section titled “P2.X — Recommendation (REVISED)”The REVOKE finding narrows the gap but does not flip the recommendation. Take Option R unless the owner wants a fresh consolidated baseline for its own sake.
- Schema-risk axis: R = zero (no migration deleted, replay preserved). S = low-but-nonzero (the REVOKE issue is now a one-script normalisation, BUT the storage-bucket/RLS/auth-hook omission + reference-DB gap-hunt remain real per the S176 precedent — these are the actual S risks, not REVOKEs).
- Operational-cost axis: ~parity — both force-push and trigger a full fleet re-clone (every SHA changes).
- Documentation axis: R is documented end-to-end (
id68-filter-repo-purge.md); S’s reconciliation + normalisation steps are not yet written. - End-state axis: S is cleaner (single public baseline, no redaction map to maintain) — the only axis where S wins.
Decision rule for the owner: if the goal is only identity-guard green → R (ratified, zero schema risk, do {68.10} once when flip-gates ready). If the owner also wants to retire the 141-file migration history into one clean baseline for maintainability → S is now genuinely viable (the REVOKE blocker is downgraded to a scripted step), but budget the storage/RLS/auth-hook reconciliation + reference-DB gap-hunt and write S-fix-a/b first.
GO-GATE LIST (Liam GO required; orchestrator surfaces)
Section titled “GO-GATE LIST (Liam GO required; orchestrator surfaces)”| # | Step | Part | Reversibility |
|---|---|---|---|
| GO-A | Close GAP A — extend generator, re-run, local reset (flagged: gates the greening, no irreversibility) | P1.0 | Reversible |
| GO-P1 | PROD DDL — apply api schema/views/RPCs to rovrymhhffssilaftdwd | P1.2 | Additive (api new); app routes to api once deployed |
| GO-P2 | PLATFORM PROD DB DDL + exposed-schema flips (staging/Platform) | P1.3 | As GO-P1; provisioning-source blast radius |
| GO-R | Option R {68.10} purge — mirror backup → filter-repo rewrite → force-push → fleet re-clone | P2/R | IRREVERSIBLE; backup-first + replay-proof |
| GO-S | Option S fresh squash — ONLY if owner picks S, after the No-Go checklist (incl. revoke-guard normalisation S-fix-a/b + storage/RLS/auth-hook re-add) | P2/S | IRREVERSIBLE |
No GO (reversible/additive): GAP-A generator change, staging DDL apply, local db reset, CI types-parity command edit, type regen, S14 bake.
CI GREENING MAP — 9 failing jobs
Section titled “CI GREENING MAP — 9 failing jobs”| Failing job | Cleared by | Classification / residual |
|---|---|---|
| identity-guard | PART 2 (Option R redaction OR Option S squash) — removes the 20260609093000:21 client-named DROP from the grepped tree. | PART-2-CLEARED by either option. Non-migration phew strings (fixtures/Python tests/kh.phew.org.uk) are in PENDING_DEID today; full green needs the full {68.10} (R) or squash + those paths cleaned — confirm the only OUTSIDE-PENDING_DEID hit is the migration site (else still red on other paths). |
| CI: types-parity | P1.4.2 ci.yml:1187 → --schema public,api + P1.4.1 regen. | PART-1-CLEARED. |
| CI: integration (9) | P1.2 api apply + GAP A api._test_delete_broken_auth_user. | PART-1-CLEARED ONLY IF GAP A closed. Apply alone does NOT add the missing RPC. |
| CI: MCP eval-l4 (2 of 3) | P1.2 api apply + GAP A api.find_duplicate_pairs (FC-44) + api.get_content_win_rate (FC-51). | PART-1-CLEARED ONLY IF GAP A closed. FC-63 (missing seed content-item UUID) is SEPARATE — seed-data fix, not schema. |
| CI: e2e (4) | guide-pages.spec (h1/TOC) + workspaces.spec (card/grid). get_workspace_counts IS in the surface (one of the 5 entrypoints) → workspaces.spec LIKELY green; guide-pages depends on a guide-content RPC — CONFIRM it is in RPC_NAMES (candidate GAP-A extension). | PARTIALLY PART-1-CLEARED — CONFIRM via the P1.1 staging E2E run. guide-pages may need a guide RPC added (GAP-A extension). |
| CI: advisors | Advisor 0010 (security_invoker) by-construction green post-api (every api view security_invoker=true; generator self-checks :563). | SEPARATE unless the failure is a 0010 / api-grant finding. Pull the advisor diff to classify. |
| CI: pytest | Python pipeline writes public.* via asyncpg search_path (INV-14, untouched). | SEPARATE — unrelated (impl3: 1306 pass + 1 test_cocoindex_server async-mock fail). Diagnose independently. |
| CI: ci-summary | Aggregates the above (ci.yml:1304+). | DERIVED — greens once deps (incl. types-parity :1328/1343) green. |
Net: Part 1 clears 4 (types-parity + integration-9 + eval-2, CONTINGENT ON GAP A) + partially e2e-4 (confirm guide RPC). Part 2 clears identity-guard (R or S; confirm migration is the sole outside-PENDING_DEID hit). advisors + pytest SEPARATE pending their finding; eval FC-63 + ci-summary SEPARATE/derived. The two parts are independent — Part 1 greens Cluster A without touching the de-ID; identity-guard is greened by the owner’s R-or-S choice on its own timeline.
ROLLBACK + RISKS
Section titled “ROLLBACK + RISKS”- Part 1 additive + reversible: api schema new, no data touched, failed apply re-applies. Half-flip hazard: never flip a remote’s exposed-schemas to
apibefore its api objects exist (prod is the existing half-flip; the migration completes it). P1.2 smoke is the catch. - Part 2 Option R: the documented
{68.10}— irreversible, backup-first non-negotiable, replay-proof on STAGING (never prod), force-push lifts+restores branch protection, full fleet re-clone. ZERO schema/data risk. - Part 2 Option S: REVOKE form mismatch trips revoke-guard (fix via S-fix-a normalisation OR S-fix-b guard widening — NOT re-injection; the ACLs are present, 114/114 non-default); storage buckets + storage RLS policies + dashboard auth hooks NOT in a schema-only dump (S176 re-added 3+6+hook) → manual reconcile; verify the reference DB (markdown_batch CHECK drift if wrong); a no-DB dry-run is impossible (lexical concat ≠ squash).
- Live hazard (NOW):
supabase/.temp/project-ref=rovrymhhffssilaftdwd(PROD). Any unverifieddb pushfrom the main checkout lands on PROD silently. First action of every apply phase = relink +catverify. - Worktree/sandbox (ID-115 impl3): id-115 worktree needs the
server-onlystub forbun run test. Local DB/REST/CLI +git commitneeddangerouslyDisableSandbox: true.db pushinteractive-foreground-only. Clear<main>/.git/worktrees/<wt>/index.lockif a commit dies. gitnexus post-commit hook races.git/index.lockon rapid cherry-picks —git -c core.hooksPath=$(mktemp -d)for multi-commit runs. KH_CLIENT_NAME_DENYLISTunset locally → de-ID (either option) validates only in CI.
LEDGER-WRITE INTENTS (orchestrator applies on MAIN — do NOT write here)
Section titled “LEDGER-WRITE INTENTS (orchestrator applies on MAIN — do NOT write here)”| When | Intent |
|---|---|
| Pre-execution | Open a GAP-A tracking subtask under ID-115 (extend generator RPC surface for _test_delete_broken_auth_user / find_duplicate_pairs / get_content_win_rate / guide-content RPC) — prerequisite of S13, sibling to S12/S13. |
| After P1.1 staging | Journal ID-115 {115.13}: staging api applied + flipped + smoke GREEN + full E2E (record e2e-4 classification). |
| After P1.2 prod | Journal {115.13}: prod half-flip completed (api populated), smoke GREEN. |
| After P1.3 Platform | Journal {115.13}: Platform-prod + Platform-staging applied + flipped + smoke GREEN → flip {115.13} → done (Checker-gated). |
| After P1.4/P1.5 | Journal the ci.yml --schema public,api fix; {115.14} S14 bake; flip {115.6}/{115.12} → done per impl3. |
| After Part 2 (R or S) | Option R: journal ID-68 {68.10} — backup location, inventory HEAD SHA, ratified map (OQ-H), Branch A/B + replay-proof, verification outputs, protection-restore + GC. Do NOT flip {68.9} done (its own Gate 1-9 table beyond identity-guard). Option S: journal a new squash subtask — revoke-guard normalisation (S-fix-a/b), reference-DB, storage/RLS/auth-hook re-add, reconcile migrations. |
Appendix — verified facts (this dispatch; empirical results bolded)
Section titled “Appendix — verified facts (this dispatch; empirical results bolded)”- Live prod
rovrymhhffssilaftdwd: 114 public functions, 114 non-defaultproacl, 0 default, 22 SECURITY DEFINER → pg_dump emits ACLs for ALL 114 (owner’s “REVOKE now emitted” CONFIRMED). Sample: 6/7public_has_execute=false(anon excluded),set_config=true(sole anon-exec, INV-20). check-revoke-guard.ts:REVOKE_FUNCTION_RE(:115) matches onlyREVOKE EXECUTE ON FUNCTION;matchRevokeForCreate(:566) requires grantee list to contain literalanon. pg_dump’sREVOKE ALL … FROM PUBLICform satisfies NEITHER → squash still trips the guard UNLESS normalised (S-fix-a) or the guard is widened (S-fix-b). Revoke-guard is diff-scoped (git diff --name-only <baseline> HEAD).- S176 baseline
20260416102457has 0REVOKE EXECUTE(predates OPS-43); OPS-4320260502143049= 131 REVOKEs / 102 fns +pg_default_acltightening. identity-guard.ymlgreps full tree minusPENDING_DEID(:95); header:40-42forbids adding paths to silence reds.20260609093000:21DROP is the outside-PENDING_DEID hit.id68-filter-repo-purge.md= the executable Option-R procedure (mirror backup → 2-pass filter-repo →--path-renamekeeps the migration applied → staging replay-proof → force-push → fleet re-clone).generate-api-views.tsRPC_NAMES=SURFACE_RPCS(−3MISSING_RPCS) + 7EXTRA_DEFINER_RPCS. ABSENT:_test_delete_broken_auth_user,find_duplicate_pairs,get_content_win_rate(GAP A; all 3 verified present inpublic). PRESENT: the 5 named entrypoints.ci.yml:1187=--schema public(single) — the types-parity red.config.toml:15=schemas=["api"];:83-85staging hook → generic (§PC-39(i) staging DONE).supabase/.temp/project-ref=rovrymhhffssilaftdwd(PROD) — drift hazard LIVE.- 141 migration files; worktree
id-115-data-api-schema-isolationtip71c68ad0e(+1 vs main: api-views search_path pin). The earlier dry-squash baseline artefact is NOT recoverable from disk (ephemeral) — REVOKE measurement taken against the live catalog instead. - mempalace precedent: S176 squash cross-referenced LIVE PROD, schema-only, needed reconcile for “a missing table, 9 missing columns, 4 MCP fns, 46 ALTER FUNCTION sig fixes, 3 storage INSERTs, 6 storage RLS policies”.