DESIGN.md: document operator mode and what it deliberately doesn't lock
CI / chart (push) Successful in 1s
CI / security (push) Successful in 1m3s
CI / test (push) Successful in 2m22s

No operator-mode section existed here before -- terdut-server's own
README.md documents the feature, but this repo's design doc never
mentioned it. Added as §6 point 7, confirmed against source
(internal/api/middleware.go's OperatorModeBlock, router.go's opMode
wrapper): it blocks human writes to exactly the resources this
operator's CRDs manage (team identity, OIDC-group binding, escalation,
dead man's switches, integrations), and nothing else -- team membership,
invites, and the on-call schedule/rota stay human-editable regardless,
confirmed from the router rather than assumed from the README's prose
alone.
This commit is contained in:
Niklas Ye
2026-10-01 19:16:53 +02:00
parent 0e3118d6aa
commit 4ab04d29a8
+21
View File
@@ -566,6 +566,27 @@ when nothing ever crosses into a tenant namespace in the first place.
Secret is updated in place. No DB-level workaround, no re-triggering a
single-shot endpoint that can't fire twice (which is what made rotation
unworkable under the old `/api/bootstrap`-only design).
7. **Operator mode** (`TERDUT_OPERATOR_MODE`, terdut-server's own
deploy-time flag, off by default) is the complementary half of this
trust model: it makes terdut-server itself refuse a *human* write (a
session or a user's own API key) on a route, while a service account's
— this operator's — still goes through. Confirmed against source
(`internal/api/middleware.go`'s `OperatorModeBlock`,
`internal/api/router.go`'s `opMode` wrapper): the blocked set is exactly
team create/rename/delete, a team's OIDC-group binding, its escalation
policy, its dead man's switches, and its integrations — precisely the
resources `TerdutTeam`, `TerdutEscalationRule`, `TerdutDeadmanSwitch` and
`TerdutAlertSource` manage, and nothing more. **Deliberately not
blocked, confirmed against the same router**: team membership and
invites (the router's own comment: "membership is deliberately never
gitops-managed"), and the on-call schedule/rota
(`/api/teams/{teamID}/schedule`, `/api/schedule/current`) — neither
route carries the `opMode` wrapper at all. A human can still add or
remove a team member, or assign who's on call, on a server running in
operator mode; only the CRD-shaped resources above are locked to
GitOps. This isn't a gap to close — it's the same boundary §4.2 already
draws for membership, confirmed to hold on the server side too, not
just stated as an intent here.
## 7. Ownership, status, garbage collection