Skip to content

ci(audit): point audit validator at our HTS terminology server (#183)#184

Open
mauripunzueta wants to merge 6 commits into
mainfrom
fix/183-ci-audit-tx-to-hts
Open

ci(audit): point audit validator at our HTS terminology server (#183)#184
mauripunzueta wants to merge 6 commits into
mainfrom
fix/183-ci-audit-tx-to-hts

Conversation

@mauripunzueta

@mauripunzueta mauripunzueta commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #183 — migrate CI off the public HL7 test terminology server tx.fhir.org.

Two GitHub Actions workflows reached out to tx.fhir.org during their runs. That server is rate-limited, occasionally unreachable, community-shared, and its server-side content drifts — so our baselines drifted with it and workflows carried "probe availability / skip if down" workarounds. This removes both live dependencies.

Changes

1. audit-events.yml — validation backend → our HTS

  • .github/workflows/audit-events.yml: --tx-base-urlhttps://hts.heliossoftware.com
  • crates/hfs/tests/audit/validate_report.py:
    • DEFAULT_TX_BASE_URLhttps://hts.heliossoftware.com; rename source id tx-r5-live-validationhts-live-validation; generalize the report heading (base URL is printed dynamically beneath it)
    • send a browser-like User-Agent on $validate-code requests (Cloudflare bot filter — see below)

2. hts.yml — comparison oracle → pinned golden values

Previously the integration-test job hit tx.fhir.org/r4 and /r5 live as the "source of truth" and compared every HTS operation against it (with skip-if-unavailable probes). Rather than repoint the oracle at our own server — which would keep a network dependency and reduce the test to a self-comparison (CI build vs. our own deploy) — this removes the oracle entirely and asserts each operation against pinned golden values: the stable, HL7-defined displays / expansion totals / outcomes for these UTG code systems.

  • Drop tx_base from the matrix, TX_BASE from the env, and the tx_post helper.
  • Replace compare_bool/compare_str (HTS-vs-tx) with expect_bool/expect_str (HTS-vs-golden).
  • Golden values captured from the reference and confirmed against the last green main run (identical for R4 and R5): gender M/F/UNMale/Female/Undifferentiated; v3-ActStatus displays == codes; $expand totals gender = 3, actStatus = 10; subsumes same→equivalent, M/Fnot-subsumed; translate v2-0001-source→true, gender-source→false, unmapped→false, reverse→true.
  • $translate loses its tx probe/skip branch — it already ran HTS-only in practice (tx was unreachable from the self-hosted runner) and is fully deterministic since the ConceptMap is seeded by the workflow.
  • Net result: the job makes zero external terminology calls; it is deterministic and no longer flakes on tx availability.

Issue checklist (#183)

  • Point audit-events.yml's --tx-base-url at our server → §1
  • Update hts.yml's oracle and revisit the availability-probe / skip logic → §2 (removed entirely via golden values; probes deleted)
  • Confirm the required terminology content is loaded — golden values make this moot for hts.yml; separately verified hts.heliossoftware.com does serve the audit + UTG systems these workflows exercise
  • Decide how CI authenticates — no auth required; and hts.yml now makes no server call at all
  • Audit for other workflows/scripts hitting tx.fhir.org — see below

Audit of remaining tx.fhir.org references

  • hts-ig-conformance.yml — already runs against a local HTS; the tx.fhir.org strings are comments / intentionally upstream-gated (mode: tx.fhir.org) fixtures. No change.
  • crates/fhirpath/src/terminology_client.rs — unit-test assertions about URL trimming; no network. No change.
  • crates/fhirpath/src/evaluator.rs:645 — the FHIRPath runtime library default terminology server. Intentionally left as-is: this is shipped product behavior (not a CI workflow), changing it alters the default for every FHIRPath consumer and would need test_r5_test_suite's oracle-derived expectations re-derived. Tracked separately from this CI-scoped issue.
  • Various crates/hts/src/** comments describing "tx.fhir.org behaviour" — documentation only.

Notes

  • HTS serves terminology ops at the root — no /r4 or /r5 path segment (those 404), and no auth. hfs.heliossoftware.com is the FHIR server (hfs binary, no terminology ops); the terminology server is hts.heliossoftware.com (hts binary).
  • Housekeeping: added __pycache__/ + *.pyc to .gitignore.

Follow-up: Cloudflare bot filter (addresses @smunini review)

The first dispatch failed the "Validate audit coverage and generate report" step across all backends. Root cause: hts.heliossoftware.com is fronted by Cloudflare, whose bot protection rejects the default Python urllib User-Agent with HTTP 403 — Error 1010 (browser_signature_banned) before the request reaches the origin, so every extracted code surfaced as "invalid" (18 unique pairs / 285 occurrences).

Fix: present a browser-like User-Agent on $validate-code. Verified live — plain client → 403; with the header → 200 and a valid Parameters response for every affected code system.

The audit-events workflow validated extracted AuditEvent codes against
the public HL7 test server tx.fhir.org, which is rate-limited, sometimes
unreachable, and outside our control. Point it at our own terminology
server at hts.heliossoftware.com instead.

- audit-events.yml: --tx-base-url -> https://hts.heliossoftware.com
- validate_report.py: DEFAULT_TX_BASE_URL -> https://hts.heliossoftware.com,
  rename source id tx-r5-live-validation -> hts-live-validation, and
  generalize the report heading (the base URL is already printed below it).

HTS serves terminology operations at the root (no /r4 or /r5 path
segment) and needs no auth; $validate-code resolves the audit code
systems (audit-event-type, restful-interaction, audit-event-action,
audit-event-outcome). The hts.yml comparison oracle is intentionally
left on tx.fhir.org as the source of truth.
@claude

claude Bot commented Jun 29, 2026

Copy link
Copy Markdown

Code review

1 issue found.


Bug: stale source name in build_terminology_context() lookup

File: crates/hfs/tests/audit/validate_report.py, line 597

The source entry was renamed from "tx-r5-live-validation" to "hts-live-validation" at line 98, but the corresponding lookup in build_terminology_context() was not updated:

if tx_validation is not None:
for source in sources:
if source.get("name") == "tx-r5-live-validation":
source["url"] = tx_validation.get("tx_base_url", source["url"])

Because no source has the name "tx-r5-live-validation" anymore, the source["url"] override at line 598 is now dead code. Its purpose is to replace the displayed URL in the report with the actual tx_base_url used at runtime. In the default CI path this is harmless (the URL constants match), but when --tx-base-url is supplied the report will show the stale default https://hts.heliossoftware.com instead of the URL actually validated against.

Fix: Change line 597 to:

if source.get("name") == "hts-live-validation":

@codecov

codecov Bot commented Jun 29, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

The source entry was renamed tx-r5-live-validation -> hts-live-validation,
but the lookup in build_terminology_context() still checked the old name,
making the per-run tx_base_url override dead code. With a custom
--tx-base-url the report would show the stale default instead of the URL
actually validated against.
@smunini

smunini commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

@mauripunzueta When I ran this GitHub action on this branch, I'm seeing errors: https://github.com/HeliosSoftware/hfs/actions/runs/28484701168

@smunini smunini left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hts.heliossoftware.com is fronted by Cloudflare, whose bot protection
rejects the default Python urllib User-Agent with HTTP 403 (Error 1010:
browser_signature_banned) before the request reaches the terminology
server. Every extracted code then surfaced as an invalid system+code
pair, so the audit coverage validation step failed across all backends.

Present a browser-like User-Agent on the $validate-code requests so the
Cloudflare filter is cleared and the real validation response is used.
hts.heliossoftware.com holds two versions of
http://terminology.hl7.org/CodeSystem/audit-event-type for the same
canonical URL: the FHIR-core 4.0.1 fragment (only `rest`) and the
complete THO 1.0.0 (adds hl7-v2, hl7-v3, document, object). Bare
$validate-code default-resolves to the 4.0.1 fragment, so `object` -
which AuditEvent.type legitimately uses - was rejected as an unknown
code. The previous tx.fhir.org/r5 backend default-resolved to 1.0.0, so
this only surfaced after migrating to HTS.

Pin the complete CodeSystem version (1.0.0) for that system so validation
resolves against the full concept list. Verified live: object and rest
both validate, and the unaffected systems are unchanged.
@mauripunzueta

Copy link
Copy Markdown
Contributor Author

@smunini Thanks for catching this — resolved now. The run is green: https://github.com/HeliosSoftware/hfs/actions/runs/28806280943

Two transport-level issues surfaced by pointing the validator at hts.heliossoftware.com (neither was a real problem with the audit data itself):

  1. Cloudflare bot filterhts.heliossoftware.com is fronted by Cloudflare, which 403'd the default Python urllib User-Agent (Error 1010 browser_signature_banned) before the request reached the server. Every code then reported as an invalid system+code pair. Fixed by sending a browser-like User-Agent on the $validate-code requests.
  2. CodeSystem version resolutionhttp://terminology.hl7.org/CodeSystem/audit-event-type exists on HTS as both the FHIR-core 4.0.1 fragment (only rest) and the complete THO 1.0.0 (adds hl7-v2, hl7-v3, document, object). A bare $validate-code default-resolves to the 4.0.1 fragment, so object — which AuditEvent.type legitimately uses — was rejected. The previous tx.fhir.org/r5 backend default-resolved to 1.0.0, which is why it only surfaced after the migration. Pinned that system to version=1.0.0.

Filed a follow-up for the deeper server-side cause (HTS default-picking the incomplete fragment): #200.

…onstant

Address code-review feedback: TX_SYSTEM_VERSION_OVERRIDES repeated the
audit-event-type canonical URL as a magic string instead of reusing the
existing AUDIT_EVENT_TYPE_SYSTEM constant. Reuse the constant so the
system URL has a single source of truth and the version pin cannot
silently miss on a future edit to one copy.
@mauripunzueta mauripunzueta requested a review from smunini July 6, 2026 23:09
The hts.yml integration-test job compared every HTS operation live against
tx.fhir.org/r4 and /r5 as the source of truth, with skip-if-unavailable
probes. Per #183, drop that public-server dependency entirely rather than
repointing it at our own HTS (which would keep a network call and reduce the
test to a self-comparison).

Assert each operation against pinned golden values instead — the stable,
HL7-defined displays, expansion totals, and outcomes for the UTG code systems
these tests exercise (identical for R4 and R5, confirmed against the last
green main run):

- lookup displays: gender M/F/UN -> Male/Female/Undifferentiated;
  v3-ActStatus displays == codes
- validate-code (CS + VS): valid -> true, invalid / wrong-display -> false
- expand totals: v3-AdministrativeGender = 3, v3-ActStatus = 10
- subsumes: same -> equivalent, M/F -> not-subsumed
- translate: v2-0001 source -> true, gender source -> false,
  unmapped -> false, reverse -> true

Replace compare_bool/compare_str (HTS-vs-tx) with expect_bool/expect_str
(HTS-vs-golden); remove tx_base from the matrix, TX_BASE from the env, the
tx_post helper, and the translate availability probe. The job now makes zero
external terminology calls and is fully deterministic.

Also gitignore __pycache__/ and *.pyc.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CI: switch GitHub Actions from tx.fhir.org to our hfs.heliossoftware.com terminology server

2 participants