docs: RFC-WS-7 observability foundation (GlitchTip)
Two charter amendments from the original WS-7 brief: - Sentry -> GlitchTip (self-hosted, protocol-compatible). Same Sentry SDKs on backend (sentry-laravel) and frontend (@sentry/vue), pointed at a self-hosted GlitchTip DSN. Avoids Sentry SaaS pricing and keeps event data on infrastructure Bert controls. - Performance monitoring out of scope (errors-only). WS-7 delivers exception capture + alerting + scrubbing + RBAC only. APM/tracing/spans deferred to a later workstream if ever needed; pre-launch with no users, the cost/benefit doesn't justify it now. RFC-as-first-commit pattern (per WS-6) so the scope-alignment document is in main before any infra/code changes land.
This commit is contained in:
223
dev-docs/RFC-WS-7-OBSERVABILITY.md
Normal file
223
dev-docs/RFC-WS-7-OBSERVABILITY.md
Normal file
@@ -0,0 +1,223 @@
|
||||
# RFC-WS-7 — Observability foundation (GlitchTip)
|
||||
|
||||
**Status:** Approved — implementation-ready
|
||||
**Workstream:** WS-7 (consolidatie-sprint mei 2026)
|
||||
**Voorgangers:** ARCH-CONSOLIDATION-2026-04 §3 besluit 8, §6.7 — ARCH-CONSOLIDATION-ADDENDUM-2026-04-24 D-06
|
||||
**Opvolgers:** ARCH-API-VALIDATION (geblokkeerd door WS-7), WS-8b (`ARCH-OBSERVABILITY.md`)
|
||||
**Doorlooptijd:** 4-5 dagen (charter zei 2-3; revisie wegens PII-scrubbing scope)
|
||||
|
||||
---
|
||||
|
||||
## 1. Doel
|
||||
|
||||
Crewli draait in productie zonder geautomatiseerde error-detectie. Stack traces alleen via SSH+grep; frontend errors onzichtbaar tenzij gerapporteerd; geen release-correlatie of alerting. WS-7 levert die foundation.
|
||||
|
||||
---
|
||||
|
||||
## 2. Charter-amendementen
|
||||
|
||||
Twee afwijkingen van charter §3 besluit 8, beide bewust:
|
||||
|
||||
**A. Sentry → GlitchTip.** Self-hosted = data binnen perimeter (Crewli verwerkt special category data: dietary, medical, accreditation passport — geen externe processor toevoegen aan het processing register). GlitchTip implementeert het Sentry-event-protocol; `sentry-laravel`, `@sentry/vue`, `@sentry/cli` werken zonder modificatie. Switchen naar Sentry SaaS later is mogelijk zonder app-wijzigingen.
|
||||
|
||||
**B. Performance monitoring uit scope.** Charter zei "Sentry Performance in prod". Deze RFC scope = errors-only. Performance-tracing pakken we later op wanneer er een concrete vraag ontstaat.
|
||||
|
||||
---
|
||||
|
||||
## 3. Architectuur
|
||||
|
||||
### 3.1 Hosting
|
||||
|
||||
Self-hosted GlitchTip op productie VPS via Docker Compose (`glitchtip-web`, `glitchtip-worker`, `glitchtip-postgres`, `glitchtip-redis`). Reverse proxy via DirectAdmin Apache; SSL via DirectAdmin Let's Encrypt op `errors.crewli.app` (consistent met bestaande subdomain-pattern).
|
||||
|
||||
### 3.2 Twee projecten / DSNs
|
||||
|
||||
- `crewli-api` — Laravel
|
||||
- `crewli-app` — apps/app SPA (single-SPA na WS-3)
|
||||
|
||||
Twee aparte projecten omdat issue-ownership (frontend vs backend) helder moet zijn én omdat scrubbing-rules per project verschillen.
|
||||
|
||||
Binnen `crewli-app` werken we met een runtime context-split via `beforeSend`-hook: routes onder `/p/*` (token-based, artist/supplier/press) krijgen strictere scrubbing en geen user-context. Organizer-routes en `/platform/*` krijgen volledige auth-context. Eén SDK, twee zones.
|
||||
|
||||
### 3.3 Environment gating
|
||||
|
||||
Lege DSN = SDK no-op (bevestigd voor zowel `sentry-laravel` als `@sentry/vue`). Geen runtime-check in app-code nodig.
|
||||
|
||||
```
|
||||
# .env.development → DSNs leeg
|
||||
# .env.staging → DSNs gevuld, GLITCHTIP_ENVIRONMENT=staging
|
||||
# .env.production → DSNs gevuld, GLITCHTIP_ENVIRONMENT=production
|
||||
```
|
||||
|
||||
### 3.4 Release identifier
|
||||
|
||||
Format `<app>@<short-sha>` (`crewli-api@f41951a`, `crewli-app@f41951a`). Bron: `git rev-parse --short HEAD` als build-time env var, geïnjecteerd in `deploy.sh` per app-build.
|
||||
|
||||
### 3.5 Source maps
|
||||
|
||||
`vite build` produceert sourcemaps → `@sentry/cli sourcemaps upload` push naar GlitchTip → `.map` bestanden **verwijderd uit `dist/`** vóór nginx ze serveert. Geen public-mapped sources op productie. Per-project upload-only auth-token in deploy environment.
|
||||
|
||||
### 3.6 Context tagging
|
||||
|
||||
| Tag | API | apps/app |
|
||||
|---|---|---|
|
||||
| `release` | altijd | altijd |
|
||||
| `environment` | altijd | altijd |
|
||||
| `app` | `api` | `app` |
|
||||
| `route_name` | `Route::currentRouteName()` | `route.name` |
|
||||
| `http.method` | altijd | n.v.t. |
|
||||
| `organisation_id` (ULID) | wanneer auth+scope gebound | uit auth store |
|
||||
| `event_id` (ULID) | wanneer event-scoped | wanneer applicabel |
|
||||
| `user_id` (ULID) | `auth()?->id()` | uit auth store, alleen session-mode |
|
||||
| `actor_type` | `organizer_admin` / `super_admin` / `portal_token` / `volunteer` / etc. | mirror |
|
||||
| `impersonation.active` | bool | n.v.t. |
|
||||
| `impersonation.impersonator_user_id` | wanneer actief | n.v.t. |
|
||||
| `queue.attempt` | binnen job-context | n.v.t. |
|
||||
|
||||
**Nooit als tag:** email, telefoon, naam, IP-adres, raw form_value content, raw cookie content.
|
||||
|
||||
Multi-tenant invariant: élke captured event uit een geauthenticeerde controller MOET `organisation_id` hebben. Een unit-test verifieert dit — als `organisation_id` ontbreekt op een geauthenticeerd path, faalt de test.
|
||||
|
||||
### 3.7 PII scrubbing
|
||||
|
||||
**Backend (`before_send` in sentry-laravel):**
|
||||
1. Request body keys (recursief, key-name match): `password`, `password_confirmation`, `current_password`, `token`, `api_key`, `secret`, `webhook_secret`, `dsn`, `signature`, `authorization`, `cookie`, `bearer`, `iban`, `bic`, `passport_number`, `bsn`.
|
||||
2. Headers: `Authorization`, `Cookie`, `Set-Cookie`, `X-API-Key`, `X-Impersonation-Token`.
|
||||
3. Form submissions: élke payload met `form_values` → recursive value-replace met `[scrubbed]`. Form values zijn definitionally PII.
|
||||
4. URL query string: scrub `?token=...`, `?api_key=...`.
|
||||
5. `send_default_pii = false` globaal (strips locals uit stack traces).
|
||||
|
||||
**Frontend (`beforeSend` in @sentry/vue):**
|
||||
1. Cookies via `document.cookie` exposure: scrub.
|
||||
2. localStorage / sessionStorage: nooit in event context (portal-state in sessionStorage MAG NIET lekken).
|
||||
3. **Mask-all op input breadcrumbs.** Sentry default maskt alleen passwords; wij masken alle `<input>` values via breadcrumb-integratie config. Crewli heeft email/phone/IBAN/dietary/medical als plain `<input>` — selectief masken is niet veilig genoeg.
|
||||
4. URL query string: zelfde patronen.
|
||||
5. Console messages: `Sentry.consoleLoggingIntegration` uitgeschakeld in productie.
|
||||
|
||||
**Verificatie — verplicht in CI:**
|
||||
- `tests/Feature/Observability/PiiScrubbingTest.php` — mock transport, assert dat sensitive keys gescrubd zijn.
|
||||
- `apps/app/src/__tests__/sentry-scrub.test.ts` — assert form-input breadcrumb-scrubbing en `/p/*` route context-strip.
|
||||
|
||||
Tests blokkeren merge als scrubbing regresseert. Geen "manuele inspectie" als verificatie.
|
||||
|
||||
### 3.8 User context
|
||||
|
||||
`Sentry::setUser([...])`: `id` = ULID, `username` = ULID (duplicate, nooit email), `ip_address` = `null`. `send_default_pii = false`.
|
||||
|
||||
### 3.9 Sampling
|
||||
|
||||
Errors 100% (self-hosted, geen kostenreden). Performance/Profile niet van toepassing (uit scope per §2 amendement B). Wel een rate-limit op event-volume per project (GlitchTip default 10k/min/project) tegen runaway-errors.
|
||||
|
||||
### 3.10 Boundary met bestaande systemen
|
||||
|
||||
GlitchTip captureert programmer errors en infrastructure failures. Het captureert **niet** verwachte business-uitkomsten (failed payment, failed identity match, dead-letter webhook delivery — die hebben eigen audit-tabellen). Bestaande systemen blijven onaangeroerd: Telescope (dev), activity_log (audit trail), impersonation_audit_logs (security audit), form_webhook_deliveries (delivery audit), Laravel default log (operationeel).
|
||||
|
||||
Concreet voor webhooks: `form_webhook_deliveries.dead_letter` is de audit-record. GlitchTip vuurt alleen bij programmeerfouten (TypeError, missing config), niet wanneer receiver een 500 retourneert.
|
||||
|
||||
### 3.11 Queue-worker integratie
|
||||
|
||||
Laravel SDK auto-captureert vanuit queue jobs. Job-attempt-number als tag (`queue.attempt`) voor "issues die uiteindelijk slagen" vs "altijd falend". Stack-trace-grouping (default) is correct gedrag voor idempotente retries — niet per attempt fingerprinten.
|
||||
|
||||
### 3.12 Alerting
|
||||
|
||||
Initieel: email-naar-Bert op nieuwe issue / regression / spike (>10 events in 5min, tune na eerste week). Slack-integratie naar BACKLOG.
|
||||
|
||||
### 3.13 Structured logging conventie (charter §6.7)
|
||||
|
||||
`BindRequestLogContext` middleware op de `api` route group:
|
||||
|
||||
```php
|
||||
Log::withContext([
|
||||
'request_id' => $request->header('X-Request-Id') ?? Str::ulid(),
|
||||
'organisation_id' => $request->user()?->organisation_id,
|
||||
'user_id' => $request->user()?->id,
|
||||
'route' => $request->route()?->getName(),
|
||||
]);
|
||||
```
|
||||
|
||||
`request_id` retour als response header `X-Request-Id`. apps/app axios interceptor genereert client-side een ULID per request en stuurt mee als header; server respecteert headerwaarde. Dit geeft front-to-back correlation in één klik vanuit GlitchTip-UI naar log-segment op de host.
|
||||
|
||||
### 3.14 Activity_log indexes (addendum D-06)
|
||||
|
||||
`(subject_type, subject_id)` en `(causer_type, causer_id)` composite indexes op `activity_log`. Infrastructure-housekeeping; geen functionele wijziging.
|
||||
|
||||
---
|
||||
|
||||
## 4. Privacy / GDPR
|
||||
|
||||
**Data na scrubbing:** alleen ULIDs, stack traces, route-namen, gecureerde tags, request_id. Scrubbing per §3.7 is de primaire control; tests in §3.7 zijn merge-blockers.
|
||||
|
||||
**Controller / processor:** Self-hosted op Crewli-infra → Crewli is controller, niet processor. Geen DPA-uitbreiding.
|
||||
|
||||
**Right to erasure (Art. 17):** Zoek events op `user.id = <ULID>`, verwijder via GlitchTip-API of directe SQL op glitchtip-postgres. Initieel handmatig; geautomatiseerd erasure-script naar BACKLOG.
|
||||
|
||||
**Retention:** 90 dagen, daarna purged.
|
||||
|
||||
**Processing register-entry:** Crewli Error Tracking (GlitchTip) — defectdetectie en service-availability monitoring — pseudonieme identifiers + technische metadata — recipient: Bert — retention 90 dagen — TLS in transit, full-disk encryption at rest, SSH-key + 2FA op web-UI.
|
||||
|
||||
**SECURITY_AUDIT.md update** in PR-4: GlitchTip als processing entry, scrubbing-config als security control, erasure-procedure als GDPR-runbook.
|
||||
|
||||
---
|
||||
|
||||
## 5. Vastgelegde defaults
|
||||
|
||||
Geen open vragen meer; deze zijn vastgesteld:
|
||||
|
||||
- DirectAdmin Let's Encrypt voor `errors.crewli.app` (consistent met bestaande subdomains).
|
||||
- Retention: 90 dagen.
|
||||
- Email-alerting initieel; Slack naar BACKLOG.
|
||||
- Mask-all op frontend input breadcrumbs.
|
||||
- 2FA verplicht op GlitchTip web-UI.
|
||||
- Daily postgres-backup naar zelfde target als Crewli main-DB.
|
||||
- Capture failures-to-write-activity-log (silent failures zijn slecht).
|
||||
- Impersonation: zowel impersonator als target user_id getagd (per §3.6 tabel).
|
||||
- Big-bang structured logging: `BindRequestLogContext` middleware in PR-2, geen call-site-wijzigingen.
|
||||
- Mock transport voor scrubbing-tests (geen externe afhankelijkheid in CI).
|
||||
- Source-map upload-token in deploy `.env` op productie-host (later naar 1Password wanneer CI komt).
|
||||
|
||||
---
|
||||
|
||||
## 6. Acceptance criteria
|
||||
|
||||
WS-7 is compleet wanneer:
|
||||
|
||||
1. GlitchTip draait op `errors.crewli.app` met TLS, alleen toegankelijk voor Bert (2FA aan).
|
||||
2. Twee projecten aangemaakt; DSNs in vault.
|
||||
3. Laravel SDK geïntegreerd; errors uit prod-API verschijnen <60s.
|
||||
4. apps/app SDK geïntegreerd; errors verschijnen met org/user/release context. `/p/*` routes hebben strictere scrubbing en geen user-context.
|
||||
5. Source-maps upload werkt; leesbare stack traces in UI; `.map` bestanden afwezig in publieke bundle.
|
||||
6. PII scrubbing-tests groen (backend + frontend).
|
||||
7. Smoke test: induced 500 in staging, verifieer dat hij verschijnt met alle verwachte tags én geen PII lekt.
|
||||
8. ARCH-OBSERVABILITY.md geschreven (WS-8b).
|
||||
9. Email-alerting geconfigureerd; getest met sample issue.
|
||||
10. Retention-policy (90 dagen) toegepast.
|
||||
11. Daily postgres-backup-script in place.
|
||||
12. Activity_log indexes (addendum D-06) gemigreerd.
|
||||
13. Structured logging conventie geïmplementeerd; `X-Request-Id` round-trip getest.
|
||||
14. SECURITY_AUDIT.md bijgewerkt.
|
||||
|
||||
---
|
||||
|
||||
## 7. Deliverables (4 PRs, `--no-ff` per CLAUDE.md)
|
||||
|
||||
| PR | Inhoud |
|
||||
|----|---|
|
||||
| **PR-1: Infra** | docker-compose.glitchtip.yml, errors.crewli.app DNS + TLS, twee projecten aangemaakt, DSNs in vault, daily-backup script |
|
||||
| **PR-2: Backend SDK + structured logging** | sentry-laravel install + config + scrubbing + context tagging, `BindRequestLogContext` middleware, `X-Request-Id` round-trip, PII scrubbing test, activity_log indexes (D-06) |
|
||||
| **PR-3: Frontend SDK** | @sentry/vue install + config + context tagging + scrubbing test + `/p/*` runtime-split + sourcemap upload-step in deploy.sh |
|
||||
| **PR-4: Docs + WS-8b** | ARCH-OBSERVABILITY.md, runbook (triage + erasure + scrubbing-tuning), SECURITY_AUDIT.md update |
|
||||
|
||||
WS-7 closure = alle 4 PRs gemerged + acceptance criteria 1-14 afgevinkt.
|
||||
|
||||
---
|
||||
|
||||
## 8. Verwijzingen
|
||||
|
||||
- ARCH-CONSOLIDATION-2026-04 §3 besluit 8 — originele Sentry-keuze (deze RFC wijzigt naar GlitchTip).
|
||||
- ARCH-CONSOLIDATION-2026-04 §6.7 — originele scope WS-7.
|
||||
- ARCH-CONSOLIDATION-ADDENDUM-2026-04-24 D-06 — activity_log indexes.
|
||||
- AUTH_ARCHITECTURE.md — per-app cookie naming, impersonation flow.
|
||||
- SECURITY_AUDIT.md — bestaande audit-posture (te updaten in PR-4).
|
||||
- BACKLOG.md — entries voor automated-erasure script, Slack alerting (post-WS-7).
|
||||
- GlitchTip docs: https://glitchtip.com/documentation
|
||||
- GlitchTip self-hosting: https://glitchtip.com/documentation/install
|
||||
Reference in New Issue
Block a user