|
| 1 | +# PACS Domain Release Gates |
| 2 | + |
| 3 | +This document defines the **release gates** that must demonstrably pass before a |
| 4 | +`pacs_system` version (starting with v1.0) is published. A generic green test |
| 5 | +suite is not sufficient: because `pacs_system` is the medical-imaging tier of the |
| 6 | +ecosystem, the release decision must explicitly show that the |
| 7 | +medical/security-sensitive surfaces were verified. |
| 8 | + |
| 9 | +Each gate below is followable **without reading test source code**. For every |
| 10 | +gate you get: the exact command (or workflow) to run, where it runs in CI, and |
| 11 | +how to interpret the result. The four mandatory domain gates are: |
| 12 | + |
| 13 | +1. DICOM conformance |
| 14 | +2. TLS / ATNA audit logging |
| 15 | +3. Anonymization (de-identification) |
| 16 | +4. Storage / index migration |
| 17 | + |
| 18 | +> Scope note: this file is the gate **matrix**. It does not duplicate the |
| 19 | +> conformance content itself — it points at the existing conformance documents |
| 20 | +> (`DICOM_CONFORMANCE_STATEMENT.md`, `IHE_CONFORMANCE.md`, `SECURITY_AUDIT.md`, |
| 21 | +> `SDS_SECURITY.md`) and at the tests/workflows that enforce them. |
| 22 | +
|
| 23 | +## How tests are selected |
| 24 | + |
| 25 | +All C++ tests are registered with CTest through `cmake/testing.cmake` using |
| 26 | +Catch2 (`catch_discover_tests`). Two selection mechanisms are available: |
| 27 | + |
| 28 | +- **CTest labels** — coarse buckets: `unit`, `integration`, `stress`, |
| 29 | + `pacs_xds_integration`, `boundary`, `modules`. Select with `ctest -L <label>`. |
| 30 | +- **Catch2 tags** — fine-grained per-domain selection embedded in each |
| 31 | + `TEST_CASE`, e.g. `[security][anonymization]`, `[security][atna]`, |
| 32 | + `[security][tls]`, `[migration]`, `[dcmtk][echo]`. With `catch_discover_tests` |
| 33 | + every Catch2 case becomes an individually named CTest test, so the simplest |
| 34 | + portable way to select a domain subset is the CTest name regex |
| 35 | + `ctest -R "<substring>"` (e.g. `ctest -R "atna"`). The compiled test binaries |
| 36 | + (`security_tests`, `storage_tests`, …) are emitted in the build root and also |
| 37 | + accept Catch2 tag expressions directly (e.g. |
| 38 | + `./security_tests "[security][atna]"`). |
| 39 | + |
| 40 | +The medical-domain unit tests (security, storage, migration, anonymization) are |
| 41 | +built into the `security_tests` and `storage_tests` binaries and carry the |
| 42 | +`unit` CTest label, so they already run as part of the default `ctest` |
| 43 | +invocation in **CI** and **Release**. The gate commands below additionally show |
| 44 | +how to run *only* the domain subset for targeted local verification. |
| 45 | + |
| 46 | +## Gate matrix |
| 47 | + |
| 48 | +| # | Gate | Catch2 tag / CTest selector | Test source | Authoritative doc | CI-enforced by | Status | |
| 49 | +|---|------|------------------------------|-------------|-------------------|----------------|--------| |
| 50 | +| 1 | DICOM conformance | `[dcmtk][echo]`, `[dcmtk][store]`, `[dcmtk][find]`, `[dcmtk][move]` (via `./bin/pacs_integration_e2e`); `integration::` label | `tools/integration_tests/test_dcmtk_{echo,store,find,move}.cpp`, `tests/integration/dicom_workflow_integration_test.cpp` | `docs/DICOM_CONFORMANCE_STATEMENT.md`, `docs/IHE_CONFORMANCE.md` | `dcmtk-interop.yml` (**DCMTK Interoperability Tests**), `integration-tests.yml` (**Integration Tests**) | CI-enforced | |
| 51 | +| 2 | TLS / ATNA audit logging | `[security][tls]`, `[security][atna]`, `[security][auditor]`, `[security][syslog]`, `[security][storage]` (audit cipher) | `tests/security/tls_policy_test.cpp`, `tests/security/atna_audit_logger_test.cpp`, `tests/security/atna_service_auditor_test.cpp`, `tests/security/atna_syslog_transport_test.cpp`, `tests/security/audit_log_cipher_test.cpp`; `tests/integration/ihe` ATNA suite (`pacs_xds_integration` label) | `docs/SECURITY_AUDIT.md`, `docs/SDS_SECURITY.md`, `docs/API_REFERENCE_SECURITY.md` | `ci.yml` (**CI**, full `ctest`), `dependency-security-scan.yml` (**Dependency Security Scan**, TLS-config report), `integration-tests.yml` (XDS ATNA bucket) | CI-enforced | |
| 52 | +| 3 | Anonymization | `[security][anonymization]` | `tests/security/anonymizer_test.cpp`, `tests/security/uid_mapping_test.cpp`; CLI: `tools/dcm_anonymize` | `docs/SECURITY_AUDIT.md`, DICOM PS3.15 Annex E (referenced from `tools/dcm_anonymize/main.cpp`) | `ci.yml` (**CI**, full `ctest`) | CI-enforced | |
| 53 | +| 4 | Storage / index migration | `[migration]`, `[migration][v1..v5]`, `[storage][database]`; plus `storage_boundary_guard` (`boundary` label) | `tests/storage/migration_runner_test.cpp`, `tests/storage/index_database_test.cpp`, `tests/storage/check_storage_boundary.py` | `docs/migration/0.x-to-1.0.md`, `docs/SDS_DATABASE.md` | `ci.yml` (**CI**, full `ctest` incl. `storage_boundary_guard`) | CI-enforced | |
| 54 | + |
| 55 | +Every gate is CI-enforced. There is no manual-only gate in the v1.0 matrix; the |
| 56 | +`storage_boundary_guard` (Python, no build required) is the only gate that can |
| 57 | +also be run with zero compilation, which makes it the cheapest pre-flight check. |
| 58 | + |
| 59 | +## Gate 1 — DICOM conformance |
| 60 | + |
| 61 | +**What it proves:** the SCP/SCU services interoperate with a reference |
| 62 | +toolkit (DCMTK) for the core DIMSE verbs (C-ECHO, C-STORE, C-FIND, C-MOVE) and |
| 63 | +that the end-to-end store/query/retrieve workflow holds. |
| 64 | + |
| 65 | +**Workflows (CI-enforced):** |
| 66 | + |
| 67 | +- `DCMTK Interoperability Tests` (`.github/workflows/dcmtk-interop.yml`) — runs |
| 68 | + the Catch2 cases tagged `[dcmtk][echo|store|find|move]` against real DCMTK |
| 69 | + binaries (`echoscu`, `storescp`, `findscu`, `movescu`) and a shell-level |
| 70 | + interop suite. Triggers on push, pull_request, and `workflow_dispatch`. |
| 71 | +- `Integration Tests` (`.github/workflows/integration-tests.yml`) — runs the |
| 72 | + `integration::`-labelled suite including |
| 73 | + `dicom_workflow_integration_test.cpp`. |
| 74 | + |
| 75 | +**Local targeted run:** |
| 76 | + |
| 77 | +The DCMTK interop cases live in the `pacs_integration_e2e` binary (sources under |
| 78 | +`tools/integration_tests/`, built when DCMTK tools are available) and are |
| 79 | +selected by Catch2 tag, exactly as the workflow does: |
| 80 | + |
| 81 | +```bash |
| 82 | +# Requires DCMTK tools (echoscu, storescp, findscu, movescu) on PATH: |
| 83 | +cd build |
| 84 | +./bin/pacs_integration_e2e "[dcmtk][echo]" |
| 85 | +./bin/pacs_integration_e2e "[dcmtk][store]" |
| 86 | +./bin/pacs_integration_e2e "[dcmtk][find]" |
| 87 | +./bin/pacs_integration_e2e "[dcmtk][move]" |
| 88 | +# DICOM end-to-end workflow (CTest integration bucket): |
| 89 | +ctest --output-on-failure -R "integration::" |
| 90 | +``` |
| 91 | + |
| 92 | +**Reference doc to cite in the release:** `docs/DICOM_CONFORMANCE_STATEMENT.md`. |
| 93 | + |
| 94 | +## Gate 2 — TLS / ATNA audit logging |
| 95 | + |
| 96 | +**What it proves:** the secure-transport policy is enforced (TLS profile, |
| 97 | +cipher policy) and that auditable events are emitted as RFC 3881 / DICOM PS3.15 |
| 98 | +ATNA messages, transported (syslog/TLS), and protected at rest (AES-256-GCM |
| 99 | +envelope + HMAC). |
| 100 | + |
| 101 | +**Workflows (CI-enforced):** |
| 102 | + |
| 103 | +- `CI` (`.github/workflows/ci.yml`) — the default `ctest` run builds and runs |
| 104 | + `security_tests`, which contains all `[security][tls]` and `[security][atna]` |
| 105 | + cases. |
| 106 | +- `Dependency Security Scan` (`.github/workflows/dependency-security-scan.yml`) |
| 107 | + — emits a TLS-configuration report for the source tree and runs the |
| 108 | + dependency vulnerability scan (Trivy, OSV, npm audit). Triggers on push, |
| 109 | + pull_request, nightly schedule, and `workflow_dispatch`. |
| 110 | +- `Integration Tests` — the XDS.b ATNA integration bucket |
| 111 | + (`pacs_xds_integration` CTest label). |
| 112 | + |
| 113 | +**Local targeted run:** |
| 114 | + |
| 115 | +# Most precise: run the security binary with Catch2 tag expressions. Tags are not |
| 116 | +# part of the CTest test names, so tag-based selection must go through the binary. |
| 117 | +```bash |
| 118 | +cd build |
| 119 | +# TLS policy + ATNA emission/transport/at-rest (exact, by tag): |
| 120 | +./security_tests "[security][tls]" |
| 121 | +./security_tests "[security][atna],[security][auditor],[security][syslog]" |
| 122 | +# Audit-at-rest cipher cases (tagged [security][storage] in audit_log_cipher_test): |
| 123 | +./security_tests "[security][storage]" |
| 124 | +# Approximate alternative via CTest test-name regex (titles, not tags): |
| 125 | +ctest --output-on-failure -R "tls_p|atna|audit" |
| 126 | +# XDS ATNA integration bucket (CTest label): |
| 127 | +ctest --output-on-failure -L pacs_xds_integration |
| 128 | +``` |
| 129 | + |
| 130 | +**Reference docs to cite:** `docs/SECURITY_AUDIT.md`, `docs/SDS_SECURITY.md`. |
| 131 | + |
| 132 | +## Gate 3 — Anonymization (de-identification) |
| 133 | + |
| 134 | +**What it proves:** de-identification honours the DICOM PS3.15 Annex E |
| 135 | +confidentiality profiles (Basic, HIPAA Safe Harbor, GDPR, Retain-* variants), |
| 136 | +including UID-mapping consistency and date shifting. |
| 137 | + |
| 138 | +**Workflow (CI-enforced):** |
| 139 | + |
| 140 | +- `CI` (`.github/workflows/ci.yml`) — the default `ctest` run executes the |
| 141 | + `[security][anonymization]` cases in `security_tests`. |
| 142 | + |
| 143 | +**Local targeted run:** |
| 144 | + |
| 145 | +```bash |
| 146 | +cd build |
| 147 | +# Most precise: by Catch2 tag (covers anonymizer_test + uid_mapping_test): |
| 148 | +./security_tests "[security][anonymization]" |
| 149 | +# Approximate alternative via CTest test-name regex (matches "Anonymizer: ..."): |
| 150 | +ctest --output-on-failure -R "Anonymiz" |
| 151 | +``` |
| 152 | + |
| 153 | +**CLI smoke check (after a build with `-DPACS_BUILD_EXAMPLES=ON`):** |
| 154 | + |
| 155 | +```bash |
| 156 | +# De-identify a sample using a named profile and inspect the result. |
| 157 | +# The binary is emitted in the build tree; adjust the path to your build layout. |
| 158 | +./dcm_anonymize --profile hipaa_safe_harbor input.dcm anonymized.dcm |
| 159 | +./dcm_anonymize --help # lists the supported profiles and tag options |
| 160 | +``` |
| 161 | + |
| 162 | +**Reference doc to cite:** `docs/SECURITY_AUDIT.md` (and PS3.15 Annex E). |
| 163 | + |
| 164 | +## Gate 4 — Storage / index migration |
| 165 | + |
| 166 | +**What it proves:** schema migrations (v1→v5 tables/indexes/triggers) apply |
| 167 | +forward correctly and the storage-boundary invariants (no cross-layer storage |
| 168 | +access) hold. |
| 169 | + |
| 170 | +**Workflow (CI-enforced):** |
| 171 | + |
| 172 | +- `CI` (`.github/workflows/ci.yml`) — the default `ctest` run executes the |
| 173 | + `[migration]` and `[storage][database]` cases in `storage_tests`, plus the |
| 174 | + `storage_boundary_guard` CTest test. |
| 175 | + |
| 176 | +**Local targeted run:** |
| 177 | + |
| 178 | +```bash |
| 179 | +# Cheapest pre-flight: storage-boundary guard (pure Python, NO build needed): |
| 180 | +python3 tests/storage/check_storage_boundary.py "$(pwd)" |
| 181 | +# After a build, the migration + index suite. Most precise is by Catch2 tag: |
| 182 | +cd build |
| 183 | +./storage_tests "[migration],[storage][database]" |
| 184 | +# Approximate alternative via CTest test-name regex: |
| 185 | +ctest --output-on-failure -R "migration|index" |
| 186 | +``` |
| 187 | + |
| 188 | +**Reference docs to cite:** `docs/migration/0.x-to-1.0.md`, `docs/SDS_DATABASE.md`. |
| 189 | + |
| 190 | +## Using the gates in a release |
| 191 | + |
| 192 | +The `Release` workflow (`.github/workflows/release.yml`) runs the full `ctest` |
| 193 | +suite in Release mode, which exercises gates 2, 3, and 4 (and the unit slice of |
| 194 | +gate 1). The DCMTK-interop slice of gate 1 and the integration slice run on the |
| 195 | +`develop` branch via `dcmtk-interop.yml` / `integration-tests.yml` and are |
| 196 | +linked from the release notes. |
| 197 | + |
| 198 | +The generated release notes include a **Release Gates** section (see the |
| 199 | +`Build release notes` step in `release.yml`) that links back to this matrix so a |
| 200 | +reviewer can confirm, from the GitHub Release page alone, that all four |
| 201 | +medical-domain gates were verified for that version. |
| 202 | + |
| 203 | +Before tagging a release, complete the checklist below and attach the four run |
| 204 | +links (or the local command output) to the release PR or the release issue: |
| 205 | + |
| 206 | +- [ ] **Gate 1 — DICOM conformance**: `DCMTK Interoperability Tests` + `Integration Tests` green on the release commit. |
| 207 | +- [ ] **Gate 2 — TLS / ATNA audit**: `CI` green (security suite) + `Dependency Security Scan` green. |
| 208 | +- [ ] **Gate 3 — Anonymization**: `CI` green (anonymization suite); optionally a `dcm_anonymize` CLI smoke run attached. |
| 209 | +- [ ] **Gate 4 — Storage / index migration**: `CI` green (migration suite + `storage_boundary_guard`). |
| 210 | + |
| 211 | +See [`CONTRIBUTING.md`](../CONTRIBUTING.md) for the contributor-facing summary of |
| 212 | +these gates. |
0 commit comments