@cosyte/x12 0.0.1 → 0.0.2
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 +234 -145
- package/README.md +19 -14
- package/TRADEMARKS.md +31 -0
- package/dist/index.cjs +132 -132
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1291 -1291
- package/dist/index.d.ts +1291 -1291
- package/dist/index.mjs +132 -132
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -4
package/CHANGELOG.md
CHANGED
|
@@ -9,48 +9,138 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
### Fixed
|
|
11
11
|
|
|
12
|
+
- **Developer-docs publish status corrected to the published reality (README-ORG-SWEEP).** The
|
|
13
|
+
docs.cosyte.com pages (`docs-content/intro.md`, `installation.md`, `troubleshooting.md`) and
|
|
14
|
+
`KNOWN-LIMITATIONS.md` still said "not yet published to npm" / "gated on the coordinated public
|
|
15
|
+
launch" (and `KNOWN-LIMITATIONS.md` still read `0.0.0`), contradicting the live `npm install`
|
|
16
|
+
command already documented. Rewritten to state the package is published at `0.0.1`, public, still
|
|
17
|
+
pre-alpha on the `0.0.x`-until-first-alpha ladder; the install command is now described as live
|
|
18
|
+
rather than aspirational. Docs only, no runtime or public-API change.
|
|
19
|
+
- **README status line corrected to the published reality (README-ORG-SWEEP).** The status line still
|
|
20
|
+
read "pre-alpha (`0.0.x`, not yet published to npm) … the first npm publish is gated on the
|
|
21
|
+
coordinated public launch," which contradicted the npm-version badge and the `pnpm add @cosyte/x12`
|
|
22
|
+
install line already in the same README. The package is published on npm at `0.0.1` from a public
|
|
23
|
+
repo. Rewritten to state that it is published at `0.0.1`, in a public repo, still pre-alpha on the
|
|
24
|
+
`0.0.x`-until-first-alpha ladder; the read/emit scope claim is unchanged. Docs only, no runtime or
|
|
25
|
+
public-API change.
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- **Em-dash brand gate in CI (`scripts/check-no-emdash.sh`, `pnpm check:no-emdash`,
|
|
30
|
+
`.github/workflows/no-emdash.yml`; `EMDASH-CONFORMANCE`).** The founder directive of 2026-07-24
|
|
31
|
+
(`knowledgebase/06-brand/voice-and-tone.md`) bans `U+2014` outright across every cosyte surface
|
|
32
|
+
and names commit messages explicitly, and the meta-repo's `documentation/conventions.md` has
|
|
33
|
+
described the rule as CI-gated; x12 was one of the repos where it was not. This ports
|
|
34
|
+
`knowledgebase`'s scanner (the text-only variant, correct here because x12 tracks no binaries:
|
|
35
|
+
all 264 tracked files decode as us-ascii or utf-8 and none holds a NUL byte, measured byte-level
|
|
36
|
+
2026-07-27) and wires it to a dedicated workflow that checks **both** the tracked files and the
|
|
37
|
+
**PR title, body, and branch commit messages**, the last of these on the non-default `edited`
|
|
38
|
+
activity type so a description retitled after the final push is re-checked before a squash merge
|
|
39
|
+
turns it into the commit message. It is a separate workflow rather than a job in `ci.yml` because
|
|
40
|
+
`ci.yml` calls the shared reusable pipeline (which runs no arbitrary repo script) and its triggers
|
|
41
|
+
drive the Node 22 + 24 matrix plus the `release-dry-run` job. x12 was already clean (0 of 21
|
|
42
|
+
markdown files carried an em dash), so **no content changed**: this is regression prevention only.
|
|
43
|
+
Dev tooling, no change to the published package surface, parser behavior, or warning codes.
|
|
44
|
+
**Two limits stated rather than claimed away.** The new check is not yet one of the org ruleset's
|
|
45
|
+
required contexts (`parser-ci-required-checks` requires `ci / verify (22|24, ubuntu-latest)` and
|
|
46
|
+
`ci / actionlint`), so today it reds visibly but does not itself block a merge; making it required
|
|
47
|
+
is an org-level change outside this repo. And a tracked text file holding a NUL byte **and a
|
|
48
|
+
pattern match** fails this shape closed: grep 3.8 reports `binary file matches` on stderr and the
|
|
49
|
+
scan refuses. The NUL alone does not trigger it, and the red is remediable by the same rewrite
|
|
50
|
+
the brand rule already demands. x12 has no NUL-bearing tracked file at all today (zero of 264,
|
|
51
|
+
measured byte-level).
|
|
52
|
+
- **`docs-content/` now ships the full canonical Diátaxis spine (DOCS-CONTENT-P4).** The sidebar was
|
|
53
|
+
Overview-only, with `cookbook.md` authored but orphaned (invisible to every reader). This wires the
|
|
54
|
+
cookbook into **Guides** and adds the rest of the spine every `@cosyte/*` package shares: four new
|
|
55
|
+
**Core Concepts** pages (the envelope/loop model; the 80/20 transaction sets, mapping each shipped
|
|
56
|
+
set to its reader/builder pair and the field it preserves verbatim; the tolerance tiers +
|
|
57
|
+
warning-code model; and decimal-exact money via `X12Decimal`), **Installation** and **Quickstart**
|
|
58
|
+
tutorials (parse an 835 and post the cash), and a **Troubleshooting & known limitations** page (the
|
|
59
|
+
fatal-vs-warn model, a symptom→cause table, PHI-in-logs discipline, and the v1 non-goals). Depth is
|
|
60
|
+
gated to the shipped surface: no unshipped API is documented. Synthetic-only fixtures throughout.
|
|
61
|
+
Docs only, no runtime or public-API change.
|
|
62
|
+
|
|
63
|
+
### Fixed
|
|
64
|
+
|
|
65
|
+
- **`scripts/sync-version.mjs` hardened against two latent defects, and gated in CI
|
|
66
|
+
(SYNC-VERSION-HARDENING).** Follow-up hardening on the VERSION-SYNC script; ported byte-identically
|
|
67
|
+
across `hl7`, `x12`, and `mllp`. (1) The version was spliced into `src/index.ts` via
|
|
68
|
+
`String.prototype.replace` with a _replacement string_, which interprets `$&`, `$1`, `` $` ``, etc.,
|
|
69
|
+
so a version like `1.2.3-$&x` would inject the matched text and corrupt the `VERSION` constant while
|
|
70
|
+
exiting 0. The replacement is now a replacer _function_, whose return value is inserted literally.
|
|
71
|
+
(2) The declaration regex was non-global, so `.replace` silently rewrote the _first_ match; a
|
|
72
|
+
column-0 decoy (e.g. inside a comment) ahead of the real declaration could be edited instead. The
|
|
73
|
+
script now matches globally, asserts exactly one declaration, and exits non-zero loudly otherwise.
|
|
74
|
+
Neither defect is reachable through Changesets today and both previously failed loud rather than
|
|
75
|
+
shipping a lying `VERSION`, so this is hardening, not a fix for an observed break. The
|
|
76
|
+
`format`/`format:check` globs now cover `scripts/**/*.mjs` so the script is prettier-gated in CI
|
|
77
|
+
(the `.mjs` scripts were matched by no format glob before; `scripts/**/*.ts` was already gated).
|
|
78
|
+
Build tooling only, no runtime or public-API change.
|
|
79
|
+
- **The `intro.md` status/roadmap section was stale**. It described Phase 1/2 as the frontier and
|
|
80
|
+
listed the now-shipped read + emit + profile surfaces as "coming in later phases." Refreshed to the
|
|
81
|
+
current shipped reality with an honest pre-alpha status banner.
|
|
82
|
+
- **A latent malformed-ISA fixture in `cookbook.md`.** The self-contained 835 example's ISA padded
|
|
83
|
+
the sender/receiver IDs to 16 bytes instead of the fixed 15, so the 106-byte ISA was misaligned and
|
|
84
|
+
delimiter detection would reject it. It went unnoticed because the cookbook block was illustrative,
|
|
85
|
+
never executed; making the examples runnable under the doc/code-agreement harness surfaced it.
|
|
86
|
+
|
|
87
|
+
### Changed
|
|
88
|
+
|
|
89
|
+
- **Every runnable docs snippet is gated by the shared doc/code-agreement harness.**
|
|
90
|
+
`test/docs-content.test.ts` runs `docSnippetSuite()` (from `@cosyte/vitest-config/snippets`) over
|
|
91
|
+
`docs-content/`, extracting each ` ```ts runnable ` block, compiling it, executing it against the
|
|
92
|
+
**built** ESM artifact, and asserting its inline `// =>` results, so a documented example can never
|
|
93
|
+
silently drift from the shipped code. Bumps the `@cosyte/vitest-config` devDependency to `^0.0.2`
|
|
94
|
+
for its `/snippets` export.
|
|
95
|
+
|
|
12
96
|
- **The Release workflow can actually start.** `.github/workflows/release.yml` calls the shared
|
|
13
97
|
`cosyte/.github` pipeline, which requests `contents`/`id-token`/`pull-requests: write`, but declared
|
|
14
|
-
no `permissions:` of its own
|
|
98
|
+
no `permissions:` of its own, so it inherited the repo default of `contents: read`. A called
|
|
15
99
|
workflow may only downgrade the caller's `GITHUB_TOKEN`, never escalate it, so GitHub rejected the
|
|
16
100
|
workflow at startup (~1s, no jobs, no logs). Every Release run from June 2026 until now failed this
|
|
17
101
|
way, unnoticed, because a `startup_failure` produces no logs to read. The caller job now declares
|
|
18
|
-
the three scopes explicitly. CI-only
|
|
102
|
+
the three scopes explicitly. CI-only, no runtime or API change.
|
|
19
103
|
|
|
20
104
|
- **The `VERSION` export now tracks `package.json`, and the missing `version`
|
|
21
105
|
script is restored (VERSION-SYNC).** Two latent release bugs, both of which
|
|
22
106
|
would have bitten at the first publish. (1) `VERSION` was hardcoded `"0.0.0"`
|
|
23
107
|
in `src/index.ts` while `changeset version` bumps only `package.json`, so a
|
|
24
|
-
published `0.0.1` would have shipped an export reading `"0.0.0"
|
|
108
|
+
published `0.0.1` would have shipped an export reading `"0.0.0"`. Every
|
|
25
109
|
consumer asserting on or logging `VERSION` told the wrong version of the
|
|
26
110
|
parser they were running. New `scripts/sync-version.mjs` rewrites the constant
|
|
27
111
|
from `package.json` (idempotent; exits non-zero if the declaration is renamed
|
|
28
|
-
rather than silently no-op'ing). (2) **No `version` script existed at all
|
|
29
|
-
|
|
112
|
+
rather than silently no-op'ing). (2) **No `version` script existed at all**.
|
|
113
|
+
The shared `cosyte/.github` release workflow drives Changesets with
|
|
30
114
|
`version: pnpm run version`, which would have failed with `ERR_PNPM_NO_SCRIPT`,
|
|
31
115
|
so the "Version Packages" PR could never have been opened. The guard in
|
|
32
116
|
`test/sanity.test.ts` was **inverted**: it asserted
|
|
33
|
-
`expect(VERSION).toBe("0.0.0")`
|
|
117
|
+
`expect(VERSION).toBe("0.0.0")` (literal against literal) which stays green
|
|
34
118
|
through exactly this drift and goes red on a _correct_ bump. It now compares
|
|
35
119
|
`VERSION` against `package.json` at test time. Ported from `@cosyte/mllp`
|
|
36
120
|
(MLLP-10), in the canonical form `@cosyte/hl7` carries. No version bumped,
|
|
37
|
-
nothing published
|
|
121
|
+
nothing published, still `0.0.0`.
|
|
38
122
|
|
|
39
123
|
### Added
|
|
40
124
|
|
|
41
|
-
- **
|
|
125
|
+
- **Trademark notice (`TRADEMARKS.md`).** This package names third-party systems to describe what it
|
|
126
|
+
interoperates with; the notice records that cosyte is not affiliated with, endorsed by, or
|
|
127
|
+
sponsored by any of them, that every reference is descriptive, and that the built-in profiles are
|
|
128
|
+
authored from public sources only. Added to `files` so it ships inside the published tarball, not
|
|
129
|
+
just on GitHub. Documentation only, no runtime or API change.
|
|
130
|
+
|
|
131
|
+
- **Phase 10: release hardening.** The v1 close-out; no new parser
|
|
42
132
|
surface, just the gates, tooling, and docs that make the package
|
|
43
133
|
trustworthy to publish.
|
|
44
134
|
- **Publish-pipeline proof.** A new `release-dry-run` CI job proves a
|
|
45
135
|
real release would succeed without burning a version or needing
|
|
46
|
-
registry auth
|
|
136
|
+
registry auth: `pnpm publish --dry-run` exercises the publish command
|
|
47
137
|
path and `npm pack --dry-run` asserts the publishable tarball assembles
|
|
48
138
|
with the right `files` set + built `dist/`. The real provenance publish
|
|
49
139
|
stays gated on the public launch.
|
|
50
140
|
- **Nightly amplified fuzz** (`.github/workflows/fuzz.yml`). Re-runs the
|
|
51
141
|
byte-flip / never-throw property targets at a higher iteration count
|
|
52
|
-
(`X12_FUZZ_RUNS`) with a rotating seed (`X12_FUZZ_SEED`)
|
|
53
|
-
search that would slow the per-commit run
|
|
142
|
+
(`X12_FUZZ_RUNS`) with a rotating seed (`X12_FUZZ_SEED`), the deep
|
|
143
|
+
search that would slow the per-commit run, and opens/auto-closes a
|
|
54
144
|
sticky issue on failure. The per-commit suite is unchanged (pinned
|
|
55
145
|
seed, base counts, coverage-stable); a finding is replayable via the
|
|
56
146
|
printed seed. New test helper `fuzzRuns()` scales only the true fuzz
|
|
@@ -70,20 +160,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
70
160
|
`RELEASE_CHAR`).
|
|
71
161
|
- **Known limitation carried forward:** an external-oracle differential
|
|
72
162
|
corpus (vs CMS Medicare 835) is not yet wired, pending a
|
|
73
|
-
redistribution-terms review
|
|
163
|
+
redistribution-terms review. See `KNOWN-LIMITATIONS.md`.
|
|
74
164
|
|
|
75
165
|
### Security
|
|
76
166
|
|
|
77
|
-
- **Dev-dependency advisory remediation (no runtime impact
|
|
167
|
+
- **Dev-dependency advisory remediation (no runtime impact:
|
|
78
168
|
`@cosyte/x12` ships zero runtime dependencies).** Added scoped
|
|
79
169
|
`pnpm.overrides` pinning two transitive **dev/build-time** packages to
|
|
80
170
|
their patched releases: `esbuild` (`>=0.27.3 <0.28.1` → `0.28.1`,
|
|
81
|
-
GHSA dev-server path-traversal
|
|
171
|
+
GHSA dev-server path-traversal, unreachable here: a library build
|
|
82
172
|
via `tsup`/`vitest`, never `esbuild serve`) and the
|
|
83
173
|
`@changesets/parse` copy of `js-yaml` (`>=4.0.0 <4.2.0` → `4.2.0`,
|
|
84
174
|
GHSA-h67p-54hq-rp68 merge-key DoS). The `js-yaml@3.14.2` pulled by
|
|
85
175
|
`read-yaml-file@1.1.0` (via `@manypkg/get-packages` →
|
|
86
|
-
`@changesets/cli`) is **intentionally left
|
|
176
|
+
`@changesets/cli`) is **intentionally left**. It calls
|
|
87
177
|
`yaml.safeLoad`, removed/throwing in js-yaml 4, so it cannot be
|
|
88
178
|
force-upgraded without breaking the release tooling; it only parses
|
|
89
179
|
trusted local repo YAML at release time. Verify gate green on the
|
|
@@ -93,8 +183,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
93
183
|
|
|
94
184
|
- **Segment splitting now honours the `?`-release-escaped terminator.**
|
|
95
185
|
`splitSegments` (the envelope tokenizer) used a naive `indexOf` for the
|
|
96
|
-
segment terminator, so a value carrying a literal terminator byte
|
|
97
|
-
emitted by `escapeRelease` as `?~` — was split mid-value: the segment
|
|
186
|
+
segment terminator, so a value carrying a literal terminator byte (emitted by `escapeRelease` as `?~`) was split mid-value: the segment
|
|
98
187
|
was truncated at the `?`, a phantom empty segment was injected, and the
|
|
99
188
|
round-trip silently corrupted the value (the element splitter
|
|
100
189
|
`splitWithRelease` had always been release-aware; only the segment
|
|
@@ -108,13 +197,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
108
197
|
|
|
109
198
|
### Added
|
|
110
199
|
|
|
111
|
-
- **Profile system
|
|
200
|
+
- **Profile system: descriptive, fixture-grounded clearinghouse / payer
|
|
112
201
|
companion-guide quirk attribution.** A `defineProfile()` API mirroring
|
|
113
202
|
the sibling `@cosyte/hl7` profile shape, plus a `profiles` namespace of
|
|
114
203
|
built-ins. The parser is already lenient and lossless, so a **v1 profile
|
|
115
204
|
is DESCRIPTIVE**: it attaches attribution metadata to the returned
|
|
116
205
|
`X12Interchange` (`ix.profile`) and powers `partitionWarnings`, but
|
|
117
|
-
NEVER alters the parse
|
|
206
|
+
NEVER alters the parse: `groups`, `warnings`, and `isa` are
|
|
118
207
|
byte-identical with and without a profile (proven by a divergence test).
|
|
119
208
|
- **`defineProfile(spec)`** validates the spec (fail-fast on name, then
|
|
120
209
|
Levenshtein "did you mean?" hints on unknown option keys, then the
|
|
@@ -123,21 +212,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
123
212
|
position; scalar `description` last-wins), re-validates the composed
|
|
124
213
|
set, and returns a frozen `X12Profile` whose `describe()` yields a
|
|
125
214
|
structured `X12ProfileDescription` bucketed by effect
|
|
126
|
-
(`relaxes` / `adds` / `requires`)
|
|
215
|
+
(`relaxes` / `adds` / `requires`): structured DATA, not hl7's
|
|
127
216
|
formatted string, so consumers can program against it.
|
|
128
|
-
- **The locked HARD RULE
|
|
217
|
+
- **The locked HARD RULE: no invented quirks.** Every quirk MUST cite a
|
|
129
218
|
`fixture` (a relative path under `test/fixtures/`) that actually
|
|
130
219
|
EXHIBITS the deviation; the field is required at the type level and
|
|
131
220
|
enforced in `defineProfile()`. The accuracy suite goes further: a
|
|
132
221
|
per-quirk DEMONSTRATOR registry asserts each cited fixture exhibits its
|
|
133
222
|
claimed deviation, and a shipped quirk with no demonstrator FAILS the
|
|
134
|
-
suite
|
|
223
|
+
suite, so a real-but-irrelevant fixture cannot slip past.
|
|
135
224
|
- **`setDefaultProfile()` / `getDefaultProfile()`** set a process-scoped
|
|
136
225
|
default applied when a `parseX12` call passes no `profile`. An explicit
|
|
137
226
|
`{ profile }` wins; `{ profile: null }` opts out of the default for
|
|
138
227
|
that call. `partitionWarnings(warnings, profile)` splits a parse's
|
|
139
228
|
warnings into `{ expected, unexpected }` on the union of the profile's
|
|
140
|
-
quirk `expectedWarnings
|
|
229
|
+
quirk `expectedWarnings`, the one behavioural hook a v1 profile
|
|
141
230
|
offers.
|
|
142
231
|
- **Built-ins ship ONLY where a Tier-2 fixture grounds them:**
|
|
143
232
|
`profiles.availity` (payer-loop `REF*2U` + service-line `REF*F8`
|
|
@@ -145,7 +234,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
145
234
|
`profiles.bcbsCommon` (backslash component separator, grounded in
|
|
146
235
|
`envelope/bcbs-subelement.edi`). Profiles whose only "deviation" would
|
|
147
236
|
be a canonical `:` baseline (e.g. a generic Medicare FFS profile) are
|
|
148
|
-
deliberately DEFERRED rather than invented
|
|
237
|
+
deliberately DEFERRED rather than invented. Shipping them would
|
|
149
238
|
violate the hard rule. Built-ins are reachable only through the
|
|
150
239
|
`profiles` namespace, never the top-level export (mirrors hl7).
|
|
151
240
|
- **API divergence from `@cosyte/hl7`, by design:** `describe()` returns
|
|
@@ -157,11 +246,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
157
246
|
and the `X12Profile` / `X12ProfileSpec` / `X12ProfileQuirk` /
|
|
158
247
|
`X12ProfileDescription` / `X12ProfileEffect` / `X12WarningPartition`
|
|
159
248
|
type tree.
|
|
160
|
-
- **Domain builders
|
|
249
|
+
- **Domain builders: `build820` (005010X218 Premium Payment) and
|
|
161
250
|
`build834` (005010X220A1 Benefit Enrollment and Maintenance).** The emit
|
|
162
251
|
counterparts to `get820Payments` and `get834Header` /
|
|
163
252
|
`get834Enrollments`, layered on the Phase 8 general builder and
|
|
164
|
-
mirroring the pure-function `build835` pattern
|
|
253
|
+
mirroring the pure-function `build835` pattern. They NEVER auto-send,
|
|
165
254
|
open a socket, or touch the filesystem, and return a frozen
|
|
166
255
|
`X12Interchange`. Completes the v1 emit scope: every v1 transaction now
|
|
167
256
|
has a domain builder.
|
|
@@ -175,7 +264,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
175
264
|
spec is reproduced field-for-field. **The 820 carries no TR3 balance
|
|
176
265
|
equation** (BPR-02 is not required to equal Σ of the RMR open items),
|
|
177
266
|
so the builder emits all monetary amounts VERBATIM and never raises a
|
|
178
|
-
balance-mismatch refusal
|
|
267
|
+
balance-mismatch refusal, a deliberate contrast with `build835`.
|
|
179
268
|
- **`build834(spec)`** assembles a complete interchange (one GS..GE
|
|
180
269
|
group, GS-01 `BE`; one ST..SE 834, ST-03 `005010X220A1`) from a typed
|
|
181
270
|
`Build834Spec` (envelope + BGN header + sponsor `N1*P5` / payer
|
|
@@ -185,7 +274,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
185
274
|
the first HD so the read side binds them to the member, not the
|
|
186
275
|
coverage loop. The output round-trips through `get834Header` /
|
|
187
276
|
`get834Enrollments` field-for-field.
|
|
188
|
-
- **Maintenance type is the 834's safety primitive
|
|
277
|
+
- **Maintenance type is the 834's safety primitive: emit verbatim,
|
|
189
278
|
refuse the unknown.** The builder places the caller-supplied INS-03 /
|
|
190
279
|
HD-01 code (X12 Code Source 875) into the segment VERBATIM and NEVER
|
|
191
280
|
infers or normalizes it; where the lenient read side only WARNS on an
|
|
@@ -196,15 +285,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
196
285
|
outside the validated subset is refused.
|
|
197
286
|
- **Refusal, not silent corruption.** `build820` REFUSES a structurally
|
|
198
287
|
impossible spec via a typed `Premium820BuildError`
|
|
199
|
-
(`X12_820_BUILD_INVALID_SPEC
|
|
288
|
+
(`X12_820_BUILD_INVALID_SPEC`: no TRN trace, no remittance, a
|
|
200
289
|
remittance with neither an `ENT` nor an `NM1` to open its loop, a
|
|
201
290
|
remittance with no `RMR` open item, an open item with no identity, an
|
|
202
291
|
over-long control number). `build834` REFUSES via a typed
|
|
203
292
|
`Enrollment834BuildError`
|
|
204
|
-
(`X12_834_BUILD_UNKNOWN_MAINTENANCE_TYPE
|
|
205
|
-
outside the X12 875 subset; `X12_834_BUILD_INVALID_SPEC
|
|
293
|
+
(`X12_834_BUILD_UNKNOWN_MAINTENANCE_TYPE`: an INS-03 / HD-01 code
|
|
294
|
+
outside the X12 875 subset; `X12_834_BUILD_INVALID_SPEC`: no member
|
|
206
295
|
loop, an empty required INS-03, an over-long control number). Both
|
|
207
|
-
messages carry structural indices / counts only
|
|
296
|
+
messages carry structural indices / counts only. `build834`
|
|
208
297
|
additionally names the offending maintenance code (an X12 control
|
|
209
298
|
code, never PHI), but never a member id or name (PHI discipline).
|
|
210
299
|
- New public exports: `build820`, `Premium820BuildError`,
|
|
@@ -212,11 +301,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
212
301
|
`Build820Spec` type tree; `build834`, `Enrollment834BuildError`,
|
|
213
302
|
`ENROLLMENT_834_BUILD_ERROR_CODES`, `Enrollment834BuildErrorCode`, and
|
|
214
303
|
the `Build834Spec` type tree.
|
|
215
|
-
- **Domain builders
|
|
216
|
-
Review
|
|
217
|
-
Review
|
|
304
|
+
- **Domain builders: `build278Request` (005010X217 Health Care Services
|
|
305
|
+
Review: Request for Review) and `build278Response` (005010X216 Services
|
|
306
|
+
Review: Response).** The emit counterparts to `get278Request` /
|
|
218
307
|
`get278Response`, layered on the Phase 8 general builder and mirroring
|
|
219
|
-
the pure-function `build277` / `build277CA` pattern
|
|
308
|
+
the pure-function `build277` / `build277CA` pattern. They NEVER
|
|
220
309
|
auto-send, open a socket, or touch the filesystem, and return a frozen
|
|
221
310
|
`X12Interchange`.
|
|
222
311
|
- **`build278Request(spec)` / `build278Response(spec)`** share one
|
|
@@ -234,7 +323,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
234
323
|
surface.** `build278Response` places the caller-supplied HCR-01
|
|
235
324
|
`actionCode` (`A1` certified / `A3` not-certified / `A4` pended / `A6`
|
|
236
325
|
modified / …) into the segment VERBATIM and NEVER infers, normalizes,
|
|
237
|
-
or upgrades it
|
|
326
|
+
or upgrades it. The round-tripped `decision.actionCode` is
|
|
238
327
|
byte-for-byte the input. `build278Request` REFUSES a review carrying a
|
|
239
328
|
decision (HCR is response-only); `build278Response` refuses a decision
|
|
240
329
|
with an empty action code.
|
|
@@ -245,22 +334,22 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
245
334
|
construction.
|
|
246
335
|
- **Refusal, not silent corruption.** The builder REFUSES a
|
|
247
336
|
structurally impossible spec via a typed `ServicesReview278BuildError`
|
|
248
|
-
(`X12_278_BUILD_INVALID_HIERARCHY
|
|
337
|
+
(`X12_278_BUILD_INVALID_HIERARCHY`: a subscriber with neither a
|
|
249
338
|
review nor a dependent, a dependent with no review;
|
|
250
|
-
`X12_278_BUILD_INVALID_SPEC
|
|
339
|
+
`X12_278_BUILD_INVALID_SPEC`: a review with no request category code,
|
|
251
340
|
a request review carrying an HCR decision, a response decision with an
|
|
252
341
|
empty action code, an over-long control number). The message carries
|
|
253
|
-
structural locators only (`subscriber.review[0]`, level codes)
|
|
342
|
+
structural locators only (`subscriber.review[0]`, level codes), never
|
|
254
343
|
a member name, member id, trace, or diagnosis code (PHI discipline).
|
|
255
344
|
- New public exports: `build278Request`, `build278Response`,
|
|
256
345
|
`ServicesReview278BuildError`, `AUTH_278_BUILD_ERROR_CODES`,
|
|
257
346
|
`ServicesReview278BuildErrorCode`, and the `Build278Spec` type tree.
|
|
258
|
-
- **Domain builders
|
|
347
|
+
- **Domain builders: `build271` (005010X279A1 Eligibility Benefit
|
|
259
348
|
Response) and `build277` / `build277CA` (005010X212 Claim Status
|
|
260
349
|
Response / 005010X214 Claim Acknowledgment).** The response-side emit
|
|
261
350
|
counterparts to `get271Eligibility` / `get277Status` /
|
|
262
351
|
`get277CADisposition`, layered on the Phase 8 general builder and
|
|
263
|
-
mirroring the pure-function `build835` / `build837` pattern
|
|
352
|
+
mirroring the pure-function `build835` / `build837` pattern. They
|
|
264
353
|
NEVER auto-send, open a socket, or touch the filesystem, and return a
|
|
265
354
|
frozen `X12Interchange`.
|
|
266
355
|
- **`build271(spec)`** assembles a complete interchange (one GS..GE
|
|
@@ -277,7 +366,7 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
277
366
|
through `parseX12` so a well-formed spec is reproduced field-for-field.
|
|
278
367
|
- **TRN echo is the safety-critical reassociation invariant.** The
|
|
279
368
|
builder places the caller-supplied trace into TRN-02 verbatim and
|
|
280
|
-
NEVER fabricates, normalizes, or mutates it
|
|
369
|
+
NEVER fabricates, normalizes, or mutates it. A build-side property
|
|
281
370
|
test feeds random trace tokens through all three builders and asserts
|
|
282
371
|
the round-tripped `referenceId` is byte-for-byte the input.
|
|
283
372
|
- **The HL spine is computed, never caller-supplied.** The builder
|
|
@@ -287,26 +376,26 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
287
376
|
hierarchy is unrepresentable and SE-01 is correct by construction.
|
|
288
377
|
- **Refusal, not silent corruption.** The builder REFUSES a
|
|
289
378
|
structurally impossible spec via a typed `Eligibility271BuildError`
|
|
290
|
-
(`X12_271_BUILD_INVALID_HIERARCHY
|
|
291
|
-
a childless receiver; `X12_271_BUILD_INVALID_SPEC
|
|
379
|
+
(`X12_271_BUILD_INVALID_HIERARCHY`: no source / a childless source /
|
|
380
|
+
a childless receiver; `X12_271_BUILD_INVALID_SPEC`: over-long control
|
|
292
381
|
number) or `ClaimStatus277BuildError`
|
|
293
|
-
(`X12_277_BUILD_INVALID_HIERARCHY
|
|
382
|
+
(`X12_277_BUILD_INVALID_HIERARCHY`: no source / a childless source /
|
|
294
383
|
receiver / provider / a subscriber with neither claim nor dependent /
|
|
295
|
-
a childless dependent; `X12_277_BUILD_INVALID_SPEC
|
|
384
|
+
a childless dependent; `X12_277_BUILD_INVALID_SPEC`: a claim with no
|
|
296
385
|
trace / status / service line, an STC with no category code, an
|
|
297
386
|
over-long control number). The message carries structural locators
|
|
298
387
|
only (`source[0].receiver[0].provider[0].subscriber[0]`, level codes,
|
|
299
|
-
counts)
|
|
388
|
+
counts), never a member name, member id, or trace (PHI discipline).
|
|
300
389
|
- New public exports: `build271`, `Eligibility271BuildError`,
|
|
301
390
|
`ELIGIBILITY_271_BUILD_ERROR_CODES`, `Eligibility271BuildErrorCode`,
|
|
302
391
|
the `Build271Spec` type tree; `build277`, `build277CA`,
|
|
303
392
|
`ClaimStatus277BuildError`, `CLAIM_STATUS_277_BUILD_ERROR_CODES`,
|
|
304
393
|
`ClaimStatus277BuildErrorCode`, and the `Build277Spec` type tree.
|
|
305
|
-
- **Domain builders
|
|
394
|
+
- **Domain builders: `build837P` / `build837I` / `build837D` (005010
|
|
306
395
|
837 Health Care Claim: Professional `X222A2`, Institutional `X223A3`,
|
|
307
396
|
Dental `X224A2`).** The claim-submission emit counterpart to
|
|
308
397
|
`get837Claims`, layered on the Phase 8 general builder and mirroring
|
|
309
|
-
the pure-function `build835` pattern
|
|
398
|
+
the pure-function `build835` pattern. They NEVER auto-send, open a
|
|
310
399
|
socket, or touch the filesystem.
|
|
311
400
|
- **`build837P/I/D(spec)`** each assemble a complete `X12Interchange`
|
|
312
401
|
(one GS..GE group, GS-01 `HC`; one ST..SE 837, ST-03 per variant)
|
|
@@ -334,17 +423,17 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
334
423
|
line whose `variant` mismatches the builder, an empty procedure /
|
|
335
424
|
revenue code, an over-long control number). The message carries
|
|
336
425
|
structural locators only (`billing[0].subscriber[0].claim[0]`, level
|
|
337
|
-
codes, counts)
|
|
426
|
+
codes, counts), never the `claimId` or a member id (PHI discipline).
|
|
338
427
|
- New public exports: `build837P`, `build837I`, `build837D`,
|
|
339
428
|
`Claim837BuildError`, `CLAIM_837_BUILD_ERROR_CODES`,
|
|
340
429
|
`Claim837BuildErrorCode`, and the `Build837Spec` type tree.
|
|
341
430
|
- Known limitation: claim-/line-level provider addresses (Loop
|
|
342
|
-
2310/2420 N3/N4) are a documented read-side limitation
|
|
431
|
+
2310/2420 N3/N4) are a documented read-side limitation: the NM1
|
|
343
432
|
fields round-trip, the address does not.
|
|
344
|
-
- **Domain builder
|
|
433
|
+
- **Domain builder: `build835` (005010X221A1 ERA).** The first
|
|
345
434
|
per-transaction emit helper layers the safety-critical TR3 invariants
|
|
346
435
|
on top of the Phase 8 general builder, mirroring the pure-function
|
|
347
|
-
`build999` / `buildTA1` pattern
|
|
436
|
+
`build999` / `buildTA1` pattern. It NEVER auto-sends, opens a socket,
|
|
348
437
|
or touches the filesystem.
|
|
349
438
|
- **`build835(spec)`** assembles a complete `X12Interchange` (one
|
|
350
439
|
GS..GE group, GS-01 `HP`; one ST..SE 835, ST-03 `005010X221A1`) from
|
|
@@ -354,7 +443,7 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
354
443
|
claims → Loop 2110 service lines → PLB) and the output round-trips
|
|
355
444
|
through `parseX12` so a balanced spec is reproduced by `get835`
|
|
356
445
|
field-for-field. Composites (CLP-08, SVC-01, SVC-06, PLB) escape
|
|
357
|
-
each component then join with the raw component separator
|
|
446
|
+
each component then join with the raw component separator. The
|
|
358
447
|
envelope is emitted inline (not via `buildInterchange`) to avoid
|
|
359
448
|
double-escaping a pre-composed element. Same-group CAS and
|
|
360
449
|
same-provider/period PLB adjustments pack into one segment (≤ 6
|
|
@@ -366,10 +455,10 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
366
455
|
(`checkServiceLineBalance` / `checkClaimBalance` /
|
|
367
456
|
`checkRemitTotalBalance`) against a materialized read model so emit
|
|
368
457
|
guard and parse warning share one source of truth. Codes:
|
|
369
|
-
`X12_835_BUILD_BALANCE_MISMATCH` (any §1.10.2 invariant
|
|
458
|
+
`X12_835_BUILD_BALANCE_MISMATCH` (any §1.10.2 invariant: line,
|
|
370
459
|
claim, or top-of-remit) and `X12_835_BUILD_INVALID_SPEC` (no TRN
|
|
371
460
|
trace, an empty CLP-01, an over-long ISA-13). The thrown message
|
|
372
|
-
carries numeric totals only
|
|
461
|
+
carries numeric totals only, never a patient-control number or
|
|
373
462
|
member id (PHI discipline).
|
|
374
463
|
- **New exports.** `build835`, `Remit835BuildError`,
|
|
375
464
|
`REMIT_835_BUILD_ERROR_CODES`, `Remit835BuildErrorCode`, and the
|
|
@@ -383,14 +472,14 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
383
472
|
`build820` / `build834`) layer on the same general surface and are
|
|
384
473
|
NOT in this change.
|
|
385
474
|
|
|
386
|
-
- **Phase 8
|
|
475
|
+
- **Phase 8: spec-clean serializer + general interchange builder (the
|
|
387
476
|
emit half lands).** Two new public surfaces close the read↔write loop.
|
|
388
477
|
- **`serializeX12(interchange, opts?)`** turns any parsed
|
|
389
478
|
`X12Interchange` back into an X12 byte stream. Default mode is
|
|
390
|
-
byte-faithful
|
|
479
|
+
byte-faithful: reconstructed purely from the verbatim `.raw`
|
|
391
480
|
strings the parser preserved (ISA + terminator, then each
|
|
392
481
|
TA1 / GS / segment / GE / IEA terminator-joined, then any
|
|
393
|
-
`trailingBytes`)
|
|
482
|
+
`trailingBytes`), so for a Tier-1 input it reproduces the source
|
|
394
483
|
bytes exactly: the idempotency fixed point
|
|
395
484
|
`serialize(parse(s)) === s`. With `{ specClean: true }` it ALSO
|
|
396
485
|
reconciles the envelope (SE-01 / GE-01 / IEA-01 counts + the
|
|
@@ -406,7 +495,7 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
406
495
|
body values via the `?` release char, and round-trips its output
|
|
407
496
|
back through `parseX12` so the returned interchange is bit-identical
|
|
408
497
|
to the parsed form. Structurally impossible specs are REFUSED with a
|
|
409
|
-
typed `X12BuildError` (`X12_BUILD_INVALID_SPEC`)
|
|
498
|
+
typed `X12BuildError` (`X12_BUILD_INVALID_SPEC`): an over-long
|
|
410
499
|
ISA-13, a body segment with no id.
|
|
411
500
|
- **New warning + exports.** `X12_SEGMENT_COUNT_MISMATCH` is a
|
|
412
501
|
serializer-only diagnostic (the parser never validated SE-01);
|
|
@@ -428,14 +517,14 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
428
517
|
validated (it checks GE-01 / IEA-01 / control pairs but not SE-01):
|
|
429
518
|
SE-01 miscounts in `837i-canonical` (30→33), `837d-canonical`
|
|
430
519
|
(25→26), `999-accept` (5→6), and a GS-06/GE-02 mismatch in
|
|
431
|
-
`278-response` (GS-06 2→1)
|
|
520
|
+
`278-response` (GS-06 2→1), an accuracy-gate win.
|
|
432
521
|
- **Known limitation (deferred).** Domain per-transaction builders
|
|
433
522
|
(`build835` / `build837P/I/D` / `build271` / …, the safety-critical
|
|
434
523
|
emit code enforcing per-TR3 balance + certification invariants) are
|
|
435
524
|
NOT in this phase; the general envelope surface they layer on top of
|
|
436
525
|
is.
|
|
437
526
|
|
|
438
|
-
- **Phase 7
|
|
527
|
+
- **Phase 7: 278 services review + 834 enrollment + 820 premium
|
|
439
528
|
payment (the v1 transaction scope rounds out).** Four new read-side
|
|
440
529
|
helpers: `get278Request` / `get278Response` (TR3 `005010X217` /
|
|
441
530
|
`005010X216`), `get820Payments` (TR3 `005010X218`), and the streaming
|
|
@@ -444,13 +533,13 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
444
533
|
278 response `HCR-01` certification action (certified /
|
|
445
534
|
not-certified / pended / modified) is captured as-is on each event /
|
|
446
535
|
service review; the 834 `INS-03` / `HD-01` maintenance type (X12 0875) is preserved and an unknown code raises
|
|
447
|
-
`X12_834_UNKNOWN_MAINTENANCE_TYPE` on the affected member only
|
|
536
|
+
`X12_834_UNKNOWN_MAINTENANCE_TYPE` on the affected member only. No
|
|
448
537
|
action is ever synthesized.
|
|
449
538
|
- **834 streaming.** `get834Enrollments` is an
|
|
450
539
|
`AsyncIterable<X12Enrollment>` yielding one member per `INS` loop;
|
|
451
540
|
a streaming property test drives a 10MB+ synthetic roster with
|
|
452
541
|
early-break. (Honest limitation: v1 still parses into `tx.segments`
|
|
453
|
-
up front
|
|
542
|
+
up front. A true file→iterator source is a v2 item.)
|
|
454
543
|
- **278 HL spine** `20 → 21 → 22 → 23` validated via the shared
|
|
455
544
|
`validateHl`; the `EV` / `SS` event + service levels are
|
|
456
545
|
deliberately tolerant (omitted from the expected-parent map).
|
|
@@ -466,7 +555,7 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
466
555
|
(H-PHI invariant). Synthetic fixtures across all three surfaces,
|
|
467
556
|
unit tests, and the 834 streaming property. Serialization is
|
|
468
557
|
Phase 8.
|
|
469
|
-
- **PHI commit-gate
|
|
558
|
+
- **PHI commit-gate: a zero-dependency, X12-shape-aware PHI scanner
|
|
470
559
|
(`scripts/phi-scan.ts`, run via `pnpm phi-scan`).** Guards the
|
|
471
560
|
synthetic fixture corpus: it refuses any test fixture or `src/` file
|
|
472
561
|
carrying real-PHI-shaped tokens so a developer cannot commit a
|
|
@@ -476,7 +565,7 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
476
565
|
`scripts/verify.sh` summary from `phi-scan SKIP` to `phi-scan ✓`.
|
|
477
566
|
- **Synthetic allow-list, not an inline header.** X12 `.edi` is
|
|
478
567
|
byte-strict (ISA must start at byte 0), so an inline
|
|
479
|
-
`# synthetic: true` marker is impossible
|
|
568
|
+
`# synthetic: true` marker is impossible. It would break every
|
|
480
569
|
parser test. Same constraint DICOM hits with binary `.dcm`, so the
|
|
481
570
|
same proven solution: `scripts/phi-allow-list.txt` positively
|
|
482
571
|
declares which names / dates-of-birth / ids / email-domains are
|
|
@@ -491,17 +580,17 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
491
580
|
- **Audited bypass.** A whole-file `--allow-fixture <path>` is
|
|
492
581
|
rejected unless `phi-scan-overrides.md` carries a matching
|
|
493
582
|
`### <path>` entry, so a silenced file is always a recorded act.
|
|
494
|
-
Every subprocess is `git` via `execFileSync` array args
|
|
583
|
+
Every subprocess is `git` via `execFileSync` array args, no shell
|
|
495
584
|
form. Unit tests cover the clean interchange, each violator class,
|
|
496
585
|
the plain-text pass, and both arms of the override gate.
|
|
497
586
|
|
|
498
|
-
- **Phase 6
|
|
499
|
-
Status
|
|
587
|
+
- **Phase 6: 271 Eligibility Benefit Response + 277 / 277CA Claim
|
|
588
|
+
Status, TR3s `005010X279A1` (270/271), `005010X212` (276/277),
|
|
500
589
|
`005010X214` (277CA).** Three new public walkers:
|
|
501
590
|
`get271Eligibility(delimiters, tx)`, `get277Status(delimiters, tx)`,
|
|
502
591
|
and `get277CADisposition(delimiters, tx)`. 277 and 277CA share one
|
|
503
592
|
internal walk disambiguated by the `ST-03` implementation-convention
|
|
504
|
-
reference
|
|
593
|
+
reference: `get277CADisposition` admits only `005010X214`;
|
|
505
594
|
`get277Status` admits either. Each returns `undefined` only on a
|
|
506
595
|
mis-routed call (wrong `ST-01`); every recoverable deviation is a
|
|
507
596
|
warning, never a throw.
|
|
@@ -509,7 +598,7 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
509
598
|
requesting 270's `TRN-02` trace verbatim onto its enclosing
|
|
510
599
|
subscriber / dependent, and a 277 echoes the 276's onto its claim,
|
|
511
600
|
so the provider can re-associate the answer with the request it
|
|
512
|
-
sent
|
|
601
|
+
sent. The walkers NEVER mutate, normalize, or drop the trace. A
|
|
513
602
|
round-trip property test asserts byte-for-byte echo across an
|
|
514
603
|
arbitrary trace grammar.
|
|
515
604
|
- **Status-code fidelity (277 family).** Each STC composite
|
|
@@ -521,7 +610,7 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
521
610
|
`X12_UNKNOWN_CLAIM_STATUS`. A 277CA provider-level batch
|
|
522
611
|
acknowledgment opens a claim on a standalone STC (no TRN).
|
|
523
612
|
- **HL parent-pointer integrity.** Enforced through the shared
|
|
524
|
-
`validateHl` primitive
|
|
613
|
+
`validateHl` primitive: 271 spine `20 → 21 → 22 → 23`; 277 / 277CA
|
|
525
614
|
spine `20 → 21 → 19 → 22 → 23`. A dangling or mis-levelled parent
|
|
526
615
|
emits `X12_HL_PARENT_MISMATCH` / `X12_HL_PARENT_LEVEL_INVALID`; the
|
|
527
616
|
walker NEVER silently re-numbers and the verbatim declared parent id
|
|
@@ -545,16 +634,16 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
545
634
|
HSD detail, and III / LS / LE markers in the 271, plus QTY / AMT
|
|
546
635
|
claim-summary roll-ups in a 277CA Loop 2200, are preserved on
|
|
547
636
|
`tx.segments` verbatim but not yet typed onto the model.
|
|
548
|
-
- **Phase 5
|
|
637
|
+
- **Phase 5: 837 Healthcare Claim, TR3s `005010X222A2` (Professional),
|
|
549
638
|
`005010X223A3` (Institutional), `005010X224A2` (Dental).** The
|
|
550
|
-
claim-creation surface
|
|
639
|
+
claim-creation surface: the volume side of HIPAA EDI traffic.
|
|
551
640
|
`get837Claims(delimiters, tx, opts?)` walks a parsed 837 transaction
|
|
552
641
|
set into the typed `X12_837Submission` model: variant detection (from
|
|
553
642
|
ST-03 implementation-convention reference, falling back to SVx
|
|
554
643
|
segment id, then to `"unknown"` with `X12_837_UNKNOWN_VARIANT`),
|
|
555
644
|
submitter (Loop 1000A NM1\*41) + receiver (Loop 1000B NM1\*40), the
|
|
556
645
|
full HL hierarchy (Loops 2000A / 2000B / 2000C), every claim header
|
|
557
|
-
(Loop 2300
|
|
646
|
+
(Loop 2300: CLM with patient account number, total charge,
|
|
558
647
|
composite POS / facility-code-qualifier / claim-frequency-code,
|
|
559
648
|
signature / assignment / benefits / release-of-information
|
|
560
649
|
indicators), and every service line typed by variant (`SV1` →
|
|
@@ -562,18 +651,18 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
562
651
|
- **HL parent-pointer integrity.** The 837 family's safety primitive
|
|
563
652
|
is the HL hierarchy (`HL-01` own id, `HL-02` parent id, `HL-03`
|
|
564
653
|
level code: `20` Information Source / `22` Subscriber / `23`
|
|
565
|
-
Dependent). An off-by-one in `HL-02` is THE #1 837 bug
|
|
654
|
+
Dependent). An off-by-one in `HL-02` is THE #1 837 bug. The
|
|
566
655
|
walker validates that every non-top-level HL's `HL-02` references
|
|
567
656
|
an earlier-emitted `HL-01` AND that the parent's level matches the
|
|
568
657
|
TR3-required parent for this level (`22` → parent `20`; `23` →
|
|
569
658
|
parent `22`). Violations emit `X12_HL_PARENT_MISMATCH` or
|
|
570
|
-
`X12_HL_PARENT_LEVEL_INVALID
|
|
659
|
+
`X12_HL_PARENT_LEVEL_INVALID`. The parser NEVER silently
|
|
571
660
|
re-numbers. The verbatim declared parent id stays on the
|
|
572
661
|
`X12HierarchicalLevel` entry.
|
|
573
662
|
- **HI qualifier → code-system provenance.** `HI` carries
|
|
574
663
|
diagnoses, principal procedures, external cause of injury,
|
|
575
664
|
condition codes, occurrence codes, value codes, and DRG / PR
|
|
576
|
-
groupings under one segment id
|
|
665
|
+
groupings under one segment id, with the qualifier (first
|
|
577
666
|
component) governing the code system. The new
|
|
578
667
|
`src/code-lists/hi-qualifiers.ts` ships a frozen `HI_QUALIFIERS`
|
|
579
668
|
registry covering the qualifiers cited across the three TR3s
|
|
@@ -605,16 +694,16 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
605
694
|
re-parsing the literal.
|
|
606
695
|
- **Variant-specific service-line types.** The
|
|
607
696
|
{@link X12_837ServiceLine} discriminated union holds three shapes:
|
|
608
|
-
- `X12_837ServiceLineProfessional
|
|
697
|
+
- `X12_837ServiceLineProfessional`: `procedureQualifier` /
|
|
609
698
|
`procedureCode` / `modifiers` from SV1-01 composite; 1-4
|
|
610
699
|
`diagnosisPointers` from SV1-07; emergency / EPSDT / family-
|
|
611
700
|
planning indicators; optional `drug` (Loop 2410 LIN + CTP NDC +
|
|
612
701
|
UCUM unit).
|
|
613
|
-
- `X12_837ServiceLineInstitutional
|
|
702
|
+
- `X12_837ServiceLineInstitutional`: `revenueCode` (NUBC 4-digit
|
|
614
703
|
from SV2-01); optional procedure / modifiers from SV2-02
|
|
615
704
|
composite; `serviceLineRate` (SV2-06); `nonCoveredCharge`
|
|
616
705
|
(SV2-07).
|
|
617
|
-
- `X12_837ServiceLineDental
|
|
706
|
+
- `X12_837ServiceLineDental`: ADA CDT `procedureCode` from
|
|
618
707
|
SV3-01; `oralCavityArea` composite from SV3-04; per-line
|
|
619
708
|
`toothInformation` from `TOO*JP` (Universal Tooth Numbering)
|
|
620
709
|
with surface codes from TOO-03's composite components;
|
|
@@ -623,7 +712,7 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
623
712
|
`serviceLine.adjudications` as `X12LineAdjudication[]`. Each
|
|
624
713
|
adjudication ships the other-payer id (SVD-01), amount paid as
|
|
625
714
|
`X12Decimal` (SVD-02), the other payer's procedure code, paid
|
|
626
|
-
units, and any CAS adjustments
|
|
715
|
+
units, and any CAS adjustments, re-using `X12RemitAdjustment` /
|
|
627
716
|
`lookupCarc` from the 835 helper since CAS semantics are
|
|
628
717
|
identical.
|
|
629
718
|
- **Loop 2320 Other Subscriber (COB).** Captured at the surface
|
|
@@ -631,9 +720,9 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
631
720
|
individual relationship, claim filing indicator, and the
|
|
632
721
|
other-subscriber + other-payer NM1 entities. Detailed CAS / OI /
|
|
633
722
|
MOA breakdown inside Loop 2320 is deferred to Phase 9 (companion-
|
|
634
|
-
guide tolerance)
|
|
723
|
+
guide tolerance). Verbatim segments remain on `tx.segments`.
|
|
635
724
|
- **Eleven dogfooded `LoopSpec` artifacts** ship through the public
|
|
636
|
-
`defineLoopSpec()` API
|
|
725
|
+
`defineLoopSpec()` API, the dogfooding gate locked in Phase 2.
|
|
637
726
|
`CLAIM_837_LOOP_1000A` / `_1000B` (submitter / receiver),
|
|
638
727
|
`CLAIM_837_LOOP_2010AA` (billing provider name), `_2010BA`
|
|
639
728
|
(subscriber name), `_2010BB` (payer name), `_2010CA` (patient
|
|
@@ -642,7 +731,7 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
642
731
|
`CLAIM_837{P,I,D}_LOOP_2000A` / `_2300` / `_2400` trees.
|
|
643
732
|
- **Bundled HI qualifier registry under
|
|
644
733
|
`src/code-lists/hi-qualifiers.ts`** alongside the existing CARC /
|
|
645
|
-
RARC / CLP_STATUS / CAGC snapshots
|
|
734
|
+
RARC / CLP_STATUS / CAGC snapshots, formally part of the
|
|
646
735
|
code-list family, not a transaction-local table.
|
|
647
736
|
- **Two new exported constants for safety + ergonomics:**
|
|
648
737
|
`HL_LEVEL_CODES` (`INFORMATION_SOURCE` `"20"` / `INFORMATION_RECEIVER`
|
|
@@ -650,21 +739,21 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
650
739
|
`NM1_QUALIFIERS` (`SUBMITTER` `"41"` / `RECEIVER` `"40"` /
|
|
651
740
|
`BILLING_PROVIDER` `"85"` / `PAY_TO_ADDRESS` `"87"` /
|
|
652
741
|
`PAY_TO_PLAN` `"PE"` / `SUBSCRIBER` `"IL"` / `PAYER` `"PR"` /
|
|
653
|
-
`PATIENT` `"QC"`)
|
|
742
|
+
`PATIENT` `"QC"`), so the walker (and any consumer Phase 8
|
|
654
743
|
builder) never has to magic-string the safety-critical
|
|
655
744
|
discriminators.
|
|
656
|
-
- **Six new shared element-read helpers in `parser/segment.ts
|
|
745
|
+
- **Six new shared element-read helpers in `parser/segment.ts`**:
|
|
657
746
|
`elementValue` / `elementOptional` / `componentOptional` /
|
|
658
|
-
`elementDecimal` / `elementDecimalOrZero` / `collectElementValues
|
|
659
|
-
|
|
747
|
+
`elementDecimal` / `elementDecimalOrZero` / `collectElementValues`,
|
|
748
|
+
extracted out of the 835 and 837 walkers (both walkers had
|
|
660
749
|
byte-identical copies). New transaction walkers (Phase 6+ 270/271,
|
|
661
|
-
277, 834) inherit them. Public surface
|
|
750
|
+
277, 834) inherit them. Public surface: exported via
|
|
662
751
|
`@cosyte/x12`.
|
|
663
752
|
- **Public-surface additions** to the warning stability snapshot:
|
|
664
753
|
`X12_HL_PARENT_MISMATCH`, `X12_HL_PARENT_LEVEL_INVALID`,
|
|
665
754
|
`X12_UNKNOWN_HI_QUALIFIER`, `X12_MISSING_REQUIRED_LOOP`,
|
|
666
|
-
`X12_837_UNKNOWN_VARIANT` (13 → 18 Tier-2 codes; additions-only
|
|
667
|
-
|
|
755
|
+
`X12_837_UNKNOWN_VARIANT` (13 → 18 Tier-2 codes; additions-only,
|
|
756
|
+
fatal registry stays at 4). All new warning factories
|
|
668
757
|
(`hlParentMismatch` / `hlParentLevelInvalid` /
|
|
669
758
|
`unknownHiQualifier` / `missingRequiredLoop` /
|
|
670
759
|
`unknown837Variant`) shape-validate echoed values through
|
|
@@ -672,11 +761,11 @@ EV/SS`), and HL-04 has-child flag from the nested input tree, so an
|
|
|
672
761
|
`/^[0-9]{2}$/u` for level codes, `/^[A-Z][A-Z0-9]{1,2}$/u` for HI
|
|
673
762
|
qualifiers, `/^[0-9A-Z]{3,6}$/u` for loop ids,
|
|
674
763
|
`/^[0-9A-Z]{3,16}$/u` for ICR) and substitute `(non-spec)` for
|
|
675
|
-
hostile inputs
|
|
764
|
+
hostile inputs, the H-PHI invariant from `@cosyte/hl7`.
|
|
676
765
|
- **PHI discipline.** Warnings NEVER echo field VALUES; the
|
|
677
766
|
`missingRequiredLoop` rationale strings are hard-coded literals
|
|
678
767
|
(no element interpolation). Patient names / member IDs / NPIs /
|
|
679
|
-
claim numbers are surfaced verbatim on the typed model only
|
|
768
|
+
claim numbers are surfaced verbatim on the typed model only, the
|
|
680
769
|
documented consumer-redaction boundary (mirrors hl7 + the 835
|
|
681
770
|
helper). The `X12ClaimNote` JSDoc explicitly flags NTE-02 as
|
|
682
771
|
PHI-bearing (provider-supplied free text). Every Phase 5 fixture
|
|
@@ -685,10 +774,10 @@ CHILD`; sequential member IDs `MEMBER001`–`MEMBER011` etc.; NPI-
|
|
|
685
774
|
shaped sequential numbers; obvious test addresses) and matches
|
|
686
775
|
the established 835 fixture conventions.
|
|
687
776
|
- **Known limitations after this phase** (deliberate v1 scope; none
|
|
688
|
-
silent
|
|
777
|
+
silent, verbatim segments remain on `tx.segments` for raw
|
|
689
778
|
access):
|
|
690
779
|
- Loop 2320/2330 Other Subscriber / Other Payer captured at the
|
|
691
|
-
surface level only
|
|
780
|
+
surface level only. Detailed CAS / OI / MOA inside Loop 2320
|
|
692
781
|
deferred to Phase 9 (companion-guide profile system).
|
|
693
782
|
- Loop 2420 service-line provider names captured verbatim on
|
|
694
783
|
`serviceLine.providers`; per-provider PRV + address not yet
|
|
@@ -723,7 +812,7 @@ CHILD`; sequential member IDs `MEMBER001`–`MEMBER011` etc.; NPI-
|
|
|
723
812
|
coverage (96.91% stmts / 90.61% branches / 97.67% funcs / 98.49%
|
|
724
813
|
lines globally; per-dir ≥90 on `parser/` + `loops/` +
|
|
725
814
|
`transactions/` + `code-lists/`) + build + attw + verify:exports.
|
|
726
|
-
- **`phi-scan` SKIP
|
|
815
|
+
- **`phi-scan` SKIP**, unchanged from Phase 4. The runtime H-PHI
|
|
727
816
|
invariant is necessary but not sufficient; static fixture
|
|
728
817
|
scanning is tracked as the `X12-PHI-SCAN` backlog follow-up.
|
|
729
818
|
|
|
@@ -733,7 +822,7 @@ CHILD`; sequential member IDs `MEMBER001`–`MEMBER011` etc.; NPI-
|
|
|
733
822
|
`elementValue` / `elementOptional` / `componentOptional` /
|
|
734
823
|
`elementDecimal` / `elementDecimalOrZero` / `collectElementValues`.
|
|
735
824
|
Re-used by the 835 helper (`get835`) and the new 837 helper
|
|
736
|
-
(`get837Claims`)
|
|
825
|
+
(`get837Claims`). Both walkers previously defined byte-identical
|
|
737
826
|
copies of these inline. Additive; no breaking change.
|
|
738
827
|
|
|
739
828
|
- **`src/code-lists/` gains `hi-qualifiers.ts`** with `HI_QUALIFIERS`
|
|
@@ -741,26 +830,26 @@ CHILD`; sequential member IDs `MEMBER001`–`MEMBER011` etc.; NPI-
|
|
|
741
830
|
`isProcedureQualifier` and the `X12HiCategory` / `X12HiCodeSystem`
|
|
742
831
|
/ `X12HiQualifier` types. Re-exported from `@cosyte/x12` root.
|
|
743
832
|
|
|
744
|
-
- **Phase 4
|
|
745
|
-
`005010X221A1`.** The cash-posting surface
|
|
833
|
+
- **Phase 4: 835 Healthcare Claim Payment/Advice (ERA), TR3
|
|
834
|
+
`005010X221A1`.** The cash-posting surface: money, the consultant ask.
|
|
746
835
|
`get835(delimiters, tx)` walks a parsed 835 transaction set into the
|
|
747
836
|
typed `X12Remittance` model: payment header (BPR), trace numbers (TRN),
|
|
748
837
|
payer / payee parties (Loops 1000A / 1000B with address / contact /
|
|
749
|
-
additional identifiers), every claim payment (Loop 2100
|
|
838
|
+
additional identifiers), every claim payment (Loop 2100: CLP plus
|
|
750
839
|
patient / subscriber / service-provider NM1s, statement-period DTMs,
|
|
751
840
|
CAS adjustments at both claim and service-line scope, MIA / MOA / LQ
|
|
752
841
|
remarks, REF / AMT supplemental amounts), every service line (Loop
|
|
753
|
-
2110
|
|
842
|
+
2110: SVC with HCPCS / CPT / NDC / revenue-code / modifier
|
|
754
843
|
destructuring, service-date DTMs, line-level CAS / REF / AMT / LQ),
|
|
755
844
|
and provider-level adjustments (PLB with multi-pair flattening). The
|
|
756
845
|
loop hierarchy ships as three frozen `LoopSpec` artifacts
|
|
757
846
|
(`REMIT_835_LOOP_2000`, `REMIT_835_LOOP_2100`, `REMIT_835_LOOP_2110`)
|
|
758
|
-
authored through the public `defineLoopSpec()` API
|
|
847
|
+
authored through the public `defineLoopSpec()` API, the **dogfooding
|
|
759
848
|
gate** locked in Phase 2. Two payer-side loop specs (1000A / 1000B)
|
|
760
849
|
also ship as introspection artifacts.
|
|
761
850
|
- **Money discipline.** All monetary fields decode as the new
|
|
762
851
|
`X12Decimal` (`src/decimal.ts`): a string-backed decimal type with
|
|
763
|
-
`BigInt`-exact arithmetic. **NEVER `parseFloat
|
|
852
|
+
`BigInt`-exact arithmetic. **NEVER `parseFloat`**: float
|
|
764
853
|
representation silently destroys cents at scale; on an 835 a dropped
|
|
765
854
|
decimal is the wrong dollar amount in someone's cash post.
|
|
766
855
|
`X12Decimal` preserves the inbound lexical form for byte-exact
|
|
@@ -771,29 +860,29 @@ CHILD`; sequential member IDs `MEMBER001`–`MEMBER011` etc.; NPI-
|
|
|
771
860
|
`toNumber()` whose JSDoc warns about precision loss. `fromBigInt(value,
|
|
772
861
|
scale)` renders canonically with zero-padded fractions; the canonical
|
|
773
862
|
`X12Decimal.ZERO` is the additive identity. Empty inbound element →
|
|
774
|
-
`undefined` (not zero)
|
|
863
|
+
`undefined` (not zero): "not supplied" and "zero dollars" are
|
|
775
864
|
spec-distinct.
|
|
776
|
-
- **Balance invariants (per TR3 X221A1 §1.10.2
|
|
865
|
+
- **Balance invariants (per TR3 X221A1 §1.10.2: "Balancing the 835").**
|
|
777
866
|
Three checks run after the walk and emit
|
|
778
|
-
`X12_835_REMIT_BALANCE_MISMATCH` on mismatch
|
|
867
|
+
`X12_835_REMIT_BALANCE_MISMATCH` on mismatch. The model is **NEVER
|
|
779
868
|
silently rebalanced**: 1. Line: `SVC-02 === SVC-03 + Σ(line CAS)` per Loop 2110. 2. Claim: `CLP-03 === CLP-04 + Σ(all CAS in claim, claim AND line
|
|
780
|
-
level)
|
|
869
|
+
level)`, the X12 spec balance. CLP-05 (patient responsibility)
|
|
781
870
|
is informational, NOT part of the balance equation. The
|
|
782
871
|
implementation matches the TR3 §1.10.2 text directly; an earlier
|
|
783
872
|
roadmap sketch (`operations/roadmaps/x12.md` §4) used a slightly
|
|
784
|
-
different decomposition
|
|
873
|
+
different decomposition. `src/transactions/remit/balance.ts`
|
|
785
874
|
documents the divergence so the contract stays consistent. 3. Top-of-remit: `BPR-02 === Σ(CLP-04) - Σ(PLB amounts)`. PLB
|
|
786
875
|
amounts are stored with the **raw EDI sign** (positive = take-back
|
|
787
876
|
from provider; negative = credit to provider), so the equation
|
|
788
877
|
_subtracts_ PLB to balance.
|
|
789
878
|
Warning messages echo only the invariant label and `X12Decimal`
|
|
790
|
-
decimal text
|
|
879
|
+
decimal text, never patient identifiers, member ids, or account
|
|
791
880
|
numbers (H-PHI invariant).
|
|
792
881
|
- **CAS triple flattening.** A single CAS segment can carry up to 6
|
|
793
882
|
`(reason, amount, quantity)` triples under one `CAS-01` group code;
|
|
794
883
|
the walker flattens them into individual `X12RemitAdjustment`
|
|
795
884
|
entries. Different group codes (CO / PR / OA / PI) require separate
|
|
796
|
-
CAS segments
|
|
885
|
+
CAS segments (they cannot mix inside one) and the decoder honors
|
|
797
886
|
that contract.
|
|
798
887
|
- **Bundled WPC + X12-internal code-list snapshots** (initial
|
|
799
888
|
subsets, pre-launch). Versioned data artifacts at
|
|
@@ -805,24 +894,24 @@ level)` — the X12 spec balance. CLP-05 (patient responsibility)
|
|
|
805
894
|
`lookupRarc(code)` / `lookupClpStatus(code)` return `{ code,
|
|
806
895
|
description }` for known codes, `undefined` otherwise; unknown
|
|
807
896
|
codes preserve the verbatim value on the parsed adjustment AND
|
|
808
|
-
emit `X12_UNKNOWN_CARC` / `X12_UNKNOWN_RARC`. - `CARC` (Claim Adjustment Reason Codes)
|
|
809
|
-
observed codes (WPC, snapshotDate 2026-06-27). - `RARC` (Remittance Advice Remark Codes)
|
|
897
|
+
emit `X12_UNKNOWN_CARC` / `X12_UNKNOWN_RARC`. - `CARC` (Claim Adjustment Reason Codes): ~30 most commonly
|
|
898
|
+
observed codes (WPC, snapshotDate 2026-06-27). - `RARC` (Remittance Advice Remark Codes): ~15 most commonly
|
|
810
899
|
observed codes covering both `M`- and `N`-prefix conventions
|
|
811
|
-
(WPC, snapshotDate 2026-06-27). - `CLP_STATUS` (CLP-02 Claim Status Code, X12 Code Source 65)
|
|
900
|
+
(WPC, snapshotDate 2026-06-27). - `CLP_STATUS` (CLP-02 Claim Status Code, X12 Code Source 65):
|
|
812
901
|
10 dispositions (1 Processed as Primary, 4 Denied, 22 Reversal,
|
|
813
|
-
…). X12-internal list, stable. - `CLAIM_ADJUSTMENT_GROUP_CODES
|
|
902
|
+
…). X12-internal list, stable. - `CLAIM_ADJUSTMENT_GROUP_CODES`: the spec-fixed 4 values
|
|
814
903
|
(`CO` / `PR` / `OA` / `PI`) as a frozen literal-union map,
|
|
815
904
|
not a snapshot (this list never grows). `isClaimAdjustmentGroupCode`
|
|
816
905
|
narrows inbound strings.
|
|
817
906
|
- **Public-surface additions** to the warning / fatal stability
|
|
818
907
|
snapshot: `X12_835_REMIT_BALANCE_MISMATCH`,
|
|
819
908
|
`X12_UNKNOWN_CARC`, `X12_UNKNOWN_RARC` (10 → 13 Tier-2 codes;
|
|
820
|
-
additions-only
|
|
909
|
+
additions-only, fatal registry stays at 4). New warning factories
|
|
821
910
|
`remitBalanceMismatch` / `unknownCarc` / `unknownRarc` carry the
|
|
822
911
|
shape-validated echo discipline (CARC / RARC echoes pass
|
|
823
912
|
`/^[A-Z0-9]{1,5}$/u` or collapse to `(non-spec)`).
|
|
824
913
|
- **PHI discipline (H-PHI invariant holds suite-wide).** Warning
|
|
825
|
-
messages never echo field VALUES
|
|
914
|
+
messages never echo field VALUES, only positional context, the
|
|
826
915
|
invariant label, the shape-validated CARC / RARC code, or numeric
|
|
827
916
|
X12Decimal text. Patient names, member ids, NPIs, payer claim
|
|
828
917
|
control numbers, and account numbers are held verbatim on the
|
|
@@ -834,7 +923,7 @@ description }` for known codes, `undefined` otherwise; unknown
|
|
|
834
923
|
synthetic spec-clean (`835-medicare-canonical.edi`,
|
|
835
924
|
`835-multi-claim.edi`, `835-with-plb.edi`,
|
|
836
925
|
`835-carc-rarc-mix.edi`, `835-imbalance.edi`) and one Tier-2
|
|
837
|
-
synthetic quirk shape (`835-availity-quirk.edi
|
|
926
|
+
synthetic quirk shape (`835-availity-quirk.edi`: REF*2U + REF*F8
|
|
838
927
|
placements). The imbalance fixture is deliberately off-by-$10 to
|
|
839
928
|
prove the balance warning fires and the model preserves the
|
|
840
929
|
verbatim amounts.
|
|
@@ -846,7 +935,7 @@ description }` for known codes, `undefined` otherwise; unknown
|
|
|
846
935
|
the balance warning fires iff out of balance (100 + 50 runs).
|
|
847
936
|
`remit-835-fuzz.property.test.ts` byte-flips every committed
|
|
848
937
|
fixture 300 times per fixture and asserts `get835` never throws
|
|
849
|
-
outside the 4 Tier-3 fatals
|
|
938
|
+
outside the 4 Tier-3 fatals, the byte-level fuzz target the
|
|
850
939
|
roadmap calls for.
|
|
851
940
|
- **Coverage gates expanded** to per-directory ≥90 on `parser/`,
|
|
852
941
|
`loops/`, `transactions/`, `code-lists/`. Phase 4 lands the gate
|
|
@@ -856,24 +945,24 @@ description }` for known codes, `undefined` otherwise; unknown
|
|
|
856
945
|
Code Source 65 for CLP-02; WPC public-domain lists for CARC / RARC;
|
|
857
946
|
X12 Data Element 1033 for the Claim Adjustment Group Code.
|
|
858
947
|
- **Known limitations after Phase 4:** no 835 _building_ yet (that's
|
|
859
|
-
Phase 8
|
|
948
|
+
Phase 8: round-trip + spec-clean serializer + builder); the
|
|
860
949
|
bundled CARC / RARC are an **initial subset** (`pnpm
|
|
861
950
|
refresh:code-lists` arrives in Phase 10); no per-payer profile
|
|
862
951
|
yet (Phase 9); CPT / ICD-10 / NDC descriptions are deliberately
|
|
863
|
-
NOT bundled (license-gated
|
|
952
|
+
NOT bundled (license-gated, see `operations/roadmaps/x12.md` §5);
|
|
864
953
|
`X12Decimal` does not yet expose multiply / divide (no balance
|
|
865
954
|
invariant needs them in v1). `phi-scan` script not yet wired for
|
|
866
|
-
x12
|
|
955
|
+
x12. The H-PHI property tests provide runtime coverage; an
|
|
867
956
|
explicit pre-commit phi-scan ships in a future slice (tracked in
|
|
868
957
|
`operations/prompts/x12-phi-scan.md`).
|
|
869
958
|
|
|
870
|
-
- **Phase 3
|
|
959
|
+
- **Phase 3: 999 + TA1 acknowledgments (TR3 005010X231A1).** Two
|
|
871
960
|
pure-function ack surfaces ship side-by-side; neither auto-sends, opens
|
|
872
961
|
a socket, or touches the filesystem. The cosyte ack archetype: the
|
|
873
962
|
library MECHANICALLY builds the disposition it is told and REFUSES to
|
|
874
963
|
fabricate an Accept against a non-empty error list. Mirrors hl7's
|
|
875
964
|
upcoming `buildAck` boundary and mllp's commit-contract pattern.
|
|
876
|
-
- **999 (Implementation Acknowledgment)
|
|
965
|
+
- **999 (Implementation Acknowledgment): TR3 005010X231A1.**
|
|
877
966
|
`parse999(raw, opts?)` decodes the AK1 → AK2 → (IK3 [→ CTX] (IK4 [→
|
|
878
967
|
CTX])\*)\* → IK5 → AK9 hierarchy into the typed `X12Ack999`. Standard
|
|
879
968
|
X12 / pre-X231A1 legacy senders that emit `AK3`/`AK4`/`AK5` instead
|
|
@@ -882,15 +971,15 @@ refresh:code-lists` arrives in Phase 10); no per-payer profile
|
|
|
882
971
|
X231A1 names. `build999(spec)` assembles a complete `X12Interchange`
|
|
883
972
|
wrapping a single ISA → GS → ST..SE → GE → IEA with one 999 inside,
|
|
884
973
|
spec-clean and round-trippable through `parseX12`.
|
|
885
|
-
- **TA1 (Interchange Acknowledgment)
|
|
974
|
+
- **TA1 (Interchange Acknowledgment): ASC X12 standard, envelope
|
|
886
975
|
level.** The Phase 1 envelope walker now captures envelope-level
|
|
887
|
-
TA1 segments verbatim onto `X12Interchange.ta1Segments
|
|
976
|
+
TA1 segments verbatim onto `X12Interchange.ta1Segments`. TA1
|
|
888
977
|
between ISA and the first GS (the canonical position) is recognized
|
|
889
978
|
as spec-conformant and NO `X12_UNEXPECTED_SEGMENT` warning fires;
|
|
890
979
|
a TA1 inside an open functional group is still flagged as unexpected
|
|
891
980
|
(non-spec). `parseTA1(interchange)` returns the typed `X12AckTA1`
|
|
892
981
|
for the first captured TA1 (or `undefined`). `buildTA1(spec)` emits
|
|
893
|
-
a fixed-position 5-element `Ta1Segment` (`TA101`–`TA105`)
|
|
982
|
+
a fixed-position 5-element `Ta1Segment` (`TA101`–`TA105`). Caller
|
|
894
983
|
wraps it in their preferred envelope. Both standalone TA1-only
|
|
895
984
|
interchanges (ISA → TA1 → IEA, no GS) and embedded TA1s round-trip.
|
|
896
985
|
- **Safety guards (refused via `AckBuildError`):** `build999` refuses
|
|
@@ -916,13 +1005,13 @@ refresh:code-lists` arrives in Phase 10); no per-payer profile
|
|
|
916
1005
|
- **PHI discipline (acks are structurally PHI-free by design):**
|
|
917
1006
|
Control numbers, segment IDs, position counters, and structural
|
|
918
1007
|
error codes ONLY. The one variable-shape surface that COULD carry
|
|
919
|
-
PHI
|
|
1008
|
+
PHI, `IK4-04` (`copyOfBadDataElement`), is documented on both the
|
|
920
1009
|
parsed-model type AND the build-spec type as a caller-supplied
|
|
921
1010
|
field that callers SHOULD omit when the offending bytes are PHI.
|
|
922
1011
|
The library NEVER auto-populates `IK4-04`. Error messages
|
|
923
1012
|
interpolate only control numbers, disposition codes, and count
|
|
924
1013
|
integers; no PHI-shape paths. The `phi-redaction-review` crew gate
|
|
925
|
-
passed at commit time; locked `999
|
|
1014
|
+
passed at commit time; locked `999: PHI safety` and `TA1: PHI
|
|
926
1015
|
safety` test blocks assert no SSN / ISO-date / long-digit-run
|
|
927
1016
|
shapes appear in built output.
|
|
928
1017
|
- **Three Tier-1 999 fixtures** (`999-accept.edi`,
|
|
@@ -941,20 +1030,20 @@ safety` test blocks assert no SSN / ISO-date / long-digit-run
|
|
|
941
1030
|
`X12Interchange.ta1Segments: readonly Ta1Segment[]` (additive, no
|
|
942
1031
|
rename); no new entries to `WARNING_CODES` or `FATAL_CODES`
|
|
943
1032
|
(Phase 3 keeps both registries at the Phase-2-locked sizes of 10
|
|
944
|
-
and 4
|
|
1033
|
+
and 4, additions-only thereafter).
|
|
945
1034
|
- **Spec traceability:** TR3 `005010X231A1` (999); ASC X12 standard §
|
|
946
1035
|
TA1 Interchange Acknowledgment; code lists 715 / 716 / 723 / I13 / I18.
|
|
947
1036
|
- **Known limitations after Phase 3:** Acks reference STRUCTURAL
|
|
948
|
-
errors only
|
|
1037
|
+
errors only. They cannot report semantic / payment errors (those
|
|
949
1038
|
live in `277CA` Phase 6 / `835` Phase 4). No multi-TA1 fan-out
|
|
950
1039
|
helper (consumers iterate `ta1Segments` directly when more than
|
|
951
1040
|
one inbound interchange is being acknowledged). The 999
|
|
952
1041
|
transaction-set surface does not yet expose a public Loop-spec
|
|
953
|
-
artifact
|
|
1042
|
+
artifact. Phase 3 hand-walks the AK1/AK2/IK3/IK4/IK5/AK9 hierarchy
|
|
954
1043
|
in `parse-999.ts`; the dogfooding gate for `defineLoopSpec` lands
|
|
955
1044
|
fully with Phase 4's 835 + Phase 5's 837 work.
|
|
956
1045
|
|
|
957
|
-
- **Phase 2
|
|
1046
|
+
- **Phase 2. Syntactic core: segment / element / composite / repetition
|
|
958
1047
|
decode + warning registry + `defineLoopSpec`.** Every body segment inside
|
|
959
1048
|
a transaction is now decoded into an immutable `X12Segment` carrying its
|
|
960
1049
|
id, raw text, and 1-indexed element array. The verbatim source survives
|
|
@@ -969,7 +1058,7 @@ safety` test blocks assert no SSN / ISO-date / long-digit-run
|
|
|
969
1058
|
trailing `?` is preserved verbatim AND warned as
|
|
970
1059
|
`X12_DANGLING_RELEASE_CHAR`; a `?` followed by a non-delimiter is
|
|
971
1060
|
preserved verbatim with no warning (Postel's Law).
|
|
972
|
-
- **Dot-path traversal
|
|
1061
|
+
- **Dot-path traversal**: `getSegmentValue(seg, "03-1")` resolves
|
|
973
1062
|
composite sub-element 1 of element 3 (both 1-indexed, matching TR3);
|
|
974
1063
|
`"03[2]"` resolves the 3rd repetition (0-indexed); `"03[2]-1"` combines
|
|
975
1064
|
them. Returns `undefined` for out-of-range paths, throws `TypeError`
|
|
@@ -979,14 +1068,14 @@ safety` test blocks assert no SSN / ISO-date / long-digit-run
|
|
|
979
1068
|
structural validation + a typed `LoopSpecDefinitionError`. Phase 3+
|
|
980
1069
|
transaction extractors author their built-in 999 / TA1 / 835 / 837
|
|
981
1070
|
loops through the SAME public API consumers use for payer-specific
|
|
982
|
-
loops
|
|
1071
|
+
loops, the dogfooding gate locked in `documentation/repos/x12.md`.
|
|
983
1072
|
- **Warning registry expanded 8 → 10** (additions-only):
|
|
984
1073
|
`X12_DANGLING_RELEASE_CHAR` (unpaired `?` at element/segment end;
|
|
985
1074
|
bytes are preserved on the parent element) and
|
|
986
1075
|
`X12_UNEXPECTED_SEGMENT` (a `GE` with no open `GS`, `SE` with no open
|
|
987
|
-
`ST`, body segment outside any `ST..SE
|
|
1076
|
+
`ST`, body segment outside any `ST..SE`, cases the Phase 1 walker
|
|
988
1077
|
dropped silently). The PUBLIC `WARNING_CODES` snapshot test is the
|
|
989
|
-
breaking-change tripwire
|
|
1078
|
+
breaking-change tripwire: renaming a code is breaking, additions
|
|
990
1079
|
are not.
|
|
991
1080
|
- **PHI discipline (mirrors hl7's H-PHI invariant):**
|
|
992
1081
|
`X12_UNEXPECTED_SEGMENT` SHAPE-VALIDATES the echoed segment id
|
|
@@ -998,12 +1087,12 @@ safety` test blocks assert no SSN / ISO-date / long-digit-run
|
|
|
998
1087
|
- **Tier-1 fixture** (`syntactic-core-body.edi`) exercises every Phase 2
|
|
999
1088
|
surface end-to-end: composites (`HI*ABK:J45.50`), repetitions
|
|
1000
1089
|
(`EQ*30^35^88`), `?`-release-character escape (`REF*EA*ID?*WITH?*STAR`),
|
|
1001
|
-
and straight-element segments (BHT, NM1). Real-world synthetic
|
|
1090
|
+
and straight-element segments (BHT, NM1). Real-world synthetic, no
|
|
1002
1091
|
PHI. Parses with zero warnings.
|
|
1003
1092
|
- **Properties:** release-escape round-trip (any value, any delimiters),
|
|
1004
1093
|
escapeRelease output is fully decodable as `?<reserved>` pairs +
|
|
1005
1094
|
non-reserved bytes (500 runs each), and a streaming-decode invariant
|
|
1006
|
-
(parser output is independent of input chunking
|
|
1095
|
+
(parser output is independent of input chunking, locks the v2
|
|
1007
1096
|
streaming surface as a non-breaking future addition).
|
|
1008
1097
|
- **`X12TransactionSet.segments` shape changed** from
|
|
1009
1098
|
`readonly string[]` to `readonly X12Segment[]`; the raw form moves to
|
|
@@ -1011,10 +1100,10 @@ safety` test blocks assert no SSN / ISO-date / long-digit-run
|
|
|
1011
1100
|
migrate.** Library-internal change; no impact on `ix.warnings`,
|
|
1012
1101
|
`ix.delimiters`, or the envelope-level accessors.
|
|
1013
1102
|
|
|
1014
|
-
- **Phase 1
|
|
1103
|
+
- **Phase 1: envelope decoder.** `parseX12()` decodes the ISA / GS / GE / IEA
|
|
1015
1104
|
interchange envelope from a raw `string` or `Buffer`, detecting all four
|
|
1016
1105
|
delimiters (`element` byte 4, `repetition` ISA-11, `component` ISA-16,
|
|
1017
|
-
`segment` post-ISA-16) from fixed positions inside the ISA itself
|
|
1106
|
+
`segment` post-ISA-16) from fixed positions inside the ISA itself. The parser
|
|
1018
1107
|
NEVER assumes a delimiter. Transaction-set bodies inside each ST..SE are kept
|
|
1019
1108
|
opaque at this phase (raw segment strings, terminator stripped); Phase 2 adds
|
|
1020
1109
|
segment / element / composite / repetition decode on top.
|
|
@@ -1028,8 +1117,8 @@ safety` test blocks assert no SSN / ISO-date / long-digit-run
|
|
|
1028
1117
|
- `X12ParseError` carries `code`, `position` (interchange/group/transaction/
|
|
1029
1118
|
segment/element indices), and a bounded `snippet` (≤ 64 chars) that is the
|
|
1030
1119
|
documented consumer-redaction boundary. Warning messages NEVER echo field
|
|
1031
|
-
values
|
|
1032
|
-
control-number pairs)
|
|
1120
|
+
values. They carry positional context plus bounded metadata (counts,
|
|
1121
|
+
control-number pairs), mirroring the hl7 H-PHI invariant.
|
|
1033
1122
|
- Strict mode (`parseX12(raw, { strict: true })`) escalates the first Tier-2
|
|
1034
1123
|
warning into a thrown `X12ParseError` carrying the warning code.
|
|
1035
1124
|
- 4 Tier-1 envelope fixtures committed under `test/fixtures/envelope/`
|
|
@@ -1042,7 +1131,7 @@ safety` test blocks assert no SSN / ISO-date / long-digit-run
|
|
|
1042
1131
|
|
|
1043
1132
|
### Changed
|
|
1044
1133
|
|
|
1045
|
-
- Inherits `@cosyte/test-utils` and `fast-check` as devDependencies
|
|
1134
|
+
- Inherits `@cosyte/test-utils` and `fast-check` as devDependencies. The
|
|
1046
1135
|
conformance-kit runners (`lenientNeverThrowsProperty`) and the property/fuzz
|
|
1047
1136
|
arbitraries land alongside the Phase 1 envelope code.
|
|
1048
1137
|
|