docs: TRACCAR ingest + processor-ws-contract synthesis + auth-mode realignment

Catches up the wiki with several pieces of work accumulated during this
session.

INGEST: TRACCAR_MAPS_ARCHITECTURE.md
- raw/TRACCAR_MAPS_ARCHITECTURE.md (source doc, read-only).
- wiki/sources/traccar-maps-architecture.md — TL;DR + key claims +
  notable quotes + TRM divergences (PostGIS-native GeoJSON, rAF
  coalescer, Zustand, longer trail, racing sprite set).
- wiki/concepts/maps-architecture.md — distilled patterns for the SPA's
  map subsystem: singleton MapLibre + side-effect-only Map* components +
  two GeoJSON sources + style-swap mapReady gate + sprite preload + WS-
  to-map data flow (with rAF coalescer) + geofence editing + camera
  control trio.
- wiki/entities/react-spa.md — corrected the "talks exclusively to
  Directus" contradiction with [[live-channel-architecture]] (SPA
  connects to two endpoints — Directus + Processor); locked stack (raw
  MapLibre over react-map-gl, Zustand over Redux); added Auth section.
- wiki/concepts/live-channel-architecture.md — single sentence cross-
  referencing [[maps-architecture]] for consumer-side throughput
  discipline.
- index.md — Sources + Concepts entries.

SYNTHESIS: processor-ws-contract
- wiki/synthesis/processor-ws-contract.md — wire-level spec for the
  live-position WebSocket: endpoint, transport, auth handshake,
  subscribe/snapshot/streaming/unsubscribe protocol, reconnect, multi-
  instance behaviour, connection limits, versioning, open questions.
  Implementation-agnostic; the producer is cookie-name-agnostic so the
  spec doesn't pin to a specific Directus auth mode.
- index.md — Synthesis entry.

AUTH-MODE REALIGNMENT (cookie -> session)
- SPA implementation surfaced that Directus SDK 'cookie' mode doesn't
  survive a hard reload cleanly. Switched the SPA to 'session' mode
  (separate commit in trm/spa). Wiki updates here:
- wiki/entities/react-spa.md §Auth pattern — describes session mode
  (single httpOnly session cookie, no separate access token, no
  /auth/refresh dance). Added "Mode choice context" note.
- wiki/synthesis/processor-ws-contract.md §Auth handshake — emphasises
  the producer is cookie-name-agnostic; reframed "Cookie refresh while
  connected" as "Session expiry while connected".

Plus all the chronological log.md entries documenting the above plus
Phase 1.5 planning, SPA Phase 1 planning, and stage verify+seed work
from earlier in the session.

