# GovOPlaN RBAC And Resource-Access Model

**Updated:** 2026-07-11

## Authorization Equation

An operation is permitted only when every applicable layer allows it:

```text
effective role/API-key capability
AND resource ownership/share access
AND workflow state
AND active governance/policy constraints
```

RBAC answers what an actor may do. ACLs answer which resource the actor may do
it to. Workflow state and policy decide whether the operation is currently
valid.

## Identity And Scope

```text
Account                         global login identity
+- User membership               tenant-local identity
   +- direct tenant roles
   +- active group memberships
   |  +- inherited tenant roles
   +- tenant-local API keys

Account
+- direct system-role assignments
```

A browser session has one active tenant membership. System privileges do not
silently grant tenant data access. API keys remain tenant-local and receive the
intersection of their configured scopes and their owner's live tenant scopes on
every request.

## Wildcards

```text
tenant:*   every canonical tenant permission
system:*   every canonical system permission
*          legacy alias interpreted as tenant:* only
```

Tenant wildcards never grant system permissions.

## Canonical Tenant Permissions

Campaigns:

```text
campaign:read
campaign:create
campaign:update
campaign:copy
campaign:archive
campaign:delete
campaign:share
campaign:validate
campaign:build
campaign:review
campaign:send_test
campaign:queue
campaign:control
campaign:send
campaign:retry
campaign:reconcile
```

Recipients:

```text
recipients:read
recipients:write
recipients:import
recipients:export
```

Files:

```text
files:read
files:download
files:upload
files:organize
files:share
files:delete
files:admin
```

Reports and audit:

```text
reports:read
reports:export
reports:send
audit:read
```

Mail servers:

```text
mail_servers:read
mail_servers:use
mail_servers:test
mail_servers:write
mail_servers:manage_credentials
```

Tenant administration:

```text
admin:users:read
admin:users:create
admin:users:update
admin:users:suspend

admin:groups:read
admin:groups:write
admin:groups:manage_members

admin:roles:read
admin:roles:write
admin:roles:assign

admin:api_keys:read
admin:api_keys:create
admin:api_keys:revoke

admin:settings:read
admin:settings:write
admin:policies:read
admin:policies:write
```

## Canonical System Permissions

```text
system:tenants:read
system:tenants:create
system:tenants:update
system:tenants:suspend

system:accounts:read
system:accounts:create
system:accounts:update
system:accounts:suspend

system:roles:read
system:roles:write
system:roles:assign

system:access:read
system:access:assign

system:audit:read
system:settings:read
system:settings:write
system:governance:read
system:governance:write
```

`system:access:*` remains a read/assignment boundary for cross-tenant and
system access handling. It is not a separate primary UI area.

## Default Tenant Roles

- **Owner:** `tenant:*`. At least one active operational owner must remain.
- **Tenant administrator:** settings, policies, users, groups, roles, API keys,
  and read access to campaigns/files/reports/audit. Real delivery remains
  separately delegable.
- **Administrator:** all tenant permissions for upgraded installations.
- **Access administrator:** membership and assignment management within
  delegation limits.
- **Campaign manager:** prepare, validate, and build campaigns; no review
  approval or real delivery by default.
- **Reviewer:** inspect and approve prepared campaign messages.
- **Sender:** mock-test, queue, control, send, retry, and reconcile prepared
  campaigns; can use/test approved mail profiles.
- **File manager:** managed file operations without campaign delivery rights.
- **Viewer:** read campaigns, recipients, files, and reports.
- **Auditor:** read campaigns, recipient evidence, reports, and audit records;
  export detailed evidence.

## Default System Roles

- **System owner:** `system:*`, protected. At least one active account must
  retain it.
- **System administrator:** all specific system permissions, editable and not
  protected.
- **System auditor:** read-only system registry/settings/governance/audit role,
  editable.

## Delegation Ceiling

For role definition, assignment, and API-key creation:

```text
requested scopes subset of actor delegateable scopes
```

Rules:

1. Tenant roles may contain tenant scopes only.
2. System roles may contain system scopes only.
3. Definition rights and assignment rights are separate.
4. Group definition and group membership management are separate.
5. API-key scopes are intersected with the owner's current effective scopes on
   every request.
6. Suspended accounts, users, tenants, or groups stop contributing access
   immediately.
7. Administrative updates are field-sensitive; a user with only status
   authority cannot change role assignments.

