@cosyte/x12 0.0.16 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,8 +7,240 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Added
11
+
12
+ - **`pnpm check:no-internal-refs` - a gate on internal project bookkeeping reaching a consumer**
13
+ (`X12-NO-INTERNAL-REFS-GATE`), with `.github/workflows/no-internal-refs.yml` running it on every
14
+ push and PR. Documentation and tooling only: the `src/` half of the diff is comment-only and
15
+ `dist/index.mjs` / `dist/index.cjs` are byte-identical base to head.
16
+
17
+ It scans `README.md`, `TRADEMARKS.md`, `LICENSE`, `KNOWN-LIMITATIONS.md`, `docs-content/`, the npm
18
+ `description` and `keywords`, and the `/** */` doc comments under `src/` (which tsup compiles into
19
+ `dist/index.d.ts` and `dist/index.d.cts`), against six rules: item identifiers, phase and wave
20
+ language, ADR references, "slice" as our word for a unit of work, meta-repo paths, and bracketed
21
+ traceability markers. Every rule runs line by line and again over paragraph-joined text, because
22
+ this repo hard-wraps and a multi-token violation that straddles a wrap is invisible to a line
23
+ scan: on the sweep this shipped with, 52 of 124 phase-rule hits and 29 of 76 identifier hits in
24
+ `src/` were visible only to the reflowed pass. `CHANGELOG.md`, `.changeset/`, `CLAUDE.md`,
25
+ `documentation/`, `scripts/`, `test/` and `//` comments are deliberately out of scope: the
26
+ convention names those as where the identifiers belong.
27
+
28
+ **It is derived from the sibling parsers' copy, not transcribed from it.** Every sibling excludes
29
+ `X12-\d{3}[A-Z]?` and `X12-\d{6}` as standards designations to protect. In this repo `X12` is also
30
+ our own project prefix. Counted here with two independent tools that agreed exactly, that pattern
31
+ matches 141 times across tracked files and **every one of the 141 is an internal identifier**, in
32
+ eight spellings, with no standards designation among them; the hyphenated standards spelling it
33
+ exists to protect occurs zero times, because this corpus writes transaction sets and guides bare
34
+ (`837P`, `005010X222A1`). Carrying the sibling line verbatim reported OK over six live violations
35
+ on shipping carriers. Both the item spellings and the hyphenated forms are now asserted
36
+ individually in the self-test, each on its own, after a control run showed a shared positive
37
+ sample was satisfied by its first entry and covered none of the rest.
38
+
39
+ `KNOWN-LIMITATIONS.md` is scanned and is the entry no sibling copy has: this package ships it in
40
+ `files`, and it held most of what the gate first found.
41
+
42
+ **What it cannot do**, stated so a green run is not over-read: it reads the `src/` doc comments
43
+ that are the SOURCE of the published declarations, never `dist/` itself, which is untracked build
44
+ output this script does not build. It catches identifiers and named patterns, not ordinary English
45
+ sentences about how the software came to exist, so the reviewer still owns half the rule. It reads
46
+ file contents and never file names. It is not yet a required check on this repo, so it reds
47
+ visibly without blocking a merge.
48
+
49
+ ### Changed
50
+
51
+ - **`WARNING_MESSAGES.X12_PRE_005010` stops asserting what ISA-12 declares.** The message a consumer
52
+ reads off `w.message` said _"ISA-12 declares a version other than the HIPAA baseline `00501`, so
53
+ the input may diverge from 005010 semantics. The declared version is preserved verbatim on the
54
+ model."_ The guard behind it tests the twelfth element of the ISA split, so both the assertion
55
+ about ISA-12 and the phrase "the declared version" presuppose a header that framed. **This is a
56
+ runtime value, not a comment: `dist/index.mjs` and `dist/index.cjs` change here, and that is the
57
+ difference from the comment-only slice above.** No guard, no code, no position and no control flow
58
+ moved: the same interchanges raise the same codes at the same positions as before.
59
+
60
+ Measured on one interchange per row, reading ISA-12 at its own fixed byte offset and
61
+ again off the split:
62
+
63
+ ```text
64
+ construction ISA-12 at its fixed offset elements[12] X12_PRE_005010 other codes
65
+ spec-clean "00501" "00501" silent -
66
+ ISA-12 declares 00401 "00401" "00401" FIRES -
67
+ ISA-05 carries `*` "00501" "^" FIRES X12_ISA_EXTRA_ELEMENT_SEPARATOR, X12_CONTROL_NUMBER_MISMATCH
68
+ ISA-06 carries `*` "00501" "^" FIRES X12_ISA_EXTRA_ELEMENT_SEPARATOR, X12_CONTROL_NUMBER_MISMATCH
69
+ ISA-08 carries `*` "00501" "^" FIRES X12_ISA_EXTRA_ELEMENT_SEPARATOR, X12_CONTROL_NUMBER_MISMATCH
70
+ ISA-13 carries `*` "00501" "00501" silent X12_ISA_EXTRA_ELEMENT_SEPARATOR
71
+ ```
72
+
73
+ The replacement names the element the guard read, states that raising the code does not establish
74
+ that the header split into `ISA` plus 16 elements, and points at `isa.raw`. It echoes no value, so
75
+ the message stays a static table lookup with no consumer bytes in it. `pre005010`'s docblock twin
76
+ of the same clause moves with it, which is why the two were left in agreement rather than
77
+ corrected one at a time. The shipped troubleshooting table carried a third spelling of the same
78
+ assertion ("ISA-12 declares a version family other than `00501`") and is corrected too.
79
+
80
+ - **Internal project bookkeeping removed from the markdown surface, the npm metadata and the
81
+ shipped doc comments.** Deliberately not "from every surface a consumer reads": source string
82
+ literals are outside this gate, and five bundled code-list snapshots still carry build-order
83
+ framing in an exported `note` field, which reaches `dist/index.mjs` and `dist/index.cjs` as a
84
+ runtime value. That is a different carrier with a different review path, it reproduces on the base,
85
+ and it is filed rather than folded in here. Item identifiers, "phase" build-order framing and
86
+ "slice" as a unit of work were translated into what the software does and what changed, in
87
+ `KNOWN-LIMITATIONS.md`, `docs-content/cookbook.md`, `docs-content/troubleshooting.md` and the doc
88
+ comments across `src/`. Nothing was deleted to
89
+ satisfy the gate: a doc comment removed rather than reworded would be a regression, since JSDoc
90
+ with `@example` on every public export is a guardrail neither lint nor coverage protects. Two
91
+ pages sent the reader to the package's `CLAUDE.md`, which the tarball does not ship; they now
92
+ point at `README.md` and the Cookbook. Measured on a local build of base commit `a3e081d`,
93
+ `dist/index.d.ts` carried 13 lines of item identifiers, 64 of phase and wave framing, 2 of
94
+ "slice" jargon and 1 meta-repo path, with `dist/index.d.cts` byte-identical to it; at head all
95
+ four counts are zero and the two twins remain byte-identical to each other. Zero on the rules is
96
+ the weaker statement: six lines of phase framing survived the first sweep while every rule read
97
+ zero, four ending in punctuation and two using the PLURAL `Phases`, which rule 2 cannot see because
98
+ it requires `phase` followed by an alphanumeric. All six were then translated, so the built
99
+ declaration file now contains no occurrence of the word at all. The plural form remains ungated and
100
+ is disclosed as such.
101
+
102
+ - **🩺 `X12_ISA_EXTRA_ELEMENT_SEPARATOR` - the ISA element split now has an arity check**
103
+ (`X12-ISA-ELEMENT-ARITY`). Additions-only: no decoding moved, no existing warning was suppressed or
104
+ narrowed, and nothing is re-framed.
105
+
106
+ `decodeIsa` splits the 105-byte ISA element area on the element separator, and the comment above it
107
+ asserted the result was "exactly 17 entries by construction because the element-separator-position
108
+ guard in `delimiters.ts` already verified the layout". That guard verifies the separator at all 16
109
+ fixed 005010 positions, which bounds the split from BELOW - it can never come out short. It was
110
+ never bounded from above. An ISA element value carrying that same byte splits again, so the element
111
+ comes back a prefix and everything after it is displaced. **How far is not derivable from
112
+ `isa.elements`**: more than one element can carry an extra separator, and one sitting between two
113
+ of them is displaced less than one sitting after both. `isa.raw` plus the ISA's fixed widths is the
114
+ only route back.
115
+
116
+ Planting the element separator inside each of the 16 fixed elements of one conformant interchange,
117
+ measured at `0.0.16`:
118
+
119
+ ```text
120
+ planted in split parts warnings at 0.0.16
121
+ ISA-01..10 18 X12_PRE_005010, X12_CONTROL_NUMBER_MISMATCH
122
+ ISA-11 - X12_INVALID_DELIMITERS (Tier-3, reached first)
123
+ ISA-12 18 X12_PRE_005010, X12_CONTROL_NUMBER_MISMATCH
124
+ ISA-13 18 X12_CONTROL_NUMBER_MISMATCH
125
+ ISA-14 18 (none)
126
+ ISA-15 18 (none)
127
+ ISA-16 - X12_INVALID_DELIMITERS (Tier-3, reached first)
128
+ ```
129
+
130
+ ISA-11 and ISA-16 ARE the in-band repetition and component separator declarations, so the plant
131
+ collides with them and the Tier-3 refusal reaches it before any of this. That is a boundary of the
132
+ probe, not a property of those two elements.
133
+
134
+ On the `ISA-01..10` and `ISA-12` rows the document declares `00501` at ISA-12's own fixed offset and
135
+ `X12_PRE_005010` fires anyway; `elements[13]`, the interchange control number and the reassociation
136
+ key, answers `"00501"`; and `elements[15]`, the test/production usage indicator, answers `"0"` on a
137
+ document whose ISA-15 says `P`. The `ISA-14` and `ISA-15` rows were reported on no channel at all.
138
+
139
+ `parseX12` raises the new warning BEFORE the ISA-derived checks that read `elements[12]` and
140
+ `elements[13]` by index, because when it fires those two may be reading a displaced element rather
141
+ than the one they name. `{ strict: true }` therefore escalates on it rather than on the warning that
142
+ follows. **That is a statement about `ix.warnings` and `onWarning` only.** `serializeX12(ix,
143
+ { specClean: true })` reconciles ISA-13 against IEA-02 off `isa.elements[13]` with no arity
144
+ awareness and never raises this code, so on that channel a lone `X12_CONTROL_NUMBER_MISMATCH` can be
145
+ a displaced read of a control number that matches byte for byte at its fixed span, and the absence
146
+ of this warning there is not evidence the header framed. Pre-existing, disclosed, not fixed.
147
+
148
+ **It is a report, not a repair.** A byte that is both an element's content under the ISA's fixed
149
+ widths and the separator that same segment declares in-band has two readings; the interchange is
150
+ not 005010-conformant either way, and nothing anyone here has read settles which reading to take,
151
+ so `isa.elements` is left exactly as the split produced it and `isa.raw` still carries all 106
152
+ bytes, which is the route back.
153
+
154
+ **The emit side is untouched, and this is not a build-side guard.** The fixed-width ISA slots go
155
+ through `pad` / `padControl` and never through the caller escaper, so `buildInterchange` still
156
+ writes such a caller value straight onto the wire. It does now surface there, because
157
+ `buildInterchange` returns `parseX12` of the bytes it just wrote:
158
+ `interchangeControlNumber: "0000*0001"` came back with ISA-13 reading `"0000"`, ISA-15 reading
159
+ `"0"` instead of `"P"`, IEA-02 displaced the same way so the control-number reconciliation agreed
160
+ with the misreading, and `warnings: []`.
161
+
10
162
  ### Documentation
