@cosyte/x12 0.0.17 → 0.1.0

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.
@@ -18,7 +18,7 @@ model.
18
18
 
19
19
  - **🩺 An ISA element carrying the element separator makes the header split into more than
20
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`** (`X12-ISA-ELEMENT-ARITY`). **Nothing is
21
+ `X12_ISA_EXTRA_ELEMENT_SEPARATOR`.** **Nothing is
22
22
  re-framed: this is a report, not a repair, and no existing warning was suppressed or narrowed.**
23
23
 
24
24
  `detectDelimiters` verifies the element separator at all 16 fixed 005010 byte positions, so the
@@ -81,12 +81,12 @@ model.
81
81
  a decision about the build side and is not taken here.
82
82
 
83
83
  - **🩺 `parseTA1`'s five decoded fields are POST-`?`-unescape as of this release, and an EMPTY
84
- TA1-02 / TA1-03 / TA1-04 / TA1-05 is REFUSED on emit. Both are behaviour changes**
85
- (`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:
86
86
  this package's emit half releases a TA1 element and its read half decoded the escape rather than
87
87
  the value.
88
88
 
89
- **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
90
90
  elements, and `parseTA1` kept reading `elements` verbatim, so a round trip through this package's
91
91
  own halves was not an identity for any value carrying a delimiter or the release character.
92
92
  Measured at `0.0.15`, over `parseX12` + `parseTA1` of what `buildTA1` had just emitted:
@@ -133,12 +133,40 @@ model.
133
133
  first, so an `ackCode: "A"` with an empty note still reports `X12_TA1_ACCEPT_WITH_NOTE`; TA1-01
134
134
  still draws the control-number refusal; and every wrong-TYPED element still draws the escape
135
135
  helper's refusal, because all five escapes run before any emptiness test. **No spec that was
136
- refused before this slice is refused differently by it.**
137
- - **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.
138
+
139
+ - **🩺 Every typed reader checks the implementation guide a transaction set declares against the
140
+ guides that reader implements, and WARNS rather than refuses where they differ.**
141
+ `X12_GUIDE_NOT_IMPLEMENTED` is raised where the declared guide is outside the reader's set and
142
+ `X12_GUIDE_NOT_DECLARED` where nothing is declared, both anchored at the ST. The reading is
143
+ decoded and returned either way, against the element positions of the guide the reader
144
+ implements, so on a warned document a value can sit at a position the declared guide defines
145
+ differently. What the check keys on, exactly:
146
+ - **The declaration is `ST-03` decoded, then `GS-08` decoded.** `ST-03` is read post-`?`-unescape,
147
+ exactly as `implementationConventionReference` is published. Only where `ST-03` is absent or
148
+ decodes to `""` is GS-08 of the enclosing functional group read, decoded the same way; a parsed
149
+ transaction set carries its group's GS header on `tx.gs` for this, and one assembled by hand
150
+ without it is read as GS-08 absent. Nothing is trimmed, case-folded or prefix-matched, so a
151
+ whitespace-only or lower-cased declaration is reported as not implemented.
152
+ - **🛑 `ST-03` and `GS-08` are NOT reconciled.** Where `ST-03` is non-empty it alone decides and
153
+ GS-08 is never read, so a document whose two declarations disagree is judged on `ST-03` and
154
+ nothing reports the disagreement.
155
+ - **The implemented set is `X12_TR3_CONFORMANCE`**, each of the reader's rows contributing its
156
+ `tr3` and every `cfrAdopted` entry. **The 837 reader also recognises every identifier its
157
+ variant table resolves**, which includes the companion-guide identifiers production claims carry,
158
+ so a conformant claim declaring one is not accused. A 278 declaring `005010X216`, the 278
159
+ notification guide, is warned on both 278 readers.
160
+ - **What the check decides is a warning, plus one label.** A 277 whose declared guide is outside
161
+ `get277Status`'s set, or absent, reads `transactionType: "unrecognized-guide"`. Everything else
162
+ that keys on `ST-03` - the 837 variant, the choice between `claim-status` and
163
+ `claim-acknowledgment`, and `get277CADisposition`'s admission - still keys on the raw text and
164
+ did not move, so the check and those decisions can read the same `ST-03` differently where a
165
+ release escape is involved (see the next entry).
138
166
 
139
167
  - **🩺 `implementationConventionReference` is POST-`?`-unescape as of this release, in every typed
140
168
  reader that publishes it, and that is a behaviour change on documents whose `ST-03` carries a
141
- release escape** (`X12-ST03-READ-NOT-RELEASE-AWARE`). `tx.st.elements` is the ST segment as framed:
169
+ release escape.** `tx.st.elements` is the ST segment as framed:
142
170
  post-element-split and PRE-unescape. Five public
143
171
  readers were handing one of those strings straight back on the model - `get837Claims`,
144
172
  `get277Status`, `get277CADisposition`, `get278Request` and `get278Response` - so a sender that
@@ -149,8 +177,8 @@ model.
149
177
  publishes `A*B`, `A?:B` publishes `A:B`, `A?~B` publishes `A~B`, `A?^B` publishes `A^B`. Every one
150
178
  of those published the framed bytes before.
151
179
  - **🛑 What decides an outcome did NOT move, deliberately.** The 837 variant lookup, the 277 /
152
- 277CA `transactionType` discriminator and `get277CADisposition`'s admission gate all still key on
153
- the RAW element text, so no document changes variant, discriminator or admission because of this
180
+ 277CA `transactionType` choice between its two claim labels and `get277CADisposition`'s
181
+ admission gate all still key on the RAW element text, so no document changes variant, discriminator or admission because of this
154
182
  entry. That matters because the two can differ: with `componentSeparator: "X"` - a letter is an
155
183
  admissible delimiter - an `ST-03` framed as `005010?X222A1` decodes to `005010X222A1`, an
156
184
  identifier the variant table holds. Keying on the decoded text would make the declaration beat
@@ -183,8 +211,8 @@ model.
183
211
  `unescapeRelease` to `submission.implementationConventionReference` yourself, drop that call.
184
212
 
185
213
  - **🩺 An EMPTY control number is REFUSED on emit as of this release, where it used to be
186
- FABRICATED, and that is a behaviour change for any caller passing one**
187
- (`X12-EMPTY-CONTROL-NUMBER-FABRICATED`). Every builder that assembles an ISA zero-pads its control
214
+ FABRICATED, and that is a behaviour change for any caller passing one.**
215
+ Every builder that assembles an ISA zero-pads its control
188
216
  number to the nine characters ASC X12 .5 fixes ISA-13 at. `padControl("1", 9)` answering
189
217
  `"000000001"` is the point of that; `padControl("", 9)` answering `"000000000"` was not, and
190
218
  nothing stood in front of it, so `interchangeControlNumber: ""` produced a frozen, well-formed
@@ -237,7 +265,7 @@ model.
237
265
  `X12_ACK_INVALID_SPEC` now. If you branch on a specific builder error code, that pairing moved.
238
266
 
239
267
  - **🩺 Which `ST-03` implementation-convention references resolve to an 837 variant CHANGED in this
240
- release, and some already-published files therefore decode differently** (`X12-VARIANT-ICR-UNGROUNDED`).
268
+ release, and some already-published files therefore decode differently.**
241
269
  Through `0.0.13` `get837Claims` recognised exactly three references: `005010X222A2`, `005010X223A3`
242
270
  and `005010X224A2`. **That set contained none of the identifiers HIPAA adopts at 45 CFR 162.1102**
243
271
  (`005010X222`, `005010X223` + `005010X223A1`, `005010X224` + `005010X224A1`), **and it was missing
@@ -273,8 +301,8 @@ model.
273
301
  - **The set is not claimed exhaustive and no count of it is published**, here or in any warning
274
302
  message. Both variant messages named the three old keys literally and were wrong the moment the
275
303
  table was corrected, so neither enumerates the set any more.
