Validating ctms-core

“Validated software” is not something a repository can ship. Under GAMP 5, validation is the responsibility of the regulated organization that deploys a system: the claim attaches to a specific installation, a specific configuration, and the procedures that operate it, never to source code in the abstract. That is true of every eTMF and CTMS on the market; vendors sell validation packages, not validated software.

What software can decide is whether your program starts from evidence or from a blank binder. ctms-core generates its technical raw material, never hand-written (ADR-0010):

Running the program

A GAMP 5-aligned pilot validation, step by step, and who owns each one. One terminology note first: GAMP 5 (Second Edition, Section 4.4) deliberately does not use IQ/OQ/PQ for computerized system life cycles (aligning with FDA’s General Principles of Software Validation) and instead describes specification and verification activities, with Table 4.1 mapping the traditional qualification terms onto them. The commands here keep the traditional names because assessors and quality agreements still ask in those terms; if your SOPs follow the Guide’s vocabulary, Table 4.1 is the bridge.

  1. Categorization and risk assessment: yours. GAMP 5 (Second Edition, Appendix M4) treats categories 3–5 as a continuum, expects a system to contain components of several categories, and warns against validating from a single category label. In those terms: deployed as shipped, your business process lives in configuration (requirement rules, studies, access grants), while any code you modify or extend is Category 5 custom software your organization then manages (Appendix D4 makes the same point for open source specifically: OSS follows the established categories, and a one-off modification becomes custom code you own). The compliance mapping is the inventory to assess against: each Part 11 / E6(R3) requirement mapped to its mechanism and the code that enforces it, so risk assessment starts from requirement → mechanism instead of a blank spreadsheet.
  2. Requirements and traceability: generated. The traceability matrix below keys each requirement to the automated tests that exercise it, from a live suite run. Adopt it as the technical requirements baseline and add your organization’s process requirements on top.
  3. Installation Qualification: generated, run by you. Deploy per the pilot checklist in the repo’s docs/05-deployment.md (OIDC against your identity provider, rotated role credentials, Object Lock storage), then run pnpm validation:iq --report iq-$(date +%F).md against that environment and file the report. IQ verifies the controls are actually installed where you deployed, not where we developed.
  4. Operational Qualification: generated, run by you. Run pnpm validation:artifacts in the qualified environment and file the OQ run report and matrix alongside the IQ report.
  5. Performance Qualification / UAT: yours. Script your own users’ scenarios against your configuration: your studies, your requirement rules, your access grants. The cookbook workflows (the monitor’s morning, the CRA’s week) are ready-made scenario templates.
  6. SOPs, training, and the QMS: yours. Operating procedures, training records, and the quality system the dossier lives in are organizational by nature; no repository can contain them. The deployment doc’s backup, restore-verification, and continuous audit-chain checks are the technical hooks your SOPs point at.
  7. Change control and periodic review: shared. Pin the commit you deployed; on every upgrade, re-run IQ and OQ and file the fresh reports. Requalification is two commands, which is the point. Between upgrades, schedule GET /audit-chain/verify so tamper-evidence is exercised continuously, not just available.

Until an organization performs these steps for its deployment, §11.10(a) remains not claimed; the compliance mapping keeps that ledger. The gap is not in the software; it is the half of validation that is yours by regulation, with the technical half already generated below.

Requirement traceability matrix

Generated from a live test run (commit 00749c6, node v22.23.1, 2026-07-13T06:08:40.079Z); regenerate with pnpm validation:artifacts. Join key: the requirement token appearing verbatim in test names, so this matrix cannot drift from the suite without showing it.