11
163
 
164
+ - **`IsaSegment` and the `X12_PRE_005010` warning factory no longer document a raw ISA element as the
165
+ value it holds** (`X12-ISA-VALUE-POINTERS`). **Documentation only: the diff is comment-only, and
166
+ `dist/index.mjs` and `dist/index.cjs` are byte-identical before and after, so no behaviour moved.**
167
+ The corrected text ships in `dist/index.d.ts` and `dist/index.d.cts`.
168
+
169
+ `isa.elements` holds raw byte text. `IsaSegment` called it "the 16 ISA values" and its `@example`
170
+ handed out `isa.elements[12]; // ISA-12 - version, expected "00501"` and
171
+ `isa.elements[13]; // ISA-13 - interchange control number`, while `pre005010`'s JSDoc said "The
172
+ declared version stays on `isa.elements[12]`". Measured on 005010 interchanges, one per row, plus
173
+ two rows this library emitted and read back:
174
+
175
+ ```text
176
+ construction cell stated / at that ISA offset elements[n] warnings
177
+ spec-clean elements[12] "00501" "00501" []
178
+ spec-clean elements[13] "000000001" "000000001" []
179
+ ISA-12 declares 00401 elements[12] "00401" "00401" X12_PRE_005010
180
+ ISA-06 carries `*` elements[12] "00501" "^" X12_ISA_EXTRA_ELEMENT_SEPARATOR, X12_PRE_005010, X12_CONTROL_NUMBER_MISMATCH
181
+ ISA-06 carries `*` elements[13] "000000001" "00501" X12_ISA_EXTRA_ELEMENT_SEPARATOR, X12_PRE_005010, X12_CONTROL_NUMBER_MISMATCH
182
+ ISA-13 carries `*` elements[12] "00501" "00501" X12_ISA_EXTRA_ELEMENT_SEPARATOR
183
+ ISA-13 carries `*` elements[13] "0000*0001" "0000" X12_ISA_EXTRA_ELEMENT_SEPARATOR
184
+ ISA-13 carries `?` elements[13] "00000001?" "00000001?" X12_CONTROL_NUMBER_MISMATCH
185
+ this library's own emit, stated " " elements[13] " " "00000000 " []
186
+ this library's own emit, stated "1" elements[13] "1" "000000001" []
187
+ ```
188
+
189
+ On the `ISA-06` rows the interchange declares `00501` at ISA-12's own fixed offset,
190
+ `X12_PRE_005010` fires anyway, and the sentence the consumer was holding sent them to an element
191
+ that answers `"^"`. On the `ISA-13 carries ?` row the `?` is content, because the ISA split is
192
+ deliberately not release-aware; its `X12_CONTROL_NUMBER_MISMATCH` is the fixture and not a finding.
193
+
194
+ **Two mechanisms falsify a cell on their own** - fixed-width padding, which needs no `?` and no
195
+ anomaly at all, and the arity displacement `X12_ISA_EXTRA_ELEMENT_SEPARATOR` reports - **so no
196
+ mechanism is named as the reason, the set of them is not published as closed, and nothing here says
197
+ which ISA element is special.**
198
+
199
+ The `pre005010` pointer is deleted outright, with nothing put back. `IsaSegment` gains the raw
200
+ label, keeps ISA-12's and ISA-13's semantics restated at segment level where they are true, and its
201
+ `@example` keeps one indexed pointer plus `.raw`, the same shape the four sibling envelope types
202
+ already carry. **The label is NOT the siblings' label**: theirs reads "pre-`?`-unescape", which is
203
+ false on the ISA. **The 1-indexed mapping onto ISA-01..ISA-16 is scoped to a 17-entry split rather
204
+ than deleted, and no shift is quantified.** No accessor, guard or test was added, and no route is
205
+ named as the route for reading an ISA element.
206
+
207
+ - **`IeaSegment`, `GsSegment`, `GeSegment`, `Ta1Segment` and `X12FunctionalGroup` no longer document
208
+ a raw envelope element as the value it holds** (`X12-ENVELOPE-VALUE-EXAMPLES`). **Documentation
209
+ only: the diff is comment-only, and `dist/index.mjs` and `dist/index.cjs` are byte-identical
210
+ before and after, so no behaviour moved.** The corrected text ships in `dist/index.d.ts`.
211
+
212
+ **Element values on those segment types are stored RAW, pre-`?`-unescape.** Their JSDoc named the
213
+ index and then named the value it holds - "IEA-02 interchange control number - must match ISA-13",
214
+ "GS-06 group control number", "TA1-01 (echoes the prior interchange's ISA-13 control number)" -
215
+ and repeated the promise inside an `@example` fence, which is the form a consumer copies. Measured
216
+ on one interchange where each documented non-ISA slot carries the sender-intended value `A*B`,
217
+ written on the wire in released form `A?*B`, **every one of those cells reads back `"A?*B"` and
218
+ not `"A*B"`.**
219
+
220
+ The same correction is applied to `parseX12`'s own `@example`, which handed out `// "HC"` at
221
+ `gs.elements[1]`.
222
+
223
+ On a `TA1` that `buildTA1` emitted and `parseX12` read back:
224
+
225
+ ```text
226
+ stated TA1-01 emitted TA1 elements[1] parseTA1()
227
+ "000000001" TA1*000000001*260601*1200*A*000 "000000001" "000000001"
228
+ "0000*0001" TA1*0000?*0001*260601*1200*A*000 "0000?*0001" "0000*0001"
229
+ "0000~0001" TA1*0000?~0001*260601*1200*A*000 "0000?~0001" "0000~0001"
230
+ "00000001?" TA1*00000001??*260601*1200*A*000 "00000001??" "00000001?"
231
+ ```
232
+
233
+ Every row `warnings: []`. TA1-01 is the reassociation key, so a consumer following the shipped
234
+ `@example` compared framed bytes against the ISA-13 it was acknowledging. `parseTA1` already read
235
+ the same bytes correctly, so the package disagreed with itself.
236
+
237
+ **Each block now carries the label `X12Segment` has always carried** - "Element values are stored
238
+ RAW, pre-`?`-unescape" - and the `@example` pointers keep their index and lose the promise. The
239
+ slot mapping (`elements[2]` = IEA-02) was true and is kept, and so are the element names, the
240
+ formats and the code-list citations, restated where they are true: as facts about the segment's
241
+ fields rather than about a raw element. No accessor, guard or test was added, and **no route is
242
+ named as the route.**
243
+
12
244
  - **The documentation carriers that told you to read an envelope element's logical value through
