@cosyte/x12 0.0.16 → 0.0.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,8 +7,110 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### Added
11
+
12
+ - **🩺 `X12_ISA_EXTRA_ELEMENT_SEPARATOR` - the ISA element split now has an arity check**
13
+ (`X12-ISA-ELEMENT-ARITY`). Additions-only: no decoding moved, no existing warning was suppressed or
14
+ narrowed, and nothing is re-framed.
15
+
16
+ `decodeIsa` splits the 105-byte ISA element area on the element separator, and the comment above it
17
+ asserted the result was "exactly 17 entries by construction because the element-separator-position
18
+ guard in `delimiters.ts` already verified the layout". That guard verifies the separator at all 16
19
+ fixed 005010 positions, which bounds the split from BELOW - it can never come out short. It was
20
+ never bounded from above. An ISA element value carrying that same byte splits again, so the element
21
+ comes back a prefix and everything after it is displaced. **How far is not derivable from
22
+ `isa.elements`**: more than one element can carry an extra separator, and one sitting between two
23
+ of them is displaced less than one sitting after both. `isa.raw` plus the ISA's fixed widths is the
24
+ only route back.
25
+
26
+ Planting the element separator inside each of the 16 fixed elements of one conformant interchange,
27
+ measured at `0.0.16`:
28
+
29
+ ```text
30
+ planted in split parts warnings at 0.0.16
31
+ ISA-01..10 18 X12_PRE_005010, X12_CONTROL_NUMBER_MISMATCH
32
+ ISA-11 - X12_INVALID_DELIMITERS (Tier-3, reached first)
33
+ ISA-12 18 X12_PRE_005010, X12_CONTROL_NUMBER_MISMATCH
34
+ ISA-13 18 X12_CONTROL_NUMBER_MISMATCH
35
+ ISA-14 18 (none)
36
+ ISA-15 18 (none)
37
+ ISA-16 - X12_INVALID_DELIMITERS (Tier-3, reached first)
38
+ ```
39
+
40
+ ISA-11 and ISA-16 ARE the in-band repetition and component separator declarations, so the plant
41
+ collides with them and the Tier-3 refusal reaches it before any of this. That is a boundary of the
42
+ probe, not a property of those two elements.
43
+
44
+ On the `ISA-01..10` and `ISA-12` rows the document declares `00501` at ISA-12's own fixed offset and
45
+ `X12_PRE_005010` fires anyway; `elements[13]`, the interchange control number and the reassociation
46
+ key, answers `"00501"`; and `elements[15]`, the test/production usage indicator, answers `"0"` on a
47
+ document whose ISA-15 says `P`. The `ISA-14` and `ISA-15` rows were reported on no channel at all.
48
+
49
+ `parseX12` raises the new warning BEFORE the ISA-derived checks that read `elements[12]` and
50
+ `elements[13]` by index, because when it fires those two may be reading a displaced element rather
51
+ than the one they name. `{ strict: true }` therefore escalates on it rather than on the warning that
52
+ follows. **That is a statement about `ix.warnings` and `onWarning` only.** `serializeX12(ix,
53
+ { specClean: true })` reconciles ISA-13 against IEA-02 off `isa.elements[13]` with no arity
54
+ awareness and never raises this code, so on that channel a lone `X12_CONTROL_NUMBER_MISMATCH` can be
55
+ a displaced read of a control number that matches byte for byte at its fixed span, and the absence
56
+ of this warning there is not evidence the header framed. Pre-existing, disclosed, not fixed.
57
+
58
+ **It is a report, not a repair.** A byte that is both an element's content under the ISA's fixed
59
+ widths and the separator that same segment declares in-band has two readings; the interchange is
60
+ not 005010-conformant either way, and nothing anyone here has read settles which reading to take,
61
+ so `isa.elements` is left exactly as the split produced it and `isa.raw` still carries all 106
62
+ bytes, which is the route back.
63
+
64
+ **The emit side is untouched, and this is not a build-side guard.** The fixed-width ISA slots go
65
+ through `pad` / `padControl` and never through the caller escaper, so `buildInterchange` still
66
+ writes such a caller value straight onto the wire. It does now surface there, because
67
+ `buildInterchange` returns `parseX12` of the bytes it just wrote:
68
+ `interchangeControlNumber: "0000*0001"` came back with ISA-13 reading `"0000"`, ISA-15 reading
69
+ `"0"` instead of `"P"`, IEA-02 displaced the same way so the control-number reconciliation agreed
70
+ with the misreading, and `warnings: []`.
71
+
10
72
  ### Documentation
11
73
 
