fix(safety): read a doNotPerform the XML reader kept as lexical text (FHIR-XML-BOOLEAN-NEGATION-LOST) - #79
Merged
Merged
Conversation
…(FHIR-XML-BOOLEAN-NEGATION-LOST) FHIR XML carries every primitive as the text of its `value` attribute, and the reader is schema-free by design, so `<doNotPerform value="true"/>` lands as the string "true" where the JSON reader builds `true`. That is a declared limit and is not what changed. What changed is the read. Measured at 4eb444f, a MedicationRequest carrying "doNotPerform":true went through this package's own serializeResourceXml (which emits `<doNotPerform value="true"/>`, correctly) and back through parseResourceXml, and readSafety returned doNotPerform: undefined, negations: [], issues: [] and safeToSummarize: true, with assertSafeToSummarize passing clean, where the JSON original reads negations: ["do-not-perform"]. The read matched only a JS boolean, and a failed match reads as absence, so an explicit instruction not to give a medication disappeared while the safety spine affirmed the result was safe to summarize. One read is widened, and only one: primitiveBooleans, the safety-layer read, whose sole caller is readDoNotPerform. It is deliberately NOT widened in the XML reader: the model still holds the lexical string, nodesEquivalent still accounts for the difference, and no re-emission changes, because a schema-free reader cannot know the datatype and coercing there would author a value the sender did not spell. The text recognised is exactly `true` and `false`, the whole of the R4 boolean lexical space. "TRUE", "True", "1", "yes", "Y", " true" and "" still read as no boolean, silently, which is a residual rather than a guarantee. The census the item asked for is reported rather than acted on, and that is a finding. Two more boolean reads have the same defect and are left standing, pinned: ElementDefinition.mustSupport and ElementDefinition.slicing.ordered, both through primitiveBoolean, the convenience read. Widening them was drafted and the conformance gate measured it retiring a diagnostic: snapshot.ts merges a differential flag only when it is not undefined, so an XML <mustSupport value="false"/> that base read as undefined began overwriting an inherited true and removing a MUST_SUPPORT_ABSENT the base emitted. A safety read and a convenience read cannot share a widening, because additivity is a property of the consumers and not of the helper. ElementDefinition.min and FHIRPath's convertToBoolean / toTrit / systemTypeOf are the same root class, censused and unchanged, and validatePrimitiveValue is untouched. No diagnostic moves, measured base-vs-head: collectProfileIssues over an XML-sourced profile and validateResource over the round-tripped MedicationRequest are identical on both trees. The only thing that moves is readSafety's doNotPerform and negations. negations is monotone across the change, so this read can only add the do-not-perform negation and never retire it; doNotPerform itself moves further than undefined-to-a-value, also moving false to true where a later value spells the negation an earlier JS boolean contradicts, which is the documented "a true anywhere wins" rule and is pinned. safeToSummarize is unmoved in both directions and the second direction is a declared gap: for a value that does not read ("1", ordinary converter output) the element is present, its value is unread, nothing records that, and the readout still affirms. Pinned rather than absorbed. Red-at-base 6-of-22 against a real 4eb444f worktree, 22-of-22 at head; the other 16 are green in both states on purpose and are named in the notes. Suite 64 files / 1316 tests, no existing test moved. Corpus: hand-authored XML fixtures plus mutations and hand-built probes, not the FHIR R4 published-examples corpus.
Documentation only. No source, no test, no behaviour: `git diff 27cb42c HEAD -- src/ test/` is empty. THIS COMMIT IS UNGRADED. The conformance gate converged at 27cb42c (REFUTED -> REFUTED -> NOT REFUTED, cumulative 3, the ADR 0016 cap), and these two changes were prescribed by pass 3 as advisories it explicitly excluded from its verdict and said it would not hold the slice for. They are applied anyway because one of them lands in CHANGELOG.md, which is frozen while the Version Packages PR is open, so a false clause there would be permanent under ADR 0001. ADR 0027 is NOT invoked and is NOT available here: its route to landing an unconverged gate requires that no pass refuted the code, and pass 1's finding was behavioural (widening the convenience read retired a MUST_SUPPORT_ABSENT through the snapshot merge). The gate converged on its own instead. A1. The doNotPerform transition sentence said the value moves false to true "where a later value spells the negation an earlier JS boolean contradicts". Pass 3 measured that the ordering adverb is not general: {"doNotPerform":["true",false]} puts the lexical true earlier and still moves false to true. The words "later" and "earlier" are deleted at all three carriers (CHANGELOG.md, the changeset, the notes) in favour of "any value ... elsewhere in the element". The load-bearing half, that negations is monotone and this read can only add the negation and never retire it, was verified true by pass 3 and is unchanged. A2. The notes said both helpers are "package-private, single-consumer". primitiveBoolean has two call sites in one module, so that now names the call sites instead of asserting a count. The ungraded status is disclosed here, in documentation/agent-notes/xml-lexical-booleans.md and in the pull request.
NSchatz
added a commit
that referenced
this pull request
Aug 9, 2026
…reach (conformance-refuter pass 2)
`conformance-refuter` pass 2 returned **REFUTED** on `9760562`, cumulative 2. **It refuted no code
again**: it verified the remedy diff was docblock text only, `booleanOf` unwidened base-to-head,
`hasUnreadableBoolean` and `checkUnreadableBooleans` untouched, the complement exact,
`safeToSummarize` monotone toward refusal, the window a superset of the read, and the seventh
mutation consistent with the code. Every finding was a claim defect, and this commit is the
**deletion** the pass prescribed. No `src/` behaviour changes.
## Finding 1, major: the corrected claim was a new claim, and it was also too wide
Pass 1's replacement said the validator "is no substitute, because it draws `TYPE_MISMATCH` on the
lexical `"1"` and on a conformant `<doNotPerform value="true"/>` alike, so it does not separate
readable from unreadable". **The three rows behind that were all XML**, and the conclusion was drawn
unscoped over a channel whose own docblock says "in either wire format". On the **JSON** wire it is
plainly false: `validatePrimitiveValue` shape-checks `typeof value === "boolean"`, so with a schema
supplied a conformant `{"doNotPerform":true}` validates **clean** while `{"doNotPerform":"Y"}` draws
`TYPE_MISMATCH`. The package's own pending `olive-comets-listen` changeset already recorded that, so
two changesets in one release body contradicted each other.
**Deleted, not qualified**, at all six carriers: `README.md`, `CHANGELOG.md` (frozen under ADR 0001
while PR #73 is open), `.changeset/olive-herons-report.md`, the notes, and both docblocks in
`src/safety/status.ts`. The pin's `conformant` row went with it, since it existed only to support the
deleted sentence and the same false error is already pinned with the `Quantity` residuals.
**What survives unscoped is availability, not discrimination**: `MedicationRequest` has no built-in
schema (`BUILTIN_SCHEMAS` is `[Patient]`), so the validator is silent about this element unless a
caller supplies one, and a readout that has to hold on every document cannot rest on a diagnostic
that only exists when a caller opts in. The pin now asserts exactly that and nothing more: silent by
default, available on request.
**This was the second generation of one over-general sentence in one paragraph, one per pass.** The
notes record both refuted versions verbatim beside the survivor, because the trap is that a corrected
claim inherits nothing.
## Finding 2, minor: the public docblock named the wrong read, and it ships in `dist/index.d.ts`
`unreadableBooleans`' docblock (and its README twin) said the window is "wider than `readSafety`'s
own read, which only visits the resource it is handed". `readSafety` does walk the whole tree, and
`arrayWrappedScalars`, named two clauses earlier, is itself computed at every nested resource root.
The subject is the **`doNotPerform` read**, which is what the private docblock already said. Corrected
in both.
## Finding 3, minor: the mirrored correction pointed the wrong way, in the frozen file
`CHANGELOG.md` twice said the channel is added by "the entry below". The `unreadableBooleans` entry is
under `### Added` at the top of `[Unreleased]`; the `#79` entry referencing it is under `### Fixed`
below. Both now say "the entry above". Not present at base.
## Still `PRE-EXISTING`, re-verified by pass 2, and the coordinator owes an item
**`ServiceRequest.doNotPerform` and `CommunicationRequest.doNotPerform` are `?!` boolean modifiers the
safety spine never reads.** Neither type is in `SAFETY_RESOURCE_TYPES` and `readSafety` gates on
`isType("MedicationRequest")`, so a **conformant** `{"resourceType":"ServiceRequest",
"doNotPerform":true}` reads `negations: []`, `safeToSummarize: true`, `assertSafeToSummarize` clean.
Identical at `05ecc5a`, so not a refutation. It is a sharper shape than the item this slice closes:
the document is conformant and the instruction is simply not looked for. Recorded in the notes;
**it is not yet in the backlog**, which is the coordinator's to file. Also still standing: "the six
safety resource types" at `README.md` and six `src/` sites while the set holds seven.
## Re-measured after the remedy
- Suite **65 files / 1,346 tests**, all green. Red-at-base still **18-of-30**, 30-of-30 at head, same
12 both-states pins.
- `verify.sh fhir` green: **11 `==>` headers, 11 `ran:` entries, zero `(FAIL)`**, no
unladdered-script warning.
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
FHIR-XML-BOOLEAN-NEGATION-LOST, filed as a STOP-THE-LINE.FHIR XML carries every primitive as the text of its
valueattribute, and the reader is schema-freeby design, so
<doNotPerform value="true"/>lands as the string"true"where the JSON readerbuilds
true. That is a declared limit and it is not what changed.What changed is the read. Measured at the base commit
4eb444f, aMedicationRequestcarrying"doNotPerform":truewent through this package's ownserializeResourceXml(which emits<doNotPerform value="true"/>, correctly) and back throughparseResourceXml, andreadSafetyreturned:
The read matched only a JS
boolean, and a failed match reads as absence, so an explicitinstruction not to give a medication disappeared while the safety spine affirmed the result was safe
to summarize. A lost
doNotPerformdoes not degrade a value; it inverts an instruction.The change
One read is widened, and only one:
primitiveBooleans, the safety-layer read, whose sole calleris
readDoNotPerform. A newbooleanOfrecognises exactlytrueandfalse, the whole of theR4
booleanlexical space.It is deliberately not widened in the XML reader. The model still holds the lexical string,
nodesEquivalentstill accounts for the difference, and nothing about re-emission changes: aschema-free reader cannot know the datatype, and coercing there would author a value the sender did
not spell and launder it across a format change.
The census, and what it did NOT license
The item asked for a census of every boolean the safety spine reads. It found three sites.
Only one was changed, and the conformance gate is why.
MedicationRequest.doNotPerformprimitiveBooleansElementDefinition.mustSupportprimitiveBooleanElementDefinition.slicing.orderedprimitiveBooleanThe first draft widened both helpers.
conformance-refuterpass 1 REFUTED it with a measuredbehavioural counterexample:
snapshot.tsmerges a differential flag only when it is notundefined,so an XML
<mustSupport value="false"/>that base read asundefinedbegan overwriting an inheritedtrueand retiring aMUST_SUPPORT_ABSENTthe base emitted. That is the retirement class#78measured at
validatePrimitiveValueand declined, reopened one layer over.The rule this bought: a safety read and a convenience read cannot share a widening. Additivity is
a property of the consumers, not of the helper.
primitiveBooleansfeedsnegations, where anewly-read value can only add.
primitiveBooleanfeeds callers that treatundefinedas inherit,where the same value removes a finding.
Censused and deliberately unchanged:
validatePrimitiveValue's boolean branch, FHIRPathconvertToBoolean/toTrit/systemTypeOf,parseMinand FHIRPathnumberOf(the same rootclass one datatype over), and the writers.
Measurements
4eb444fworktree rather than argued:collectProfileIssuesover an XML-sourced profile andvalidateResourceover the round-trippedMedicationRequestare identical on both trees. The only thing that moves isreadSafety'sdoNotPerformandnegations.named in the notes: seven lexical-space negative controls, the type gate, the decimal-form control,
the JSON-reader control, three profile-census pins and three residual pins. Non-vacuity by
mutation: a case-insensitive
booleanOfreds 3, dropping thefalsearm reds 1, accepting"1"reds 1.
verify.sh fhirgreen: 11 step headers, 11ran:entries, zero(FAIL), no unladdered-scriptwarning. Suite 64 files / 1,316 tests, no existing test moved.
Residuals, pinned in
test/xml-lexical-boolean.test.tsClosing any of them must red its test in the same change.
ElementDefinition.minunread from XML, silently, so an XML-sourced profile declares requiredelements this library enforces none of.
convertToBooleanreads an XML-sourcedfalseastrue.safeToSummarizeis unmoved in both directions, and the second direction is a real gap: for avalue that does not read (
"1", ordinary v2/C-CDA converter output) the element is present, itsvalue is unread, nothing records it, and the readout still affirms.
SafetyReadouthas locationchannels for content the codec could not read and none for "value written, not readable". Base did
this for
truetoo, so it is strictly improved, but it wants its own item.The lexical space itself is silent:
"TRUE","1","yes"," true"read as no boolean withnothing on any diagnostic channel.
The gate
conformance-refuter, REFUTED -> REFUTED -> NOT REFUTED, cumulative 3 (4b67787,1d9953d,27cb42c; the first two shas were amended away).the claim: the profile widening is reverted,
primitiveBooleanis byte-identical to base, and thecensus is reported instead.
sentence "can only fill in a value that was
undefined" was falsified by this slice's own fixture,the third generation of one universal. Replaced with the pass's own wording.
minor severity, nothing behavioural. It independently re-derived red-at-base 6-of-22, re-ran the
suite green, and verified every claim it was asked to grade.
One commit is UNGRADED and disclosed:
713e718applies pass 3's two advisories, which itexcluded from its verdict and said it would not hold the slice for. Documentation only,
git diff 27cb42c 713e718 -- src/ test/is empty. It deletes an over-specifiedlater/earlierordering from the
doNotPerformtransition sentence at three carriers and corrects"single-consumer" to name the call sites. It is applied rather than deferred because one carrier is
CHANGELOG.md, frozen while #73 is open, where a false clause would be permanent under ADR 0001.ADR 0027 is NOT invoked and is not available here: its route requires that no pass refuted the
code, and pass 1's finding was behavioural. The gate converged on its own.
Corpus caveat
Hand-authored XML fixtures plus mutations and hand-built probes, not the FHIR R4
published-examples corpus. Nothing here is corpus-wide.
Not touched
PR #73 (Version Packages) is untouched and stays open. No umbrella pointer bump in this PR.