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

18 KiB
Raw Blame History

title, description, status, owner, created, supersedes-sections-of
title description status owner created supersedes-sections-of
ARCH Consolidation — Architect Decisions Addendum (2026-04-24) Beslissingen op de 6 architect-vragen uit het WS-1 rapport binding architect 2026-04-24 /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):

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.

Git-log kanttekening commit 3. De 1Password signer gaf herhaalde "failed to fill whole buffer" errors op de lange HEREDOC body van de bedoelde commit-message; de uiteindelijke commit landde met alleen de titel (refactor(form-builder): pre-publish check reads form_field_bindings; drop binding JSON columns, SHA 61719bf). De volledige rationale — pre-publish check switch van JSON naar relationele query, kolom-drops op form_fields.binding en form_field_library.default_binding, factory/resource/form-request cleanup, fixture-rewrites — staat in de WS-5a completion notitie, niet in git show 61719bf. Amenden is niet geprobeerd: CLAUDE.md verbiedt signed-commit amenden.


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.