Add scripted verification checklist for provisioning trigger - #71
Conversation
…ger (#63) provision_workspace_membership() (the SECURITY DEFINER trigger auto-provisioning workspace_members on invite) had only ever been checked by ad hoc, undocumented rolled-back transactions (PR #60 review finding M5). Add one reusable, self-asserting asset. Changes: - Add scripts/verify_provisioning_trigger.sql: 6 numbered, independent BEGIN...ROLLBACK scenarios (happy path, UPDATE-after-INSERT, is_default guard, orphan company_id, idempotent re-run, routine last_sign_in_at no-op), each self-reporting PASS/FAIL via RAISE NOTICE/EXCEPTION on synthetic fixtures — zero residue on prod - Link the script from docs/operations.md §1 with guidance on when to run it Task 3 (live 6/6 PASS run against prod + cross-repo-log entry) is a post-merge step requiring Supabase MCP access, matching PR #60's precedent. Fixes #63 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
🔍 Comprehensive PR ReviewPR: #71 SummaryThis PR adds a 6-scenario, self-asserting SQL verification checklist for the Verdict:
🟠 High IssueHeader comment claims coverage of the #62 cast-guard regression that no scenario tests📍 No scenario inserts a malformed (non-UUID) View fix (also resolves the MEDIUM issue below)-- Covers one failure mode this trigger has already hit in prod — the 42P17 "WHEN cannot
-- reference OLD" class (guard now lives in the function body, fixed in commit f3920ab) —
-- plus the is_default partial-unique-index collision (latent per issue #61, never
-- reproduced; hardened via the #61 UNIQUE constraint) and the three non-firing /
-- idempotency paths. Does NOT cover #62 (the malformed-company_id cast guard / WHEN-clause
-- regex) — no scenario here exercises a non-UUID company_id string.🟡 Medium Issue"already hit" mischaracterizes the is_default collision as a reproduced incident📍 Issue #61 states "Priority: P2. Latent, not currently reproducible" — the guard was designed in from the trigger's first commit and hardened after a code-review finding, not an observed failure. The header's "already hit once each" wording blurs that distinction. Options: Fix now (same edit as the HIGH issue above, single comment block) | Skip (leaves the mischaracterization) Recommendation: Fix now — zero behavior change, one edit resolves both HIGH and MEDIUM findings. 🟢 Low IssueView 1 low-priority suggestion
✅ What's Good
📋 Suggested Follow-up Issues
Next Steps
Reviewed by Archon comprehensive-pr-review workflow |
Fixed: - scripts/verify_provisioning_trigger.sql header overclaimed coverage of the #62 malformed-company_id cast guard (no scenario tests it) and mischaracterized the is_default collision as a reproduced prod incident (issue #61 records it as latent, never reproduced). Skipped: - README.md scripts/ tree omission of the new script — docs-impact agent explicitly recommends no action (tree already omits 3 pre-existing scripts; piecemeal fix would just extend the gap).
⚡ Self-Fix Report (Aggressive)Status: COMPLETE Fixes Applied (2 total)
View all fixes
Tests Added(none — no test-coverage finding; this PR is itself the test-coverage deliverable for #63) Skipped (1)
Suggested Follow-up Issues
Validation✅ Offline pytest (157 passed, 17 deselected) | Self-fix by Archon · aggressive mode · fixes pushed to |
…prod Ran scripts/verify_provisioning_trigger.sql (PR #71, issue #63) via Supabase MCP execute_sql against prod zgkvbjqlvebttbnkklpo, 6 blocks one at a time. All PASS, 0 residue after (every block rolls back). Closes the deferred live-verification item from #71. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Summary
provision_workspace_membership()— the SECURITY DEFINER trigger onauth.usersthatauto-provisions
workspace_membersrows on invite — has only ever been checked by ad hoc,undocumented rolled-back transactions run one-off through Supabase MCP
execute_sqleach timea related migration landed (PR #60, #61/#68, #62/#70). PR #60's review flagged this as finding
M5: there was no reusable, complete verification asset.
This PR adds one: a single self-asserting SQL script,
scripts/verify_provisioning_trigger.sql,covering the 6 scenarios named in the issue. Each scenario is an independent
BEGIN … ROLLBACKblock using synthetic fixture users (never real prod data), ending with a
PASS/FAILRAISEself-check instead of requiring the operator to eyeball table contents.Changes
scripts/verify_provisioning_trigger.sql(new, +283 lines) — 6 numbered, independent,self-asserting scenarios, each wrapped in its own
BEGIN … ROLLBACK(zero residue on prodregardless of outcome):
INSERTwithcompany_idpresent (happy path)company_idset only on a laterUPDATE(the real auth-gateway 2-step path)is_defaultguard — user already holds a default membership elsewherecompany_idresolves to nohalcrm_workspacesrow (RAISE WARNING, no abort, no row)last_sign_in_atupdate does not re-invoke the triggerdocs/operations.md(+8 lines) — new bullet under §1 "Migrations" pointing to the scriptand stating when to run it (after any change to
provision_workspace_membership(),trg_provision_workspace_membership,workspace_members, orhalcrm_workspaces.company_id).No change to the trigger function, trigger definition, or any table schema — this is
verification-only.
Deviation from plan
Scenario 3 uses
INSERT(empty meta) → seed theic-ingenieurs-conseilsdefault membership →UPDATE company_id, rather than a singleINSERTwithcompany_idalready set. A singleINSERTwould fire the trigger before the pre-existing default membership is seeded (FK onworkspace_members.user_idrequires the user to already exist), defeating the exact guardscenario 3 targets. The INSERT-empty → seed → UPDATE sequence is the only FK-valid ordering that
exercises the invariant (new row must land
is_default=falsewhen a default already exists);behavior under test is unchanged.
Validation
grep -c 'ROLLBACK;' scripts/verify_provisioning_trigger.sql(expect 6)grep -c 'COMMIT;' scripts/verify_provisioning_trigger.sql(expect 0)grep -c 'verify_provisioning_trigger' docs/operations.md(expect 1)make test(deno check ×2 + offline pytest)The live prod run (Task 3 of the plan) is a post-merge step for Renaud, matching PR #60's
"committed only, verified separately" precedent — it requires interactive Supabase MCP access
to
zgkvbjqlvebttbnkklpoand will be logged as a dated entry indocs/cross-repo-log.mdoncerun.
Scope limits
one solely for a manual, ~monthly verification checklist would violate KISS/YAGNI. The
existing Supabase MCP
execute_sqlpath is the tool every prior verification already used.CI — migration smoke-test against an ephemeral DB with the prod schema (catch 42P17-class errors before apply) #65 (ephemeral-DB migration smoke test).
provision_edifice_org— per the 2026-07-23 cross-repo-log decision,not propagating trigger-adjacent tooling across repos without a concrete need.
Fixes #63