@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 +598 -12
- package/KNOWN-LIMITATIONS.md +372 -28
- package/README.md +1 -1
- package/dist/index.cjs +351 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -40
- package/dist/index.d.ts +72 -40
- package/dist/index.mjs +351 -37
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
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
|
-
**
|
|
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) }`.
|
|
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
|
-
|
|
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
|
|
1011
|
-
`
|
|
1012
|
-
|
|
1013
|
-
rather than
|
|
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
|