@cosyte/x12 0.0.15 → 0.0.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,225 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Documentation
11
+
12
+ - **The documentation carriers that told you to read an envelope element's logical value through
13
+ `getSegmentValue` are corrected** (`X12-ENVELOPE-VALUE-ROUTES`). **Documentation and tests only: no
14
+ runtime line changed and the emitted JS is byte-identical, so no behaviour moved.**
15
+
16
+ `getSegmentValue` takes an `X12Segment`, which requires `id`. `IsaSegment`, `IeaSegment`,
17
+ `GsSegment`, `GeSegment`, `Ta1Segment` and the inline ST/SE types on `X12TransactionSet` declare
18
+ only `raw` and `elements`, so passing any one of them is `TS2345`, "Property 'id' is missing".
19
+ **All seven are measured here.** The carriers include
20
+ `KNOWN-LIMITATIONS.md`'s _"Read through `getSegmentValue` if you want the logical value"_ for a
21
+ `gs`, which is the one that owed you a route and so is the one replaced rather than cut.
22
+
23
+ **Two routes reach the decoded text, both were already published in this package, and neither is
24
+ universal.** `unescapeRelease` on the element string is a public export. Adding an `id` and taking
25
+ the dot-path is what `buildTA1`'s own JSDoc prescribes ("add one to read a TA1 through it") and
26
+ what this repo's tests do. They are not interchangeable:
27
+
28
+ ```text
29
+ element raw unescapeRelease add id + dot-path transmitted
30
+ GS-04, a released `*` "2026?*0601" "2026*0601" "2026*0601" "2026*0601"
31
+ GS-07, a REAL repetition "A^B" "A^B" "A" "A^B"
32
+ TA1-01, a released `^` "0000?^0001" "0000^0001" "0000^0001" "0000^0001"
33
+ ```
34
+
35
+ Every row `warnings: []`. A bare dot-path means repetition 0, which is why the GS-07 row differs.
36
+
37
+ **The public signature was NOT widened, and that was the decision rather than the cheap way out.**
38
+ `getSegmentValue`'s body never reads `id`, so widening its parameter to an `elements`-only
39
+ structural type is free, non-breaking and emits nothing. It was refused because the function
40
+ unescapes unconditionally while the ISA is positional, where a `?` is content and never an escape.
41
+ Widening admits `IsaSegment` and makes a silently wrong read of ISA-13, the reassociation key,
42
+ compile:
43
+
44
+ ```text
45
+ transmitted ISA-13 arity elements[13] decoded (both routes) elements[16]
46
+ "000000??1" 17 "000000??1" "000000?1" ":"
47
+ "0000?*001" 18 "0000?" "0000?" "P"
48
+ "00000001?" 17 "00000001?" "00000001?" ":"
49
+ ```
50
+
51
+ No route reads an ISA element correctly across those rows. On the middle one `decodeIsa` split on
52
+ the element separator, so `elements[13]` is a prefix and ISA-16 re-indexed onto ISA-15's value.
53
+ **So no route is prescribed for the ISA at all**, and the missing arity check on that split is
54
+ filed rather than fixed here. A capability that is right on six envelope types and silently wrong
55
+ on the seventh is not an ergonomic win, so the claim was cut instead and no code grew.
56
+
57
+ `test/parser-envelope-value-routes.test.ts` pins all of it. Seven `@ts-expect-error` assertions
58
+ cover the seven types, and `typecheck` runs over `test/`, so **they red if the signature is ever
59
+ widened**: widening is not forbidden, it is made loud, because the assertions have to be deleted
60
+ and that is where the ISA question gets answered. Under a mutation that widens the signature the
61
+ file reports seven unused directives, not one, which is what distinguishes it from a green vacuous
62
+ test.
63
+
64
+ Two earlier statements are deleted rather than reworded, because both are measured false. _"There
65
+ is no decoded read for an envelope element; `unescapeRelease` on the string is the only route"_:
66
+ there are two, and the second is prescribed inside this package. _"A raw-vs-`unescapeRelease` cell
67
+ on the ISA is a tautology that detects nothing"_: `unescapeRelease` does not know the ISA is
68
+ exempt, so on `"000000??1"` the cell differs.
69
+
70
+ Unchanged and filed: `parse-ta1.ts` and `KNOWN-LIMITATIONS.md` still mis-cite `X12Segment.elements`
71
+ for types that are not one. That statement is true where these were false, and it has a twin, so it
72
+ stays its own slice. `docs-content`'s dot-path example is scoped to body segments and was already
73
+ correct.
74
+
75
+ - **Eleven JSDoc pointers that sent a consumer to an envelope `elements[n]` for a value are deleted**
76
+ (`X12-ENVELOPE-VALUE-POINTERS`). **Documentation only: the diff is comment-only and the emitted JS
77
+ is byte-identical, so no behaviour changed.**
78
+
79
+ `X12_CONTROL_NUMBER_MISMATCH`'s JSDoc told you to read `isa.elements[13]`, `iea.elements[2]`,
80
+ `gs.elements[6]`, `ge.elements[2]`, `st.elements[2]` and `se.elements[2]` "off the model when you
81
+ need the values". Envelope element values are stored RAW, pre-`?`-unescape, so on a document whose
82
+ control number carries a release escape those reads hand back the framed bytes and not the value:
83
+ `gs.elements[6]` reads `"000?*99"` where the value is `"000*99"`. The same shape sat in
84
+ `X12_GROUP_COUNT_MISMATCH`'s and `X12_TRANSACTION_COUNT_MISMATCH`'s "both numbers stay on the model
85
+ (`iea.elements[1]` …)" parentheticals and in `serializeX12`'s module doc, which named three more.
86
+ All of those are **deleted**; nothing was added to make one true, and no accessor, guard or test
87
+ assertion was minted.
88
+
89
+ The surviving sentences still say the bytes stay on the model, which is true. What is gone is the
90
+ promise that a raw element index yields the decoded value.
91
+
92
+ Pointers that **label** their surface as raw are correct and are untouched: `raw.elements[5]` "is
93
+ the byte surface", `gs.elements[8]` gets "the sender's bytes rather than a normalization",
94
+ `seg.elements[0]` stays "verbatim".
95
+
96
+ **This does not bound the class.** `src/parser/types.ts` still names `iea.elements[2]`,
97
+ `gs.elements[6]`, `ge.elements[2]` and `ta1.elements[1]` for values in the `@example` blocks of the
98
+ envelope types, with no raw label; those are unchanged here and filed. `ta1.elements[1]` is the one
99
+ to know about: TA1-01 is the reassociation key, so a consumer following that example compares
100
+ framed bytes to the ISA-13 it acknowledges.
101
+
102
+ Two more pre-existing limits, unchanged and filed. **`getSegmentValue` cannot read an envelope
103
+ segment**: it takes an `X12Segment`, which requires `id`, and `IsaSegment`, `IeaSegment`,
104
+ `GsSegment`, `GeSegment`, `Ta1Segment` and the ST/SE types declare only `raw` and `elements`, so
105
+ passing one is a `TS2345`. And **the ISA element
106
+ split has no arity check**: `decodeIsa` splits on the element separator, so an ISA-13 carrying a raw
107
+ separator yields 18 elements where 17 are documented and silently re-indexes ISA-14/15/16.
108
+
109
+ ### Fixed
110
+
111
+ - **🩺 `implementationConventionReference` is POST-`?`-unescape in every typed reader that publishes
112
+ it** (`X12-ST03-READ-NOT-RELEASE-AWARE`). A behaviour change on any document whose `ST-03` carries
113
+ a release escape.
114
+
115
+ `X12TransactionSet.st.elements` is the ST segment as framed - post-element-split and PRE-unescape -
116
+ and five public readers were handing one
117
+ of those strings straight back on the model: `get837Claims`, `get277Status`,
118
+ `get277CADisposition`, `get278Request` and `get278Response` (`walk277` and `walk278` are shared, so
119
+ three reads serve the five). A sender that escaped a delimiter inside `ST-03` got the escape back
120
+ rather than the value it stated. The cells that were run, framed element text on the left:
121
+
122
+ ```text
123
+ ST-03 as framed published through 0.0.15 published now
124
+ "A??B" "A??B" "A?B"
125
+ "A?*B" "A?*B" "A*B"
126
+ "A?:B" "A?:B" "A:B"
127
+ "A?~B" "A?~B" "A~B"
128
+ "A?^B" "A?^B" "A^B"
129
+ ```
130
+
131
+ The grounding is this package disagreeing with itself and nothing else: every dot-path read
132
+ (`elementValue`, `elementOptional`, `componentOptional`) already unescaped, and `parse999` already
133
+ decoded `AK2-03`, the identically-named field. No TR3 clause is claimed, on either side.
134
+
135
+ **🛑 What DECIDES an outcome did not move, and that was a deliberate scope call.** The 837 variant
136
+ lookup, the 277 / 277CA `transactionType` discriminator and `get277CADisposition`'s admission gate
137
+ all still key on the RAW element text, so **no document changes variant, discriminator or
138
+ admission because of this entry.** They can differ: a letter is an admissible delimiter, so with
139
+ `componentSeparator: "X"` an `ST-03` framed as `005010?X222A1` decodes to `005010X222A1`, which the
140
+ variant table holds. Keying on the decoded text would make the declaration beat the `SVx` fallback
141
+ there, and on a document whose only service segment is an `SV2` that stops the line decoding and
142
+ raises `X12_837_SERVICE_LINE_NOT_DECODED`. That is a change to how an already published document
143
+ decodes a service line rather than a decode fix, so it is left open. **The difference is one-way:
144
+ no document that resolved a variant, or was admitted by `get277CADisposition`, stops doing so** -
145
+ no identifier any of the three tests is keyed on carries a delimiter or the release character, so
146
+ raw text equal to one decodes to itself.
147
+
148
+ **🛑 The consumer-visible consequence, stated rather than left to be inferred: the published
149
+ reference can name a guide this reader did NOT resolve to, and nothing warns about the
150
+ divergence.** On the `componentSeparator: "X"` document above,
151
+ `submission.implementationConventionReference` reads `005010X222A1`, which the variant table
152
+ holds, while `submission.variant` is `I` from the `SVx` fallback. **"Nothing warns" is NOT the
153
+ boundary of it.** On the same delimiters, a body with no `SVx` at all publishes `005010X222A1`
154
+ with `variant: "unknown"` and raises `X12_837_UNKNOWN_VARIANT`, and a body naming more than one
155
+ variant publishes it with `variant: "P"` and raises `X12_837_AMBIGUOUS_VARIANT` - in both cases
156
+ the code that fired says `ST-03` named no identifier this reader recognises while the model field
157
+ holds one it does. **That is why `X12_837_UNKNOWN_VARIANT`'s closing pointer at the model is
158
+ deleted rather than reworded.** The 277 shape is the
159
+ same:
160
+ the model publishes `005010X214` while `transactionType` is `claim-status` and
161
+ `get277CADisposition` returns `undefined`. **Through `0.0.15` the published value WAS the keyed
162
+ value, so the model could not disagree with itself. Gate on `variant` / `transactionType`, never
163
+ on the published reference.** No code moved and no code was minted.
164
+
165
+ **No normalisation and no new warning.** Nothing is trimmed, case-folded or prefix-matched, and a
166
+ whitespace-only `ST-03` is still published untrimmed. A dangling `?` at the end of the element
167
+ still raises no `X12_DANGLING_RELEASE_CHAR` here: the sink is a no-op, matching `getSegmentValue`'s
168
+ default, `parseTA1` and `parse999`, so every other element these readers decode drops it too.
169
+ Each reader's own empty / absent mapping is unchanged - `walk278` still collapses `""` to
170
+ `undefined`, the other two still publish `""`. **`tx.st.elements` is untouched and is still the
171
+ verbatim framed surface.** If you were applying `unescapeRelease` to
172
+ `submission.implementationConventionReference` yourself, drop that call.
173
+
174
+ - **🩺 `parseTA1`'s five decoded fields are POST-`?`-unescape, and an empty TA1-02 / TA1-03 / TA1-04
175
+ / TA1-05 is refused on emit** (`X12-TA1-RESIDUALS`). Both are behaviour changes. They ship as one
176
+ entry because they are the two ends of the same disagreement: this package's emit half releases a
177
+ TA1 element and its read half decoded the escape rather than the value.
178
+
179
+ **The read half.** The entry below made `buildTA1` release all five caller elements; `parseTA1`
180
+ kept reading `elements` verbatim, so a round trip through this package's own halves was not an
181
+ identity for any value carrying a delimiter or the release character. Measured at `0.0.15`, over
182
+ `parseX12` + `parseTA1` of what `buildTA1` had just emitted:
183
+
184
+ ```text
185
+ interchangeControlNumber raw emitted parseTA1 read dot-path read
186
+ "00000001?" TA1*00000001??*260601*1200*A*000 "00000001??" "00000001?"
187
+ "0000*0001" TA1*0000?*0001*260601*1200*A*000 "0000?*0001" "0000*0001"
188
+ "0000~0001" TA1*0000?~0001*260601*1200*A*000 "0000?~0001" "0000~0001"
189
+ "0000:0001" TA1*0000?:0001*260601*1200*A*000 "0000?:0001" "0000:0001"
190
+ "0000^0001" TA1*0000?^0001*260601*1200*A*000 "0000?^0001" "0000^0001"
191
+ ```
192
+
193
+ Every row `warnings: []`. TA1-01 is the reassociation key, so the left column is a key that
194
+ matches no ISA-13. The right column is the same element. The grounding is that
195
+ disagreement and nothing else - no clause anyone here has read settles what a TA1 element may
196
+ contain.
197
+
198
+ **`raw` is untouched and is still the verbatim byte surface**, so `ta1.raw.elements` still answers
199
+ the bytes. A value with no `?` in it is unchanged on both surfaces, which is every conformant TA1.
200
+ **If you were applying `unescapeRelease` to `parseTA1`'s output yourself, as `KNOWN-LIMITATIONS.md`
201
+ advised while this was open, drop that call.** The dangling-`?` warning
202
+ `X12_DANGLING_RELEASE_CHAR` is not surfaced here, because `parseTA1` returns no warnings channel;
203
+ `parse999` drops the same warning from the same helper for the same reason.
204
+
205
+ **The emit half.** `escapeRelease` early-returns on `""` and `buildTA1` carried a required-field
206
+ guard for TA1-01 alone, so the other four slots emitted an absent element with `warnings: []`:
207
+
208
+ ```text
209
+ interchangeDate: "" TA1*000000001**1200*A*000 interchangeTime: "" TA1*000000001*260601**A*000
210
+ ackCode: "" TA1*000000001*260601*1200**000 noteCode: "" TA1*000000001*260601*1200*R*
211
+ ```
212
+
213
+ Each now draws `AckBuildError` with the existing code `X12_ACK_INVALID_SPEC`, naming the slot and
214
+ the spec property and never the value. **No new error code is minted and no warning code moves.**
215
+ The grounding is again inside the package: `BuildTA1Spec` declares all five properties as required
216
+ `string`s, `""` is what defeats that at run time, and the in-package answer to an empty required
217
+ element is uniformly refusal.
218
+
219
+ **🛑 It does NOT trim, at any of the five slots** - a whitespace-only element still builds, exactly
220
+ as at TA1-01, because trimming is a normalisation rule and no source consulted for this package
221
+ states one. **🛑 It does not narrow what a NON-empty element may contain**: an out-of-enum
222
+ `ackCode: "X"` still builds and the read half still applies its documented fail-safe narrow to
223
+ `R`, which is the same answer an empty one gave. **🛑 Every guard that stood before it keeps its
224
+ precedence** - `enforceAcceptIsClean` still runs first, TA1-01 still draws the control-number
225
+ refusal, and every wrong-TYPED element still draws the escape helper's refusal, because all five
226
+ escapes run before any emptiness test. No spec that was refused before this change is refused
227
+ differently by it.
228
+
10
229
  ### Added
