docs(architecture): land consolidation sprint briefing document
Adds ARCH-CONSOLIDATION-2026-04.md as the authoritative reference for the upcoming 8-workstream architecture consolidation sprint: purpose registry cleanup, ULID consistency, JSON column split, binding infrastructure, FormBindingApplicator, single-SPA consolidation to crewli.app, observability foundation, docs consolidation. Sprint scope, leading principles, workstream ordering, and chat-transition protocol are captured in the document. Follow-up chats will start from this document as primary context. Also updates BACKLOG.md with an active-sprint marker pointing to the briefing.
This commit is contained in:
590
dev-docs/ARCH-CONSOLIDATION-2026-04.md
Normal file
590
dev-docs/ARCH-CONSOLIDATION-2026-04.md
Normal file
@@ -0,0 +1,590 @@
|
||||
# Crewli — Architectuur Consolidatie Sprint (april 2026)
|
||||
|
||||
> **Status:** briefing document, vertrekpunt van de consolidatie-sprint.
|
||||
> **Doelgroep:** Bert (product owner + solo dev), Claude (architect/PM rol in chat), Claude Code (executie).
|
||||
> **Positie t.o.v. andere docs:** dit document is tijdelijk. Na afronding van de sprint wordt
|
||||
> de inhoud gedistribueerd over `/CLAUDE.md`, `/dev-docs/SCHEMA.md`, `/dev-docs/ARCH-*.md` en
|
||||
> `/dev-docs/dev-guide.md`. Tijdens de sprint is het de enkele bron van waarheid voor wát
|
||||
> we aan het doen zijn en waaróm.
|
||||
|
||||
---
|
||||
|
||||
## 0. Context in één alinea
|
||||
|
||||
Crewli is een multi-tenant SaaS-platform voor festival- en event-personeelsbeheer
|
||||
(vrijwilligers, crew, artiesten, leveranciers, pers, gasten). Pre-launch. Geen
|
||||
gebruikers, geen live data, geen tijdsdruk. Ambitie: een enterprise-grade product
|
||||
dat binnen Stichting Echt Feesten gebruikt wordt en daarna bredere markt op
|
||||
kan. Het fundament is vandaag op ~80% — functioneel grotendeels aanwezig,
|
||||
architecturaal op sommige plekken een compromis dat onder productie-druk had
|
||||
moeten standhouden. Dit document legt vast hoe we die laatste 20% invullen
|
||||
voordat we aan de 4 kern-workflows (EVENT_REGISTRATION, ARTIST_ADVANCE,
|
||||
SUPPLIER_INTAKE en één generiek type) beginnen.
|
||||
|
||||
---
|
||||
|
||||
## 1. Leidende principes voor de sprint
|
||||
|
||||
Deze principes zijn toetssteen voor elke beslissing tijdens de consolidatie.
|
||||
Bij twijfel tijdens executie: teruglezen, toetsen, beslissen.
|
||||
|
||||
**P1 — Enterprise betekent discipline in scope, niet afwezigheid van scope.**
|
||||
We bouwen een fundament dat 23 denkbare purposes aan kan, maar we implementeren
|
||||
er nu 4. De rest is vocabulaire, geen functionaliteit. Elke feature die niet
|
||||
onmisbaar is voor het fundament of voor de 4 kern-workflows wordt op de backlog
|
||||
gezet met duidelijke trigger-conditie ("als X, dan bouwen we Y").
|
||||
|
||||
**P2 — Queryable data krijgt eigen kolommen of tabellen. JSON is uitsluitend
|
||||
voor echt opaque config.** Herinterpretatie van het bestaande principe: strenger
|
||||
toepassen. Validatieregels, conditional logic, field options, bindings — allemaal
|
||||
eigen tabellen. `settings` JSON krimpt fors.
|
||||
|
||||
**P3 — ULID overal. Zonder uitzonderingen.** Ook pivot-tabellen. Ook logging-tabellen.
|
||||
Consistent = onderhoudbaar = veilig.
|
||||
|
||||
**P4 — Polymorfe relaties zijn een bewust architectuur-patroon, geen kludge.**
|
||||
`form_schemas.owner_type/owner_id` en `form_submissions.subject_type/subject_id`
|
||||
blijven polymorf. Nieuwe polymorfe relaties voegen we toe waar het domein dit
|
||||
rechtvaardigt (niet speculatief).
|
||||
|
||||
**P5 — Eén SPA, twee hostnames.** Geen twee SPAs. Geen drie. Herconsolidatie
|
||||
van `apps/portal/` in `apps/app/` met domein-gebaseerde routing. Zie §4.
|
||||
|
||||
**P6 — End-to-end-werkend hoofdpad voor fancy features.** FormBindingApplicator
|
||||
+ Person-creation + entity-updates zijn eerste-klas fundament, niet een addendum.
|
||||
Geen enkele fancy feature (conditional logic, webhooks, delegation) mag afgerond
|
||||
heten zolang de basis-pipeline niet volledig gebouwd en getest is.
|
||||
|
||||
**P7 — Observability is fundament, geen nice-to-have.** Een enterprise-product
|
||||
zonder gestructureerde logging, error tracking en performance monitoring is geen
|
||||
enterprise-product. Deze laag wordt in de sprint neergezet.
|
||||
|
||||
**P8 — Documentatie loopt synchroon met code.** Elke sprint-PR wijzigt of
|
||||
creëert dev-docs. Geen "docs volgen later". Dit is al praktijk bij Crewli en
|
||||
blijft zo.
|
||||
|
||||
---
|
||||
|
||||
## 2. De drie valstrikken, en hoe we ze tegenhouden
|
||||
|
||||
De grote risico's van een open-ended consolidatie-sprint zijn bekend. Expliciet
|
||||
benoemen zodat we ze kunnen herkennen als ze zich voordoen.
|
||||
|
||||
**Valstrik 1 — Scope-creep tijdens refactor.** "We zijn toch bezig in deze code,
|
||||
laten we ook X meenemen." Weerstand: X komt op `/dev-docs/BACKLOG.md` met
|
||||
trigger-conditie, wordt niet mee-genomen. Enige uitzondering: X is een directe
|
||||
blocker voor het huidige consolidatie-werkpakket.
|
||||
|
||||
**Valstrik 2 — Gold-plating.** Iets verder perfectioneren dan nodig. Elke
|
||||
refactor-actie moet een concrete trigger hebben: "dit veroorzaakt drift in Y"
|
||||
of "dit blokkeert feature Z" of "dit is een security-risico". "Dit kan
|
||||
eleganter" is geen geldige trigger.
|
||||
|
||||
**Valstrik 3 — Feature-sprawl verkapt als fundering.** "Als we toch bezig zijn
|
||||
met bindings, laten we ook een binding-designer-UI bouwen." Nee. Fundering =
|
||||
datamodel + services + tests. Features komen daarna, in de 4-workflow-build.
|
||||
|
||||
**Bij elke nieuwe ingeving tijdens de sprint stelt Claude de drie vragen:**
|
||||
1. Is dit een scope-uitbreiding? → backlog
|
||||
2. Is dit elegantie-verbetering zonder concrete trigger? → niet doen
|
||||
3. Is dit een feature verkleed als fundering? → naar na de consolidatie
|
||||
|
||||
---
|
||||
|
||||
## 3. Vastgelegde besluiten (niet ter discussie in deze sprint)
|
||||
|
||||
Deze beslissingen zijn genomen en worden *niet* opnieuw geopend. Voor volgende
|
||||
chats: behandel deze als gegeven.
|
||||
|
||||
1. **Binding als eerste-klas burger.** Aparte `form_field_bindings` tabel met
|
||||
typed kolommen: `field_id`, `target_entity`, `target_attribute`, `merge_strategy`,
|
||||
`trust_level`. Eén veld kan meerdere bindings hebben (multi-binding scenarios).
|
||||
Details in §6.1.
|
||||
|
||||
2. **FormBindingApplicator als hoofdpad.** Nieuwe service die na
|
||||
`FormSubmissionSubmitted` bindings doorloopt en entities bijwerkt. Inclusief
|
||||
automatische Person-creation bij public EVENT_REGISTRATION submissions
|
||||
zonder subject. Details in §6.2.
|
||||
|
||||
3. **Denormalized columns op `form_submissions`.** `event_id`, `organisation_id`
|
||||
en `submitted_by_user_id` zijn al aanwezig of worden toegevoegd als directe
|
||||
foreign keys, niet alleen via schema-joins. Details in §6.3.
|
||||
|
||||
4. **Purpose registry in config, geen `custom` escape.** Afgeslankte set van
|
||||
7 purposes voor v1.0: EVENT_REGISTRATION, ARTIST_ADVANCE, SUPPLIER_INTAKE,
|
||||
POST_EVENT_EVALUATION, INCIDENT_REPORT, SIGNATURE_CONTRACT, USER_PROFILE.
|
||||
Registry leeft in `config/form_builder/purposes.php` + typed value-objects.
|
||||
Nieuwe purposes vereisen een migratie + listener — dat is correct, niet
|
||||
lastig. Details in §6.4.
|
||||
|
||||
5. **ULID consistent overal, ook pivots.** Bestaande integer-PK-pivots worden
|
||||
gemigreerd. Opsporings-pas in week 1 van de sprint. Details in §6.5.
|
||||
|
||||
6. **Eigen tabellen voor `validation_rules`, `conditional_logic`,
|
||||
`form_fields.options`.** Typed, queryable. `form_schemas.settings` krimpt
|
||||
naar echt opaque config (rendering hints). Details in §6.6.
|
||||
|
||||
7. **Eén SPA op één domein.** `apps/portal/` wordt een route-tree binnen
|
||||
`apps/app/`. Alles op `crewli.app`. Route-guards bepalen welke layout,
|
||||
welke context, welke permissies. Multi-role users krijgen een context-
|
||||
switcher in de nav-balk. Details in §4.
|
||||
|
||||
8. **Observability foundation.** Sentry voor error tracking, structured
|
||||
logging via Laravel's context, performance monitoring via Laravel Telescope
|
||||
(dev) + Sentry Performance (prod). Details in §6.7.
|
||||
|
||||
9. **Enterprise scope betekent 4 kern-workflows voor v1.0.** Niet 23. De
|
||||
overige purposes in de registry zijn vocabulaire zonder implementatie;
|
||||
zij krijgen implementatie wanneer een concrete klant-case dit rechtvaardigt.
|
||||
|
||||
---
|
||||
|
||||
## 4. Het één-SPA besluit: uitvoerbaar overzicht
|
||||
|
||||
Gekozen architectuur: **één codebase, één build, één domein.**
|
||||
|
||||
### 4.1 Hostnames
|
||||
- `crewli.app` — alle gebruikers, alle rollen, één SPA
|
||||
- `api.crewli.app` — Laravel backend (ongewijzigd)
|
||||
- `docs.crewli.app` — VitePress docs (ongewijzigd)
|
||||
|
||||
Rationale: vrijwilligers en organizers zijn verschillende rollen binnen
|
||||
hetzelfde product, niet verschillende producten op hetzelfde platform. Dit volgt
|
||||
het patroon van Slack/Linear/Notion/GitHub, waar alle gebruikers onder één
|
||||
domein werken en de UI zich aanpast aan rol + context. Subdomeinen per rol (zoals
|
||||
Shopify/Stripe) worden pas zinvol wanneer er fundamenteel verschillende producten
|
||||
zijn — niet ons geval.
|
||||
|
||||
### 4.2 Directory-structuur na consolidatie
|
||||
|
||||
```
|
||||
apps/
|
||||
app/ ← enige Vue SPA
|
||||
src/
|
||||
pages/
|
||||
(auth)/ ← login, forgot-password, MFA
|
||||
portal/ ← wat nu in apps/portal/src/pages/ zit
|
||||
my-shifts/
|
||||
availability/
|
||||
profile/
|
||||
...
|
||||
register/ ← publieke formulier-flow (niet ingelogd)
|
||||
[public_token].vue
|
||||
events/ ← organizer: events + sub-pages
|
||||
persons/ ← organizer: persons overview
|
||||
organisations/ ← organizer: settings
|
||||
platform/ ← super-admin (role:super_admin)
|
||||
index.vue ← post-login landing of context-switcher
|
||||
components/
|
||||
organizer/ ← organizer-only componenten
|
||||
portal/ ← portal-only componenten
|
||||
shared/ ← gedeeld tussen beide
|
||||
composables/
|
||||
forms/ ← voormalige packages/form-schema/ komt hier
|
||||
...
|
||||
layouts/
|
||||
OrganizerLayout.vue ← voor organizer routes, met organizer-nav
|
||||
PortalLayout.vue ← voor portal routes, met portal-nav
|
||||
PublicLayout.vue ← voor /register/* routes, minimaal
|
||||
router/
|
||||
index.ts ← route-guards bepalen layout + toegang
|
||||
routes.ts ← één route-tree, layout per section
|
||||
plugins/
|
||||
theme.ts ← context-based Vuetify theme injectie
|
||||
portal/ ← verdwijnt na sprint
|
||||
api/ ← ongewijzigd
|
||||
packages/
|
||||
form-schema/ ← verdwijnt, inhoud verhuist naar apps/app/src/composables/forms/
|
||||
```
|
||||
|
||||
### 4.3 Routing en context-switching
|
||||
|
||||
Er is geen hostname-detectie. Routes bepalen alles:
|
||||
|
||||
```ts
|
||||
// Conceptueel — definitieve implementatie in sprint
|
||||
const routes = [
|
||||
// Publiek (geen auth)
|
||||
{ path: '/login', component: LoginPage, meta: { layout: 'PublicLayout' } },
|
||||
{ path: '/register/:publicToken', component: PublicFormPage, meta: { layout: 'PublicLayout' } },
|
||||
{ path: '/forgot-password', component: ForgotPasswordPage, meta: { layout: 'PublicLayout' } },
|
||||
|
||||
// Portal routes (auth required, any role)
|
||||
{
|
||||
path: '/portal',
|
||||
meta: { layout: 'PortalLayout', requiresAuth: true, context: 'portal' },
|
||||
children: [
|
||||
{ path: 'my-shifts', component: MyShiftsPage },
|
||||
{ path: 'availability', component: AvailabilityPage },
|
||||
{ path: 'profile', component: ProfilePage },
|
||||
],
|
||||
},
|
||||
|
||||
// Organizer routes (auth + organizer role)
|
||||
{
|
||||
path: '/',
|
||||
meta: { layout: 'OrganizerLayout', requiresAuth: true, context: 'organizer',
|
||||
requiresRole: 'organizer' },
|
||||
children: [
|
||||
{ path: '', component: OrganizerDashboard },
|
||||
{ path: 'events', component: EventsList },
|
||||
// ... etc
|
||||
],
|
||||
},
|
||||
|
||||
// Platform admin (auth + super_admin role)
|
||||
{
|
||||
path: '/platform',
|
||||
meta: { layout: 'OrganizerLayout', requiresAuth: true, requiresRole: 'super_admin' },
|
||||
children: [...]
|
||||
},
|
||||
]
|
||||
```
|
||||
|
||||
**Post-login landing voor multi-role gebruikers:**
|
||||
- Enkel portal-rol → redirect naar `/portal/my-shifts`
|
||||
- Enkel organizer-rol → redirect naar `/` (dashboard)
|
||||
- Beide rollen → landing-page op `/` met twee kaarten (Portal / Organizer) óf
|
||||
direct naar laatst-gebruikte context (lokaal opgeslagen als preference)
|
||||
|
||||
**Context-switcher in nav-balk** voor multi-role users:
|
||||
- Permanent zichtbaar in OrganizerLayout en PortalLayout
|
||||
- Eén klik switch naar andere context
|
||||
- Vergelijkbaar met Slack's workspace-switcher
|
||||
|
||||
### 4.4 Wat blijft werken ongewijzigd
|
||||
|
||||
- Sanctum session auth (één cookie, één domein) — simpeler dan eerst gedacht
|
||||
- Portal-token-auth voor publieke routes (`/register/{public_token}`,
|
||||
`/public/forms/...` API-endpoints) — blijft als aparte middleware
|
||||
- Impersonation — blijft binnen organizer-tree
|
||||
- API-calls naar `api.crewli.app` — ongewijzigd
|
||||
|
||||
**Wat wegvalt door één-domein-keuze:** cross-domain cookie isolation is niet meer
|
||||
nodig. De `CookieBearerToken`-middleware per app (uit AUTH_ARCHITECTURE.md) mag
|
||||
vereenvoudigd worden in de sprint — dit wordt meegenomen onder WS-3.
|
||||
|
||||
### 4.5 Wat dit kost
|
||||
|
||||
Reële inschatting: 2-4 dagen werk in de consolidatie-sprint.
|
||||
- 1 dag: directory-structuur omzetten, imports repareren
|
||||
- 1 dag: router + layouts + route-guards + context-switcher
|
||||
- 1 dag: theming per context, Vuetify-config, auth-cleanup
|
||||
- 1 dag: testen, deploy-config verifiëren
|
||||
|
||||
Iets simpeler dan bij het hostname-alternatief omdat we geen hostname-detector
|
||||
hoeven te bouwen en omdat cross-domain cookie complexiteit vervalt.
|
||||
|
||||
### 4.6 Toekomst-deur openhouden
|
||||
|
||||
Als later één van deze drie scenario's optreedt, kunnen we alsnog splitsen
|
||||
zonder dat we vandaag te vroeg gesplitst hadden:
|
||||
|
||||
- Portal wil SSR voor SEO → portal splitsen naar Nuxt op aparte subdomain
|
||||
- Een third product (marketplace, kassa-app) verschijnt → apart project op
|
||||
eigen subdomain
|
||||
- Whitelabel per organisatie met eigen subdomain → routing-laag erboven
|
||||
|
||||
Geen van deze scenario's is vandaag aan de orde. Splitsen vanuit één SPA is
|
||||
goed te doen wanneer nodig (één tot twee weken werk).
|
||||
|
||||
---
|
||||
|
||||
## 5. Prioriteitsvolgorde werkstromen
|
||||
|
||||
De consolidatie-sprint kent **acht werkstromen** (afgekort WS). Ze worden in
|
||||
deze volgorde uitgevoerd, met waar mogelijk parallelle PRs binnen een werkstroom.
|
||||
|
||||
**WS-1 — Opsporings-pas (1 dag)**
|
||||
Volledige scan van de codebase op: niet-ULID primary keys, JSON-kolommen met
|
||||
queryable data die ik nog niet geïdentificeerd heb, polymorfe relaties die
|
||||
inconsistent zijn. Output: een definitieve lijst van te migreren items.
|
||||
Gaat voor alle andere werkstromen, omdat latere WS-en hun scope ervan
|
||||
afhangen.
|
||||
|
||||
**WS-2 — Purpose registry + legacy purpose-cleanup (2-3 dagen)**
|
||||
Van 23 naar 7 purposes. Registry in config. Typed value-objects. Migratie
|
||||
die bestaande `form_schemas` opschoont (alle schemas met "weg te halen"
|
||||
purposes → archief of delete, afhankelijk van of er seed-data hangt).
|
||||
|
||||
**WS-3 — Eén SPA consolidatie (3-5 dagen)**
|
||||
Zoals in §4. Kan deels parallel aan WS-4 en WS-5 want raakt vooral
|
||||
frontend. Apart maken van frontend-consolidatie en backend-consolidatie
|
||||
vermijdt rebase-hel.
|
||||
|
||||
**WS-4 — ULID-consistentie + denormalized submission columns (2-3 dagen)**
|
||||
Pivot-tabellen naar ULID. `event_id`, `organisation_id` op submissions
|
||||
(en elders waar de opsporings-pas ze vindt).
|
||||
|
||||
**WS-5 — JSON-kolom-opsplitsing (4-6 dagen)**
|
||||
Vier sub-werkstromen die in serie moeten (elk heeft data-migratie):
|
||||
- WS-5a — `form_field_bindings` tabel
|
||||
- WS-5b — `form_field_validation_rules` tabel
|
||||
- WS-5c — `form_field_conditional_logic` tabel
|
||||
- WS-5d — `form_field_options` tabel
|
||||
|
||||
**WS-6 — Binding-pipeline / FormBindingApplicator (4-5 dagen)**
|
||||
Nieuwe service. Listener. Person-creation-flow. Guardrails in
|
||||
FormSchemaService. Volledige testdekking voor alle 7 purposes (ook als
|
||||
ze nog geen functionaliteit hebben — tests verifiëren dat de binding-
|
||||
infrastructuur ze ondersteunt).
|
||||
|
||||
**WS-7 — Observability foundation (2-3 dagen)**
|
||||
Sentry SDK (backend + frontend). Structured logging conventies. Telescope
|
||||
voor dev. Performance monitoring. Runbook voor incident response.
|
||||
|
||||
**WS-8 — Documentatie-consolidatie (2-3 dagen)**
|
||||
`/CLAUDE.md` bijwerken met nieuwe conventies. `/dev-docs/SCHEMA.md` volledige
|
||||
rewrite naar v2.0 met nieuwe tabellen. `/dev-docs/ARCH-FORM-BUILDER.md`
|
||||
bijwerken. Nieuwe `/dev-docs/ARCH-BINDINGS.md`. Nieuwe
|
||||
`/dev-docs/ARCH-OBSERVABILITY.md`. `/dev-docs/BACKLOG.md` opschonen:
|
||||
verwijderen wat door de sprint is opgelost, expliciet markeren wat na
|
||||
de sprint nog wordt opgepakt.
|
||||
|
||||
**Totale inschatting sprint:** 22-32 dagen werk.
|
||||
**Doorlooptijd bij actieve inzet:** 4-6 weken.
|
||||
**Aantal PRs:** 25-35, veel zijn klein en focused.
|
||||
|
||||
---
|
||||
|
||||
## 6. Per werkstroom: scope, uitkomst, klaar-criteria
|
||||
|
||||
### 6.1 WS-5a — form_field_bindings
|
||||
|
||||
**Scope:** Nieuwe tabel `form_field_bindings`. Vrije `binding` JSON-kolom op
|
||||
`form_fields` wordt vervangen door een queryable relatie. Eén veld kan meerdere
|
||||
bindings hebben.
|
||||
|
||||
**Tabel-ontwerp (concept, finetunen in sprint):**
|
||||
```
|
||||
form_field_bindings:
|
||||
id (ulid, primary)
|
||||
form_field_id (ulid, foreign, cascade)
|
||||
target_entity (string, 50) -- 'person', 'artist', 'company', 'user'
|
||||
target_attribute (string, 100) -- 'email', 'first_name', 'stage_name', etc.
|
||||
merge_strategy (enum) -- 'overwrite', 'append', 'replace', 'first_write_wins'
|
||||
trust_level (int, default 50) -- 0-100, hoger = dominanter bij conflict
|
||||
is_identity_key (bool, default false)-- wordt gebruikt voor person-matching?
|
||||
created_at, updated_at
|
||||
unique (form_field_id, target_entity, target_attribute)
|
||||
```
|
||||
|
||||
**Uitkomst:**
|
||||
- Bestaande `binding` JSON-data gemigreerd naar rijen in deze tabel
|
||||
- Builder-UI (later) kan een dropdown tonen gevuld uit binding-registry
|
||||
- Backend kan queryen "welke velden binden aan `person.email`"
|
||||
- Conflict-strategie per binding expliciet
|
||||
|
||||
**Klaar-criteria:**
|
||||
- Migratie loopt vooruit én terug zonder data-verlies
|
||||
- Feature tests: schema create → binding toevoegen → submission submit → entity updated
|
||||
- `form_fields.binding` JSON-kolom verdwenen
|
||||
- ARCH-BINDINGS.md geschreven met complete uitleg
|
||||
|
||||
### 6.2 WS-6 — FormBindingApplicator
|
||||
|
||||
**Scope:** Nieuwe service `App\Services\FormBuilder\FormBindingApplicator`. Nieuwe
|
||||
listener `App\Listeners\FormBuilder\ApplyBindingsOnFormSubmit`. Person-creation-
|
||||
helper. Update van `FormSchemaService` met pre-publish-validation.
|
||||
|
||||
**Uitkomst:**
|
||||
- Bij elke `FormSubmissionSubmitted` worden alle bindings doorlopen
|
||||
- Voor elke binding: lookup value in submission, haal merge-strategie op,
|
||||
update target entity
|
||||
- Bij public EVENT_REGISTRATION zonder subject: creëer Person, zet als
|
||||
subject op submission, voer dan bindings uit
|
||||
- Identity-match draait na binding-applicatie op nieuwe Person
|
||||
- Alle actions gelogged in activity log met bron-submission
|
||||
|
||||
**Klaar-criteria:**
|
||||
- Test: public registratie → Person aangemaakt met alle binding-attributen
|
||||
- Test: authenticated update-submission → bestaande Person bijgewerkt
|
||||
- Test: conflict (twee submissions, zelfde email-binding, verschillende waarde)
|
||||
→ last-write-wins met activity log entry
|
||||
- Test: tag-picker additief (niet overschrijvend)
|
||||
- Test: schema met AVAILABILITY_PICKER zonder event-koppeling kan niet publiceren
|
||||
- Test: EVENT_REGISTRATION schema zonder email-binding kan niet publiceren
|
||||
|
||||
### 6.3 WS-4 — Denormalized submission columns
|
||||
|
||||
**Scope:** Kolommen `event_id` (nullable), `organisation_id` (niet-nullable),
|
||||
`submitted_by_user_id` (bestaat al) op `form_submissions`. Observer vult ze bij
|
||||
create. Backfill-script voor bestaande submissies (indien aanwezig in seed-data).
|
||||
|
||||
**Uitkomst:**
|
||||
- Queries zoals "alle submissions voor event X" worden directe queries, geen joins
|
||||
- Analytics / reporting kan efficiënt draaien
|
||||
- Multi-event reuse scenario's (ARTIST_ADVANCE) werken: één schema, submissies
|
||||
naar meerdere events
|
||||
|
||||
**Klaar-criteria:**
|
||||
- Feature tests: submission krijgt correcte event_id en organisation_id
|
||||
- Scope-middleware gebruikt event_id direct (waar relevant)
|
||||
- SCHEMA.md bijgewerkt
|
||||
|
||||
### 6.4 WS-2 — Purpose registry
|
||||
|
||||
**Scope:** `app/FormBuilder/Purposes/PurposeDefinition.php` (value object).
|
||||
`config/form_builder/purposes.php` (registry). `PurposeRegistry` service.
|
||||
`form_schemas.purpose` blijft een string-kolom, maar `custom_purpose_slug`
|
||||
kolom verdwijnt.
|
||||
|
||||
**Purposes v1.0:**
|
||||
1. `event_registration` — vrijwilligers/crew aanmelden, subject=Person
|
||||
2. `artist_advance` — artiesten advance invullen, subject=Artist
|
||||
3. `supplier_intake` — leveranciers onboarding, subject=Company
|
||||
4. `post_event_evaluation` — feedback na afloop, subject=Person
|
||||
5. `incident_report` — incidenten melden, subject=Person
|
||||
6. `signature_contract` — contracten ondertekenen, subject=User
|
||||
7. `user_profile` — profiel-updates, subject=User
|
||||
|
||||
**Uitkomst:**
|
||||
- Purposes zijn data-driven: je kunt in config een nieuwe toevoegen zonder
|
||||
enum-migratie, maar je moet wel bewust zijn dat een nieuwe purpose meestal
|
||||
een listener vereist
|
||||
- Elke PurposeDefinition heeft: label, subject_type, default_submission_mode,
|
||||
allows_public_access, required_bindings (lijst van bindings die het schema
|
||||
moet hebben om publiceerbaar te zijn)
|
||||
|
||||
**Klaar-criteria:**
|
||||
- Tests voor elke purpose: kan schema gecreëerd worden, kan gepubliceerd worden
|
||||
(met juiste bindings), submit-flow werkt end-to-end
|
||||
- ARCH-FORM-BUILDER.md bijgewerkt
|
||||
|
||||
### 6.5 WS-4 — ULID-consistentie
|
||||
|
||||
**Scope:** Alle pivot-tabellen met integer PK → ULID. Opsporings-pas (WS-1)
|
||||
definieert de exacte lijst. Data-migratie per tabel.
|
||||
|
||||
**Uitkomst:** Consistent ID-gebruik. Geen ID-enumeration-risico meer.
|
||||
Eenvormig test-patroon.
|
||||
|
||||
**Klaar-criteria:**
|
||||
- Alle primary keys in business-schema zijn ULID
|
||||
- Alle foreign keys consistent
|
||||
- Tests lopen door
|
||||
|
||||
### 6.6 WS-5b/c/d — Remaining JSON-splits
|
||||
|
||||
Dezelfde aanpak als WS-5a maar voor:
|
||||
- `form_field_validation_rules` (targeting `validation_rules` JSON)
|
||||
- `form_field_conditional_logic` (targeting `conditional_logic` JSON, met
|
||||
AND/OR tree structuur)
|
||||
- `form_field_options` (targeting `options` JSON, met position + label +
|
||||
metadata per optie)
|
||||
|
||||
**Conditional logic verdient speciale aandacht.** AND/OR trees in een relationele
|
||||
tabel zijn niet triviaal. Overweging in de sprint: platte lijst met
|
||||
`logical_group_id` vs recursieve tabel met `parent_id`. Beslissing tijdens
|
||||
WS-5c, niet vooruit.
|
||||
|
||||
### 6.7 WS-7 — Observability
|
||||
|
||||
**Scope:**
|
||||
- Sentry SDK installeren in `apps/api/` (Laravel) en `apps/app/` (Vue)
|
||||
- Structured logging convention: alle logs via `Log::channel('structured')`
|
||||
met context (organisation_id, user_id, request_id)
|
||||
- Laravel Telescope in dev only
|
||||
- Sentry Performance voor transaction tracking in prod
|
||||
- Runbook in `dev-docs/RUNBOOK.md` met: hoe te reageren op alerts, waar
|
||||
logs te vinden, hoe een error te tracen
|
||||
|
||||
**Klaar-criteria:**
|
||||
- Test: geforceerde exception in een controller verschijnt in Sentry (staging)
|
||||
- Test: elke request heeft een unique request_id in logs
|
||||
- Telescope werkt in dev, niet in prod
|
||||
|
||||
### 6.8 WS-3 — Eén SPA consolidatie
|
||||
|
||||
Zie §4 voor scope en stappen.
|
||||
|
||||
**Klaar-criteria:**
|
||||
- `apps/portal/` is verwijderd
|
||||
- Beide hostnames serveren correcte route-tree
|
||||
- Auth werkt op beide hostnames met isolatie
|
||||
- Alle bestaande tests in apps/app/ draaien groen
|
||||
- Nieuwe tests voor hostname-detectie
|
||||
- Deploy-config (DirectAdmin) verifieert beide hostnames
|
||||
|
||||
---
|
||||
|
||||
## 7. Starten van de volgende chat
|
||||
|
||||
Deze chat wordt afgesloten. De volgende chat start met een dichte briefing die
|
||||
**exact dit document** en de volgende meta-instructie als context geeft.
|
||||
|
||||
**Opening-prompt voor de volgende chat (kopieer letterlijk):**
|
||||
|
||||
```
|
||||
Nieuwe chat in project Crewli. We beginnen de architectuur-consolidatie-sprint
|
||||
zoals vastgelegd in /dev-docs/ARCH-CONSOLIDATION-2026-04.md (lees dat document
|
||||
eerst uit Gitea, main branch).
|
||||
|
||||
Context:
|
||||
- Pre-launch. Geen gebruikers, geen tijdsdruk. Doel: enterprise-grade fundament.
|
||||
- 8 werkstromen (WS-1 t/m WS-8). We starten met WS-1: opsporings-pas.
|
||||
- Ik wil dat je ook /CLAUDE.md en /dev-docs/SCHEMA.md leest voor basiscontext,
|
||||
en kort aangeeft wat je hebt begrepen.
|
||||
|
||||
Verzoek voor deze eerste chat:
|
||||
1. Bevestig dat je het consolidatie-document en CLAUDE.md hebt gelezen.
|
||||
2. Lever de prompt voor WS-1 (opsporings-pas). Deze moet een Claude Code opdracht
|
||||
zijn die het hele codebase doorloopt op:
|
||||
- Primary keys die geen ULID zijn
|
||||
- JSON-kolommen met queryable data die niet in §6 zijn benoemd
|
||||
- Polymorfe relaties die inconsistent gemodelleerd zijn
|
||||
- Overige architectuur-smells die we in de consolidatie moeten meenemen
|
||||
3. Output van WS-1 is een rapport (Markdown), geen code. Op basis daarvan
|
||||
beslissen we of de scope van andere werkstromen aanpassing behoeft.
|
||||
|
||||
Regels die tijdens de sprint gelden:
|
||||
- Elke nieuwe ingeving: toets aan de drie valstrikken in §2 van het
|
||||
consolidatie-document. Bij twijfel: backlog.
|
||||
- Elke architectuur-keuze tijdens executie: toets aan de principes in §1.
|
||||
- Geen scope-uitbreiding zonder expliciete go van mij.
|
||||
|
||||
Bevestig dat je klaar bent voor WS-1.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Verwijzingen naar bestaand materiaal
|
||||
|
||||
Voor de volgende chat is het nuttig dat deze documenten als achtergrond
|
||||
beschikbaar zijn (niet allemaal lezen, maar weten dat ze bestaan):
|
||||
|
||||
- `/CLAUDE.md` — projectafspraken, zero-compromise regels
|
||||
- `/dev-docs/SCHEMA.md` v2.1 — huidig database-schema
|
||||
- `/dev-docs/ARCH-FORM-BUILDER.md` — bestaande form-builder architectuur
|
||||
- `/dev-docs/AUTH_ARCHITECTURE.md` — dual-mode auth, cross-app isolation
|
||||
- `/dev-docs/VUEXY_COMPONENTS.md` — frontend component-bibliotheek
|
||||
- `/dev-docs/BACKLOG.md` — bestaande backlog, wordt opgeschoond in WS-8
|
||||
|
||||
---
|
||||
|
||||
## 9. Openstaand (niet in scope van consolidatie-sprint)
|
||||
|
||||
Om expliciet te zijn over wat *niet* in deze sprint gebeurt:
|
||||
|
||||
- Implementatie van de 4 kern-workflows (komt erna als aparte sprint S3c...S3f)
|
||||
- Nieuwe UI-modules (komt erna)
|
||||
- Marketplace / subscription / billing (toekomst)
|
||||
- Mobile app (toekomst)
|
||||
- Locale-uitbreiding voorbij NL/EN (komt wanneer marktbehoefte concreet is)
|
||||
- Performance-optimalisatie voorbij observability foundation (wanneer metrics
|
||||
dit rechtvaardigen)
|
||||
|
||||
---
|
||||
|
||||
## 10. Sign-off ruimte
|
||||
|
||||
Voor Bert om te bevestigen voordat we de volgende chat starten:
|
||||
|
||||
- [ ] Leidende principes §1 akkoord
|
||||
- [ ] Vastgelegde besluiten §3 akkoord
|
||||
- [ ] SPA-consolidatie §4 akkoord
|
||||
- [ ] Werkstroom-volgorde §5 akkoord
|
||||
- [ ] Purposes v1.0 lijst §6.4 akkoord
|
||||
- [ ] Openings-prompt §7 klaar om te gebruiken
|
||||
|
||||
Na jouw sign-off commit ik dit document naar de repo en sluiten we deze chat.
|
||||
@@ -6,6 +6,14 @@
|
||||
> **Gebruik:** Voeg nieuwe items toe als ze tijdens development ontstaan.
|
||||
> Geef elk item een prioriteit en fase zodra je het gaat oppakken.
|
||||
|
||||
## Architectuur consolidatie sprint (actief)
|
||||
|
||||
Zie `dev-docs/ARCH-CONSOLIDATION-2026-04.md` voor volledige scope, principes en
|
||||
werkstroomvolgorde. Sprint gestart april 2026, 8 werkstromen, 22-32 dagen werk
|
||||
totaal. Tijdens de sprint worden bestaande backlog-items die door de sprint
|
||||
worden opgelost daar expliciet gemarkeerd, en krijgen items die na de sprint
|
||||
worden opgepakt een `[post-consolidatie]` tag.
|
||||
|
||||
---
|
||||
|
||||
## Fase 3 — Geplande features
|
||||
|
||||
Reference in New Issue
Block a user