@cosyte/x12 0.0.14 → 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 +800 -5
- package/KNOWN-LIMITATIONS.md +553 -19
- package/README.md +1 -1
- package/dist/index.cjs +397 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +233 -52
- package/dist/index.d.ts +233 -52
- package/dist/index.mjs +397 -44
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/KNOWN-LIMITATIONS.md
CHANGED
|
@@ -16,6 +16,162 @@ model.
|
|
|
16
16
|
|
|
17
17
|
## Data / decode boundaries
|
|
18
18
|
|
|
19
|
+
- **🩺 `parseTA1`'s five decoded fields are POST-`?`-unescape as of this release, and an EMPTY
|
|
20
|
+
TA1-02 / TA1-03 / TA1-04 / TA1-05 is REFUSED on emit. Both are behaviour changes**
|
|
21
|
+
(`X12-TA1-RESIDUALS`). They are one slice because they are the two ends of the same disagreement:
|
|
22
|
+
this package's emit half releases a TA1 element and its read half decoded the escape rather than
|
|
23
|
+
the value.
|
|
24
|
+
|
|
25
|
+
**The read half.** `X12-TA1-EMIT-NOT-RELEASE-AWARE` made `buildTA1` release all five caller
|
|
26
|
+
elements, and `parseTA1` kept reading `elements` verbatim, so a round trip through this package's
|
|
27
|
+
own halves was not an identity for any value carrying a delimiter or the release character.
|
|
28
|
+
Measured at `0.0.15`, over `parseX12` + `parseTA1` of what `buildTA1` had just emitted:
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
interchangeControlNumber raw emitted parseTA1 read dot-path read
|
|
32
|
+
"00000001?" TA1*00000001??*260601*1200*A*000 "00000001??" "00000001?"
|
|
33
|
+
"0000*0001" TA1*0000?*0001*260601*1200*A*000 "0000?*0001" "0000*0001"
|
|
34
|
+
"0000~0001" TA1*0000?~0001*260601*1200*A*000 "0000?~0001" "0000~0001"
|
|
35
|
+
"0000:0001" TA1*0000?:0001*260601*1200*A*000 "0000?:0001" "0000:0001"
|
|
36
|
+
"0000^0001" TA1*0000?^0001*260601*1200*A*000 "0000?^0001" "0000^0001"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Every row `warnings: []`. TA1-01 is the reassociation key, so the left column is a key that
|
|
40
|
+
matches no ISA-13. The right column is the same element. The grounding is that
|
|
41
|
+
disagreement and nothing else - no clause anyone here has read settles what a TA1 element may
|
|
42
|
+
contain.
|
|
43
|
+
|
|
44
|
+
**What that costs, stated rather than argued away.** A consumer who was applying `unescapeRelease`
|
|
45
|
+
to `parseTA1`'s output themselves - the remedy this file used to prescribe - now has the library
|
|
46
|
+
doing it, so **drop the hand-rolled call**. And a value with no `?` in it is unchanged on both
|
|
47
|
+
surfaces, which is every conformant TA1. **`raw` is untouched and is still the verbatim byte
|
|
48
|
+
surface**: read `ta1.raw.elements` when you want the bytes.
|
|
49
|
+
|
|
50
|
+
**The emit half.** `escapeRelease` early-returns on `""` and `buildTA1` carried a required-field
|
|
51
|
+
guard for TA1-01 alone, so the other four slots emitted an absent element with `warnings: []`:
|
|
52
|
+
`interchangeDate: ""` gave `TA1*000000001**1200*A*000`, `interchangeTime: ""` gave
|
|
53
|
+
`TA1*000000001*260601**A*000`, `ackCode: ""` gave `TA1*000000001*260601*1200**000` and
|
|
54
|
+
`noteCode: ""` gave `TA1*000000001*260601*1200*R*`. **The item filed two of those four.** Each now
|
|
55
|
+
draws `AckBuildError` / `X12_ACK_INVALID_SPEC` naming the slot and the spec property and never the
|
|
56
|
+
value. **No new error code was minted and no warning code moved.** The grounding is again inside
|
|
57
|
+
the package: `BuildTA1Spec` declares all five properties as required `string`s, `""` is what
|
|
58
|
+
defeats that at run time, and the in-package answer to an empty required element is uniformly
|
|
59
|
+
refusal (`patientControlNumber`, `claimId`, `maintenanceTypeCode`, `requestCategoryCode`, the
|
|
60
|
+
277's `categoryCode`, and TA1-01 itself).
|
|
61
|
+
- **🛑 It does NOT trim, at any of the five slots.** A whitespace-only element still builds -
|
|
62
|
+
`TA1*000000001*260601*1200*R* ` is still emitted for `noteCode: " "`. Trimming is a
|
|
63
|
+
normalisation rule and no source consulted for this package states one, exactly as at TA1-01.
|
|
64
|
+
- **🛑 It does not narrow what a NON-empty element may contain, and the read half stays lenient.**
|
|
65
|
+
An out-of-enum `ackCode: "X"` still builds and still reads back as `R` through the documented
|
|
66
|
+
fail-safe narrow - the same answer an empty one gave. This guard bounds ABSENCE on the emit side
|
|
67
|
+
and nothing else.
|
|
68
|
+
- **🛑 Every guard that stood before it keeps its precedence.** `enforceAcceptIsClean` still runs
|
|
69
|
+
first, so an `ackCode: "A"` with an empty note still reports `X12_TA1_ACCEPT_WITH_NOTE`; TA1-01
|
|
70
|
+
still draws the control-number refusal; and every wrong-TYPED element still draws the escape
|
|
71
|
+
helper's refusal, because all five escapes run before any emptiness test. **No spec that was
|
|
72
|
+
refused before this slice is refused differently by it.**
|
|
73
|
+
- **No census of other builders' required elements is published here.** This slice measured TA1.
|
|
74
|
+
|
|
75
|
+
- **🩺 `implementationConventionReference` is POST-`?`-unescape as of this release, in every typed
|
|
76
|
+
reader that publishes it, and that is a behaviour change on documents whose `ST-03` carries a
|
|
77
|
+
release escape** (`X12-ST03-READ-NOT-RELEASE-AWARE`). `tx.st.elements` is the ST segment as framed:
|
|
78
|
+
post-element-split and PRE-unescape. Five public
|
|
79
|
+
readers were handing one of those strings straight back on the model - `get837Claims`,
|
|
80
|
+
`get277Status`, `get277CADisposition`, `get278Request` and `get278Response` - so a sender that
|
|
81
|
+
escaped a delimiter inside `ST-03` got the escape rather than the value it stated. `parse999` has
|
|
82
|
+
always decoded the identically-named `AK2-03`, and every dot-path read already unescaped; that
|
|
83
|
+
disagreement inside this package is the whole grounding, and no TR3 clause is claimed. The cells
|
|
84
|
+
that were run, on the ST-03 element text a sender framed: `A??B` now publishes `A?B`, `A?*B`
|
|
85
|
+
publishes `A*B`, `A?:B` publishes `A:B`, `A?~B` publishes `A~B`, `A?^B` publishes `A^B`. Every one
|
|
86
|
+
of those published the framed bytes before.
|
|
87
|
+
- **🛑 What decides an outcome did NOT move, deliberately.** The 837 variant lookup, the 277 /
|
|
88
|
+
277CA `transactionType` discriminator and `get277CADisposition`'s admission gate all still key on
|
|
89
|
+
the RAW element text, so no document changes variant, discriminator or admission because of this
|
|
90
|
+
entry. That matters because the two can differ: with `componentSeparator: "X"` - a letter is an
|
|
91
|
+
admissible delimiter - an `ST-03` framed as `005010?X222A1` decodes to `005010X222A1`, an
|
|
92
|
+
identifier the variant table holds. Keying on the decoded text would make the declaration beat
|
|
93
|
+
the `SVx` fallback there, which on a document whose only service segment is an `SV2` stops that
|
|
94
|
+
line decoding and raises `X12_837_SERVICE_LINE_NOT_DECODED`. That is a change to how an already
|
|
95
|
+
published document decodes a service line rather than a decode fix, and it is **open**, not
|
|
96
|
+
taken here. **The difference is one-way: no document that resolved or was admitted before stops
|
|
97
|
+
doing so**, because no identifier any of the three tests is keyed on contains a delimiter or the
|
|
98
|
+
release character, so raw text equal to one decodes to itself.
|
|
99
|
+
- **🛑 The published reference can name a guide this reader did NOT resolve to, and nothing warns
|
|
100
|
+
about the divergence.** On the `componentSeparator: "X"` document above,
|
|
101
|
+
`submission.implementationConventionReference` reads `005010X222A1`, which the variant table
|
|
102
|
+
holds, while `submission.variant` is `I` from the `SVx` fallback. **"Nothing warns" is NOT the
|
|
103
|
+
boundary of it:** on the same delimiters a body with no `SVx` publishes `005010X222A1` with
|
|
104
|
+
`variant: "unknown"` and raises `X12_837_UNKNOWN_VARIANT`, and a body naming more than one
|
|
105
|
+
variant publishes it with `variant: "P"` and raises `X12_837_AMBIGUOUS_VARIANT` - each time the
|
|
106
|
+
code that fired says `ST-03` named no identifier this reader recognises while the model field
|
|
107
|
+
holds one it does. **`X12_837_UNKNOWN_VARIANT`'s closing pointer at the model is therefore
|
|
108
|
+
deleted.** **Through `0.0.15` the published value WAS the keyed value, so the model could not
|
|
109
|
+
disagree with itself. Gate on `variant` / `transactionType`, never on the published reference.**
|
|
110
|
+
- **🛑 It introduces no normalisation and no new warning.** Nothing is trimmed, case-folded or
|
|
111
|
+
prefix-matched; a whitespace-only `ST-03` is still published untrimmed. A dangling `?` at the end
|
|
112
|
+
of the element still raises no `X12_DANGLING_RELEASE_CHAR` on these readers: the sink is a no-op,
|
|
113
|
+
which is what `getSegmentValue` defaults to and what `parseTA1` and `parse999` do, so every other
|
|
114
|
+
element these readers decode drops it too. That residual is **unchanged and open.**
|
|
115
|
+
- **Each reader's own empty / absent mapping is unchanged.** `walk278` still collapses `""` to
|
|
116
|
+
`undefined`; `get837Claims` and `walk277` still publish `""`. Decoding cannot reach those
|
|
117
|
+
branches differently, because no non-empty element decodes to `""`.
|
|
118
|
+
- **`tx.st.elements` is untouched and is still the verbatim framed surface.** If you were applying
|
|
119
|
+
`unescapeRelease` to `submission.implementationConventionReference` yourself, drop that call.
|
|
120
|
+
|
|
121
|
+
- **🩺 An EMPTY control number is REFUSED on emit as of this release, where it used to be
|
|
122
|
+
FABRICATED, and that is a behaviour change for any caller passing one**
|
|
123
|
+
(`X12-EMPTY-CONTROL-NUMBER-FABRICATED`). Every builder that assembles an ISA zero-pads its control
|
|
124
|
+
number to the nine characters ASC X12 .5 fixes ISA-13 at. `padControl("1", 9)` answering
|
|
125
|
+
`"000000001"` is the point of that; `padControl("", 9)` answering `"000000000"` was not, and
|
|
126
|
+
nothing stood in front of it, so `interchangeControlNumber: ""` produced a frozen, well-formed
|
|
127
|
+
interchange carrying a nine-digit control number nobody supplied, with `warnings: []` and ISA-13
|
|
128
|
+
reconciling perfectly against IEA-02. A control number is how an interchange is reconciled and
|
|
129
|
+
acknowledged, so a fabricated one does not fail: it succeeds against the wrong thing.
|
|
130
|
+
|
|
131
|
+
The other control numbers took the same input and were silent in a different way. They reach the
|
|
132
|
+
wire through the escape helper, which early-returns on `""`, so the required element was lost.
|
|
133
|
+
**The bytes are not the same in every builder**: `buildInterchange` and `build999` join without
|
|
134
|
+
trimming, so the element goes out empty, while the seven domain builders share a segment helper
|
|
135
|
+
that drops a trailing empty element, so the trailer loses it outright. Measured at `0.0.15`,
|
|
136
|
+
through `buildInterchange` and `build834`:
|
|
137
|
+
|
|
138
|
+
```text
|
|
139
|
+
buildInterchange interchangeControlNumber: "" ISA*…*00501*000000000*0*P*:~ … ~IEA*1*000000000~
|
|
140
|
+
buildInterchange groupControlNumber: "" GS*HC*…*1200**X*005010X222A2~ … ~GE*1*~
|
|
141
|
+
buildInterchange transactionSetControlNumber: ST*837**005010X222A2~ … ~SE*3*~
|
|
142
|
+
build834 groupControlNumber: "" GS*BE*…*1200**X*005010X220A1~ … ~GE*1~
|
|
143
|
+
build834 transactionSetControlNumber: ST*834**005010X220A1~ … ~SE*21~
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Every one of those emitted `warnings: []`, which is the property that holds across both families:
|
|
147
|
+
no diagnostic on any channel separated an absent control number from a supplied one. The acknowledgment builders carried the same class at the slots
|
|
148
|
+
where they **echo** the document being acknowledged, which is the whole reason a sender can match
|
|
149
|
+
an ack to what they sent: `build999` emitted `AK1*HC**005010X222A2~` and `AK2*837*~`, and
|
|
150
|
+
`buildTA1` emitted `TA1**260601*1200*A*000`. Every one of these now draws that builder's own typed,
|
|
151
|
+
code-tagged refusal (`X12_BUILD_INVALID_SPEC` and its siblings; `X12_ACK_INVALID_SPEC` on the ack
|
|
152
|
+
path) naming the slot and the spec property, before anything is emitted. **No new error code was
|
|
153
|
+
minted and no warning code moved.** What changes is that a build that used to return a document now
|
|
154
|
+
throws, and that a spec wrong in two ways can now report a different one of the two: see the
|
|
155
|
+
precedence bullet below, which is the qualifier a draft of this entry left out.
|
|
156
|
+
- **🛑 The guard does NOT trim, and a whitespace-only control number is
|
|
157
|
+
still accepted**: `interchangeControlNumber: " "` still emits ISA-13 as `00000000 `, and
|
|
158
|
+
`buildTA1` does no padding at all, so it emits whatever whitespace it was handed, verbatim. This
|
|
159
|
+
is a real residual rather than an oversight. Trimming
|
|
160
|
+
would be a normalisation rule, no source consulted for this package states one, and every
|
|
161
|
+
empty-required-element guard this one mirrors (`patientControlNumber`, `claimId`,
|
|
162
|
+
`maintenanceTypeCode`, `requestCategoryCode`, the 277's `categoryCode`) is byte-strict for the
|
|
163
|
+
same reason. **Validate at your own boundary if your partner can send you blanks.**
|
|
164
|
+
- **A SHORT control number still zero-pads.** The guard is not "ISA-13 must be nine characters":
|
|
165
|
+
`interchangeControlNumber: "1"` still emits `000000001`, which is what `padControl` is for.
|
|
166
|
+
- **🛑 Every guard sits at the envelope-assembly site, so every guard that runs BEFORE it keeps its
|
|
167
|
+
precedence, and that is the whole claim.** `build835`'s balance equation, `build999`'s AK9
|
|
168
|
+
counts, `buildTA1`'s accept-must-mean-accept check, the 837's and 277's hierarchy checks and
|
|
169
|
+
`build834`'s unknown-maintenance-type refusal all still fire ahead of this one. **What it does
|
|
170
|
+
NOT preserve is an ordering against a defect detected LATER**, during body assembly, which now
|
|
171
|
+
reports the control-number refusal instead: `build999` with an empty `interchangeControlNumber`
|
|
172
|
+
and six AK9 syntax error codes threw `X12_ACK_COUNT_MISMATCH` at `0.0.15` and throws
|
|
173
|
+
`X12_ACK_INVALID_SPEC` now. If you branch on a specific builder error code, that pairing moved.
|
|
174
|
+
|
|
19
175
|
- **🩺 Which `ST-03` implementation-convention references resolve to an 837 variant CHANGED in this
|
|
20
176
|
release, and some already-published files therefore decode differently** (`X12-VARIANT-ICR-UNGROUNDED`).
|
|
21
177
|
Through `0.0.13` `get837Claims` recognised exactly three references: `005010X222A2`, `005010X223A3`
|
|
@@ -53,14 +209,392 @@ model.
|
|
|
53
209
|
- **The set is not claimed exhaustive and no count of it is published**, here or in any warning
|
|
54
210
|
message. Both variant messages named the three old keys literally and were wrong the moment the
|
|
55
211
|
table was corrected, so neither enumerates the set any more.
|
|
56
|
-
- **🩺
|
|
57
|
-
`
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
212
|
+
- **🩺 CLOSED, in the slice after this one: the emit side takes a caller override**
|
|
213
|
+
(`X12-837-EMIT-IDENTIFIER-FIXED`). `build837P` / `build837I` / `build837D` still DEFAULT to
|
|
214
|
+
`005010X222A2` / `005010X223A3` / `005010X224A2` in ST-03 and GS-08, and
|
|
215
|
+
**`Build837EnvelopeSpec.implementationConventionReference` now states another** - one value,
|
|
216
|
+
both elements. Two of the defaults are not what the companion guides above require, so a partner
|
|
217
|
+
asking for `005010X222A1` or `005010X223A2` used to reject what this builder emitted and there
|
|
218
|
+
was no way to comply. **The default itself was deliberately NOT re-stamped:** which published
|
|
219
|
+
guide identifier a partner accepts is a partner fact rather than a spec fact, and changing bytes
|
|
220
|
+
this library already emitted would break the partners it works with today.
|
|
221
|
+
- **What the override refuses of its own, all `X12_837_BUILD_INVALID_SPEC`, and none of it
|
|
222
|
+
echoes your value back:** an empty reference (a trailing empty element is not emitted, so it
|
|
223
|
+
would delete ST-03 and GS-08 rather than send them empty); one carrying an active delimiter or
|
|
224
|
+
the release character; and one this library's own reader resolves to a **different** 837
|
|
225
|
+
variant, which would emit a file declaring one variant and carrying another's service
|
|
226
|
+
segments. **No total is published**, because those sit on top of the element-type guard every
|
|
227
|
+
string slot in every builder already has, which refuses a non-string with the same code. Take
|
|
228
|
+
the list as what this field adds, never as a closed account of everything that can refuse.
|
|
229
|
+
- **🩺 It bounds the VALUE, not the length, and the two elements' maxima differ.** GS-08 is data
|
|
230
|
+
element 480 (`AN 1/12`) and ST-03 is element 1705 (`AN 1/35`), so "one value, both elements"
|
|
231
|
+
is true of the bytes and not of what each element may legally hold. Nothing here refuses an
|
|
232
|
+
over-length reference, in line with every other envelope field this library takes.
|
|
233
|
+
- **A reference outside the read table is emitted as given, deliberately.** The published-errata
|
|
234
|
+
set is not provably exhaustive, so refusing an unrecognised identifier would claim an
|
|
235
|
+
exhaustiveness nothing here supports. The honest cost, pinned as a test: this library's own
|
|
236
|
+
reader falls back to the `SVx` scan on such a file, exactly as it does for any unrecognised
|
|
237
|
+
ST-03.
|
|
238
|
+
|
|
239
|
+
- **🩺 A `?` immediately before the element separator inside an envelope segment now frames as ONE
|
|
240
|
+
element, and that is a SYMMETRIC behaviour change on already-published decoding**
|
|
241
|
+
(`X12-ENVELOPE-SPLITTER-NOT-RELEASE-AWARE`).
|
|
242
|
+
Through `0.0.14` the envelope segments' element splitter was a plain `String.prototype.split`, so a
|
|
243
|
+
released element separator (`?*`) still ended the element and SHIFTED every element after it down a
|
|
244
|
+
slot. It was a property of the whole envelope segment and never of one element, so the element a
|
|
245
|
+
reader went looking for came back out of its neighbour's slot. Measured then, through `parseX12`:
|
|
246
|
+
|
|
247
|
+
```text
|
|
248
|
+
GS*HC*S*R*20260601*1200*1*X*005010?*X222A1~ ten elements, GS-08 read "005010?"
|
|
249
|
+
applicationSenderCode "SEND*ER" GS-08 read "X", the GS-07 agency code
|
|
250
|
+
groupControlNumber "1*2" GS-08 read "X"; warnings: []
|
|
251
|
+
transactionSetControlNumber "00*01" ST-03 read "01"; warnings: []
|
|
252
|
+
CLM*PT?*ACCT*150.00~ three elements, CLM-01 held "PT?*ACCT"
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
The body element was the control and was always correct. `splitElements` now honours the release
|
|
256
|
+
character exactly as `decodeSegment` and the segment-terminator scanner already did, so every row
|
|
257
|
+
above reads one element fewer and the value lands in its own slot.
|
|
258
|
+
- **🛑 READ THE CHANGE AS SYMMETRIC. IT IS NOT ONLY A CORRECTION.** A `?` before the separator has
|
|
259
|
+
two readings and 005010 does not transmit which the sender meant. Where the sender **escaped** a
|
|
260
|
+
delimiter, the release before this one framed it wrongly and this one frames it correctly: a
|
|
261
|
+
correction, the rows above. Where the sender sent a **literal `?`** as the element's last byte,
|
|
262
|
+
the release before this one framed it correctly and this one merges the element with its
|
|
263
|
+
successor, so **the segment loses its LAST element**: a regression.
|
|
264
|
+
`GS*HC*SUB1*RCV?*20260601*1200*000000123*X*005010X222A1~` read nine entries through `0.0.14` and
|
|
265
|
+
reads **eight** here, with GS-06 answering `"X"` and GS-08 gone.
|
|
266
|
+
- **🛑 What this changes for a consumer.** One input class decodes differently: an envelope segment
|
|
267
|
+
(`GS`, `GE`, `ST`, `SE`, `IEA`, `TA1`) carrying a `?` immediately before the element separator.
|
|
268
|
+
Every other release sequence in an envelope element (`??`, `?:`, `?^`, `?~`, `?A`) framed
|
|
269
|
+
identically before and after and still does. **No warning code is added, and
|
|
270
|
+
`X12_CONTROL_NUMBER_MISMATCH` moves in BOTH DIRECTIONS.** Where the old shift displaced a control
|
|
271
|
+
number it STOPS firing, so a consumer that rejects on that code will now **accept** such a
|
|
272
|
+
document; where a literal `?` newly displaces one it STARTS firing, so that same consumer will
|
|
273
|
+
now **reject** a document `0.0.14` accepted. A genuine mismatch still raises it.
|
|
274
|
+
- **🩺 The regression direction reaches an 837's variant and its money, by one route.** `ST-03` is
|
|
275
|
+
what decides the 837 variant, so an `ST-02` ending in a literal `?` destroys `ST-03` and the
|
|
276
|
+
document falls back to the `SVx` scan. An 837 declaring `005010X222A1` whose only service segment
|
|
277
|
+
is an `SV2` read `variant` `"P"`, `charge` `undefined` and `X12_837_SERVICE_LINE_NOT_DECODED`
|
|
278
|
+
through `0.0.14`, and here reads `variant` `"I"` with `charge` `150.00` and that warning silent.
|
|
279
|
+
**A warned non-decode becomes a decoded amount.** If you receive envelope elements that may end
|
|
280
|
+
in a literal `?`, re-check any routing driven off `submission.variant`.
|
|
281
|
+
- **Why it was taken anyway, and it is CONSISTENCY rather than a spec clause.** The two readings
|
|
282
|
+
are mutually exclusive and nothing in 005010 picks between them. `decodeSegment` has read BODY
|
|
283
|
+
elements the escape-wins way on **every released version** (`REF*EA*RCV?*NEXT` has always been
|
|
284
|
+
two elements). The envelope now obeys the one rule the rest of the package already obeyed, and
|
|
285
|
+
`buildInterchange` stops disagreeing with itself: at `0.0.14` it released GS-02 on emit and then
|
|
286
|
+
answered GS-08 as `"X"` from its own return value.
|
|
287
|
+
- **🩺 THE EXPOSURE IS NOT INBOUND BYTES ONLY.** Envelope slots routed through the builders'
|
|
288
|
+
release escaper are safe. **Not every emit slot is routed through it, and no total is published
|
|
289
|
+
here** - what follows is the routes measured to REACH the regression direction, not a closed
|
|
290
|
+
account of what bypasses the escaper.
|
|
291
|
+
|
|
292
|
+
**`buildInterchange` did not escape GS-04, GS-05 or GS-07** (`groupDate`, `groupTime`,
|
|
293
|
+
`responsibleAgencyCode`) at this release. A `groupDate` of `"2026060?"` emitted
|
|
294
|
+
`GS*HC*SENDER*RECEIVER*2026060?*1200*1*X*005010X222A1`, which its own return value read as nine
|
|
295
|
+
elements with GS-08 intact through `0.0.14` and read as **eight** at `0.0.15`, GS-08 gone, plus
|
|
296
|
+
`X12_CONTROL_NUMBER_MISMATCH`. **Closed by `X12-INTERCHANGE-GS-EMIT-NOT-RELEASE-AWARE`** - that
|
|
297
|
+
entry is at the top of this section and it is the one that describes the current tree.
|
|
298
|
+
|
|
299
|
+
- **An envelope element ending in a literal `?` is a dangling release character and is NOT warned.**
|
|
300
|
+
`X12_DANGLING_RELEASE_CHAR` fires only for an odd run of `?` at the very END of a segment, so a
|
|
301
|
+
mid-segment one reaches no check. That is true of body elements too and is unchanged here.
|
|
302
|
+
- **Values are still RAW, pre-`?`-unescape**, exactly as `X12Segment.elements` has always
|
|
303
|
+
documented: `gs.elements[2]` on the first row reads `"SEND?*ER"`, not `"SEND*ER"`. For the
|
|
304
|
+
logical value, `unescapeRelease` the element string. `GsSegment` carries no `id`, so
|
|
305
|
+
`getSegmentValue` does not accept a `gs` (`TS2345`); adding an `id` and taking the dot-path is
|
|
306
|
+
the other route, and **the two answer different questions**: a bare dot-path splits on the
|
|
307
|
+
repetition separator and answers repetition 0, so a GS-07 of `"A^B"` reads back `"A"` through it
|
|
308
|
+
and `"A^B"` through `unescapeRelease`, `warnings: []`.
|
|
309
|
+
`elements.join(separator)` therefore still
|
|
310
|
+
reproduces the segment byte for byte, which is what `serializeX12` relies on when it substitutes
|
|
311
|
+
a recomputed `SE-01` / `GE-01` / `IEA-01` into a control segment.
|
|
312
|
+
- **🩺 The ISA is deliberately exempt and stays positional.** ASC X12 .5 makes the ISA fixed-width,
|
|
313
|
+
which is what lets the delimiters be recovered from it before anything is parsed, so a `?` in an
|
|
314
|
+
ISA element is content and never an escape. `buildInterchange` states the same rule from the emit
|
|
315
|
+
side: it pads each ISA element and never escapes one.
|
|
316
|
+
- **A degenerate delimiter set whose element separator IS `?` is unchanged by THIS entry.** `?`
|
|
317
|
+
cannot both separate and escape, so `splitElements` falls back to the literal split, the same
|
|
318
|
+
guard the segment-terminator scanner already carried. **Read that as the ENVELOPE splitter only:
|
|
319
|
+
this sentence used to say "the splitter", and `decodeSegment` did NOT carry the guard**, so a
|
|
320
|
+
degenerate interchange framed its envelope correctly and collapsed every BODY segment. The entry
|
|
321
|
+
below closes that half.
|
|
322
|
+
- **🩺 This does NOT make an UNESCAPED delimiter safe, and nothing here claims it does.** A bare
|
|
323
|
+
active delimiter in an envelope element still ends that element, because that is what a delimiter
|
|
324
|
+
is; only the sender escaping it is now honoured. `build837`'s
|
|
325
|
+
`implementationConventionReference` still REFUSES a value carrying an active delimiter or the
|
|
326
|
+
release character, and is deliberately not relaxed on the strength of this fix: a partner's
|
|
327
|
+
parser is not obliged to be release-aware either. `buildInterchange` applies no domain guard to
|
|
328
|
+
any envelope field and will still emit all of them, though it now reports back what it wrote -
|
|
329
|
+
at `0.0.14` it released GS-02 on emit and then answered GS-08 as `"X"` from its own return value.
|
|
330
|
+
|
|
331
|
+
- **🩺 A BODY segment in an interchange whose ELEMENT SEPARATOR is `?` now frames its elements,
|
|
332
|
+
where it used to come back as ONE element with an id of `(non-spec)`**
|
|
333
|
+
(`X12-BODY-DEGENERATE-RELEASE-SEPARATOR`). `detectDelimiters` reads the element separator
|
|
334
|
+
positionally out of ISA byte 4 and rejects only control characters, whitespace and a non-distinct
|
|
335
|
+
set, so a sender may declare `?` there, and `buildInterchange` accepted `elementSeparator: "?"`
|
|
336
|
+
from a caller when this was measured (it refuses now - see the emit-side entry below).
|
|
337
|
+
`src/parser/envelope.ts` guarded that degenerate set in both of its own splitters;
|
|
338
|
+
`decodeSegment`, which every BODY segment plus the `ST`, the `SE` and every retained orphan goes
|
|
339
|
+
through, did not. It split with the release-aware splitter, where a `?` consumes the byte after it,
|
|
340
|
+
so no split ever happened. Measured through `parseX12` at `0.0.15`:
|
|
341
|
+
|
|
342
|
+
```text
|
|
343
|
+
ST?837?0001?005010X222A1 id "(non-spec)", 1 element
|
|
344
|
+
NM1?85?2?ACME CLINIC?????XX?1234567893 id "(non-spec)", 1 element
|
|
345
|
+
SE?3?0001 id "(non-spec)", 1 element
|
|
346
|
+
warnings: []
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
- **🩺 The envelope framed correctly the whole time, which is what made it silent.** One group,
|
|
350
|
+
one transaction, `GE-01`, `IEA-01`, `GS-06`/`GE-02` and `ST-02`/`SE-02` all reconciling, an empty
|
|
351
|
+
warning array - and a transaction body no reader could see, because every reader in this package
|
|
352
|
+
dispatches on `seg.id`. A consumer got an empty claim list from a well-formed document.
|
|
353
|
+
- **🛑 It changes how an already-published document decodes, deliberately, and the tiebreak is
|
|
354
|
+
CONSISTENCY rather than a spec clause** - the same call the envelope-splitter entry above made.
|
|
355
|
+
005010 does not transmit a release character at all, so nothing in it says what a `?` means once
|
|
356
|
+
a sender has declared `?` as structure. **What is NOT the same as that entry: the class is not
|
|
357
|
+
symmetric.** A one-element segment with an id of `(non-spec)` is not a second reading of
|
|
358
|
+
`NM1?85?2?ACME CLINIC`; there is no direction in which the old framing was the right one.
|
|
359
|
+
- **No warning code is added and nothing moves onto a new code.** One is SUBTRACTED, in one place:
|
|
360
|
+
`X12_DANGLING_RELEASE_CHAR` fired on any degenerate segment ending in an empty last element,
|
|
361
|
+
because the check keys on a trailing `?`. With `?` as the separator that trailing byte is an
|
|
362
|
+
empty element, not an unpaired escape, so `PER?IC?NAME?TE?5551234?` is silent now.
|
|
363
|
+
- **🛑 The guard is per ROLE, and it is a READ-side guard. A `?` REPETITION or COMPONENT separator
|
|
364
|
+
still does not split, and that is deliberate and measured.** `escapeRelease` writes `??` for a
|
|
365
|
+
literal `?` whatever role `?` was declared in, so documents this library emitted through `0.0.15`
|
|
366
|
+
carry `CLM*PATIENT??ACCT*150.00` and `getSegmentValue(clm, "01")` reads `"PATIENT?ACCT"` back out
|
|
367
|
+
of them. Splitting those two roles literally would re-frame that as two empty components, so it
|
|
368
|
+
would stop reading a value this library itself wrote. **That reason survives the emit-side
|
|
369
|
+
refusal below - those documents exist**, which is why the read side did not move with it.
|
|
370
|
+
- **🟢 CLOSED, in the slice after this one, and WIDER than it was filed
|
|
371
|
+
(`X12-EMIT-DEGENERATE-RELEASE-DELIMITER`): every builder REFUSES a delimiter set in which any of
|
|
372
|
+
the four roles is `?`.** The entry as filed named the element separator and one mechanism - a
|
|
373
|
+
caller VALUE the escape cannot protect, because the protecting `?` is itself the separator. Two
|
|
374
|
+
things were measured wrong about that:
|
|
375
|
+
- **The class is FOUR roles, not three.** The segment terminator does the same thing: a released
|
|
376
|
+
byte inside a value ends the segment early, so a phantom segment appears mid-transaction.
|
|
377
|
+
- **There is a SECOND mechanism, and it needs no caller value at all.** A builder joins
|
|
378
|
+
composites with the component separator and repetitions with the repetition separator, so
|
|
379
|
+
where either is `?` the library's own structural join is emitted as an escape. `build837P` on
|
|
380
|
+
`componentSeparator: "?"` emitted `SV1-01-2` (the procedure code) and `HI-01-2` (the diagnosis
|
|
381
|
+
code) fused into the preceding component **on every document, no trigger byte in any value**,
|
|
382
|
+
`warnings: []`. That is the sharper of the two: a claim is adjudicated on those codes.
|
|
383
|
+
|
|
384
|
+
A value-level mitigation was refused for a measured reason rather than a stylistic one: it
|
|
385
|
+
cannot reach the second mechanism, and _"keep `?` out of your values"_ had already been refuted
|
|
386
|
+
in this arc for protecting nobody. It refuses specs that built at `0.0.15`, some of which
|
|
387
|
+
round-tripped through this library's own parser - **that round trip is not the bar**, because
|
|
388
|
+
ISA-11 and ISA-16 transmit the declared set and a conformant receiver splits on it. Pinned in
|
|
389
|
+
`test/builder-degenerate-release-delimiter.test.ts` and, per builder, in each build suite.
|
|
390
|
+
|
|
391
|
+
- **🩺 A delimiter that is not SHAPED like one is refused on emit too, by every builder.** Each of
|
|
392
|
+
the four roles must be a **string of exactly one visible character**, and the four must be
|
|
393
|
+
**mutually distinct**. That is not a rule invented for emit: it is the predicate
|
|
394
|
+
`detectDelimiters` already applies to an inbound ISA, where failing it is the Tier-3 fatal
|
|
395
|
+
`X12_INVALID_DELIMITERS`. A builder composing a document its own parser refuses to read was
|
|
396
|
+
disagreeing with itself. Nothing is trimmed, coerced or substituted - the set is refused.
|
|
397
|
+
|
|
398
|
+
Three things it closed, and they are not one defect. A **multi-character** delimiter built with
|
|
399
|
+
`warnings: []`: a `segmentTerminator` of `"~~"` put phantom segments on the model that `SE-01`
|
|
400
|
+
never counted, and a `componentSeparator` of `":~"` read back through a well-formed ISA while the
|
|
401
|
+
builder's own terminator became an uncounted empty segment. **No claim is made about which roles
|
|
402
|
+
were affected**; declare one character per role. A **non-string** delimiter was coerced by the join but not by the
|
|
403
|
+
escape, so the document framed on a byte no element value was protected from: an 837 with
|
|
404
|
+
`componentSeparator: 1` read `SV1-01-2` back as `992` rather than the procedure code `99213`,
|
|
405
|
+
`warnings: []`. And **`buildTA1` had no net at all** - it is the one builder with no trailing
|
|
406
|
+
`parseX12`, so EVERY role and EVERY shape was silent there: `elementSeparator: ""` returned
|
|
407
|
+
`TA10000000012606011200A000`, the reassociation key and the disposition fused into one blob, and
|
|
408
|
+
`elementSeparator: "||"` returned `TA1||000000001||260601||1200||A||000`, which inside an ISA
|
|
409
|
+
reads back with `TA1-01` empty and `ackCode: "R"` - an Accept emitted as a Reject.
|
|
410
|
+
|
|
411
|
+
**🛑 Two things change for a caller.** A spec that failed at base with an `X12ParseError` /
|
|
412
|
+
`X12_INVALID_DELIMITERS` escaping out of the `build*` call now refuses earlier with that
|
|
413
|
+
builder's own typed error and its existing code, so a consumer catching the parse class stops
|
|
414
|
+
catching and one catching the build class starts; no code is minted. And a
|
|
415
|
+
`segmentTerminator` of `"~\r\n"` - asking for line-broken output - **built with `warnings: []`
|
|
416
|
+
before and is refused now** (no count of such shapes is published; that is not the only one).
|
|
417
|
+
The check also runs **before** the control-number guards, so a spec that is mis-shaped _and_
|
|
418
|
+
carries an empty control number now reports the delimiter refusal rather than the control-number
|
|
419
|
+
one, on the same code. A message moves; no code does. It never did what it looked like it did: `parseX12` tolerates CR/LF
|
|
420
|
+
between segments, so the model recorded `~` and `serializeX12` emitted no line breaks. Reading a
|
|
421
|
+
file that is written that way is unaffected; only declaring it on emit is.
|
|
422
|
+
|
|
423
|
+
- **🩺 `PRE-EXISTING` and NOT closed by that refusal: it is a UTF-16 CODE-UNIT rule, not a byte
|
|
424
|
+
rule.** A character that is one code unit but several bytes on the wire satisfies it and still
|
|
425
|
+
displaces every ISA position after it, so `componentSeparator: "\u00a7"` builds with
|
|
426
|
+
`warnings: []` and a byte-oriented receiver reads ISA-16 as `0xC2` and the terminator as `0xA7`.
|
|
427
|
+
The smart quote `"\u2019"` a companion-guide PDF gives you instead of `'` does the same.
|
|
428
|
+
Disclosed rather than guarded: the read side counts code units too (`charAt`), so moving one side
|
|
429
|
+
alone would put emit and read back out of step, which is exactly what that refusal exists to
|
|
430
|
+
prevent. **Declare delimiters from the basic single-byte set.**
|
|
431
|
+
|
|
432
|
+
- **🩺 PRE-EXISTING and NOT closed here: on a degenerate set a `?~` still swallows the segment
|
|
433
|
+
terminator.** `findUnescapedTerminator` guards its own role only, so with `?` as the element
|
|
434
|
+
separator a segment that ends in an EMPTY LAST ELEMENT puts a `?` immediately before the
|
|
435
|
+
terminator and the scanner reads it as an escape: `PER?IC?NAME?TE?5551234?EX?~SE?3?0001~` frames
|
|
436
|
+
as ONE segment and raises `X12_MISSING_SE`. This slice does not touch framing. But **the READ of
|
|
437
|
+
that merged blob did move, so do not take "framing is untouched" as "nothing about this residual
|
|
438
|
+
moved"**: at `0.0.15` the merge produced one `(non-spec)` element no walker looked at, and here
|
|
439
|
+
it frames, so `~SE` and the SE's own control number land in `PER`'s communication-number slots.
|
|
440
|
+
`X12_MISSING_SE` still fires, so it is not silent. Both are pinned in
|
|
441
|
+
`test/parser-segment-degenerate-release-separator.test.ts` so they cannot move unnoticed.
|
|
442
|
+
- **Values are still RAW, pre-`?`-unescape**, and `elements.join(separator)` still reproduces the
|
|
443
|
+
segment byte for byte, so `serializeX12`'s count substitution and the byte-exact round trip are
|
|
444
|
+
unaffected. Both are pinned.
|
|
445
|
+
- **The ISA is untouched and stays positional**, for the reason the envelope-splitter entry above
|
|
446
|
+
gives: it is fixed-width, which is what lets the delimiters be recovered from it at all.
|
|
447
|
+
|
|
448
|
+
- **🩺 `buildInterchange` now RELEASES GS-04, GS-05 and GS-07, so the interchange it hands back
|
|
449
|
+
reports the values you passed, and the bytes it writes for such a value CHANGED**
|
|
450
|
+
(`X12-INTERCHANGE-GS-EMIT-NOT-RELEASE-AWARE`). Through `0.0.15` it mapped its release escaper over
|
|
451
|
+
GS-01, GS-02, GS-03, GS-06 and GS-08 and wrote `groupDate` (GS-04), `groupTime` (GS-05) and
|
|
452
|
+
`responsibleAgencyCode` (GS-07) raw. It returns `parseX12` of the bytes it just wrote, so a value
|
|
453
|
+
carrying an active delimiter in one of those three took a slot of its own and shifted every element
|
|
454
|
+
after it down one, inside a single call. Measured on one group with
|
|
455
|
+
`versionRelease: "005010X222A2"` and `groupControlNumber: "1"`:
|
|
456
|
+
|
|
457
|
+
| spec field | read GS-06 | read GS-08 | warnings |
|
|
458
|
+
| ------------------------------ | ---------- | ---------- | ------------------------------------------------------- |
|
|
459
|
+
| `groupDate: "2026*0601"` | `"1200"` | `"X"` | `X12_CONTROL_NUMBER_MISMATCH` |
|
|
460
|
+
| `groupTime: "12*00"` | `"00"` | `"X"` | `X12_CONTROL_NUMBER_MISMATCH` |
|
|
461
|
+
| `responsibleAgencyCode: "X*Y"` | `"1"` | `"Y"` | none |
|
|
462
|
+
| `groupTime: "12~00"` | absent | absent | `X12_UNEXPECTED_SEGMENT`, `X12_CONTROL_NUMBER_MISMATCH` |
|
|
463
|
+
| `groupDate: "20260601?"` | `"X"` | absent | `X12_CONTROL_NUMBER_MISMATCH` |
|
|
464
|
+
|
|
465
|
+
**🩺 The `responsibleAgencyCode` row is the one to know about, because nothing was raised on any
|
|
466
|
+
channel.** GS-06 kept its own slot, so it still reconciled against GE-02 and no control-number
|
|
467
|
+
warning fired; what moved was GS-08, the version / release / industry identifier code. All five
|
|
468
|
+
rows now read the values the caller passed, with an empty warning array.
|
|
469
|
+
- **🛑 It changes bytes, and the bound is the same one the two entries below carry.** A value
|
|
470
|
+
containing none of the four delimiters and no `?` is emitted byte-for-byte as before, which is
|
|
471
|
+
every conformant GS-04 / GS-05 / GS-07. **Read the property rather than a direction list:** the
|
|
472
|
+
interchange the call returns now reports the values you passed, where before it reported whatever
|
|
473
|
+
the shift left in each slot. **What is narrower here than in the two entries below: no reader
|
|
474
|
+
moved.** An inbound document from a trading partner decodes exactly as it did at `0.0.15`; what
|
|
475
|
+
changed is what this library emits.
|
|
476
|
+
- **Read the delimiter set by ROLE, never by byte.** `InterchangeSpec` lets you declare all four,
|
|
477
|
+
so which BYTES shift is a property of the set you declared: with `elementSeparator: "|"` it was a
|
|
478
|
+
GS-07 of `"X|Y"` that took GS-08's slot and `"X*Y"` that was inert. **Only the element separator
|
|
479
|
+
and the segment terminator ever shifted the segment's own framing, plus a `?` immediately before
|
|
480
|
+
the element separator.** The **repetition** and **component** separators moved the dot-path
|
|
481
|
+
reader instead, and releasing them is a **gain** there: the
|
|
482
|
+
composite read `"07-1"` answered `"X"` for `"X:Y"`. **The measured cost is a mid-string `?`,
|
|
483
|
+
and only on the surfaces documented as raw** - `gs.elements[4]` reads `"2026??0601"` where it
|
|
484
|
+
read `"2026?0601"`, while the dot-path read of that value unescapes and is unchanged. No total is
|
|
485
|
+
published: that is what was measured, not a closed account.
|
|
486
|
+
- **A caller who was pre-releasing these values themselves is now escaping twice.** `"2026?*0601"`
|
|
487
|
+
in gives `2026???*0601` out and a dot-path read of `"2026?*0601"` where it read `"2026*0601"`.
|
|
488
|
+
Drop the hand-rolled escape.
|
|
489
|
+
- **A wrong-typed GS element now names its slot.** The type check runs over the unescaped parts, so
|
|
490
|
+
a numeric `groupDate` still refuses with `buildInterchange: "GS"-04 must be a string` rather than
|
|
491
|
+
degrading to the builder-named message the escaper alone would give. The five slots that already
|
|
492
|
+
escaped gained the slot name with it. **`null` and `undefined` in these three fields are ABSENT,
|
|
493
|
+
not refused** - each resolves through a default before either guard sees it.
|
|
494
|
+
- **A LITERAL segment id this library writes is never escaped, and that is a rule rather than an
|
|
495
|
+
omission.** `esc` releases against the delimiter set the CALLER declared, and a
|
|
496
|
+
`componentSeparator` of `"S"` is admissible, so escaping element 0 would turn the literal `"GS"`
|
|
497
|
+
into `G?S` and the group header would stop being a `GS`. `GE`, `ST`, `SE` and `IEA` already
|
|
498
|
+
followed that rule. **Read "literal" strictly, and this one is `PRE-EXISTING` rather than
|
|
499
|
+
anything this release changed:** a `SegmentSpec` body segment is `[segmentId, ...elements]`
|
|
500
|
+
supplied wholesale, `buildInterchange` has released that caller-supplied id since before this
|
|
501
|
+
release, and `SegmentSpec`'s JSDoc says it is emitted verbatim. The two disagree. It is noisy
|
|
502
|
+
rather than silent - a `?`-prefixed id is rejected by the envelope walker as an orphan with
|
|
503
|
+
`X12_UNEXPECTED_SEGMENT` - and which side is wrong is a decision, so it is recorded rather than
|
|
504
|
+
changed here.
|
|
505
|
+
- **What this does NOT close.** `buildInterchange`'s IEA-02 does not go through the escaper: it is
|
|
506
|
+
padded and has to stay byte-equal to the fixed-width ISA-13 it reconciles against, so that is a
|
|
507
|
+
decision of its own. The ISA fixed-width slots are still outside both guards. And an unescaped
|
|
508
|
+
active delimiter is still not safe anywhere, because that is what a delimiter is.
|
|
509
|
+
|
|
510
|
+
- **🩺 `buildTA1` now RELEASES its five caller-supplied elements, so an Accept acknowledgment this
|
|
511
|
+
library emits no longer reads back as a Reject, and the bytes it writes for such a value CHANGED**
|
|
512
|
+
(`X12-TA1-EMIT-NOT-RELEASE-AWARE`). Through `0.0.14` `buildTA1` joined the five values with the
|
|
513
|
+
element separator and escaped none of them, so a value carrying an active delimiter took a slot of
|
|
514
|
+
its own and shifted every element after it down one. TA1-04 is the disposition and TA1-05 the note,
|
|
515
|
+
and `parseTA1` narrows an out-of-enum TA1-04 to `R`. Measured with `parseX12` + `parseTA1` over
|
|
516
|
+
`ISA … <what buildTA1 returned> … IEA`, `ackCode` `"A"` and `noteCode` `"000"` throughout:
|
|
517
|
+
|
|
518
|
+
| `interchangeControlNumber` | emitted at `0.0.14` | read `ackCode` | read TA1-01 | warnings |
|
|
519
|
+
| -------------------------- | --------------------------------- | -------------- | -------------------- | ------------------------ |
|
|
520
|
+
| `"000000001"` | `TA1*000000001*260601*1200*A*000` | `"A"` | `"000000001"` | none |
|
|
521
|
+
| `"00000001?"` | `TA1*00000001?*260601*1200*A*000` | `"R"` | `"00000001?*260601"` | none |
|
|
522
|
+
| `"0000*0001"` | `TA1*0000*0001*260601*1200*A*000` | `"R"` | `"0000"` | none |
|
|
523
|
+
| `"0000~0001"` | `TA1*0000~0001*260601*1200*A*000` | `"R"` | `"0000"` | `X12_UNEXPECTED_SEGMENT` |
|
|
524
|
+
|
|
525
|
+
**An Accept acknowledgment this library emitted read back as a Reject, on the element that
|
|
526
|
+
reassociates it, with nothing raised on any channel.** The `*` and `~` rows did that on every
|
|
527
|
+
released version; the `?` row is the one the envelope-splitter entry above opened. **🩺 The inverse
|
|
528
|
+
exists and is the less safe direction:** the read narrows an out-of-enum TA1-04 to `R`, so a
|
|
529
|
+
well-typed shift always lands on Reject, but `noteCode` is checked by the type system and by
|
|
530
|
+
nothing at run time, so a `noteCode` of literally `"A"` shifted onto TA1-04 and made a **Reject
|
|
531
|
+
read back as an Accept** - and a sender who reads an Accept does not resubmit. All four now read
|
|
532
|
+
back the disposition that was emitted.
|
|
533
|
+
- **The grounding is inside this package, not in a spec clause**, the same tiebreak the
|
|
534
|
+
envelope-splitter entry records. `buildTA1` emitted bytes that this package's own reader decoded
|
|
535
|
+
into a different disposition than the caller asked for, while every other builder already
|
|
536
|
+
released the same class of element through the same helper. Nobody here has read a clause that
|
|
537
|
+
settles it, and nothing above claims one.
|
|
538
|
+
- **🛑 It changes bytes this library already put on the wire, which is the cost.** A value
|
|
539
|
+
containing none of the four delimiters and no `?` is emitted byte-for-byte as before, and that
|
|
540
|
+
is every conformant TA1: TA1-01 echoes ISA-13, TA1-02 / TA1-03 echo ISA-09 / ISA-10, and
|
|
541
|
+
TA1-04 / TA1-05 are code list values. A value containing one is now released.
|
|
542
|
+
- **🛑 No warning code is added and no case moves onto a new code, but the consumer predicate
|
|
543
|
+
MOVES IN BOTH DIRECTIONS.** `parseTA1` of a `buildTA1` output now reports the disposition and
|
|
544
|
+
note the caller passed; before, it reported whatever element the shift left in TA1-04, which
|
|
545
|
+
could be the caller's, a coincidental in-enum value, or an out-of-enum one narrowed to `"R"`. So
|
|
546
|
+
`ackCode === "R"` **stops** firing where an Accept had been shifted onto it, and **starts**
|
|
547
|
+
firing where a Reject had been shifted off it: `interchangeTime: "12*A"` with `ackCode: "R"`
|
|
548
|
+
read `"A"` before and reads `"R"` now, with every field a valid member of its union.
|
|
549
|
+
`ackCode === "A"` moves the same two ways. What is one-directional is the safety, which is a
|
|
550
|
+
different statement: nothing now reports a disposition the caller did not ask for.
|
|
551
|
+
- **What releasing the REST of the set costs, and where it does not cost.** Only `*`, `~` and a
|
|
552
|
+
`?` immediately before the separator ever shifted the segment's own element framing. **`^` and
|
|
553
|
+
`:` moved the dot-path reader instead, and releasing them is a gain there:**
|
|
554
|
+
`getSegmentValue(ta1, "01")` answered `"0000"` through `0.0.14` for a control number of
|
|
555
|
+
`"0000^0001"`, silently truncating the reassociation key to the first repetition, and answers
|
|
556
|
+
`"0000^0001"` now; the composite read `"01-1"` answered `"0000"` for `"0000:0001"` and answers
|
|
557
|
+
the whole value now. **The measured pure cost is a mid-string `?`, and only on the surfaces
|
|
558
|
+
documented as raw**: `raw` and `elements` read `"0000??0001"` where they read `"0000?0001"`,
|
|
559
|
+
while every dot-path read unescapes and answered `"0000?0001"` on both.
|
|
560
|
+
No total is published: that is what was measured, not a closed account. (`getSegmentValue`
|
|
561
|
+
takes an `X12Segment` and `Ta1Segment` carries no `id`, so add one to read a TA1 through it.)
|
|
562
|
+
**🩺 A clause naming `parseTA1`'s fields as a third such surface stood here and is DELETED, not
|
|
563
|
+
reworded** - `X12-TA1-RESIDUALS` made those fields post-unescape, so it is measured false.
|
|
564
|
+
- **A caller who was hand-rolling the escape** (the remedy this file named while the defect was
|
|
565
|
+
open) regresses on both kinds of surface: `"00000001??"` in, `TA1*00000001????*…` out, and
|
|
566
|
+
`getSegmentValue` answering `"00000001??"` where it answered `"00000001?"`. The framing and the
|
|
567
|
+
disposition stay correct, but **drop the hand-rolled escape.**
|
|
568
|
+
- **An EMPTY control number was not refused by this slice, and is refused by the NEXT one.**
|
|
569
|
+
`escapeRelease` early-returns on `""` and `buildTA1` carried no required-field guard, so
|
|
570
|
+
`interchangeControlNumber: ""` emitted `TA1**260601*1200*A*000` with `warnings: []`, here and at
|
|
571
|
+
every earlier release. `X12-EMPTY-CONTROL-NUMBER-FABRICATED` closed it across every builder; see
|
|
572
|
+
the entry at the top of this file, including the whitespace-only residual it leaves open.
|
|
573
|
+
- **🩺 The READ half did not move IN THIS SLICE, and `X12-TA1-RESIDUALS` is the one that moved
|
|
574
|
+
it** - see the entry at the top of this file. A control number of `"00000001?"` read back as
|
|
575
|
+
`"00000001?*260601"` before this slice and as `"00000001??"` after it; the clause that stood
|
|
576
|
+
here telling callers to apply `unescapeRelease` themselves is **deleted, not reworded**, because
|
|
577
|
+
the library now does it.
|
|
578
|
+
- **The release is scoped to the delimiter set the CALLER states.** `BuildTA1Options` gained
|
|
579
|
+
`repetitionSeparator` / `componentSeparator` / `segmentTerminator` beside the existing
|
|
580
|
+
`elementSeparator`, the same four `build999` already takes, and they exist for escaping and
|
|
581
|
+
nothing else - `buildTA1` still emits no terminator. Escaping against a guessed set is a value
|
|
582
|
+
corruption rather than a safe default: `unescapeRelease` preserves `?X` verbatim for any `X`
|
|
583
|
+
outside the declared set, so a value released against the wrong delimiter comes back carrying a
|
|
584
|
+
stray `?`. **The defaults are the cosyte archetype and this function cannot verify them** - if
|
|
585
|
+
you embed a TA1 in an envelope that declares different separators, state them.
|
|
586
|
+
- **A non-string element now REFUSES** with `AckBuildError` / `X12_ACK_INVALID_SPEC`, and that is a
|
|
587
|
+
prerequisite rather than a bonus. Releasing a value means routing it through the escape helper,
|
|
588
|
+
and the bare `escapeRelease` underneath it returns its empty accumulator for a `number`, so
|
|
589
|
+
escaping without the type check would have replaced a shifted TA1-01 with a vanished one. A
|
|
590
|
+
numeric control number emitted `TA1*12345*…` at `0.0.14` (the number surviving onto `elements`,
|
|
591
|
+
inside a value typed `readonly string[]`) and an absent one emitted `TA1**250101*…`; both refuse.
|
|
592
|
+
**No existing refusal moves code:** `ackCode` `"A"` with a non-`000` note still reports
|
|
593
|
+
`X12_TA1_ACCEPT_WITH_NOTE` and still runs first.
|
|
594
|
+
- **What this does NOT close.** `buildTA1` still uses no segment joiner, so its refusal names the
|
|
595
|
+
builder and never the slot; `buildInterchange` did not escape GS-04 / GS-05 / GS-07 at this
|
|
596
|
+
release, which `X12-INTERCHANGE-GS-EMIT-NOT-RELEASE-AWARE` then closed; and an unescaped active
|
|
597
|
+
delimiter is still not safe anywhere, because that is what a delimiter is.
|
|
64
598
|
|
|
65
599
|
- **🩺 BREAKING for `build277` callers: a 277 service line now REQUIRES `unitsOfService`, because
|
|
66
600
|
SVC-07 is a required element in `005010X212` and this library was not emitting it at all**
|
|
@@ -830,14 +1364,10 @@ N-char spec limit` refusal, one per emitting module, where the branch fires **be
|
|
|
830
1364
|
the array guard's primitive arm. If you were reading a value back out of one of those messages, that
|
|
831
1365
|
is a behaviour change.
|
|
832
1366
|
|
|
833
|
-
**
|
|
1367
|
+
**What that sentence does NOT say, and it is deliberate.** The array guard still reports the
|
|
834
1368
|
`length` and the class tag of a forged array-like, bounded through the same renderer: those describe
|
|
835
1369
|
the SHAPE you forged rather than the contents of a document element, and they are the whole
|
|
836
|
-
diagnostic for `{ length: "9".repeat(120000) }`.
|
|
837
|
-
alongside the type (`build999: "AK9"-01 must be a string, ...`), because it holds the whole segment;
|
|
838
|
-
the escape-helper and `X12Decimal` guards name the **builder**, so on those two the value used to
|
|
839
|
-
stand in for a locator and now nothing does. That is a real diagnostic cost, and it is written down
|
|
840
|
-
rather than hidden.
|
|
1370
|
+
diagnostic for `{ length: "9".repeat(120000) }`.
|
|
841
1371
|
|
|
842
1372
|
- **`defineProfile()` refusals are bounded on the same terms, since `0.0.6`.** `X12ProfileError.message`
|
|
843
1373
|
used to interpolate your profile name, quirk id, effect, fixture path and expected-warning codes
|
|
@@ -913,10 +1443,12 @@ N-char spec limit` refusal, one per emitting module, where the branch fires **be
|
|
|
913
1443
|
than reaching the message.
|
|
914
1444
|
|
|
915
1445
|
**Read the "through a builder's segment joiner" qualifier literally.** `buildTA1` does not use one,
|
|
916
|
-
and is therefore **not covered
|
|
917
|
-
|
|
918
|
-
(`TA1**250101*1200*A*000`)
|
|
919
|
-
|
|
1446
|
+
and is therefore **not covered by the segment guard**. Through `0.0.14` that meant nothing checked
|
|
1447
|
+
its five elements at all, and a numeric or `undefined` `interchangeControlNumber` was emitted
|
|
1448
|
+
silently (`TA1**250101*1200*A*000`); `X12-TA1-EMIT-NOT-RELEASE-AWARE` closed that by routing them
|
|
1449
|
+
through the escape helper, which type-checks first and refuses with `AckBuildError` /
|
|
1450
|
+
`X12_ACK_INVALID_SPEC`. What being outside the segment guard still costs is the **slot** in the
|
|
1451
|
+
refusal message: it names `buildTA1`, never `TA1-01`.
|
|
920
1452
|
|
|
921
1453
|
The monetary slots got their own guard on top of it: a slot typed `X12Decimal` refuses anything that
|
|
922
1454
|
is not one, rather than rendering `number.toString()` into the document.
|
|
@@ -928,7 +1460,9 @@ N-char spec limit` refusal, one per emitting module, where the branch fires **be
|
|
|
928
1460
|
`1?*BOGUS`); the fixed-width ISA slots do not go through it at all.
|
|
929
1461
|
2. **Whether an `X12Decimal` carries the SCALE you meant.** `fromString("0.3")` and
|
|
930
1462
|
`fromString("0.30")` are both accepted and both emit verbatim. That choice is yours.
|
|
931
|
-
3. **
|
|
1463
|
+
3. **The delimiter set `buildTA1` releases against**, per the paragraph above. It defaults to the
|
|
1464
|
+
cosyte archetype and the function cannot verify it; state the separators on `BuildTA1Options`
|
|
1465
|
+
if you embed a TA1 in an envelope that declares different ones.
|
|
932
1466
|
4. **`build835`'s balance-equation amounts, which refuse UNTYPED.** `build835` runs its balance
|
|
933
1467
|
guard before it builds the escape helper, and that guard calls `X12Decimal` methods on your
|
|
934
1468
|
value. So a raw `number` there throws a plain `TypeError` with **no `code`** - some saying the
|