docs: deleteForm cascade + deleteSubmission/relinkAll; HANDOFF pass-33

API_REFERENCE + ARCHITECTURE updated (deleteForm now permanently
cascades incl. signed waivers; immutability caveat; new deleteSubmission
+ relinkAllSubmissions). Corrected pass-32 header to DEPLOYED.
This commit is contained in:
Moheeb Zara
2026-05-17 00:12:43 -07:00
parent 1e6e5d523b
commit cec2d69097
3 changed files with 27 additions and 6 deletions
+1 -1
View File
@@ -892,7 +892,7 @@ validate input with Zod via `parseInput`, authorize via `requireMember` /
### Forms & waivers: `forms.ts` (migrations 026-029)
Management actions are gated by `forms.manage` (checked via the `user_has_permission` RPC; the `forms` RLS independently enforces it):
- `getPublicForm({ space, slug })` — service-client read for the public `/f/[space]/[slug]` page; returns only a published, non-members form. `createForm(input)` — friendly error on slug collision (slug is unique per space, migration 028). `updateForm(input)` — slug is immutable; for a published waiver, a legal-text or schema change bumps `version` (existing submissions stay valid against their own snapshot, non-blocking re-sign). `setFormStatus(input)` — draft/published/closed. `deleteForm(input)` — refused if the form has any submissions (close instead; submissions are immutable records). `listForms()`, `getFormResults(input)`, `exportFormResultsCsv(input)` (audited via `activity_log`).
- `getPublicForm({ space, slug })` — service-client read for the public `/f/[space]/[slug]` page; returns only a published, non-members form. `createForm(input)` — friendly error on slug collision (slug is unique per space, migration 028). `updateForm(input)` — slug is immutable; for a published waiver, a legal-text or schema change bumps `version` (existing submissions stay valid against their own snapshot, non-blocking re-sign). `setFormStatus(input)` — draft/published/closed. `deleteForm({formId, confirm:true})` — **permanently** deletes the form and FK-cascades every submission (including signed waivers); requires explicit `confirm:true` (a stale/accidental call cannot destroy records) and audits the destroyed count; the UI gates it behind a destructive confirm. `deleteSubmission({submissionId})` — `forms.manage`; permanently removes one submission via the service client (form_submissions has no client write policy), space-scoped, audited. `relinkAllSubmissions()` — `forms.manage`; re-runs email→member association across the whole space (members earliest-joined first for deterministic shared-email claim; reuses `linkSubmissionsByEmail`; NULL-only). `listForms()`, `getFormResults(input)`, `exportFormResultsCsv(input)` (audited via `activity_log`).
- `submitForm(input)` — public entry point (anonymous, signed-in non-member, or member by form `visibility`). Reads the form with the service client (anon has no `forms` grant), enforces visibility + waiver consent, validates `answers` against the stored field schema (`lib/forms-schema.ts`, unknown keys discarded), snapshots schema/legal-text/version, captures IP + user-agent, and writes the row with the service client (`form_submissions` has no write policy, so this is the only path; rows are immutable).
- `linkSubmissionsForMember(input)` — `forms.manage` admin manual re-link of prior submissions by email (kept for explicit re-linking).
- `listMemberSubmissions({ memberId })` — `forms.manage`; the forms a member has submitted (title, kind, version, date), for the per-member "Forms" panel on `/members`. Honors the `form_submissions`/`forms` RLS (no service-client bypass); metadata only — answers stay in the audited results surface.