# Observability triage runbook > Praktische gids voor wanneer Bert (of een latere op-call) een > GlitchTip alert / email krijgt of zelf de Issues-pagina opent. > > **Audience:** maintainer met repo-toegang en ssh-toegang naar de > productie-host. > > **Cross-references:** > [`ARCH-OBSERVABILITY.md`](../ARCH-OBSERVABILITY.md) (architectuur en > tag-taxonomie), [`GLITCHTIP.md`](../GLITCHTIP.md) (operationele > stack — boot, backup, restore), > [`observability-erasure.md`](./observability-erasure.md) (Art. 17 > procedure als triage in een GDPR-deletion-verzoek omdraait). --- ## §1 Eerste 60 seconden — issue inspecteren Open de issue in GlitchTip. Vóór je begint na te denken over de exception zelf, check de tags. Het kost 30 seconden en bepaalt vaak direct de prioriteit. ### 1.1 `actor_scope` — wat is de blast radius? | Waarde | Wat het betekent voor triage | |---|---| | `organisation` | Eén klant geraakt. Tag `organisation_id` zegt welke. | | `platform` | super_admin op een platform-route. Bug in admin-tooling, niet in een klant-flow. | | `user` | Authenticated user op een non-org route (`/me/*`, `/portal/profiel`). Vaak account-settings of auth-flow bug. | | `portal` | Token-based portal flow (artist advance, public form fill). Geen `user_id` aanwezig — debug via `request_id` correlation. | | `anonymous` | Public pagina, niet ingelogd. Login-flow / public-form bug. | ### 1.2 `release` — actuele deploy of oud residual? `release=crewli-api@` of `crewli-app@`. Vergelijk met `git log --oneline -5` op `main`. Als de SHA matcht het laatste deploy: het probleem zit in de huidige codebase. Als de SHA achterloopt: kan een long-running browser-session zijn die nog op een oude bundle draait, of een achtergrondservice die nog niet herstart is na deploy. ### 1.3 `actor_type` — wie is geraakt? `super_admin` / `organizer_admin` / `org_member` / `portal_token` / `unauthenticated`. Een `super_admin`-only bug is laag-prio voor klanten; een `org_member`-bug raakt vrijwilligers en moet sneller fix. ### 1.4 `organisation_id` — één klant of platform-breed? | Aanwezig op één issue | Eén klant. Reach out direct als blocker. | | Aanwezig met variable values across events binnen 1 issue | Multi-tenant bug; spike in events is een P0-signaal. | | Afwezig | Verwacht voor `actor_scope=platform` / `user` / `anonymous`. Niet aanwezig zijn betekent dus niet automatisch "platform-breed" — check `actor_scope` eerst. | ### 1.5 `impersonation.active` — admin-context? Bijna altijd `'false'`. Wanneer `'true'`, dan was een super_admin aan het impersonating. Bug kan in de target-user's data zitten of in de impersonation-flow zelf. Crosscheck: `impersonation.impersonator_user_id` (admin's ULID) en `impersonation.session_id` voor lookup in `impersonation_audit_logs`. ### 1.6 `request_id` — correlation naar Laravel logs Elke gecaptured event draagt `request_id` als tag of in de extra context (gezet door `BindRequestLogContext` middleware). Pak die ULID, ssh naar productie-host en grep: ```bash grep "01HX...." storage/logs/laravel.log | head -50 ``` Logregels die door dezelfde request flowden delen die ULID. Geeft context die GlitchTip's eigen breadcrumbs niet hebben (bijv. SQL queries, queue-job dispatches). --- ## §2 Triage-classificatie | Klasse | Signaal | Actie | |---|---|---| | **P0 — productie down** | Spike (>10 events / 5 min) op meerdere users; OF auth-flow bug die nieuwe logins blokkeert; OF spike op `actor_type=super_admin` (admin tooling kapot) | Direct fix-deploy. Notify klanten als > 30 min downtime verwacht. | | **P1 — single-user blocker** | Repeating events same `user_id`, geen workaround. User kan niet doorwerken. | Reach out to user, fix in current sprint, mark issue assigned in GlitchTip. | | **P2 — degraded UX** | Cosmetisch (bv. broken UI-state na specifieke actie), geen data-loss, workaround beschikbaar. | BACKLOG entry, plan in volgende sprint. Mark issue ignored in GlitchTip met BACKLOG-link in comment. | | **P3 — known-tolerated** | Bekende edge case, gedocumenteerd in BACKLOG of een ARCH-doc. Niet fix-baar zonder grotere refactor. | Mark issue resolved/ignored in GlitchTip met inline comment naar de tracking-locatie. | **Volume-thresholds zijn richtlijnen, niet hard rules.** Eén event op een super_admin-route die data corrupt zou maken is P0 ondanks count=1. Honderd events op een rate-limited public form-spam attempt is geen P0 ondanks volume. --- ## §3 Reproductie-stappen ### 3.1 Pak het stack-trace + tags GlitchTip toont stack trace met sourcemap-resolved frames (zie [`ARCH-OBSERVABILITY.md §8`](../ARCH-OBSERVABILITY.md)). Open de exception, klik op het top frame, lees de regel. ### 3.2 Cross-correlate met laravel.log ```bash ssh crewli@ cd /home/crewli/crewli/api grep "" storage/logs/laravel.log ``` Geeft je: alle log-regels die door deze request flowden, in volgorde. Zoek naar: SQL-query timing (langzame query?), queue-dispatch (achtergrond-job triggered?), warning-logs vóór de exception. ### 3.3 Lokale reproductie ```bash make services # start mysql + redis + mailpit + glitchtip make api # start Laravel dev server make app # start Vue dev server ``` Voor backend-only state-bugs: `php artisan tinker` om de specifieke state te recreëren. ```php // Voorbeeld: reproduceer een form-submission bug $schema = \App\Models\FormBuilder\FormSchema::find('01HX...'); $submission = \App\Models\FormBuilder\FormSubmission::find('01HY...'); event(new \App\Events\FormBuilder\FormSubmissionSubmitted($submission)); ``` Voor frontend-state-bugs: gebruik de Vue DevTools om Pinia store-state te dump'en, recreëer de state via `useAuthStore().$patch({...})` in de console. ### 3.4 Breadcrumbs GlitchTip's breadcrumbs tonen de event-historie vóór de exception. Frontend: route-changes, fetch-calls, console-warnings (input-text masked). Backend: query-events (sentry-laravel built-in), job-dispatch. Het patroon "user navigated → fetch failed → exception" is vaak genoeg om de bug te lokaliseren zonder lokale repro. --- ## §4 Common patronen ### 4.1 "ValidationException doorbreekt naar GlitchTip" Mag niet gebeuren per RFC §3.10. `ignore_exceptions` in `config/sentry.php` zou ValidationException moeten filteren. Check: ```bash grep -A 6 "ignore_exceptions" api/config/sentry.php ``` Als de class ontbreekt: regression — voeg toe en commit. Als de class er staat: misschien een subclass die niet matcht? `ignore_exceptions` matcht via `is_a($exception, $class)` dus subclasses werken normaal, maar verifieer. ### 4.2 "Same exception fingerprint, multiple events" GlitchTip dedupliceert via stack-trace-fingerprint. Honderd events binnen een minuut met dezelfde fingerprint = runaway error. Mogelijke oorzaken: - Cron-job die elke minuut faalt → check supervisord / cron-logs op productie-host. - Queue-worker die een job retry'd zonder backoff → check `queue.attempt` tag op de events; als die monoton stijgt, is retry de oorzaak. - Frontend infinite-loop in een Vue component (bijv. setup() throws in een loop) → check `release` tag, mogelijk net-deployed code. ### 4.3 "actor_scope=organisation maar organisation_id ontbreekt" Multi-tenant invariant violation. Per [ARCH §3.3](../ARCH-OBSERVABILITY.md): wanneer `actor_scope=organisation`, MOET `organisation_id` aanwezig zijn als valide ULID. Zo niet: file-level bug in `AuthScopeContextListener::resolveTenantContext()`. P1 — fix in current sprint en breidt `AuthScopeContextListenerTest::test_organisation_id_present_when_actor_scope_is_organisation` uit met de specifieke trigger-route. ### 4.4 "Frontend events zonder user_id terwijl user is ingelogd" Twee mogelijke oorzaken: - `actor_scope=portal` — verwacht gedrag voor token-based portal flows. Geen bug. - Pinia auth-store niet geïnitialiseerd op het moment van capture. Vaak na hard-refresh op een protected route waar `useAuthStore` nog niet `setUser()` heeft gehad. Check `useAuthStore().isInitialized`. ### 4.5 "Backend events zonder organisation_id op een org-scoped route" Per ARCH §4.1: auth-listener fires bij `Authenticated` / `TokenAuthenticated`, route-context komt uit `BindSentryRouteContext`. Als de listener vóór route-binding draait, kan organisation_id ontbreken. Realistisch: dit is geen probleem in de huidige implementatie omdat `request()` binnen de listener al de gebonden route ziet — maar als een refactor het pattern breekt, vangt `AuthScopeBindingHttpFlowTest` het. ### 4.6 "GlitchTip krijgt geen events meer (zwarte stilte)" Frontend: check CSP-violation in DevTools Console (zie [ARCH §7](../ARCH-OBSERVABILITY.md)). Bij nieuwe environment / domein moet de ingest-host opnieuw whitelisted worden. Backend: check `Integration::handles($exceptions)` in `api/bootstrap/app.php`. Per [BACKLOG OBS-6](../BACKLOG.md) is dit een bekende silent-failure-mode bij sentry-laravel install. Test: ```bash cd api && php artisan tinker \Sentry\captureMessage('triage smoke test') ``` Refresh GlitchTip Issues; als er geen event verschijnt, zit het probleem in de SDK-config zelf (DSN, network) niet in `Integration::handles`. --- ## §5 Resolutie ### 5.1 Mark as resolved Na fix-deploy: open de issue in GlitchTip, klik **Resolve**. GlitchTip's auto-resolve-on-version werkt niet altijd betrouwbaar in zelf-hosted setups; doe het handmatig zodra je de deploy ziet draaien op de prod-host. Als de issue terugkomt op een latere `release` tag: GlitchTip markeert 'm automatisch als **regressed**. Behandel als nieuwe P1 (de bug die je dacht gefixed te hebben is niet geheel weg). ### 5.2 Mark as ignored Voor P2/P3 issues die niet in deze sprint worden gefixt: klik **Ignore** met een comment die wijst naar de BACKLOG-entry of een GitHub-issue. Voorkomt dat dezelfde issue opnieuw triage-aandacht krijgt. ### 5.3 BACKLOG update Wanneer de root cause een architectural pattern aanpassing vereist (geen quick fix), maak een nieuwe BACKLOG entry met `OBS-` prefix en cross-link naar de GlitchTip issue. Volg de format van bestaande [OBS-* entries](../BACKLOG.md). --- ## §6 Audit trail Elke triage-actie laat sporen na: - **GlitchTip:** issue-state changes (resolved / ignored / regressed) met user (Bert) en timestamp. - **Git:** fix-commits met issue-link in de body indien van toepassing. - **BACKLOG.md:** entries voor architectural follow-ups. - **`runbooks/observability-erasure.md`** als de triage in een GDPR-deletion-verzoek omdraait. Voor incidents waar 1+ klant geraakt is, log een externe incident-summary (klant, impact, timeline, fix) in een aparte incident-tracker — niet in GlitchTip zelf, want GlitchTip data wordt na 90 dagen gepurged. --- ## §7 Form-builder binding failures ### Context `FormBindingApplicator` draait synchroon binnen `ApplyBindingsOnFormSubmit::handle` voor elke form-submission. Als die throwt — schema-misconfiguratie, infra-issue, data-integrity violation of deadline-timeout — vangt de listener de throw op, schrijft een `form_submission_action_failures` rij in de outer-transaction, en zet `apply_status='failed'` op de parent-submission. GlitchTip vangt het exception-event op met de `crewli-api` project-tags. De alert-rule op `apply_status=failed AND form_schema.has_public_token=true` brengt alleen de klant-zichtbare failures naar boven — organizer-driven private-form failures zijn zichtbaar in de Form Failures admin-UI zonder alert, omdat hun blast radius beperkt is tot één organisatie. Per RFC-WS-6.md §Q3 v1.3 + ARCH-BINDINGS.md §6 (two-transaction pattern) en §11 (failures lifecycle). ### 7.1 Eerste check — `failure_response_code` De submission-rij draagt een denormalised classification-token in `failure_response_code`. Vier waardes: | Token | Oorzaak | Triage-pad | |---|---|---| | `schema_config_error` | Organizer-config issue (renamed kolom, deleted target-entity, ontbrekende identity-key binding die publish-guards toch doorlieten) | Contact de organizer van het schema; **NIET retryen** — retry produceert dezelfde exception | | `temporary_error` | Infra-issue (DB-connection, lock-for-update wait, deadline-wrapper timeout) | Retry via `php artisan form-failures:retry --id={failure_ulid}` of admin-UI; als een tweede retry óók faalt, escaleer als infra-incident | | `data_integrity_error` | Data-shape violation (type-mismatch, FK-violation, soft-deleted target-entity) | Onderzoek de failing binding's `target_entity` + `target_attribute` in `form_field_bindings`; lost meestal op als een schema-config issue verkleed als data | | `unknown_error` | Iets buiten de binding-applicator hiërarchie (raw `\Throwable`, `IdentityMatchInvariantViolation`) | Triage via de GlitchTip exception-trace; dit is dev-investigation territory | De exception-class op de action-failure rij is de canonical truth; `failure_response_code` is de afgeleide classificatie die door de response-renderer gebruikt wordt. ### 7.2 Tweede check — public-token presence De GlitchTip event-tag `form_schema.has_public_token` onderscheidt: | Tag-waarde | Betekenis | Severity | |---|---|---| | `true` | Public submission flow (vrijwilliger registratie-window, public form-fill) | Customer-impact bevestigd; mobiliseer binnen minuten tijdens active festival registratie | | `false` | Organizer-driven private flow (managed crew roster, internal data import) | Blast radius beperkt tot één organisatie; onderzoek binnen uren | ### 7.3 Retry vs dismiss Gebruik het artisan-command: ```bash # Retry een enkele failure php artisan form-failures:retry --id={failure_ulid} # Retry alle open failures voor een submission php artisan form-failures:retry --submission={submission_ulid} # Dry-run eerst als onzeker php artisan form-failures:retry --id={failure_ulid} --dry-run ``` Wanneer wel retryen: - `temporary_error` — ja, bijna altijd; de deadline-wrapper of infra-hiccup is waarschijnlijk inmiddels weg. - `unknown_error` — alleen na dev-investigatie die bevestigt dat de throw transient was. Wanneer dismissen: - `schema_config_error` nadat de organizer het schema heeft gefixt en opnieuw heeft gesubmit — de originele failure-rij blijft staan als audit, dismiss met reden `schema_deleted` of `binding_removed` per het relevante geval. - `data_integrity_error` nadat dev-investigatie het herleidt tot een one-off data-state die niet meer geldt — dismiss met `data_quality_issue`. - Iedere failure voor een submission die de organizer expliciet als duplicaat geclassificeerd heeft — dismiss met `duplicate_submission`. De `DismissalReasonType` enum heeft zes cases; `OTHER` vereist een free-text note. Per ARCH-BINDINGS §11.3. ### 7.4 Severe-failure escalatie Als GlitchTip `>10` events in 1 uur toont gescoped op één `form_schema_id`, behandel als P1: 1. Acknowledge de alert in het team-channel. 2. Identificeer het schema via `form_submission.form_schema_id` op een willekeurige failed submission. 3. Pauzeer publieke toegang tot het schema tijdelijk — disable het public token tot de root-cause geïdentificeerd is. 4. Standaard incident-triage geldt verder. Dit patroon wijst bijna altijd op een publish-guard gap: een schema-configuratie die de guards hadden moeten weigeren is er toch doorheen geslipt, en elke submission ertegen produceert dezelfde exception. ### 7.5 Cross-references - [`RFC-WS-6.md`](../RFC-WS-6.md) v1.3.1 — volledige architectuur, in het bijzonder §Q3 v1.3 additions. - [`ARCH-BINDINGS.md`](../ARCH-BINDINGS.md) v1.2 — §6 two-transaction pattern, §11 failures lifecycle, §11.3 dismissal reasons. - [`observability-erasure.md`](./observability-erasure.md) — Art. 17 procedure als triage in een GDPR-deletion-verzoek omdraait. - BACKLOG `TECH-CHANNEL-AUTH-ORG-ADMIN` — bekende follow-up voor live-update channel-auth uitbreiding (org-admin scope).