11
230
 
12
231
  - **🩺 `Build837EnvelopeSpec.implementationConventionReference` - the caller now states the ST-03 /
@@ -409,6 +628,80 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
409
628
 
410
629
  ### Changed
411
630
 
631
+ - **🩺 An EMPTY control number is now REFUSED on emit, where ISA-13 / IEA-02 used to be FABRICATED
632
+ out of it** (`X12-EMPTY-CONTROL-NUMBER-FABRICATED`). Reproduced on the base tree at `28b417f`,
633
+ which is `main` at published `0.0.15`.
634
+
635
+ Every builder that assembles an ISA zero-pads its control number to the nine characters ASC X12 .5
636
+ fixes ISA-13 at. `padControl("1", 9)` answering `"000000001"` is the point of that helper;
637
+ `padControl("", 9)` answering `"000000000"` was not, and nothing stood in front of it. So
638
+ `interchangeControlNumber: ""` produced a frozen, well-formed interchange carrying a nine-digit
639
+ control number the caller never supplied, ISA-13 reconciling perfectly against IEA-02, with an
640
+ empty warning array. **A control number is how an interchange is reconciled and acknowledged, so a
641
+ fabricated one does not fail: it succeeds against the wrong thing, and the sender gets no signal.**
642
+
643
+ **The class is wider than that slot, and it has two mechanisms rather than one.** The group and
644
+ transaction-set control numbers reach the wire through the release escaper, which early-returns on
645
+ `""`, so an empty one lost the required element. **The bytes are not the same in every builder**:
646
+ `buildInterchange` and `build999` join without trimming, so the element goes out empty, while the
647
+ seven domain builders share a segment helper that drops a trailing empty element, so the trailer
648
+ loses it outright. Measured one variable at a time, through `buildInterchange` and `build834`:
649
+
650
+ ```text
651
+ buildInterchange interchangeControlNumber: "" ISA*…*00501*000000000*0*P*:~ … ~IEA*1*000000000~
652
+ buildInterchange groupControlNumber: "" GS*HC*…*1200**X*005010X222A2~ … ~GE*1*~
653
+ buildInterchange transactionSetControlNumber: ST*837**005010X222A2~ … ~SE*3*~
654
+ build834 groupControlNumber: "" GS*BE*…*1200**X*005010X220A1~ … ~GE*1~
655
+ build834 transactionSetControlNumber: ST*834**005010X220A1~ … ~SE*21~
656
+ ```
657
+
658
+ Every one of those emitted `warnings: []`, which is the property that holds across both families:
659
+ no diagnostic on any channel separated an absent control number from a supplied one. The acknowledgment builders carried the same class at the slots
660
+ where they **echo** the document being acknowledged, which is the whole reason a sender can match
661
+ an ack to what they sent: `build999` emitted `AK1*HC**005010X222A2~` and `AK2*837*~`, and
662
+ `buildTA1` emitted `TA1**260601*1200*A*000`. The `buildTA1` case was already disclosed in
663
+ `KNOWN-LIMITATIONS.md` as tracked and open; `AK1-02` and `AK2-02` were not disclosed anywhere.
664
+
665
+ All of them now draw that builder's own typed, code-tagged refusal before anything is emitted,
666
+ naming the slot and the spec property: `build837: groupControlNumber is empty. GS-06 / GE-02 is a
667
+ required control number and this builder never invents one, so nothing is emitted.` **No new error
668
+ code is minted and no warning code moves.** What changes is that a call which used to hand you a
669
+ document now throws, and that a spec which is wrong in two ways can now report a different one of
670
+ the two. See the precedence bullet below: this is not "nothing a consumer branches on changes", and
671
+ a draft of this entry said exactly that.
672
+
673
+ **Why refuse rather than warn, stated as a decision because the standard does not settle it.**
674
+ 005010 says nothing about what a builder should do with an absent control number. The tiebreak is
675
+ CONSISTENCY inside this package: every other empty required element on the emit side is already
676
+ refused by name (`build835`'s `patientControlNumber`, `build837`'s `claimId`, `build834`'s
677
+ `maintenanceTypeCode`, `build278`'s `requestCategoryCode`, `build277`'s `categoryCode`), emit is
678
+ the deliberately strict half of this library, and a warning would have had to travel the parse
679
+ channel a builder returns, which is the registry consumers use to grade INBOUND documents.
680
+
681
+ **What it costs, and it is a real break:** a caller relying on `""` to mean "pad me a control
682
+ number" is broken by this, deliberately. What they were shipping was `000000000`, a real value a
683
+ trading partner may already have assigned to something else.
684
+ - **🛑 The guard is byte-strict `=== ""` and does NOT trim.** A whitespace-only control number is
685
+ still accepted and still padded: `" "` emits ISA-13 as `00000000 `. `buildTA1` does no padding at
686
+ all, so it emits whatever whitespace it was handed, verbatim. Trimming would be a normalisation
687
+ rule, no source consulted for this package states one, and the five in-package guards this
688
+ mirrors are byte-strict for the same reason. Pinned as a test and disclosed in
689
+ `KNOWN-LIMITATIONS.md` as the one shape still worth screening at your own boundary.
690
+ - **A SHORT control number still zero-pads.** The guard is not "ISA-13 must be nine characters":
691
+ `"1"` still emits `000000001`, which is what the padding is for.
692
+ - **🛑 Every guard sits at the envelope-assembly site, so every guard that runs BEFORE it keeps its
693
+ precedence, and that is the whole claim.** `build835`'s balance equation, `build999`'s AK9 count
694
+ reconciliation, `buildTA1`'s accept-must-mean-accept check, the 837's and 277's hierarchy checks
695
+ and `build834`'s unknown-maintenance-type refusal all still fire ahead of this one, each
696
+ verified. **What it does NOT preserve is an ordering against a defect detected LATER**, during
697
+ body assembly, which now reports the control-number refusal instead. Measured: `build999` with an
698
+ empty `interchangeControlNumber` and six AK9 syntax error codes threw `X12_ACK_COUNT_MISMATCH` at
699
+ `0.0.15` and throws `X12_ACK_INVALID_SPEC` here. If you branch on a specific builder error code,
700
+ that pairing moved.
701
+ - **No census of the slots that are NOT routed through the guard is published**, here or anywhere
702
+ else in the package. The claim is the property: a control number routed through the new guard is
703
+ refused when empty.
704
+
412
705
  - **🩺 `get837Claims` now recognises every published `ST-03` implementation-convention reference for
