Skip to content

fix(conformance): stamp the Medications section 2014-06-09, not 2015-08-01 - #107

Merged
NSchatz merged 2 commits into
mainfrom
fix/medications-section-template-stamp
Aug 10, 2026
Merged

fix(conformance): stamp the Medications section 2014-06-09, not 2015-08-01#107
NSchatz merged 2 commits into
mainfrom
fix/medications-section-template-stamp

Conversation

@NSchatz

@NSchatz NSchatz commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

What this fixes

The six CCD SHALL sections do not share one version stamp, and the builder assumed they did.

Each assert in the normative C-CDA R2.1 Schematron's CCD (V3) errors rule names a @root and an @extension, as a pair. Five are 2015-08-01:

Section Root Stamp CONF
Allergies (entries required) (V3) …22.2.6.1 2015-08-01 1198-30662
Medications (entries required) (V2) …22.2.1.1 2014-06-09 1198-30664
Problem (entries required) (V3) …22.2.5.1 2015-08-01 1198-30666
Results (entries required) (V3) …22.2.3.1 2015-08-01 1198-30670
Social History (V3) …22.2.17 2015-08-01 1198-30688
Vital Signs (entries required) (V3) …22.2.4.1 2015-08-01 1198-30690

medicationsSection called sectionElement and emptySection without an extension argument, so both took the builder's R21 default and stamped …22.2.1.1 with 2015-08-01. That pair is satisfied by no rule in the IG, so every CCD and every Referral Note this builder produced failed CONF:1198-30664, populated or empty. Both call sites now pass a named MED_SECTION_EXT.

R2.1 revised this section at the earlier stamp and never re-issued it: the Schematron carries cda:section rule contexts for both Medications roots (…22.2.1 and …22.2.1.1) at 2014-06-09 and zero rule contexts for either root at 2015-08-01. The Referral Note's own errors rule requires the same …22.2.1.1:2014-06-09, so one fix corrects both document types, and editCcda rebuilds sections through the same function and inherits it.

How the rule context was read, and why that matters here

The predecessor slice had its first refuter pass rejected for reading asserts without their enclosing rule context, so this was measured deliberately:

  • 1198-30664 is sentence-unique: exactly one hit in the file.
  • Its nearest enclosing <sch:rule> is …22.1.2-2015-08-01-errors-abstract, which is abstract="true" and carries no context attribute at all.
  • The selecting context comes from the concrete rule that sch:extends it: cda:ClinicalDocument[cda:templateId[@root='…22.1.2' and @extension='2015-08-01']], an existential predicate over the document's templateId children, i.e. an R2.1-stamped CCD.

Reading the assert's nearest enclosing rule and stopping there yields an abstract rule and no context. That is the trap.

Source: HL7/CDA-ccda-2.1, validation/Consolidated CDA Templates for Clinical Notes (US Realm) DSTU R2.1.sch, public and unauthenticated, 1,010,531 bytes, sha256 04be58046a675735616e46cf52053688a2fc9d0c88010f14fd1c5a2f4ca5bd54, hash-checked before it was read. Deliberately not vendored (1 MB third-party artifact).

Is this a breaking change?

It is a wire-format change, and it is not a breaking API change.

Emitted bytes change on every document carrying a Medications section, so a consumer keyed on the exact old root + extension pair stops matching. But this package's own parser, sectionForTemplateRoot and the editable-section table all match on the root alone, so parse, edit, section lookup and round-trip are unaffected; and the old pair was unsatisfiable by any rule in the IG, so no conformant consumer could have been keyed on it. Legacy documents carrying the old stamp still parse, still round-trip byte-identically, and are never silently restamped.

patch on the 0.0.x pre-alpha ladder.

Scope, stated rather than implied

This closes that one CONF, for a populated document. It does not upgrade the package's general conformance claim: nothing in CI validates an emit against the Schematron, so every CONF id this code cites remains mechanically unenforced, and "a built document round-trips with zero warnings" still does not mean "a validator would pass it".

