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