413
706
  the three 837 guides, and some already-published files therefore decode differently**
414
707
  (`X12-VARIANT-ICR-UNGROUNDED`). This is a grounding unit: the table `VARIANT_BY_ICR` had three keys
@@ -684,13 +977,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
684
977
  "An `X12Decimal` slot holds no identifier today" would have been the wrong kind of argument: a fact
685
978
  about today's slots rather than a property of the guard.
686
979
 
687
- **Two things that property does NOT say, and both were drafted as absolutes first.** The array
980
+ **What that property does NOT say, and it was drafted as an absolute first.** The array
688
981
  guard still renders a forged array-like's `length` and its class tag through `renderCallerValue`,
689
982
  bounded: those describe the SHAPE a caller forged rather than an element's contents, and they are
690
- the whole diagnostic for `{ length: "9".repeat(120000) }`. And **only the segment-join guard names
691
- the SLOT** - `esc` and `escDec` name the BUILDER, a limit `caller-string.ts` already recorded, so on
692
- those two the echoed value used to stand in for a locator and now nothing does. That is a real
693
- diagnostic cost and it is disclosed rather than argued away.
983
+ the whole diagnostic for `{ length: "9".repeat(120000) }`.
694
984
 
695
985
  **The segment guard's slot locator is now bounded by GRAMMAR rather than by length.** `parts[0]` is
