Evalium – Technical Foundations (Execution Ledger Edition)
Engineering rules, invariants, and development standards.
This document defines how Evalium must be built — the principles, mandatory patterns, and guardrails that ensure:
- Operational defensibility (append-only ledger, chain-of-custody, verifiable history)
- Security (RLS as the boundary, capability auth)
- Scalability + maintainability (modular monolith, event-driven boundaries, consistent DB access)
For structural diagrams and system component relationships, see:
➡️ docs/architecture/architecture
For the K/O/E domain model and terminology, see:
➡️ docs/product/KOE-model.md
0. Core intent (non-negotiable)
Evalium is not “a place where data is edited.”
Evalium is an Execution Ledger: a system that records what happened, who did it, when, and under what conditions — and can prove it later.
Implication: the system must be safe under scrutiny:
-
You can correct mistakes, but you cannot erase history
-
You can present “editable” UX, but the backend must remain forensically truthful
-
The canonical truth is the ledger, not the UI, not an export, not a PDF
0.1 Canonical Language & Alias Policy (Non-Negotiable)
Evalium intentionally uses broad, cross-industry terminology for all core
data models, APIs, and architectural documentation.
This is a design decision, not an accident.
Canonical Language (Source of Truth)
The following terms are canonical and MUST be used in:
-
database schemas
-
backend services
-
APIs and contracts
-
architectural documentation
-
ADRs and invariants
Examples include (non-exhaustive):
-
Evaluation
-
Programme
-
Assignment
-
Session
-
Submission
-
Knowledge / Observation / Evidence (K/O/E)
-
Engagement (where defined)
-
Ledger, Amendment, Void, Snapshot
These terms are intentionally broad so the platform can serve
multiple industries without re-architecting its core.
Alias Language (Presentation Only)
Industry-specific or customer-preferred terminology MAY be introduced only as an
alias layer, for example:
-
UI labels
-
customer-specific configuration
-
documentation overlays
-
reporting views
-
client portals
Examples:
-
“Evaluation” → “Checklist”, “Inspection”, “Audit”
-
“Programme” → “Assurance Pack”, “Certification Path”
-
“Knowledge” → “Licence to Operate”
-
“Observation” → “Field Check”
-
“Evidence” → “Artefact”
Aliases MUST NOT:
-
appear in database table names
-
appear in API contracts
-
change business logic semantics
-
introduce conditional behaviour in core services
Invariant
**The system is built once, described broadly, and adapted at the edges.
The core language does not change per industry.**
If an industry concept cannot be expressed cleanly using the canonical model,
the model may be extended — but it must remain generic.
0.2 Evidence Doctrine (First-Class Ledger Primitive)
Evidence is a first-class Execution Ledger primitive. It is not just:
- a file attachment
- a question type
- a UI-only concept
Evidence exists to substantiate a claim in the K/O/E model and must be:
- immutable and auditable
- context-aware when policy requires it
- recorded as append-only ledger events (approve/reject/void)
Evidence rigor is policy-driven:
- The system MUST support Level-4 (context-verified) evidence.
- The system MUST NOT require Level-4 unless the evaluation policy demands it.
Entry points may vary (standalone, inline, mixed), but all evidence flows through the same core model and ledger events.
0.3 Normal Operation vs. Forensic Depth
Evalium is designed to be Calm by Default.
Most users interact with the system through normal operation flows:
- Saving work
- Issuing tasks
- Reviewing results
- Sharing outcomes
During normal operation, technical invariants such as immutability, access control, and audit integrity operate silently in the background. Users are not required to understand or manage these guarantees.
Forensic depth is exposed progressively and situationally:
- When results are corrected
- When changes have downstream impact
- When outcomes must be explained or defended
- When governance or compliance actions are taken
This approach allows Evalium to remain easy to adopt for SMBs and professional services, while remaining defensible under audit or dispute.
Concrete examples of normal operation and depth discovery are documented in Normal Use & Depth Discovery, with language constraints enforced by Language & Tone Guidelines.
1. Service Architecture Principles
The backend follows a Modular Monolith architecture: a single deployable unit, composed of distinct, decoupled services with clear boundaries.
1.1 Single-responsibility services
Large, multi-domain services should be progressively refactored into smaller, focused services. New features should be built in new, focused services where appropriate.
1.2 Event-driven communication
For cross-boundary interactions, prioritize decoupled, event-driven listener patterns.
Core services emit domain events; other services listen and react. Prefer this over hard-coded service-to-service dependencies.
2. Core Engineering Principles (Unbreakable)
2.1 Backend-first development
All backend flows must be implemented, validated, and tested before any UI is built.
Feature implementation order:
- Schema + sqlc queries
- Service logic
- HTTP handler
- Shell script regression
- Go integration test
- API cookbook example
- Frontend wiring
2.2 Database is the security boundary
Evalium uses PostgreSQL Row-Level Security (RLS) as the final enforcer of tenant and org isolation.
No handler, service, or API may bypass RLS assumptions.
2.3 All business logic is versioned
authoring objects (evaluations, questions, scoring config, feedback config) are immutable once versioned.
Changes always create new versions, never overwrite history.
2.4 Ledger + snapshots guarantee forensic integrity
Execution records are immutable and verifiable forever.
- The ledger is the ground truth (append-only)
- Snapshots freeze authoring structure and metadata at execution time so later authoring changes cannot rewrite history
2.5 Capability-based authorization
Roles map to capabilities; capabilities map to allowed actions.
No hard-coded role logic in handlers.
2.6 Passwordless authentication (with step-up support)
Magic-link login is supported for low-friction access.
However, the backend must support step-up authentication for high-stakes ledger actions (see Section 4.4).
2.7 One-way DB access
No service or handler may query the DB directly.
All DB access must go through TxManager.
2.8 WORM ledger invariant (Write Once, Read Many)
Execution ledger tables are INSERT-only.
- No
UPDATEorDELETEof execution records - Corrections are additive events referencing originals
- “Delete” in UX becomes Void or Amend in data
Clarification: This invariant applies to execution ledger tables (submissions, submission_items, evidence events, approvals, ratifications). Operational support tables (e.g. sessions, assignments, projections, caches) may be mutable where explicitly defined.
Operational ledgers (audit_logs, compliance_ledger) are also append-only, but are not part of the execution ledger.
Enforcement details: docs/architecture/LEDGER-BOUNDARY-AND-ENFORCEMENT.md.
2.9 Contextual binding is mandatory (when required)
Every execution ledger write must accept a context_metadata payload.
- If a workflow requires Level 4 (Context-Verified) execution, the backend MUST reject writes without required context
- Context requirements are policy-driven and enforced server-side
2.10 Keyed idempotency is mandatory for durable writes
Evalium MUST enforce keyed idempotency on in-scope mutating endpoints.
Rule:
POST,PATCH,PUT, andDELETEoperations requireIdempotency-Keyunless explicitly exempted by policy.
Purpose:
- prevent duplicate durable/admin writes under retry conditions
- guarantee deterministic replay behavior for the same logical request
Contract minimums:
- missing key rejected at the handler boundary
- key length validated at the handler boundary
- same key + same payload replays stored response
- same key + different payload is rejected as conflict
Canonical policy and implementation standard:
➡️ docs/architecture/IDEMPOTENCY-AND-RETRY-POLICY.md
2.11 Localized content contract is mandatory for localized resources
For resources that carry user-authored product content (for example question payloads and passages), Evalium MUST use a canonical localized envelope:
defaultLocale+localesare the source-of-truth shape at rest and on non-projected reads- locale identifiers must be well-formed BCP-47 tags and normalized before persistence
- read endpoints may support
?lang=<tag>projection; when projection is requested, the response must include the resolved locale value - request payloads may be lenient for backward compatibility, but response contracts must remain deterministic per endpoint mode
Boundary: this governs product/authored content only. Frontend chrome (labels, menus, buttons) remains frontend i18n assets.
Guardrail:
- OpenAPI localization lint (
scripts/check_openapi_localization.sh) must pass in CI.
2.12 Taxonomy is the only canonical contract for tag-like authoring requirements
For authoring/content-definition surfaces that express tag-like requirements (for example question taxonomy assignment, evaluation bucket criteria, and passage question-source bucket criteria), taxonomy MUST be the source of truth.
- Canonical write contracts MUST use taxonomy term identifiers or explicit taxonomy replacement endpoints.
- New authoring contracts MUST NOT introduce raw string fields such as
tags,tagFilter, orexcludeTagsas the architectural source of truth. - If a compatibility shim still accepts legacy raw-tag input, it MUST translate immediately to taxonomy semantics and be documented as transitional rather than canonical.
- OpenAPI and generated/frontend contract layers define the target-state API shape; local convenience adapters do not override that rule.
Boundary: this governs authoring/content-definition semantics only. UI copy may still say "tags" and read/query surfaces may carry temporary compatibility labels while the underlying contract converges on taxonomy.
Guardrails:
- Backend architecture guard
backend/internal/architecture/taxonomy_authoring_contract_guard_test.gomust keep canonical evaluation-bucket contracts taxonomy-only and raw-tag seams explicitly allowlisted. - Frontend contract guard
frontend/src/lib/contract/taxonomy-authoring-contract.test.tsmust keep generated bucket contracts taxonomy-only and prevent raw-tag drift beyond the legacy contract seam.
3. Database Access Rules (Critical)
These rules prevent data leaks and guarantee correct RLS enforcement.
3.1 All DB access MUST go through TxManager
Never use:
pool.Query(...)pool.Exec(...)context.Background()
Always use:
TxManager.WithinTx(ctx, func(tx pgx.Tx) error {
// All SQL inside here
})
3.2 TxManager injects scope via SET LOCAL
Inside each transaction:
SET LOCAL app.tenant_id = '...';
SET LOCAL app.org_unit_id = '...';
SET LOCAL app.org_scope_all = 'true' | 'false';
Scope values come from authenticated context (JWT claims).
Handlers never set scope manually.
3.3 SET LOCAL only — never SET SESSION
SET SESSION risks leaking scope across pooled connections.
SET LOCAL is cleared automatically at transaction end.
3.4 Mandatory row security
Ensure:
SET row_security = on;
Applied to every new connection.
3.5 Siloed tables must include
tenant_id UUID NOT NULLorg_unit_id UUID NOT NULL- RLS policies
- Index
(tenant_id, org_unit_id)
3.6 Canonical lock ordering
To avoid deadlocks under load, acquire locks in a consistent order:
assignmentsdelivery_sessionsevaluations/evaluation_versionsresult_corrections/result_correction_batchessubmissions/submission_items/submission_score_versions
Authoritative reference:
➡️ docs/implementation/db-lock-order.md
4. Authentication & Identity
Evalium supports low-friction identity while remaining non-repudiation-ready.
4.1 Magic links (baseline)
Magic link login for users and client reporting views.
All /api/v1/** routes require a valid JWT.
Only unauthenticated routes:
/auth/login/auth/verify/health
Server must fail fast if JWT_SECRET is missing.
4.2 JWT payload (minimal)
{
"userId": "...",
"tenantId": "...",
"orgUnitId": "...",
"roleIds": ["author", "reviewer"]
}
JWT must NOT contain:
- capabilities
- content metadata
- evaluation data
- structured tags
Capabilities are resolved server-side.
4.4 Step-up authentication for high-stakes actions
Some actions require stronger proof than session trust.
Examples:
- State ratification
- Level 4 context-verified execution
- Voiding a ratified record
- Changing verification policy on a published evaluation
Backend contract:
- Request must include step-up proof
- Backend must validate before ledger write
- Ledger event must record method used
Implementation details are pluggable (MFA, WebAuthn, etc.).
5. Authorization (RBAC + Capabilities)
5.1 Capability-based model
Capabilities are atomic permissions such as:
sessions.launchledger.viewledger.ratifyresults.viewusers.manage
5.2 Roles
Roles are bundles of capabilities.
UI labels may change (Operator, Verifier, Client), but enforcement remains capability-driven.
6. Execution Ledger, Versioning & Snapshots
This section defines forensic truth.
6.1 Two worlds
- authoring: versioned, replaceable
- Execution: immutable, append-only
6.2 Immutable execution (WORM)
Execution ledger tables include:
- submissions
- submission_items
- evidence events / attachments
- review approvals
- ratification events
No record is ever overwritten.
6.3 Snapshots
Each submission snapshot freezes:
- evaluation structure
- question versions
- tags
- scoring config
- feedback config
- org scope
- assignment metadata
Reporting must use the snapshot, not live authoring tables.
6.4 Amendments (the “Save button lie”)
Users expect “edit”.
Backend enforces amend.
- Original record preserved
- New amendment references original
- Current state is a projection
UI must surface history.
6.5 Voids (replace Delete)
There is no delete.
Void means:
- Original record remains
- Void event appended with reason, actor, timestamp
- Visible in audit views
6.6 Corrections vs amendments
Derived outcomes (scores, pass/fail) are corrected via new versions, never overwrites.
7. Verification Levels (Data Trust Model)
Not all data is equal.
7.1 Levels
- Self-Verified
- Peer-Verified
- System-Verified
- Context-Verified
7.2 Enforcement
Required level defined by policy.
Backend enforces:
- context requirements
- review flows
- system validation
- L4 is supported everywhere, but never required unless policy demands it
7.3 Context metadata (minimum contract)
{
"client": { "device_id": "...", "user_agent": "...", "ip": "..." },
"time": { "observed_at": "...", "network_time_source": "..." },
"geo": { "lat": 0.0, "lng": 0.0, "accuracy_m": 0, "source": "..." },
"auth": { "step_up": false, "method": "magic_link|webauthn|mfa", "id": "..." }
}
Policy defines required fields.
Missing required fields → reject write.
Knowledge (K) events may be self-declarative or system-verified, depending on protocol design. Both forms are treated as execution records when submitted.
8. Silo Isolation Rules
8.1 Mandatory org assignment
Every siloed table has exactly one org_unit_id.
8.2 Scope flow
Auth → Scope middleware → TxManager → RLS
9. Assignment → Session → Submission
This lifecycle represents execution logging, not “test taking”.
10. Evidence & Structured Tagging
10.1 Evidence orthogonality (explicit)
Evidence is orthogonal to Knowledge and Observation.
- Evidence may be captured by the subject (self-evidence)
- By an observer/inspector (third-party evidence)
- Or as a standalone evaluation
In all cases, evidence binds to the same execution ledger, verification rules, and context requirements.
10.2 Evidence is forensic capture
Evidence uploads are not “files”.
They are:
- ledger events
- context-bound
- hashable
- attributable
11. Reporting & Views
Artifacts are projections.
Ledger is truth.
PDFs are snapshots.
11.1 Glass Box Principle (Chain-of-Custody Projector)
The Glass Box is a read-only, ledger-backed projector for external truth.
It MUST:
- read from ledger + snapshot sources only
- avoid
reporting_*or other derived tables as truth - enforce scope at the SQL layer for every query
It MUST NOT:
- mutate data
- become a dashboard or reporting layer
12. Frontend Contract Rules
Frontend never enforces permissions.
Backend decides.
UI must never offer destructive ledger actions.
13. Developer Workflow Standards
No direct DB access. No snapshot mutation. No destructive execution operations.
14. Summary
Evalium enforces:
✔ append-only truth ✔ amendments instead of edits ✔ contextual binding ✔ verification levels ✔ keyed idempotency on durable writes ✔ deterministic localized-content contract on localized resources ✔ step-up identity readiness ✔ cryptographic ratification readiness ✔ forensic-grade reconstruction
For system structure, see:
➡️ docs/architecture/architecture