The README was 1,240 lines of reference material and still described a SQLite quick start. It is now a short tour (highlights, screenshots of the web UI, an accurate quick start against Postgres), and each topic has its own page under docs/ with an index: deployment, configuration, Alertmanager, incidents, notifications, escalation, dead man's switches, single sign-on, web UI, API and development. SERVICE-ACCOUNTS.md is rewritten from a proposal into a reference, and TEAM-LOOKUP.md is gone with the endpoint it described. The "Upgrading to ..." sections for an unreleased product are dropped. Claude-Session: https://claude.ai/code/session_016mBLURvJoMuUEr9cB2RpUN
3.0 KiB
Service accounts
A non-human credential for automation (terdut-operator, CI, scripts). It is not a
users row: no password, no is_admin, no OIDC identity, so it can never be
pulled into login or group sync, and it is never mistaken for a person in an
audit trail. The bearer token has the same shape as an API key (SHA-256 hash
stored, raw value shown once), prefixed tdsa_.
Scopes
- instance — acts as owner of every team's configuration (rename, OIDC
groups, escalation, dead man's switches, integrations, members, delete) and may
create teams. It is not a member of any team, so it reads no incidents or
queue. It is never an administrator: user management and
/api/admin/settingsstay human-only. - team — acts as owner of exactly one team, through a single synthetic membership. It may also mint another service account for its own team.
An account has many keys, so rotating is "mint a new key, revoke the old one" without losing the account's identity or history.
Endpoints
POST /api/service-accounts{name, scope, team_id}— returns the account and its first key. An instance-scoped account is granted by a human administrator; a team-scoped one by an administrator, that team's owner, or an instance-scoped account.GET /api/service-accounts?name=— look one up by name.POST /api/service-accounts/{id}/keys,DELETE .../keys/{keyID}— mint or revoke a key. An instance-scoped account may manage any team-scoped account's keys, and any account may manage its own.
Seeding the operator's account
TERDUT_OPERATOR_KEY (at least 32 characters) creates the instance-scoped account
terdut-operator if missing and replaces its seed key with this value at every
start (internal/api/operator_key.go). The deployer generates the key and
nothing has to call /api/bootstrap for it; rotating is a restart with a new
value. With TERDUT_OPERATOR_MODE on, configuration writes by humans are refused
and a service account of either scope passes.
How it is enforced
Every request resolves to one Caller (internal/api/caller.go): a human
(session or API key) or a service account.
Caller.IsAdmin()is true only for a human administrator.AdminOnlyandrequireSelfOrAdminkey on it alone; do not widen them — each time a gap came up the fix was a narrower purpose-built capability instead.Caller.IsInstanceServiceAccount()is true only for an instance-scoped account, never for a human.requireTeamOwnerandcallerOwnsTeamadmit it for any team.Caller.Role(teamID)/TeamIDs()are a human's memberships or a team-scoped account's single owner membership; instance scope has none.Caller.AsHuman()is what a handler must call when it needs a realuser_id; handlers meant for people answer 403 to a service account instead of writing a zero id.
Where a service account acts on an incident (acknowledge, resolve), the
timeline and acknowledged_by record it through parallel *_service_account_id
columns, never as a user.