Architecture decision records
Architecture decision records
One decision per file, each in Status / Context / Decision / Consequences form. A record is amended by a later record that supersedes it, never edited to change its meaning; corrections of fact land in the evolution log.
| Record | Decision | Status |
|---|---|---|
| 0001 | Thin-core architecture — one Go binary owning state, sequencing, keys, and recovery; everything else a pinned subprocess | Accepted |
| 0002 | Project name exocomp, after the Control D ctrld collision ruled out the working name |
Accepted |
| 0003 | Secrets envelope: age recipients → DEK → HKDF subkeys, with a recovery kit that closes the credential loop | Accepted |
| 0004 | OpenTofu HTTP state backend implemented in SQLite, sidestepping the S3 conditional-write gap | Accepted |
| 0005 | Job sandboxing via nono's enforcing tier; jobs are denied every powerful local socket |
Accepted |
| 0006 | uv build-time Python toolchain plus a doctor-gated operator escape hatch in the data volume | Accepted |
| 0007 | Audit hash chain deferred; revival requires an off-host-anchored head | Accepted |
| 0008 | Multi-step choreography via a step ledger on the job store; DBOS deferred | Accepted |
| 0009 | LAN listeners require locally-minted bearer tokens; TLS by default | Accepted |
| 0010 | Git-centric source model; deploy/fleet keys; state-only vs run-in-server workspaces | Accepted |
| 0011 | Project deps from root requirements.txt/pyproject.toml, uv-resolved into hash-gated caches; uv ships at runtime | Accepted |
| 0012 | Litestream as an optional fast-RPO sidecar replicating exocomp.db's WAL; restic stays authoritative; unencrypted-remote refused without ack; sidecar not embedded | Accepted |
| 0013 | OpenBao Transit as an optional additive seal for the DEK (auto-unseal); keyring.age recovery preserved; unreachable is an explicit error, never silent fallback | Accepted |
| 0014 | Agent-facing surface: one core, three thin faces (CLI --output json, HTTP /api/v1, MCP) sharing Decision 0009 auth, the one queue, and the internal/wire envelope contract | Accepted |
| 0015 | Ansible collections go per-source: content-hash-keyed cache dir, resolved at claim time, GC'd with the source lifecycle; Decision 0011's shared collections decision superseded | Accepted |
| 0016 | exocomp ships as a self-contained base image that is also an artifact carrier; uv is bootstrapped when absent, ansible is PATH-delegated with PYTHONPATH, and an operator's image is captured for recovery |
Accepted |
| 0017 | One internal/probe vocabulary for checks and gates; checks gain dns/tls/metric; the SSRF guard is per-face and Location is opt-in per kind |
Accepted |
| 0018 | Opt-in aggregate check events carrying the present host set, with --limit-hosts expansion governed by the trusted-source line |
Superseded by 0019 |
| 0019 | Aggregate check events, event.hosts, and --limit-hosts removed; per-host events are simpler, and migration 0041 stays as inert schema |
Accepted |
| 0020 | Reload first, restart after drain, recover interrupted jobs; schedules reload without a restart | Accepted |
| 0021 | Keep the host scope write-only; the request response itself is the polling surface | Accepted |
| 0022 | Withdraw the v1.0 label; the release line is pre-1.0 from v0.1.0, and release-please owns later versions |
Accepted |
Some decisions are recorded elsewhere because they are shape rather than choice: the platform support tiers are in architecture/platform-contract.md.