fix(conformance): stamp the Medications section 2014-06-09, not 2015-08-01 - #107
Merged
Conversation
…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
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
@rootand an@extension, as a pair. Five are2015-08-01:…22.2.6.12015-08-01…22.2.1.12014-06-09…22.2.5.12015-08-01…22.2.3.12015-08-01…22.2.172015-08-01…22.2.4.12015-08-01medicationsSectioncalledsectionElementandemptySectionwithout an extension argument, so both took the builder'sR21default and stamped…22.2.1.1with2015-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 namedMED_SECTION_EXT.R2.1 revised this section at the earlier stamp and never re-issued it: the Schematron carries
cda:sectionrule contexts for both Medications roots (…22.2.1and…22.2.1.1) at2014-06-09and zero rule contexts for either root at2015-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, andeditCcdarebuilds 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-30664is sentence-unique: exactly one hit in the file.<sch:rule>is…22.1.2-2015-08-01-errors-abstract, which isabstract="true"and carries nocontextattribute at all.sch:extendsit:cda:ClinicalDocument[cda:templateId[@root='…22.1.2' and @extension='2015-08-01']], an existential predicate over the document'stemplateIdchildren, 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, sha25604be58046a675735616e46cf52053688a2fc9d0c88010f14fd1c5a2f4ca5bd54, 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+extensionpair stops matching. But this package's own parser,sectionForTemplateRootand 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.patchon the0.0.xpre-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:extendsexpansion, phase handling and first-rule-wins semantics, and the.schis 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.1with no stamp, and passed throughout. The new tests assert the pair for all six SHALL sections, and additionally assert the2015-08-01pair 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, notgit checkout.Full suite 928 passing, up from 918.
verify.sh ccdagreen (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:PRE-EXISTING, major:docs-content/quickstart.mdcarried 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.INTRODUCED, minor: the changeset headline was unconditional where its own body was conditional. Rewritten.INTRODUCED, minor: missingProvenance: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-01outside 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.mdgains a note that the six section templates do not share one stamp.documentation/agent-notes.mdgains the full long-form record.ccda/CLAUDE.mdgains 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.