74
+ - **`IeaSegment`, `GsSegment`, `GeSegment`, `Ta1Segment` and `X12FunctionalGroup` no longer document
75
+ a raw envelope element as the value it holds** (`X12-ENVELOPE-VALUE-EXAMPLES`). **Documentation
76
+ only: the diff is comment-only, and `dist/index.mjs` and `dist/index.cjs` are byte-identical
77
+ before and after, so no behaviour moved.** The corrected text ships in `dist/index.d.ts`.
78
+
79
+ **Element values on those segment types are stored RAW, pre-`?`-unescape.** Their JSDoc named the
80
+ index and then named the value it holds - "IEA-02 interchange control number - must match ISA-13",
81
+ "GS-06 group control number", "TA1-01 (echoes the prior interchange's ISA-13 control number)" -
82
+ and repeated the promise inside an `@example` fence, which is the form a consumer copies. Measured
83
+ on one interchange where each documented non-ISA slot carries the sender-intended value `A*B`,
84
+ written on the wire in released form `A?*B`, **every one of those cells reads back `"A?*B"` and
85
+ not `"A*B"`.**
86
+
87
+ The same correction is applied to `parseX12`'s own `@example`, which handed out `// "HC"` at
88
+ `gs.elements[1]`.
89
+
90
+ On a `TA1` that `buildTA1` emitted and `parseX12` read back:
91
+
92
+ ```text
93
+ stated TA1-01 emitted TA1 elements[1] parseTA1()
94
+ "000000001" TA1*000000001*260601*1200*A*000 "000000001" "000000001"
95
+ "0000*0001" TA1*0000?*0001*260601*1200*A*000 "0000?*0001" "0000*0001"
96
+ "0000~0001" TA1*0000?~0001*260601*1200*A*000 "0000?~0001" "0000~0001"
97
+ "00000001?" TA1*00000001??*260601*1200*A*000 "00000001??" "00000001?"
98
+ ```
99
+
100
+ Every row `warnings: []`. TA1-01 is the reassociation key, so a consumer following the shipped
101
+ `@example` compared framed bytes against the ISA-13 it was acknowledging. `parseTA1` already read
102
+ the same bytes correctly, so the package disagreed with itself.
103
+
104
+ **Each block now carries the label `X12Segment` has always carried** - "Element values are stored
105
+ RAW, pre-`?`-unescape" - and the `@example` pointers keep their index and lose the promise. The
106
+ slot mapping (`elements[2]` = IEA-02) was true and is kept, and so are the element names, the
107
+ formats and the code-list citations, restated where they are true: as facts about the segment's
108
+ fields rather than about a raw element. No accessor, guard or test was added, and **no route is
109
+ named as the route.**
110
+
111
+ **The `IsaSegment` block is deliberately unchanged**, and no rule about the ISA is stated here in
112
+ either direction.
113
+
12
114
  - **The documentation carriers that told you to read an envelope element's logical value through
13
115
  `getSegmentValue` are corrected** (`X12-ENVELOPE-VALUE-ROUTES`). **Documentation and tests only: no
14
116
  runtime line changed and the emitted JS is byte-identical, so no behaviour moved.**
@@ -16,6 +16,70 @@ model.
16
16
 
17
17
  ## Data / decode boundaries
18
18
 