13
245
  `getSegmentValue` are corrected** (`X12-ENVELOPE-VALUE-ROUTES`). **Documentation and tests only: no
14
246
  runtime line changed and the emitted JS is byte-identical, so no behaviour moved.**
@@ -16,13 +16,77 @@ model.
16
16
 
17
17
  ## Data / decode boundaries
18
18
 
19
+ - **🩺 An ISA element carrying the element separator makes the header split into more than
20
+ `ISA` + 16 elements. Through `0.0.16` no check saw that at all; it is now reported as
21
+ `X12_ISA_EXTRA_ELEMENT_SEPARATOR`.** **Nothing is
22
+ re-framed: this is a report, not a repair, and no existing warning was suppressed or narrowed.**
23
+
24
+ `detectDelimiters` verifies the element separator at all 16 fixed 005010 byte positions, so the
25
+ ISA split can never come out SHORT. It was never bounded from ABOVE. An element value carrying
26
+ that same byte splits again, so that element comes back a **prefix** and everything after it is
27
+ **displaced**. **🛑 How far is NOT derivable from `isa.elements`, and no arithmetic on it works.**
28
+ More than one element can carry an extra separator, and an element sitting between two of them is
29
+ displaced less than one sitting after both; `isa.raw` plus the ISA's fixed widths is the only route
30
+ back. Measured on one conformant interchange, planting the element separator inside each of the 16
31
+ fixed elements in turn:
32
+
33
+ ```text
34
+ planted in split parts warnings through 0.0.16
35
+ ISA-01..10 18 X12_PRE_005010, X12_CONTROL_NUMBER_MISMATCH
36
+ ISA-11 - X12_INVALID_DELIMITERS (Tier-3, thrown before any of this)
37
+ ISA-12 18 X12_PRE_005010, X12_CONTROL_NUMBER_MISMATCH
38
+ ISA-13 18 X12_CONTROL_NUMBER_MISMATCH
39
+ ISA-14 18 (none)
40
+ ISA-15 18 (none)
41
+ ISA-16 - X12_INVALID_DELIMITERS (Tier-3, thrown before any of this)
42
+ ```
43
+
44
+ ISA-11 and ISA-16 **are** the in-band repetition and component separator declarations, so planting
45
+ the element separator there collides with them and the Tier-3 refusal reaches it first. That is a
46
+ boundary of the probe, not a claim that those two elements are special.
47
+
48
+ **What the displaced reads cost, on the rows that had a warning as much as on the rows that had
49
+ none.** On the `ISA-01..10` and `ISA-12` rows the interchange declares `00501` at ISA-12's own
50
+ fixed offset and `X12_PRE_005010` fires anyway, because `elements[12]` has become the repetition
51
+ separator; `elements[13]`, the interchange control number and the reassociation key, answers
52
+ `"00501"`; and `elements[15]`, the test/production usage indicator, answers `"0"` on a document
53
+ whose ISA-15 says `P`. On the `ISA-13` row the control number is a prefix. **The `ISA-14` and
54
+ `ISA-15` rows carried `warnings: []`** while the usage indicator read empty.
55
+
56
+ **The route back is `isa.raw`**, which still carries all 106 bytes verbatim, and the ISA's fixed
57
+ widths make the transmitted span of any element recoverable from it. Recovering it is **your**
58
+ decision, not this library's: a byte that is both an element's content under those fixed widths and
59
+ the separator the same segment declares in-band has two readings, the interchange is not
60
+ 005010-conformant either way, and nothing anyone here has read says which reading to take. So the
61
+ parser reports that the header did not frame and leaves `isa.elements` exactly as the split
62
+ produced it.
63
+
64
+ **When this warning is present, treat every other ISA-derived diagnostic on that interchange as
65
+ provisional.** `parseX12` raises it ahead of the ones it raises after it for that reason, which
66
+ also means `{ strict: true }` escalates on it rather than on the displaced-value warning that
67
+ follows. **🩺 Read that as a statement about `ix.warnings` and `onWarning`, and about nothing
68
+ else.** `serializeX12(ix, { specClean: true })` reconciles ISA-13 against IEA-02 off
69
+ `isa.elements[13]` with no arity awareness and **never raises this code at all**, so on that
70
+ channel a lone `X12_CONTROL_NUMBER_MISMATCH` can be a displaced read of a control number that in
71
+ fact matches byte for byte at its fixed span, and **the absence of this warning there is not
72
+ evidence the header framed**. Pre-existing, disclosed, not fixed here.
73
+
74
+ **The emit side is unchanged and is NOT guarded by this.** The fixed-width ISA slots go through
75
+ `pad` / `padControl` and never through the caller escaper, so `buildInterchange` still writes a
76
+ caller value carrying the element separator straight onto the wire. What changed is that
77
+ `buildInterchange` returns `parseX12` of the bytes it just wrote, so the report now reaches that
78
+ path too: `interchangeControlNumber: "0000*0001"` used to come back with ISA-13 reading `"0000"`,
79
+ ISA-15 reading `"0"` instead of `"P"`, IEA-02 displaced the same way so the control-number
80
+ reconciliation **agreed with the misreading**, and `warnings: []`. Refusing such a value on emit is
81
+ a decision about the build side and is not taken here.
82
+
19
83
  - **🩺 `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:
84
+ TA1-02 / TA1-03 / TA1-04 / TA1-05 is REFUSED on emit. Both are behaviour changes.**
85
+ They are one change because they are the two ends of the same disagreement:
22
86
  this package's emit half releases a TA1 element and its read half decoded the escape rather than
23
87
  the value.
24
88
 
25
- **The read half.** `X12-TA1-EMIT-NOT-RELEASE-AWARE` made `buildTA1` release all five caller
89
+ **The read half.** An earlier release made `buildTA1` release all five caller
26
90
  elements, and `parseTA1` kept reading `elements` verbatim, so a round trip through this package's
27
91
  own halves was not an identity for any value carrying a delimiter or the release character.
28
92
  Measured at `0.0.15`, over `parseX12` + `parseTA1` of what `buildTA1` had just emitted:
@@ -69,12 +133,12 @@ model.
69
133
  first, so an `ackCode: "A"` with an empty note still reports `X12_TA1_ACCEPT_WITH_NOTE`; TA1-01
70
134
  still draws the control-number refusal; and every wrong-TYPED element still draws the escape
71
135
  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.
136
+ refused before this change is refused differently by it.**
137
+ - **No census of other builders' required elements is published here.** This change measured TA1.
74
138
 
75
139
  - **🩺 `implementationConventionReference` is POST-`?`-unescape as of this release, in every typed
76
140
  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:
141
+ release escape.** `tx.st.elements` is the ST segment as framed:
78
142
  post-element-split and PRE-unescape. Five public
79
143
  readers were handing one of those strings straight back on the model - `get837Claims`,
80
144
  `get277Status`, `get277CADisposition`, `get278Request` and `get278Response` - so a sender that
@@ -119,8 +183,8 @@ model.
119
183
  `unescapeRelease` to `submission.implementationConventionReference` yourself, drop that call.
120
184
 
121
185
  - **🩺 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
186
+ FABRICATED, and that is a behaviour change for any caller passing one.**
187
+ Every builder that assembles an ISA zero-pads its control
124
188
  number to the nine characters ASC X12 .5 fixes ISA-13 at. `padControl("1", 9)` answering
125
189
  `"000000001"` is the point of that; `padControl("", 9)` answering `"000000000"` was not, and
126
190
  nothing stood in front of it, so `interchangeControlNumber: ""` produced a frozen, well-formed
@@ -173,7 +237,7 @@ model.
173
237
  `X12_ACK_INVALID_SPEC` now. If you branch on a specific builder error code, that pairing moved.
174
238
 
175
239
  - **🩺 Which `ST-03` implementation-convention references resolve to an 837 variant CHANGED in this
176
- release, and some already-published files therefore decode differently** (`X12-VARIANT-ICR-UNGROUNDED`).
240
+ release, and some already-published files therefore decode differently.**
177
241
  Through `0.0.13` `get837Claims` recognised exactly three references: `005010X222A2`, `005010X223A3`
178
242
  and `005010X224A2`. **That set contained none of the identifiers HIPAA adopts at 45 CFR 162.1102**
179
243
  (`005010X222`, `005010X223` + `005010X223A1`, `005010X224` + `005010X224A1`), **and it was missing
@@ -209,8 +273,8 @@ model.
209
273
  - **The set is not claimed exhaustive and no count of it is published**, here or in any warning
210
274
  message. Both variant messages named the three old keys literally and were wrong the moment the
211
275
  table was corrected, so neither enumerates the set any more.
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
276
+ - **🩺 CLOSED, in the change after this one: the emit side takes a caller override.**
277
+ `build837P` / `build837I` / `build837D` still DEFAULT to
214
278
  `005010X222A2` / `005010X223A3` / `005010X224A2` in ST-03 and GS-08, and
215
279
  **`Build837EnvelopeSpec.implementationConventionReference` now states another** - one value,
216
280
  both elements. Two of the defaults are not what the companion guides above require, so a partner
@@ -237,8 +301,7 @@ model.
237
301
  ST-03.
238
302
 
239
303
  - **🩺 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`).