276
- - **🩺 CLOSED, in the slice after this one: the emit side takes a caller override**
277
- (`X12-837-EMIT-IDENTIFIER-FIXED`). `build837P` / `build837I` / `build837D` still DEFAULT to
304
+ - **🩺 CLOSED, in the change after this one: the emit side takes a caller override.**
305
+ `build837P` / `build837I` / `build837D` still DEFAULT to
278
306
  `005010X222A2` / `005010X223A3` / `005010X224A2` in ST-03 and GS-08, and
279
307
  **`Build837EnvelopeSpec.implementationConventionReference` now states another** - one value,
280
308
  both elements. Two of the defaults are not what the companion guides above require, so a partner
@@ -301,8 +329,7 @@ model.
301
329
  ST-03.
302
330
 
303
331
  - **🩺 A `?` immediately before the element separator inside an envelope segment now frames as ONE
304
- element, and that is a SYMMETRIC behaviour change on already-published decoding**
305
- (`X12-ENVELOPE-SPLITTER-NOT-RELEASE-AWARE`).
332
+ element, and that is a SYMMETRIC behaviour change on already-published decoding.**
306
333
  Through `0.0.14` the envelope segments' element splitter was a plain `String.prototype.split`, so a
307
334
  released element separator (`?*`) still ended the element and SHIFTED every element after it down a
308
335
  slot. It was a property of the whole envelope segment and never of one element, so the element a
@@ -357,7 +384,7 @@ model.
357
384
  `responsibleAgencyCode`) at this release. A `groupDate` of `"2026060?"` emitted
358
385
  `GS*HC*SENDER*RECEIVER*2026060?*1200*1*X*005010X222A1`, which its own return value read as nine
359
386
  elements with GS-08 intact through `0.0.14` and read as **eight** at `0.0.15`, GS-08 gone, plus
360
- `X12_CONTROL_NUMBER_MISMATCH`. **Closed by `X12-INTERCHANGE-GS-EMIT-NOT-RELEASE-AWARE`** - that
387
+ `X12_CONTROL_NUMBER_MISMATCH`. **Closed by a later release** - that
361
388
  entry is at the top of this section and it is the one that describes the current tree.
362
389
 
363
390
  - **An envelope element ending in a literal `?` is a dangling release character and is NOT warned.**
@@ -393,8 +420,8 @@ model.
393
420
  at `0.0.14` it released GS-02 on emit and then answered GS-08 as `"X"` from its own return value.
394
421
 
395
422
  - **🩺 A BODY segment in an interchange whose ELEMENT SEPARATOR is `?` now frames its elements,
396
- where it used to come back as ONE element with an id of `(non-spec)`**
397
- (`X12-BODY-DEGENERATE-RELEASE-SEPARATOR`). `detectDelimiters` reads the element separator
423
+ where it used to come back as ONE element with an id of `(non-spec)`.**
424
+ `detectDelimiters` reads the element separator
398
425
  positionally out of ISA byte 4 and rejects only control characters, whitespace and a non-distinct
399
426
  set, so a sender may declare `?` there, and `buildInterchange` accepted `elementSeparator: "?"`
400
427
  from a caller when this was measured (it refuses now - see the emit-side entry below).
@@ -431,8 +458,8 @@ model.
431
458
  of them. Splitting those two roles literally would re-frame that as two empty components, so it
432
459
  would stop reading a value this library itself wrote. **That reason survives the emit-side
433
460
  refusal below - those documents exist**, which is why the read side did not move with it.
434
- - **🟢 CLOSED, in the slice after this one, and WIDER than it was filed
435
- (`X12-EMIT-DEGENERATE-RELEASE-DELIMITER`): every builder REFUSES a delimiter set in which any of
461
+ - **🟢 CLOSED, in the change after this one, and WIDER than it was filed:
462
+ every builder REFUSES a delimiter set in which any of
436
463
  the four roles is `?`.** The entry as filed named the element separator and one mechanism - a
437
464
  caller VALUE the escape cannot protect, because the protecting `?` is itself the separator. Two
438
465
  things were measured wrong about that:
@@ -497,7 +524,7 @@ model.
497
524
  terminator.** `findUnescapedTerminator` guards its own role only, so with `?` as the element
498
525
  separator a segment that ends in an EMPTY LAST ELEMENT puts a `?` immediately before the
499
526
  terminator and the scanner reads it as an escape: `PER?IC?NAME?TE?5551234?EX?~SE?3?0001~` frames
500
- as ONE segment and raises `X12_MISSING_SE`. This slice does not touch framing. But **the READ of
527
+ as ONE segment and raises `X12_MISSING_SE`. This change does not touch framing. But **the READ of
501
528
  that merged blob did move, so do not take "framing is untouched" as "nothing about this residual
502
529
  moved"**: at `0.0.15` the merge produced one `(non-spec)` element no walker looked at, and here
503
530
  it frames, so `~SE` and the SE's own control number land in `PER`'s communication-number slots.
@@ -510,8 +537,8 @@ model.
510
537
  gives: it is fixed-width, which is what lets the delimiters be recovered from it at all.
511
538
 
512
539
  - **🩺 `buildInterchange` now RELEASES GS-04, GS-05 and GS-07, so the interchange it hands back
513
- reports the values you passed, and the bytes it writes for such a value CHANGED**
514
- (`X12-INTERCHANGE-GS-EMIT-NOT-RELEASE-AWARE`). Through `0.0.15` it mapped its release escaper over
540
+ reports the values you passed, and the bytes it writes for such a value CHANGED.**
541
+ Through `0.0.15` it mapped its release escaper over
515
542
  GS-01, GS-02, GS-03, GS-06 and GS-08 and wrote `groupDate` (GS-04), `groupTime` (GS-05) and
516
543
  `responsibleAgencyCode` (GS-07) raw. It returns `parseX12` of the bytes it just wrote, so a value
517
544
  carrying an active delimiter in one of those three took a slot of its own and shifted every element
@@ -572,8 +599,8 @@ model.
572
599
  active delimiter is still not safe anywhere, because that is what a delimiter is.
573
600
 
574
601
  - **🩺 `buildTA1` now RELEASES its five caller-supplied elements, so an Accept acknowledgment this
575
- library emits no longer reads back as a Reject, and the bytes it writes for such a value CHANGED**
576
- (`X12-TA1-EMIT-NOT-RELEASE-AWARE`). Through `0.0.14` `buildTA1` joined the five values with the
602
+ library emits no longer reads back as a Reject, and the bytes it writes for such a value CHANGED.**
603
+ Through `0.0.14` `buildTA1` joined the five values with the
577
604
  element separator and escaped none of them, so a value carrying an active delimiter took a slot of
578
605
  its own and shifted every element after it down one. TA1-04 is the disposition and TA1-05 the note,
579
606
  and `parseTA1` narrows an out-of-enum TA1-04 to `R`. Measured with `parseX12` + `parseTA1` over
@@ -624,19 +651,19 @@ model.
624
651
  No total is published: that is what was measured, not a closed account. (`getSegmentValue`
625
652
  takes an `X12Segment` and `Ta1Segment` carries no `id`, so add one to read a TA1 through it.)
626
653
  **🩺 A clause naming `parseTA1`'s fields as a third such surface stood here and is DELETED, not
627
- reworded** - `X12-TA1-RESIDUALS` made those fields post-unescape, so it is measured false.
654
+ reworded** - a later release made those fields post-unescape, so it is measured false.
628
655
  - **A caller who was hand-rolling the escape** (the remedy this file named while the defect was
629
656
  open) regresses on both kinds of surface: `"00000001??"` in, `TA1*00000001????*…` out, and
630
657
  `getSegmentValue` answering `"00000001??"` where it answered `"00000001?"`. The framing and the
631
658
  disposition stay correct, but **drop the hand-rolled escape.**
632
- - **An EMPTY control number was not refused by this slice, and is refused by the NEXT one.**
659
+ - **An EMPTY control number was not refused by this change, and is refused by the NEXT one.**
633
660
  `escapeRelease` early-returns on `""` and `buildTA1` carried no required-field guard, so
