feat(forms): onboarding form/waiver step with required enforcement (Phase 4)
- scripts/027 (idempotent CHECK replace) + schema.sql Section 14: allow space_onboarding_steps.step_type = 'form'; referenced form id lives in the step's existing config.form_id (no new column) - finishOnboarding: a required form step is satisfied when a submission for config.form_id by the member exists (any version — re-sign is non-blocking / auto-satisfy); fails open if the form is missing or unpublished so a misconfig cannot trap a member. Probe uses the service client (submissions are forms.manage-only under RLS) - onboarding page enriches form steps with the published form def + prior-submission flag; onboarding-flow renders FormRenderer + waiver legal text/consent and submits via submitForm then marks the step done - customize: forms fetched and threaded to the onboarding panel; new "+ Form step" button + form picker writing config.form_id - validations/types/casts include 'form'; forms.test.ts +2 - docs: DATABASE_SCHEMA/DB_SCHEMA_MAP/API_REFERENCE/ARCHITECTURE/HANDOFF 287/287 tests, build exit 0. No deploy.
This commit is contained in:
@@ -894,4 +894,6 @@ Management actions are gated by `forms.manage` (checked via the `user_has_permis
|
||||
- `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-link of prior anonymous submissions by email. The automatic verified-email retro-link is Phase 5.
|
||||
|
||||
Not implemented yet (planned): builder UI, public `/f/[slug]` page, onboarding form-step integration, automatic retro-link on email verification.
|
||||
Onboarding integration (Phase 4, migration 027): a `space_onboarding_steps` step can have `step_type = 'form'` with `config.form_id`. The onboarding flow renders the form/waiver and submits via `submitForm`; `finishOnboarding` treats a required form step as satisfied if a submission for that form by the member already exists (any version — re-sign is non-blocking), and fails open if the configured form is missing/unpublished so a misconfiguration cannot trap members.
|
||||
|
||||
Not implemented yet (planned): automatic retro-link on email verification + join/signup/addMember/import hooks (Phase 5). The `linkSubmissionsForMember` primitive exists.
|
||||
|
||||
@@ -422,10 +422,11 @@ forms + waivers).
|
||||
|
||||
### Forms & waivers (migration 026 — Phase 1: schema + RLS)
|
||||
|
||||
`forms` and `form_submissions` back a custom form/waiver builder. Phases 1-3
|
||||
`forms` and `form_submissions` back a custom form/waiver builder. Phases 1-4
|
||||
are done (schema + RLS; server actions in `lib/actions/forms.ts`; the builder
|
||||
UI under `/forms` + the public `/f/[slug]` page). Onboarding integration and
|
||||
automatic retro-linking are Phases 4-5. Security shape:
|
||||
UI under `/forms` + the public `/f/[slug]` page; onboarding `form` step type
|
||||
with required-waiver enforcement + auto-satisfy, migration 027). Automatic
|
||||
retro-linking on email verification is Phase 5. Security shape:
|
||||
all submissions (anonymous, public-authenticated, or members) are written by a
|
||||
single validated server action using the service client AFTER server-side
|
||||
schema validation and snapshotting (same pattern as `finishOnboarding`); the
|
||||
|
||||
@@ -686,7 +686,7 @@ CREATE INDEX idx_activity_space ON activity_log(space_id, created_at);
|
||||
|
||||
---
|
||||
|
||||
## Migrations 014-026 (additions since the 13-table baseline)
|
||||
## Migrations 014-027 (additions since the 13-table baseline)
|
||||
|
||||
`scripts/schema.sql` is the canonical idempotent schema; each numbered
|
||||
migration is mirrored as a section in it. Tables/columns added:
|
||||
@@ -705,6 +705,7 @@ migration is mirrored as a section in it. Tables/columns added:
|
||||
| 023 | `space_role_permissions`, `ops_acl`; `user_effective_roles()`, `user_has_permission()` (SECURITY DEFINER); `secrets`/`knowledge_base` SELECT rewritten as `existing-rule OR ops_acl-match` (additive) |
|
||||
| 024 | `prevent_member_self_role_change()` extended to also block self-change of `tier_id` and `onboarding_completed_at` |
|
||||
| 025 | Re-asserts the hardened `incidents_insert` policy verbatim (production convergence; access-neutral, no schema change) |
|
||||
| 027 | Adds `'form'` to the `space_onboarding_steps.step_type` CHECK so an onboarding step can embed a custom form/waiver (referenced form id stored in the step's existing `config` jsonb; no new column) |
|
||||
| 026 | `forms`, `form_submissions` (custom forms + waivers). Globally-unique form slug; jsonb field schema; per-submission snapshot of schema/legal-text/version for immutable waiver records. RLS additive + default-deny: forms SELECT = `forms.manage` holders (all) or members (published only), write = `user_has_permission(..., 'forms.manage')`; form_submissions SELECT = `forms.manage` only, NO write policy (every submission is written by one validated service-client server action, and submissions are immutable). New `forms.manage` permission seeded to board + backfilled |
|
||||
|
||||
New enum: `comment_entity_type` = `forum_thread | proposal | incident | policy`.
|
||||
|
||||
+14
-4
@@ -4,7 +4,7 @@ Append-only. Newest entries on top. Keep each entry to one screen.
|
||||
|
||||
---
|
||||
|
||||
## 2026-05-16 (pass 18) — Forms + waivers Phases 1-3 (schema/RLS + server actions + builder UI), LOCAL, awaiting deploy approval
|
||||
## 2026-05-16 (pass 18) — Forms + waivers Phases 1-4 (schema/RLS + actions + builder UI + onboarding step), LOCAL, awaiting deploy approval
|
||||
|
||||
Branch: `main`. Migration: `026` (not yet deployed). `pnpm build` + `pnpm exec vitest run` gate pending in this entry's session.
|
||||
|
||||
@@ -47,11 +47,21 @@ Audited the migration before continuing. Three fixes applied: dropped redundant
|
||||
- Gate: `pnpm exec vitest run` 285/285; `pnpm build` exit 0; `/forms`, `/forms/[id]`, `/forms/new`, `/f/[slug]` all compiled.
|
||||
- NOT browser-verified (no live browser this session): smoke-test the builder (add/reorder/remove fields, live preview, create + edit + publish + close + delete), the results CSV download, and a public submission for each visibility (anon, public_auth signed-out gate, waiver consent).
|
||||
|
||||
### Forms + waivers Phase 4 (this session) — onboarding form step, LOCAL
|
||||
|
||||
- `scripts/027_onboarding_form_step.sql` (idempotent constraint replace) + `schema.sql` Section 14 inline CHECK now allows `step_type = 'form'`. Referenced form id lives in the step's existing `config.form_id` (no new column). validations `onboardingStepTypeSchema` + onboarding action union + customize types/casts include `'form'`.
|
||||
- `finishOnboarding`: a required `form` step is satisfied if a submission for `config.form_id` by this member exists (any version — non-blocking re-sign / auto-satisfy). Fails open if the form is missing or unpublished so a misconfig cannot trap a member out of their space. Submission probe uses the service client (submissions are forms.manage-only under RLS).
|
||||
- `app/onboarding/page.tsx` enriches `form` steps with the published form def + a prior-submission flag; `onboarding-flow.tsx` renders `FormRenderer` + waiver legal text/consent and submits via `submitForm` then `markOnboardingStepDone`; already-submitted or unavailable forms show a non-blocking notice.
|
||||
- Admin: `customize/page.tsx` fetches the space's forms; `onboarding-panel.tsx` gets a "+ Form step" button and a form `<select>` (writes `config.form_id`); empty-state hint when no published forms.
|
||||
- Tests: `__tests__/forms.test.ts` +2 (onboarding form-step contract). Gate: `pnpm exec vitest run` 287/287; `pnpm build` exit 0; onboarding + customize compiled. NOT unit-tested: `finishOnboarding` enforcement/auto-satisfy is DB-bound (no mocked test); covered by reasoning + manual.
|
||||
- NOT browser-verified: admin adds a form step + picks a form; member onboarding renders/sumbits the form/waiver; required-form blocks finish until submitted; auto-satisfy when a prior submission exists; fail-open on unpublished form.
|
||||
|
||||
### Open / next
|
||||
|
||||
- **Awaiting user approval to deploy** (push to `main` = prod). Phases 1-3 are all LOCAL (commits `3a2d3ca`, `66dd9b7`, Phase 2, Phase 3); do NOT push without explicit go. Migration `026` ships with the first push.
|
||||
- Phases 4-5 not started: onboarding form-step + required-waiver enforcement + auto-satisfy when a matching signed waiver exists; automatic retro-link hooks (join/signup/addMember/import + email verification) — `linkSubmissionsForMember` primitive already exists. Phase 3 is code-complete but NOT browser-verified (see Phase 3 smoke-test list above).
|
||||
- After an approved push: watch the Actions run, then smoke-test that `026` applied (`_migrations_applied` row) and a space's board has `forms.manage`.
|
||||
- **Awaiting user approval to deploy** (push to `main` = prod). Phases 1-4 are all LOCAL; do NOT push without explicit go. Migrations `026` + `027` ship with the first push.
|
||||
- Phase 5 not started: automatic retro-link on email verification + join/signup/addMember/import hooks — `linkSubmissionsForMember` primitive already exists (currently exposed as the forms.manage manual-link).
|
||||
- Phases 3-4 are code-complete but NOT browser-verified (see the smoke-test lists in each subsection). No live browser this session.
|
||||
- After an approved push: watch the Actions run; smoke-test that `026`+`027` applied (`_migrations_applied`), a space's board has `forms.manage`, and the builder/public page/onboarding form step work end to end.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user