# Actors — mod-cms

Audience: AI coding agents first.

Roles read from their definition, not inferred.

## Role hierarchy

`ROLE_HIERARCHY = ['viewer', 'editor', 'admin']` — ordered, ascending. `requireRole` admits a
principal at or above the named rung. Source: `apps/mod-cms/src/lib/admin-engine.ts:8`.

| Actor | Exact role id | Authority | Ownership boundary | Evidence |
|---|---|---|---|---|
| anonymous-operator | none — no principal exists | Runs first-run setup exactly once; creates the first admin. Bounded by the single-use claim lease, not by a role. | No resource is owned yet; the lease is the only authority. | `apps/mod-cms/src/pages/api/install.ts:75` |
| anonymous-visitor | none — no principal | Reads published content; submits the contact form and comments. | Owns nothing; every write is unauthenticated and moderated downstream. | `apps/mod-cms/src/pages/api/contact.ts`, `apps/mod-cms/src/pages/api/comments.ts` |
| viewer | `viewer` | Lowest rung of the hierarchy. | Not yet traced — no journey asserts a viewer outcome. | `apps/mod-cms/src/lib/admin-engine.ts:8` |
| editor | `editor` | Admitted by `requireEditor`. | Not yet traced — the editor-vs-admin split is a journey to author. | `apps/mod-cms/src/lib/auth.ts:125` |
| admin | `admin` | Admitted by `requireAdmin`; top rung, so it also satisfies every editor gate. | Not yet traced — admin ownership boundaries are a journey to author. | `apps/mod-cms/src/lib/auth.ts:113` |

## Notes

- `actorFromPrincipal` derives `isAdmin` / `isEditor` from the principal's roles array
  (`apps/mod-cms/src/lib/auth.ts:167-169`); it is the shared shape the admin routes gate on.
- A service token resolves to a principal too (`buildServiceTokenResolver`), so an agent bearer is an
  actor on the same rungs. No journey covers it yet — see `scan-problems.md`.