634
661
  `interchangeControlNumber: ""` emitted `TA1**260601*1200*A*000` with `warnings: []`, here and at
635
- every earlier release. `X12-EMPTY-CONTROL-NUMBER-FABRICATED` closed it across every builder; see
662
+ every earlier release. A later release closed it across every builder; see
636
663
  the entry at the top of this file, including the whitespace-only residual it leaves open.
637
- - **🩺 The READ half did not move IN THIS SLICE, and `X12-TA1-RESIDUALS` is the one that moved
664
+ - **🩺 The READ half did not move IN THIS CHANGE, and a later release is the one that moved
638
665
  it** - see the entry at the top of this file. A control number of `"00000001?"` read back as
639
- `"00000001?*260601"` before this slice and as `"00000001??"` after it; the clause that stood
666
+ `"00000001?*260601"` before the EMIT half moved and as `"00000001??"` after it; the clause that stood
640
667
  here telling callers to apply `unescapeRelease` themselves is **deleted, not reworded**, because
641
668
  the library now does it.
642
669
  - **The release is scoped to the delimiter set the CALLER states.** `BuildTA1Options` gained
@@ -657,12 +684,12 @@ model.
657
684
  `X12_TA1_ACCEPT_WITH_NOTE` and still runs first.
658
685
  - **What this does NOT close.** `buildTA1` still uses no segment joiner, so its refusal names the
659
686
  builder and never the slot; `buildInterchange` did not escape GS-04 / GS-05 / GS-07 at this
660
- release, which `X12-INTERCHANGE-GS-EMIT-NOT-RELEASE-AWARE` then closed; and an unescaped active
687
+ release, which a later one then closed; and an unescaped active
661
688
  delimiter is still not safe anywhere, because that is what a delimiter is.
662
689
 
663
690
  - **🩺 BREAKING for `build277` callers: a 277 service line now REQUIRES `unitsOfService`, because
664
- SVC-07 is a required element in `005010X212` and this library was not emitting it at all**
665
- (`X12-277-SVC07-NOT-DECODED`). Through the release before this one `get277Status` read SVC-01
691
+ SVC-07 is a required element in `005010X212` and this library was not emitting it at all.**
692
+ Through the release before this one `get277Status` read SVC-01
666
693
  through SVC-04 and stopped, and `build277` emitted exactly those four, so **every X212 277 this
667
694
  library produced with a Loop 2220 service line was short a required element** and every 277 it read
668
695
  silently discarded the submitted units. Both sides are fixed: `X12ServiceLineStatus.unitsOfService`
@@ -696,7 +723,7 @@ model.
696
723
  string** - at base the same spec emitted a bare `SVC~`, because the SVC-07 slot did not exist. `SVC-03` (Line Item Payment
697
724
  Amount) is usage **R** in X212 and usage **N** in X214 and is optional in both builders. And the
698
725
  read side is unchanged and still lenient: an X212 277 arriving with no SVC-07 raises **no
699
- warning**, because that would need a new Tier-2 registry code and the defect this slice closes is
726
+ warning**, because that would need a new Tier-2 registry code and the defect this change closes is
700
727
  on the emit side. All of it is pre-existing and reproduces at `e3cdf49`; widening the guard would
701
728
  have turned this into that audit, which is its own item.
702
729
 
@@ -1242,7 +1269,7 @@ Code"`, `SVC05 / 380 / "Units of Service Paid Count"`, `SVC06 / C003`,
1242
1269
 
1243
1270
  **`undefined` on either quantity still means "not decoded", not "absent".** The element may have
1244
1271
  been missing, or present and unparseable as a decimal; the two share one `undefined` on the model.
1245
- They no longer share a silence: as of `X12-QUANTITY-SILENT-DEFAULTS` the unparseable case raises
1272
+ They no longer share a silence: as of an earlier release the unparseable case raises
1246
1273
  `X12_UNPARSEABLE_DECIMAL` at that `position.elementIndex` and the absent case raises nothing, so a
1247
1274
  warning at the element is what tells them apart. The verbatim element is still on `tx.segments`.
1248
1275
 
@@ -1252,7 +1279,7 @@ Code"`, `SVC05 / 380 / "Units of Service Paid Count"`, `SVC06 / C003`,
1252
1279
  no warning. Re-emitting from the model with this release produces conformant bytes.
1253
1280
 
1254
1281
  **The 277's SVC-07 is a different element in a different TR3 and is now decoded and emitted in its
1255
- own right** (`X12-277-SVC07-NOT-DECODED`, below). Do not read either field off the other.
1282
+ own right** (see the 277 entry below). Do not read either field off the other.
1256
1283
 
1257
1284
  - **Bundled code-list snapshots are pre-launch initial subsets, not the full WPC-published lists.**
1258
1285
  CARC, RARC, Claim-Status-Category (CSCC), Claim-Status (CSC), service-type, CLP-status, and
@@ -1260,9 +1287,11 @@ Code"`, `SVC05 / 380 / "Units of Service Paid Count"`, `SVC06 / C003`,
1260
1287
  the long-tail codes most workflows branch on. An inbound code **outside** a snapshot still parses:
1261
1288
  the verbatim code is preserved on the model and an `X12_UNKNOWN_*` warning is raised. Only the
1262
1289
  human-readable **description** is absent (`undefined`). A stale or partial snapshot therefore yields
1263
- a missing description, **never a wrong code**. Run `pnpm refresh:code-lists` to audit snapshot
1264
- freshness; regenerating the full lists (`--fetch`) is a redistribution-terms-gated release step (see
1265
- below), not a runtime fetch.
1290
+ a missing description, **never a wrong code**. Every snapshot states which it is: `meta.completeness`
1291
+ is `"cited-subset"` on all seven today, so a lookup miss means this package does not carry the code
1292
+ and never that the publisher did not issue it. Run `pnpm refresh:code-lists` to audit snapshot
1293
+ freshness; regenerating the full lists (`--fetch`) is a release step gated per list on that list's
1294
+ own recorded redistribution terms (see below), not a runtime fetch.
1266
1295
 
1267
1296
  - **`serialize(parse(s)) === s` is NOT guaranteed.** `serializeX12` rebuilds the interchange from the
1268
1297
  model, so every segment the parser recorded comes back verbatim (element padding, composites, and
@@ -1329,14 +1358,15 @@ Code"`, `SVC05 / 380 / "Units of Service Paid Count"`, `SVC06 / C003`,
1329
1358
  context and anchor), `ta1Segments`, `trailingBytes` and the warning multiset are unchanged by the
1330
1359
  round trip, and every emit is a fixed point.
1331
1360
 
