Skip to main content

Evalium — Roles, Access Control, & Identity Specification (Execution Ledger Edition v3.1)


1. Purpose

This document defines Evalium’s:

  • Authentication model (identity + step-up)
  • Authorization model (roles/capabilities)
  • Multi-tenant + multi-org (silo) enforcement via PostgreSQL RLS
  • Access patterns for Admin Console, Operator Execution, and Client Portal (Glass Box)
  • External sharing via magic links (reporting and ledger visibility)
  • Future-proof custom roles
  • SMB-friendly UX rules (simple by default; defensibility underneath)

This is the authoritative spec for backend permission enforcement and UI access behaviour.

For engineering invariants (WORM ledger, amendments/voids, contextual binding, verification levels), see: docs/architecture/FOUNDATION.md

For idempotency scope and retry semantics, see: docs/architecture/IDEMPOTENCY-AND-RETRY-POLICY.md

For system structure (Engagement/Project wrapper, Client Portal, hashing/ratification components), see: docs/architecture/architecture


2. Core concepts (Operational Defensibility Context)

Evalium is an Execution Ledger:

  • authoring defines what should be done (templates, rubrics, gates) via immutable versioning.
  • Execution records what happened (append-only ledger events and immutable ledger entries).
  • Reporting / Portals are projections over immutable history (with controlled disclosure).

UX principle:

  • Users expect “Edit / Save”
  • Backend performs “Amend” (append-only) and can show history when needed

3. Isolation Model (Tenant + Org Unit Silos)

Evalium enforces strict data separation using:

  1. Tenants: customer-level isolation
  2. Org Units: silos inside a tenant (e.g., UK, DE, Safety Team, Region A)
  3. Roles & Capabilities: feature control
  4. TxManager + PostgreSQL RLS: final enforcement per request

Org hierarchy note: org units may be arranged in a user-defined hierarchy (future), but Evalium enforcement remains tenant + org unit only.

3.1 RLS GUCs

TxManager sets these per transaction:

SET LOCAL app.tenant_id     = '<uuid>';
SET LOCAL app.org_unit_id = '<uuid>';
SET LOCAL app.org_scope_all = 'true' | 'false';

3.2 Combined RLS Policy Pattern

Every siloed table must use a combined tenant + org policy:

USING (
tenant_id = current_setting('app.tenant_id')::uuid
AND (
current_setting('app.org_scope_all') = 'true'
OR org_unit_id = current_setting('app.org_unit_id')::uuid
)
)

This guarantees:

  • Tenant isolation
  • Org-unit siloing
  • Optional global visibility only for privileged users

3.3 Connection Safety

Evalium uses SET LOCAL only (never SET SESSION) to prevent scope leakage across pooled connections.

3.4 SMB UI Rule

If a tenant has exactly one org unit:

  • Hide org selectors
  • Hide org context in navigation
  • Default to a single-organisation UX

4. Authentication (Identity)

Evalium optimizes for SMB adoption (low friction) while supporting non-repudiation-ready workflows for high-stakes execution.

4.1 JWT Claims (Lean + Stable)

JWT stores:

user_id
tenant_id
org_unit_id
role_ids[]
org_scope_all: true|false (optional, privileged)

Rules:

  • Capabilities are not stored in the JWT
  • Capabilities are resolved server-side per request from role definitions (cacheable)

Dev/Test Tokens

In dev/test, helpers mint JWTs based on stored user_roles bindings (roles must exist + be assigned) so tests reflect real RBAC state.

4.2 Identity Modes (MVP → Enterprise)

  • No passwords stored
  • Low friction for SMB users
  • Secure, expiring, single-use tokens

B. Email + Password (Phase 2+)

  • Hashing (Argon2id recommended)
  • Reset flows
  • MFA optional

C. SSO/OIDC (Enterprise)

  • Optional, later

4.3 Identity Tiers (Access Strength Model)

Evalium distinguishes identity strength for different actions:

  • Tier 0 — Anonymous: no access beyond /health and auth endpoints
  • Tier 1 — Link Principal (Scoped Viewer): access via scoped magic link (read-only or narrowly permitted actions), revocable, expiring
  • Tier 2 — Authenticated User: normal JWT user session (operators/admins)
  • Tier 3 — Step-Up Verified: stronger proof required for high-stakes actions (e.g., WebAuthn/MFA)

