Architecture
Components
.github/workflows/ci-quality.yml GitHub Actions entry point (env + artifact)
│
▼
ci/scripts/run-ci.mjs runs 16 phases, parses output
│ ├─ ci/reports/raw/<phase>.log raw logs
│ └─ ci/reports/results.json machine-readable raw result
▼
ci/scripts/ci-policy.mjs scoring + PASS/FAIL gates + findings
▼
ci/scripts/generate-ci-report.mjs 4 report files + $GITHUB_STEP_SUMMARY
The same chain runs inside Docker:
ci/docker-compose.ci.yml
├─ mongo-ci (test MongoDB, tmpfs — not persistent)
├─ minio-ci (test MinIO, tmpfs — not persistent)
├─ minio-ci-init(test bucket: iqv-platform-ci)
├─ mailpit-ci (test SMTP — no mail reaches a real recipient)
└─ ci-runner (ci/Dockerfile, Node 22)
Why this split?
- The workflow stays thin. Phases live in
run-ci.mjs, not in YAML, so the identical pipeline runs locally and in CI. - One source of policy. Score and PASS/FAIL live only in
ci-policy.mjs; report files are just formatted views of that output. - Isolation from production. CI compose uses a separate project name
(
iqv-ci), separate service names and a separate database/bucket; the rootdocker-compose.ymlis untouched.
Output contract
Each phase record inside results.json:
| Field | Meaning |
|---|---|
id, name, group |
phase identity |
command, cwd |
executed command |
exitCode, durationMs, timedOut |
run outcome |
counts |
{passed, failed, skipped, total} — parsed from output |
evidence |
last 40 error lines |
rawLog |
path to raw/<id>.log |