1332
- What is **measured** across the 56 committed fixtures: every emit is a fixed point (serializing it
1361
+ What is **measured** across every committed fixture: each emit is a fixed point (serializing it
1333
1362
  again is a byte-level no-op) and re-parses to an identical model with an identical warning stream;
1334
- the 14 fixtures with no line breaks return byte-identical; and the other 42 differ from their source
1363
+ the fixtures with no line breaks return byte-identical; and the rest differ from their source
1335
1364
  by **line breaks and nothing else**, with no element value lost, altered, reordered, or re-escaped.
1336
- Two caveats on that corpus, both of which limit how far the sweep can be pushed: it contains **no
1337
- instance of cases 2 to 6** (zero fixtures produce an `orphanSegments` entry, which is why the
1338
- orphan sweep above is constructed rather than drawn from it), and **13 of the 14
1339
- byte-identical fixtures are `golden/*.edi`**, which
1365
+ No corpus size is quoted: it moves with every fixture added, and `test/serialize.test.ts`
1366
+ re-derives the sweep from the tree on every run. Two caveats on that corpus, both of which limit
1367
+ how far the sweep can be pushed: it contains **no instance of cases 2 to 6** (zero fixtures produce
1368
+ an `orphanSegments` entry, which is why the orphan sweep above is constructed rather than drawn
1369
+ from it), and **all but one of the byte-identical fixtures are `golden/*.edi`**, which
1340
1370
  are serializer output by construction, leaving `envelope/no-trailing-crlf.edi` as the only
1341
1371
  independent witness. Preserving the original framing byte-for-byte would need the model to carry
1342
1372
  per-segment framing and TA1 position it does not have today; that is a tracked model change, not a
@@ -1509,7 +1539,7 @@ N-char spec limit` refusal, one per emitting module, where the branch fires **be
1509
1539
  **Read the "through a builder's segment joiner" qualifier literally.** `buildTA1` does not use one,
1510
1540
  and is therefore **not covered by the segment guard**. Through `0.0.14` that meant nothing checked
1511
1541
  its five elements at all, and a numeric or `undefined` `interchangeControlNumber` was emitted
1512
- silently (`TA1**250101*1200*A*000`); `X12-TA1-EMIT-NOT-RELEASE-AWARE` closed that by routing them
1542
+ silently (`TA1**250101*1200*A*000`); a later release closed that by routing them
1513
1543
  through the escape helper, which type-checks first and refuses with `AckBuildError` /
1514
1544
  `X12_ACK_INVALID_SPEC`. What being outside the segment guard still costs is the **slot** in the
1515
1545
  refusal message: it names `buildTA1`, never `TA1-01`.
@@ -1579,12 +1609,14 @@ N-char spec limit` refusal, one per emitting module, where the branch fires **be
1579
1609
 
1580
1610
  - **A forged non-array in a builder spec refuses; in a few places it throws an untyped `TypeError`.**
1581
1611
  The types say `readonly T[]`, but a JavaScript or JSON caller can hand a builder something else. As
1582
- of `0.0.6` every indexed loop in every builder takes its bound from a checked array (32 loops across
1583
- 7 modules), so an object like `{ length: "9".repeat(120000) }` draws that builder's own typed
1612
+ of `0.0.6` every indexed loop in every builder takes its bound from a checked array, so an object
1613
+ like `{ length: "9".repeat(120000) }` draws that builder's own typed
1584
1614
  refusal - previously the length coerced to `Infinity` and the builder **looped forever instead of
1585
- refusing**. Measured across the nineteen probes the suite ships: at base **16 hung** and 3 threw an
1586
- untyped `TypeError`; at head **17 refuse with a typed, coded error** and 2 still throw the untyped
1587
- `TypeError`.
1615
+ refusing**. No loop or probe count is quoted here: both move with every builder added, and
1616
+ `test/builder-array-bounds.test.ts` re-derives them from the source on every run. Measured over the
1617
+ probes that suite shipped at the `0.0.6` base: most **hung** with no refusal and the rest threw an
1618
+ untyped `TypeError`. Each of those probes now refuses with a typed, coded error, except the
1619
+ `for…of` reads below, which still throw the untyped `TypeError`.
1588
1620
 
1589
1621
  **`null` is treated as absent, not forged**, exactly as the `?? []` this replaced did, so an
1590
1622
  optional list you send as `null` still builds. On a required list `null` draws that builder's own
@@ -1594,11 +1626,24 @@ N-char spec limit` refusal, one per emitting module, where the branch fires **be
1594
1626
 
1595
1627
  Not covered, and disclosed rather than fixed: the places a builder reads a caller array with
1596
1628
  `for…of` - `buildInterchange`'s `spec.groups`, `build999`'s `functionalGroup.transactionResponses`,
1597
- and every optional leaf array such as `claim.dates` or `member.references`. Those throw
1629
+ and the optional leaf arrays such as `claim.dates` or `member.references`. Those throw
1598
1630
  `TypeError: … is not iterable` immediately. They terminate, so they are not the hang, but they carry
1599
1631
  **no `code`**, so `err.code` is `undefined` and you cannot branch on it. Validate your spec shape at
1600
1632
  your own boundary if it comes from JSON.
1601
1633
 
1634
+ **`build270` is the exception, and it is deliberately stricter than its siblings.** EVERY list slot
1635
+ on `Build270Spec` - the spine (`informationSources`, `receivers`, `subscribers`, `dependents`,
1636
+ `inquiries`, `serviceTypeCodes`) and the leaves (`traces`, `references`, `dates`, `address.lines`,
1637
+ `diagnosisCodePointers`, `procedure.modifiers`) alike - is read through the same checked-array
1638
+ chokepoint, so a forged array-like anywhere in a 270 spec draws a typed, code-tagged
1639
+ `Eligibility270BuildError` rather than an untyped `TypeError`. **The same slots are checked for
1640
+ HOLES at the same point**, because a real array with `undefined` or `null` sitting in a slot is a
1641
+ list and clears the chokepoint: that is the shape a JSON payload with a dropped record carries, and
1642
+ unguarded it reached an emitter and threw the untyped `TypeError` this whole section is about. A
1643
+ hole refuses wherever it stands, naming the slot and nothing out of the inquiry. A name loop sent
1644
+ as `null` is refused the same way, at every level. The other builders are unchanged: on those, a
1645
+ hole in a list still reaches whatever dereferences it.
1646
+
1602
1647
  **One qualification worth stating precisely: on the acknowledgment path the value is not always
1603
1648
  strictly your own.** TR3 005010X231A1 requires AK2-02 to be a verbatim copy of the acknowledged
1604
1649
  transaction set's ST-02, and `buildTA1` exists to echo an inbound ISA-13, so a _document's_ control
@@ -1645,41 +1690,394 @@ N-char spec limit` refusal, one per emitting module, where the branch fires **be
1645
1690
  mismatch and preserve the inbound values verbatim. The library will not "fix" a payer artifact for
1646
1691
  you. Gate your own posting/adjudication on the warning.
1647
1692
 
1648
- ## Conformance testing not yet wired
1649
-
1650
- - **No external-oracle differential corpus yet.** A best-effort differential harness against CMS
1651
- Medicare 835 public examples (and/or another external X12 reader) is planned for the first real
1652
- release but is **not yet wired**, pending a redistribution-terms review of the CMS sample material.
1653
- Conformance today rests on the three-tier synthetic corpus (spec-clean → vendor-quirk → round-trip
1654
- goldens), property/round-trip tests, and a nightly amplified byte-flip fuzz job, not on parity with
1655
- a third-party implementation. Do not assume byte-for-byte agreement with any specific vendor parser.
1693
+ ## Trading-partner profiles: what a conformance classification is, and is not
1694
+
1695
+ - **A profile never changes what this library emits.** A profile describes what a trading partner
1696
+ SENDS. It attaches attribution to a parse and partitions that parse's warnings into expected and
1697
+ unexpected, and that is the whole of its behaviour. It cannot loosen, tighten or otherwise touch
1698
+ the emit side: `serializeX12`, `buildInterchange` and every `build*` domain builder produce
1699
+ byte-identical output with any profile active and with none, and refuse the same specs with the
1700
+ same error type, code and message. There is no opt-in flag that would change this, and
1701
+ `test/profiles-emit-independence.test.ts` fails the build if the property is ever broken. The
1702
+ reason it is a locked invariant rather than a convention: a profile that widened the emit would
1703
+ put a non-conformant document on the wire, and 45 CFR 162.915 forbids the trading partner from
1704
+ having asked for that in the first place.
1705
+
1706
+ - **Each quirk's conformance classification is a RECORDED HUMAN JUDGEMENT, not a check this
1707
+ library performed.** `describe()` states, per quirk, whether the deviation is one a trading partner
1708
+ may lawfully require. Read that as documentation someone wrote down, and confirm it against your
1709
+ own copy of the relevant implementation guide before you rely on it. In particular, **a
1710
+ classification that turns on an element being marked "not used" in an adopted TR3 rests on someone
1711
+ having read that TR3.** This package bundles none: 45 CFR 162.920(a) states a fee is charged for
1712
+ the implementation specifications, so "is this element marked not used?" is not a question the
1713
+ library can answer at run time, and it does not pretend to. Nothing in the classification is
1714
+ derived from your document, from the quirk's `effect`, or from any code list shipped here.
1715
+
1716
+ - **The axis is 45 CFR 162.915, and it has three states, not two.** `permitted` and `not-permitted`
1717
+ are measured against that rule, which forbids a covered entity from entering a trading partner
1718
+ agreement that would change the definition, data condition or use of a data element or segment in
1719
+ a standard, add data elements or segments to the maximum defined data set, use any code or data
1720
+ element marked "not used" in (or absent from) the standard's implementation specification, or
1721
+ change the meaning or intent of that implementation specification. The third state,
1722
+ `undetermined`, means **no judgement was recorded**, and it is the fail-safe default: a quirk that
1723
+ says nothing about conformance renders as `undetermined`, never as `permitted`. Read
1724
+ `undetermined` as "this package makes no claim", which is not the same as "harmless". Two of the
1725
+ three quirks shipped by the built-in profiles are `undetermined` today for exactly the fee reason
1726
+ above.
1727
+
1728
+ - **A `requires` quirk is a record of what a partner MANDATES, never a claim that the standard
1729
+ supports the mandate.** Those are two different statements and the buckets do not conflate them:
1730
+ membership of `requires` confers nothing on the conformance axis, and a partner mandating an
1731
+ element the adopted guide marks "not used" classifies as a deviation from the standard. If you
1732
+ need to know whether your partner may lawfully require something, read `conformance`, not the
1733
+ effect bucket.
1734
+
1735
+ ## What the differential corpus proves, and what it does not
1736
+
1737
+ - **Every synthetic document this repository ships is read by this library and, where an independent
1738
+ reader maps its transaction, by that reader too, and the two readings are compared element
1739
+ position by element position.** Two independent, open-source X12 readers serve as oracles, each
1740
+ pinned to one exact version and used only as a development tool: neither is ever a dependency of
1741
+ this package, whose published runtime dependency set is empty. The first is pyx12, a BSD-licensed
1742
+ HIPAA X12 parser and validator. The second is LinuxForHealth x12, an Apache-2.0-licensed X12
1743
+ reader with a transaction model per implementation guide, pinned together with every package it
1744
+ runs on and the Python version it runs under, because its own metadata leaves the Pydantic release
1745
+ it needs free. A transaction is compared against the first oracle that maps it and against no
1746
+ other. `pnpm run differential` runs the comparison and writes `test/differential/report.json`,
1747
+ which carries each oracle's name, version and licence as the installed reader declares them (for
1748
+ the second, also the Python version and every companion package version it ran with), the commit
1749
+ this library was read from, which oracle each transaction was compared against with the
1750
+ implementation guide and the map or model that oracle bound it to, the documents that went through
1751
+ both readers, the number of element positions compared, every document the run looked at and did
1752
+ not compare with the reason, and every disagreement found, with both readings left exactly as each
1753
+ reader returned them. `pnpm run differential:check` runs the same comparison and succeeds only when
1754
+ it reproduces the committed report. Which comparison a document belongs to is decided by the
1755
+ document's own GS-08 and ST-01, never by where it is filed, so the awkward documents are in the
1756
+ corpus rather than beside it.
1757
+ - **Which reader compares what.** pyx12 compares the 277 claim acknowledgment (005010X214), the 820,
1758
+ the 834, the 835, the professional claim, the institutional claim and the 999. LinuxForHealth x12
1759
+ compares the 270, the 271, the 276 and the 277 claim status response (005010X212), for which pyx12
1760
+ publishes no 005010 implementation guide map. LinuxForHealth x12 also models the 834, the 835 and
1761
+ both claims, and those are still compared against pyx12 alone: no transaction here is read by both
1762
+ oracles, so no row carries two readers' agreement.
1763
+ - **🩺 The second oracle reads a document only through its transaction model for the guide the
1764
+ document declares, and that model refuses much of this corpus, so its comparisons are narrower than
1765
+ the documents filed under them.** A document the model refuses is recorded in the report as
1766
+ unevaluated, with the oracle and the kind of refusal, and never counted as agreement. Among the
1767
+ documents here that it refuses are the ones that deliberately break the hierarchy or a row, 271
1768
+ documents whose AAA rejection reasons fall outside the codes it enumerates for that loop, the 270
1769
+ and 271 documents carrying a dependent level, and the 276 documents whose subscriber carries a
1770
+ dependent level beside a claim of its own, as the canonical 276 fixture does. A model can also
1771
+ accept a document and set one of its segments aside without raising, so a document is compared only
1772
+ when the model kept every segment of every transaction set, ST through SE, that the oracle framed
1773
+ for it; one whose model kept fewer is recorded as unevaluated with its own refusal kind, and none of
1774
+ its positions is counted as agreement. 🩺 The two readers place the 276 subscriber's demographics
1775
+ segment (DMG) differently: that model reads it ahead of the subscriber's name and this library
1776
+ after it. A document carrying one where this library puts it loses it to that model, which then
1777
+ refuses the document; and this library's 276 reader keeps a DMG only after the name already read at
1778
+ its level, so one sent ahead of the name is not on the decoded inquiry and no warning says so. Which
1779
+ position the implementation guide specifies is not settled here, and nothing about either reader
1780
+ moved to produce this. So the one 276 compared against it carries no subscriber DMG. Agreement on the
1781
+ 270, the 271, the 276 and the 277 claim status response is agreement over the documents that model
1782
+ accepted whole, which the report lists by name, and nothing wider: in particular no document
1783
+ compared against it carries a dependent level, and no 276 compared against it carries a DMG, so that
1784
+ comparison says nothing about either.
1785
+ - **🩺 This library and pyx12 disagree about the release character, and the report records the
1786
+ disagreement rather than settling it.** One synthetic document escapes the element separator with
1787
+ `?` inside a transaction set body. This library reads `?` as a release character there, as it has
1788
+ on every released version, so `REF*EA*ID?*WITH?*STAR` is two elements whose second value is
1789
+ `ID*WITH*STAR`; pyx12 reads no release character, so the same segment is four elements whose second
1790
+ value is `ID?`. 005010 transmits no release character at all and nothing in it picks between the
1791
+ two readings, which is the same ambiguity the envelope-splitter entry above is about, and it is the
1792
+ one input class in this corpus on which two conforming readers can genuinely differ. **The
1793
+ disagreement is published, not resolved:** the report holds both readings verbatim for that
1794
+ position, nothing about how this library reads the release character moved to produce it, and
1795
+ `pnpm run differential` exits non-zero for as long as it stands. If you exchange documents that
1796
+ escape a delimiter in a body element, do not assume your counterparty's parser frames that segment
1797
+ the way this one does.
1798
+ - **The rows of this library's read scope that neither oracle maps are compared against nothing, and
1799
+ the report names every one with the reason.** Neither reader publishes a 005010 implementation
1800
+ guide map or model for the 278 in either direction or for the dental claim, neither has one for the
1801
+ 275 (006020X314) or the 277 request for additional information (006020X313), and neither can bind
1802
+ the TA1 interchange acknowledgment, which sits in the envelope rather than in a functional group
1803
+ and carries no implementation guide identifier. Read that as a statement about the maps and models
1804
+ those readers publish and not about what they can frame: what is missing is the implementation
1805
+ guide, not the ability to read bytes. Either way this library's reading of those transactions is
1806
+ compared against nothing here, and silence about them is silence, never agreement.
1807
+ - **For two of the compared transactions the two implementations target different published errata of
1808
+ the same implementation guide, and the report records both identifiers rather than implying one.**
1809
+ pyx12 maps an earlier errata of the professional claim and of the institutional claim than this
1810
+ library implements, so agreement there is agreement between two revisions of one guide.
1811
+ - **Agreement with either reader is not conformance to a Technical Report Type 3, and it is not a
1812
+ compliance statement.** Two parsers can agree and both be wrong about the same clause; neither
1813
+ reader is the standard, and each transaction here is compared against one of them, never both. No
1814
+ report produced here says anything about HIPAA compliance, which is a property of a deployment and
1815
+ never of a library. Conformance still rests on the three-tier synthetic corpus (spec-clean,
1816
+ vendor-quirk, round-trip goldens), property and round-trip tests, and a nightly amplified byte-flip
1817
+ fuzz job. Do not assume byte-for-byte agreement with any specific vendor parser.
1818
+ - **The corpus is this repository's own synthetic fixtures and nothing else.** No CMS Medicare 835
1819
+ public sample material is in it: the redistribution-terms review of that material is not cleared,
1820
+ and it is not used here until it is. What that bounds is the evidence, not the claim: two readers
1821
+ agreeing over synthetic documents have agreed over synthetic documents.
1656
1822
 
