diff --git a/DESIGN.md b/DESIGN.md index 7102e3e..61bfd64 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -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