304
+ element, and that is a SYMMETRIC behaviour change on already-published decoding.**
242
305
  Through `0.0.14` the envelope segments' element splitter was a plain `String.prototype.split`, so a
243
306
  released element separator (`?*`) still ended the element and SHIFTED every element after it down a
244
307
  slot. It was a property of the whole envelope segment and never of one element, so the element a
@@ -293,7 +356,7 @@ model.
293
356
  `responsibleAgencyCode`) at this release. A `groupDate` of `"2026060?"` emitted
294
357
  `GS*HC*SENDER*RECEIVER*2026060?*1200*1*X*005010X222A1`, which its own return value read as nine
295
358
  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
359
+ `X12_CONTROL_NUMBER_MISMATCH`. **Closed by a later release** - that
297
360
  entry is at the top of this section and it is the one that describes the current tree.
298
361
 
299
362
  - **An envelope element ending in a literal `?` is a dangling release character and is NOT warned.**
@@ -329,8 +392,8 @@ model.
329
392
  at `0.0.14` it released GS-02 on emit and then answered GS-08 as `"X"` from its own return value.
330
393
 
331
394
  - **🩺 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
395
+ where it used to come back as ONE element with an id of `(non-spec)`.**
396
+ `detectDelimiters` reads the element separator
334
397
  positionally out of ISA byte 4 and rejects only control characters, whitespace and a non-distinct