1657
1823
  ## Scope (non-goals for v1)
1658
1824
 
1659
- - **Healthcare HIPAA 005010 only.** Non-healthcare transaction sets (850/856/810/204, etc.), the
1660
- EDIFACT syntax family, and pre-005010 versions are out of v1 scope. Pre-005010 input is tolerated
1661
- and flagged (`X12_PRE_005010`), not decoded to those older field maps.
1825
+ - **Healthcare HIPAA 005010, and two 006020 guides: the claims attachments pair.** Beside the
1826
+ 005010 transaction sets, the package reads and builds the 277 Health Care Claim Request for
1827
+ Additional Information (`006020X313`) and the 275 Additional Information to Support a Health Care
1828
+ Claim or Encounter (`006020X314`), which 45 CFR 162.920(a)(20) and (a)(19) name and 45 CFR
1829
+ 162.2002 adopts for the period on and after May 26, 2028. No other 006020 guide is read or built:
1830
+ the services review pair the same rule proposed and did not adopt, the 278 `006020X315` and the
1831
+ 275 Additional Information to Support a Health Care Services Review `006020X316`, is out of
1832
+ scope, and a 275 declaring `006020X316` is read as a guide the 275 reader does not implement.
1833
+ Non-healthcare transaction sets
1834
+ (850/856/810/204, etc.), the EDIFACT syntax family, and pre-005010 versions are out of v1 scope.
1835
+ Pre-005010 input is tolerated and flagged (`X12_PRE_005010`), not decoded to those older field
1836
+ maps. What the attachments pair leaves open, stated here beside it:
1837
+ - **The base X12 006020 transaction sets are typed; no implementation-guide usage is asserted.**
1838
+ Both guides are sold and no source this package carries states which segments, qualifiers or
1839
+ codes they make required, situational or repeatable. So the readers carry every code and
1840
+ qualifier verbatim and decide nothing about whether it is valid for the guide, and the builders
1841
+ write every BHT code, HL level and child code, entity, reference and date qualifier exactly as
1842
+ given and supply none. `build277RequestForAdditionalInformation` refuses only the absences the
1843
+ base 006020 277 makes mandatory (no level, no claim-level request, a request with no TRN, an
1844
+ empty C043-01 or C043-02, a service line with no SVC); `build275` refuses only what would make a
1845
+ BDS-02 untrue or a BDS empty (no attachment, empty data, a filter code that is not three
1846
+ characters, a character above U+00FF). A document conformant to either guide can carry a segment
1847
+ or qualifier these models leave untyped: the 277's SBR, PAT, DMG, heading name loop, PWK loops
1848
+ and TOO, the 275's DTM, IN1, DMG, PRV, PER, NX1 and each line's NM1, HI, SVC, DTP, CAT, PID and
1849
+ OOI. They stay verbatim on the transaction set, and neither builder writes them: `build275`
1850
+ writes each BDS directly after its line's LX, TRN, STC and REF, with no DTP, CAT or OOI before
1851
+ it, so a trading partner whose guide places the BDS inside those loops needs them added by hand
1852
+ through `buildInterchange`.
1853
+ - **The 277 request reader validates no hierarchy.** Each HL is carried with HL-01, HL-02, HL-03
1854
+ and HL-04 as sent; no parent pointer is checked and no level code is expected, because no
1855
+ carried source states the guide's hierarchy. A claim-level segment sent before the first HL
1856
+ reaches no level and stays on the transaction set.
1857
+ - **ISA-12 is not decided here.** Which ISA-12 a 006020 interchange carries is an open question no
1858
+ carried source answers. Both builders write this package's default, `00501`, unless the caller
1859
+ supplies `interchangeControlVersion`, and both readers key on ST-03 and GS-08 and never on
1860
+ ISA-12.
1861
+ - **`06020X314` is not an adopted identifier.** 45 CFR 162.2002(c) prints the 275's guide one digit
1862
+ short, as `06020X314`, where 162.920(a)(19) prints `006020X314`. The conformance row adopts the
1863
+ 162.920 spelling and records the other on its note as a typographical variant, so a 275 declaring
1864
+ `06020X314` is warned `X12_GUIDE_NOT_IMPLEMENTED`.
1865
+ - **Only the attachment data refuses to print.** A 275 attachment's BDS-03 is held in
1866
+ `X12AttachmentData`, whose string, JSON and `util.inspect` forms carry its octet count and no
1867
+ octet. Every other field of both readings, the names, member and claim identifiers and trace
1868
+ numbers included, is a plain string, as on every other reading in this package.
1869
+ - **An attachment is octets, and a string is not.** Pass an interchange carrying attachments to
1870
+ `parseX12` as a `Buffer`, which is read one character per octet. `serializeX12` of a built 275
1871
+ is a string of one character per octet: write it as latin1 (`Buffer.from(text, "latin1")`),
1872
+ because encoding a character above U+007F as UTF-8 makes two octets of one and BDS-02 stops being
1873
+ true of what was sent. Nothing inside an attachment is decoded; BDS-01 names a filter the sender
1874
+ applied and this package applies none.
1662
1875
  - **No transport.** AS2, SFTP, and MLLP-style delivery are out of scope. This is a parser/serializer,
