feat(knowledge): map the CRM surface, and find a P0 the red check was reporting - #1690
Conversation
… reporting
Three tasks, one thread: the permanently-red `Supabase Preview` check turned out
to be telling the truth.
1. A COMMITTED P0 MIGRATION IS NOT APPLIED TO PRODUCTION
supabase/migrations/20260819200000_preserve_golf_history_on_account_deletion.sql
carries the owner decision of 2026-08-18 — "There should be no deletion of golf
shot history" — and measures that 93 of 94 players reach the cascade. Verified
read-only against production 2026-08-30, it is not live:
golf_players.user_id still NOT NULL (migration: nullable)
golf_players_user_id_fkey ON DELETE CASCADE (migration: SET NULL)
golf_player_anonymize_on_unlink does not exist
So DELETE /api/account/delete still destroys that player's rounds, holes, shots,
reviews and stats cache. Filed as
memory/incidents/golf_round_lifecycle/INC-2026-08-30-account-deletion-still-cascades-golf-history.md.
NOT fixed here: applying a production migration needs explicit owner
authorization, and `supabase db push` / `migration up` are denied by
permissions.deny, correctly.
The wider divergence is registered as
MIGRATIONS_REPO_PRODUCTION_LEDGER_DIVERGENCE: 846 ledger versions against 337
tracked files. Most of that gap is explainable — 240 versions predate the
squashed baseline, and applying via dashboard/MCP stamps its own timestamp, so
46 of 54 "missing" August versions are the same migration under a different
version. Two of this session's own first guesses were WRONG for the same reason
(baseball_event_telemetry is a migration name, not a table; the table is
baseball_timeline_event_acks), and the incident records that rather than hiding
it. Supabase Preview is therefore NOT decommissioned: it reports a real
condition, and turning it off would silence a true alarm.
2. THE CRM SURFACE IS MAPPED
memory/features/crm_outreach.md, written from verified code — 3 routes, 20
action files (two of which break the crm- naming convention and are listed
individually), 11 API routes, 20 owned tables, 6 integrations. Registered as a
feature and now owns the crm_recruiting_pipeline runtime key, closing the last
feature_awareness_gap.
KNOWLEDGE ROUTING ONLY, and checked rather than assumed: src/lib/admin/
feature-registry.ts carries an owner directive that CRM is never wrapped,
tagged, or shown on the Bridge board. That row is untouched — still
excluded: 'crm', still actions: {}. Verified that memory/registry.yml is read
only by scripts/knowledge/**, scripts/repo-doctor and the drift gates, and that
src/lib/reliability/collect.ts resolves feature ids from a hardcoded route table
because it "cannot read a build-time artifact". Nothing here reaches the Bridge.
Also recorded, not silently fixed: sending is human-triggered by omission —
process-sequences is fully wired but deliberately absent from vercel.json, so
adding it to "fix a missing schedule" would start sending cold email.
3. TWO STALE CLAIMS CORRECTED FORWARD
DATABASE_STATUS.md's "NOT FIXED — needs a product decision" heading sat above a
section that had since grown an "✅ APPLIED" block with role-impersonation
evidence. Re-verified against live pg_policies (not the migration file — trap
G8): baseball_coaches_select is org-scoped. Already fixed.
And the convergence report said "two items marked NOT FIXED". There was one,
plus one separately-declared deliberate gap — a count written into prose, wrong,
in the run whose thesis is that stale counts read as current forever.
4. THE GATE THAT CAUGHT THE INCIDENT
docs:schema-drift failed on the incident, correctly: it names four identifiers
that are not in the schema. That is the incident's SUBJECT — deleting them
deletes the finding — and baselining them would be wrong twice, since the
baseline is for known-bad references that should shrink. So a document may now
DECLARE, per identifier, that it names something because it is absent:
<!-- schema-drift-absent: name_one, name_two -->
Exempt only those names, only in that file, printed on every run. A typo two
lines away is still caught; another document's declaration covers nothing.
Injection: making the declaration file-global turns that test red, script
restored byte-identical (sha256).
Verified: preflight 0, helm-os:check 0, control-plane:verify VERIFIED (6
registered gaps), 223 tests across src/test/scripts, ratchet 30514 unchanged.
All production reads were read-only through the account MCP.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbDxyygyXRUEERuGocpZZH
|
ⓘ Qodo reviews are paused because your trial has ended. Ask your workspace admin to add credits to resume reviews. Manage billing |
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
The designed workflow, second exercise: the next ordinary PR clears the previous PR's transitionally-closed row while adding its own. No push to main. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NbDxyygyXRUEERuGocpZZH
CI caught this and local runs did not, for a reason worth writing down: the generator enumerates `git ls-files`, which reports the INDEX. Running it before `git add` of a new document counts the tree without that document — 1693 instead of 1694 — and the local --check then agrees with itself, because both halves read the same stale index. CI checks out the merge commit, where the file IS tracked, and disagrees. So the generator is deterministic across checkouts (the convergence run proved that by reading CI's own log); what is order-dependent is WHEN you run it relative to staging. Regenerate after `git add`, not before. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NbDxyygyXRUEERuGocpZZH
…ation header
The schema half was verified first-hand — user_id NOT NULL, FK ON DELETE
CASCADE, no anonymize function — and that proves the migration is unapplied. But
the impact sentence ("account deletion still destroys the history", "93 of 94
players reach the cascade") came from the MIGRATION'S OWN HEADER, written
2026-08-19. Eleven days and many merges later that is a quoted comment, not
evidence, in a record filed at R4 for irreversible data loss. G8 inverted, and
in the worst possible place.
Read the route as it stands today. It holds:
route.ts:150 USER_BLOCKING_TABLES is still exactly the three attribution
tables — nothing blocks on golf_rounds or golf_shots
route.ts:308 still ends in admin.from('users').delete().eq('id', user.id)
whole file names golf_rounds / golf_shots / golf_players NOWHERE, so no
application-side preservation was added either
So the finding stands, now on first-hand evidence with file:line anchors instead
of a quotation. The 93-of-94 ratio is re-attributed as 2026-08-18's measurement,
explicitly not re-measured here — it is a live-data figure this session cannot
cheaply reproduce, and pretending otherwise would be the same error one level
down.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NbDxyygyXRUEERuGocpZZH
Tasks 11, 12 and 14. They turned out to be one thread: the permanently-red
Supabase Previewcheck was telling the truth.1. A committed P0 migration is not applied to production
supabase/migrations/20260819200000_preserve_golf_history_on_account_deletion.sqlcarries the owner decision of 2026-08-18 — "There should be no deletion of golf shot history". Verified read-only against production today:golf_players.user_idnullableNOT NULLON DELETE SET NULLON DELETE CASCADEgolf_player_anonymize_on_unlink()And the consequence was verified against the route as it stands today, not quoted from that migration's header — eleven days and many merges separate them, and a stopgap could have landed:
route.ts:150—USER_BLOCKING_TABLESis still exactly the three attribution tables; nothing blocks ongolf_rounds/golf_shotsroute.ts:308— still ends inadmin.from('users').delete().eq('id', user.id)golf_rounds/golf_shots/golf_playersnowhereSo the cascade still destroys the player's rounds, holes, shots, reviews and stats cache. The "93 of 94 players" figure is re-attributed as 2026-08-18's measurement, explicitly not re-measured here.
Not fixed here. Applying a production migration needs explicit owner authorization;
supabase db push/migration upare denied bypermissions.deny, correctly.Task 14's answer is A, not B. The check reports a real condition; decommissioning it would silence a true alarm. The wider divergence (846 ledger versions vs 337 tracked files) is registered as
MIGRATIONS_REPO_PRODUCTION_LEDGER_DIVERGENCEwith honest arithmetic: most of the gap is explainable — 240 versions predate the squashed baseline, and applying via dashboard/MCP stamps its own timestamp, so 46 of 54 "missing" August versions are the same migration under a different version. Two of my own first guesses were wrong for that reason, and the incident records them rather than hiding them.2. The CRM surface is mapped
memory/features/crm_outreach.md, written from verified code. Ownscrm_recruiting_pipeline, closing the lastfeature_awareness_gap.Knowledge routing only — checked, not assumed.
feature-registry.tscarries an owner directive that CRM is never wrapped, tagged, or shown on the Bridge board. That row is untouched (excluded: 'crm',actions: {}). Verifiedmemory/registry.ymlis read only byscripts/knowledge/**,scripts/repo-doctorand the drift gates, and thatsrc/lib/reliability/collect.tsuses a hardcoded route table because it "cannot read a build-time artifact".Recorded rather than silently fixed: sending is human-triggered by omission —
process-sequencesis wired but deliberately absent fromvercel.json, so adding it to "fix a missing schedule" starts sending cold email.3. Two stale claims corrected forward
DATABASE_STATUS.md's "NOT FIXED — needs a product decision" heading sat above a section that had grown an "✅ APPLIED" block. Re-verified against livepg_policies, not the migration file: already fixed. And the convergence report said "two items" — there was one, plus one declared deliberate gap.4. The gate caught the incident, correctly
docs:schema-driftfailed on the incident: it names four identifiers not in the schema. That is the incident's subject. Baselining them would be wrong twice — the baseline is for known-bad references that should shrink. So a document may now declare, per identifier:Exempt only those names, only in that file, printed every run. Injection: making the declaration file-global turns that test red; script restored byte-identical.
One thing CI caught that local runs could not
The document inventory enumerates
git ls-files, i.e. the index. Running it beforegit addof a new doc counts the tree without that doc, and the local--checkthen agrees with itself because both halves read the same stale index. Regenerate after staging.Verification
preflight 0 ·
helm-os:check0 ·control-plane:verifyVERIFIED (6 gaps) · 223 tests insrc/test/scripts· ratchet 30514 unchanged. All production reads were read-only.🤖 Generated with Claude Code
https://claude.ai/code/session_01NbDxyygyXRUEERuGocpZZH