Skipped from this commit: .claude/agent-memory/* (user-local agent
state, not project content); .gitignore (already-modified by user
outside this session's scope).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-02 18:15:09 +02:00
parent 130b44778a
commit f92595a62a
8 changed files with 1144 additions and 21 deletions
+63
View File
@@ -108,3 +108,66 @@ Index updated: new source row. No new entity/concept pages created — the doc s
- New "Network exposure" subsection inside Deployment: directus is internal-only on stage / prod (`expose: 8055` not `ports:`). A reverse proxy (Traefik / Caddy / nginx) on the host or attached to `trm_default` terminates TLS and forwards the public domain to `http://directus:8055`. The asymmetry with [[tcp-ingestion]] (which must host-publish for GPS devices) is named, and the dev compose's deliberate divergence is noted.
Three CI iterations on the directus repo's first push exposed three distinct production-breaking bugs (port collision; bootstrap-before-apply ordering + silent ERROR exit; ghost-collection apply conflict). The dry-run gate caught all of them before the image touched stage. The "ghost-collection" stripping is now automated in `scripts/schema-snapshot.sh` so future captures don't regress.
## [2026-05-02] note | Stage deploy verified + Rally Albania 2026 seed landed
Stage Directus is live at `api.stage.new.trmtracking.org` and matches the local snapshot. Verification done via the `directus-stage` MCP server:
- All 12 user collections present (`organizations`, `organization_users`, `organization_vehicles`, `organization_devices`, `vehicles`, `devices`, `events`, `classes`, `entries`, `entry_crew`, `entry_devices` + custom fields on `directus_users`).
- Field shapes, types, notes, and relations identical to local. `migrations_applied` + `positions` (db-init) and `schema_migrations` (processor migration runner) tables also present, as expected.
- Composite UNIQUE constraints landed — probed `(event_id, code)` on `classes` with a duplicate `M-1` insert, got `RECORD_NOT_UNIQUE`. Confirms `db-init-post/001` + `002` ran on stage (the post-schema phase introduced during task 1.8 CI iterations).
Rally Albania 2026 dogfood seed (task 1.9) replayed against stage: 1 org (`msc-albania`), 1 event (`rally-albania-2026`, 2026-06-06 → 2026-06-13), 18 classes (M-1..M-8, Q-1..Q-3, C-1/C-2/C-A/C-3, S-1..S-3), 1 vehicle (Toyota Land Cruiser 70), 3 devices (FMB920 chassis + FMB920 dash backup + FMB003 panic). Junction rows (`organization_vehicles` ×1, `organization_devices` ×3) wired. UUIDs differ from the local seed; record of stage UUIDs lives in `trm/directus/.planning/phase-1-slice-1-schema/09-rally-albania-2026-seed.md` Done section if needed.
End-to-end registration walkthrough (`organization_users` + `entries` + `entry_crew` + `entry_devices`) deferred to manual operator pass through the admin UI — the MCP `items` tool blocks writes to core collections like `directus_users`, so the user-attaches-to-entry flow can't be MCP-driven. That manual walkthrough is the actual dogfood acceptance gate for slice-1 schema.
Drift flagged: field notes on `events.slug`, `classes.code`, and `entries.race_number` still reference "db-init/005" — those constraints moved to `db-init-post/` during the CI fix. Cosmetic only, no behavior impact; worth a snapshot-side cleanup pass next time someone touches the schema.
## [2026-05-02] ingest | TRACCAR_MAPS_ARCHITECTURE.md
Ingested the deep architectural reference for traccar-web's maps subsystem after recognising during SPA-planning discussion that Traccar already fields the exact stack we're converging on (MapLibre GL JS + GeoJSON sources + WebSocket fan-out). Created [[traccar-maps-architecture]] (source page, with TRM divergences enumerated) and [[maps-architecture]] (concept page distilling the inherited patterns: singleton map, side-effect-only `Map*` components, two-effect setup/setData split, two-source clustered+selected design, style-swap `mapReady` gate, sprite preload, rAF coalescer at the WS boundary, geofence editing via `@mapbox/mapbox-gl-draw`, three-way camera control split).
Updated [[react-spa]] heavily: appended the new source; corrected the "talks exclusively to Directus" claim that conflicted with [[live-channel-architecture]] (the SPA connects to two endpoints — Directus for business plane, Processor for telemetry firehose); locked in the stack (raw MapLibre over `react-map-gl`, Zustand over Redux, `maplibre-google-maps` adapter as optional Google-tiles path); added an Auth section documenting the same-domain cookie + reverse-proxy pattern; rewrote Real-time rendering to point at [[maps-architecture]] and headline the rAF coalescer + per-device bounded ring buffers. One sentence + cross-reference added to [[live-channel-architecture]] flagging consumer-side throughput discipline.
Headline takeaway: Traccar's frontend architecture is mostly correct — the lag the user experienced isn't the rendering layer (which is WebGL `setData` and fast) but throughput discipline (per-message Redux dispatch cascading through selectors and rebuilding feature collections at every position arrival). TRM inherits the architecture and adds an rAF coalescer at the WS boundary plus Zustand to neutralise the failure mode. Tile-source decision unblocked: Google Maps via the official Map Tiles API is legitimate through the `maplibre-google-maps` protocol adapter (bring-your-own-key, runtime-config-gated). Dogfood-day starter set: Esri World Imagery (satellite, free) + OpenTopoMap (free) + OSM raster, with Google Satellite as an optional add when an operator provides a key.
## [2026-05-02] synthesis | Processor WebSocket contract + wiki/planning drift surfaced
Wrote [[processor-ws-contract]] as the wire-level spec for the live-position WebSocket: endpoint shape, cookie-based auth handshake, subscribe/snapshot/streaming/unsubscribe protocol, reconnect semantics, multi-instance fan-out behaviour, connection limits, versioning rules. Both the SPA and the producing service will build against this page; changes require coordinated updates on both sides.
Surfaced a real wiki/planning drift while researching: [[processor]] entity page lists "Broadcast live positions" as a top-level responsibility and [[live-channel-architecture]] specifies the design, but the processor's actual planning roadmap (`trm/processor/.planning/`) has no task for it. Phase 1 (done) is throughput-only; Phase 2 is geofence/IO/timing; Phase 3 is hardening; Phase 4 only mentions a "WebSocket gateway" as an uncommitted fallback service. The drift happened because [[live-channel-architecture]] was synthesised on 2026-05-01, after Phase 1's plan had locked — the wiki absorbed the corrected design, the processor's planning didn't reconcile.
Recommendation pending user decision: add a new processor phase ("Phase 1.5 — Live broadcast") that implements [[processor-ws-contract]] inside the processor service. Alternatives are Option B (separate `trm/live-gateway` service, aligning with the old Phase 4 framing — adds a deploy unit and contradicts the wiki) and Option C (defer the live map for the dogfood — thins the SPA's value-add over Directus admin). The synthesis page is implementation-agnostic so the contract is locked regardless of which option lands.
## [2026-05-02] note | Phase 1.5 planning landed (Option A chosen)
Promoted the Processor's WebSocket broadcast endpoint to a real planning artefact. Created `trm/processor/.planning/phase-1-5-live-broadcast/` with a phase README and six task files: 1.5.1 WS server scaffold + heartbeat, 1.5.2 cookie auth handshake, 1.5.3 subscription registry & per-event authorization, 1.5.4 broadcast consumer group & fan-out, 1.5.5 snapshot-on-subscribe, 1.5.6 integration test. Each follows the existing Phase 1 task-file shape (Goal / Deliverables / Specification / Acceptance / Risks / Done) so an implementer can pick one up self-contained.
Updated `trm/processor/.planning/ROADMAP.md` with a Phase 1.5 section between Phase 1 and Phase 2, including the per-task table. Pruned the stale "WebSocket gateway for live updates" candidate from Phase 4's README and reframed it as the documented [[live-channel-architecture]] escape hatch — to be promoted to a numbered phase only when measurements justify lifting the WS endpoint out of the Processor process. Updated [[processor-ws-contract]]'s Implementation status section to reflect "planned as Phase 1.5" instead of "designed but not scheduled."
Wiki / planning drift surfaced earlier today is now closed: the wiki's [[processor]] / [[live-channel-architecture]] / [[processor-ws-contract]] design and the processor's planning roadmap agree on what gets built, where, and how it's sequenced. Implementation can start on 1.5.1 whenever; SPA work can proceed against [[processor-ws-contract]] in parallel as long as it doesn't ship to stage before Phase 1.5 lands.
## [2026-05-02] note | Auth-mode wiki realignment (cookie → session)
SPA implementation surfaced that Directus SDK's `'cookie'` auth mode doesn't survive a hard reload cleanly — the in-memory access token is gone, and `/users/me` 401s before autoRefresh can establish a new one. Switched the SPA to `'session'` mode (`authentication('session', { credentials: 'include' })`), where the session itself lives in the httpOnly cookie and the browser sends it on every request including the WebSocket upgrade. Reload survives without any client-side state.
Updated [[react-spa]] §"Auth pattern" to describe session mode (single httpOnly session cookie, no separate access token, no `/auth/refresh` dance). Added a "Mode choice context" note explaining why session mode is the right default for an SPA that needs reload-survives behaviour.
Updated [[processor-ws-contract]] §"Auth handshake" to drop the explicit "(mode: cookie)" annotation and emphasise that the producer is **cookie-name-agnostic** — it forwards the entire `Cookie` header to `/users/me` and lets Directus identify the session. The producer's implementation was already cookie-name-agnostic in practice (the 1.5.2 implementation forwards the whole header), so no processor-side code change is needed; the wiki just now matches the implementation. Reframed "Cookie refresh while connected" open question as "Session expiry while connected" with the cleaner session-mode semantics.
Processor Phase 1.5 is fully shipped (`c07ea0e` 1.5.4, `f4b50ca` 1.5.5, `2f2cf5c` 1.5.6) — six tasks, 178/178 unit tests, 6 integration scenarios. The cookie-mode language in the processor's planning task files (1.5.2 in particular) is left as-is — it's the historical spec the implementation landed against; the implementation itself is mode-agnostic.
## [2026-05-02] note | trm/spa planning landed
User created `trm/spa` repo on Gitea and seeded a minimal Vite 8 + React 19 + TypeScript 6 scaffold (App.tsx returns "SPA"). Wrote the full planning structure mirroring the conventions established by `trm/processor` and `trm/directus`.
Created in `trm/spa/.planning/`:
- `ROADMAP.md` — navigation hub with status legend, architectural anchors, eight non-negotiable design rules (singleton MapLibre, side-effect-only `Map*` components, rAF coalescer, same-origin-everything, in-memory access token, role-aware UI, runtime config, native PostGIS GeoJSON), four phases.
- `phase-1-foundation/` — README + 9 task files: 1.2 stack rounding-out (Tailwind + shadcn/ui + TanStack Router/Query + Zustand + @directus/sdk + zod + react-hook-form + Prettier), 1.3 Vite dev proxy + path aliases + tsconfig hardening, 1.4 runtime config endpoint, 1.5 Directus auth client (cookie mode + refresh + Zustand auth store), 1.6 login page, 1.7 routing skeleton (TanStack Router file-based + role-aware guards), 1.8 logout flow (with cross-tab sync), 1.9 Gitea CI + Dockerfile + nginx static serve, 1.10 compose service block in `trm/deploy`.
- `phase-2-live-map/README.md` — sketched task table for the live-monitoring map; depends on processor Phase 1.5 landing. Nine tasks: MapLibre singleton, tile-source switcher, sprite preload, WS client + rAF coalescer + Zustand store, MapPositions, MapTrails, event picker, camera control trio, connection-status indicators.
- `phase-3-dogfood-readiness/README.md` — error boundaries, connection-state UI, mobile-responsive baseline, per-device detail panel, empty/loading-state polish, Vitest setup, production logging, visual brand pass.
- `phase-4-future/README.md` — geometry editor (depends on directus Phase 2), replay mode, heatmaps / deck.gl, i18n (Albanian), dark mode, Playwright E2E, leaderboard, spectator-facing public map, notifications, operator chat. None committed.
Each task file follows the existing Goal / Deliverables / Specification / Acceptance / Risks / Done shape so an implementer agent can pick one up self-contained. Phase 1 sequencing: 1.2 → 1.3 → 1.4 → 1.5 → (1.6 ‖ 1.7) → 1.8, with 1.9+1.10 (deploy plumbing) developable in parallel after 1.3 lands.
End state of Phase 1: a deployable empty shell — auth + protected routes + login/logout + CI + compose deploy block. End state of Phase 2: the dogfood-day deliverable. End state of Phase 3: actually fielded for race operators on race day, not just a tech demo.