# Evalium Changelog
## [2026-03-11] — Taxonomy-only authoring contract (Docs + Guardrails)
### Added
- Foundational rule that taxonomy is the canonical source of truth for tag-like authoring/content-definition requirements:
- `docs/architecture/FOUNDATION.md`
- Focused backend/frontend guardrails to keep canonical bucket contracts taxonomy-only and confine any remaining raw-tag seams to explicit compatibility boundaries:
- `backend/internal/architecture/taxonomy_authoring_contract_guard_test.go`
- `frontend/src/lib/contract/taxonomy-authoring-contract.test.ts`
### Changed
- `docs/product/content-library-approach.md` now distinguishes canonical taxonomy-native contract surfaces from transitional raw-tag compatibility shims.
## [2026-03-07] — Content Document v1 Contract (Docs)
### Added
- New canonical authored-content contract doc: `docs/architecture/CONTENT-DOCUMENT-V1-CONTRACT.md` covering:
- localized `ContentDocument v1` envelope rules
- frozen v1 capability model for question/evaluation/passage/media content work
- concrete runtime API semantics for passage reads, evaluation preview, and submission/runtime replay
- compile-at-publish and pin-at-submission lifecycle rules
- New ADR formalizing the contract and runtime lifecycle boundary:
- `docs/architecture/ADR/0015-content-document-v1-and-runtime-lifecycle.md`
### Changed
- `docs/EVALIUM-INDEX.md` now includes the authored-content contract in Tier 1 guidance and required assistant context.
- `docs/product/content-library-approach.md` now points to the canonical authored-content contract so follow-on packages do not redefine it.
## [2026-02-24] — Status And Readiness Contract (Docs)
### Added
- New canonical contract doc: `docs/architecture/STATUS-AND-READINESS-CONTRACT.md` covering:
- dual-time readiness semantics (`defensibleAtExecution`, `readyNow`)
- explainable KOE/readiness blocks (`reasons`, `asOf`, policy/snapshot refs)
- capability-redacted glass-box projections
- exceptions queue triage model and status-plane normalization
- packs lineage completeness requirements
- skills explainability gating on persisted provenance facts
### Changed
- `docs/EVALIUM-INDEX.md` now includes the status/readiness contract in Tier 1 architecture guidance and required assistant context.
- Product status docs now explicitly track readiness-lens and lineage follow-ups (`implementation-status-feature-matrix.md`, `content-library-approach.md`).
## [2026-02-24] — Proof Readiness/KOE Phase 9 (ADR + CI Contract Guardrail)
### Added
- New ADR formalizing normalized status/readiness contract enforcement:
- `docs/architecture/ADR/0014-normalized-status-readiness-contract-enforcement.md`
- New CI guardrail script to prevent drift between canonical status/reason constants and OpenAPI readiness enums:
- `scripts/check_status_readiness_contract.sh`
### Changed
- `Makefile` lint-arch pipeline now runs `check_status_readiness_contract.sh`.
## [2026-02-24] — Content Packs Lineage Read Surface (Provenance Chain)
### Added
- New pack revision lineage read endpoint:
- `GET /api/v1/content-packs/\{id\}/revisions/\{revisionId\}/lineage`
- Lineage response includes:
- revision metadata (`revisionNumber`, `manifestHash`)
- revision item source chain (`pos`, `sourceEvaluationVersionId`, `metadata`)
- install chain (`installedEvaluationId`, `installedEvaluationVersionId`, timestamps)
- submission snapshot linkage metrics per install (`submissionCount`, `snapshotLinkedSubmissionCount`)
- summary aggregates (`installCount`, `submissionCount`, `snapshotLinkedSubmissionCount`)
- Integration coverage:
- `backend/internal/http/handler/integration/content_packs_lineage_integration_test.go`
### Changed
- Content pack smoke now asserts lineage retrieval in addition to publish/install:
- `backend/tests/content_packs.sh`
- OpenAPI schemas/paths updated for lineage response contracts:
- `openapi/components/schemas/libraries.yaml`
- `openapi/paths/libraries/library-revision-lineage.yaml`
- `openapi/openapi.yaml`
## [2026-02-24] — Proof Readiness/KOE Phase 8 (Normalized Status Contract Enforcement)
### Added
- Shared backend status contract package for canonical readiness/triage/reason/state taxonomy:
- `backend/internal/services/statuscontract/contract.go`
- New contract-level unit coverage:
- `backend/internal/services/statuscontract/contract_test.go`
- `backend/internal/services/reporting/exceptions_contract_test.go`
- Expanded integration contract assertions for canonical state/reason enums across:
- submission readiness surfaces + glass-box redaction behavior (`status_readiness_contract_integration_test.go`)
- defensibility exception submission/engagement/programme lists (`reporting_exceptions_integration_test.go`)
### Changed
- Readiness derivation now consumes shared status constants instead of local duplicated strings:
- `backend/internal/http/handler/status_readiness.go`
- Defensibility exception services now normalize/validate readiness state, triage state, and reason codes against the shared contract before emitting API rows:
- `backend/internal/services/reporting/exceptions.go`
- OpenAPI readiness schema tightened with explicit enums for KOE states, readiness reason codes, and `logicVersion`:
- `openapi/components/schemas/readiness.yaml`
## [2026-02-24] — Proof Readiness/KOE Phase 3 (Capability Redaction Contracts)
### Added
- Contract integration coverage for readiness-reason visibility boundaries:
- internal submission surfaces preserve full reason codes (`GET /api/v1/submissions/\{id\}`, evaluation/user submission lists)
- glass-box submission link-principal surface redacts reasons by default
- privileged link-principal token path verifies explicit reason visibility override
- test: `backend/internal/http/handler/integration/status_readiness_contract_integration_test.go`
- Smoke coverage now asserts glass-box reason redaction on dual-time readiness flow:
- `backend/tests/proof_readiness_dual_time_smoke.sh`
### Changed
- Link-principal claim model now supports explicit readiness reason visibility capability:
- optional JWT claim: `canViewReadinessReasons`
- `GET /api/v1/glassbox/submissions/\{id\}` now redacts `koeStatus.*.reasons` and `proofReadiness.*.reasons` unless `canViewReadinessReasons=true`.
- Added handler contract unit test for deterministic reason redaction while preserving derived states/refs:
- `backend/internal/http/handler/status_readiness_test.go`
## [2026-02-24] — Proof Readiness/KOE Phase 4 (Defensibility Exceptions Queue Scaffold)
### Added
- Submission-lens defensibility exceptions queue API:
- `GET /api/v1/defensibility/exceptions/submissions`
- `POST /api/v1/defensibility/exceptions/submissions/{submissionId}/triage`
- Queue contract fields include derived readiness + reason codes plus mutable triage metadata:
- `state` (`open|acknowledged|resolved`)
- `ownerUserId`
- `firstSeenAt` / `lastSeenAt`
- `suppressedUntil`
- New reporting triage storage table with RLS/grants:
- `reporting.defensibility_exception_triage`
- migrations: `286_defensibility_exception_triage.sql`, `287_defensibility_exception_triage_policies.sql`
- Coverage:
- integration: `backend/internal/http/handler/integration/reporting_exceptions_integration_test.go`
- smoke: `backend/tests/defensibility_exceptions_smoke.sh` (wired into `backend/tests/test_defensibility_all.sh`)
### Changed
- Reporting routes now expose defensibility exception queue/triage handlers with capability enforcement.
- OpenAPI includes defensibility exception queue and triage request/response schemas + paths.
- Added queue lifecycle refresh endpoint:
- `POST /api/v1/defensibility/exceptions/submissions/refresh`
- Refresh lifecycle behavior now:
- upserts active submission exceptions (reopens previously resolved rows)
- marks cleared submission exceptions as `resolved`
- keeps triage metadata (`ownerUserId`, `suppressedUntil`) intact unless row is reopened.
## [2026-02-24] — Proof Readiness/KOE Phase 5 (Engagement + Programme Rollups)
### Added
- Rollup defensibility exceptions read APIs:
- `GET /api/v1/defensibility/exceptions/engagements`
- `GET /api/v1/defensibility/exceptions/programme-requirements`
- Rollup payload contract now includes aggregate impact counts:
- `activeSubmissionCount`
- `blockedSubmissionCount`
- `needsReviewSubmissionCount`
- OpenAPI path/schema coverage for engagement and programme-requirement rollup queue responses.
- Expanded queue coverage:
- integration: `backend/internal/http/handler/integration/reporting_exceptions_integration_test.go`
- smoke: `backend/tests/defensibility_exceptions_smoke.sh`
### Changed
- Queue refresh (`POST /api/v1/defensibility/exceptions/submissions/refresh`) now synchronizes lifecycle across all three lenses in one pass:
- submission
- engagement
- programme requirement
- Rollup list endpoints reuse queue filters (`state`, `readinessState`, `ownerUserId`, `includeSuppressed`, pagination) and emit triage metadata consistently with the submission lens.
## [2026-02-24] — Proof Readiness/KOE Phase 6 (Rollup Triage Writes)
### Added
- Rollup-lens triage write APIs:
- `POST /api/v1/defensibility/exceptions/engagements/{engagementId}/triage`
- `POST /api/v1/defensibility/exceptions/programme-requirements/{programmeRequirementId}/triage`
- Service-level triage update methods for engagement/programme subjects with existence checks and shared state validation.
- OpenAPI path/schema coverage for rollup triage responses.
### Changed
- Reporting handler now shares a single triage-body parser/validator across submission, engagement, and programme triage endpoints.
- Defensibility integration + smoke coverage now verifies rollup triage suppression visibility (`default` vs `includeSuppressed`) in addition to submission triage lifecycle.
## [2026-02-24] — Proof Readiness/KOE Phase 7 (Policy Snapshot Versioning)
### Added
- Submission snapshot verification policy now persists versioned policy provenance beyond raw required level:
- `version_snapshot.verificationPolicy.policyRef`
- `version_snapshot.verificationPolicy.requireContext`
- `version_snapshot.verificationPolicy.requireStepUp`
- `version_snapshot.verificationPolicy.requiredContextFields`
- Policy ref generation now uses a deterministic profile hash over verification policy shape (`level + context/step-up requirements + required context fields`), producing stable refs of form:
- `verification_policy/v1/level_N/<digest>`
- New policy-ref lookup helper for readiness list surfaces to map execution/current levels to profile-based refs without per-row queries.
### Changed
- Readiness policy derivation now prefers snapshot-captured execution policy refs and falls back to legacy level refs only for older snapshots.
- Submission/evaluation/user readiness list surfaces now emit profile-based `policyRefCurrent` values based on active policy profiles.
- `backend/tests/proof_readiness_dual_time_smoke.sh` now asserts policy refs by versioned prefix rather than legacy exact string.
- Unit/snapshot coverage updated:
- `backend/internal/services/submission_readiness_policy_test.go`
- `backend/internal/services/results_service_snapshot_test.go`
## [2026-02-24] — Proof Readiness/KOE Phase 1 (Backend + OpenAPI)
### Added
- Canonical derived submission status logic in backend handler mapping:
- emits `koeStatus` (`knowledge`, `observation`, `evidence`) with stable reason codes
- emits `proofReadiness` (`defensibleAtExecution`, `readyNow`, `asOf`, `logicVersion`, `computedAgainst.snapshotRef`)
- Shared OpenAPI schemas for readiness/KOE blocks:
- `openapi/components/schemas/readiness.yaml`
- Contract coverage test for reason-code/state stability:
- `backend/internal/http/handler/status_readiness_test.go`
### Changed
- Submission response DTOs now include derived status blocks on:
- `GET /api/v1/submissions/\{id\}`
- submission list surfaces that reuse `Submission` mapping
- `GET /api/v1/glass-box/submissions/\{id\}`
- OpenAPI submission + glass-box schemas now reference shared readiness/KOE components.
## [2026-02-24] — Proof Readiness/KOE Phase 2 (Dual-Time Policy Refs)
### Added
- Shared submission policy-ref resolver (`backend/internal/services/submission_readiness_policy.go`) that derives:
- execution policy from submission snapshot verification policy (`version_snapshot.verificationPolicy.requiredLevel`)
- current policy from the evaluation active version required verification level
- canonical refs (`verification_level/v1/level_N`)
- Readiness divergence rule: when current policy is stricter than execution policy, `readyNow` includes `policy_requirements_tightened`.
### Changed
- `GET /api/v1/submissions/\{id\}` now uses service-derived policy refs when building `proofReadiness`.
- `GET /api/v1/glass-box/submissions/\{id\}` now uses service-derived policy refs when building `proofReadiness`.
- Submission list surfaces now use batched service-derived policy refs (`GET /api/v1/evaluations/\{id\}/submissions`, `GET /api/v1/users/\{id\}/submissions`) with one lookup query by submission IDs (no per-row active-policy queries).
## [2026-02-24] — authoring Dependency Usage Graph Parity
### Added
- Paired dependency/where-used usage endpoints for inventory entities:
- `GET /api/v1/questions/\{id\}/usage`
- `GET /api/v1/evaluations/\{id\}/usage` (expanded payload)
- Question usage graph dependencies:
- authored evaluation references (section item links)
- passage link references
- Evaluation usage graph dependencies:
- assignment references
- programme requirement references
- content pack item references
- Regression coverage for dependency usage graph behavior:
- integration coverage in `backend/internal/http/handler/integration/questions_usage_integration_test.go`
- integration coverage in `backend/internal/http/handler/integration/evaluations_usage_integration_test.go`
- authoring smoke `backend/tests/dependency_usage_smoke.sh` (wired into `backend/tests/test_authoring_all.sh`)
### Changed
- Content library roadmap/status docs now mark dependency/where-used graph parity as implemented and move next focus to packs provenance/lineage expansion.
## [2026-02-23] — authoring Bulk Actions Parity
### Added
- Paired authoring bulk action endpoints:
- `POST /api/v1/questions/bulk`
- `POST /api/v1/evaluations/bulk`
- Bulk action support for `archive`, `restore`, and `taxonomyReplace` with:
- `dryRun` preview mode
- per-item result reporting (`ok`, `code`, `message`, change flags)
- aggregate summary reporting (`total`, `succeeded`, `failed`, `changed`)
- authoring regression coverage for the new bulk surface:
- integration test `backend/internal/http/handler/integration/bulk_actions_integration_test.go`
- smoke script `backend/tests/authoring_bulk_actions.sh` wired into `backend/tests/test_authoring_all.sh`
### Changed
- Content library roadmap/status docs now mark bulk metadata parity as implemented and move next focus to dependency graph expansion + packs provenance/lineage.
## [2026-02-20] — Localization Contract Hardening
### Added
- CI OpenAPI localization guardrail `scripts/check_openapi_localization.sh` (with text-field allowlist) to enforce shared `LangQueryParam` usage and localized passage schema contracts.
- Bulk import content-type smoke `backend/tests/bulk_import_content_types.sh` covering raw `text/csv` preview/commit + idempotent replay + update semantics for users and subjects.
### Changed
- Passage localization contract tightened: plain content is normalized to canonical localized envelope (`defaultLocale` + `locales`, default `en`) on create/update.
- Question detail localization now supports `?lang=` projection for active version payloads with explicit resolved locale metadata.
- Locale handling now validates well-formed BCP-47 tags for passage localized payloads and `lang` query projection.
- Reporting smoke polling/readiness waits were hardened to reduce transient projection race failures in CI.
- OpenAPI import contracts now document both supported request media types (`text/csv` and JSON `{csv: ...}`) for user/subject preview + commit endpoints and typed user import responses.
## [2026-02-20] — Content Packs Foundation
### Added
- Library packs schema migration (`backend/internal/db/schema/280_libraries.sql`) with tenant/org RLS policies for packs, items, revisions, and installs.
- Content Packs service + HTTP routes for pack create/list/detail, item add, revision publish, and revision install (served under `/libraries` paths):
- `GET /api/v1/libraries`
- `POST /api/v1/libraries`
- `GET /api/v1/libraries/\{id\}`
- `POST /api/v1/libraries/\{id\}/items`
- `POST /api/v1/libraries/\{id\}/publish`
- `POST /api/v1/libraries/\{id\}/revisions/\{revisionId\}/install`
- authoring smoke `backend/tests/libraries_packs.sh` (added to `backend/tests/test_authoring_all.sh`) covering end-to-end idempotent create/add/publish/install flows.
### Changed
- OpenAPI now documents Packs tag + content pack schemas/path contracts with required `Idempotency-Key` on pack write endpoints.
## [2026-02-20] — Assignment + Question Bulk Import
### Added
- Assignment import preview/commit endpoints:
- `POST /api/v1/assignments/import/preview`
- `POST /api/v1/assignments/import/commit`
- Question content import preview/commit endpoints:
- `POST /api/v1/questions/import/preview`
- `POST /api/v1/questions/import/commit`
- Atomic assignment import execution in `AssignmentsService` with row-level error attribution (`AssignmentImportRowError`) and keyed idempotent commit semantics.
- Atomic question import execution in `QuestionsService` with row-level error attribution (`QuestionImportRowError`) and keyed idempotent commit semantics.
- Assignment import smoke `backend/tests/assignments_import_idempotency.sh` and admin suite wiring in `backend/tests/test_admin_all.sh`.
- Question import smoke `backend/tests/questions_import_idempotency.sh` and authoring suite wiring in `backend/tests/test_authoring_all.sh`.
### Changed
- OpenAPI now documents assignment and question import request/response contracts for CSV and JSON `{csv: ...}` payloads.
## [2026-02-19] — Idempotency Policy Formalization (Docs)
### Added
- Canonical idempotency policy document: `docs/architecture/IDEMPOTENCY-AND-RETRY-POLICY.md` defining required scope, exemptions, header contract, replay semantics, and implementation standard.
- CI architecture guardrail `scripts/check_idempotency_routes.sh` to fail mutating routes missing `requireIdempotencyKey` (with policy exemptions allowlisted).
- CI OpenAPI guardrail `scripts/check_openapi_idempotency.sh` to fail mutating operations missing `IdempotencyKeyHeader` (with policy exemptions allowlisted).
### Changed
- `FOUNDATION.md`, `architecture`, `LEDGER-BOUNDARY-AND-ENFORCEMENT.md`, and `EVALIUM-INDEX.md` now explicitly reference keyed idempotency as a mandatory durable-write control.
- `security/roles-and-access-control.md` now makes capability-vs-idempotency responsibilities explicit for mutating routes.
- `implementation/api-response-contract.md` now aligns idempotency response semantics and governance checks with the canonical idempotency policy.
## [2026-02-16] — Idempotency Coverage Expansion
### Added
- Keyed idempotency for submission approval/review actions, claim/dispute events, and remediation batch apply/revert.
- Keyed idempotency for admin writes: user create, group create/member add, and role create/clone/assign.
### Changed
- Remediation apply/revert responses now replay idempotent results when `Idempotency-Key` matches.
## [2026-02-15] — Step-Up Proofs + Policy-Driven Verification
### Added
- `step_up_proofs` table + issuance on auth verify; step-up proofs carry method, expiry, and consumption.
- Proof validation for `auth.step_up` contexts (actor match + expiry + method allowlist).
### Changed
- L4 enforcement now requires valid step-up proof IDs across submissions, evidence actions, review actions, claims/disputes, and evaluation publish.
## [2026-02-15] — Claims + Disputes Lifecycle (B1/B2)
### Added
- Claims API with sources, event stream, validity windows, and L4 context enforcement.
- Disputes API with event stream (`challenge`, `response`, `resolution`, `withdrawn`, `note`) and L4 context enforcement.
### Changed
- Disputes default list hides withdrawn items; resolved remains visible by default.
## [2026-02-10] — Subject Imports + Filtering
### Added
- Subjects import preview/commit with idempotent commit semantics.
- Subject list filtering by type, asset type, tags, and identifiers (including alias matches).
## [2026-02-06] — Delegated Authority Provenance (A4)
### Added
- Optional `authority` context on submissions and evidence flows, merged into ledger metadata for approvals, decisions, and execution events.
### Changed
- Submission and evidence ledger events now preserve acting-on-behalf provenance when provided.
## [2026-02-06] — Visibility Events (A5)
### Added
- Visibility metadata (`visibilityType`, `visibilityChannel`) for view/export events on submissions, reporting exports, and Glass Box access.
## [2026-02-06] — Submission Approval State Gating (A1)
### Changed
- Submission approve/reject decisions are only permitted from `pending_review`; repeated decisions now return conflict.
## [2026-02-06] — Retention Intent Markers (A6)
### Added
- Submission retention intent fields (`retain_until`, `retention_basis`, `hold_flag`) with indexes for retention filtering.
### Changed
- Retention intent markers default from active `retention_policies` at submit-time (basis recorded as `policy:<id>`), while remaining non-automated for enforcement.
## [2026-02-06] — Enforcement Mode Registry (A7)
### Added
- Enforcement mode registry (`defensibility_enforcement_modes`) with seeded modes for core defensibility capabilities.
### Changed
- Enforcement mode declaration is now a first-class platform primitive.
## [2026-02-06] — Assignment Subjects Auto-Link (A8)
### Added
- Submissions now inherit assignment subject links at submit-time for durable actor/subject attribution.
### Changed
- Subject attribution is automatic when assignments carry a subject; manual attach remains for exceptions.
## [2026-02-06] — Assignment Target Visibility for Subjects
### Changed
- Assignment RLS now allows assignment targets to read their own assignments across org scope, enabling subject visibility for external inspectors.
## [2026-02-04] — Observation Four-Eyes Review Policy
### Added
- Evaluation version `review_policy` (`self_approve` | `four_eyes`) with schema enforcement.
- Four-eyes enforcement in submission approval (self-approval blocked when `reviewPolicy=four_eyes`).
- Service test `TestSubmissionApprovalFourEyesBlocksSelfApproval` and smoke `backend/tests/observation_submission_four_eyes.sh`.
### Changed
- Observation approval now respects evaluation review policy while keeping `approval_status` workflow intact.
## [2026-02-02] — Observation Batch Context + Assignment-Based Subject Visibility
### Added
- Assignment-to-subject linkage (`assignment_subjects`) to connect planning context to observed subjects (phase-1 one-subject-per-assignment).
- Batch grouping identifiers on assignments and submissions (`batch_id`) with execution-time copy into submissions.
- User scope helpers (`set_user_id`, `current_user_id`) for RLS evaluation in assignment-based visibility.
### Changed
- Subject RLS now permits assignment-based visibility for external inspectors (active assignments or completed submissions).
## [2026-02-01] — Observation Subjects + Findings Projection
### Added
- Subject/asset scaffolding: `subjects`, `subject_users`, `assets`, `asset_identifiers`, and `submission_subjects` tables with RLS policies, grants, and CRUD endpoints.
- Submission subject link API (`/submissions/\{id\}/subjects`) for attaching subjects to execution records.
- Findings projection: `reporting.report_findings` table, projection logic, and `finding.detected` ledger events for observation findings.
### Changed
- Observation findings now emit ledger events and are projected for reporting filters.
## [2026-01-30] — Glass Box + Link Principals + Submission Hashing
### Added
- Glass Box API lenses (engagement/assignment/submission/programme) with Link Principal auth, GET-only routing, scope-bound queries, and CI guard against `reporting_*` usage.
- Glass Box smoke suite (`backend/tests/test_glassbox_all.sh`) with assignment/submission/programme coverage.
- Submission state hashing: schema, sqlc, service + endpoint (`POST /submissions/\{id\}/hash`), ledger event `submission.state.hashed`, and hash smoke suite.
- Evidence storage tier events + worker + smoke (`backend/tests/evidence_storage_tier_worker.sh`) to append ledger storage tier updates.
### Changed
- Glass Box routes now use link-principal auth instead of cookie sessions.
- Evidence storage tier metadata is validated through the storage-tier worker path for lifecycle signals.
## [2026-01-31] — Observations Findings Evidence Requirement
### Added
- Observation findings enforcement that requires evidence metadata before approval, plus service coverage and a new observation smoke (`backend/tests/observation_findings_evidence_required.sh`).
### Changed
- Observation delivery now treats findings with mandatory evidence as a first-class execution invariant; observation smoke suite updated accordingly.
## [2026-01-29] — Evidence Hashing + Mixed Evidence
### Added
- Evidence ingestion hashing (SHA-256) and `storageTier` metadata (`HOT` default) enforced across evidence metadata and `evidence.file.attached` ledger events.
- Mixed evidence flow (answer + artefact in same item) with service test `TestMixedEvidenceWithAnswer` and smoke `backend/tests/evidence_mixed.sh`.
- Storage client `GetObject` support (MinIO + filesystem) to verify evidence assets at ingest time.
### Changed
- Results service now requires a storage client to validate evidence assets and records hash + storage tier in ledger metadata.
- Privacy worker reuses the shared storage client; evidence smokes assert real MinIO uploads.
## [2026-01-27] — Evidence Ledger Canonicalization + Verification Enforcement
### Added
- Standalone evidence flow: zero-item submissions, evidence metadata, and approve/reject ledger events; new service test `TestStandaloneEvidenceMetadataAndDecision` and smoke `backend/tests/evidence_standalone.sh`.
- Verification context enforcement for L4 evidence actions (step-up required), plus `backend/tests/verification_context_required.sh`.
- L4 submit enforcement requires `sessions.proctor` capability (integration test coverage).
- Canonical evidence spec: `docs/implementation/evidence-ledger-implementation.md`.
### Changed
- Evidence doctrine and entry points documented in FOUNDATION/architecture/KOE; implementation matrix now distinguishes standalone (implemented) vs inline (planned).
## [2026-01-28] — Inline Evidence + MinIO-Backed Evidence Smokes
### Added
- Inline evidence API routes (`/submissions/items/\{itemId\}/evidence/*`) with item-linked ledger events and service support.
- Evidence inline smoke (`backend/tests/evidence_inline.sh`) plus MinIO upload verification for evidence smokes (ledger/standalone/inline) to ensure real object storage is exercised.
### Changed
- Evidence ledger event expectations now key to `submission_item_id` for item evidence; tests and smokes assert association consistency.
## [2026-01-22] — Engagements Phase 3 Scaffolding
### Added
- Engagements core (`engagements`, `engagement_events`) with RLS and append-only event stream; engagement timeline projection combines engagement events, submissions, and ledger events.
- Engagement API (`/api/v1/engagements`) for create/list/update, event append, and timeline read.
- Engagement capability gates (`engagements.read`, `engagements.manage`) and default role bindings.
- Engagements smoke suite (`backend/tests/test_engagements_all.sh`) and `engagements_crud.sh`.
### Changed
- Assignments accept optional `engagementId`; submissions now carry `engagement_id` copied at submit time.
## [2026-01-21] — Evidence Ledger Review Lifecycle
### Added
- Evidence ledger review endpoints (`POST /api/v1/submissions/\{id\}/evidence/approve|reject`) with capability gates (`submissions.approve`, `submissions.review`) and append-only ledger events (`evidence.approved`, `evidence.rejected`).
- Evidence lifecycle smoke (`backend/tests/evidence_ledger_events.sh`) proves attach → approve/reject ledger trail; delivery suite now runs it by default.
- Service support for evidence decisions (`RecordEvidenceDecision`) and tests covering ledger event emission with reviewer metadata.
### Changed
- RBAC seeds include evidence review capabilities for default roles (owner/global-admin/org-admin approve; marker review).
## [2026-01-11] — ASVS L3 Cookie Sessions + MRQ Health Exports
### Added
- ASVS L3 web-session auth: server-side session records, HttpOnly Secure cookies, CSRF enforcement, rotation/overlap, and auth error codes for UI-safe handling.
- Question Health export bundle now emits MCQ and MRQ panel CSVs as a consistent multi-file ZIP (header-only when empty) with a manifest.
### Changed
- Question Health MRQ analytics now use scored-only denominators for facility and option/pattern percentages; invalid/exempt rates remain separate quality signals.
## [2025-12-24] — Reporting v1 Session Attempt Report
### Added
- Session attempt report endpoint (`GET /api/v1/session-attempts/{submissionId}`) with snapshot structure, item attempts, timing duration source/idle estimate, integrity metrics/attention level, tag key summaries, UI-ready item helpers, and `actionsAllowed`/`links` affordances (no PII).
- Evaluation summary adds score/outcome coverage + `scoresProvisional` for scored-but-ungraded attempts; attempt-based fields are explicitly named, hero completion rate is withheld when population coverage is partial/unknown, and `funnel.notStarted` is null with display text when population is unknown.
- Evaluation summary now includes score mode defaults (`scoreModeDefault`, `scoreShownDenominator`), histogram specs for score buckets, and completion-method rollups for UI-ready breakdowns.
- Evaluation summary endpoint now returns blocks-only scope/hero/distribution/funnel/outcomes/timing/activity/attention blocks plus `actionsAllowed` and param-based `links` (no path strings), with completion-time source metadata.
- Reporting projections now read completion method/timing from durable submissions fields (not delivery_sessions), and reporting FKs tolerate session cleanup.
## [2025-12-23] — Reporting v1 CSV Exports
### Added
- Question Health CSV export endpoint (`GET /api/v1/question-health/export`) with filtered rollup output (no PII).
- Evaluation summary endpoint (`GET /api/v1/evaluation-summary`) with assignment breakdowns, pass/fail rates, and completion timing.
## [2025-12-22] — Reporting v1 Projection Scaffold
### Added
- Reporting schema (`reporting.*`) with RLS, projection job queue, and dirty-set tracking for percentile recompute.
- Reporting projection service + workers (projection + percentiles) and `ReportingStore` seam for future replica reads.
- Question Health read endpoints with rollup-driven DTOs (no PII denormalisation; snapshot tags remain canonical).
### Changed
- Submission finalisation now enqueues reporting projections for deterministic rollups.
## [2025-12-21] — Compliance v1 Contracts Frozen
### Added
- OpenAPI now documents all Compliance Centre routes (`/api/v1/compliance/*`) with stable DTOs for jobs, ledger, retention incidents, and privacy packs; bundled spec regenerated.
- Compliance jobs expose SLA fields (startedAt, completedAt, durationMs) in HTTP responses; integration coverage locks hold-blocked forget, restrict place/lift, and unlink isolation.
- Privacy Pack endpoint tested end-to-end (artifact/manifest/event), DSAR export cooldown enforced with `force=true` override.
### Changed
- Compliance TODO/implementation docs aligned with “implemented vs deferred” status for v1; handler auth matrix is documented in OpenAPI.
## [2025-12-18] — Retention Policies + Telemetry Domain
### Added
- Retention policies are now required and drive anonymize/evidence_delete/telemetry_delete jobs with blocked/partial receipts; `privacy.retention.blocked` and `privacy.telemetry.deleted` events are emitted with normalized metadata.
- Compliance retention incidents endpoint (`GET /api/v1/compliance/retention/incidents`) returns blocked/failed ledger rows with sanitized metadata and filtering (domain, blockedReason).
### Changed
- Compliance outbox enforces idempotent inserts by tenant/event/job_id; retention policies no longer fall back when missing (error instead of silent skip).
- Legal holds are treated as `blocked` (not failed) for retention/telemetry jobs; compliance metadata sanitization strips error/payload before surfacing.
- Future retention domains (observation/proctoring/media-first) are deferred until those modules and storage tables exist.
## [2025-12-17] — Compliance Centre Foundations + RLS Test Hardening
### Added
- Compliance Centre backend flows are live: privacy jobs/ledger schemas patched (`scope`, `decision_metadata`, `legal_basis_text`, `artifact_hash`), MinIO/FS storage clients support delete, and forget/DSAR jobs write receipts (hashes) to the compliance ledger.
- Forget (Hybrid Scrub) endpoint added (`POST /api/v1/compliance/forget`, cap `privacy.forget`) and worker now scrubs evidence: deletes uploaded assets referenced in submission answers and redacts free-text fields; emits `receipt_ref` + artifact hash in ledger metadata.
- DSAR export end-to-end: `/api/v1/compliance/dsar-export` enqueues jobs, worker writes zip + receipt to storage, ledger exposes artifact/receipt refs; `dsar_minio_e2e.sh` script self-mints auth and verifies MinIO objects.
- Shared RLS-enabled test pool helper and test fixture cleanup; full Go suite (`go test ./...`) now runs green under RLS.
### Changed
- Integration test truncation retries increased to reduce deadlocks during setup; worker tests set row_security on connections to align with production RLS semantics.
## [2025-12-16] — MinIO E2E + Script Auth Guidance
### Added
- MinIO configuration doc now includes a local DSAR→MinIO walkthrough (envs, mc alias, `dsar_minio_e2e.sh` flow) with automatic auth via test helpers.
- FOUNDATION documents the required auth pattern for scripts/tests: source `auth_login_helper.sh` or `auth_env.sh` and reuse `AUTH_HEADER` (no hardcoded tokens).
- Compliance ledger taxonomy drafted in `docs/security/compliance-ledger-taxonomy.md` (current vs required events) and linked from foundations.
- Draft data retention/anonymization policy stub added at `docs/security/data-retention-policy.md` (placeholders to be finalized and enforced via privacy jobs/retention worker).
### Changed
- MinIO client now normalizes `EXPORT_ENDPOINT` (allows http(s) URLs with paths) instead of failing, preventing silent fallback to filesystem storage when a URL is provided.
## [2025-12-13] — Compliance Centre Scaffold
### Added
- Privacy jobs/compliance ledger now have sqlc accessors and a lock helper; a background PrivacyJobsWorker processes pending jobs and writes ledger entries.
- Unit test covers end-to-end worker execution on a seeded job; worker is wired into the server runtime.
- Concurrency test ensures privacy jobs are not double-processed under parallel worker runs.
- Users now support soft-delete/anonymize flows: DELETE enqueues a privacy job, redacts PII, and logs to the compliance ledger.
- Compliance API mounted at `/api/v1/compliance` (requires `privacy.manage`) with job create/list and ledger list; privacy jobs support `anonymize_user`, `dsar_export` (writes zip + receipt artifacts to storage), and `evidence_delete`.
### Changed
- Server startup launches the privacy worker alongside the programme outbox/reaper loops to keep compliance jobs flowing.
## [2025-12-12] — Locking Docs & Runtime Worker
### Added
- Programme assignments worker now runs in the server runtime (outbox → assignments) alongside reaper/scheduler.
- FOUNDATION explicitly links locking policy, concurrency tests, and CI race checks (x86_64 runner; arm64 hosts can’t run race).
- Lock policy doc aligned with actual helpers/tests; concurrency coverage section lists reaper/remediation/worker tests.
- New concurrency guard for delivery session creation on a shared assignment.
### Changed
- Transaction mapping in `db-lock-order.md` now references the concrete helpers (`LockExpiredSessionsAndSubmissions`, `LockSubmissionForRemediation`, etc.) and their tests.
## [2025-12-11] — Lock Policy Enforcement & Programme Outbox
### Added
- Centralised all row locks into `backend/internal/db/locks` with canonical ordering; CI guard `scripts/check_for_update.sh` blocks stray `FOR UPDATE`.
- Lock helpers for evaluation lifecycle, assignments/sessions/reaper, remediation apply, and programme outbox polling (tenant/org scoped).
- Programme assignment outbox schema (`190_programme_assignment_outbox.sql`) and worker skeleton to fan out enrolment → assignments via Cluster A.
- Concurrency tests for remediation apply (`remediation_service_concurrency_test.go`) and the expiry reaper (`results_service_reaper_concurrency_test.go`) to detect deadlocks.
### Changed
- Delivery reaper now locks only the current tenant/org sessions before closing; remediation apply uses lock helpers for batches/submissions.
- Programme enrolment now enqueues outbox rows instead of issuing assignments inline; worker processes them asynchronously.
## [2025-02-06] — Programmes Schema Bootstrap
### Added
- Programme definition tables (`programs`, `program_requirements`) with tenant/org RLS and app grants.
- Programme enrolment + progress ledger tables (`program_enrolments`, `program_progress`) with RLS.
- Capabilities `programmes.read` and `programmes.manage` seeded to default roles (owner/global-admin/org-admin manage; author/reviewer read).
## [2025-12-05] — Lockdown Probe & Results Remediation
### Added
- Lockdown-only probe endpoint (`GET /api/v1/evaluations/{evaluationId}/sessions/\{sessionId\}/lockdown/probe`) that reuses the existing lockdown guard and returns a small JSON payload when headers/config are satisfied; integration tests cover enforced and non-enforced flows.
- Results remediation pipeline: correction batches with per-question rules (`mark_correct`, `drop_item`, `replace_key`), submission score versioning (`submission_score_versions`), and remediation apply endpoint (`POST /results/correction-batches/\{id\}/apply`) to rescore affected submissions and bump `latest_score_version`.
- Remediation seed scripts for manual E2E checks: single-MCQ happy path, multi-item mark_correct, drop_item, replace_key, targeted subset (one submission changed, another untouched), and multi-attempt rescore. Each asserts snapshots/answers stay unchanged while scores update.
- Remediation lifecycle: batches carry `status`/`applied_at`, apply is gated to `draft`, revert creates compensating batches and restores scores/outcomes from score history (append-only), list/detail/apply/revert APIs exposed.
- Revert response now returns both the original and compensating batch with statuses; integration tests cover apply/revert lifecycle, list/detail filters, and score-version invariants (`latest_score_version` matches `submissions.score/max_score`).
### Changed
- Scoring writes now produce consistent numeric values (stringified float → numeric) to keep submission scores present across initial and remediation runs.
- Remediation apply is now idempotent: repeated apply on an already-applied batch returns 200 with the prior rescored count (no-op) and no duplicate downstream events. Added focused remediation service tests to lock behaviour.
## [2025-12-09] — Modular Services + Idempotent Remediation
### Added
- Buckets and sections moved into dedicated services (`internal/services/buckets`, `internal/services/sections`) and injected directly into handlers and EvaluationService.
- EvaluationService split into focused files (core CRUD/version lifecycle, usage, preview, validation) and simplified constructor; preview/validation remains the same externally.
- Remediation service covered by focused unit test to enforce idempotent apply and listener behaviour; seed ensures locales exist for trigger validation.
- Outcomes scaffolding: new `internal/services/outcomes` package defines `ScoringEngine`/`ProgressTracker` interfaces and typed DTOs to prepare for migrating scoring/progress logic.
### Changed
- Evaluation preview/validation HTTP endpoints now depend on the `EvaluationPreviewer` interface, keeping the preview contract swappable while the service remains the concrete implementation.
- Delivery session creation locks assignments and related submissions up front (`GetAssignmentForUpdate`, `LockSubmissionsForAssignment`) to enforce canonical lock ordering and avoid deadlocks under parallel load.
- Remediation apply now locks submissions + submission_items before rescoring to align with the canonical lock order and reduce deadlocks in parallel runs.
- Canonical lock ordering documented in FOUNDATION.md to make the rule explicit for future multi-table flows.
### Changed
- Evaluation HTTP handler now calls bucket/section services directly for bucket/section/item routes; EvaluationService acts as a lean orchestrator for evaluations/versions.
- Integration helpers retry schema truncation on deadlock to stabilise end-to-end tests.
- Server wiring updated to instantiate bucket/section services alongside EvaluationService.
## [2025-12-02] — Delivery Resilience (Expiry & Reaper)
### Added
- Session resilience schema: `delivery_sessions` now carry `expires_at`, `last_active_at`, `termination_reason`, optimistic `lock_version`, `max_viewed_item_index`, `current_section_id`, and `device_fingerprint`; `submission_items` capture `client_timestamp`; `delivery_session_section_states` table scaffolded for section-level timing.
- Assignment security config column added to support future lockdown modes; unique index on `submissions.session_id` for idempotent submit.
- Shared `CalculateSessionExpiry` helper to compose base limits + override extensions; sessions set `expires_at` at creation and are kept in sync on resume.
- Background reaper worker auto-submits expired sessions (with grace) using the guarded status transition/idempotent submission insert.
- Reaper observability: expvar counters exposed (`evalium_reaper_sessions_closed_total`, `evalium_reaper_last_run_ms`) and per-run log summaries (`reaper run closed=N duration=X`) so ops can monitor expiry activity.
- Non-blocking lockdown probe: session create/redeem/answer/event/submit now capture lightweight device fingerprints (IP/UA/headers) into `delivery_sessions.device_fingerprint`/client fields and log missing/mismatched headers against `assignments.security_config` for SEB/lockdown readiness without blocking delivery.
- Lockdown enforcement validated: integration tests cover `requireLockdown=true` missing-header rejection and the happy path with required headers present; delivery resilience smoke includes lockdown rejection.
- Canonical delivery resilience smoke (`backend/tests/delivery_resilience.sh`) covers invite → session → event → answer → submit, monitor telemetry, and expiry enforcement; linked in README under Tests.
### Changed
- Redeem flow now computes effective expiry with override extensions; active sessions refresh allowed attempts/time limit/expiry on resume and are discarded if already beyond the grace window so a fresh attempt can be issued when allowed.
- Record/Submit handlers treat `expires_at = NULL` as “no limit” and enforce expiry with grace when present.
- RLS policies completed and made idempotent across all content/delivery tables (including media/join tables); `passages` and `evaluation_buckets` now carry `org_unit_id` to align with org-scoped policies; obsolete Clerk cleanup dropped.
- Delivery expiry grace is 30s (`expiryGrace` in results_service); `expires_at` = session start + timeLimitOverride (when provided), NULL when no limit; handlers and reaper honor `expires_at + grace`.
- Session events now respect expiry/status and bump `last_active_at`/`last_activity_at` so telemetry recording is consistent with liveness tracking.
- High-stakes scaffolding: added `expires_at` to `delivery_session_section_states` for per-section timing; schema ordering updated to include the new migration.
## [2025-12-01] — Assignments Telemetry + Audit / Groups Foundations
### Added
- Assignment Command Center endpoint (`/assignments/\{id\}/monitor`) now returns progressRatio, currentItemIndex, lastActiveAt, runLabel, overrides, scores/outcomes, and telemetrySummary aggregated from session events.
- Session events storage (`delivery_session_events`) with RLS and insert API (`/evaluations/\{id\}/sessions/\{sessionId\}/events`) for ghost-proctoring hooks.
- Audit trail: `audit_logs` table with RLS; audit emission for role create/update/delete, role assign/unassign, role clone, auth failures (401/403), and magic-link redeem/invalid flows; cache invalidation hooks on role changes.
- Role binding expiry (`user_roles.valid_until`) with API support; capability resolution ignores expired bindings. Role clone endpoint added to duplicate roles + capabilities.
- User metadata column (`users.metadata`) and groups schema (dynamic/static): `groups` with `is_dynamic`, `membership_rule` (JSONB), `join_token`, `allowed_domains`, `metadata`; `group_members` with metadata, RLS, and indexes. Dynamic group evaluation service stub added.
- Assignment overrides schema for per-user accommodations (`assignment_overrides`) plus override API (`POST /assignments/\{id\}/override`) and Redeem logic that resumes active sessions, applies extra attempts/time extensions, and enforces total attempts (base + override).
- Recurring compliance schedules (`assignment_schedules`) with schedule runner: creates new assignments from latest published versions using cron expressions and dynamic run-label templates, then advances `next_run_at`.
- User import ingestion endpoints (`/users/import/preview`, `/users/import/commit`) with fuzzy header mapping, metadata extraction into `users.metadata`, dry-run validation, and commit path that triggers dynamic group evaluation.
- Group CRUD HTTP API and group-based assignment targeting (Create/Redeem) with org/RLS checks; monitor query now expands group targets to members.
### Impact
- Monitor data is now enterprise-ready for Command Center dashboards with built-in telemetry counts.
- Security visibility improved via audit logs for auth failures and RBAC operations.
- RBAC is future-proofed for custom roles, binding expiry, and cloning without schema churn.
- Groups/metadata foundations unblock dynamic cohorting and group-based assignment targeting in upcoming work.
- Retakes and accommodations are enforced centrally (no frontend filtering), preserving auditability and allowing “resume” flows. Recurring schedules automate yearly/monthly compliance issuance with distinct run labels for reporting. Ingestion now safely fuels dynamic grouping without polluting the DB.
## [2025-11-27] — Auth & RBAC Hardening
### Changed
- JWT minting in dev/test flows now aligns with real DB user→role bindings: `backend/tests/auth_env.sh` ensures roles exist, binds them in `user_roles`, and mints tokens embedding bound `roleIds` via `make_token_for_slugs` (no arbitrary UUIDs).
- Test scripts that hit protected endpoints (`org_silos.sh`, `reporting_submissions.sh`, etc.) now rely on `auth_env.sh` tokens derived from stored bindings rather than hardcoded role IDs.
- Reporting endpoints tightened: `/evaluations/\{id\}/submissions` now requires `reporting.view`; `/submissions/\{id\}` requires `reporting.view_named`. Added `reporting_submissions.sh` smoke (401/403/200) and org-scope submission smoke (`submissions_org_scope.sh`) to prove RLS + caps.
- AuthZ middleware smoke refactored to use DB-bound tokens; org silo and reporting smokes updated likewise.
### Added
- Assignments backend roadmap doc (`docs/product/assignments-backend-roadmap.md`) outlining schema, RLS, lifecycle, and capabilities for upcoming assignments feature.
### Impact
- Tokens used in tests reflect actual RBAC state, reducing the risk of passing with impossible or invalid auth claims.
- Dev/test behaviour is closer to production semantics (roles must be bound in DB to appear in JWTs).
- Reporting access now respects reporting capabilities and org RLS in smokes; documentation added for upcoming assignments work.
## [2025-03-16] — Org-Scoped Roles + Auth Introspection
### Added
- Org-unit column on `user_roles` with RLS enforcement and index to prepare per-org bindings; uniqueness now includes org scope.
- Roles API accepts optional `orgUnitId` on assign/remove; responses now echo the scope. Defaults to the caller’s org; `orgUnitId=tenant` removes tenant-wide bindings.
- `/auth/me` introspection endpoint returns claims + resolved capabilities for the caller.
- Auth smoke script now checks `/auth/me` and prints capabilities.
- Route→capability matrix documented in `docs/security/roles-and-access-control.md`.
- Capability seeds added for future media/glossary/locales surfaces (`media.manage`, `glossary.manage`, `locales.manage`), mapped to Owner/Global Admin/Org Admin by default.
### Changed
- `delivery_session_seed.sh` fetch instructions now include Authorization header to avoid 401s.
## [2025-03-15] — Auth Enforcement + RBAC Foundations
### Added
- Global JWT auth middleware on all `/api/v1/**` routes; only `/auth/login`, `/auth/verify`, and `/health` remain public.
- No dev auth bypass; `JWT_SECRET` is required (server fails fast) with `.dev/jwt-secret` + `dev/run-server.sh` for local DX.
- Capability gating on questions, passages, evaluations (read/write), sessions (launch), and submissions (view); shared Require helper.
- Role APIs: list roles/capabilities, assign/remove roles to users; new sqlc queries for roles/capabilities.
- Test infra: shared `auth_env.sh` for minting JWTs; scripts and integration tests now send Authorization headers; auth smoke `authz_middleware.sh` covers 401/403/200.
- Integration RBAC seeding and JWT injection for end-to-end auth+RLS coverage.
- Role CRUD (create/update/delete non-system roles) with capability enforcement, plus shell/Go tests.
- Magic link auth endpoints (`/auth/login`, `/auth/verify`) with single-use tokens; magic_links schema added; shell smoke `auth_magic_links.sh`.
- Grants refreshed for app role after all tables are created to avoid permission drift.
### Changed
- Backend-first flow steps made explicit in FOUNDATION.md.
- Removed all references to dev auth bypass in middleware/authz; scope now comes solely from JWT claims.
- Updated server wiring to mount roles handler and enforce auth across surfaces.
### Removed
- Legacy Clerk and `users.role` (replaced by roles/capabilities schema).
- Dev auth bypass paths in middleware/authz.
## [2025-02-02] — Backend-First Architecture Overhaul
### Added
- Full RLS tenant + org-unit siloing (TxManager + SET LOCAL)
- Combined tenant+org RLS policies across all core tables
- evalium_app runtime role (non-superuser, RLS enforced)
- Locked-in defaults for org silo regression scripts (API_URL/DB URL/ADDR/TENANT_ID)
- Immutable versionSnapshot in submissions
- Structured question tags + feedbackTagKeys
- Feedback modes (none, overall, tags, items)
- Submission and item-level scoring metrics
- Invitation-token assignment security model
- Magic link reporting model (with display_name)
- Default RBAC roles (Owner, Global Admin, Org Admin, Author, Reviewer, Proctor, Marker, Candidate, External Auditor, Billing Admin)
- org_silos.sh and org_silos_testdb.sh RLS regression tests
### Changed
- Removed all pool-level GUC setting
- All DB access must go through TxManager
- Dev tenant seed skips gracefully on permission denied when using the app role
- Removed Clerk from the identity model (native auth planned)
- Updated evaluation, assignments, and results models to use structured tags
- Replaced MVP-first approach with backend-first foundation build
- Updated roadmap to reflect auth, siloing, scoring, and assignment security being Phase 0 infrastructure
### Removed
- Match_org_scope filters in Go (RLS is now the primary enforcer)
- Clerk OAuth integration from auth design