Requirement Mechanism Verifying tests Result
§11.10(a) validation of systems Not claimed, but the raw material is generated, not hand-written: pnpm validation:iq checks a live environment’s controls (15 checks, sign-off report), pnpm validation:artifacts emits an OQ run report and a requirement→test traceability matrix from a live suite run. Formal CSV (GAMP 5, SOPs, training) remains an organizational program (no automated test)
§11.10(b) accurate and complete copies Every version’s bytes are content-addressed (sha256) and immutable; API serves originals at /files/{sha256} and, scoped to the version’s study/site, at /document-versions/{id}/content — the UI’s preview shows these exact bytes; no rendition is ever stored or served, and office formats render client-side in the viewer’s browser from those same bytes, labeled as a reading aid (ADR-0027, ADR-0030) accurate and complete copies (§11.10(b)) serves the original bytes at /files/{sha256}, verifiable against the hash
GET /document-versions/{id}/content (ADR-0027) returns the exact bytes with the uploaded mime type, file name, and hash (§11.10(b))
PASS
§11.10(c) record protection & retention Versions, signatures, and audit events reject UPDATE/DELETE via database triggers, for every role append-only enforcement (Part 11 §11.10(c) §11.10(e)) rejects UPDATE on audit_event at the database level
append-only enforcement (Part 11 §11.10(c) §11.10(e)) rejects DELETE on audit_event
append-only enforcement (Part 11 §11.10(c) §11.10(e)) rejects UPDATE and DELETE on document_version
append-only enforcement (Part 11 §11.10(c) §11.10(e)) rejects UPDATE and DELETE on signature
append-only enforcement (Part 11 §11.10(c) §11.10(e)) rejects UPDATE and DELETE on document_return (ADR-0015)
least-privilege runtime role (§11.10(c) §11.10(d)) cannot TRUNCATE domain tables
least-privilege runtime role (§11.10(c) §11.10(d)) cannot disable triggers (not the table owner)
least-privilege runtime role (§11.10(c) §11.10(d)) cannot run DDL in the schema
least-privilege runtime role (§11.10(c) §11.10(d)) cannot write audit_event directly, yet its DML is still audited
least-privilege runtime role (§11.10(c) §11.10(d)) keeps immutability guarantees (UPDATE/DELETE rejected by trigger)
s3 driver (MinIO, Object Lock) WORM (§11.10(c)): a locked object version cannot be deleted, even by the root credential
PASS
§11.10(d) limited system access OIDC/SSO (AUTH_MODE=oidc): JWTs validated against the IdP’s issuer, audience, and JWKS; the verified email claim resolves to a person, or the request is refused — never a fallback actor. The API runs as a least-privilege DB role (ctms_app: DML only, no TRUNCATE/DDL, no direct audit writes). A dev-token mode remains for the demo and is not a Part 11 posture least-privilege runtime role (§11.10(c) §11.10(d)) cannot TRUNCATE domain tables
least-privilege runtime role (§11.10(c) §11.10(d)) cannot disable triggers (not the table owner)
least-privilege runtime role (§11.10(c) §11.10(d)) cannot run DDL in the schema
least-privilege runtime role (§11.10(c) §11.10(d)) cannot write audit_event directly, yet its DML is still audited
least-privilege runtime role (§11.10(c) §11.10(d)) keeps immutability guarantees (UPDATE/DELETE rejected by trigger)
authentication (§11.10(d)) rejects a missing or unknown bearer token with 401
authentication (§11.10(d)) resolves a dev token to a person and serves the request
OIDC authentication (§11.10(d)) accepts a valid token and resolves the person by email claim
OIDC authentication (§11.10(d)) rejects a token for the wrong audience
OIDC authentication (§11.10(d)) rejects a forged token (wrong key)
OIDC authentication (§11.10(d)) rejects an authenticated identity with no person record (403, not a fallback actor)
OIDC authentication (§11.10(d)) rejects a token whose email is explicitly unverified
PASS
§11.10(e) audit trails Computer-generated, timestamped audit_event rows written by AFTER-triggers on every domain-table mutation; append-only; prior values preserved as full row images; hash-chained so retroactive edits are detectable (ctms_verify_audit_chain()) append-only enforcement (Part 11 §11.10(c) §11.10(e)) rejects UPDATE on audit_event at the database level
append-only enforcement (Part 11 §11.10(c) §11.10(e)) rejects DELETE on audit_event
append-only enforcement (Part 11 §11.10(c) §11.10(e)) rejects UPDATE and DELETE on document_version
append-only enforcement (Part 11 §11.10(c) §11.10(e)) rejects UPDATE and DELETE on signature
append-only enforcement (Part 11 §11.10(c) §11.10(e)) rejects UPDATE and DELETE on document_return (ADR-0015)
audit trail (§11.10(e)) writes an attributed, chained event for every domain mutation
audit trail (§11.10(e)) verifies clean on untampered data
audit trail (§11.10(e)) detects tampering when a row is altered with triggers disabled
PASS
§11.10(g) authority checks Role-based grants (access_grant: admin / trial_ops / monitor / read_only / ingest / site_staff → read / upload / sign / approve / administer / log, scoped to study or site; ingest is the machine-identity role for source-system filing, ADR-0011; site_staff is the site seat and log gates writing a site’s own DoA/training entries, ADR-0023) enforced per route; grant changes are themselves audited and revocation is a fact, not a delete. Actor identity bound per transaction (ctms.actor_id) the auditor’s seat: unscoped read_only (ADR-0028) cannot change anything (§11.10(g)): upload, sign, bulk-approve, grant, sync all 403
authorization (§11.10(g), ADR-0008) denies operations the role does not include, naming the permission
authorization (§11.10(g), ADR-0008) allows reads for every seeded role
authorization (§11.10(g), ADR-0008) denies approval signatures to the monitor role but allows review
authorization (§11.10(g), ADR-0008) enforces grant scope: a study-scoped grant does not reach other studies
the site seat is a permission scope (ADR-0023) is refused everywhere else — other sites, study-wide reads, the portfolio (§11.10(g))
delegation-of-authority log (ADR-0023) refuses self-delegation, empty tasks, and monitor authorship (§11.10(g))
PASS
§11.50 signature manifestation signature rows carry signer, timestamp, and meaning (author/review/approval); UI displays all three upload -> sign lifecycle lands pending, becomes effective on approval, binds signature to hash (§11.50 §11.70) PASS
§11.70 signature/record linking Signature stores a copy of the signed version’s content hash; binding is verifiable independently of the version row. The web demonstrates it on demand: “Verify bytes” re-fetches the version’s content, recomputes SHA-256 in the reader’s browser, and compares against the recorded hash and every signature bound to it (ADR-0028) upload -> sign lifecycle lands pending, becomes effective on approval, binds signature to hash (§11.50 §11.70)
bulk approval (ADR-0026) one re-authentication opens the series; each version gains its own signature bound to its own hash (§11.200 §11.70)
PASS
§11.200 signature components Signing requires re-authentication: in OIDC mode a freshly issued token for the same subject with auth_time inside a short window (default 300 s); method and time are recorded on the signature row, and a DB CHECK requires them on every new signature. The dev-mode stub restates the bearer token — API-shape parity, not a credential challenge. Bulk approval is a §11.200(a)(1)(i) series of signings: one re-authentication opens the series, and every version still gains its own recorded signature (ADR-0026) signing re-authentication (§11.200) rejects a signature without valid re-authentication
signing re-authentication (§11.200) records the re-auth method and time on the signature row
signing re-authentication (§11.200) is DB-enforced: a direct INSERT without re-auth fields is rejected
OIDC signing re-authentication (§11.200) accepts a fresh re-auth token for the same subject and records it
OIDC signing re-authentication (§11.200) rejects a stale re-auth token (auth_time outside the freshness window)
OIDC signing re-authentication (§11.200) rejects a re-auth token minted for a different subject
bulk approval (ADR-0026) one re-authentication opens the series; each version gains its own signature bound to its own hash (§11.200 §11.70)
bulk approval (ADR-0026) refuses the series without valid re-authentication (§11.200)
PASS

Requirements without automated verification: §11.10(a) — see the mechanism column and docs/03-compliance.md for their status (some are documented as not claimed or future work).