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):
pnpm validation:iqis Installation Qualification: it checks a live environment’s installed controls (migrations, triggers, role privilege ceilings, audit-chain integrity, storage WORM posture) and emits a sign-off-able report.pnpm validation:artifactsruns the real test suite and emits the Operational Qualification run report plus the requirement traceability matrix below. The join key is the requirement token appearing verbatim in test names, so the matrix cannot drift from the suite without showing it.
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.
- 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.
- 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.
- 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 runpnpm validation:iq --report iq-$(date +%F).mdagainst that environment and file the report. IQ verifies the controls are actually installed where you deployed, not where we developed. - Operational Qualification: generated, run by you. Run
pnpm validation:artifactsin the qualified environment and file the OQ run report and matrix alongside the IQ report. - 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.
- 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.
- 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/verifyso 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).