Skip to content

Commit 5fe2730

Browse files
authored
docs(release): formalize PACS domain release gates (#1185)
Add docs/RELEASE_GATES.md: a gate matrix that formalizes the four PACS medical-domain release gates (DICOM conformance, TLS/ATNA audit logging, anonymization, storage/index migration). Each gate maps to its exact Catch2 tag / CTest selector, test source, authoritative conformance doc, and CI-enforcing workflow, and is followable without reading test source code. All four gates are CI-enforced (CI, DCMTK Interoperability Tests, Integration Tests, Dependency Security Scan); no manual-only gate remains. Wire gate visibility into the release flow: - release.yml release notes gain a "Release Gates" section linking the matrix and listing the CI workflow per gate. - CONTRIBUTING.md links the matrix and the zero-build storage-boundary pre-flight check. - CHANGELOG.md [Unreleased] records the addition. Closes #1176
1 parent 43f0d7b commit 5fe2730

4 files changed

Lines changed: 243 additions & 0 deletions

File tree

.github/workflows/release.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,20 @@ jobs:
258258
GIT_TAG v${{ inputs.version }}
259259
)
260260
\`\`\`
261+
262+
### Release Gates
263+
264+
This release was verified against the four PACS medical-domain
265+
release gates. See
266+
[docs/RELEASE_GATES.md](https://github.com/kcenon/pacs_system/blob/v${{ inputs.version }}/docs/RELEASE_GATES.md)
267+
for the full gate matrix, exact commands, and workflow names.
268+
269+
| Gate | CI workflow |
270+
|------|-------------|
271+
| DICOM conformance | DCMTK Interoperability Tests, Integration Tests |
272+
| TLS / ATNA audit logging | CI, Dependency Security Scan |
273+
| Anonymization | CI |
274+
| Storage / index migration | CI (incl. \`storage_boundary_guard\`) |
261275
EOF
262276
263277
{

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- Formalize the four PACS medical-domain release gates (DICOM conformance, TLS/ATNA audit logging, anonymization, storage/index migration) in `docs/RELEASE_GATES.md`: a gate matrix mapping each gate to its exact Catch2 tag / CTest selector, test source, authoritative conformance doc, and CI-enforcing workflow, plus a pre-release checklist that is followable without reading test source. Linked from `CONTRIBUTING.md` and surfaced as a "Release Gates" section in the generated release notes (`release.yml`) ([#1176](https://github.com/kcenon/pacs_system/issues/1176))
13+
1014
## [1.0.0] - TBD
1115

1216
### Added

CONTRIBUTING.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,19 @@ Aim for:
141141
- Critical paths: 100% coverage
142142
- Error handling: Test failure scenarios
143143

144+
### Release Gates
145+
146+
Before a release, the four medical-domain gates (DICOM conformance,
147+
TLS/ATNA audit logging, anonymization, storage/index migration) must be
148+
demonstrably green. Each gate has an exact command or workflow name and is
149+
followable without reading test source code. See
150+
[`docs/RELEASE_GATES.md`](docs/RELEASE_GATES.md) for the gate matrix and the
151+
pre-release checklist. The cheapest pre-flight check needs no build:
152+
153+
```bash
154+
python3 tests/storage/check_storage_boundary.py "$(pwd)"
155+
```
156+
144157
## Pull Requests
145158

146159
### PR Checklist

docs/RELEASE_GATES.md

Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
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

Comments
 (0)