## Campaign Ownership And ACLs

A campaign has exactly one owner:

```text
owner user OR owner group
```

Additional active shares may target users or groups with `read` or `write`.

Resolution:

- owner user: read and write;
- member of owner group: read and write;
- explicit read share: read;
- explicit write share: read and write;
- `tenant:*`: tenant-wide ACL bypass;
- ordinary campaign permission without ownership/share: no object access.

ACLs do not add capabilities. A write share still needs the specific permission
for update, validation, review, send, report, retry, or reconciliation.

## Files

The current file access model distinguishes tenant-level file capabilities from
space/folder/file ownership:

| Scope | Meaning |
| --- | --- |
| `files:read` | browse/read visible file spaces and metadata |
| `files:download` | download file content when ACL permits |
| `files:upload` | create files in writable spaces |
| `files:organize` | create folders, move files, and update metadata where ACL permits |
| `files:share` | share files/spaces according to owner and policy rules |
| `files:delete` | delete or retire files where ACL permits |
| `files:admin` | tenant-wide administration of user/group file spaces |

External file connections and spaces are additionally constrained by connector
policy and owner/group assignment in the files module.

## Resource Access Explanations

The access module exposes a diagnostic endpoint for explaining why a principal
can see or operate on a concrete resource:

```text
GET /api/v1/admin/access/resource-explanation
```

Required query values:

| Field | Meaning |
| --- | --- |
| `user_id` | Tenant membership to explain. The current module UIs pass the signed-in user. |
| `resource_type` | Module-owned type such as `file`, `folder`, or `campaign`. |
| `resource_id` | Stable module resource identifier. |
| `action` | Permission/action being explained, for example `files:file:read`. |
| `tenant_id` | Optional tenant override for system/admin contexts. |

The access module always contributes effective-scope provenance for the action.
Installed modules may add resource provenance by exposing a
`ResourceAccessExplanationProvider` through a capability consumed by access.
Providers should return only facts they own, using these provenance kinds:

| Kind | Meaning |
| --- | --- |
| `resource` | The concrete resource or an explicit not-found result. |
| `owner` | Matching user/group ownership. |
| `share` | Matching explicit user/group/tenant share. |
| `policy` | Administrative bypass or policy-derived grant. |
| `role` / `right` | Scope and role provenance from access itself. |

Files currently registers `files.access` and explains file assets plus folders.
Persisted folders use their database ID. Folder rows inferred from file paths use
a deterministic virtual ID:

```text
virtual-folder:v1:<tenant_id>:<owner_type>:<owner_id>:<base64url(normalized_path)>
```

The files provider validates virtual folder IDs before returning provenance: the
tenant and owner must match the resource ID, and at least one active file must
exist below the normalized folder path. This keeps virtual folder explanations
stable without forcing every inferred tree node to become a stored folder row.

Campaign currently registers `campaigns.access` and explains the campaign
ownership/sharing object itself. Finer-grained campaign sub-objects are tracked
separately in `govoplan-campaign#50` until their resource identifiers and access
rules are decided.

Cross-user resource explanation is a policy feature, not a module-local UI
detail. Until `govoplan-policy#6` is resolved, module UIs should default to
current-user explanation and avoid importing access-admin user-picking
components.

## Mail Servers

| Scope | Meaning |
| --- | --- |
| `mail_servers:read` | profile metadata and effective policy visibility |
| `mail_servers:use` | use an allowed profile for a campaign or message flow |
| `mail_servers:test` | run connectivity tests without revealing secrets |
| `mail_servers:write` | create/update profile metadata where policy allows |
| `mail_servers:manage_credentials` | create/replace SMTP/IMAP secrets or lower-level credentials where policy allows |

Reusable encrypted profiles exist. Effective usability is also constrained by
hierarchical mail-profile policy, ownership, allowed/forced profile sets,
credential inheritance mode, lower-level override switches, and allow/deny
patterns.

## Compatibility Aliases

Compatibility aliases may exist in backend code for upgraded installations, but
new UI and docs should use canonical scopes.

Current alias direction:

```text
*                      -> tenant:*
system:tenants:write   -> create/update/suspend tenant scopes
system:access:write    -> system access assignment/write scopes
```

A separate `retention:*` family is not currently canonical because retention is
managed through system settings and tenant policy scopes. Add it only if
retention operation duties need separation from general policy/settings
administration.