19
+ - **🩺 An ISA element carrying the element separator makes the header split into more than
20
+ `ISA` + 16 elements. Through `0.0.16` no check saw that at all; it is now reported as
21
+ `X12_ISA_EXTRA_ELEMENT_SEPARATOR`** (`X12-ISA-ELEMENT-ARITY`). **Nothing is
22
+ re-framed: this is a report, not a repair, and no existing warning was suppressed or narrowed.**
23
+
24
+ `detectDelimiters` verifies the element separator at all 16 fixed 005010 byte positions, so the
25
+ ISA split can never come out SHORT. It was never bounded from ABOVE. An element value carrying
26
+ that same byte splits again, so that element comes back a **prefix** and everything after it is
27
+ **displaced**. **🛑 How far is NOT derivable from `isa.elements`, and no arithmetic on it works.**
28
+ More than one element can carry an extra separator, and an element sitting between two of them is
29
+ displaced less than one sitting after both; `isa.raw` plus the ISA's fixed widths is the only route
30
+ back. Measured on one conformant interchange, planting the element separator inside each of the 16
31
+ fixed elements in turn:
32
+
33
+ ```text
34
+ planted in split parts warnings through 0.0.16
35
+ ISA-01..10 18 X12_PRE_005010, X12_CONTROL_NUMBER_MISMATCH
36
+ ISA-11 - X12_INVALID_DELIMITERS (Tier-3, thrown before any of this)
37
+ ISA-12 18 X12_PRE_005010, X12_CONTROL_NUMBER_MISMATCH
38
+ ISA-13 18 X12_CONTROL_NUMBER_MISMATCH
39
+ ISA-14 18 (none)
40
+ ISA-15 18 (none)
41
+ ISA-16 - X12_INVALID_DELIMITERS (Tier-3, thrown before any of this)
42
+ ```
43
+
44
+ ISA-11 and ISA-16 **are** the in-band repetition and component separator declarations, so planting
45
+ the element separator there collides with them and the Tier-3 refusal reaches it first. That is a
46
+ boundary of the probe, not a claim that those two elements are special.
47
+
48
+ **What the displaced reads cost, on the rows that had a warning as much as on the rows that had
49
+ none.** On the `ISA-01..10` and `ISA-12` rows the interchange declares `00501` at ISA-12's own
50
+ fixed offset and `X12_PRE_005010` fires anyway, because `elements[12]` has become the repetition
51
+ separator; `elements[13]`, the interchange control number and the reassociation key, answers
52
+ `"00501"`; and `elements[15]`, the test/production usage indicator, answers `"0"` on a document
53
+ whose ISA-15 says `P`. On the `ISA-13` row the control number is a prefix. **The `ISA-14` and
54
+ `ISA-15` rows carried `warnings: []`** while the usage indicator read empty.
55
+
56
+ **The route back is `isa.raw`**, which still carries all 106 bytes verbatim, and the ISA's fixed
57
+ widths make the transmitted span of any element recoverable from it. Recovering it is **your**
58
+ decision, not this library's: a byte that is both an element's content under those fixed widths and
59
+ the separator the same segment declares in-band has two readings, the interchange is not
60
+ 005010-conformant either way, and nothing anyone here has read says which reading to take. So the
61
+ parser reports that the header did not frame and leaves `isa.elements` exactly as the split
62
+ produced it.
63
+
64
+ **When this warning is present, treat every other ISA-derived diagnostic on that interchange as
65
+ provisional.** `parseX12` raises it ahead of the ones it raises after it for that reason, which
66
+ also means `{ strict: true }` escalates on it rather than on the displaced-value warning that
67
+ follows. **🩺 Read that as a statement about `ix.warnings` and `onWarning`, and about nothing
68
+ else.** `serializeX12(ix, { specClean: true })` reconciles ISA-13 against IEA-02 off
69
+ `isa.elements[13]` with no arity awareness and **never raises this code at all**, so on that
70
+ channel a lone `X12_CONTROL_NUMBER_MISMATCH` can be a displaced read of a control number that in
71
+ fact matches byte for byte at its fixed span, and **the absence of this warning there is not
72
+ evidence the header framed**. Pre-existing, disclosed, not fixed here.
73
+
74
+ **The emit side is unchanged and is NOT guarded by this.** The fixed-width ISA slots go through
75
+ `pad` / `padControl` and never through the caller escaper, so `buildInterchange` still writes a
76
+ caller value carrying the element separator straight onto the wire. What changed is that
77
+ `buildInterchange` returns `parseX12` of the bytes it just wrote, so the report now reaches that
78
+ path too: `interchangeControlNumber: "0000*0001"` used to come back with ISA-13 reading `"0000"`,
79
+ ISA-15 reading `"0"` instead of `"P"`, IEA-02 displaced the same way so the control-number
80
+ reconciliation **agreed with the misreading**, and `warnings: []`. Refusing such a value on emit is
81
+ a decision about the build side and is not taken here.
82
+
19
83
  - **🩺 `parseTA1`'s five decoded fields are POST-`?`-unescape as of this release, and an EMPTY
20
84
  TA1-02 / TA1-03 / TA1-04 / TA1-05 is REFUSED on emit. Both are behaviour changes**
21
85
  (`X12-TA1-RESIDUALS`). They are one slice because they are the two ends of the same disagreement:
