Skip to content

Identity & authority

Every actor in the system, and exactly what each one is allowed to do: who can ask for things, who can say yes, and who only gets to watch.

Steelmoth has exactly one operator, and the system knows them by two names:

  • mark@canarybuilds.com — the canonical operator email. The dashboard compares the signed-in email against this exact value (lowercased, exact match). A different email, even one delivered by the trusted login proxy, is refused with “not the canonical operator”. There is no list of allowed emails — there is one.
  • @mareekkk — the GitHub account. CODEOWNERS assigns every protected path to it, so no high-stakes change merges without this account’s review.

Admin rights on the dashboard come from group membership only: the login provider (Authentik, application steelmoth-dashboard) attaches the user’s groups to the request, and a member of dashboard-admins gets the admin role. An older email-based admin list was deliberately removed (RFC 0074 / ADR 0097) so there is one mechanism, not two. Note both checks apply: the email must be canonical and the group must be present.

Account What it’s for What it can do Where its key lives
cnysoho Authors pull requests on behalf of coding agents, via the gh-as-soho.sh wrapper. PRs from this account let the operator review and approve someone else’s work instead of self-approving their own. Repo collaborator: can push branches and open PRs. Broker alias gh-token-soho, fetched from prontera at use time — never on disk here.
cnry-morpheus Authors the Morpheus drain’s pull requests, and nothing else. It never approves anything. Deliberately not a repo collaborator — its pushes and PR calls fail until the operator decides otherwise. The narrowest identity in the system. Broker alias morpheus-gh-token, same broker, same just-in-time fetch.

Both tokens are the only broker secrets that fail loudly: if the broker can’t produce them, the helper raises an error instead of returning an empty string. Pushing with the wrong identity would silently break the review flow, so it’s better to stop.

Agent kinds — how the control plane names its workers

Section titled “Agent kinds — how the control plane names its workers”

Every workshop session reports who it is. The free-form identity string is kept as-is, and a normalized kind is derived from it: claude_code, codex, morpheus — anything unrecognized becomes other. The database refuses any other value outright: the sessions table carries a CHECK constraint listing exactly those four. An agent can’t invent a privileged-sounding kind, because the set of kinds is fixed in the schema.

Role Owns Can do
steelmoth_migrator Every table and sequence. Change the shape of the database — only ever used by migrations.
steelmoth_app Nothing. Read and write rows, table by table, exactly as granted. On the violations table the grant is SELECT + INSERT only — the running system can add audit rows but can never edit or delete one.

Data is also split into two schemas: steelmoth for the product (user data, the governance audit family) and operator_authority for the workshop control plane (sessions, scopes, approvals, violations). Scoped product tables carry row-level security with FORCE turned on — meaning the rule applies even to the table’s own connection role, so the app can’t sidestep its own tenant isolation. A few operator-global control-plane tables (waitlist, agreements, pool slots) instead use a deliberately permissive policy, because they belong to the operator, not to any tenant.

Capability groups — authority inside the product

Section titled “Capability groups — authority inside the product”

When the product’s assistant acts for a user, what it may do comes from the user’s groups, mapped to named capabilities (25 distinct in total):

Group Capabilities
prt-admins 16 — the full administrative set.
prt-org-managers 2 — organization-level management.
prt-workspace-managers 2 — workspace-level management.
dashboard-admins Aliased to the prt-admins set, so the one operator group works in both worlds.

An unknown group contributes nothing — membership in a group the system has never heard of grants zero capabilities. The group-to-capability lookup is also the designed seam for the future trust kernel: today it reads login-provider groups; later it will read claims from a signed trust token, and nothing else in the pipeline has to change.

Two ways to be trusted by the approval API

Section titled “Two ways to be trusted by the approval API”

The gate (and the shims) talk to the dashboard’s approval API. The API accepts a caller in exactly two ways: the request comes from localhost or a private network address (the normal case — everything runs on one box), or it presents a shared token in the X-Operator-Authority-Gate-Token header, compared in constant time. Anything else is refused.

There is also one special local identity: when the private dashboard runs loopback-only with STEELMOTH_DASHBOARD_PRIVATE_AUTH_BYPASS=1, it synthesizes an admin identity — but only on loopback hosts, and if an operator email is configured for it that isn’t the canonical one, the bypass yields nothing. Even the backdoor checks the one-human rule.

When instructions conflict, agents follow a fixed order, written in AGENTS.md:

1. what the user just said
2. safety and security invariants
3. the engineering standards
4. the source code and live system evidence
5. wiki prose and other documentation ← lowest

So a user instruction wins — unless it collides with a safety invariant, which nothing overrides from below. And documentation (including this site) never outranks what the code actually does.

Actor Can ask Can decide Watched by
Operator (mark@canarybuilds.com / @mareekkk) Anything Approvals, scope grants, merges, settings Settings audit, CHANGELOG, git history
Coding agents (claude_code, codex) Commands, edits, scope widening, deploys — through the gate Nothing Gate, Landlock, sweep, violations log, sessions
Morpheus (drain) Drain PRs via cnry-morpheus Nothing — can’t even push yet Same stack + its own run audit
cnysoho (bot) Open PRs Nothing GitHub history, CODEOWNERS review
Product assistant Actions for users, through the four-stage pipeline Nothing — server classifies, policy decides Hash-chained ledger, governance decisions
Product users Whatever their group’s capabilities allow Their own escalations Scoped audit tables (FORCE RLS)
steelmoth_app (DB role) Row reads/writes as granted Nothing structural Grants + RLS it cannot disable