ADR-0003: Go operator with the App CRD as the single source of truth¶
Status: Accepted — 001-walking-skeleton (2026-06) Principles served: IV, V Source: specs/001-walking-skeleton/research.md D-1, D-2; docs/design.md D5; .specify/memory/constitution.md Principle IV
Context¶
The Tend operator owns the app lifecycle and is the largest custom build item — the actual product core. It needs a real state machine (sandbox, promotion-pending, production, frozen, retired), CRD types, and admission integration. Every platform behavior — provisioning, policy enforcement, lifecycle, cost attribution, estate views — must reconcile from one authoritative representation of each app, or the governance plane silently rots through divergent state.
Decision¶
The operator is written in Go with Kubebuilder / controller-runtime and
tested with envtest. Each app is exactly one App custom resource
(tendtech.io/v1alpha1) carrying owner, expiry, declared data scopes,
purpose classification, environment state, and references to its git repo
and attestation chain. The App CR in etcd is the only authoritative state;
no platform database exists. The evidence/estate API is a stateless
projection assembled at read time, and access logs are gateway telemetry —
neither holds authoritative app state.
Alternatives considered¶
- Metacontroller / shell-based operators — rejected: too thin for a real state machine.
- Python (kopf) — rejected: weaker CRD/webhook ecosystem and a second toolchain beside the Go platform services.
- A backing Postgres for app metadata — rejected: duplicate source of truth and sync rot, the exact failure Principle IV guards against; the skeleton has no scale pressure that would justify a read store.
Consequences¶
The operator stays authoritative and the estate stays answerable from one
place. Go keeps the operator and the platform services in one toolchain.
The cost is that anything needing durable per-app facts must either live on
the CR (as status.costStatus does) or be an explicitly non-authoritative
projection or log; later features re-affirmed this doctrine — cost
suspension is a status condition, never a new state value, and access logs
live in the gateway's store, never on the CR. Operator scope creep remains
the recorded product risk, bounded by the lifecycle state machine.