335
398
  set, so a sender may declare `?` there, and `buildInterchange` accepted `elementSeparator: "?"`
336
399
  from a caller when this was measured (it refuses now - see the emit-side entry below).
@@ -367,8 +430,8 @@ model.
367
430
  of them. Splitting those two roles literally would re-frame that as two empty components, so it
368
431
  would stop reading a value this library itself wrote. **That reason survives the emit-side
369
432
  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
433
+ - **🟢 CLOSED, in the change after this one, and WIDER than it was filed:
434
+ every builder REFUSES a delimiter set in which any of
372
435
  the four roles is `?`.** The entry as filed named the element separator and one mechanism - a
373
436
  caller VALUE the escape cannot protect, because the protecting `?` is itself the separator. Two
374
437
  things were measured wrong about that:
@@ -433,7 +496,7 @@ model.
433
496
  terminator.** `findUnescapedTerminator` guards its own role only, so with `?` as the element
434
497
  separator a segment that ends in an EMPTY LAST ELEMENT puts a `?` immediately before the
435
498
  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
499
+ as ONE segment and raises `X12_MISSING_SE`. This change does not touch framing. But **the READ of
437
500
  that merged blob did move, so do not take "framing is untouched" as "nothing about this residual
438
501
  moved"**: at `0.0.15` the merge produced one `(non-spec)` element no walker looked at, and here