Important framing: Tier 1 is not “a user”; it is a Link Principal with explicit scope + permissions + expiry.

4.4 Step-Up Authentication (Contract Requirement)

Some actions require step-up authentication (Tier 3), especially when they:

  • Create or ratify high-trust ledger state
  • Void records after ratification
  • Perform Level 4 Context-Verified execution events
  • Change verification requirements/policies

Backend contract (mandatory):

  • Requests that require step-up MUST include a step-up proof token (mechanism implementation-defined)
  • Backend validates the proof and records the method used in the ledger context metadata

5. Authorization Model (Capabilities First)

5.1 Capability = Atomic Permission

Capabilities are stable identifiers that represent actions. Capability checks answer:

“Are you allowed to attempt this action?”

They do not replace verification policy (which answers: “what proof is required to write?”).

Suggested capability groups (stable, expandable)

authoring

  • questions.read, questions.write
  • evaluations.read, evaluations.write
  • taxonomy.read, taxonomy.manage, taxonomy.assign
  • programmes.read, programmes.manage (if used)

Engagement / Project (Golden Thread)

  • engagements.read
  • engagements.manage

Assignments / Execution

  • assignments.issue
  • sessions.launch
  • sessions.monitor (future)

Execution ledger write surface (split by intent)

  • execution.write (record execution answers/events)
  • evidence.attach (upload/attach evidence + metadata binding)
  • review.sign (peer verification / sign-off)
  • ledger.amend (append an amendment event)
  • ledger.void (append a void event)
  • ledger.view (read ledger timelines / history views)

Ratification

  • ledger.ratify (stakeholder ratification)

Reporting

  • reporting.view (aggregated)
  • reporting.view_named (PII-bearing views)
  • reporting.export

Identity & Admin

  • users.read, users.manage
  • roles.read, roles.manage
  • org.manage
  • billing.read_invoice, billing.manage_subscription

System (dev/build gated)

  • debug.access (dev builds only)

Capability names may expand; existing ones should remain backwards compatible.

5.2 Roles = Sets of Capabilities

Roles are tenant-scoped collections of capabilities.

Rules:

  • No hard-coded role checks in handlers

  • Permission checks are capability-based only:

    • if user.can("ledger.void")
    • if role == "Owner"

5.3 Policy vs Capability (must be explicit)

Even when a capability allows an action, policy may still reject it.

Examples:

  • User has ledger.void but policy forbids voiding after ratification without step-up.
  • User has reporting.view_named but disclosure policy forbids named views for a given portal link scope.
  • User has taxonomy.assign but may only change tags on content they can already read; content edits still require the relevant *.write capability.

5.4 Frontend contract: actions, not raw capability strings

The frontend should not branch directly on raw capability names in page code.

Instead, pages and shared components should consume stable action predicates such as:

  • questions:view_list
  • questions:create
  • questions:edit
  • questions:assign_taxonomy
  • evaluations:bulk_mutate

This keeps the UI stable when coarse capabilities later split into finer enterprise permissions.

Current mapping guidance:

  • questions.read grants question/passage list/detail viewing
  • questions.write grants question/passage content mutation
  • evaluations.read grants evaluation list/detail viewing
  • evaluations.write grants evaluation mutation
  • taxonomy.assign grants tag mutation on readable content without implying content edit rights

5.5 Capability checks do not replace idempotency policy

Capability answers who may attempt an action. Idempotency answers how retries are safely handled for mutating actions.

For in-scope mutating endpoints, both are required:

  • capability check (authorization)
  • keyed idempotency (Idempotency-Key) per policy

Missing either control must reject the request.


6. Route → Capability Matrix (Current HTTP Surface)

This section documents the current mapping pattern. The identifiers may evolve as endpoints are reorganized, but enforcement remains capability-based.

Public

  • POST /auth/login
  • GET /auth/verify
  • /health

Auth only (no capability check)

  • GET /auth/me (introspection)

