From ad941cc944043723905c7f093ff8594219c9249d Mon Sep 17 00:00:00 2001 From: "bert.hausmans" Date: Fri, 24 Apr 2026 13:49:05 +0200 Subject: [PATCH] docs: add architect decisions addendum after WS-1 --- .../ARCH-CONSOLIDATION-ADDENDUM-2026-04-24.md | 258 ++++++++++++++++++ 1 file changed, 258 insertions(+) create mode 100644 dev-docs/ARCH-CONSOLIDATION-ADDENDUM-2026-04-24.md diff --git a/dev-docs/ARCH-CONSOLIDATION-ADDENDUM-2026-04-24.md b/dev-docs/ARCH-CONSOLIDATION-ADDENDUM-2026-04-24.md new file mode 100644 index 00000000..5a9d7adb --- /dev/null +++ b/dev-docs/ARCH-CONSOLIDATION-ADDENDUM-2026-04-24.md @@ -0,0 +1,258 @@ +--- +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). + +--- + +## 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. + +Volgende stap: prompt opstellen voor WS-2 (Purpose registry) met Q6-consolidatie als integraal onderdeel van de werkstroom.