fix(db): guard ::uuid cast in provisioning trigger against malformed company_id - #66
fix(db): guard ::uuid cast in provisioning trigger against malformed company_id#66BluegReeno wants to merge 1 commit into
Conversation
🔍 PR Review SynthesisPR: #66
|
| Severity | Count |
|---|---|
| 🔴 CRITICAL | 0 |
| 🟠 HIGH | 0 |
| 🟡 MEDIUM | 1 |
| 🟢 LOW | 2 |
🟡 Medium Issue (Needs Decision)
Malformed company_id skip has no diagnostic signal
📍 supabase/migrations/20260722195616_guard_company_id_uuid_cast.sql:20-26
The function already logs (RAISE WARNING) when company_id is well-formed but matches no halcrm_workspaces row. This PR's new malformed-company_id skip is structurally the same outcome (no membership created) but leaves zero trace, because a WHEN clause can't execute RAISE. A user with a malformed company_id gets a working invite but silently zero workspace access, with no log breadcrumb explaining why.
Both agents agree: not a blocker for this PR (moving the check into the function body would exceed this PR's intentionally trigger-only scope). Recommended: file a follow-up issue for a periodic ops check (e.g. scan auth.users for non-UUID company_id values with no matching workspace_members row) — mirroring how this same PR already flagged the sibling edifice trg_provision_edifice_org gap in docs/cross-repo-log.md.
🟢 Low Issues
View 2 low-priority notes
| Issue | Location | Suggestion |
|---|---|---|
Regex accepts only canonical dashed UUID form, narrower than Postgres's ::uuid parser |
20260722195616_guard_company_id_uuid_cast.sql:24-25 |
No action needed — company_id is only ever written by auth-gateway in canonical form; widening would be speculative (YAGNI) |
| Same finding, from error-handling's independent pass | same location | Optional: one-line comment documenting the canonical-form assumption for future readers |
✅ What's Good
- Live-verified against prod: trigger/function text match the migration exactly, function body byte-identical to the prior migration, no
42P17regression. docs/cross-repo-log.mdproactively documents the out-of-scope edifice companion-trigger gap rather than leaving it silent.- Validation was a rolled-back behavioral test against real prod data (both the malformed-input and valid-UUID paths), not just static review.
.claude/STATUS.mdcorrectly moves issue P2 — Guard ::uuid cast in provisioning trigger against malformed company_id #62 to Done in the same commit.
📋 Suggested Follow-up
| Issue Title | Priority | Related Finding |
|---|---|---|
Add periodic ops check for malformed company_id values silently skipped by provisioning trigger |
P3 | MEDIUM finding above |
(The edifice-side companion-trigger fix is already tracked as a follow-up in docs/cross-repo-log.md / .claude/STATUS.md — not a gap in this repo's tracker.)
Next Steps
- No CRITICAL/HIGH issues block merge.
- Decide on the MEDIUM finding: file the periodic-check follow-up (recommended) or explicitly accept the gap.
- Optional: add the one-line regex-assumption comment.
- Confirm whether the missing test-coverage/comment-quality/docs-impact passes are needed before treating this review as complete.
- PR is currently a draft — mark ready when the above is resolved.
Synthesized from 2/5 planned review agents. Artifacts: artifacts/runs/87b9961cdc3f64c7e651dffe5b31eb1a/review/
⚡ Self-Fix Report (Aggressive)Status: COMPLETE Fixes Applied (1 total)
View all fixes
Tests Added(none — this repo does not unit-test SQL migrations; validation stays manual review + rolled-back behavioral test, already done before review) Docs Updated
Skipped (2)
Suggested Follow-up Issues
Validation✅ Tests (157 passed, pytest) | Self-fix by Archon · aggressive mode · fixes pushed to |
…company_id (#62) The WHEN clause of trg_provision_workspace_membership now regex-validates the canonical UUID shape before the function's unconditional ::uuid cast runs, so a malformed company_id fails WHEN, the function never executes, and the auth.users write commits (provisioning silently skipped) instead of aborting on 22P02. Trigger-only fix-forward — function body unchanged, WHEN references only NEW (no 42P17). Already applied to prod zgkvbjqlvebttbnkklpo via apply_migration. Flattened onto main (was a merge commit) to resolve the STATUS.md / cross-repo-log.md overlap with #61 (#68) — both follow-ups now land in the same backlog line and Done section. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
|
097d340 to
26425b3
Compare
Summary
provision_workspace_membership()(PR #60,supabase/migrations/20260719000000_provision_workspace_membership.sql) castsraw_app_meta_data->>'company_id'touuidunconditionally in itsDECLAREsection. The trigger'sWHENclause only checked the value was non-empty text, not that it was a syntactically valid UUID — a malformedcompany_idraised Postgres22P02beforeBEGIN, aborting the wholeauth.userswrite (breaks the invite/signup path).This PR ships a fix-forward migration that adds a UUID-format regex check to the trigger's
WHENclause. A malformed value now failsWHEN, the function never runs, the unguarded::uuidcast is never evaluated, and theauth.userswrite commits normally (provisioning silently skipped for that row — same "skip, never abort" contract already used elsewhere in this function).Changes
supabase/migrations/20260722195616_guard_company_id_uuid_cast.sql(new):DROP TRIGGER/CREATE TRIGGERonly — the trigger'sWHENclause now also regex-validates the UUID format (^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$, case-insensitive via~*) before the existing non-empty check. The function body (public.provision_workspace_membership()) is untouched — noCREATE OR REPLACE FUNCTION.docs/cross-repo-log.md: dated entry documenting the fix, "Impact on edifice: None" for this repo's own trigger, plus a flag for a newly discovered companion issue (see below)..claude/STATUS.md: moved#62from open follow-ups to Done.WHENonly referencesNEW(neverOLD), so this cannot regress the42P17guard already fixed once in this file (commitf3920ab) — that guard stays in the function body, gated onTG_OP = 'UPDATE'.New finding during validation
The edifice-owned companion trigger
trg_provision_edifice_org(provision_edifice_org()) has the identical unguarded::uuidcast and fires whenevercompany_idis non-null andallowed_appscontainsedifice. It was hit mid-validation (test user hadallowed_apps: ["edifice"]) and is not fixed by this PR — it's edifice-owned and out of scope for #62. Flagged indocs/cross-repo-log.mdand.claude/STATUS.mdas a follow-up issue to file in the edifice repo.Validation
WHENclause.zgkvbjqlvebttbnkklpo) viaapply_migration(Supabase MCP) — success, no error. Confirmed post-apply viapg_get_triggerdef.company_id("not-a-uuid") →auth.usersUPDATE now commits (22P02no longer raised).workspace_membersrow (provisioning correctly skipped).ROLLBACK→ zero residue, Yani's row unchanged.make test→deno checkclean on both Edge Functions, 157 pytest passed / 17 deselected.Not in scope
#61(M1,UNIQUEonhalcrm_workspaces.company_id),#63(M5 checklist),#64(L3 CLAUDE.md doc) — each a separate open issue.RAISE WARNINGfor the malformed-input skip case —WHENclauses can't execute statements; the issue's proposed fix is exactly the regex guard shipped here.Fixes #62