a8e808e71c
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.
42 lines
4.2 KiB
Markdown
42 lines
4.2 KiB
Markdown
# Phase 4 — Permissions & policies
|
||
|
||
**Status:** ⬜ Not started — depends on Phases 1–3 (all collections must exist before policies are drafted)
|
||
**Outcome:** Every collection × action combination has an explicit Directus 11 Policy attached. Multi-tenant isolation is enforced by Directus's dynamic-filter mechanism, not by application code. Operators see only data scoped to orgs they belong to, with the actions allowed by their role. Non-admin users can register entries, view live tracking, review their own results — all without ever needing admin role.
|
||
|
||
## Why this is a separate phase
|
||
|
||
- **Premature policy commitment is expensive.** Defining policies before the data model has shaken out leads to filters that break when a collection's shape changes. Phases 1–3 get one to two iterations on the schema; Phase 4 lands when the model is stable.
|
||
- **Policy filters are tedious but not architectural.** This is admin-UI configuration work, not design. Roughly 5 roles × ~20 collections × 4 CRUD actions = ~400 (role × collection × action) cells, most of which are templated repeats of "user is in this org via `organization_users`".
|
||
- **Testable as a unit.** End-state: a non-admin test user with `participant` role can perform exactly the operations they should, and zero others. Phase 4's CI / dogfood verification is a permission-boundary test suite.
|
||
|
||
## Roles to support (per [[directus-schema-draft]])
|
||
|
||
| Role | Power |
|
||
|---|---|
|
||
| `org-admin` | Full CRUD within their org. Can manage `organization_users`, classes, events. |
|
||
| `race-director` | Manage entries, segments, geofences, penalties for events in their org. Approve / publish stage results. Cannot create new orgs. |
|
||
| `marshal` | Read-only on most collections; can flag faulty positions and write notes on entries during the event. Time-limited (only during active event). |
|
||
| `timekeeper` | Edit `entry_segment_starts.target_at` (late-arrival reseeding); read all entries; cannot modify penalties. |
|
||
| `participant` | Read-only on entries they appear in (via `entry_crew`); read on the events they're registered for; no writes. |
|
||
| `viewer` | Read-only on public-facing event data (live map, published results). Lowest privilege; default for any user not otherwise scoped. |
|
||
|
||
## Tasks (sketched, not detailed)
|
||
|
||
| # | Task | Notes |
|
||
|---|------|-------|
|
||
| 4.1 | Draft the canonical "user is in this org with this role" filter expression | One JSON filter that gets reused. Lives in a template / snippet for copy-paste. |
|
||
| 4.2 | `org-admin` policy | All CRUD on org-scoped collections, scoped via the canonical filter. |
|
||
| 4.3 | `race-director` policy | CRUD on events / entries / classes / penalties for events in their org. |
|
||
| 4.4 | `marshal` policy | Field-level write on `positions.faulty`; entry notes; otherwise read-only. |
|
||
| 4.5 | `timekeeper` policy | Field-level write on `entry_segment_starts.target_at` and `manual_override`; otherwise read-only. |
|
||
| 4.6 | `participant` policy | Filter on entries via `entry_crew.user_id = $CURRENT_USER`. |
|
||
| 4.7 | `viewer` policy | Public read on a curated subset (live positions for active events, published `stage_results`). |
|
||
| 4.8 | Snapshot regeneration + CI verification | All policies round-trip via `directus schema snapshot` (verify the format is faithful — Directus's policy serialization has historically been finicky). |
|
||
| 4.9 | Permission-boundary test suite | Custom test that creates a user per role, attempts a series of CRUD operations, asserts allowed/denied per a fixture. Runs in CI alongside the dry-run. |
|
||
|
||
## Open questions blocking task-level detail
|
||
|
||
1. **Marshal time-limiting.** Marshal access tied to "during active event" — does Directus's dynamic filter support time-bounded conditions natively, or does this need a custom hook (Phase 5)?
|
||
2. **Field-level vs row-level restrictions.** Some collections (`positions`, `entry_segment_starts`) need field-level write restrictions (only one column writable). Verify Directus 11 supports field-level policies in the dynamic-filter mechanism, or fall back to a hook that rejects writes to other fields.
|
||
3. **Snapshot fidelity.** Does `directus schema snapshot` faithfully capture all policy filter JSON? If not, policies might need to live in a separate seed script applied alongside the snapshot.
|