Debug (dev-only)

  • /debug/** requires debug.access AND must be disabled/blocked in production builds.
SurfaceRoutes (/api/v1…)Capability
QuestionsGET /questions, GET /questions/\{id\}questions.read
POST/PATCH/DELETE /questions/**, /versions/**, /publish, /archivequestions.write
PUT /taxonomy/questions/\{id\}/terms, taxonomy-only PATCH /questions/\{id\}, POST /questions/bulk with taxonomyReplacequestions.read + taxonomy.read + (questions.write or taxonomy.assign)
PassagesGET /passages, GET /passages/\{id\}questions.read
POST/PATCH/DELETE /passages/**, /questions/**questions.write
PATCH /passages/\{id\} for taxonomy-only updates, POST /passages/bulk with taxonomyReplacequestions.read + taxonomy.read + (questions.write or taxonomy.assign)
EvaluationsGET /evaluations/**, /versions/**, /preview, /validate, /usageevaluations.read
POST/PATCH/DELETE /evaluations/**, /versions, /publish, /feedback, /sections/**evaluations.write
PUT /taxonomy/evaluations/\{id\}/terms, POST /evaluations/bulk with taxonomyReplaceevaluations.read + taxonomy.read + (evaluations.write or taxonomy.assign)
BucketsGET /buckets/**evaluations.read
POST/PATCH/DELETE /buckets/**, /previewevaluations.write
ProgrammesGET /programmes/**, /programmes/\{id\}/requirementsprogrammes.read
POST/PATCH/DELETE /programmes/**, /requirements/**programmes.manage
SessionsPOST /evaluations/\{id\}/sessions/** (create/answers/submit)sessions.launch + execution.write
Evidence / Media/media/**evidence.attach (and/or media.manage if retained)
Evidence reviewPOST /submissions/\{id\}/evidence/approvesubmissions.approve
POST /submissions/\{id\}/evidence/rejectsubmissions.review
Ledger viewsGET /submissions/\{id\}ledger.view + (if named) reporting.view_named
Evaluation submissionsGET /evaluations/\{id\}/submissionsreporting.view
UsersGET /users, GET /users/\{id\}users.read
POST/PATCH/DELETE /users/**users.manage
RolesGET /roles, /roles/capabilitiesroles.read
POST/PATCH/DELETE /roles/**, role bindings routesroles.manage
Debug (dev)/debug/**debug.access

Note: If you keep media.manage, keep it as an internal/admin capability. For execution flows prefer evidence.attach.

Alignment: Evidence review uses capabilities only. Avoid hard-coded role names (e.g., "proctor"); use capability aliases where a domain term is needed.

Idempotency alignment: For in-scope mutating routes in this matrix, Idempotency-Key is required per docs/architecture/IDEMPOTENCY-AND-RETRY-POLICY.md (explicit exemptions are policy-defined).


7. Default Roles (Operational Defensibility Baseline)

These roles ship as defaults. Tenants may later customize roles (Phase 2+).

7.1 Tenant Owner

Full tenant control.

  • All permissions
  • Billing management
  • Org management
  • User management
  • Reporting across all orgs
  • Ledger admin actions (amend/void/verify/ratify) when permitted by policy

7.2 Tenant Admin (Global)

Controls all orgs within a tenant (but not billing).

  • Manage users and roles
  • Manage authoring
  • Issue assignments
  • Reporting across all orgs
  • Manage engagements/projects (if enabled)
  • Configure verification policies (where allowed)

7.3 Org Admin

Controls one org unit.

  • Manage users in org
  • Manage content in org
  • Issue assignments in org
  • Reporting in org
  • Manage engagements/projects in org (if enabled)

7.4 Author

Creates and maintains versioned templates/checklists/gates.

  • questions.read/write
  • evaluations.read/write
  • Publish versions
  • View reports for authored content (org-scoped)

7.5 Operator (Practitioner)

The person doing the work / executing tasks.

  • Launch assigned tasks
  • execution.write
  • evidence.attach (where allowed)
  • View own outcomes/results (where policy allows)

7.6 Verifier (Supervisor)

Peer reviewer/sign-off for Level 2 flows.

  • ledger.view
  • review.sign
  • Read evidence needed to verify
  • No authoring/admin access

Verifier actions may require step-up auth depending on policy.

7.7 Client / Stakeholder (Glass Box User)

Read-only ledger visibility + (optional) ratification.

  • ledger.view (typically via portal projection)
  • ledger.ratify (if enabled)
  • No authoring/admin access; no execution write access

Notes:

  • Often provisioned via magic link (Tier 1) or dedicated accounts (Tier 2)
  • Ratification typically requires step-up auth

7.8 Reviewer (External SME)

Read-only access to authoring content (org-scoped), optional expiry.

  • questions.read, evaluations.read
  • No publish/edit

7.9 Auditor (Internal/External)

Inspection of records and reporting.

  • reporting.view
  • Optional reporting.view_named (strictly controlled)
  • ledger.view
  • No authoring/admin edits

7.10 Billing Admin

Finance-only access.

  • billing.read_invoice
  • billing.manage_subscription No access to authoring/execution/reporting/ledger data.

8. External Sharing (Magic Links) — First-Class

Evalium supports secure, scoped, revocable links via a Link Principal.

  • Engagement/Project summary (glass box)
  • Evaluation summary
  • Assignment/Cohort summary
  • Single ledger entry / submission view
  • Aggregated-only reporting
  • Aggregated + Named (PII-bearing) where explicitly enabled
tenant_id
org_unit_id
scope_type (engagement|evaluation|assignment|submission)
scope_id
permissions[] (e.g., ledger.view, reporting.view, ledger.ratify)
expires_at
can_view_named: true|false
display_name (optional)
revoked_at (optional)
created_by_user_id

8.3 Rules

  • Links are revocable
  • Links are RLS-enforced
  • Links should be short-lived by default
  • PII-bearing views require explicit enablement (can_view_named=true) AND the link must include the right permission(s)

9. Guest & Temporary Access Patterns

9.1 Temporary Reviewers

  • Guest accounts with expiry timestamps
  • Org-scoped and/or evaluation-scoped
  • Read-only authoring review

9.2 Guest Reporting / Client Visibility

  • Magic link viewer (Tier 1 / Link Principal)
  • Or dedicated stakeholder account (Tier 2+)
  • Always RLS enforced

10. Custom Roles & RBAC Engine (Non-Negotiable)

The RBAC engine is final. UI may only expose a subset of roles initially, but the backend must support unlimited custom roles via capability lookups.

Rules:

  • No hardcoded role checks
  • JWT contains role_ids, never capability lists
  • Capability lookup is server-side and cacheable
  • Role changes must take effect immediately without reissuing JWTs (server-side lookup ensures this)

10.1 Custom Role Management (Roadmap)

  • Phase 1: default roles managed via API

  • Phase 2: UI Role Editor:

    • create/clone/edit roles
    • capability selection UI
    • org-scoped role assignment rules

11. UX Principles (SMB-Friendly, Defensibility Underneath)

  1. Hide complexity when unnecessary (single org = no org UI)

  2. Prefer “show/hide” over permission errors (but backend still blocks)

  3. Low friction access:

    • magic links for viewers/clients/auditors
    • passwordless login for teams
  4. Avoid heavy legal jargon in UI labels:

    • “History”, “Amended”, “Voided”, “Verified”, “Approved”
  5. Preserve the “Save button lie”:

    • workflow feels editable
    • history remains immutable and visible when needed

12. Implementation State Summary

Already Implemented (per current architecture direction)

  • Multi-tenant + multi-org silo architecture
  • RLS + TxManager using SET LOCAL
  • Combined tenant+org RLS policies
  • Lean JWT approach (role_ids only)
  • Version freeze model on submission (snapshot-driven reporting)
  • Core RBAC scaffolding endpoints (roles, bindings) where present

To Implement / Align Next

  1. Role/terminology updates (Candidate→Operator, Proctor→Verifier, add Client/Stakeholder)
  2. Ledger-specific capability split (execution.write vs review.sign vs amend/void/ratify)
  3. Step-up auth contract enforcement for high-stakes actions
  4. Magic link expansion to support Glass Box ledger views (engagement-scoped)
  5. Verification policy enforcement (required context metadata + tier requirements) at write boundaries

13. Future-Proof Guarantees

This access model ensures:

  • Safe scaling to many org silos
  • Custom roles without redesign
  • Secure external sharing with revocation and expiry
  • Controlled PII exposure (named vs aggregated)
  • Evolution of auth methods without re-architecting
  • A clear path to client ratification and milestone state proofs