696
986
  caller-supplied in `buildInterchange`, which takes `[segmentId, ...elements]` wholesale, so it is
@@ -950,6 +1240,303 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
950
1240
 
951
1241
  ### Fixed
952
1242
 
1243
+ - **🩺 BREAKING (emit side): a delimiter that is not SHAPED like a delimiter is REFUSED by every
1244
+ builder** (`X12-EMIT-DELIMITER-SHAPE-UNCHECKED`). Each of the four roles must be a **string of
1245
+ exactly one visible character**, and the four must be **mutually distinct**. Reproduced on the base
1246
+ tree at `a21f8ea`.
1247
+
1248
+ **The rule is not invented for emit.** `detectDelimiters` already decides what a delimiter is for
1249
+ this package, and decides it as a Tier-3 fatal: one character at each of four fixed ISA positions, each
1250
+ visible, the four distinct, else `X12_INVALID_DELIMITERS` thrown even in lenient mode. The
1251
+ builders now import that predicate rather than restating it, so the emit refusal and the read fatal
1252
+ cannot drift apart into two ideas of what a delimiter is. **A builder composing a document its own
1253
+ parser refuses to read was disagreeing with itself.** Nothing is trimmed, coerced or substituted:
1254
+ the set is refused. The one-character requirement is structural rather than conventional, because
1255
+ the ISA is fixed-width per ASC X12 .5 and `Delimiters` records exactly one character per role.
1256
+
1257
+ **🛑 It is a UTF-16 code-unit rule and NOT a byte rule, and that difference is a residual it does
1258
+ not close.** A character that is one code unit but several bytes on the wire satisfies it and still
1259
+ displaces every ISA position after it: `componentSeparator: "\u00a7"` builds with `warnings: []`,
1260
+ and a byte-oriented receiver reads ISA-16 as `0xC2` and the terminator as `0xA7`. The smart quote
1261
+ `"\u2019"` that a companion-guide PDF gives you instead of `'` does the same. Disclosed, not
1262
+ guarded: the read side counts code units too, so moving one side alone would put them back out of
1263
+ step.
1264
+
1265
+ **Three mechanisms, and they are not one defect.**
1266
+ - **LENGTH.** `build837P` with `segmentTerminator: "~~"` built with `warnings: []` and put 31
1267
+ segment rows on a transaction whose `SE-01` declares 16, every other row a phantom the caller
1268
+ never wrote. **No claim is published about which roles were silent** - two drafts of this entry
1269
+ stated an asymmetry and both were measured false, the second inside the fix for the first.
1270
+ `buildTA1` with `{ elementSeparator: "||" }` returned `TA1||000000001||260601||1200||A||000`,
1271
+ which inside an ISA reads back with `TA1-01` empty and `ackCode: "R"` - an Accept emitted as a
1272
+ Reject. And `buildInterchange` with `componentSeparator: ":~"` built with `warnings: []` too,
1273
+ reading back through a well-formed ISA while the builder's own terminator became an uncounted
1274
+ empty segment, with no element value escaped against `:`.
1275
+ - **TYPE, where the join coerces and the escape does not.** `Array.prototype.join` coerces a
1276
+ non-string delimiter and the document frames on the coerced byte, but `escapeRelease` compares
1277
+ delimiters with `===`, so no element value is escaped against it. `build837P` with
1278
+ `componentSeparator: 1` emitted `SV1*HC199213`, which reads `SV1-01-2` back as `992` rather than
1279
+ the procedure code `99213`, and `CLM-05`'s place-of-service composite as `111B11`; `build271`
1280
+ with `repetitionSeparator: 1` emitted `EB*1**3011`, and `EB-03`'s two service type codes stopped
1281
+ reading back as two. `warnings: []` on both. 🩺 No unusual caller value is involved, and a
1282
+ length rule cannot reach this at all.
1283
+ - **`buildTA1` had no net.** It is the only builder that does not end in `parseX12`, so
1284
+ `elementSeparator: ""` RETURNED `TA10000000012606011200A000`: the reassociation key, the date,
1285
+ the time, the disposition and the note code in one undelimited blob.
1286
+
1287
+ **⚖️ Refuse rather than warn, and no code is minted.** The tiebreak is consistency with the two
1288
+ guards that shipped before it, not a 005010 clause; a warning would have to travel the READ
1289
+ registry a builder returns, which `#83` was refuted for. Each builder refuses with its own existing
1290
+ typed error. The check runs immediately after the release-character guard, so nothing that guard
1291
+ pinned moves and its equality test is byte for byte unchanged.
1292
+
1293
+ **🛑 What a caller catches MOVES, in both directions.** At base most mis-shaped sets failed as an
1294
+ `X12ParseError` with `X12_INVALID_DELIMITERS` escaping out of the `build*` call, carrying a 64-byte
1295
+ `snippet` of the interchange just composed. They now refuse earlier with that builder's own error
1296
+ and its existing code, **so a consumer catching the parse class around a `build*` call stops
1297
+ catching and one catching the build class starts.**
1298
+
1299
+ **🛑 It refuses shapes that built with `warnings: []`, and no count of them is published** - a
1300
+ draft said "two" and one more turned up the moment a shape the census had not enumerated was tried.
1301
+ **The plausible one is `segmentTerminator: "~\r\n"`.** If you declared that to get line-broken output it never produced
1302
+ any: CR/LF between segments is tolerated on READ, so the model recorded `~` and `serializeX12`
1303
+ emitted no line breaks. Reading a file written that way is unaffected; only declaring it on emit
1304
+ is.
1305
+
1306
+ **🛑 The read side and `serializeX12` are untouched.** `parseX12` accepts everything it accepted
1307
+ before, because documents declaring these sets exist and Postel's Law puts them on the lenient
1308
+ half. A letter or digit is still an admissible delimiter: ASC X12 constrains a delimiter by
1309
+ position, not by character class, and a delimiter that collides with the DATA is a different
1310
+ family that this does not close.
1311
+
1312
+ - **🩺 BREAKING (emit side): a delimiter set in which the release character `?` is also a delimiter
1313
+ is REFUSED by every builder, in all FOUR roles** (`X12-EMIT-DEGENERATE-RELEASE-DELIMITER`).
1314
+ Reproduced on the base tree at `51de7b2`.
1315
+
1316
+ **The property, stated so one more trigger byte cannot falsify it.** `escapeRelease` protects a
1317
+ byte by **prefixing** `?` to it, in whatever role `?` was declared, so when `?` is one of the four
1318
+ delimiters the protection is emitted as structure. The inverse holds at the same time: a builder
1319
+ joins composites with the component separator and repetitions with the repetition separator, so
1320
+ where either of those IS `?` the library's own structural join is emitted as an escape sequence.
1321
+
1322
+ **Two mechanisms, and only the first involves a caller's value.** The filed defect named the first
1323
+ and reached three roles; measured, the class is four roles, and the second mechanism fires on
1324
+ documents in which no value carries any trigger byte at all:
1325
+
1326
+ ```text
1327
+ elementSeparator "?" buildInterchange ["CLM","PATIENT?ACCT","150.00"]
1328
+ reads ["CLM","PATIENT","","ACCT","150.00"]
1329
+ segmentTerminator "?" buildInterchange ["CLM","PAT*ACCT","150.00"]
1330
+ CLM-01 reads "PAT", a phantom segment follows
1331
+ componentSeparator "?" build837P, EVERY document, no trigger byte:
1332
+ SV1-01-2 (the procedure code) reads undefined
1333
+ HI-01-2 (the diagnosis code) reads undefined
1334
+ repetitionSeparator "?" build271, EVERY document, no trigger byte:
1335
+ EB-03 "30" + "1" reads back as one code "30?1"
1336
+ warnings: [] on every row.
1337
+ ```
1338
+
1339
+ 🩺 The second mechanism is the sharper one: a procedure code and a diagnosis code are what a claim
1340
+ is adjudicated on, and neither the caller nor the receiver had any signal that they were fused.
1341
+
1342
+ **⚖️ REFUSE rather than warn, and the whole SET rather than the values that trip.** 005010 does not
1343
+ transmit a release character at all and settles none of this, so the tiebreak is CONSISTENCY with
1344
+ the guards this package already carries on emit and with emit being the strict half of Postel's
1345
+ Law here - the same call `X12-EMPTY-CONTROL-NUMBER-FABRICATED` made one slice earlier. A
1346
+ value-level guard was rejected on measurement: it cannot reach the second mechanism at all, and
1347
+ _"keep `?` out of your values"_ was already refuted once in this arc for protecting nobody. **No
1348
+ warning code is minted and no case moves onto a new code** - each builder refuses with its own
1349
+ existing typed error.
1350
+
1351
+ **🛑 It refuses specs that built at `0.0.15`, including ones that round-tripped through this
1352
+ library's own parser** - `componentSeparator: "?"` with a scalar value did. That round trip is not
1353
+ the bar: ISA-11 and ISA-16 transmit the declared set, so a conformant receiver splits on it, and
1354
+ what this library could read back says nothing about what they read.
1355
+
1356
+ **🛑 A message moves, never a code, and no count of what moved is published.** The check runs where
1357
+ a builder resolves its delimiters, so every guard a builder runs earlier keeps precedence
1358
+ (`build835`'s balance equations, `build837`'s spine, `build999`'s AK9 counts and `buildTA1`'s
1359
+ `enforceAcceptIsClean` all still report first, measured base vs head) and everything later yields.
1360
+ `requireControlNumber` is later in **every** builder that has one, so on a degenerate set both the
1361
+ empty-control-number and the non-string-control-number refusals above are preempted at every one of
1362
+ their slots - same code, different message. A draft of this bullet said _"one report moved"_ and a
1363
+ refuter measured that false, so the ordering is stated and the sites are not counted.
1364
+
1365
+ **🛑 It is an equality test on the value you declare, not a guarantee about the documents this
1366
+ library can compose.** State the bound as a property of the SET, never of the document. Whether a
1367
+ declared value is even shaped like a delimiter is the separate refusal below.
1368
+
1369
+ **🛑 The READ side is deliberately untouched, and so is `serializeX12`.** `parseX12` still accepts
1370
+ every degenerate set and still frames a degenerate body segment; `serializeX12` re-emits one byte
1371
+ for byte, because it works from a model a SENDER's bytes produced. Documents this library emitted
1372
+ before this guard exist, and Postel's Law puts them on the lenient half.
1373
+
1374
+ - **🩺 A control number that is not a STRING is refused on emit, so `interchangeControlNumber: []`
1375
+ and `new String("")` no longer fabricate ISA-13 as `000000000`**
1376
+ (`X12-CONTROL-NUMBER-GUARD-NOT-TYPE-CHECKED`). Reproduced on the base tree at `a226595`. The
1377
+ empty-control-number guard above is byte-strict, and byte-strict means a value that is not a string
1378
+ is not `""`, so a non-string walked past it and reached `padControl`, which reads `.length` and
1379
+ then concatenates. Measured through `buildInterchange`:
1380
+
1381
+ ```text
1382
+ interchangeControlNumber: [] ISA-13 = "000000000" warnings: []
1383
+ interchangeControlNumber: new String("") ISA-13 = "000000000" warnings: []
1384
+ interchangeControlNumber: new String("ABC") ISA-13 = "000000ABC" warnings: []
1385
+ interchangeControlNumber: new String(" ") ISA-13 = "00000000 " warnings: []
1386
+ ```
1387
+
1388
+ The first two are the **same fabricated `000000000`** the empty guard closed, reached through a
1389
+ different input type. The other two are silent **coercions** of a boxed string, a value this
1390
+ library refuses by name wherever it escapes one. `requireControlNumber` refuses a non-string ahead
1391
+ of the empty test now, reporting the TYPE through `caller-string.ts`'s describer and never echoing
1392
+ the value.
1393
+ - **🛑 No error code is minted and no warning code moves, but DIAGNOSTICS DO MOVE, so "nothing
1394
+ else changed" would be false.** `undefined` and `null` threw a bare `TypeError` with no `code`
1395
+ and now throw the builder's typed refusal, so **a consumer catching `TypeError` there no longer
1396
+ catches**. Shapes that used to make the builder write a malformed fixed-width ISA its own
1397
+ re-parse rejected as `X12_INVALID_DELIMITERS` refuse before anything is written now, so **a
1398
+ predicate on that code stops firing for them**. And **the MESSAGE moved at the control-number
1399
+ slots that already refused a non-string**, because they refuse from this guard one step earlier:
1400
+ same class, same code, wording that names the slot and the spec property. Read that as the
1401
+ property and not as a claim about what the old message said. **If you match on message text at a
1402
+ control-number slot, re-read it.**
1403
+ - **It narrows what a control number may BE, never what it may CONTAIN.** A whitespace-only
1404
+ control number is still accepted, unchanged and by design: trimming would be a
1405
+ normalisation rule and no source consulted for this package states one. The asymmetry that
1406
+ creates is stated rather than smoothed over - `new String(" ")` is refused because it is not a
1407
+ string, and the primitive `" "` is not. A SHORT control number still zero-pads. The ISA's other
1408
+ fixed-width slots go through `pad` rather than `padControl`, are guarded by no control-number
1409
+ test, and a numeric one still throws a bare `TypeError`; disclosed rather than closed.
1410
+
1411
+ - **🩺 A BODY segment in an interchange whose ELEMENT SEPARATOR is `?` now frames its elements,
1412
+ where it used to come back as ONE element with an id of `(non-spec)`**
1413
+ (`X12-BODY-DEGENERATE-RELEASE-SEPARATOR`). Reproduced on the base tree at `72bafc2`.
1414
+ `detectDelimiters` reads the element separator positionally out of ISA byte 4 and rejects only
1415
+ control characters, whitespace and a non-distinct set, so a sender may declare `?` there, and
1416
+ `buildInterchange` accepts `elementSeparator: "?"` from a caller. `src/parser/envelope.ts` guarded
1417
+ that degenerate set in both of its own splitters, once for the segment terminator and once for an
1418
+ envelope segment's elements. `decodeSegment` - which every BODY segment plus the `ST`, the `SE`
1419
+ and every retained orphan goes through - did not. It used the release-aware splitter, where a `?`
1420
+ consumes the byte after it, so on such an interchange no split ever happened:
1421
+
1422
+ ```text
1423
+ ST?837?0001?005010X222A1 id "(non-spec)", 1 element
1424
+ NM1?85?2?ACME CLINIC?????XX?1234567893 id "(non-spec)", 1 element
1425
+ SE?3?0001 id "(non-spec)", 1 element
1426
+ warnings: []
1427
+ ```
1428
+
1429
+ **🩺 The envelope framed correctly the whole time, which is what made it silent.** One group, one
1430
+ transaction, `GE-01`, `IEA-01`, `GS-06`/`GE-02` and `ST-02`/`SE-02` all reconciling, an empty
1431
+ warning array - and a transaction body no reader could see, because every reader in this package
1432
+ dispatches on `seg.id`. A consumer got an empty claim list out of a well-formed document.
1433
+ `buildInterchange` disagreed with itself the same way: it returns `parseX12` of the bytes it just
1434
+ wrote, so a caller passing `elementSeparator: "?"` got back a model holding none of the segments
1435
+ it had supplied. **It now reports the segments it wrote, for a value with no `?` in it - read that
1436
+ qualifier, it is load-bearing and the paragraph on the emit side below says why.**
1437
+
1438
+ **🛑 It changes how an already-published document decodes, deliberately**, exactly as the envelope
1439
+ splitter did in the release before it, and on the same tiebreak: CONSISTENCY with the guard this
1440
+ package already carried twice, not a spec clause. 005010 does not transmit a release character at
1441
+ all, so nothing in it says what a `?` means once a sender has declared `?` as structure. **What is
1442
+ NOT the same: this class is not symmetric.** A one-element segment with an id of `(non-spec)` is
1443
+ not a second reading of `NM1?85?2?ACME CLINIC`, so unlike that slice there is no direction in
1444
+ which the old framing was the right one.
1445
+
1446
+ **No warning code is added and no case moves onto a new code.** One is SUBTRACTED, in one place:
1447
+ `X12_DANGLING_RELEASE_CHAR` fired on any degenerate segment ending in an empty last element,
1448
+ because the check keys on a trailing `?`. With `?` as the separator that trailing byte is an empty
1449
+ element and not an unpaired escape, so `PER?IC?NAME?TE?5551234?` is silent now.
1450
+
1451
+ **🛑 The guard is per ROLE, and it is a READ-side guard.** A `?` REPETITION or COMPONENT separator
1452
+ still does not split. `escapeRelease` writes `??` for a literal `?` whatever role `?` was declared
1453
+ in, so documents this library emitted through `0.0.15` carry `CLM*PATIENT??ACCT*150.00` and
1454
+ `getSegmentValue(clm, "01")` reads `"PATIENT?ACCT"` back out of them. A literal split of those two
1455
+ roles would re-frame that as two empty components, so it would stop reading a value this library
1456
+ itself wrote. **That reason survives the emit-side entry above: those documents exist.**
1457
+
1458
+ **🩺 What else it does not close, pinned rather than left to be rediscovered.** On a degenerate set
1459
+ a `?~` still swallows the segment terminator: `findUnescapedTerminator` guards its own role only,
1460
+ so a segment ending in an empty last element puts a `?` immediately before the terminator and
1461
+ merges with its successor (`PER?IC?NAME?TE?5551234?EX?~SE?3?0001~` frames as one segment and raises
1462
+ `X12_MISSING_SE`). Framing is untouched - but the READ of that merged blob did move, so do not take
1463
+ it as "nothing moved": it now frames, so `~SE` and the SE's control number land in `PER`'s
1464
+ communication-number slots where at base they sat inside one `(non-spec)` element. Values are still
1465
+ RAW, `elements.join(sep)` still reproduces the segment byte for byte, and the ISA stays positional.
1466
+
1467
+ - **🩺 `buildInterchange` now release-escapes GS-04, GS-05 and GS-07, so the interchange it hands
1468
+ back reports the group date, group time and responsible agency code you passed**
1469
+ (`X12-INTERCHANGE-GS-EMIT-NOT-RELEASE-AWARE`). Reproduced on the base tree at `837d4bc`. The
1470
+ builder mapped its escaper over GS-01, GS-02, GS-03, GS-06 and GS-08 and wrote `groupDate`,
1471
+ `groupTime` and `responsibleAgencyCode` raw, and it returns `parseX12` of the bytes it just wrote,
1472
+ so a value carrying an active delimiter in one of those three took a slot of its own and shifted
1473
+ every element after it down one - inside a single call. Measured on one group with
1474
+ `versionRelease: "005010X222A2"` and `groupControlNumber: "1"`:
1475
+
1476
+ | spec field | read GS-06 | read GS-08 | warnings |
1477
+ | ------------------------------ | ---------- | ---------- | ------------------------------------------------------- |
1478
+ | `groupDate: "2026*0601"` | `"1200"` | `"X"` | `X12_CONTROL_NUMBER_MISMATCH` |
1479
+ | `groupTime: "12*00"` | `"00"` | `"X"` | `X12_CONTROL_NUMBER_MISMATCH` |
1480
+ | `responsibleAgencyCode: "X*Y"` | `"1"` | `"Y"` | none |
1481
+ | `groupTime: "12~00"` | absent | absent | `X12_UNEXPECTED_SEGMENT`, `X12_CONTROL_NUMBER_MISMATCH` |
1482
+ | `groupDate: "20260601?"` | `"X"` | absent | `X12_CONTROL_NUMBER_MISMATCH` |
1483
+
1484
+ **🩺 The `responsibleAgencyCode` row is the one to know about, because nothing was raised on any
1485
+ channel.** GS-06 kept its own slot, so it still reconciled against GE-02; what moved was GS-08, the
1486
+ version / release / industry identifier code, which is the element the entry above lets a caller
1487
+ state. All five rows now read the values the caller passed, with an empty warning array.
1488
+
1489
+ **The grounding is inside this package rather than in a spec clause**, the same tiebreak the two
1490
+ entries below record: one function disagreed with itself. It returns `parseX12` of bytes it wrote
1491
+ and then answers a slot out of its neighbour's, and `SegmentSpec`'s documented contract already
1492
+ said the builder applies the release escape so an active delimiter inside a value survives.
1493
+
1494
+ **🛑 It changes bytes this library already put on the wire, and that is the cost.** A value
1495
+ containing none of the four delimiters and no `?` is emitted byte-for-byte as before, which is
1496
+ every conformant GS-04 / GS-05 / GS-07.
1497
+
1498
+ **🛑 No warning code is added and no case moves onto a new code. Read the property rather than a
1499
+ direction list:** the interchange the call returns now reports the values the caller passed, where
1500
+ before it reported whatever the shift left in each slot. **What is narrower here than in the two
1501
+ entries below, and is the part to carry away: no reader moved.** No executable line under
1502
+ `src/parser/` changed, so an inbound document from a trading partner decodes exactly as it did at
1503
+ `0.0.15`; what changed is what this library emits, and therefore how its own output reads back.
1504
+ **Say it that way rather than "the parser is untouched"** - this slice's own graded review forced a
1505
+ JSDoc correction in `src/parser/envelope.ts`, where a stale census of the released GS/ST slots had
1506
+ been published.
1507
+
1508
+ **State the delimiter set by ROLE, never by byte.** `InterchangeSpec` lets you declare all four, so
1509
+ which BYTES shift is a property of the set you declared: with `elementSeparator: "|"` a GS-07 of
1510
+ `"X|Y"` is what took GS-08's slot and `"X*Y"` was inert. **Only the ELEMENT SEPARATOR and the
1511
+ SEGMENT TERMINATOR ever shifted the segment's own framing, plus a `?` immediately before the
1512
+ element separator.** The **repetition** and **component** separators moved the dot-path reader
1513
+ instead, and releasing them is a **gain** there: the composite read `"07-1"`
1514
+ answered `"X"` for `"X:Y"`. **The measured cost is a mid-string `?`, and only on the surfaces
1515
+ documented as raw** - `gs.elements[4]` reads `"2026??0601"` where it read `"2026?0601"`, while the
1516
+ dot-path read of that value unescapes and is unchanged. No total is published: that is what was
1517
+ measured, not a closed account. **A caller who was pre-releasing these values themselves is now
1518
+ escaping twice** and should drop the hand-rolled escape.
1519
+
1520
+ **A wrong-typed GS element still names its slot.** The type check runs over the unescaped parts, so
1521
+ a numeric `groupDate` refuses with `buildInterchange: "GS"-04 must be a string` rather than
1522
+ degrading to the builder-named message the escaper alone would produce; the five slots that already
1523
+ escaped gained the slot name with it. Same class, same `X12_BUILD_INVALID_SPEC`, and still no echo
1524
+ of the value. **`null` and `undefined` in these three fields are ABSENT, not refused** - each
1525
+ resolves through a default before either guard sees it.
1526
+
1527
+ **A LITERAL segment id this library writes is never escaped.** `esc` releases against the
1528
+ delimiter set the CALLER declared, and a `componentSeparator` of `"S"` is admissible, so mapping
1529
+ the escaper over element 0 would turn the literal `"GS"` into `G?S` and the group header would stop
1530
+ being a `GS`. `GE` / `ST` / `SE` / `IEA` already followed that rule. **Read "literal" strictly:** a
1531
+ `SegmentSpec` body segment carries a CALLER-supplied id, `buildTransaction` has released it since
1532
+ before this slice, and `SegmentSpec`'s own JSDoc still says it is emitted verbatim. That
1533
+ disagreement predates this slice, is unchanged by it, and is filed rather than closed here.
1534
+
1535
+ **What this does not close:** `buildInterchange`'s IEA-02 is padded rather than escaped and has to
1536
+ stay byte-equal to the fixed-width ISA-13 it reconciles against, so that is a decision of its own;
1537
+ the ISA fixed-width slots remain outside both guards; and an unescaped active delimiter is still
1538
+ not safe anywhere, because that is what a delimiter is.
1539
+
953
1540
  - **🩺 `buildTA1` now release-escapes its five caller-supplied elements, so an Accept acknowledgment
