Files
crewli/dev-docs/ARCH-CONSOLIDATION-ADDENDUM-2026-04-24.md

272 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
title: ARCH Consolidation — Architect Decisions Addendum (2026-04-24)
description: Beslissingen op de 6 architect-vragen uit het WS-1 rapport
status: binding
owner: architect
created: 2026-04-24
supersedes-sections-of: /dev-docs/ARCH-CONSOLIDATION-2026-04.md §5 (scope inschattingen)
---
# ARCH Consolidation — Architect Decisions Addendum (2026-04-24)
## Context
Op 2026-04-24 leverde WS-1 (opsporings-pas) een rapport met 45 bevindingen en 6 architect-vragen. Dit addendum legt de beantwoording van die vragen vast én corrigeert waar nodig de werkstroom-scope. Dit document is **bindend** voor WS-2 t/m WS-8.
Bronnen:
- `/dev-docs/ARCH-CONSOLIDATION-2026-04.md` — sprint charter (§1 principes, §3 vastgelegde besluiten, §5 werkstromen)
- `/dev-docs/ARCH-CONSOLIDATION-WS1-REPORT.md` — opsporings-rapport (45 bevindingen)
Het charter blijft ongewijzigd. §5 inschattingen worden in dit addendum herzien. §3 vastgestelde besluiten worden bevestigd (niet gewijzigd) — de architect-precisering op besluit 5 die tijdens review was overwogen is verworpen: besluit 5 blijft exact zoals geformuleerd.
## Samenvatting beslissingen
| Q | Onderwerp | Besluit |
|---|-----------|---------|
| Q1 | ULID voor 11 non-ULID tabellen | **Alle 11 migreren naar ULID.** Charter §3 besluit 5 blijft exact zoals geformuleerd; geen exceptions. |
| Q2 | OrganisationScope strategie | **FK-chain scope extension.** Denormalisatie alleen op tabellen die rapportage-hot zijn. Charter §3 besluit 3 bevestigd als enige uitzondering op basis van meetbaar rapportage-gebruik. |
| Q3 | WS-5 scope | **Vier committed splits op `form_fields` + drie library-mirror splits.** `role_restrictions` blijft JSON. |
| Q4 | Sanctum `personal_access_tokens` morph | **Ongewijzigd laten.** Framework-polymorfie valt buiten de domain morph-map. Documenteren in WS-8. |
| Q5 | SCHEMA.md rewrite-shape | **Aparte `/dev-docs/ARCH-PLANNED-MODULES.md`** voor tabellen zonder migratie. SCHEMA.md wordt strict "waarheid van wat bestaat in de database". |
| Q6 | `subject_type` allow-list | **Consolideren onder WS-2 purpose registry.** Compile-time test dwingt morph-map-alignment met purpose-registry af. |
---
## Q1 — ULID voor 11 non-ULID tabellen
**Besluit:** Alle 11 tabellen uit Categorie A van het WS-1 rapport worden naar ULID gemigreerd. Charter §3 besluit 5 ("ULID consistent overal, ook pivots, ook logging-tabellen") blijft ongewijzigd. Geen exceptions.
**Tabellen:**
| ID | Tabel | Categorie |
|----|-------|-----------|
| A-01 | `organisation_user` | Pure pivot, geen model |
| A-02 | `event_user_roles` | Pure pivot, geen model |
| A-03 | `crowd_list_persons` | Pure pivot, geen model |
| A-04 | `event_person_activations` | Pure pivot, geen model |
| A-05 | `user_organisation_tags` | Pivot met model |
| A-06 | `person_section_preferences` | Pivot met model |
| A-07 | `mfa_backup_codes` | Ephemeral data |
| A-08 | `mfa_email_codes` | Ephemeral data |
| A-09 | `form_submission_section_statuses` | Pivot met model |
| A-10 | `form_values` | EAV hot path |
| A-11 | `form_value_options` | EAV hot path |
**Motivatie:**
- **Pre-launch window:** geen data-migratie, geen downtime, geen backfill-pijn. Dit is het enige moment waarop consistentie gratis is. Een half jaar later met productie-data kost hetzelfde werk weekenden met downtime.
- **Performance-argumenten zijn theoretisch op Crewli's schaal:** EAV join-performance tussen int en ULID keys is verwaarloosbaar bij de volumes waar Crewli zich op richt (enkele miljoenen `form_values` per festival-jaar, ruim binnen MySQL + goede indexen op ULID).
- **Consistentie-winst is concreet en blijvend:** elke uitzondering introduceert cognitieve belasting ("waarom is deze tabel anders?") die besluit 5 juist wil voorkomen.
- **Valstrik 2 (gold-plating) niet van toepassing:** dit is geen elegantie-verbetering, dit is uitvoering van een reeds genomen besluit.
**Scope-impact:** WS-4 migreert 11 PK's in plaats van 0 of 2.
**Migratie-notes voor WS-4:**
- Voor tabellen met een model (`UserOrganisationTag`, `PersonSectionPreference`, `MfaBackupCode`, `MfaEmailCode`, `FormSubmissionSectionStatus`, `FormValue`, `FormValueOption`): `HasUlids` trait toevoegen.
- Voor pure pivots zonder model (`organisation_user`, `event_user_roles`, `crowd_list_persons`, `event_person_activations`): `$table->ulid('id')->primary()` in de migratie, geen model nodig.
- Inline migratie-commentaar "int AI for join performance" wordt vervangen door de ULID-migratie zelf; geen extra rationale-documentatie in de migratie.
---
## Q2 — OrganisationScope strategie
**Besluit:** `OrganisationScope` wordt uitgebreid met een declaratieve FK-chain strategy. Denormalisatie van `organisation_id` blijft beperkt tot tabellen die **meetbaar rapportage-hot** zijn.
**Concreet:**
- `form_submissions` behoudt denormalized `organisation_id` (per charter §3 besluit 3 — bevestigd als bewuste exception voor rapportage-queries: dashboards, CSV-exports, aggregerende counts over duizenden rijen).
- Andere form-builder child tables krijgen **geen eigen `organisation_id` kolom**; zij gebruiken FK-chain via hun parent:
- `form_schema_sections` → via `form_schemas.organisation_id`
- `form_fields` → via `form_schemas.organisation_id`
- `form_values` → via `form_submissions.organisation_id` (denormalized parent)
- `form_value_options` → via `form_submissions.organisation_id`
- `form_submission_section_statuses` → via `form_submissions.organisation_id`
- `form_submission_delegations` → via `form_submissions.organisation_id`
- `form_schema_webhooks` → via `form_schemas.organisation_id`
- `form_webhook_deliveries` → via `form_submissions.organisation_id`
**Shape van de scope-extensie (concept — implementatie-details in WS-4):**
```php
class FormField extends Model
{
use HasUlids;
protected static function tenantScopeStrategy(): array
{
return ['via' => FormSchema::class, 'fk' => 'form_schema_id'];
}
}
```
`OrganisationScope` leest de strategy en bouwt de JOIN automatisch op.
**Motivatie:**
- **Normalisatie-zuiverheid:** `organisation_id` heeft één bron van waarheid (`form_schemas`, `events`, enz.). Denormalisatie introduceert synchronisatie-risico en dubbele boekhouding.
- **Single responsibility:** `OrganisationScope` blijft *de* multi-tenant guard. Geen gedeelde verantwoordelijkheid tussen observer + scope over 8 tabellen.
- **Toekomstbestendig:** hetzelfde patroon werkt straks automatisch voor accreditation, briefings en andere modules met sub-tabellen. Eén scope-klasse uitbreiding, geen migraties per module.
- **Denormalisatie-uitzondering gerechtvaardigd:** `form_submissions` is rapportage-hot. De denormalisatie is een meetbare performance-optimalisatie voor aggregerende queries, geen "veilig-voor-het-geval-dat" patroon.
**Rapportage-hotness criterium:** een tabel krijgt alleen denormalized `organisation_id` als er regelmatig aggregerende queries rechtstreeks op draaien (counts, group-by, exports over duizenden rijen). Alle andere tabellen gebruiken FK-chain via hun parent.
**Scope-impact:** WS-4 krijgt één scope-klasse uitbreiding + scope-registratie op 9 form-builder child models + 5 event-data models (uit WS-1 rapport D-04: `ShiftAssignment`, `ShiftWaitlist`, `VolunteerAvailability`, `PersonSectionPreference`, `PersonIdentityMatch`). Geen 8 extra kolom-migraties.
---
## Q3 — WS-5 scope
**Besluit:** WS-5 splitst de vier committed kolommen op `form_fields` (per charter §3 besluit 6) én de drie library-mirrors op `form_field_library`. `role_restrictions` blijft JSON.
**Te splitsen:**
| Bron kolom | Doel tabel | Sub-WS |
|------------|-----------|--------|
| `form_fields.binding` | `form_field_bindings` | WS-5a |
| `form_fields.validation_rules` | `form_field_validation_rules` | WS-5b |
| `form_fields.conditional_logic` | `form_field_conditional_logic` | WS-5c |
| `form_fields.options` | `form_field_options` | WS-5d |
| `form_field_library.default_binding` | `form_field_bindings` (met owner discriminator) | WS-5a |
| `form_field_library.validation_rules` | `form_field_validation_rules` (met owner discriminator) | WS-5b |
| `form_field_library.options` | `form_field_options` (met owner discriminator) | WS-5d |
**Blijft JSON:**
- `form_fields.role_restrictions` — kleine set Spatie rol-strings. Geen FK-partner mogelijk (rollen zijn strings in Spatie-permission, geen aparte tabel-PK's). Niet queryable in practice. Relationele splitsing levert geen architectuur-winst.
- `form_fields.translations`, `form_field_library.translations` — flat key-value bags per locale, nooit queried. Splitsen is speculatief.
**Motivatie:**
- **Consistent relationeel form-builder domein:** library en fields delen dezelfde onderliggende tabellen voor options, bindings en validation rules. Twee stijlen in hetzelfde domein is inconsistent.
- **Library-entries krijgen een discriminator** (exacte shape in WS-5a — ofwel `owner_type` enum (`field|library`), ofwel een paar nullable FK's (`form_field_id` OR `form_field_library_id`)). Discriminator-keuze wordt in WS-5a besloten op basis van query-patterns.
- **`FormFieldService::insertFromLibrary` kopieert rijen** tussen library-entries en field-entries in plaats van JSON te hydrateren. Natuurlijk werk binnen WS-5; geen aparte PR.
**Scope-impact:** WS-5 wordt ~7-8 dagen in plaats van 4-6. Drie extra sub-werkstromen voor de library-mirrors passen binnen de bestaande WS-5a/b/d PR-structuur (library valt onder dezelfde PR als de corresponderende field-split).
### Uitvoering — WS-5a (2026-04-24)
**Discriminator-keuze:** polymorphe morph (`owner_type` enum met waarden `form_field` en `form_field_library`, `owner_id` ULID). Het alternatief (twee nullable FK-kolommen `form_field_id` / `form_field_library_id`) is verworpen:
- **MySQL 8 heeft geen partial-unique-support** waarmee je per FK-kolom exclusief uniek kunt zijn; dat maakt de paired-FK-vorm in deze engine fundamenteel onhandig.
- **Consistentie over WS-5b/c/d**: `form_field_validation_rules`, `form_field_conditional_logic` en `form_field_options` gebruiken dezelfde owner-discriminator shape per Q3. Eén idiomatisch patroon over de hele familie wint van per-tabel workarounds.
- **Morph-map aliassen** `form_field` en `form_field_library` stonden al geregistreerd in `AppServiceProvider::registerMorphMap()` voor activity-log doeleinden; WS-5a heeft ze zonder extra werk hergebruikt.
**Scope-enforcement:** `OrganisationScope` (Q2 FK-chain resolver) kan geen morph-parent walken. WS-5a levert `FormFieldBindingScope` als sibling-scope die een UNION bouwt over beide owner-ketens (`form_field → form_schema → organisation_id` `form_field_library → organisation_id`). Zie ARCH-FORM-BUILDER §6.7.
**Service-grens:** `FormFieldBindingService` is de enige schrijver. `FormFieldService::insertFromLibrary` kopieert rijen via `copyBindings`, niet JSON (Q3 row-copy mandaat). Snapshot-writer en API-resources lezen via `toJsonShape` zodat het externe JSON-contract ongewijzigd blijft.
---
## Q4 — Sanctum `personal_access_tokens`
**Besluit:** De Sanctum-default voor `tokenable_type / tokenable_id` (FQCN in DB, geen morph-map entry) blijft ongewijzigd.
**Motivatie:**
- Framework-polymorfie valt buiten de domain morph-map conventie. Een alias toevoegen introduceert onderhoudsschuld bij elke Sanctum-upgrade.
- Domain polymorfie (`form_schemas.owner_type`, `form_submissions.subject_type`) blijft strict in morph-map geregistreerd; dit besluit raakt daar niet aan.
**Documentatie-actie (WS-8):** in de ARCH-documentatie wordt een expliciete regel opgenomen:
> De `enforceMorphMap`-conventie geldt voor domain polymorfe relaties. Framework-relaties (Sanctum `tokenable_type`, Spatie `activitylog.subject_type`, Spatie `activitylog.causer_type`) volgen hun framework-defaults en zijn expliciet uitgezonderd van de morph-map conventie.
---
## Q5 — SCHEMA.md rewrite-shape
**Besluit:** WS-8 extraheert alle tabellen-zonder-migratie uit SCHEMA.md naar een apart document `/dev-docs/ARCH-PLANNED-MODULES.md`. SCHEMA.md wordt strict "waarheid van wat bestaat in de database".
**Te verplaatsen tabellen** (uit WS-1 rapport bevinding D-01):
- §3.5.4 Volunteer Profile & History: `volunteer_festival_history`, `post_festival_evaluations`, `festival_retrospectives`
- §3.5.6 Accreditation Engine: `accreditation_categories`, `accreditation_items`, `event_accreditation_items`, `accreditation_assignments`, `access_zones`, `access_zone_days`, `person_access_zones`
- §3.5.7 Artists & Advancing: `performances`, `stages`, `stage_days`, `advance_submissions`, `artist_contacts`, `artist_riders`, `itinerary_items`
- §3.5.8 Communication & Briefings: `briefing_templates`, `briefings`, `briefing_sends`, `communication_campaigns`, `messages`, `message_replies`, `broadcast_messages`, `broadcast_message_targets`
- §3.5.9 Check-In & Operational: `check_ins`, `show_day_absence_alerts`, `scanners`, `inventory_items`, `event_info_blocks`, `event_info_block_crowd_types`, `production_requests`, `material_requests`
**Onderhoud-pattern:**
- Bij elke PR die een planned-module tabel aanmaakt verhuist de betreffende sectie van `ARCH-PLANNED-MODULES.md` naar `SCHEMA.md`. Dit wordt onderdeel van de PR-template checklist.
- `ARCH-PLANNED-MODULES.md` behoudt de index/soft-delete/FK-intent zoals vastgelegd in de originele SCHEMA.md rijen, zodat de planning-informatie niet verloren gaat.
- SCHEMA.md Rule 4 (required indexes) verwijst alleen nog naar bestaande tabellen na de rewrite.
**Motivatie:**
- **Enterprise documentatie-discipline:** onboarding developers zien direct wat er in de DB staat, zonder ruis van planning.
- **SCHEMA.md Rule 4 consistent:** required indexes kunnen niet meer verwijzen naar fantoom-tabellen.
- **Planned-modules eigen evolutie:** kan sneller bijgewerkt worden zonder SCHEMA-ruis.
**Scope-impact:** WS-8 wordt ~4-5 dagen in plaats van 2-3.
---
## Q6 — `subject_type` allow-list
**Besluit:** De allow-list uit `config/form_subjects.php` wordt opgeheven en geconsolideerd onder de purpose-registry in WS-2. `PurposeDefinition` heeft per charter §3 besluit 4 al een `subject_type` veld; dat wordt de enige bron van waarheid.
**Concreet (uitvoering in WS-2):**
- `config/form_subjects.php` verdwijnt na WS-2.
- `StoreFormSubmissionRequest` leidt toegestane `subject_type` waarden af uit `PurposeRegistry::allSubjectTypes()`.
- `AppServiceProvider::boot` bouwt de morph-map deels vanuit de purpose-registry (per purpose een subject-type entry) + framework-entries (activity-log subjects/causers, Sanctum uitgezonderd).
- **Compile-time guard:** een unit-test faalt als een `subject_type` in `PurposeRegistry` niet in de morph-map staat, of vice versa. Dit vervangt de huidige "developer discipline" afspraak.
**Motivatie:**
- **Eén bron van waarheid** voor subject-semantiek. Geen twee-bestanden-sync met impliciete koppeling.
- **Verstevigt charter §3 besluit 4:** `PurposeDefinition` wordt écht de volledige purpose-specificatie.
**Scope-impact:** WS-2 krijgt ~halve dag extra werk. De migratie zelf is klein; het compile-time consistency testje is het echte werk.
---
## Herziene werkstroom-scope en inschatting
Charter §5 inschattingen zijn op basis van dit addendum herzien:
| WS | Onderwerp | Charter inschatting | Herzien |
|----|-----------|---------------------|---------|
| WS-1 | Opsporings-pas | 1 dag | 1 dag (afgerond 2026-04-24) |
| WS-2 | Purpose registry | 2-3 dagen | 2-3 dagen + Q6 consolidatie (~halve dag) |
| WS-3 | Één SPA consolidatie | 3-5 dagen | 3-5 dagen (ongewijzigd) |
| WS-4 | ULID + denormalized submission columns + scope | 2-3 dagen | **5-6 dagen** (Q1 elf ULID migraties + Q2 scope extension + scope-registratie op 14 models + D-05 Person SoftDeletes verify) |
| WS-5 | JSON-kolom-opsplitsing | 4-6 dagen | **7-8 dagen** (Q3 library-mirrors toegevoegd aan WS-5a/b/d) |
| WS-6 | FormBindingApplicator | 4-5 dagen | 4-5 dagen (ongewijzigd) |
| WS-7 | Observability foundation | 2-3 dagen | 2-3 dagen + D-06 activity_log indexes |
| WS-8 | Documentatie-consolidatie | 2-3 dagen | **4-5 dagen** (Q4 framework-exception + Q5 planned-modules extractie + PK-decisions doc) |
**Totale herziene inschatting:** 28-38 dagen werk (charter had 22-32 dagen). Toename van ~6 dagen, volledig verklaard door de strict-enterprise invulling van Q1, Q2 en Q3.
**Werkstroom-volgorde ongewijzigd:** WS-1 → WS-2 → WS-3 (parallel mogelijk met WS-4/5) → WS-4 → WS-5 → WS-6 → WS-7 → WS-8.
---
## Openstaande bevindingen zonder architect-beslissing
WS-1 rapport Categorie D bevindingen die geen architect-beslissing vereisten en in de relevante werkstromen worden meegenomen:
| ID | Bevinding | Werkstroom |
|----|-----------|-----------|
| D-03 | Form-builder child models registreren OrganisationScope niet | WS-4 (als onderdeel van Q2 uitvoering) |
| D-04 (event-data subset) | ShiftAssignment / ShiftWaitlist / VolunteerAvailability / PersonSectionPreference / PersonIdentityMatch zonder scope | WS-4 |
| D-04 (user/admin subset) | MFA / TrustedDevice / UserProfile / EmailLog / OrganisationEmailSettings etc. | backlog, per-model ticket |
| D-05 | Person SoftDeletes verificatie | WS-4 |
| D-06 | activity_log `(subject_type, subject_id)` en `(causer_type, causer_id)` indexes | WS-7 |
| D-07 | Morph map forward-looking entries documentatie-link | WS-8 |
---
## Verwijzingen
- `/dev-docs/ARCH-CONSOLIDATION-2026-04.md` — sprint charter (§1 principes, §3 vastgestelde besluiten blijven ongewijzigd; §5 inschattingen herzien in dit addendum).
- `/dev-docs/ARCH-CONSOLIDATION-WS1-REPORT.md` — bron van alle 45 bevindingen waarnaar dit addendum verwijst.
---
## Sign-off
- **Architect review:** akkoord per Claude Chat sessie 2026-04-24, iteratief verscherpt over drie rondes (initial → strict-enterprise op Q1/Q3 → FK-chain correctie op Q2).
- **Product owner:** akkoord per Bert Hausmans 2026-04-24.
- **WS-5a afronding:** 2026-04-24 — relationele `form_field_bindings` tabel, polymorphic owner, snapshot-parity, JSON-kolommen gedropt.
Volgende stap: prompt opstellen voor WS-2 (Purpose registry) met Q6-consolidatie als integraal onderdeel van de werkstroom.