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.
6.5 KiB
Task 1.5 — Event-participation collections
Phase: 1 — Slice 1 schema + deploy pipeline
Status: ⬜ Not started
Depends on: 1.4
Wiki refs: docs/wiki/synthesis/directus-schema-draft.md (Event-level participation section), docs/wiki/sources/rally-albania-regulations-2025.md (§2.2–§2.5 for class taxonomy reference)
Goal
Create the per-event participation collections in the Directus admin UI: events, classes, entries, entry_crew, entry_devices. These are scoped to a single event and form the unit of timing.
Deliverables
Create the following collections via the admin UI. Field shapes per directus-schema-draft.
events
| Field | Type | Notes |
|---|---|---|
id * |
UUID | |
organization_id * |
M2O → organizations | event lives in exactly one org |
name * |
string | "Rally Albania 2026" |
slug * |
string | unique within an org |
discipline * |
string (dropdown) | enum: rally, time-trial, regatta, trail-run, hike — drives validation |
starts_at * |
timestamp | event window begin |
ends_at * |
timestamp | event window end |
regulation_doc_url |
string | external URL to the rulebook PDF/page (e.g. wiki/sources/rally-albania-regulations-2025.md) |
notes |
text |
Unique constraint: (organization_id, slug).
classes
| Field | Type | Notes |
|---|---|---|
id * |
UUID | |
event_id * |
M2O → events | classes are per-event |
code * |
string | "M-1", "C-2", "S-1", … |
name * |
string | human-readable |
description |
text | eligibility rules in plain text |
sort_order |
integer | for display ordering |
Unique constraint: (event_id, code).
entries
The unit of timing. One row per (vehicle or solo participant) registered for an event.
| Field | Type | Notes |
|---|---|---|
id * |
UUID | |
event_id * |
M2O → events | |
vehicle_id |
M2O → vehicles | nullable — null for foot races (trail-run, hike) |
team_id |
M2O → teams | nullable — for now, no teams collection in Phase 1, leave the field nullable and unwired (teams collection is Phase 2 territory if needed; per the schema draft, teams are an org-level catalog item) |
class_id * |
M2O → classes | required: every entry has a class |
race_number * |
integer | per Rally Albania §5: 1–199 moto, 2xx quad, 3xx car, 4xx SSV |
status * |
string (dropdown) | enum: registered, confirmed, started, finished, dnf, dns, dq, withdrawn |
registered_at |
timestamp | default now() |
notes |
text |
Unique constraint: (event_id, race_number) — no two entries share a race number in the same event.
Status enum semantics (from the schema draft):
registered— paid, not yet confirmed at scrutineeringconfirmed— passed scrutineering, eligible to startstarted— has begun the first stagefinished— completed all stages within MTAdnf— did not finish (started but couldn't complete)dns— did not start (confirmed but absent at start)dq— disqualified (rule violation, see Rally Albania §12.13)withdrawn— voluntary withdraw (Rally Albania §12.15 — MTA penalty for remaining stages)
teamsdeferred: Phase 1 doesn't define ateamscollection. Theteam_idfield onentriesis nullable and the FK target is intentionally unwired in Phase 1. Drop the field entirely if it complicates the snapshot — re-add in Phase 2 if a real team relationship is needed.
entry_crew (junction)
| Field | Type | Notes |
|---|---|---|
id * |
UUID | |
entry_id * |
M2O → entries | |
user_id * |
M2O → directus_users | |
role * |
string (dropdown) | enum: pilot, co-pilot, navigator, mechanic, rider, runner, hiker |
Unique constraint: (entry_id, user_id) — a user can't appear twice in the same entry's crew.
entry_devices (junction)
| Field | Type | Notes |
|---|---|---|
id * |
UUID | |
entry_id * |
M2O → entries | |
device_id * |
M2O → devices | |
assigned_user_id |
M2O → directus_users | nullable. null = vehicle-mounted; set = body-worn on this crew member |
mount_position |
string | optional free text: "panic_button_pilot", "hardwired_dash", "backup_chassis" |
Unique constraint: (entry_id, device_id) — a device can't appear twice in the same entry.
Specification
- All M2O
ON DELETE:RESTRICTby default. Cascading from event → entries is appealing but risky for audit/historical purposes — leaveRESTRICTand require explicit operator action. statusenum order matters for display. Set the dropdown's option order to match the lifecycle:registered→confirmed→started→finished→dnf→dns→dq→withdrawn.race_numberis integer, not string. Plate background color (white/yellow/green/red per Rally Albania §5.5) is derivable from the number range; not a stored field.- No permission policies yet — Phase 4 territory. Admin-only access.
- No
team_idfield if it adds complexity — the schema draft leaves teams as an org-level catalog item that's not yet defined. Phase 1 ships entries without team support.
Acceptance criteria
- All five collections exist in the admin UI with the fields listed above.
- Required fields flagged required.
- Unique constraints enforced.
- M2O relations work in the admin UI.
entries.statusdropdown shows all eight values in lifecycle order.- Manually walk through the registration: create an event → create classes → create one entry referencing a vehicle, class, and race number → add two
entry_crewrows (pilot + co-pilot) → add threeentry_devicesrows (one withassigned_user_idset, two with null). All FKs resolve. - Try to create a second entry with the same
race_numberin the same event → error. pnpm run schema:snapshotproduces a snapshot containing the new collections.- Cross-checked against the schema draft: every field that should exist does, every nullable field is nullable, every unique constraint is in place.
Risks / open questions
assigned_user_idon entry_devices — Directus represents this as an M2O. Verify the snapshot encodes the nullable / non-required nature correctly.- Cascading deletes vs RESTRICT — RESTRICT is the safe default but may make admin UX painful (you can't delete an event without first deleting all its entries, etc.). Phase 4 / Phase 5 may revisit with custom Flows that walk the dependency graph.
Done
(Fill in commit SHA + one-line note when this lands.)