954
1541
  this library emits no longer reads back as a Reject** (`X12-TA1-EMIT-NOT-RELEASE-AWARE`).
955
1542
  Reproduced on the base tree at `e8f34b9`. `buildTA1` joined the five values with the element
@@ -999,19 +1586,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
999
1586
  `getSegmentValue(ta1, "01")` answered `"0000"` at `0.0.14` for a control number of `"0000^0001"`,
1000
1587
  silently truncating the reassociation key to the first repetition, and answers `"0000^0001"` now;
1001
1588
  the composite read `"01-1"` answered `"0000"` for `"0000:0001"` and answers the whole value now.
1002
- **The measured pure cost is a mid-string `?`, and only on the surfaces documented as raw**: `raw`,
1003
- `elements` and `parseTA1`'s fields read `"0000??0001"` where they read `"0000?0001"`, while every
1589
+ **The measured pure cost is a mid-string `?`, and only on the surfaces documented as raw**: `raw`
1590
+ and `elements` read `"0000??0001"` where they read `"0000?0001"`, while every
1004
1591
  dot-path read unescapes and answered `"0000?0001"` on both. No total is published: that is what was
1005
1592
  measured, not a closed account. `getSegmentValue` takes an `X12Segment` and `Ta1Segment` carries no
1006
1593
  `id`, so add one to read a TA1 through it. If you were escaping the value yourself, as `KNOWN-LIMITATIONS.md`