package/dist/index.cjs CHANGED
@@ -121,6 +121,7 @@ function detectDelimiters(raw) {
121
121
  var WARNING_CODES = {
122
122
  X12_CONTROL_NUMBER_MISMATCH: "X12_CONTROL_NUMBER_MISMATCH",
123
123
  X12_PRE_005010: "X12_PRE_005010",
124
+ X12_ISA_EXTRA_ELEMENT_SEPARATOR: "X12_ISA_EXTRA_ELEMENT_SEPARATOR",
124
125
  X12_GROUP_COUNT_MISMATCH: "X12_GROUP_COUNT_MISMATCH",
125
126
  X12_TRANSACTION_COUNT_MISMATCH: "X12_TRANSACTION_COUNT_MISMATCH",
126
127
  X12_SEGMENT_COUNT_MISMATCH: "X12_SEGMENT_COUNT_MISMATCH",
@@ -196,6 +197,7 @@ var WARNING_MESSAGES = {
196
197
  X12_CONTROL_NUMBER_MISMATCH_GROUP: "Control number mismatch: GS-06 (header) and GE-02 (trailer) disagree. Both values are preserved verbatim on the model; neither is echoed here.",
197
198
  X12_CONTROL_NUMBER_MISMATCH_TRANSACTION: "Control number mismatch: ST-02 (header) and SE-02 (trailer) disagree. Both values are preserved verbatim on the model; neither is echoed here.",
198
199
  X12_PRE_005010: 'ISA-12 declares a version other than the HIPAA baseline "00501", so the input may diverge from 005010 semantics. The declared version is preserved verbatim on the model.',
200
+ X12_ISA_EXTRA_ELEMENT_SEPARATOR: "The ISA element area carries at least one element separator beyond the 16 sitting at their fixed 005010 byte positions, so the header does not split into `ISA` plus 16 elements. `isa.elements` is that split as it came out: an element containing an extra separator comes back a prefix and everything after it is displaced. How far is NOT derivable from `isa.elements`, so any other ISA-derived diagnostic on this interchange may be reporting a displaced value. All 106 bytes are preserved verbatim on `isa.raw`, which with the ISA's fixed widths is the route back. Which reading is correct - the byte as data under the ISA's fixed widths, or as a separator - is NOT decided here, and nothing is re-framed.",
199
201
  X12_GROUP_COUNT_MISMATCH: "IEA-01 does not equal the number of GS..GE groups actually present in the interchange. The declared count is preserved verbatim on the model and is NEVER silently corrected.",
200
202
  X12_TRANSACTION_COUNT_MISMATCH: "GE-01 does not equal the number of ST..SE transaction sets actually present in the group. The declared count is preserved verbatim on the model and is NEVER silently corrected.",
201
203
  X12_SEGMENT_COUNT_MISMATCH: "SE-01 does not equal the number of segments actually present in the transaction set it closes. The declared count is preserved verbatim and is corrected only on `serializeX12(ix, { specClean: true, recomputeCounts: true })`.",
@@ -284,6 +286,13 @@ function pre005010(position) {
284
286
  position
285
287
  };
286
288
  }
289
+ function isaExtraElementSeparator(position) {
290
+ return {
291
+ code: WARNING_CODES.X12_ISA_EXTRA_ELEMENT_SEPARATOR,
292
+ message: WARNING_MESSAGES.X12_ISA_EXTRA_ELEMENT_SEPARATOR,
293
+ position
294
+ };
295
+ }
287
296
  function groupCountMismatch(position) {
288
297
  return {
289
298
  code: WARNING_CODES.X12_GROUP_COUNT_MISMATCH,
@@ -1048,6 +1057,7 @@ function collectElementValues(seg, start, end, delimiters) {
1048
1057
  }
1049
1058
 
1050
1059
  // src/parser/envelope.ts
1060
+ var ISA_DECODED_PART_COUNT = 17;
1051
1061
  function decodeIsa(raw, delimiters) {
1052
1062
  const isaRaw = raw.slice(0, ISA_MIN_LENGTH);
1053
1063
  const isaHead = isaRaw.slice(0, ISA_MIN_LENGTH - 1);
@@ -1114,6 +1124,9 @@ function decodeEnvelope(raw, delimiters) {
1114
1124
  warnings.push(w);
1115
1125
  };
1116
1126
  const isa = decodeIsa(raw, delimiters);
1127
+ if (isa.elements.length !== ISA_DECODED_PART_COUNT) {
1128
+ warnings.push(isaExtraElementSeparator({ segmentIndex: 0, interchangeIndex: 0 }));
1129
+ }
1117
1130
  const isa12 = el(isa.elements, 12);
1118
1131
  if (isa12 !== "00501") {
1119
1132
  warnings.push(pre005010({ segmentIndex: 0, interchangeIndex: 0, elementIndex: 12 }));
@@ -10723,7 +10736,7 @@ var profiles = Object.freeze({
10723
10736
  });
10724
10737
 
10725
10738
  // src/index.ts
10726
- var VERSION = "0.0.16";
10739
+ var VERSION = "0.0.17";
10727
10740
 
10728
10741
  exports.ACK_BUILD_ERROR_CODES = ACK_BUILD_ERROR_CODES;
10729
10742
  exports.ALL_WARNING_MESSAGES = ALL_WARNING_MESSAGES;
@@ -10885,6 +10898,7 @@ exports.isAcceptDisposition = isAcceptDisposition;
10885
10898
  exports.isClaimAdjustmentGroupCode = isClaimAdjustmentGroupCode;
10886
10899
  exports.isDiagnosisQualifier = isDiagnosisQualifier;
10887
10900
  exports.isProcedureQualifier = isProcedureQualifier;
10901
+ exports.isaExtraElementSeparator = isaExtraElementSeparator;
10888
10902
  exports.lookupCarc = lookupCarc;
10889
10903
  exports.lookupClaimStatus = lookupClaimStatus;
10890
10904
  exports.lookupClaimStatusCategory = lookupClaimStatusCategory;