439
502
  it frames, so `~SE` and the SE's own control number land in `PER`'s communication-number slots.
@@ -446,8 +509,8 @@ model.
446
509
  gives: it is fixed-width, which is what lets the delimiters be recovered from it at all.
447
510
 
448
511
  - **🩺 `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
512
+ reports the values you passed, and the bytes it writes for such a value CHANGED.**
513
+ Through `0.0.15` it mapped its release escaper over
451
514
  GS-01, GS-02, GS-03, GS-06 and GS-08 and wrote `groupDate` (GS-04), `groupTime` (GS-05) and
452
515
  `responsibleAgencyCode` (GS-07) raw. It returns `parseX12` of the bytes it just wrote, so a value
453
516
  carrying an active delimiter in one of those three took a slot of its own and shifted every element
@@ -508,8 +571,8 @@ model.
508
571
  active delimiter is still not safe anywhere, because that is what a delimiter is.
509
572
 
510
573
  - **🩺 `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
574
+ library emits no longer reads back as a Reject, and the bytes it writes for such a value CHANGED.**
575
+ Through `0.0.14` `buildTA1` joined the five values with the
513
576
  element separator and escaped none of them, so a value carrying an active delimiter took a slot of
514
577
  its own and shifted every element after it down one. TA1-04 is the disposition and TA1-05 the note,
515
578
  and `parseTA1` narrows an out-of-enum TA1-04 to `R`. Measured with `parseX12` + `parseTA1` over
@@ -560,19 +623,19 @@ model.
560
623
  No total is published: that is what was measured, not a closed account. (`getSegmentValue`
561
624
  takes an `X12Segment` and `Ta1Segment` carries no `id`, so add one to read a TA1 through it.)
562
625
  **🩺 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.
626
+ reworded** - a later release made those fields post-unescape, so it is measured false.
564
627
  - **A caller who was hand-rolling the escape** (the remedy this file named while the defect was
565
628
  open) regresses on both kinds of surface: `"00000001??"` in, `TA1*00000001????*…` out, and
566
629
  `getSegmentValue` answering `"00000001??"` where it answered `"00000001?"`. The framing and the
567
630
  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.**
631
+ - **An EMPTY control number was not refused by this change, and is refused by the NEXT one.**
569
632
  `escapeRelease` early-returns on `""` and `buildTA1` carried no required-field guard, so
