WS-6 v1.3-delta — Closure docs-PR #12

Merged
bert.hausmans merged 4 commits from docs/ws-6-v1.3-delta-closure into main 2026-05-08 10:30:20 +02:00
Showing only changes of commit c5682f181f - Show all commits

View File

@@ -465,22 +465,122 @@ Person. De portal `IdentityMatchBanner` leest dit veld en toont de
juiste copy. Contract ligt vast in
`tests/Feature/FormBuilder/Listeners/TriggerPersonIdentityMatchOnFormSubmitTest`.
**Resterend werk (de eigenlijke FORM-05):** public form submissions
(subject_type=null) krijgen momenteel *altijd* 'pending' omdat er nog
geen Person bestaat om tegen te matchen. Breid uit met:
**Resterend werk (de eigenlijke FORM-05):** post-D2 (PR #11 `23a5696`)
schrijft de queued `TriggerPersonIdentityMatchOnFormSubmit` voor
self-registered public submissions (`registration_source='self'`)
standaard `'none'` zodra de exact-email-match in
`PersonIdentityService::detectMatches` leeg terugkomt — het bestaande
fuzzy-name fallback-pad wordt expliciet overgeslagen voor self-registered
Persons (zie `PersonIdentityService::detectMatches` regel 117119, *"skip
fuzzy matching for self-registered persons — they provided their own
email, so that's the canonical identity"*). Public submitters die een
typo in hun email maken of een ander email-account gebruiken dan eerder
bij de organisatie geregistreerd, krijgen daardoor geen "we may have
found you" signaal en moeten organiser-side handmatig gereconcilieerd
worden.
- Nieuwe methode op PersonIdentityService:
`detectMatchesByValues(array $values, string $organisationId): MatchResult`
- Een extra tak in `TriggerPersonIdentityMatchOnFormSubmit::resolveStatus`
die voor public submissions de values uit `FormSubmission->values`
extraheert (email / first_name / last_name via de schema binding),
deze methode aanroept, en 'matched' / 'pending' / 'none' schrijft.
Breid uit met:
- Nieuwe methode op `PersonIdentityService`:
```php
public function detectMatchesByValues(
array $values,
string $organisationId,
): Collection {
// Multi-signal match: email + first_name + last_name + DOB +
// optionele custom-field whitelist, gewogen naar
// betrouwbaarheid van de bron-velden. Werkt los van
// $person->registration_source, dus self-registered submissions
// krijgen óók fuzzy-name + DOB candidates terug.
}
```
- Een extra match-arm in de status-derivation van
`TriggerPersonIdentityMatchOnFormSubmit::handle()`. D2 inlinete de
status-derivation in `handle()` zelf — er is geen aparte
`resolveStatus` methode meer. De huidige logica:
```php
$matches = $this->identityService->detectMatches($person);
$status = match (true) {
$person->user_id !== null => 'matched',
$matches->isNotEmpty() => 'pending',
default => 'none',
};
```
wordt uitgebreid met een value-based fallback wanneer de Person geen
`user_id` heeft én de exact-email matcher leeg terugkomt:
```php
$matches = $this->identityService->detectMatches($person);
$valueMatches = $matches->isEmpty() && $person->user_id === null
? $this->identityService->detectMatchesByValues(
$this->extractFormValuesForMatching($submission),
$submission->organisation_id,
)
: collect();
$status = match (true) {
$person->user_id !== null => 'matched',
$matches->isNotEmpty() || $valueMatches->isNotEmpty() => 'pending',
default => 'none',
};
```
De `extractFormValuesForMatching` helper trekt de relevante velden
(email / first_name / last_name / date_of_birth + eventuele
whitelisted custom-fields) uit `FormSubmission->values` via de
schema-binding metadata.
Zo krijgt de portal-UX een betekenisvol signaal in plaats van een
constante 'pending'.
`'none'` voor elke public submitter die niet exact-email matched.
Prioriteit: Medium. Kan gebundeld worden met de organizer
`person_identity_matches` UI (ook nog een frontend gap).
**Prioriteit:** Medium. Bundel met de organizer
`person_identity_matches` UI (frontend gap) én met
`FRONTEND-ECHO-IDENTITY-MATCH-SUBSCRIPTION` (de portal banner update zo
dat hij de eindstatus real-time ontvangt).
---
### FRONTEND-ECHO-IDENTITY-MATCH-SUBSCRIPTION
**Aanleiding:** WS-6 v1.3-delta D2 (PR #11 `23a5696`, 2026-05-08) introduceerde
de `FormSubmissionIdentityMatchResolved` broadcast event class en bijbehorende
`submission.{id}` private channel. De backend dispatcht het event maar de
frontend portal IdentityMatchBanner subscribet nog niet — de banner refresht
momenteel via TanStack Query refetch-on-window-focus, wat een interim-pad is
maar niet de ontworpen UX.
**Wat:**
- Pinia store-extension OF inline `Echo.private('submission.${submissionId}')`
subscription in de IdentityMatchBanner component
- Op event-receipt: invalidate de TanStack Query cache key voor de submission
resource zodat de banner copy automatisch update naar de finale staat
('matched' / 'pending' / 'none')
- Authorization wordt al afgedwongen door `routes/channels.php` callback
(submitter-only voor nu — zie ook TECH-CHANNEL-AUTH-ORG-ADMIN)
- E2E test: simuleer een form submission, verify dat de banner copy in real-time
van "we're checking matches…" naar de finale status flipt zonder reload
**Vereisten:**
- Laravel Echo client setup in `apps/app/` (verifieren: bestaat al voor andere
channels of moet nog worden toegevoegd?)
- Soketi server in development docker-compose en in productie deployment
(verifieren: WS-7 noemt Soketi in stack maar is broadcast wiring al getest?)
**Trigger-conditie:** Naar voren halen wanneer (a) de UX van TanStack
refetch-on-focus klagende klanten oplevert, OF (b) de eerstvolgende form-builder
frontend sprint (RFC-FORM-BUILDER-UI / S3b) gepland wordt — neem het mee in
diezelfde sprint zodat alle Echo-wiring tegelijk landt.
**Estimate:** 0.51 dag (afhankelijk van of Echo client setup nog moet).
**Refs:** `dev-docs/RFC-WS-6.md` v1.3.1 §Q1 v1.3 add 2, `dev-docs/ARCH-BINDINGS.md`
v1.2 §5.3, `apps/app/src/...` (portal IdentityMatchBanner location to be
identified during implementation), `routes/channels.php`.
---
@@ -1027,6 +1127,50 @@ ARCH-discussie en RFC.
---
### HARD-DEADLINE-QUERY-TIMEOUT
**Aanleiding:** WS-6 v1.3-delta D2 (PR #11 `23a5696`, 2026-05-08) introduceerde
`FormBindingApplicator::withDeadline(int $seconds)` — een **soft** deadline via
post-call microtime check. Deze beschermt tegen "applicator runs slow over many
bindings" (de long tail) maar **kan niet onderbreken** tijdens een hangende
MySQL-query of een lock-for-update wait die langer duurt dan de deadline.
Voor enterprise SaaS op piekmomenten (vrijwilligersregistratie-windows,
festival check-in flows) is een hangende query een reëel risico — PHP-FPM
worker blokkeert, queue depth loopt op, downstream impact.
**Wat:**
Drie potentiële uitvoeringspaden, te kiezen tijdens RFC-fase:
1. **MySQL connection-level timeouts**: zet `read_timeout` en `write_timeout`
op de Eloquent connection config voor de inner-transaction context.
Eenvoudig, maar globale impact (alle queries via die connection krijgen de
timeout).
2. **Per-query `MAX_EXECUTION_TIME` hints**: MySQL 5.7+ ondersteunt
`/*+ MAX_EXECUTION_TIME(N) */` query hints voor SELECT statements. Niet
ondersteund door MariaDB. Gericht maar database-vendor-specifiek.
3. **`pcntl_alarm` + signal handler**: hard interrupt via SIGALRM in de queue
worker context (CLI sapi). Werkt niet in HTTP context (PHP-FPM disablet
pcntl in de meeste configuraties). Combineren met optie 1 voor HTTP context.
**Trigger-conditie:** Naar voren halen wanneer (a) eerste productie-incident
waar `FormBindingApplicatorTimeoutException` getrigerd wordt en de soft
deadline blijkt onvoldoende (post-mortem toont een hangende query), OF (b)
SLO-vereisten van een enterprise klant strikte response-time-garanties
opleggen die de soft deadline niet kan halen.
**Estimate:** 2-3 dagen (RFC + implementatie + tests + connection config voor
alle environments).
**Refs:** `dev-docs/RFC-WS-6.md` v1.3.1 §Q1 v1.3 add 4, `dev-docs/ARCH-BINDINGS.md`
v1.2 §5.3, `api/app/FormBuilder/Bindings/FormBindingApplicator.php` (soft
deadline implementation).
---
### ~~TECH-02 — scopeForFestival helper op Event model~~ ✅ OPGELOST
---