Skip to content

Decision records (ADRs)

Every significant architectural call is recorded as an ADR in docs/adr/ in the platform repository — the why behind the design, kept honest by marking superseded decisions instead of rewriting history. Check the relevant ADR before proposing an alternative to something already settled; if a decision looks wrong for what you’re building, propose a new ADR rather than quietly working around it.

ADR Decision Status
0001 App-level multi-competition tenancy (a competition_id on every scoped table), not schema-per-competition Accepted
0002 Kernel / required-core / optional-module split — nearly everything is a module Accepted
0003 JWT access + refresh tokens, one scheme shared by REST and WebSockets Accepted
0004 Roles and permissions as data, not a hardcoded enum Accepted
0005 An async in-process pub/sub event bus as the mutation-notification core Accepted
0006 pytest (SQLite-backed, infra-free) + Vitest Accepted
0007 First registered user becomes Administrator Superseded by 0010
0008 Refresh tokens are stateful, hashed, rotating DB sessions Accepted
0009 Synchronous event dispatch in Tier 0 Superseded by 0012
0010 Seeded default administrator credentials Superseded by 0017
0011 Theming is site-wide only; per-competition theming deferred Accepted
0012 Event dispatch splits into sync-critical and background lanes Accepted
0013 Webhook egress policy — SSRF blocklist, header stripping, value hardening Accepted
0014 CRDT transport as a dumb relay with client-snapshot persistence Accepted
0015 Username is the primary identifier; email is optional Accepted
0016 Platform export/import — registry-driven, additive backup Accepted
0017 First-run setup wizard; no seeded default admin Accepted
0018 Regex flag evaluation contained against ReDoS Accepted
0019 Per-install derived JWT secret — never a repo-public default Accepted
0020 Hash what is only verified; encrypt what must be retrieved Accepted
0021 External identity via OIDC, with local login as break-glass Accepted
0022 SAML and LDAP identity providers — generalizing the provider model Accepted
0023 AI assistant provider integration and execution model Accepted
0024 Built-in SSO provider presets — configuration, never credentials Accepted
0025 Multi-worker via a Redis broadcast relay behind the connection manager Accepted
0026 Cross-worker presence via heartbeat-TTL liveness in Redis Accepted
0027 Certificate rendering — server-side Pillow → PNG, not a headless browser Accepted
0028 Competition status is an explicit gameplay gate; the schedule drives it Accepted
0029 Frontend i18n — next-intl with a cookie locale, translations in Crowdin Accepted
0030 Post-event report rendering — HTML + WeasyPrint, not Pillow or a headless browser Accepted
0031 Multi-instance deployment behind a load balancer (Fargate/ALB) Accepted
0032 Multi-tenant Entra issuer validation via a tenant-substituted issuer template Accepted
0033 A generic oauth2 provider kind, with userinfo as the identity source Accepted
0034 Custom pages store rich text and render as a React tree, never as HTML Accepted
0035 Relicense from AGPL-3.0 to Apache 2.0; module exception retired Accepted
0036 Challenge instancing — provisioner kinds, background-lane lifecycle, hashed unique flags Accepted
0037 Prometheus /metrics — kernel middleware, off by default, gated by token/allowlist Accepted

Two threads are worth reading in sequence to see the project’s decision style: the bootstrap story (0007 → 0010 → 0017 — from “first user wins” to seeded credentials to a proper setup wizard) and the event-dispatch story (0005 → 0009 → 0012 — from a simple bus to explicitly split delivery lanes once automations needed slow external calls).