272 lines
18 KiB
Markdown
272 lines
18 KiB
Markdown
---
|
||
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.
|