570
633
  `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
634
+ every earlier release. A later release closed it across every builder; see
572
635
  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
636
+ - **🩺 The READ half did not move IN THIS CHANGE, and a later release is the one that moved
574
637
  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
638
+ `"00000001?*260601"` before the EMIT half moved and as `"00000001??"` after it; the clause that stood
576
639
  here telling callers to apply `unescapeRelease` themselves is **deleted, not reworded**, because
577
640
  the library now does it.
578
641
  - **The release is scoped to the delimiter set the CALLER states.** `BuildTA1Options` gained
@@ -593,12 +656,12 @@ model.
593
656
  `X12_TA1_ACCEPT_WITH_NOTE` and still runs first.
594
657
  - **What this does NOT close.** `buildTA1` still uses no segment joiner, so its refusal names the
595
658
  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
659
+ release, which a later one then closed; and an unescaped active
597
660
  delimiter is still not safe anywhere, because that is what a delimiter is.
598
661
 
599
662
  - **🩺 BREAKING for `build277` callers: a 277 service line now REQUIRES `unitsOfService`, because
600
- SVC-07 is a required element in `005010X212` and this library was not emitting it at all**
601
- (`X12-277-SVC07-NOT-DECODED`). Through the release before this one `get277Status` read SVC-01
663
+ SVC-07 is a required element in `005010X212` and this library was not emitting it at all.**
664
+ Through the release before this one `get277Status` read SVC-01
602
665
  through SVC-04 and stopped, and `build277` emitted exactly those four, so **every X212 277 this
603
666
  library produced with a Loop 2220 service line was short a required element** and every 277 it read
604
667
  silently discarded the submitted units. Both sides are fixed: `X12ServiceLineStatus.unitsOfService`
@@ -632,7 +695,7 @@ model.
632
695
  string** - at base the same spec emitted a bare `SVC~`, because the SVC-07 slot did not exist. `SVC-03` (Line Item Payment
633
696
  Amount) is usage **R** in X212 and usage **N** in X214 and is optional in both builders. And the
634
697
  read side is unchanged and still lenient: an X212 277 arriving with no SVC-07 raises **no
635
- warning**, because that would need a new Tier-2 registry code and the defect this slice closes is
698
+ warning**, because that would need a new Tier-2 registry code and the defect this change closes is
636
699
  on the emit side. All of it is pre-existing and reproduces at `e3cdf49`; widening the guard would
637
700
  have turned this into that audit, which is its own item.
638
701
 
@@ -1178,7 +1241,7 @@ Code"`, `SVC05 / 380 / "Units of Service Paid Count"`, `SVC06 / C003`,
1178
1241
 
1179
1242
  **`undefined` on either quantity still means "not decoded", not "absent".** The element may have
1180
1243
  been missing, or present and unparseable as a decimal; the two share one `undefined` on the model.
1181
- They no longer share a silence: as of `X12-QUANTITY-SILENT-DEFAULTS` the unparseable case raises
1244
+ They no longer share a silence: as of an earlier release the unparseable case raises
1182
1245
  `X12_UNPARSEABLE_DECIMAL` at that `position.elementIndex` and the absent case raises nothing, so a
1183
1246
  warning at the element is what tells them apart. The verbatim element is still on `tx.segments`.
1184
1247
 
@@ -1188,7 +1251,7 @@ Code"`, `SVC05 / 380 / "Units of Service Paid Count"`, `SVC06 / C003`,
1188
1251
  no warning. Re-emitting from the model with this release produces conformant bytes.
1189
1252
 
1190
1253
  **The 277's SVC-07 is a different element in a different TR3 and is now decoded and emitted in its
1191
- own right** (`X12-277-SVC07-NOT-DECODED`, below). Do not read either field off the other.
1254
+ own right** (see the 277 entry below). Do not read either field off the other.
1192
1255
 
1193
1256
  - **Bundled code-list snapshots are pre-launch initial subsets, not the full WPC-published lists.**
1194
1257
  CARC, RARC, Claim-Status-Category (CSCC), Claim-Status (CSC), service-type, CLP-status, and
