Personas
A persona is a versioned bundle of permissions seeded into the roles table (BuiltinRole{key, name, scope_kind, description, permissions, version}, PERSONA_VERSION = 3). Bundles are expanded from patterns
at import time, so a typo fails the API at start-up; sync_builtin_roles inserts missing personas and
updates changed bundles at every API start and never deletes. Ten personas exist: four in organisation scope
and six in program scope. A role assignment binds a user or group to a persona in org scope or in one
program; persona checks that need the role itself (not only its keys) resolve group grants through
roles.effective_role_keys.
Version 3 (phase 3 R3, ADR 0021) distributes the six twin and
hand-off keys and moves two approvals. program.twins.read (oversight of every twin) goes to owners, leads
and quality reviewers only, subtracted from the scientist, the manufacturing specialist and the viewer
exactly as program.threads.read is; program.twins.read_own, program.twins.use and
program.handoffs.manage go to every program persona except the viewer; program.handoffs.read is absorbed
by every program persona’s reads; the viewer gains program.twins.read_own and program.handoffs.read
only. The scientist loses evidence.hypotheses.approve and plans.plans.approve: deciding a hypothesis and
approving a plan sit with the lead and the owner, as the approval matrix below has always said, so the matrix
and the code now agree. Organisation personas are unchanged from version 2.
Summary
| Key | Name | Scope | Permissions | Bundle |
|---|---|---|---|---|
org_owner | Organisation owner | org | 13 | Full control of the organisation, including owner grants. |
org_admin | Organisation admin | org | 12 | Manages programs, users, groups and roles; cannot grant or revoke owners. |
org_member | Organisation member | org | 5 | Reads organisation settings, programs, users, groups and roles. |
org_auditor | Organisation auditor | org | 6 | Reads every organisation record including the audit log. |
program_owner | Program owner | program | 74 | Full control of one program across all four products, including owner grants. |
program_lead | Program lead | program | 73 | Runs the program across all four products; cannot grant or revoke program owners. |
scientist | Scientist | program | 49 | Investigates evidence, drafts plans, protocols, experiments and reports; hypothesis decisions and approvals stay with the lead and owner. |
quality_reviewer | Quality reviewer | program | 43 | Compares records with SOPs, decides review findings, records deviations and reads every console thread and twin for oversight. |
manufacturing_specialist | Manufacturing specialist | program | 38 | Plans milestones, dependencies and open items for manufacturing planning; approvals stay with approvers. |
viewer | Viewer | program | 29 | Reads every program record and their own console threads; has no twin and changes nothing. |
Sizes at version 2 were 68, 67, 47, 38, 34 and 27 for the six program personas; the organisation personas did
not change. Version 4 (R5 since ADR 0032; stage gates and the role-memory keys) is designed to read 78, 77, 50, 45, 40 and 30 before
the role-memory keys are counted; R5 also adds org.twins.use to org_owner and org_admin. R4 leaves version 3 as it is.
Matrix
One row per resource, one column per persona. Letters are the actions of the permission strings the
persona holds on that resource: r read, o read_own, c create, u update, d delete, m manage,
a approve, l link, s sync, x use, w manage_owners. A dash means no permission on that resource.
The table is rendered from BUILTIN_ROLES at version 3 and recounted on publish day.
| Resource | org_owner | org_admin | org_member | org_auditor | program_owner | program_lead | scientist | quality_reviewer | manufacturing_specialist | viewer |
|---|---|---|---|---|---|---|---|---|---|---|
org.settings | r u | r u | r | r | - | - | - | - | - | - |
org.programs | r c m | r c m | r | r | - | - | - | - | - | - |
org.users | r m | r m | r | r | - | - | - | - | - | - |
org.groups | r m | r m | r | r | - | - | - | - | - | - |
org.roles | r m w | r m | r | r | - | - | - | - | - | - |
org.audit | r | r | - | r | - | - | - | - | - | - |
program.settings | - | - | - | - | r u | r u | r | r | r | r |
program.members | - | - | - | - | r m | r m | r | r | r | r |
program.roles | - | - | - | - | r m w | r m | r | r | r | r |
program.connections | - | - | - | - | r m s | r m s | r | r | r | r |
program.audit | - | - | - | - | r | r | r | r | r | r |
program.agent_runs | - | - | - | - | r m | r m | r | r | r | r |
program.threads | - | - | - | - | r o x m | r o x m | o x | r o x m | o x | o |
program.documents | - | - | - | - | r m | r m | r m | r m | r | r |
program.agent_memory | - | - | - | - | r m | r m | r m | r | r | r |
program.twins | - | - | - | - | r o x m | r o x m | o x | r o x | o x | o |
program.handoffs | - | - | - | - | r m | r m | r m | r m | r m | r |
evidence.questions | - | - | - | - | r m | r m | r m | r | r | r |
evidence.sources | - | - | - | - | r m l | r m l | r m l | r | r | r |
evidence.findings | - | - | - | - | r m | r m | r m | r | r | r |
evidence.hypotheses | - | - | - | - | r m a | r m a | r m | r | r | r |
evidence.gaps | - | - | - | - | r m | r m | r m | r | r | r |
evidence.agents | - | - | - | - | x | x | x | - | - | - |
plans.plans | - | - | - | - | r m a | r m a | r m | r | r m | r |
plans.protocols | - | - | - | - | r m a | r m a | r m | r | r | r |
plans.experiments | - | - | - | - | r m | r m | r m | r | r | r |
plans.reports | - | - | - | - | r m a | r m a | r m | r | r | r |
plans.tools | - | - | - | - | r x | r x | r x | r | r | r |
plans.agents | - | - | - | - | x | x | x | - | - | - |
review.sops | - | - | - | - | r m a | r m a | r | r m a | r | r |
review.checks | - | - | - | - | r | r | r | r | r | r |
review.findings | - | - | - | - | r m a | r m a | r | r m a | r | r |
review.deviations | - | - | - | - | r m | r m | r | r m | r | r |
review.agents | - | - | - | - | x | x | x | x | - | - |
progress.milestones | - | - | - | - | r m | r m | r | r | r m | r |
progress.dependencies | - | - | - | - | r m | r m | r | r | r m | r |
progress.open_items | - | - | - | - | r m | r m | r m | r m | r m | r |
progress.approvals | - | - | - | - | r m a | r m a | r | r | r m | r |
progress.agents | - | - | - | - | x | x | - | - | x | - |
How the bundles are built
org_owneris everyorg.*key;org_adminis the same minusorg.roles.manage_owners;org_memberis everyorg.*.readminusorg.audit.read;org_auditoris everyorg.*.read.program_owneris every program-scope key (program.*,evidence.*,plans.*,review.*,progress.*);program_leadis the same minusprogram.roles.manage_owners.scientistreads everything in program scope except other people’s threads and twins, holds all ofevidence.*exceptevidence.hypotheses.approve, all ofplans.*exceptplans.plans.approve,plans.protocols.approveandplans.reports.approve, plusprogress.open_items.manage,review.agents.use,program.threads.read_own,program.threads.use,program.twins.read_own,program.twins.use,program.handoffs.manage,program.documents.manageandprogram.agent_memory.manage.quality_reviewerreads everything including every thread and every twin (program.threads.read,program.twins.read), holds all ofreview.*,progress.open_items.manage,program.threads.read_own,program.threads.use,program.threads.manage,program.twins.read_own,program.twins.use,program.handoffs.manageandprogram.documents.manage.manufacturing_specialistreads everything except other people’s threads and twins, holdsprogress.*exceptprogress.approvals.approve(which includesprogress.agents.use), plusplans.plans.manage,program.threads.read_own,program.threads.use,program.twins.read_own,program.twins.useandprogram.handoffs.manage.viewerreads everything in program scope, their own threads and their own (non-existent) twin, and changes nothing.
Approvals stay with approvers by design: only program_owner and program_lead hold
evidence.hypotheses.approve, plans.plans.approve, plans.protocols.approve, plans.reports.approve and
progress.approvals.approve together; review.sops.approve and review.findings.approve add the quality
reviewer. Agents are not users and hold no persona: they act under a service identity
agent:<agent_key>@<org_id> and every write they propose is approved by a person exercising the write’s own
permission. A persona twin holds no persona either: its tool allowlist is the intersection of the agent’s
role catalogue and the bound person’s live permissions, so it can never exercise a key its person does not
hold, and it appears in no row of the approval matrix. Viewers have no twin.
Approval matrix
Who decides what, by the permission the decision exercises and the signature meaning it carries. The
persona column is derived from BUILTIN_ROLES: the api-products test test_approval_matrix_from_builtin_roles
renders this table from the bundles and diffs it against PRODUCT-CONTRACT.md and the block below, so the
matrix cannot drift from the code again. R4 rows are designed and carry the release. Twins appear in no row.
| Decision | Permission exercised | Signature meaning | Personas |
|---|---|---|---|
| Finding, gap or source enters the record | evidence.findings.manage / evidence.gaps.manage / evidence.sources.manage | none | scientist, lead, owner |
| Hypothesis accepted or rejected | evidence.hypotheses.approve | none | lead, owner |
| Plan approved | plans.plans.approve | none | lead, owner |
| Protocol / report approved | plans.protocols.approve / plans.reports.approve | approved | lead, owner |
| Review finding decided | review.findings.approve | none | quality reviewer, lead, owner |
| Requirement set accepted | review.sops.approve | approved | quality reviewer, lead, owner |
| Deviation recorded or classified | review.deviations.manage | none | quality reviewer, lead, owner |
| Approval request decided | progress.approvals.approve | as target | lead, owner |
| Hand-off sent, accepted, declined, completed, withdrawn | program.handoffs.manage, scoped by party | none | scientist, quality reviewer, manufacturing specialist, lead, owner |
| Twin enabled, paused, resumed, retired (own); standing plan approved | program.twins.use | none | scientist, quality reviewer, manufacturing specialist, lead, owner |
| Any twin paused with a reason | program.twins.manage | none | lead, owner |
| Stage gate ready (R4, designed) | progress.stages.manage | none | lead, owner; manufacturing specialist for the manufacturing gate only |
| Stage gate reviewed (R4, designed) | review.stage_reviews.approve + quality_reviewer persona | reviewed | quality reviewer |
| Stage gate closed (R4, designed) | progress.stages.approve | approved | owner, lead (not the reviewed signer) |
Workbench roles are not personas
Phase 4 adds a layer above the personas, the workbench roles (designed):
fifteen keys that choose a person’s home screen, rail, toolset, skills and harness and never grant a
permission, so no row of this page changes with them (“Workbench roles change what you see first, never what
you may do”; PERSONA_VERSION stays 3 and the catalog stays at 87 in R4).
What changes next
Version 4 (R5 by ADR 0032, which moves stage
gates out of R4; the matrix rows above still read “R4” until the generator is re-run) adds review.stage_reviews.approve, progress.stages.read,
progress.stages.manage and progress.stages.approve; every program persona gains the read, the quality
reviewer the review signature, the manufacturing specialist the manage key for the manufacturing gate only,
owners and leads all four. R5 adds org.twins.use to org_owner and org_admin. The
twins reference lists every key, count and route per release.
Source: api/app/auth/personas.py (PERSONA_VERSION = 3, TWIN_OVERSIGHT, TWIN_PARTICIPANT, SCIENTIST_APPROVALS_WITHDRAWN, BUILTIN_ROLES), api/app/auth/permissions.py (EXPECTED_PERMISSION_COUNT = 87), api/app/services/roles.py, docs/lib/content.ts (PERSONAS, PERSONA_BUNDLE_SIZES, TWIN_OVERSIGHT_PERSONAS), PRODUCT-CONTRACT.md sections 10.2, 11.4 and 11.7, PRODUCT-PLAN.md Part 7 sections 3.5 and 7.3, decisions/0021-persona-twins.md