@cosyte/x12 0.0.1 → 0.0.3

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