@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 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 — so it inherited the repo default of `contents: read`. A called
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 — no runtime or API change.
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"` — every
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
- the shared `cosyte/.github` release workflow drives Changesets with
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")` — literal against literal — which stays green
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 — still `0.0.0`.
121
+ nothing published, still `0.0.0`.
38
122
 
39
123
  ### Added
40
124
 
41
- - **Phase 10 — release hardening.** The v1 close-out; no new parser
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 — `pnpm publish --dry-run` exercises the publish command
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`) — the deep
53
- search that would slow the per-commit run — and opens/auto-closes a
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 — see `KNOWN-LIMITATIONS.md`.
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 — unreachable here: a library build
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** — it calls
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 — descriptive, fixture-grounded clearinghouse / payer
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 — `groups`, `warnings`, and `isa` are
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`) — structured DATA, not hl7's
215
+ (`relaxes` / `adds` / `requires`): structured DATA, not hl7's
127
216
  formatted string, so consumers can program against it.
128
- - **The locked HARD RULE — no invented quirks.** Every quirk MUST cite a
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 — so a real-but-irrelevant fixture cannot slip past.
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` — the one behavioural hook a v1 profile
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 — shipping them would
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 — `build820` (005010X218 Premium Payment) and
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 — they NEVER auto-send,
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 — a deliberate contrast with `build835`.
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 — emit verbatim,
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` — no TRN trace, no remittance, a
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` — an INS-03 / HD-01 code
205
- outside the X12 875 subset; `X12_834_BUILD_INVALID_SPEC` — no member
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 — `build834`
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 — `build278Request` (005010X217 Health Care Services
216
- Review — Request for Review) and `build278Response` (005010X216 Services
217
- Review — Response).** The emit counterparts to `get278Request` /
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 — they NEVER
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 — the round-tripped `decision.actionCode` is
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` — a subscriber with neither a
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` — a review with no request category code,
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) — never
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 — `build271` (005010X279A1 Eligibility Benefit
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 — they
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 — a build-side property
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` — no source / a childless source /
291
- a childless receiver; `X12_271_BUILD_INVALID_SPEC` — over-long control
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` — no source / a childless source /
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` — a claim with no
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) — never a member name, member id, or trace (PHI discipline).
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 — `build837P` / `build837I` / `build837D` (005010
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 — they NEVER auto-send, open a
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) — never the `claimId` or a member id (PHI discipline).
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 — the NM1
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 — `build835` (005010X221A1 ERA).** The first
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 — it NEVER auto-sends, opens a socket,
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 — the
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 — line,
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 — never a patient-control number or
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 — spec-clean serializer + general interchange builder (the
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 — reconstructed purely from the verbatim `.raw`
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`) — so for a Tier-1 input it reproduces the source
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`) — an over-long
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) — an accuracy-gate win.
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 — 278 services review + 834 enrollment + 820 premium
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 — no
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 — a true file→iterator source is a v2 item.)
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 — a zero-dependency, X12-shape-aware PHI scanner
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 — it would break every
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 — no shell
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 — 271 Eligibility Benefit Response + 277 / 277CA Claim
499
- Status — TR3s `005010X279A1` (270/271), `005010X212` (276/277),
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 — `get277CADisposition` admits only `005010X214`;
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 — the walkers NEVER mutate, normalize, or drop the trace. A
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 — 271 spine `20 → 21 → 22 → 23`; 277 / 277CA
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 — 837 Healthcare Claim — TR3s `005010X222A2` (Professional),
637
+ - **Phase 5: 837 Healthcare Claim, TR3s `005010X222A2` (Professional),
549
638
  `005010X223A3` (Institutional), `005010X224A2` (Dental).** The
550
- claim-creation surface — the volume side of HIPAA EDI traffic.
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 — CLM with patient account number, total charge,
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 — the
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` — the parser NEVER silently
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 — with the qualifier (first
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` — `procedureQualifier` /
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` — `revenueCode` (NUBC 4-digit
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` — ADA CDT `procedureCode` from
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 — re-using `X12RemitAdjustment` /
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) — verbatim segments remain on `tx.segments`.
723
+ guide tolerance). Verbatim segments remain on `tx.segments`.
635
724
  - **Eleven dogfooded `LoopSpec` artifacts** ship through the public
636
- `defineLoopSpec()` API — the dogfooding gate locked in Phase 2.
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 — formally part of the
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"`) — so the walker (and any consumer Phase 8
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
- — extracted out of the 835 and 837 walkers (both walkers had
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 — exported via
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
- — fatal registry stays at 4). All new warning factories
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 — the H-PHI invariant from `@cosyte/hl7`.
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 — the
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 — verbatim segments remain on `tx.segments` for raw
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 — detailed CAS / OI / MOA inside Loop 2320
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** — unchanged from Phase 4. The runtime H-PHI
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`) — both walkers previously defined byte-identical
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 — 835 Healthcare Claim Payment/Advice (ERA) — TR3
745
- `005010X221A1`.** The cash-posting surface — money, the consultant ask.
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 — CLP plus
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 — SVC with HCPCS / CPT / NDC / revenue-code / modifier
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 — the **dogfooding
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`** — float
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) — "not supplied" and "zero dollars" are
863
+ `undefined` (not zero): "not supplied" and "zero dollars" are
775
864
  spec-distinct.
776
- - **Balance invariants (per TR3 X221A1 §1.10.2 — "Balancing the 835").**
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 — the model is **NEVER
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)` — the X12 spec balance. CLP-05 (patient responsibility)
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 — `src/transactions/remit/balance.ts`
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 — never patient identifiers, member ids, or account
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 — they cannot mix inside one — and the decoder honors
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) — ~30 most commonly
809
- observed codes (WPC, snapshotDate 2026-06-27). - `RARC` (Remittance Advice Remark Codes) — ~15 most commonly
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` — the spec-fixed 4 values
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 — fatal registry stays at 4). New warning factories
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 — only positional context, the
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` — REF*2U + REF*F8
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 — the byte-level fuzz target the
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 — round-trip + spec-clean serializer + builder); the
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 — see `operations/roadmaps/x12.md` §5);
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 — the H-PHI property tests provide runtime coverage; an
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 — 999 + TA1 acknowledgments (TR3 005010X231A1).** Two
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) — TR3 005010X231A1.**
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) — ASC X12 standard, envelope
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` — TA1
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`) — caller
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 — `IK4-04` (`copyOfBadDataElement`) — is documented on both the
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 — PHI safety` and `TA1 — PHI
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 — additions-only thereafter).
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 — they cannot report semantic / payment errors (those
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 — Phase 3 hand-walks the AK1/AK2/IK3/IK4/IK5/AK9 hierarchy
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 — syntactic core: segment / element / composite / repetition
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** — `getSegmentValue(seg, "03-1")` resolves
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 — the dogfooding gate locked in `documentation/repos/x12.md`.
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` — cases the Phase 1 walker
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 — renaming a code is breaking, additions
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 — no
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 — locks the v2
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 — envelope decoder.** `parseX12()` decodes the ISA / GS / GE / IEA
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 — the parser
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 — they carry positional context plus bounded metadata (counts,
1032
- control-number pairs) — mirroring the hl7 H-PHI invariant.
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 — the
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