@@ -1445,7 +1508,7 @@ N-char spec limit` refusal, one per emitting module, where the branch fires **be
1445
1508
  **Read the "through a builder's segment joiner" qualifier literally.** `buildTA1` does not use one,
1446
1509
  and is therefore **not covered by the segment guard**. Through `0.0.14` that meant nothing checked
1447
1510
  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
1511
+ silently (`TA1**250101*1200*A*000`); a later release closed that by routing them
1449
1512
  through the escape helper, which type-checks first and refuses with `AckBuildError` /
1450
1513
  `X12_ACK_INVALID_SPEC`. What being outside the segment guard still costs is the **slot** in the
1451
1514
  refusal message: it names `buildTA1`, never `TA1-01`.
@@ -1617,5 +1680,5 @@ the maintainers have not reviewed.
1617
1680
 
1618
1681
  ---
1619
1682
 
1620
- For the phase-by-phase surface and the exact fields each helper decodes, see the package's
1621
- [`CLAUDE.md`](./CLAUDE.md) status section and the [Cookbook](./docs-content/cookbook.md).
1683
+ For the per-transaction read and emit surface, and the exact fields each helper decodes, see
1684
+ [`README.md`](./README.md) and the Cookbook on the documentation site.
package/dist/index.cjs CHANGED
@@ -121,6 +121,7 @@ function detectDelimiters(raw) {
121
121
  var WARNING_CODES = {
122
122
  X12_CONTROL_NUMBER_MISMATCH: "X12_CONTROL_NUMBER_MISMATCH",
123
123
  X12_PRE_005010: "X12_PRE_005010",
124
+ X12_ISA_EXTRA_ELEMENT_SEPARATOR: "X12_ISA_EXTRA_ELEMENT_SEPARATOR",
124
125
  X12_GROUP_COUNT_MISMATCH: "X12_GROUP_COUNT_MISMATCH",
125
126
  X12_TRANSACTION_COUNT_MISMATCH: "X12_TRANSACTION_COUNT_MISMATCH",
126
127
  X12_SEGMENT_COUNT_MISMATCH: "X12_SEGMENT_COUNT_MISMATCH",
@@ -195,7 +196,8 @@ var WARNING_MESSAGES = {
195
196
  X12_CONTROL_NUMBER_MISMATCH_INTERCHANGE: "Control number mismatch: ISA-13 (header) and IEA-02 (trailer) disagree. Both values are preserved verbatim on the model; neither is echoed here.",
196
197
  X12_CONTROL_NUMBER_MISMATCH_GROUP: "Control number mismatch: GS-06 (header) and GE-02 (trailer) disagree. Both values are preserved verbatim on the model; neither is echoed here.",
197
198
  X12_CONTROL_NUMBER_MISMATCH_TRANSACTION: "Control number mismatch: ST-02 (header) and SE-02 (trailer) disagree. Both values are preserved verbatim on the model; neither is echoed here.",
198
- X12_PRE_005010: 'ISA-12 declares a version other than the HIPAA baseline "00501", so the input may diverge from 005010 semantics. The declared version is preserved verbatim on the model.',
199
+ X12_PRE_005010: 'The twelfth element of the ISA split does not read "00501", the HIPAA baseline interchange control version number, so the input may diverge from 005010 semantics. Whether that element is ISA-12 is NOT established here: where `X12_ISA_EXTRA_ELEMENT_SEPARATOR` is also present on this interchange the header did not split into `ISA` plus 16 elements, so the element read may be a displaced one. All 106 ISA bytes are preserved verbatim on `isa.raw`, which with the ISA fixed widths is the route back. Nothing is echoed here.',
200
+ X12_ISA_EXTRA_ELEMENT_SEPARATOR: "The ISA element area carries at least one element separator beyond the 16 sitting at their fixed 005010 byte positions, so the header does not split into `ISA` plus 16 elements. `isa.elements` is that split as it came out: an element containing an extra separator comes back a prefix and everything after it is displaced. How far is NOT derivable from `isa.elements`, so any other ISA-derived diagnostic on this interchange may be reporting a displaced value. All 106 bytes are preserved verbatim on `isa.raw`, which with the ISA's fixed widths is the route back. Which reading is correct - the byte as data under the ISA's fixed widths, or as a separator - is NOT decided here, and nothing is re-framed.",
199
201
  X12_GROUP_COUNT_MISMATCH: "IEA-01 does not equal the number of GS..GE groups actually present in the interchange. The declared count is preserved verbatim on the model and is NEVER silently corrected.",
200
202
  X12_TRANSACTION_COUNT_MISMATCH: "GE-01 does not equal the number of ST..SE transaction sets actually present in the group. The declared count is preserved verbatim on the model and is NEVER silently corrected.",
201
203
  X12_SEGMENT_COUNT_MISMATCH: "SE-01 does not equal the number of segments actually present in the transaction set it closes. The declared count is preserved verbatim and is corrected only on `serializeX12(ix, { specClean: true, recomputeCounts: true })`.",
@@ -284,6 +286,13 @@ function pre005010(position) {
284
286
  position
285
287
  };
286
288
  }
289
+ function isaExtraElementSeparator(position) {
290
+ return {
291
+ code: WARNING_CODES.X12_ISA_EXTRA_ELEMENT_SEPARATOR,
292
+ message: WARNING_MESSAGES.X12_ISA_EXTRA_ELEMENT_SEPARATOR,
293
+ position
294
+ };
295
+ }
287
296
  function groupCountMismatch(position) {
288
297
  return {
289
298
  code: WARNING_CODES.X12_GROUP_COUNT_MISMATCH,
@@ -1048,6 +1057,7 @@ function collectElementValues(seg, start, end, delimiters) {
1048
1057
  }
1049
1058
 
1050
1059
  // src/parser/envelope.ts
1060
+ var ISA_DECODED_PART_COUNT = 17;
1051
1061
  function decodeIsa(raw, delimiters) {
1052
1062
  const isaRaw = raw.slice(0, ISA_MIN_LENGTH);
1053
1063
  const isaHead = isaRaw.slice(0, ISA_MIN_LENGTH - 1);
@@ -1114,6 +1124,9 @@ function decodeEnvelope(raw, delimiters) {
1114
1124
  warnings.push(w);
1115
1125
  };
1116
1126
  const isa = decodeIsa(raw, delimiters);
1127
+ if (isa.elements.length !== ISA_DECODED_PART_COUNT) {
1128
+ warnings.push(isaExtraElementSeparator({ segmentIndex: 0, interchangeIndex: 0 }));
1129
+ }
1117
1130
  const isa12 = el(isa.elements, 12);
1118
1131
  if (isa12 !== "00501") {
1119
1132
  warnings.push(pre005010({ segmentIndex: 0, interchangeIndex: 0, elementIndex: 12 }));
@@ -10723,7 +10736,7 @@ var profiles = Object.freeze({
10723
10736
  });
10724
10737
 
10725
10738
  // src/index.ts
10726
- var VERSION = "0.0.16";
10739
+ var VERSION = "0.0.18";
10727
10740
 
10728
10741
  exports.ACK_BUILD_ERROR_CODES = ACK_BUILD_ERROR_CODES;
10729
10742
  exports.ALL_WARNING_MESSAGES = ALL_WARNING_MESSAGES;
@@ -10885,6 +10898,7 @@ exports.isAcceptDisposition = isAcceptDisposition;
10885
10898
  exports.isClaimAdjustmentGroupCode = isClaimAdjustmentGroupCode;
10886
10899
  exports.isDiagnosisQualifier = isDiagnosisQualifier;
10887
10900
  exports.isProcedureQualifier = isProcedureQualifier;
10901
+ exports.isaExtraElementSeparator = isaExtraElementSeparator;
10888
10902
  exports.lookupCarc = lookupCarc;
10889
10903
  exports.lookupClaimStatus = lookupClaimStatus;
10890
10904
  exports.lookupClaimStatusCategory = lookupClaimStatusCategory;