1663
1876
  not a communications stack.
1664
1877
  - **Published, still pre-alpha.** The package is published on npm as `@cosyte/x12` from a public
1665
1878
  repo, but it stays on the `0.0.x`-until-first-alpha ladder. `npm view @cosyte/x12 version` is the
1666
1879
  only source of truth for the current version, so this page does not restate one. Treat the API as
1667
1880
  pre-alpha and pin the exact version until the first alpha.
1668
- - **No typed model for the 270 and 276 inquiries.** Every other v1 transaction has both a
1669
- per-transaction reader and a domain builder. The 270 eligibility inquiry and the 276 claim-status
1670
- inquiry have neither: they parse into segments, composites, and dot-paths like any other X12 input,
1671
- and the responses (271, 277) decode fully, but the inquiry directions have no typed surface yet.
1881
+ - **A 276 hierarchical level whose declared parent does not resolve is left off the returned tree.**
1882
+ The 276 reader attaches a level by its own HL-02 and by nothing else, so a pointer naming a level
1883
+ that is not present, a pointer naming a level of the wrong kind, and a parent chain that returns
1884
+ to itself each leave that level, and everything transmitted beneath it, absent from the model. The
1885
+ loss is reported (`X12_276_LEVEL_DETACHED`, beside the code for the pointer defect itself), the
1886
+ declared pointer stays verbatim on `hierarchies`, and the segments stay verbatim on the
1887
+ transaction set. Nothing is re-parented onto whichever level happened to be open, because that
1888
+ would be this library inventing the one structure a 276 exists to state: which provider is asking
1889
+ about which patient's claim.
1890
+ - **A 276 REF, DTP or AMT short of what its row is built from loses the whole row, and says so.**
1891
+ Each is a record and not a slot. Without both REF-01 and REF-02 there is no reference row; without
1892
+ both DTP-01 and DTP-03 there is no date row, and the format qualifier that says single date or
1893
+ range goes with it; without a decodable AMT-02 there is no amount row. The reader builds none,
1894
+ fabricates nothing to stand in, and reports the loss (`X12_276_REFERENCE_ROW_DROPPED`,
1895
+ `X12_276_DATE_ROW_DROPPED`, `X12_AMOUNT_ROW_DROPPED`) at that segment. **The report is bounded to
1896
+ the row the reader tried to build.** A segment that decoded and then found no claim or service
1897
+ line open to sit on is a different loss, it stays silent, and it is not on any of those channels.
1898
+ - **The 276 reader surfaces four of the SVC elements and no postal address.** SVC-01, SVC-02, SVC-04
1899
+ and SVC-07 reach the typed service line; SVC-03, SVC-05 and SVC-06 are left unread, because this
1900
+ is the request direction and a submitter states what it billed rather than what was paid. An N3 or
1901
+ N4 a sender transmits under a 276 name loop reaches no typed field either, as it reaches none on
1902
+ the 277 beside it. All of them stay verbatim on `tx.segments`; read them there.
1903
+ - **A 270 hierarchical level whose declared parent does not resolve is left off the returned tree.**
1904
+ The 270 reader attaches a level by its own HL-02 and by nothing else, so a pointer naming a level
1905
+ that is not present, a pointer naming a level of the wrong kind, and a parent chain that returns to
1906
+ itself each leave that level, and everything transmitted beneath it, absent from the model. The
1907
+ loss is reported (`X12_270_LEVEL_DETACHED`, beside the code for the pointer defect itself), the
1908
+ declared pointer stays verbatim on `hierarchies`, and the segments stay verbatim on the transaction
1909
+ set. Nothing is re-parented onto whichever level happened to be open, because that would be this
1910
+ library inventing the one structure a 270 exists to state.
1911
+ - **A 270 DTP short of what a date row is built from loses the whole row, and says so.** A DTP is a
1912
+ record and not a slot: without both the qualifier that says what the date is for (DTP-01) and the
1913
+ value itself (DTP-03) there is no row to build, and the format qualifier that says single date or
1914
+ range goes with it. The reader builds none, fabricates nothing to stand in, and reports the loss
1915
+ (`X12_270_DATE_ROW_DROPPED`) at that segment, so an empty `dates` list can be told apart from a
1916
+ date the sender stated and this reader could build no row from. **The report is bounded to the row
1917
+ the reader tried to build.** A segment that would have attached to a level or an inquiry but
1918
+ arrives before the first HL opens one reaches no part of the model and stays SILENT, the date row
1919
+ included, so an empty list is evidence about a level this reader did return and about nothing else.
1920
+ The segments stay verbatim on the transaction set either way.
1921
+ - **A 270's tolerated deviations are reported once per transaction set, not once per occurrence.**
1922
+ A declared non-conventional delimiter raises `X12_270_NON_CONVENTIONAL_DELIMITER` once however many
1923
+ of the four roles deviate, and a line break between segments raises
1924
+ `X12_270_INTER_SEGMENT_LINE_BREAK` once however many runs the document carries. Both anchor at the
1925
+ ISA. The framing run is also consumed by the shared parse and recorded nowhere on the model, so
1926
+ only `parse270Inquiries`, which receives the bytes, can report it: `get270Inquiry` is handed the
1927
+ model and reports the delimiter half alone.
1928
+ - **Between segments, only CR and LF are absorbed. A space or a tab there costs you the whole
1929
+ interchange, on every transaction set.** The shared parse consumes a run of CR / LF before the next
1930
+ segment opens; any other whitespace becomes the head of the next segment's identifier, so `GS` is
1931
+ read as a non-spec segment id, no functional group frames, every segment after the ISA lands on
1932
+ `ix.orphanSegments` with an `X12_UNEXPECTED_SEGMENT` warning, and the interchange also reports
1933
+ `X12_MISSING_IEA`. Nothing is silent at the interchange level, but the typed readers see no
1934
+ transaction set at all: `parse270Inquiries` answers the EMPTY list, which is the same value it uses
1935
+ for an interchange that genuinely carries no 270, so **do not read an empty result as evidence the
1936
+ sender sent no inquiry without checking `ix.warnings` first.** This is long-standing shared-parse
1937
+ behaviour and the 270 work deliberately did not widen it: framing tolerance is shared by every
1938
+ transaction set, and moving it is a change to all of them rather than to one reader.
1939
+ - **A BDS or BIN segment's binary data is framed by the octet count its length element declares,
1940
+ never by delimiters, and where that count cannot be honoured it is reported, never corrected.**
1941
+ BDS-03 and BIN-02 are read as exactly the number of octets BDS-02 or BIN-01 declares, anywhere after
1942
+ the ISA, so an element, repetition or component separator, a segment terminator, a `?`, a CR or an
1943
+ LF inside the data is data: the segment is one segment, the data is one element, and the segments
1944
+ after it frame as sent. **`?` has no effect inside the declared span**, not even immediately before
1945
+ the terminator that follows it. `getSegmentValue`, `getAllSegmentValues` and `elementValue` return
1946
+ the data element verbatim, with no release unescape and no split on the repetition or component
1947
+ separator. **Nothing inside it is decoded**: BDS-01's filter (`B64`, for example) is not applied,
1948
+ and no typed reader decodes an attachment. **The count is in octets.** A `Buffer` is read one character
1949
+ per octet, so its count is exact; a string is counted one UTF-16 code unit per octet, which is exact
1950
+ at or below U+00FF, and a span holding anything above that raises `X12_BINARY_LENGTH_UNVERIFIABLE`
1951
+ and is still carried verbatim, so pass an interchange that carries attachments as a `Buffer`. The
1952
+ three ways the count can fail, each warned at the segment:
1953
+ - `X12_BINARY_LENGTH_INVALID`: the length element is absent, empty, or not 1 to 15 ASCII digits (a
1954
+ sign, a decimal point or whitespace is malformed; leading zeros are not). **No length is inferred,
1955
+ so this is the one case the count does not protect**: the segment is framed by its delimiters
1956
+ exactly as a segment of any other id is, and a delimiter inside its data splits it and can
1957
+ mis-frame every segment after it.
1958
+ - `X12_BINARY_DATA_TRUNCATED`: the input ends inside the declared span. The data element holds
1959
+ only the octets present and nothing past the end is read, and because the span runs to the end
1960
+ of the input, the transaction, group and interchange trailers are reported missing.
1961
+ - `X12_BINARY_LENGTH_MISMATCH`: the byte after the declared span is not the segment terminator (a
1962
+ length shorter or longer than the data), or the segment ends before a data element begins
1963
+ although a non-zero length was declared. The data element is exactly the declared span; the
1964
+ bytes from its end to the next terminator stay on the segment's `raw`, so a length longer than
1965
+ the data carries the start of the next segment into this one, and the default `serializeX12` emit
1966
+ still reproduces the input byte for byte.
1967
+
1968
+ **The length element is never rewritten on emit**, in either `serializeX12` mode, including where
1969
+ it disagrees with the data: that would be a silent correction. A span that ends exactly at the end
1970
+ of the input is complete and raises no binary warning; its missing terminator is supplied on emit,
1971
+ as for any unterminated final segment, so where a truncated span fell exactly one octet short, a
1972
+ re-parse of that emit reads the supplied terminator as the last octet of the data. Where `?` is the
1973
+ declared element separator or segment terminator, the elements before the data element are split
1974
+ literally, as that role's splitter already does.
1975
+
1976
+ ## Code-list validity dates
1977
+
1978
+ - **🩺 A published Stop date is read as the FIRST day the code is no longer valid, and the
1979
+ maintainer does not say whether that is right.** `x12.org` renders a code's dates as
1980
+ `Start: 01/01/1995 | Last Modified: 11/01/2017 | Stop: 05/01/2018` and states nowhere whether the
1981
+ Stop date is the LAST day the code was valid or the FIRST day it was not. The two readings differ
1982
+ by exactly one day, on exactly the boundary a document produced that day sits on.
1983
+
1984
+ This package reads it as the first day the code is NOT valid, so the valid interval is half-open:
1985
+ a document date on or after the Stop date reports `not-valid`, and the day before it reports
1986
+ `valid`. The ground is the shape of the source and not a statement by the maintainer: the
1987
+ published dates are first-of-month publication-cycle dates, so the Stop date reads as the
1988
+ publication on which the code left the list rather than the last day it was on it. **That is a
1989
+ judgement, not a fact, and a consumer whose contract turns on the boundary day itself should
1990
+ confirm it with the maintainer rather than with this package.** `CARC.dates` and `RARC.dates`
1991
+ carry the published values unchanged, so a consumer who reads the boundary the other way has
1992
+ everything needed to compute it.
1993
+
1994
+ Two further bounds on the same data, neither of them a defect:
1995
+ - **A code with no published start date is `indeterminate`, never `valid` and never `not-valid`.**
1996
+ Validity is an interval and there is no interval without a start. No bundled code is in this
1997
+ state at the capture the dates were transcribed from; the answer exists because the fail-safe
1998
+ has to hold if one ever is.
1999
+ - **The dates describe the bundled subset and nothing wider.** A code outside it answers
2000
+ `indeterminate` with the inbound value echoed verbatim, which is a statement about what this
2001
+ package bundles rather than about what the maintainer publishes.
1672
2002
 
