Files
julian a8e808e71c Scaffold directus service planning structure
Initial commit. Establishes the .planning/ tree mirroring processor's
shape (ROADMAP.md as nav hub + per-phase folders with READMEs and
granular task files).

Six phases:

1. Slice 1 schema + deploy pipeline — what Rally Albania 2026 needs.
   Org catalog (orgs, users, vehicles, devices) + event participation
   (events, classes, entries, entry_crew, entry_devices). db-init/
   for the positions hypertable + faulty column. snapshot/apply
   tooling. Gitea CI dry-run. Dogfood seed of Rally Albania 2026.
   Nine task files with full Goal / Deliverables / Specification /
   Acceptance criteria / Risks / Done sections.

2. Course definition — stages, segments, geofences, waypoints, SLZs.
   PostGIS extension introduced here.

3. Timing & penalty tables — co-developed with processor Phase 2.
   entry_segment_starts, entry_crossings, entry_penalties,
   stage_results, penalty_formulas.

4. Permissions & policies — Directus 11 dynamic-filter Policies per
   logical role. Deployment-time work, deferred to keep early phases
   focused on the data model.

5. Custom extensions — TypeScript hooks/endpoints implementing the
   cross-plane workflows the schema implies (faulty-flag → Redis
   stream emit, stage-open materializer, etc.).

6. Future / optional — retroactivity preview UI, command-routing
   Flows, audit trails, federation rule import. Not committed.

Non-negotiable design rules captured in ROADMAP.md: schema authority
in Directus + snapshot-as-code + db-init for non-Directus DDL +
sequential idempotent migrations + entrypoint apply order + no
application logic in Flows + permissions deferred to Phase 4.

Architectural anchors point at the wiki at ../docs/wiki/ — the schema
draft, the Rally Albania 2025 source page, plus the existing
processor/postgres-timescaledb/live-channel pages. Each task file
calls out the wiki refs an implementing agent should read first.

README.md mirrors the processor service README structure: quick start,
local Docker test, prod/stage deployment notes, env vars, CI behavior.
2026-05-01 20:42:44 +02:00
..

Phase 5 — Custom extensions

Status: Not started — depends on Phase 3 (timing tables); some extensions can predate Phase 4 (permissions) Outcome: TypeScript extensions implementing the cross-plane workflows the schema implies. Each extension is small, focused, well-tested, and bundled into the image via extensions/. Together they let Directus react to operator actions in ways that ripple to processor (recompute requests) and to the SPA (live updates).

Why these are extensions, not Flows

  • Reviewable, testable, version-controlled. Extensions are TypeScript modules under extensions/, ESLint-checked, unit-tested, reviewed in PR. Flows are admin-UI configuration that round-trips through snapshots but isn't readable as code.
  • Domain logic doesn't belong in declarative orchestration. Flows shine for "on event-write, send a Slack message". They're inadequate for "on positions.faulty UPDATE, compute the affected window, emit a Redis Stream message with payload shape X."
  • Performance and correctness. Extensions run in the Directus Node process with full access to services, database, custom error handling, structured logging. Flows are higher-overhead and harder to reason about under load.

Tasks (sketched, not detailed)

# Task Notes
5.1 Faulty-flag → Redis Stream emit Hook on positions UPDATE where faulty changed. Emits a recompute:requests message with `{ device_id, ts, action: 'set'
5.2 events.discipline validation hook Pre-create / pre-update hook on entries that validates: discipline=rally → vehicle_id required; discipline=trail-run → vehicle_id null + crew is exactly one runner. Per directus-schema-draft decision.
5.3 Stage-open materialization endpoint Custom endpoint POST /stages/:id/open. Reads start_order_strategy, queries the strategy's input data, materializes entry_segment_starts rows per category. Race-director-permissioned.
5.4 CP closing-time computation Hook or scheduled task that, when the last competitor's ideal start is computed, sets time_control_closed_at on each CP geofence (Rally Albania §9.19: 60 min after last ideal).
5.5 "Copy crew from previous entry" endpoint Custom endpoint POST /entries/:id/copy-crew-from/:source_entry_id. Replaces the recipient's entry_crew rows with cloned values from the source. Per directus-schema-draft decision (no crews collection; UX shortcut).
5.6 Penalty review batch publish endpoint Custom endpoint POST /stages/:id/publish-results. Validates entry_penalties are all reviewed, writes stage_results.published_at, freezes the stage. Race-director-permissioned.
5.7 Entry registration validation Pre-create hook on entries that checks race_number is in the valid range for the vehicle's category (Rally Albania §5.4 number bands). Friendly error if violated.
5.8 Extension build pipeline extensions/ builds via pnpm build:extensions to produce the loadable extension files. Wired into the Dockerfile. CI runs the build + extension unit tests.

Open questions blocking task-level detail

  1. Hook framework choice. Directus's hook system supports filter (mutate-allowed, blocking) and action (post-event, non-blocking). Most of these tasks fit one or the other; explicit per task. Validation hooks are filter; emit-to-Redis hooks are action.
  2. Redis client in extensions. ioredis direct from inside the Directus process? Or reuse a shared service module? Phase 5 task 5.1 makes the decision.
  3. Test strategy. Vitest for unit tests; a dev Directus container + testcontainers Postgres for integration tests. Mirror the processor split.
  4. Permission interaction. Custom endpoints check permissions explicitly via accountability — verify which Phase 4 policy applies, gate the endpoint accordingly.