A document with no medications emits the entries-optional root only, because declaring entries-required with zero entries violates that template's own "SHALL contain at least one entry". Such a document still fails CONF:1198-30664. That is inherent to having no medications rather than a residue of this change, and it applies equally to Allergies, Problems, Results and Vital Signs.

The CI Schematron harness was weighed and declined

The same predecessor slice filed "no CI harness validates an emit against the Schematron". Taking it here was considered and declined deliberately: a real harness needs an XPath engine (the one runtime dependency has none), plus correct sch:extends expansion, phase handling and first-rule-wins semantics, and the .sch is not vendored so it first needs a fetch-or-vendor decision. Getting rule-context semantics wrong inside a harness would be worse than having none, which is precisely this defect's family. It stays its own slice.

What landed instead is a transcription of the six asserts with the source sha256 recorded beside it. It pins this defect class for the six SHALL sections and nothing wider, and it is not a validator. The residual is named in the repo record: nothing in CI re-verifies that hash, so the table can drift from the artifact it cites.

Testing

Why a root-only test never caught this: the pre-existing assertion checked for 2.16.840.1.113883.10.20.22.2.1.1 with no stamp, and passed throughout. The new tests assert the pair for all six SHALL sections, and additionally assert the 2015-08-01 pair is absent, so a reverted default reds instead of passing vacuously.

Red at base: 5 of the 11 tests touching this are red at bdd551e; 6 are green there as controls (the five non-Medications SHALL rows, plus the round-trip re-parse). The five reds all name Medications. Base builder was restored by file copy, not git checkout.

Full suite 928 passing, up from 918. verify.sh ccda green (typecheck, lint, format, phi-scan, no-emdash, no-internal-refs, coverage, build, attw); the agent-notes contract gate green separately, since the shared ladder does not run it.

Review gate

conformance-refuter, NOT REFUTED on pass 1 of the 4-pass cap. It independently re-fetched and re-hashed the Schematron, re-walked the abstract-rule chain, censused all 24 section roots the builder emits, probed legacy and mixed-stamp inputs, and re-ran the head tests against the base builder to confirm 5 red / 6 green. It specifically checked whether the fixture and snapshot edits masked a failure and found they did not, since the golden snapshot is fixture-driven rather than builder-driven.

It raised three findings, all three fixed in 4694721:

  1. PRE-EXISTING, major: docs-content/quickstart.md carried the same non-existent pair while labelling itself "Clean, spec-conformant input", and the sibling sample had been corrected while this one was left. Stamp corrected. Note the executable docs gate runs that snippet green and structurally cannot catch a wrong stamp, because recognition matches the root alone.
  2. INTRODUCED, minor: the changeset headline was unconditional where its own body was conditional. Rewritten.
  3. INTRODUCED, minor: missing Provenance: marker. Added, as PRIMARY / single artifact / hash-pinned, with the drift residual named.

Disclosed: those three remedies are UNGRADED. The subagent ceiling for this process was reached before a second pass could be spawned, so pass 2 did not run. The remedies were instead self-checked: a tree-wide sweep finds no surviving occurrence of either Medications root paired with 2015-08-01 outside the new negative assertions themselves, and the executable docs test still runs 21/21 rather than skipping. Two of the three remedies are prose only; the third is the same one-attribute change already graded on its sibling sample and on the fixtures. Landing an ungraded remedy on a NOT REFUTED pass has in-repo precedent.

Documentation

README.md gains a note that the six section templates do not share one stamp. documentation/agent-notes.md gains the full long-form record. ccda/CLAUDE.md gains the one-line trap, paid for by a relocation, not a ratchet raise: the trailing paragraph announcing the pre-scaffold relocation moved verbatim into the long-form record, along with the precedence rule it stated. No trap was deleted, reworded or shortened to fit. The file lands at 31,994 of 32,000.

…08-01

The six CCD SHALL sections do not share one version stamp, and the builder
assumed they did. Each assert in the normative C-CDA R2.1 Schematron's CCD (V3)
errors rule names a @root AND an @extension, as a pair. Five are 2015-08-01;
Medications (...22.2.1.1) is 2014-06-09. R2.1 revised that section at the
earlier stamp and never re-issued it: the Schematron carries section rule
contexts for both Medications roots at 2014-06-09 and zero for either root at
2015-08-01.

