On this page
Architecture decision records
These records explain why CommonLayer is built the way it is. ADR-0001 to ADR-0016 are the key decisions of the implementation plan (D1–D16); the later records capture decisions made while building, as recorded in the work-package status files.
Every record has the same short format: status (what was decided and how far it is implemented), context, decision and consequences. Where the implementation differs from the original plan, the record says so. A record is never rewritten to change its decision; a later record supersedes it. When later events changed a record's status or consequences (for example a test that has since run), the record carries a dated Amendment section at the end and an updated status line.
Plan decisions
| ADR | Decision | Status |
|---|---|---|
| 0001 | Modular monolith with thin binaries | Implemented; every domain composed, REST and gRPC (amended) |
| 0002 | Vertical domain crates, ports and the event outbox | Implemented, with recorded deviations |
| 0003 | SQLx with runtime-checked queries and real-database tests | Implemented |
| 0004 | PostgreSQL is the job queue and the outbox; Valkey is optional | Implemented; throughput not load-tested (amended) |
| 0005 | Append-only double-entry ledger with database-enforced balances | Implemented; runtime role since WP-70a (amended) |
| 0006 | RBAC with one static role-to-permission matrix | Implemented; human-only set added (amended) |
| 0007 | REST with generated OpenAPI; gRPC for machine clients | Implemented: OpenAPI document and gRPC (amended) |
| 0008 | Server-side sessions, hashed tokens, JWTs only for the registry | Implemented |
| 0009 | CNCF Distribution as registry; the platform issues tokens | Implemented |
| 0010 | Cosign, Trivy and Grype as CLIs; OSV over HTTP | Implemented; key-pair path ran in the publish drill; keyless never run (amended) |
| 0011 | Images from verified upstream source on a from-scratch root | Implemented for linux/amd64; arm64 never built (amended) |
| 0012 | Next.js App Router served same-origin with the API | Implemented; trusted-proxy bullet superseded by 0040 (amended) |
| 0013 | protox compiles protobuf in build.rs | Implemented with the gRPC service definitions (amended) |
| 0014 | Money is i64 minor units plus a currency code | Implemented |
| 0015 | Highly available PostgreSQL through CloudNativePG | Failover test passed once on a GitHub runner; its moved hook form failed next (amended) |
| 0016 | Apache-2.0 for platform, template, charts and build configuration | Applied |
Decisions made during implementation
| ADR | Decision | Source |
|---|---|---|
| 0017 | Project categories follow the manifests | WP-10 |
| 0018 | commonlayer-observability is an allowed dependency of domain crates | WP-11, 12, 14, 15 |
| 0019 | The global rate limit runs after authentication (no longer fails open: per-process fallback, amended) | WP-02, WP-70a |
| 0020 | BusyBox is rebuilt with network applets compiled out | WP-20 |
| 0021 | PostgreSQL uses the ICU locale provider by default | WP-22 |
| 0022 | One integration-test binary per crate | WP-01 |
| 0023 | An authentication gate answers 401 before body parsing | WP-02 |
| 0024 | A workload principal for GitHub Actions OIDC submissions (wired, amended) | WP-01, WP-11, WP-40 |
| 0025 | One SSRF-safe client for user-influenced outbound HTTP | WP-01 |
| 0026 | Signing identities are globs over the workflow path | WP-20, WP-11 |
| 0027 | Referrer discovery without the OCI referrers API (amended) | WP-11 |
| 0028 | Explicit verification waivers; some checks never waivable | WP-11 |
| 0029 | SLA deadlines are permanent facts in their own table | WP-12 |
| 0030 | Embargoed findings never change the public security state | WP-10 |
| 0031 | The ledger books the amount a provider confirms | WP-14 |
| 0032 | Payout gates are identical for simulated and live runs | WP-14 |
| 0033 | Eligibility evaluations are re-derivable and time-scoped | WP-13 |
| 0034 | The base image ships its OpenVEX statements | WP-20 |
| 0035 | Approval decisions are human-only; service accounts never hold them | WP-70a, WP-70b (review HIGH-2) |
| 0036 | The processes connect as a least-privilege runtime database role | WP-70a, WP-70g |
| 0037 | A second person confirms high-impact not_affected statements | WP-70a |
| 0038 | Runtime settings are a closed, typed registry of policy values | WP-70a |
| 0039 | Source-built components are declared in the SBOM; their scan coverage rests on Grype | WP-70f |
| 0040 | A forwarded client address is honoured only behind a declared proxy | WP-70g, WP-70e |
| 0041 | An ambiguous payout stays unknown and keeps its idempotency key | WP-70b (review HIGH-4) |
| 0042 | Catalog entries stay in_development until signed artifacts are released | WP-70f |
| 0043 | One Playwright configuration against the portal on port 3000 | WP-70e |
| 0044 | Security workflows probe for code scanning and fall back to artifacts | WP-70d |
| 0045 | The proposer never approves a reward, and a contribution is rewarded once | WP-70b (review HIGH-1) |
| 0046 | Repositories stay with their maintainers: App connection, managed access, source backups, succession | WP-80 |
| 0047 | Onboarding a project is connecting its repository, and one rule engine checks it | WP-80 |
| 0048 | CI spends runner time on the newest commit and on what changed | coordinator |
| 0049 | Every project is its own repository: first-party trees carry the template's .github/ and schema, own tags and signing identity; the platform only checks exported trees | WP-83 |
Writing a new record
Copy the shape of an existing record, take the next number, and link it from this table. Record the decision when it is made, including the alternatives that were rejected and what the decision costs.