@cosyte/x12 0.0.18 → 0.1.1

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.
@@ -136,6 +136,34 @@ model.
136
136
  refused before this change is refused differently by it.**
137
137
  - **No census of other builders' required elements is published here.** This change measured TA1.
138
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).
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
169
  release escape.** `tx.st.elements` is the ST segment as framed:
@@ -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
@@ -1259,9 +1287,11 @@ Code"`, `SVC05 / 380 / "Units of Service Paid Count"`, `SVC06 / C003`,
1259
1287
  the long-tail codes most workflows branch on. An inbound code **outside** a snapshot still parses:
1260
1288
  the verbatim code is preserved on the model and an `X12_UNKNOWN_*` warning is raised. Only the
1261
1289
  human-readable **description** is absent (`undefined`). A stale or partial snapshot therefore yields
1262
- a missing description, **never a wrong code**. Run `pnpm refresh:code-lists` to audit snapshot
1263
- freshness; regenerating the full lists (`--fetch`) is a redistribution-terms-gated release step (see
1264
- 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.
1265
1295
 
1266
1296
  - **`serialize(parse(s)) === s` is NOT guaranteed.** `serializeX12` rebuilds the interchange from the
1267
1297
  model, so every segment the parser recorded comes back verbatim (element padding, composites, and
@@ -1328,14 +1358,15 @@ Code"`, `SVC05 / 380 / "Units of Service Paid Count"`, `SVC06 / C003`,
1328
1358
  context and anchor), `ta1Segments`, `trailingBytes` and the warning multiset are unchanged by the
1329
1359
  round trip, and every emit is a fixed point.
1330
1360
 
1331
- 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
1332
1362
  again is a byte-level no-op) and re-parses to an identical model with an identical warning stream;
1333
- 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
1334
1364
  by **line breaks and nothing else**, with no element value lost, altered, reordered, or re-escaped.
1335
- Two caveats on that corpus, both of which limit how far the sweep can be pushed: it contains **no
1336
- instance of cases 2 to 6** (zero fixtures produce an `orphanSegments` entry, which is why the
1337
- orphan sweep above is constructed rather than drawn from it), and **13 of the 14
1338
- 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
1339
1370
  are serializer output by construction, leaving `envelope/no-trailing-crlf.edi` as the only
1340
1371
  independent witness. Preserving the original framing byte-for-byte would need the model to carry
1341
1372
  per-segment framing and TA1 position it does not have today; that is a tracked model change, not a
@@ -1578,12 +1609,14 @@ N-char spec limit` refusal, one per emitting module, where the branch fires **be
1578
1609
 
1579
1610
  - **A forged non-array in a builder spec refuses; in a few places it throws an untyped `TypeError`.**
1580
1611
  The types say `readonly T[]`, but a JavaScript or JSON caller can hand a builder something else. As
1581
- of `0.0.6` every indexed loop in every builder takes its bound from a checked array (32 loops across
1582
- 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
1583
1614
  refusal - previously the length coerced to `Infinity` and the builder **looped forever instead of
1584
- refusing**. Measured across the nineteen probes the suite ships: at base **16 hung** and 3 threw an
1585
- untyped `TypeError`; at head **17 refuse with a typed, coded error** and 2 still throw the untyped
1586
- `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`.
1587
1620
 
1588
1621
  **`null` is treated as absent, not forged**, exactly as the `?? []` this replaced did, so an
1589
1622
  optional list you send as `null` still builds. On a required list `null` draws that builder's own
@@ -1593,11 +1626,24 @@ N-char spec limit` refusal, one per emitting module, where the branch fires **be
1593
1626
 
1594
1627
  Not covered, and disclosed rather than fixed: the places a builder reads a caller array with
1595
1628
  `for…of` - `buildInterchange`'s `spec.groups`, `build999`'s `functionalGroup.transactionResponses`,
1596
- 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
1597
1630
  `TypeError: … is not iterable` immediately. They terminate, so they are not the hang, but they carry
1598
1631
  **no `code`**, so `err.code` is `undefined` and you cannot branch on it. Validate your spec shape at
1599
1632
  your own boundary if it comes from JSON.
1600
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
+
1601
1647
  **One qualification worth stating precisely: on the acknowledgment path the value is not always
1602
1648
  strictly your own.** TR3 005010X231A1 requires AK2-02 to be a verbatim copy of the acknowledged
1603
1649
  transaction set's ST-02, and `buildTA1` exists to echo an inbound ISA-13, so a _document's_ control
@@ -1644,39 +1690,392 @@ N-char spec limit` refusal, one per emitting module, where the branch fires **be
1644
1690
  mismatch and preserve the inbound values verbatim. The library will not "fix" a payer artifact for
1645
1691
  you. Gate your own posting/adjudication on the warning.
1646
1692
 
1647
- ## Conformance testing not yet wired
1648
-
1649
- - **No external-oracle differential corpus yet.** A best-effort differential harness against CMS
1650
- Medicare 835 public examples (and/or another external X12 reader) is planned for the first real
1651
- release but is **not yet wired**, pending a redistribution-terms review of the CMS sample material.
1652
- Conformance today rests on the three-tier synthetic corpus (spec-clean → vendor-quirk → round-trip
1653
- goldens), property/round-trip tests, and a nightly amplified byte-flip fuzz job, not on parity with
1654
- 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.
1655
1822
 
1656
1823
  ## Scope (non-goals for v1)
1657
1824
 
1658
- - **Healthcare HIPAA 005010 only.** Non-healthcare transaction sets (850/856/810/204, etc.), the
1659
- EDIFACT syntax family, and pre-005010 versions are out of v1 scope. Pre-005010 input is tolerated
1660
- 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.
1661
1875
  - **No transport.** AS2, SFTP, and MLLP-style delivery are out of scope. This is a parser/serializer,
1662
1876
  not a communications stack.
1663
1877
  - **Published, still pre-alpha.** The package is published on npm as `@cosyte/x12` from a public
1664
1878
  repo, but it stays on the `0.0.x`-until-first-alpha ladder. `npm view @cosyte/x12 version` is the
1665
1879
  only source of truth for the current version, so this page does not restate one. Treat the API as
1666
1880
  pre-alpha and pin the exact version until the first alpha.
1667
- - **No typed model for the 270 and 276 inquiries.** Every other v1 transaction has both a
1668
- per-transaction reader and a domain builder. The 270 eligibility inquiry and the 276 claim-status
1669
- inquiry have neither: they parse into segments, composites, and dot-paths like any other X12 input,
1670
- 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.
1671
2002
 
1672
2003
  ## Code-list `--fetch` regeneration
1673
2004
 
1674
2005
  `pnpm refresh:code-lists` (default) validates the bundled snapshots and prints a freshness audit,
1675
2006
  offline and CI-safe. The `--fetch` mode that would **regenerate** the full lists from their canonical
1676
- WPC / X12 sources is deliberately **not** run in automation: redistributing the full WPC code
1677
- descriptions requires a redistribution-terms review that has not cleared, and it needs outbound
1678
- network. The tool prints the canonical source manifest and exits rather than fabricating descriptions
1679
- 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`.
1680
2079
 
1681
2080
  ---
1682
2081