Retires the "integer AI PK for join performance" exception documented
in earlier migrations and SCHEMA.md §3.5.11 Rule 1. Every business and
pivot table now uses ULID primary keys, per
/dev-docs/ARCH-CONSOLIDATION-ADDENDUM-2026-04-24.md Q1.
Tables migrated (WS-1 A-01 through A-11):
- Pure pivots: organisation_user, event_user_roles, crowd_list_persons,
event_person_activations
- Model-backed: user_organisation_tags, person_section_preferences,
mfa_backup_codes, mfa_email_codes, form_submission_section_statuses,
form_values, form_value_options
Migration pattern: one new migration per table (plus one combined for
the form_values / form_value_options FK pair), timestamped today,
dropping + recreating with the new ULID PK. Pre-launch — no backfill
required. Original migrations remain in place; the new migrations
apply in timestamp order for a clean schema history.
Pivot model correction (addendum drift):
The addendum's "no model required for pure pivots" reading did not
account for Laravel's BelongsToMany::attach() — it cannot auto-generate
a pivot ULID without a Pivot subclass. Minimal Pivot classes under
app/Models/Pivots/ (OrganisationUser, EventUserRole, CrowdListPerson,
EventPersonActivation) carry HasUlids so attach() works. The six
belongsToMany relations (User.organisations / .events, Organisation.users,
Event.users, CrowdList.persons, Person.crowdLists) now ->using(...) the
appropriate Pivot class. DB::table()->insert() on event_person_activations
in DevSeeder populates the ULID inline via Str::ulid(). FormValueObserver
uses bulk FormValueOption::insert() which bypasses model events — ULIDs
are now generated inline there too.
Docs:
- SCHEMA.md §3.5.11 Rule 1 rewritten to mandate ULID on pivots too, with
legacy note citing the addendum.
- All eleven table entries updated from "int AI PK" to "ULID PK" with
addendum Q1 references.
- form_values and form_submission_section_statuses prose blocks updated
to drop the retired ARCH §4.4 / "high-volume pivot" rationale.
- form_value_options.form_value_id column type corrected from
"int FK" to "ULID FK".
Tests: tests/Feature/Schema/UlidPrimaryKeyTest.php covers HasUlids trait
presence, ULID shape + 26-char Crockford pattern, Route::bind resolution,
distinct + sortable pivot ULIDs, attach() auto-generation on pure pivots,
and the A-10/A-11 FK chain. 10 tests / 28 new assertions. Full suite:
977 passed (2662 assertions).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- SCHEMA.md §3.5.12 header rewritten for the 7-purpose vocabulary and
`PurposeRegistry`. The `custom_purpose_slug` column is dropped from
the `form_schemas` table and removed from the index list. The
`form_submissions.subject_type` note cites
`PurposeRegistry::allSubjectTypes()` instead of the deleted
`config/form_subjects.php`.
- ARCH-FORM-BUILDER.md TL;DR updated: goal bullet cites 7 purposes
(v1.0); §3.2 bullet notes the legacy 22-variant vocabulary is
retired. §17.3 replaced: the "Custom purposes per organisation"
section is gone; the new "Purpose registry" section documents the
seven-slug table, PurposeDefinition shape, PurposeRegistry API,
MorphMapAlignmentTest guard, the pre-publish binding check, and a
step-by-step "adding a new purpose" checklist.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
S2c Phase 8.
- API.md: new **Form Builder (Public)** section documenting all 6
public endpoints (GET schema + time-slots + sections; POST draft,
PUT save, POST submit) with request/response examples, error codes,
and the identity_match / schema_drift contracts. No PII-echo noted
explicitly.
- SCHEMA.md bumped to v2.1:
- changelog entry for S2c.
- form_submissions table gains schema_version_at_open +
identity_match_status columns; UNIQUE (form_schema_id,
idempotency_key) replaces the composite index; a new composite
index (form_schema_id, identity_match_status) landed for the
organiser "pending-match" dashboard.
- ARCH-FORM-BUILDER.md bumped to v1.3 with new §10.4 "Public
submission lifecycle — draft/save/submit split" documenting the
three-endpoint contract, idempotency, schema-drift detection,
access rules, the standardised error envelope, and the dependency
data sub-endpoints.
- BACKLOG.md adds:
- FORM-04 (grace_days configurable — current implementation still
uses the hard-coded 7-day window)
- DOC-01 (Scramble / OpenAPI generator for API.md to reduce the
docs-drift effort going forward).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reflects the post-S1+S2a+S2b database state. Nothing but SCHEMA.md changed.
- Header: Version → 2.0, added v2.0 changelog entry covering the 13 new
tables, the 3 dropped legacy tables, the preserved
person_section_preferences, organisations.default_locale, and the
events.registration_show_* drops.
- Table of Contents: updated §3.5.5b name to "Section Preferences",
added entries for §3.5.10 Email Infrastructure, §3.5.11 Rules,
§3.5.12 Form Builder (which were already in the file but missing
from the TOC).
- §3.5.1 organisations: added default_locale column (FormLocaleResolver
fallback chain, ARCH §16.2).
- §3.5.1 events: removed registration_show_section_preferences +
registration_show_availability columns with a pointer at
form_fields.is_portal_visible / conditional_logic.
- §3.5.4: removed the never-created volunteer_profiles table block;
the other three tables in that section (volunteer_festival_history,
post_festival_evaluations, festival_retrospectives) are unchanged.
- §3.5.5b: renamed to "Section Preferences"; design note pointing at
events.registration_show_section_preferences replaced with a pointer
at form_fields.is_portal_visible / conditional_logic.
- §3.5.9: renamed to "Check-In & Operational"; removed the never-created
public_forms stub and the colliding legacy form_submissions block
(both documented planned-but-never-created tables) with a short note
pointing at the Form Builder as the home for form concepts. Flagged
separately below because it's technically beyond the task's explicit
scope but unavoidable (SCHEMA.md would otherwise describe two
different tables under the same name).
- §3.5.12 Form Builder: summary replaced with full per-table
documentation for all 13 tables in the ARCH §4 order — user_profiles,
form_schemas (polymorphic owner, public_token rotation with
public_token_previous + public_token_rotated_at, edit_lock_*),
form_schema_sections, form_field_library, form_fields, form_submissions,
form_submission_section_statuses, form_submission_delegations,
form_values (observer-driven typed columns value_indexed/number/date/bool
and form_value_options multi-value rebuild per ARCH §7.2),
form_value_options, form_templates, form_schema_webhooks,
form_webhook_deliveries. Added short notes on activity log strategy
and the §31.10 FORM-02 tag-sync listener.
Migrations-vs-ARCH discrepancies (migrations win, per CLAUDE.md):
- form_values carries created_at / updated_at timestamps, though ARCH §4.4
does not list them. Documented as present.
- form_webhook_deliveries has no timestamps columns; last_attempt_at is
the effective timestamp. Documented as such.
- form_schema_webhooks stores url / secret as encrypted TEXT columns
(Eloquent-cast encryption); ARCH says "encrypted" without specifying.
Documented the column type.
- public_forms + legacy form_submissions documented in §3.5.9 never
existed in the DB (confirmed via Schema::hasTable). Removed those
doc stubs; the naming collision with the new Form Builder
form_submissions made leaving them in place a correctness hazard.
SCHEMA.md
- New §3.5.12 "Form Builder" with the legacy-tables-retained note
placed prominently directly under the section header (per S1 wrap-up
Path 3 decision: Phase 8 deferred to S2).
- Crosswalk: every legacy volunteer_profiles column → its new home
(user_profiles columns vs form_fields vs person_tags).
- Summary table for the 13 new tables with one-line purpose + ARCH §
pointer each.
- Activity log strategy and multi-tenancy discipline noted.
- §3.5.4 marked SUPERSEDED with a pointer to the new section.
/dev-docs/form-builder-migration-playbook.md (new)
- Operator runbook for forms:migrate-legacy-data on real legacy data.
- Pre-flight audit, dry-run, migrate, verify, spot-check, rollback
paths spelled out. Same legacy-tables-retained note prominently.
/dev-docs/form-builder-getting-started.md (new)
- Developer onboarding. Mental model, code samples for creating a
schema/field/submission/value, adding a new subject type, registering
a custom field type, suppressing activity log via
App\Support\ActivityLog::suppressed.
/dev-docs/COPY_CATALOGUE.md (new)
- Seeded verbatim from ARCH §30 (naming conventions, tooltip catalogue,
warning catalogue) with a header explaining purpose, growth strategy,
and the per-PR update workflow.
/docs/organizer/forms/concepts/wat-is-een-formulier.md (new VitePress)
- Dutch, informal je/jij. Follows /docs/.templates/concept-page.md.
- Three example use-cases: vrijwilligersregistratie, artist advance,
incidentrapportage. Light foundation; depth arrives in S2-S5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add configurable column widths (full/half) and optional descriptions
for radio/select/checkbox options on registration form fields.
- Migration adds display_width column to both tables
- FieldDisplayWidth enum with smart defaults per field type
- normalized_options accessor for backwards-compatible option format
- Portal form renderer uses display_width for VRow/VCol grid layout
- Radio/select/checkbox options render with descriptions
- Admin field editor supports display_width toggle and description input
- System templates updated with appropriate widths and descriptions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three verification methods (TOTP authenticator, email code, backup codes),
trusted device management with 30-day expiry, role-based enforcement for
super_admin and org_admin, admin reset capability, and full test coverage
(46 tests). Modifies login flow to support MFA challenge/response with
temporary session tokens stored in cache.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds the full transactional email system:
- Redis queue (QUEUE_CONNECTION=redis), SES config in .env.example
- 3 migrations: organisation_email_settings, organisation_email_templates, email_logs
- EmailTemplateType and EmailLogStatus enums with Dutch defaults
- EmailService as central entry point for all email sending
- SendTransactionalEmail queued job with retries and idempotency
- TransactionalMail mailable with responsive HTML + plain text templates
- Organisation-level branding (colors, logo, footer, reply-to)
- Per-type template overrides with {variable} substitution
- Email log with filtering by status, type, date range, recipient
- Preview and send-test endpoints for template management
- API endpoints: email-settings, email-templates (CRUD), email-logs (read-only)
- Integrated into existing flows: invitations, password reset, email
verification, registration approval/rejection
- 37 new tests across 4 test files, all existing tests updated
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Password reset: multi-app support with custom notification linking to correct
frontend (app/portal/admin). Email change: self-service with password
confirmation and admin-initiated, both sending verification to new address
with 24h expiry. Confirmation sent to old email on completion. Password
change: authenticated endpoint revoking other sessions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements the full identity matching engine: email matching (HIGH confidence),
fuzzy name matching with Levenshtein distance (MEDIUM confidence, upgradable to
HIGH with DOB tiebreaker), manual link/unlink, revert confirmed matches, and
automatic detection via PersonObserver. Includes 33 comprehensive tests, frontend
integration with confirm/dismiss/unlink UI, and match indicators in the persons list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update SCHEMA.md (v1.8), design-document.md (v1.9), and API.md with
EAV system for dynamic event-specific registration fields, section
preferences, tag picker sync architecture, and field templates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Cross-cutting migration affecting the entire stack:
- Database: 3 migrations splitting name columns with data migration
- Models: first_name/last_name on User, Person; contact_first_name/contact_last_name on Company; backward-compatible name accessors
- API: all resources return first_name, last_name, full_name; assignablePersons endpoint updated
- Requests: validation rules updated for all person/user/company forms
- Services: VolunteerRegistrationService, ShiftAssignmentService, InvitationService updated
- Frontend: TypeScript types, Zod schemas, all forms split into Voornaam/Achternaam fields
- Display: all person/user name references use full_name; initials use first_name[0]+last_name[0]
- Tests: all 371 tests passing
- Docs: SCHEMA.md and API.md updated
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add cancelled_by, cancellation_source (organiser|volunteer|system), and
cancelled_at columns to shift_assignments. Cancel flow now records who
cancelled and why. Assign flow reactivates existing cancelled/rejected
records instead of creating duplicates, preventing UNIQUE constraint
violations. Assignable-persons endpoint returns previous_assignment data
for contextual UI indicators. Frontend shows cancellation source labels,
previous assignment history in assign dialog, and "Opnieuw toewijzen"
buttons with volunteer-cancelled confirmation dialogs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add show_in_registration and registration_description columns to festival_sections.
Registration form now shows deduplicated sections by name (across sub-events),
filtered by show_in_registration=true, grouped by category with card-based UI.
Section preferences use section_name instead of section_id.
Add GET/PUT registration-settings endpoints for festival-level bulk management.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements enterprise-grade identity resolution (detect → suggest → confirm)
for Person ↔ User linking. Matches are detected automatically on person
creation and user account creation, then surfaced to organisers for explicit
confirmation or dismissal. No silent auto-linking.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>