1673
2003
  ## Code-list `--fetch` regeneration
1674
2004
 
1675
2005
  `pnpm refresh:code-lists` (default) validates the bundled snapshots and prints a freshness audit,
1676
2006
  offline and CI-safe. The `--fetch` mode that would **regenerate** the full lists from their canonical
1677
- WPC / X12 sources is deliberately **not** run in automation: redistributing the full WPC code
1678
- descriptions requires a redistribution-terms review that has not cleared, and it needs outbound
1679
- network. The tool prints the canonical source manifest and exits rather than fabricating descriptions
1680
- the maintainers have not reviewed.
2007
+ sources is deliberately **not** run in automation: regeneration needs outbound network and is a human
2008
+ release step, and the tool fetches nothing and fabricates no description a maintainer has not
2009
+ published.
2010
+
2011
+ **Whether a bundled list's descriptions may be redistributed is answered PER LIST, and the lists do
2012
+ not agree.** Each bundled snapshot carries its own maintaining organisation and its own
2013
+ redistribution record on `meta`, so a consumer deciding whether a description is theirs to display,
2014
+ cache or re-publish reads that list's answer rather than a single sentence covering all seven:
2015
+
2016
+ | Bundled list | External code list | Maintained by | Redistribution | Approach for permission |
2017
+ | ------------------------------------- | ------------------ | ------------- | --------------------- | ----------------------- |
2018
+ | CARC (Claim Adjustment Reason Codes) | 139 | ASC X12 | licence required | ASC X12 (see below) |
2019
+ | RARC (Remittance Advice Remark Codes) | 411 | CMS | permitted, no licence | none needed |
2020
+ | CSCC (Claim Status Category Codes) | 507 | ASC X12 | licence required | ASC X12 (see below) |
2021
+ | CSC (Claim Status Codes) | 508 | ASC X12 | licence required | ASC X12 (see below) |
2022
+ | Service type (EB-03) | 958 | ASC X12 | licence required | ASC X12 (see below) |
2023
+ | CLP-02 claim status | none | ASC X12 | **not established** | ASC X12 (see below) |
2024
+ | INS-03 maintenance type | none | ASC X12 | **not established** | ASC X12 (see below) |
2025
+
2026
+ The **External code list** column is the list's id on the X12 External Code Lists index. It is the
2027
+ column that decides the one beside it: a list with a row there is answered from that row, and only a
2028
+ list with no row can be one the carried sources never name. `none` means exactly that the index
2029
+ publishes no row for the list, not that the list is unimportant.
2030
+
2031
+ **The approach, for every row that needs one:** ASC X12, which publishes that it is the only
2032
+ organisation authorised to grant permission for the use of X12 products. Permission to reproduce X12
2033
+ intellectual property is requested through the licensing programme X12 publishes at
2034
+ <https://x12.org/products/licensing-program>, by email to the intellectual-property address given
2035
+ there, naming the requester and their organisation and describing both the artifact and its intended
2036
+ distribution. Subscriptions to X12-maintained code lists are bought through the Code List Update
2037
+ Subscription page linked from <https://x12.org/codes>.
2038
+
2039
+ The evidence behind each row is quoted on the list itself, at `meta.redistribution.terms`. In short:
2040
+ the X12 External Code Lists index records CARC as external code list 139, the two claim-status lists
2041
+ as 507 and 508, and the service type codes as 958, all maintained by an X12 code maintenance
2042
+ committee group, under the statement that all X12 work products are copyrighted and that
2043
+ subscriptions to X12-maintained code lists are purchased. X12's own intellectual-property policy,
2044
+ reproduced in HL7's CARIN Consumer Directed Payer Data Exchange code system stubs, adds that all X12
2045
+ products are subject to it and that X12 is the only organisation authorised to grant permission for
2046
+ their use. The same index records RARC as external code list 411 maintained by CMS, and HL7's CARIN
2047
+ terminology licensure page puts CARC among the code systems that require a purchased licence and RARC
2048
+ among those requiring none.
2049
+
2050
+ **Being cited from a TR3 is not evidence of a missing publisher.** Three bundled lists point their
2051
+ `meta.source` at a purchased Technical Report Type 3: the service type codes, the CLP-02 dispositions
2052
+ and the INS-03 maintenance types. Only the last two are absent from the External Code Lists index;
2053
+ the service type codes have a row there, so they are answered from it. Where a bundled subset was
2054
+ read is a different question from what the publisher publishes, and only the second decides the
2055
+ terms.
2056
+
2057
+ **"Not established" is a recorded answer here, not a gap waiting to be tidied.** The last two lists,
2058
+ the CLP-02 dispositions and the INS-03 maintenance types, are printed inside a purchased ASC X12
2059
+ Technical Report Type 3 and have no row on the External Code Lists index at all, and no source
2060
+ obtained for this package names them or states whether their descriptions may be redistributed. They
2061
+ are therefore treated as **not redistributable** wherever a permission decision is made, exactly as a
2062
+ licence-restricted list is: an unsettled question is never read as a permission.
2063
+
2064
+ Consequences you can rely on:
2065
+
2066
+ - **A list whose descriptions may not be redistributed stays exactly the cited part it already
2067
+ ships.** Nothing is enlarged, refetched or relicensed by recording the terms, and the record names
2068
+ the licensor who could change that.
2069
+ - **`--fetch` answers each list on its own terms.** It reports each bundled list's regeneration
2070
+ permission independently, refuses only the lists whose own record forbids it, prints the licensor
2071
+ to approach beside each refusal, and never refuses a list because a different list is restricted.
2072
+ It exits `2` while any list is refused and `0` when none is.
2073
+ - **Snapshot validation refuses an unlabelled list.** A bundled list carrying no maintaining
2074
+ organisation, or no redistribution record, fails `pnpm refresh:code-lists` with a message naming
2075
+ that list, so it cannot reach a release unlabelled.
2076
+ - **`codeListRedistributionIsPermitted(meta)` is the single decision point**, and it is exported.
2077
+ Only a recorded permission answers `true`; a licence requirement, an unsettled status and a missing
2078
+ record all answer `false`.
1681
2079
 
1682
2080
  ---
1683
2081
 
1684
- For the phase-by-phase surface and the exact fields each helper decodes, see the package's
1685
- [`CLAUDE.md`](./CLAUDE.md) status section and the [Cookbook](./docs-content/cookbook.md).
2082
+ For the per-transaction read and emit surface, and the exact fields each helper decodes, see
2083
+ [`README.md`](./README.md) and the Cookbook on the documentation site.