medicationsSection called sectionElement and emptySection without an extension
argument, so both took the R21 default. That pair is unsatisfiable by any rule
in the IG, so every CCD and every Referral Note this builder produced failed
CONF:1198-30664, populated or empty. Both call sites now pass a named
MED_SECTION_EXT. The Referral Note's own errors rule requires the same stamp,
so one fix corrects both document types, and editCcda rebuilds sections through
the same function and inherits it.

Emitted bytes change on every document carrying a Medications section, so a
consumer keyed on the exact old pair stops matching. Not a breaking API change:
this package's parser, sectionForTemplateRoot and the editable-section table all
match on the root alone, so parse, edit and round-trip are unaffected, and no
conformant consumer could have been keyed on a stamp the IG does not define.

A root-only test never caught it: the pre-existing assertion checked the root
with no stamp and passed throughout. The new tests assert the pair for all six
SHALL sections and assert the 2015-08-01 pair is absent, so a reverted default
reds instead of passing vacuously. Five of the eleven tests touching this are
red at bdd551e; six are green there as controls.

Closes item (2) of the three filed by the SHALL-set slice. Item (3), a CI
Schematron harness, is deliberately not taken here and stays its own slice.
This does not upgrade the package's general conformance claim.
Three remedies from conformance-refuter pass 1.

docs-content/quickstart.md carried the same non-existent
...22.2.1.1:2015-08-01 pair while labelling itself "Clean, spec-conformant
input". PRE-EXISTING, but the slice corrected its sibling sample in the same
directory and left this one, so documentation-follows-code was half-done for
this change. The executable docs gate runs that snippet green and structurally
cannot catch a wrong stamp, because section recognition matches the root alone.

The changeset headline claimed a built CCD stops failing CONF:1198-30664
unconditionally, where its own scope paragraph correctly restricts that to a
populated document. In this repo the changeset summary is the published
changelog entry, so the headline is what most readers see.

Adds the missing Provenance: marker. PRIMARY, single artifact, hash-pinned,
with the residual named: the six-row table is a hand transcription citing a
sha256 that nothing in CI re-verifies, so it can drift from the artifact it
cites. Same gap as open item (3), closed by the same harness.
@NSchatz
NSchatz merged commit a0fba0a into main Aug 10, 2026
8 checks passed
@NSchatz
NSchatz deleted the fix/medications-section-template-stamp branch August 10, 2026 12:33
NSchatz added a commit that referenced this pull request Aug 10, 2026
Written after pass 2 returned NOT REFUTED on 8b469c2, so this delta is
disclosed as ungraded. All four changes are prose; none touches code, tests or
emitted bytes.

Pass 2 minor 1 (INTRODUCED): the correction bullet cited PR #109, which does
not exist, and called it "pass 2" without saying whose. It was #107's pass 2
that named the wrong id, and #108 that fixes it. Both corrected.

Pass 2 minor 2 (PRE-EXISTING, byte-identical at main so it could not block):
the changeset's empty-section paragraph said "A document with no medications
... still fails CONF:1198-30664", but a Referral Note in that state fails
1198-30923, never -30664. Its agent-notes counterpart already said "A CCD", so
the two copies disagreed in scope and this one publishes verbatim. Scoped to a
CCD and the Referral Note case named alongside it.

Pass 1 minor 2: a spliced paragraph seam in the same bullet, where the new
block ran into the old text mid-line. Broken into its own paragraph.

Pass 2 nit: the docblock and the test comment compressed the rule context to
"a ...22.1.2-stamped ClinicalDocument", dropping the @extension='2015-08-01'
half of the predicate. Both now name both halves. The conclusion is unchanged,
a Referral Note carries neither root, but this saga has been about reading a
predicate precisely enough, so the short form is the wrong economy here.

check:agent-notes, check:no-emdash and check:no-internal-refs green; builder
suite 205 passing.
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.

1 participant