1007
1594
  advised while this was open, drop that - you are now escaping twice on both kinds of surface
1008
1595
  (`"00000001??"` in, `TA1*00000001????*…` out, `getSegmentValue` answering `"00000001??"`).
1009
1596
 
1010
- **The read half did not move.** `parseTA1` still reads elements RAW, pre-`?`-unescape, exactly as
1011
- `X12Segment.elements` has always documented, so a control number of `"00000001?"` now reads back as
1012
- `"00000001??"` rather than as `"00000001?*260601"`. Apply `unescapeRelease` if you need the value
1013
- rather than the bytes; unescaping on the read side would move every TA1 a consumer already reads
1014
- and is not done here.
1597
+ **The read half did not move in THIS entry**, so a control number of `"00000001?"` read back as
1598
+ `"00000001??"` rather than as `"00000001?*260601"`. The `X12-TA1-RESIDUALS` entry above is the one
1599
+ that moved it, and the clause that stood here telling you to apply `unescapeRelease` yourself is
1600
+ **deleted** rather than reworded: the library does it now.
1015
1601
 
1016
1602
  **`BuildTA1Options` gained `repetitionSeparator`, `componentSeparator` and `segmentTerminator`**
1017
1603
  beside the existing `elementSeparator`, the same four `build999` already takes. They exist for