@cosyte/astm 0.0.3 → 0.0.5

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/dist/index.d.ts CHANGED
@@ -3,8 +3,8 @@
3
3
  * record layer.
4
4
  *
5
5
  * **PHI discipline (the whole point).** A position identifies *where* a
6
- * deviation occurred — the record's ordinal index, its type letter, and the
7
- * 1-based field / component indices — and **never** carries a field value. A
6
+ * deviation occurred (the record's ordinal index, its type letter, and the
7
+ * 1-based field / component indices) and **never** carries a field value. A
8
8
  * warning or error may be logged verbatim without leaking a patient name, an
9
9
  * identifier, or a result value.
10
10
  */
@@ -51,21 +51,21 @@ interface AstmPosition {
51
51
  * ```
52
52
  */
53
53
  declare const WARNING_CODES: {
54
- /** A record's type letter is not one of the modeled types — surfaced as an unsupported record. */
54
+ /** A record's type letter is not one of the modeled types: surfaced as an unsupported record. */
55
55
  readonly ASTM_RECORD_UNKNOWN_TYPE: "ASTM_RECORD_UNKNOWN_TYPE";
56
- /** The header declared delimiters other than the canonical `H|\^&` — tolerated, noted. */
56
+ /** The header declared delimiters other than the canonical `H|\^&`: tolerated, noted. */
57
57
  readonly ASTM_NONSTANDARD_DELIMITERS: "ASTM_NONSTANDARD_DELIMITERS";
58
- /** An escape sequence body was not one of `&F&`/`&S&`/`&R&`/`&E&` — preserved verbatim. */
58
+ /** An escape sequence body was not one of `&F&`/`&S&`/`&R&`/`&E&`: preserved verbatim. */
59
59
  readonly ASTM_UNKNOWN_ESCAPE_SEQUENCE: "ASTM_UNKNOWN_ESCAPE_SEQUENCE";
60
60
  /**
61
61
  * A result value field carried an *unescaped* component delimiter, so it split into more than one
62
- * component. Both the full raw value and the split are surfaced and this warning fires — the
62
+ * component. Both the full raw value and the split are surfaced and this warning fires: the
63
63
  * ambiguity is never resolved silently into a truncated value.
64
64
  */
65
65
  readonly ASTM_RECORD_AMBIGUOUS_VALUE_SPLIT: "ASTM_RECORD_AMBIGUOUS_VALUE_SPLIT";
66
66
  /**
67
67
  * A result's abnormal-flag field (`R` field 7) carried a letter outside HL7 Table 0078. The flag is
68
- * surfaced as `undefined` — never dropped, and **never coerced to `normal`** (a clinical error).
68
+ * surfaced as `undefined`: never dropped, and **never coerced to `normal`** (a clinical error).
69
69
  */
70
70
  readonly ASTM_RECORD_UNDEFINED_ABNORMAL_FLAG: "ASTM_RECORD_UNDEFINED_ABNORMAL_FLAG";
71
71
  /**
@@ -75,7 +75,7 @@ declare const WARNING_CODES: {
75
75
  readonly ASTM_RECORD_UNDEFINED_RESULT_STATUS: "ASTM_RECORD_UNDEFINED_RESULT_STATUS";
76
76
  /**
77
77
  * A result's reference-range field (`R` field 6) did not match a recognized form (`low-high`,
78
- * `<high`, `>low`). The text is surfaced verbatim as `unparsed` — a bound is **never fabricated**.
78
+ * `<high`, `>low`). The text is surfaced verbatim as `unparsed`: a bound is **never fabricated**.
79
79
  */
80
80
  readonly ASTM_RECORD_UNPARSEABLE_REFERENCE_RANGE: "ASTM_RECORD_UNPARSEABLE_REFERENCE_RANGE";
81
81
  /**
@@ -84,35 +84,57 @@ declare const WARNING_CODES: {
84
84
  */
85
85
  readonly ASTM_RECORD_UNITS_ABSENT: "ASTM_RECORD_UNITS_ABSENT";
86
86
  /**
87
- * A `C` (comment) record had no valid preceding `H`/`P`/`O`/`R` parent — an **orphan**. The comment
87
+ * A `C` (comment) record had no valid preceding `H`/`P`/`O`/`R` parent: an **orphan**. The comment
88
88
  * is attached to the message root (`attachedToRoot: true`) and surfaced, **never dropped**.
89
89
  */
90
90
  readonly ASTM_RECORD_ORPHAN_COMMENT: "ASTM_RECORD_ORPHAN_COMMENT";
91
91
  /**
92
92
  * A `YYYYMMDDHHMMSS` timestamp had an odd digit run that truncates a two-digit component in half
93
93
  * (e.g. a partial day/hour). The raw run is preserved and the structured value stops at the last
94
- * **complete** component — the dangling digit is **never zero-filled into a fabricated time**.
94
+ * **complete** component: the dangling digit is **never zero-filled into a fabricated time**.
95
95
  */
96
96
  readonly ASTM_RECORD_PARTIAL_TIMESTAMP: "ASTM_RECORD_PARTIAL_TIMESTAMP";
97
97
  /**
98
98
  * A `Q` (request-information) record carried a request-information status code (field 13). The code
99
99
  * *set* is `[OSS-derived / paywalled]` with no publicly-groundable enumeration, so the parser
100
100
  * interprets **none** of them: the status is surfaced verbatim and this value-free warning flags that
101
- * it was passed through **uninterpreted** — never mapped to a guessed meaning.
101
+ * it was passed through **uninterpreted**, never mapped to a guessed meaning.
102
102
  */
103
103
  readonly ASTM_RECORD_UNINTERPRETED_QUERY_STATUS: "ASTM_RECORD_UNINTERPRETED_QUERY_STATUS";
104
104
  /**
105
- * A message carried **both** a `Q` (request) and an `R` (result) record — a contradictory shape. The
105
+ * A message carried **both** a `Q` (request) and an `R` (result) record: a contradictory shape. The
106
106
  * message is classified `host-query` (the `Q` **dominates**, so it is never read as a result set) and
107
107
  * this warning flags the anomaly. Positional context only; no field value.
108
108
  */
109
109
  readonly ASTM_RECORD_AMBIGUOUS_MESSAGE_KIND: "ASTM_RECORD_AMBIGUOUS_MESSAGE_KIND";
110
+ /**
111
+ * A later `H` (header) record declared a **different** delimiter set from the one in force, and the
112
+ * parser **followed it**: that header and every record after it are read with the newly-declared
113
+ * delimiters, until the next `H`. Records already read keep the set that was in force when they were
114
+ * read: a redeclaration never reinterprets bytes that have already been consumed.
115
+ *
116
+ * A stream carrying several messages back to back is ordinary, and a header that simply **repeats**
117
+ * the delimiters already in force is a no-op that warns nothing. This code fires only when the set
118
+ * actually changes, because that is the point at which a reader still using the old set would begin
119
+ * merging fields together.
120
+ */
121
+ readonly ASTM_RECORD_DELIMITERS_REDECLARED: "ASTM_RECORD_DELIMITERS_REDECLARED";
122
+ /**
123
+ * A later `H` (header) record could not declare a usable delimiter set: it was too short, or the
124
+ * field separator it named also appeared among the other three, leaving the four roles
125
+ * indistinguishable. The delimiters **already in force are kept** and every record is still surfaced;
126
+ * a set is never guessed and no record is dropped.
127
+ *
128
+ * The same condition on the *first* header is unrecoverable and remains the
129
+ * `ASTM_RECORD_UNDECLARED_DELIMITERS` fatal: there is no earlier set to fall back to.
130
+ */
131
+ readonly ASTM_RECORD_UNREADABLE_REDECLARATION: "ASTM_RECORD_UNREADABLE_REDECLARATION";
110
132
  /**
111
133
  * The downgraded form an active vendor {@link AstmProfile} produces from a deviation it *expects*
112
134
  * (see `src/profiles/`). The original warning is **never dropped**: its code moves to
113
135
  * {@link AstmRecordWarning.toleratedCode}, the warning is re-badged `PROFILE_QUIRK_APPLIED` with
114
136
  * `expected: true` and the tolerating profile named, so a consumer can filter known, grounded noise
115
- * while the fact of the deviation — and where it was — survives. A profile can only ever reach this
137
+ * while the fact of the deviation, and where it was, survives. A profile can only ever reach this
116
138
  * path for a **non-safety-critical** code (enforced at profile-definition time); a safety-critical
117
139
  * deviation (a result value, flag, status, patient identifier, code system, or a frame-integrity
118
140
  * warning) can **never** be tolerated, so it can never be re-badged here.
@@ -148,14 +170,14 @@ interface AstmRecordWarning {
148
170
  * `true` when an active vendor {@link AstmProfile} *expected* this deviation and re-badged it as a
149
171
  * {@link WARNING_CODES.PROFILE_QUIRK_APPLIED}. An `expected` warning does **not** escalate to a
150
172
  * thrown `AstmStrictError` in strict mode (the whole point of the profile is that this deviation is
151
- * known and benign) — it is still recorded, so nothing is hidden. Absent on an untolerated warning.
173
+ * known and benign): it is still recorded, so nothing is hidden. Absent on an untolerated warning.
152
174
  */
153
175
  readonly expected?: boolean;
154
176
  /** The name of the {@link AstmProfile} that tolerated this warning, when `expected`. */
155
177
  readonly profile?: string;
156
178
  /**
157
179
  * When `code` is {@link WARNING_CODES.PROFILE_QUIRK_APPLIED}, the original warning code the profile
158
- * tolerated — so a consumer can still see *which* deviation was re-badged as expected.
180
+ * tolerated, so a consumer can still see *which* deviation was re-badged as expected.
159
181
  */
160
182
  readonly toleratedCode?: WarningCode;
161
183
  }
@@ -181,6 +203,31 @@ declare function unknownRecordType(position: AstmPosition): AstmRecordWarning;
181
203
  * ```
182
204
  */
183
205
  declare function nonStandardDelimiters(position: AstmPosition): AstmRecordWarning;
206
+ /**
207
+ * Build an `ASTM_RECORD_DELIMITERS_REDECLARED` warning. Emitted when a later `H`
208
+ * record declares a delimiter set different from the one in force; the new set is
209
+ * honored from that header onward. Positional context only: never the delimiters
210
+ * themselves.
211
+ *
212
+ * @example
213
+ * ```ts
214
+ * import { delimitersRedeclared } from "@cosyte/astm";
215
+ * delimitersRedeclared({ recordIndex: 5, recordType: "H" });
216
+ * ```
217
+ */
218
+ declare function delimitersRedeclared(position: AstmPosition): AstmRecordWarning;
219
+ /**
220
+ * Build an `ASTM_RECORD_UNREADABLE_REDECLARATION` warning. Emitted when a later
221
+ * `H` record cannot declare a usable delimiter set; the set already in force is
222
+ * kept and every record is still surfaced.
223
+ *
224
+ * @example
225
+ * ```ts
226
+ * import { unreadableRedeclaration } from "@cosyte/astm";
227
+ * unreadableRedeclaration({ recordIndex: 2, recordType: "H" });
228
+ * ```
229
+ */
230
+ declare function unreadableRedeclaration(position: AstmPosition): AstmRecordWarning;
184
231
  /**
185
232
  * Build an `ASTM_UNKNOWN_ESCAPE_SEQUENCE` warning. The sequence is preserved
186
233
  * verbatim in the decoded value; the warning body carries neither the sequence
@@ -195,7 +242,7 @@ declare function nonStandardDelimiters(position: AstmPosition): AstmRecordWarnin
195
242
  declare function unknownEscapeSequence(position: AstmPosition): AstmRecordWarning;
196
243
  /**
197
244
  * Build an `ASTM_RECORD_AMBIGUOUS_VALUE_SPLIT` warning. Emitted when a result
198
- * value field split on an unescaped component delimiter — the full raw value and
245
+ * value field split on an unescaped component delimiter: the full raw value and
199
246
  * the split are both surfaced, never a silent truncation.
200
247
  *
201
248
  * @example
@@ -299,7 +346,7 @@ declare function uninterpretedQueryStatus(position: AstmPosition): AstmRecordWar
299
346
  */
300
347
  declare function ambiguousMessageKind(position: AstmPosition): AstmRecordWarning;
301
348
  /**
302
- * Build a `PROFILE_QUIRK_APPLIED` warning — the downgraded form an active vendor profile produces from
349
+ * Build a `PROFILE_QUIRK_APPLIED` warning: the downgraded form an active vendor profile produces from
303
350
  * a deviation it *expects*. The original warning is **not dropped**: its `code` moves to
304
351
  * `toleratedCode`, the warning is re-badged `PROFILE_QUIRK_APPLIED`, `expected` is set, and the
305
352
  * tolerating profile is named. The original `position` and `message` are preserved (both PHI-free by
@@ -326,7 +373,7 @@ declare function profileQuirkApplied(original: AstmRecordWarning, profileName: s
326
373
  *
327
374
  * ASTM/CLSI-LIS02 messages are **self-describing**: every `H` (header) record
328
375
  * declares the four delimiters it uses, in the bytes immediately after the `H`.
329
- * A parser MUST read them from the record and MUST NOT hardcode them — a vendor
376
+ * A parser MUST read them from the record and MUST NOT hardcode them: a vendor
330
377
  * is free to declare non-standard delimiters and a conformant reader follows.
331
378
  *
332
379
  * The canonical declaration is `H|\^&`:
@@ -358,18 +405,18 @@ declare function profileQuirkApplied(original: AstmRecordWarning, profileName: s
358
405
  * ```
359
406
  */
360
407
  interface Delimiters {
361
- /** Field separator — the char immediately after `H` in the header. */
408
+ /** Field separator: the char immediately after `H` in the header. */
362
409
  readonly field: string;
363
- /** Repeat (repetition) separator — ASTM `\` by default. */
410
+ /** Repeat (repetition) separator: ASTM `\` by default. */
364
411
  readonly repeat: string;
365
- /** Component separator — ASTM `^` by default. */
412
+ /** Component separator: ASTM `^` by default. */
366
413
  readonly component: string;
367
- /** Escape character — ASTM `&` by default (introduces `&F&`/`&S&`/`&R&`/`&E&`). */
414
+ /** Escape character: ASTM `&` by default (introduces `&F&`/`&S&`/`&R&`/`&E&`). */
368
415
  readonly escape: string;
369
416
  }
370
417
  /**
371
418
  * The canonical ASTM delimiter set (`H|\^&`). Used only as documentation and as
372
- * a comparison baseline for the non-standard-delimiter warning — never as a
419
+ * a comparison baseline for the non-standard-delimiter warning: never as a
373
420
  * parse-time default (delimiters are always read from the header).
374
421
  *
375
422
  * @example
@@ -383,10 +430,18 @@ declare const CANONICAL_DELIMITERS: Delimiters;
383
430
  * Read the four delimiters from a header record's raw text (a single `H`
384
431
  * record, its terminator already stripped).
385
432
  *
386
- * Returns `{ ok: false }` when the record cannot declare all four delimiters —
387
- * it is shorter than `H` + a field separator + a 3-char delimiter definition —
388
- * which the caller escalates to the `ASTM_RECORD_UNDECLARED_DELIMITERS` fatal.
389
- * This function does not throw; delimiter resolution and the fatal decision are
433
+ * Returns `undefined` when the record cannot declare all four delimiters: it is
434
+ * shorter than `H` + a field separator + a 3-char delimiter definition, or the
435
+ * field separator it names also appears among the other three.
436
+ *
437
+ * What the caller does with that depends on **which** header it is. On the first
438
+ * header it is the `ASTM_RECORD_UNDECLARED_DELIMITERS` fatal, because there is no
439
+ * earlier set to fall back to. On any later header (a stream may carry several
440
+ * messages, each declaring its own set) the delimiters already in force are kept
441
+ * and an `ASTM_RECORD_UNREADABLE_REDECLARATION` warning is raised instead; a set is
442
+ * never guessed and no record is dropped.
443
+ *
444
+ * This function does not throw: delimiter resolution and the escalation decision are
390
445
  * kept separate so the reader stays pure and testable.
391
446
  *
392
447
  * @param headerRecord - The raw `H` record text (no trailing CR/LF).
@@ -413,12 +468,12 @@ declare function readDelimiters(headerRecord: string): Delimiters | undefined;
413
468
  declare function isNonStandard(d: Delimiters): boolean;
414
469
 
415
470
  /**
416
- * The ASTM date/time value — `YYYYMMDDHHMMSS`, precision-preserving, no timezone.
471
+ * The ASTM date/time value: `YYYYMMDDHHMMSS`, precision-preserving, no timezone.
417
472
  *
418
473
  * ASTM timestamps are a run of digits, most-significant first, truncated at
419
474
  * whatever precision the instrument reports. There is **no timezone field**:
420
475
  * the value is local to the instrument and this parser never assumes UTC. A
421
- * date-only value (`YYYYMMDD`) is a normal, complete value at day precision —
476
+ * date-only value (`YYYYMMDD`) is a normal, complete value at day precision:
422
477
  * **not** an error and never zero-filled into a fake time.
423
478
  */
424
479
  /** The precision to which an {@link AstmDate} is populated. */
@@ -427,7 +482,7 @@ type AstmDatePrecision = "year" | "month" | "day" | "hour" | "minute" | "second"
427
482
  * A parsed ASTM date/time. Immutable plain data; the populated fields extend
428
483
  * exactly as far as {@link AstmDate.precision}. Absent components are left
429
484
  * `undefined` rather than defaulted, so a consumer can tell "midnight" from
430
- * "no time given". No timezone is modeled — the value is instrument-local.
485
+ * "no time given". No timezone is modeled: the value is instrument-local.
431
486
  *
432
487
  * @example
433
488
  * ```ts
@@ -449,10 +504,10 @@ interface AstmDate {
449
504
  /** How far the components are populated. */
450
505
  readonly precision: AstmDatePrecision;
451
506
  /**
452
- * `true` when the digit run does **not** align to a whole-component boundary — an odd number of
507
+ * `true` when the digit run does **not** align to a whole-component boundary: an odd number of
453
508
  * digits that cuts a two-digit component (month/day/hour/minute/second) in half (lengths 5, 7, 9,
454
509
  * 11, 13). The full run is preserved in {@link AstmDate.raw} and the structured value is truncated
455
- * to the last **complete** component — the dangling digit is **never zero-filled into a fabricated
510
+ * to the last **complete** component: the dangling digit is **never zero-filled into a fabricated
456
511
  * time**. Absent (never `false`) for a clean value. A caller surfaces this as a value-free
457
512
  * `ASTM_RECORD_PARTIAL_TIMESTAMP` warning.
458
513
  */
@@ -461,7 +516,7 @@ interface AstmDate {
461
516
  /**
462
517
  * Parse an ASTM `YYYYMMDDHHMMSS` value, preserving whatever precision is
463
518
  * present. Returns `undefined` for a value that is not a usable timestamp (fewer
464
- * than four leading digits for the year, or non-digit content) — a caller keeps
519
+ * than four leading digits for the year, or non-digit content): a caller keeps
465
520
  * the raw field text either way, so nothing is lost. A partial value is parsed,
466
521
  * never rejected.
467
522
  *
@@ -480,7 +535,7 @@ interface AstmDate {
480
535
  declare function parseAstmDate(raw: string): AstmDate | undefined;
481
536
  /**
482
537
  * Render an {@link AstmDate} as an ISO-8601-*like* string truncated to its
483
- * precision, with **no** `Z` and **no** offset — because ASTM carries no
538
+ * precision, with **no** `Z` and **no** offset: because ASTM carries no
484
539
  * timezone and appending one would fabricate information. A consumer that knows
485
540
  * the instrument's zone can attach it; this function never assumes UTC.
486
541
  *
@@ -501,9 +556,9 @@ declare function astmDateToLocalISO(d: AstmDate): string;
501
556
  * spec's component order is LOINC-slot / test-name / coding-scheme / local-code.
502
557
  * In the field the way real analyzers emit it:
503
558
  *
504
- * - **component 1** is a LOINC slot — *almost always empty*; a few vendors place
559
+ * - **component 1** is a LOINC slot: *almost always empty*; a few vendors place
505
560
  * an inline LOINC when one exists;
506
- * - **component 4** is the vendor/local code — the identifier that is actually
561
+ * - **component 4** is the vendor/local code: the identifier that is actually
507
562
  * present, and therefore the **primary** identifier here.
508
563
  *
509
564
  * Following the sibling parsers' conservative posture, this module is a
@@ -513,11 +568,11 @@ declare function astmDateToLocalISO(d: AstmDate): string;
513
568
  */
514
569
  /** Where a Universal Test ID's usable identifier came from. */
515
570
  type UniversalTestIdProvenance =
516
- /** Component 1 (the LOINC slot) is populated — a candidate LOINC, recognized not validated. */
571
+ /** Component 1 (the LOINC slot) is populated: a candidate LOINC, recognized not validated. */
517
572
  "inline-loinc-candidate"
518
573
  /** Component 4 (the vendor/local code) carries the identifier. */
519
574
  | "local-code"
520
- /** Only the test name (component 2) is present — no code. */
575
+ /** Only the test name (component 2) is present: no code. */
521
576
  | "name-only"
522
577
  /** Nothing usable in the field. */
523
578
  | "empty";
@@ -536,13 +591,13 @@ type UniversalTestIdProvenance =
536
591
  interface UniversalTestId {
537
592
  /** The field's components, verbatim and in order. */
538
593
  readonly components: readonly string[];
539
- /** Component 1 when populated — a *candidate* LOINC (provenance only, never validated). */
594
+ /** Component 1 when populated: a *candidate* LOINC (provenance only, never validated). */
540
595
  readonly loincCandidate?: string;
541
- /** Component 2 — the test / battery name, when present. */
596
+ /** Component 2: the test / battery name, when present. */
542
597
  readonly testName?: string;
543
- /** Component 3 — the coding-scheme selector, when present. */
598
+ /** Component 3: the coding-scheme selector, when present. */
544
599
  readonly codingScheme?: string;
545
- /** Component 4 — the vendor/local code: the primary identifier when no inline LOINC is given. */
600
+ /** Component 4, the vendor/local code: the primary identifier when no inline LOINC is given. */
546
601
  readonly localCode?: string;
547
602
  /** Where the primary identifier came from. */
548
603
  readonly provenance: UniversalTestIdProvenance;
@@ -562,7 +617,7 @@ declare function recognizeUniversalTestId(components: readonly string[]): Univer
562
617
  /**
563
618
  * The primary code to key a result on: the inline LOINC candidate when a vendor
564
619
  * supplied one, otherwise the local (vendor) code. Returns `undefined` when the
565
- * field carries no code at all (name-only or empty) — never a guess.
620
+ * field carries no code at all (name-only or empty): never a guess.
566
621
  *
567
622
  * @param u - A recognized Universal Test ID.
568
623
  * @returns The primary code, or `undefined`.
@@ -578,7 +633,7 @@ declare function primaryCode(u: UniversalTestId): string | undefined;
578
633
  * Positional context for frame-codec warnings and errors.
579
634
  *
580
635
  * **PHI discipline (the whole point).** A frame deviation is located by **frame
581
- * number + byte offset** and nothing else — never the record text a frame
636
+ * number + byte offset** and nothing else: never the record text a frame
582
637
  * carries. A frame warning or error may be logged verbatim without ever leaking a
583
638
  * result value, a patient identifier, or any other field the reassembled record
584
639
  * bytes contain.
@@ -604,12 +659,12 @@ interface AstmFramePosition {
604
659
  }
605
660
 
606
661
  /**
607
- * The **frame** warning registry (`ASTM_FRAME_*`) for the ASTM/CLSI-LIS01 codec —
662
+ * The **frame** warning registry (`ASTM_FRAME_*`) for the ASTM/CLSI-LIS01 codec:
608
663
  * the second of the package's two registries, alongside the record layer's
609
664
  * `ASTM_RECORD_*` (both share only the `EMPTY_INPUT` fatal).
610
665
  *
611
666
  * A frame warning is the lenient codec's record of a tolerated byte-level
612
- * deviation: it never crashes, never invents a value, and — critically — never
667
+ * deviation: it never crashes, never invents a value, and, critically, never
613
668
  * lets an untrusted frame be silently merged into a record. Every warning carries
614
669
  * a stable code plus an {@link AstmFramePosition} (frame number + byte offset) and
615
670
  * **never** the record bytes a frame carries (PHI discipline). Consumers compare
@@ -634,12 +689,12 @@ declare const FRAME_WARNING_CODES: {
634
689
  * A frame's two-hex-char checksum did not match the modulo-256 sum recomputed over its bytes. The
635
690
  * frame is surfaced with `trusted: false` and its text is **never merged** into a reassembled
636
691
  * record (default **warn** in lenient mode, escalated to a thrown error in `strict`). Corruption is
637
- * never silently trusted — the "checksums are routinely not validated" claim was refuted; we
692
+ * never silently trusted: the "checksums are routinely not validated" claim was refuted; we
638
693
  * validate.
639
694
  */
640
695
  readonly ASTM_FRAME_BAD_CHECKSUM: "ASTM_FRAME_BAD_CHECKSUM";
641
696
  /**
642
- * A frame's sequence number was not the expected next value (`1 → … → 7 → 0 → …`) — a frame was
697
+ * A frame's sequence number was not the expected next value (`1 → … → 7 → 0 → …`): a frame was
643
698
  * possibly dropped. The stream is **never silently concatenated across the gap** as if contiguous;
644
699
  * the in-progress record is tainted and not emitted as a clean reassembly.
645
700
  */
@@ -653,7 +708,7 @@ declare const FRAME_WARNING_CODES: {
653
708
  readonly ASTM_FRAME_UNTERMINATED: "ASTM_FRAME_UNTERMINATED";
654
709
  /**
655
710
  * A frame's record text exceeded the 240-byte limit without a split. The frame is still surfaced
656
- * (and, if its checksum validates, reassembled) — the deviation is flagged, not silently dropped
711
+ * (and, if its checksum validates, reassembled): the deviation is flagged, not silently dropped
657
712
  * (warn in lenient, thrown in `strict`).
658
713
  */
659
714
  readonly ASTM_FRAME_OVERSIZE: "ASTM_FRAME_OVERSIZE";
@@ -730,14 +785,14 @@ declare function frameOversize(position: AstmFramePosition): AstmFrameWarning;
730
785
 
731
786
  /**
732
787
  * The **LTP** warning registry (`ASTM_LTP_*`) for the ASTM/CLSI-LIS01 protocol
733
- * layer — the third of the package's registries, alongside the record layer's
788
+ * layer: the third of the package's registries, alongside the record layer's
734
789
  * `ASTM_RECORD_*` and the frame codec's `ASTM_FRAME_*`.
735
790
  *
736
791
  * An LTP warning is the protocol reducer's (or the transport detector's) record
737
792
  * of a tolerated session-level deviation: an ambiguous framed/raw stream, a
738
793
  * control event that arrived in a state that did not expect it, or a frame the
739
794
  * receiver had to reject (`NAK`) rather than accept. Every warning carries a
740
- * stable code plus, at most, a **frame number** — never the record bytes a frame
795
+ * stable code plus, at most, a **frame number**: never the record bytes a frame
741
796
  * carries (PHI discipline). Consumers compare `warning.code ===
742
797
  * LTP_WARNING_CODES.<CODE>`; renaming a code is a **breaking change**.
743
798
  */
@@ -754,21 +809,21 @@ declare function frameOversize(position: AstmFramePosition): AstmFrameWarning;
754
809
  declare const LTP_WARNING_CODES: {
755
810
  /**
756
811
  * The transport detector could not tell a framed stream from a raw (unframed) one from its leading
757
- * byte — it was neither `STX`/`ENQ` nor a bare record letter. It **defaults to framed** and warns,
812
+ * byte: it was neither `STX`/`ENQ` nor a bare record letter. It **defaults to framed** and warns,
758
813
  * never guessing silently into data loss; a profile override forces the mode. (cobas b121 drops
759
- * framing over TCP; cobas 4800 / Iguana retain it — both realities exist, so an unrecognizable lead
814
+ * framing over TCP; cobas 4800 / Iguana retain it, both realities exist, so an unrecognizable lead
760
815
  * is defaulted, not assumed.)
761
816
  */
762
817
  readonly ASTM_LTP_AMBIGUOUS_TRANSPORT: "ASTM_LTP_AMBIGUOUS_TRANSPORT";
763
818
  /**
764
- * A control event arrived in a protocol state that did not expect it — e.g. an inbound `ACK`/`NAK`
819
+ * A control event arrived in a protocol state that did not expect it: e.g. an inbound `ACK`/`NAK`
765
820
  * at a receiver (which sends, never receives, those), or an `ENQ` mid-transfer. The event is
766
821
  * surfaced and handled defensively (an unexpected `ACK`/`NAK` is **never** read as acceptance of
767
822
  * data); it never advances the transfer as if valid.
768
823
  */
769
824
  readonly ASTM_LTP_UNEXPECTED_EVENT: "ASTM_LTP_UNEXPECTED_EVENT";
770
825
  /**
771
- * The receiver rejected a received frame with a `NAK` instead of accepting it — because its
826
+ * The receiver rejected a received frame with a `NAK` instead of accepting it: because its
772
827
  * checksum failed, it was unterminated, or its frame number was out of sequence. The frame's text
773
828
  * is **never** appended to the record and the transfer does **not** advance; the receiver awaits the
774
829
  * sender's retransmit. This is the protocol-level face of the frame codec's fail-safe: a bad frame
@@ -783,7 +838,7 @@ declare const LTP_WARNING_CODES: {
783
838
  type LtpWarningCode = (typeof LTP_WARNING_CODES)[keyof typeof LTP_WARNING_CODES];
784
839
  /**
785
840
  * A single LTP protocol / transport warning: a stable code, a value-free
786
- * human-readable message, and — for a frame-scoped deviation — the frame number
841
+ * human-readable message, and, for a frame-scoped deviation, the frame number
787
842
  * only. Never carries a frame's record bytes.
788
843
  *
789
844
  * @example
@@ -791,7 +846,7 @@ type LtpWarningCode = (typeof LTP_WARNING_CODES)[keyof typeof LTP_WARNING_CODES]
791
846
  * import type { AstmLtpWarning } from "@cosyte/astm";
792
847
  * const w: AstmLtpWarning = {
793
848
  * code: "ASTM_LTP_FRAME_REJECTED",
794
- * message: "Frame rejected — NAK sent, retransmit expected.",
849
+ * message: "Frame rejected, NAK sent, retransmit expected.",
795
850
  * frameNumber: 2,
796
851
  * };
797
852
  * ```
@@ -846,22 +901,22 @@ declare function ltpFrameRejected(frameNumber?: number): AstmLtpWarning;
846
901
  * (RS-232) always uses full E1381 framing. Over **TCP it varies within a single
847
902
  * vendor**: the Roche cobas 4800 and iNTERFACEWARE Iguana retain the full
848
903
  * `ENQ`/`ACK` + `STX`/checksum framing over TCP (**framed-TCP**), while the cobas
849
- * b121 drops all low-level framing — "TCP itself ensures correctness" — and streams
904
+ * b121 drops all low-level framing, "TCP itself ensures correctness", and streams
850
905
  * de-framed record bytes directly (**raw-TCP**). A reader must handle both and must
851
906
  * not assume the handshake is present.
852
907
  *
853
908
  * {@link detectFraming} decides which it is from the stream's leading byte: `STX`
854
909
  * or `ENQ` ⇒ framed (feed it to the frame codec + {@link ltpReduce}); a bare record
855
910
  * letter ⇒ raw (feed it straight to `parseAstmRecords`). Anything else is
856
- * **ambiguous**, and the fail-safe is to **default to framed** and warn — never to
857
- * guess silently into data loss — with an explicit override for a profile that
911
+ * **ambiguous**, and the fail-safe is to **default to framed** and warn, never to
912
+ * guess silently into data loss, with an explicit override for a profile that
858
913
  * knows better.
859
914
  */
860
915
 
861
916
  /**
862
917
  * The transport framing of an ASTM byte stream: `"framed"` (E1381 `STX`/checksum
863
918
  * frames, the serial and framed-TCP realities) or `"raw"` (framing dropped,
864
- * de-framed record bytes streamed directly — the raw-TCP reality).
919
+ * de-framed record bytes streamed directly, the raw-TCP reality).
865
920
  */
866
921
  type AstmFraming = "framed" | "raw";
867
922
  /**
@@ -870,8 +925,8 @@ type AstmFraming = "framed" | "raw";
870
925
  interface DetectFramingOptions {
871
926
  /**
872
927
  * A profile-supplied override. When set, detection is bypassed entirely and this
873
- * value is returned with no warning — the way a Phase-8 vendor profile forces
874
- * raw for a cobas b121 or framed for a cobas 4800 regardless of the leading byte.
928
+ * value is returned with no warning: the way a vendor profile forces raw for
929
+ * a cobas b121 or framed for a cobas 4800 regardless of the leading byte.
875
930
  */
876
931
  readonly override?: AstmFraming;
877
932
  }
@@ -920,7 +975,7 @@ declare function detectFraming(bytes: Uint8Array, options?: DetectFramingOptions
920
975
  * plus an optional **transport override** (the raw-vs-framed-TCP knob). It mirrors
921
976
  * the sibling `@cosyte/hl7` `defineProfile` and `@cosyte/ccda` `defineCcdaProfile`
922
977
  * shape (`name` / `lineage` / `describe()` / `extends`-merge) but models ASTM's
923
- * quirks — a warning code the stream is expected to trip, and its framing reality —
978
+ * quirks (a warning code the stream is expected to trip, and its framing reality)
924
979
  * rather than HL7 v2 Z-segments or C-CDA template deviations.
925
980
  *
926
981
  * A profile **never changes an extracted value**. It operates purely at the
@@ -929,29 +984,29 @@ declare function detectFraming(bytes: Uint8Array, options?: DetectFramingOptions
929
984
  * layer (forcing framed/raw before a single byte is parsed). The two load-bearing
930
985
  * safety rules, enforced by `defineAstmProfile`:
931
986
  *
932
- * 1. **A profile can never tolerate a safety-critical warning code** — a result
987
+ * 1. **A profile can never tolerate a safety-critical warning code**: a result
933
988
  * value, abnormal flag, result status, reference range, units, patient/comment
934
989
  * context, message-kind ambiguity, code system, or any frame/LTP integrity
935
990
  * warning (see `src/profiles/safety.ts`). Naming one is a *definition-time
936
- * throw*, not a silent relaxation — so a profile can never make a bad checksum
991
+ * throw*, not a silent relaxation, so a profile can never make a bad checksum
937
992
  * "ok" or a cancelled result read "final."
938
993
  * 2. **A tolerated deviation is downgraded, never dropped.** The parser still
939
994
  * records it (re-coded {@link WARNING_CODES.PROFILE_QUIRK_APPLIED}, flagged
940
995
  * `expected: true`, carrying the original `toleratedCode`) so nothing is
941
- * silently hidden — Postel's Law with a receipt.
996
+ * silently hidden, Postel's Law with a receipt.
942
997
  */
943
998
 
944
999
  /**
945
- * Any warning code from any of the three registries — the record layer
1000
+ * Any warning code from any of the three registries: the record layer
946
1001
  * (`ASTM_RECORD_*` / `ASTM_NONSTANDARD_DELIMITERS` / `ASTM_UNKNOWN_ESCAPE_SEQUENCE`),
947
1002
  * the frame codec (`ASTM_FRAME_*`), or the LTP protocol layer (`ASTM_LTP_*`). A
948
1003
  * {@link QuirkTolerance} may *name* any of them, but the safety gate refuses all
949
- * but the small, benign, record-layer subset — so the union exists mainly to let
1004
+ * but the small, benign, record-layer subset, so the union exists mainly to let
950
1005
  * the gate reject a frame/LTP code with a precise, typed message.
951
1006
  */
952
1007
  type AnyAstmWarningCode = WarningCode | FrameWarningCode | LtpWarningCode;
953
1008
  /**
954
- * Provenance for an {@link AstmProfile} — the **real, cited public artifact** a
1009
+ * Provenance for an {@link AstmProfile}: the **real, cited public artifact** a
955
1010
  * profile's quirks are grounded in. A quirk is encoded only when a real
956
1011
  * (de-identified) document or a redistributable reference corpus grounds it; this
957
1012
  * record is where that grounding is stated, so a reviewer can trace every
@@ -970,7 +1025,7 @@ type AnyAstmWarningCode = WarningCode | FrameWarningCode | LtpWarningCode;
970
1025
  interface AstmProfileProvenance {
971
1026
  /** Short human-readable name of the grounding source (corpus, transcript, or spec). */
972
1027
  readonly source: string;
973
- /** A citation the grounding can be traced to — a URL, DOI, or repo+path. */
1028
+ /** A citation the grounding can be traced to: a URL, DOI, or repo+path. */
974
1029
  readonly reference: string;
975
1030
  /** When the grounding was last verified (ISO date) or the pinned commit SHA. */
976
1031
  readonly retrieved?: string;
@@ -980,7 +1035,7 @@ interface AstmProfileProvenance {
980
1035
  /**
981
1036
  * Optional structural narrowing for a {@link QuirkTolerance}. When present, the
982
1037
  * tolerance applies only to warnings whose PHI-free {@link AstmPosition} matches
983
- * every provided field — so a profile can expect a deviation on one record type
1038
+ * every provided field, so a profile can expect a deviation on one record type
984
1039
  * (e.g. an unknown escape only inside `R` result values) without blanket-tolerating
985
1040
  * it everywhere. Matching is on **structural identifiers only** (record-type
986
1041
  * letter, 1-based field index); there is no matching on any field *value*, by
@@ -1017,7 +1072,7 @@ interface AstmQuirkMatch {
1017
1072
  interface AstmQuirkTolerance {
1018
1073
  /** The existing, non-safety-critical warning code this profile expects. */
1019
1074
  readonly code: AnyAstmWarningCode;
1020
- /** Why the profile expects this deviation — grounded in its provenance. */
1075
+ /** Why the profile expects this deviation: grounded in its provenance. */
1021
1076
  readonly rationale: string;
1022
1077
  /** Optional structural narrowing (record type / field index). */
1023
1078
  readonly match?: AstmQuirkMatch;
@@ -1027,7 +1082,7 @@ interface AstmQuirkTolerance {
1027
1082
  * {@link defineAstmProfile}; consumers pass it to `parseAstmRecords(raw, { profile })`
1028
1083
  * (or register it as the process default), and feed its {@link AstmProfile.transport}
1029
1084
  * to `detectFraming(bytes, { override })`. Hand-authoring the object literal is
1030
- * supported but discouraged — the factory validates the safety rules and attaches
1085
+ * supported but discouraged: the factory validates the safety rules and attaches
1031
1086
  * `describe()`.
1032
1087
  *
1033
1088
  * @example
@@ -1042,13 +1097,13 @@ interface AstmProfile {
1042
1097
  readonly name: string;
1043
1098
  /** Optional human-readable description. */
1044
1099
  readonly description?: string;
1045
- /** Resolved lineage — `[...parents, name]`, first-occurrence deduped. */
1100
+ /** Resolved lineage: `[...parents, name]`, first-occurrence deduped. */
1046
1101
  readonly lineage: readonly string[];
1047
1102
  /** The expected, non-safety-critical deviations this profile tolerates. */
1048
1103
  readonly tolerate: readonly AstmQuirkTolerance[];
1049
1104
  /**
1050
1105
  * The raw-vs-framed-TCP override. When set, a consumer forces this framing via
1051
- * `detectFraming(bytes, { override: profile.transport })` — the way a profile
1106
+ * `detectFraming(bytes, { override: profile.transport })`, the way a profile
1052
1107
  * that knows a vendor's transport reality (e.g. framing dropped over raw TCP)
1053
1108
  * bypasses leading-byte auto-detection. Absent means "let detection decide."
1054
1109
  */
@@ -1084,9 +1139,9 @@ interface DefineAstmProfileOptions {
1084
1139
  }
1085
1140
 
1086
1141
  /**
1087
- * Safety-critical result semantics for the ASTM `R` (result) record — Phase 2.
1142
+ * Safety-critical result semantics for the ASTM `R` (result) record.
1088
1143
  *
1089
- * Phase 1 surfaced the `R` record's flag, status, units, and reference-range
1144
+ * The parser surfaces the `R` record's flag, status, units, and reference-range
1090
1145
  * fields **raw**. This module turns those raw letters into modeled, **fail-safe**
1091
1146
  * semantics, under one rule: **never a confident wrong value.**
1092
1147
  *
@@ -1095,33 +1150,33 @@ interface DefineAstmProfileOptions {
1095
1150
  * coerced to `normal`**.
1096
1151
  * - A **result status** (field 9) is modeled so a **correction (`C`)** or a
1097
1152
  * **cancellation (`X`)** can never read as an active-final result, and an
1098
- * **absent** status is a typed `unspecified` — **never assumed `F` (final)**.
1153
+ * **absent** status is a typed `unspecified`: **never assumed `F` (final)**.
1099
1154
  * - A **reference range** (field 6) is parsed into a low/high (or open-ended)
1100
- * pair; an unparseable range is surfaced **verbatim** — a bound is **never
1155
+ * pair; an unparseable range is surfaced **verbatim**, a bound is **never
1101
1156
  * fabricated**.
1102
1157
  *
1103
1158
  * Every bound and code is surfaced verbatim; nothing is collapsed, reconciled,
1104
1159
  * or converted. Units are handled at the record layer (a numeric value without
1105
- * units warns; units are never defaulted, guessed, or converted — see
1160
+ * units warns; units are never defaulted, guessed, or converted, see
1106
1161
  * `./parse.ts`).
1107
1162
  */
1108
1163
  /**
1109
1164
  * The recognized abnormal-flag letters, from **HL7 v2.2 Table 0078** (the flag
1110
- * *value* set — a published fact set, not CLSI prose). Read in the context of an
1165
+ * *value* set, a published fact set, not CLSI prose). Read in the context of an
1111
1166
  * `R` record's field 7: here `S`/`R`/`I` are the microbiology susceptibility
1112
1167
  * codes and `R` is **not** the repeat delimiter.
1113
1168
  */
1114
1169
  type AbnormalFlagCode = "L" | "H" | "LL" | "HH" | "<" | ">" | "N" | "A" | "AA" | "U" | "D" | "B" | "W" | "S" | "R" | "I";
1115
1170
  /**
1116
1171
  * The modeled meaning of a recognized {@link AbnormalFlagCode}, plus the two
1117
- * fail-safe sentinels: `undefined` (a flag was present but is not in Table 0078)
1118
- * — which is **never** collapsed to `normal`.
1172
+ * fail-safe sentinels: `undefined` (a flag was present but is not in Table 0078),
1173
+ * which is **never** collapsed to `normal`.
1119
1174
  */
1120
1175
  type AbnormalFlagMeaning = "below-normal" | "above-normal" | "critically-below-normal" | "critically-above-normal" | "below-scale" | "above-scale" | "normal" | "abnormal" | "very-abnormal" | "significant-change-up" | "significant-change-down" | "better" | "worse" | "susceptible" | "resistant" | "intermediate" | "undefined";
1121
1176
  /**
1122
1177
  * A recognized (or explicitly *un*recognized) abnormal flag. The raw field text
1123
1178
  * is always preserved; `recognized` is `false` and `meaning` is `"undefined"`
1124
- * for any letter outside Table 0078 — the flag is surfaced, never dropped, and
1179
+ * for any letter outside Table 0078, the flag is surfaced, never dropped, and
1125
1180
  * **never coerced to `normal`**.
1126
1181
  *
1127
1182
  * @example
@@ -1147,7 +1202,7 @@ interface AbnormalFlag {
1147
1202
  *
1148
1203
  * Leading/trailing whitespace is ignored for the lookup but the `raw` text is
1149
1204
  * preserved verbatim. An unrecognized flag yields `{ recognized: false, meaning:
1150
- * "undefined" }` — surfaced, never dropped, and **never `"normal"`**.
1205
+ * "undefined" }`, surfaced, never dropped, and **never `"normal"`**.
1151
1206
  *
1152
1207
  * @param raw - The verbatim field-7 text.
1153
1208
  * @returns The interpreted flag.
@@ -1161,13 +1216,13 @@ interface AbnormalFlag {
1161
1216
  declare function interpretAbnormalFlag(raw: string): AbnormalFlag;
1162
1217
  /**
1163
1218
  * The recognized result-status letters (`R`-record field 9). The clinically
1164
- * load-bearing members are **`C` (correction — supersedes a previously
1219
+ * load-bearing members are **`C` (correction, supersedes a previously
1165
1220
  * transmitted value)** and **`X` (cannot be done / cancelled)**.
1166
1221
  */
1167
1222
  type ResultStatusCode = "F" | "C" | "P" | "R" | "S" | "I" | "X";
1168
1223
  /**
1169
1224
  * The modeled meaning of a result status, plus two fail-safe sentinels:
1170
- * `unspecified` (the field was absent — **never assumed `final`**) and
1225
+ * `unspecified` (the field was absent, **never assumed `final`**) and
1171
1226
  * `undefined` (a letter was present but is not a recognized status).
1172
1227
  */
1173
1228
  type ResultStatusMeaning = "final" | "correction" | "preliminary" | "previously-transmitted" | "partial" | "pending" | "cancelled" | "unspecified" | "undefined";
@@ -1177,11 +1232,11 @@ type ResultStatusMeaning = "final" | "correction" | "preliminary" | "previously-
1177
1232
  *
1178
1233
  * - **`isActiveFinal`** is `true` **only** for a plain `F` (final). It is `false`
1179
1234
  * for a correction (`C`), a cancellation (`X`), a preliminary/partial/pending
1180
- * result, an absent status, and an unrecognized one — so a superseded or
1235
+ * result, an absent status, and an unrecognized one, so a superseded or
1181
1236
  * cancelled result can **never** read as current/final.
1182
- * - **`supersedes`** is `true` for `C` — this value replaces a previously
1237
+ * - **`supersedes`** is `true` for `C`: this value replaces a previously
1183
1238
  * transmitted one.
1184
- * - **`cancelled`** is `true` for `X` — the result cannot be done / was cancelled.
1239
+ * - **`cancelled`** is `true` for `X`: the result cannot be done / was cancelled.
1185
1240
  *
1186
1241
  * @example
1187
1242
  * ```ts
@@ -1200,17 +1255,17 @@ interface ResultStatus {
1200
1255
  readonly meaning: ResultStatusMeaning;
1201
1256
  /** Whether the raw text matched a recognized status letter. */
1202
1257
  readonly recognized: boolean;
1203
- /** `true` **only** for a plain `F` (final) — never for `C`, `X`, absent, or unrecognized. */
1258
+ /** `true` **only** for a plain `F` (final): never for `C`, `X`, absent, or unrecognized. */
1204
1259
  readonly isActiveFinal: boolean;
1205
- /** `true` for `C` — this value supersedes a previously transmitted result. */
1260
+ /** `true` for `C`: this value supersedes a previously transmitted result. */
1206
1261
  readonly supersedes: boolean;
1207
- /** `true` for `X` — the result cannot be done / was cancelled. */
1262
+ /** `true` for `X`: the result cannot be done / was cancelled. */
1208
1263
  readonly cancelled: boolean;
1209
1264
  }
1210
1265
  /**
1211
1266
  * Interpret an `R`-record result-status field (field 9).
1212
1267
  *
1213
- * An **absent** status (`undefined` or empty) is typed `unspecified` — **never
1268
+ * An **absent** status (`undefined` or empty) is typed `unspecified`: **never
1214
1269
  * assumed `final`**. A **present but unrecognized** letter is `undefined`
1215
1270
  * (recognized `false`). In every non-`F` case `isActiveFinal` is `false`, so a
1216
1271
  * correction, a cancellation, or an unknown status can never read as current.
@@ -1229,10 +1284,10 @@ declare function interpretResultStatus(raw: string | undefined): ResultStatus;
1229
1284
  /**
1230
1285
  * The shape of a parsed reference range.
1231
1286
  *
1232
- * - `closed` — both a low and a high bound (`3.5-5.0`).
1233
- * - `open-low` — an upper bound only (`<5`): everything at or below `high`.
1234
- * - `open-high` — a lower bound only (`>10`): everything at or above `low`.
1235
- * - `unparsed` — the text did not match a recognized form; both bounds are
1287
+ * - `closed`: both a low and a high bound (`3.5-5.0`).
1288
+ * - `open-low`: an upper bound only (`<5`), so everything at or below `high`.
1289
+ * - `open-high`: a lower bound only (`>10`), so everything at or above `low`.
1290
+ * - `unparsed`: the text did not match a recognized form; both bounds are
1236
1291
  * absent and the raw text is surfaced. **A bound is never fabricated.**
1237
1292
  */
1238
1293
  type ReferenceRangeKind = "closed" | "open-low" | "open-high" | "unparsed";
@@ -1241,10 +1296,10 @@ type ReferenceRangeKind = "closed" | "open-low" | "open-high" | "unparsed";
1241
1296
  * (never coerced to a float, never rounded, never converted) so nothing is lost
1242
1297
  * or fabricated.
1243
1298
  *
1244
- * **`[OSS-derived]` — the exact lower/upper delimiter (`-`) and the open-ended
1299
+ * **`[OSS-derived]`: the exact lower/upper delimiter (`-`) and the open-ended
1245
1300
  * `<x`/`>x` forms are taken from the permissively-licensed OSS reference parsers
1246
1301
  * and cross-verified vendor transcripts; they are not confirmed against the
1247
- * purchased CLSI LIS02-A2 (roadmap §10 Q1). Anything that does not match these
1302
+ * purchased CLSI LIS02-A2. Anything that does not match these
1248
1303
  * forms is surfaced verbatim as `unparsed`, never guessed into a bound.**
1249
1304
  *
1250
1305
  * @example
@@ -1271,8 +1326,8 @@ interface ReferenceRange {
1271
1326
  * open-ended) pair.
1272
1327
  *
1273
1328
  * Recognized forms are `low-high` (closed), `<high` (open-low), and `>low`
1274
- * (open-high). Anything else — including an ambiguous multi-dash string or a
1275
- * bare non-numeric token — is returned as `kind: "unparsed"` with the raw text
1329
+ * (open-high). Anything else, including an ambiguous multi-dash string or a
1330
+ * bare non-numeric token, is returned as `kind: "unparsed"` with the raw text
1276
1331
  * preserved and **no bound invented**. Bounds are surfaced as verbatim text, not
1277
1332
  * coerced to numbers.
1278
1333
  *
@@ -1294,13 +1349,12 @@ declare function parseReferenceRange(raw: string): ReferenceRange;
1294
1349
  * A message is a header (which carries the resolved delimiters as provenance), an
1295
1350
  * ordered list of typed records, and the Tier-2 warnings the lenient parser
1296
1351
  * accumulated. Every record keeps its raw {@link AstmField}s **and** the typed
1297
- * accessors relevant to its type — the raw tree is always present so nothing the
1352
+ * accessors relevant to its type: the raw tree is always present so nothing the
1298
1353
  * parser did not model is lost.
1299
1354
  *
1300
- * Phase-1 scope: `H`/`P`/`O`/`R`/`L` are modeled; every other type letter is an
1301
- * {@link UnsupportedRecord} (surfaced, never dropped). Result-flag/status
1302
- * *semantics*, comments/query/`M`/`S`, framing, and serialization are later
1303
- * phases — this layer surfaces the safety-critical values **raw**.
1355
+ * `H`/`P`/`O`/`R`/`C`/`Q`/`M`/`S`/`L` are modeled; every other type letter is an
1356
+ * {@link UnsupportedRecord} (surfaced, never dropped). Alongside the modeled
1357
+ * views, this layer always surfaces the safety-critical values **raw**.
1304
1358
  */
1305
1359
 
1306
1360
  /**
@@ -1333,14 +1387,25 @@ interface RecordBase {
1333
1387
  */
1334
1388
  interface HeaderRecord extends RecordBase {
1335
1389
  readonly type: "H";
1336
- /** The four delimiters resolved from this header. */
1390
+ /**
1391
+ * The four delimiters **this** header put into force: the set its own fields, and every record
1392
+ * after it, were read with.
1393
+ *
1394
+ * A stream may carry several messages (`H` … `L`) and each header declares its own set, so on a
1395
+ * multi-header stream these can differ between headers; {@link AstmMessage.delimiters} is the
1396
+ * first header's. When a later header cannot declare a usable set, the set already in force is
1397
+ * kept and reported here (with an `ASTM_RECORD_UNREADABLE_REDECLARATION` warning) rather than a
1398
+ * guessed one.
1399
+ */
1337
1400
  readonly delimiters: Delimiters;
1338
1401
  /**
1339
- * The header's exact wire text (terminator excluded), preserved because the
1340
- * escape character appears **literally** inside the delimiter-definition field
1341
- * (`\^&`), which the generic escape-aware tokenizer cannot split cleanly. The
1342
- * delimiters are read from this raw text (never from {@link RecordBase.fields}),
1343
- * and the serializer reconstructs the header's data fields from it.
1402
+ * The header's exact wire text (terminator excluded), kept as **provenance**: the
1403
+ * delimiters are read from this raw text, never from {@link RecordBase.fields}.
1404
+ *
1405
+ * It is **not** what the serializer emits from. To change a header field, edit
1406
+ * {@link RecordBase.fields}: `fields[0]` is the type letter, `fields[1]` is the
1407
+ * delimiter declaration, and the header's ASTM data fields follow from `fields[2]`.
1408
+ * Editing this string instead has no effect on emit.
1344
1409
  */
1345
1410
  readonly rawLine: string;
1346
1411
  }
@@ -1357,158 +1422,158 @@ interface PatientName {
1357
1422
  * The `P` (patient) record.
1358
1423
  *
1359
1424
  * **Safety:** the practice-assigned ID (field 3) and the laboratory-assigned ID
1360
- * (field 4) are modeled as **distinct** fields and never collapsed — conflating
1425
+ * (field 4) are modeled as **distinct** fields and never collapsed, conflating
1361
1426
  * them is the primary result-misfiling path.
1362
1427
  */
1363
1428
  interface PatientRecord extends RecordBase {
1364
1429
  readonly type: "P";
1365
- /** Field 2 — sequence number. */
1430
+ /** Field 2: sequence number. */
1366
1431
  readonly seq?: string;
1367
- /** Field 3 — practice-assigned patient ID. Distinct from {@link PatientRecord.laboratoryAssignedId}. */
1432
+ /** Field 3: practice-assigned patient ID. Distinct from {@link PatientRecord.laboratoryAssignedId}. */
1368
1433
  readonly practiceAssignedId?: string;
1369
- /** Field 4 — laboratory-assigned patient ID. Distinct from {@link PatientRecord.practiceAssignedId}. */
1434
+ /** Field 4: laboratory-assigned patient ID. Distinct from {@link PatientRecord.practiceAssignedId}. */
1370
1435
  readonly laboratoryAssignedId?: string;
1371
1436
  /**
1372
- * Field 5 — a third patient identifier (e.g. a national/alternate ID), surfaced verbatim. Kept
1373
- * separate from the practice- and laboratory-assigned IDs — the three never collapse into one.
1437
+ * Field 5: a third patient identifier (e.g. a national/alternate ID), surfaced verbatim. Kept
1438
+ * separate from the practice- and laboratory-assigned IDs: the three never collapse into one.
1374
1439
  */
1375
1440
  readonly patientIdThree?: string;
1376
- /** Field 6 — patient name (`Last^First^Middle`). */
1441
+ /** Field 6: patient name (`Last^First^Middle`). */
1377
1442
  readonly name?: PatientName;
1378
- /** Field 7 — mother's maiden name, surfaced verbatim (a surname component; PHI). */
1443
+ /** Field 7: mother's maiden name, surfaced verbatim (a surname component; PHI). */
1379
1444
  readonly mothersMaidenName?: string;
1380
- /** Field 8 — birthdate (`YYYYMMDDHHMMSS`, precision-preserving; a truncated run sets `truncated`). */
1445
+ /** Field 8: birthdate (`YYYYMMDDHHMMSS`, precision-preserving; a truncated run sets `truncated`). */
1381
1446
  readonly birthDate?: AstmDate;
1382
- /** Field 9 — sex, surfaced raw (`M`/`F`/`U`/vendor value). */
1447
+ /** Field 9: sex, surfaced raw (`M`/`F`/`U`/vendor value). */
1383
1448
  readonly sex?: string;
1384
1449
  }
1385
1450
  /**
1386
- * The `O` (order) record — binds a result to a specimen.
1451
+ * The `O` (order) record: binds a result to a specimen.
1387
1452
  */
1388
1453
  interface OrderRecord extends RecordBase {
1389
1454
  readonly type: "O";
1390
- /** Field 2 — sequence number. */
1455
+ /** Field 2: sequence number. */
1391
1456
  readonly seq?: string;
1392
- /** Field 3 — specimen / accession ID. */
1457
+ /** Field 3: specimen / accession ID. */
1393
1458
  readonly specimenId?: string;
1394
- /** Field 4 — instrument specimen ID. */
1459
+ /** Field 4: instrument specimen ID. */
1395
1460
  readonly instrumentSpecimenId?: string;
1396
- /** Field 5 — Universal Test ID (same caret structure as a result's). */
1461
+ /** Field 5: Universal Test ID (same caret structure as a result's). */
1397
1462
  readonly universalTestId?: UniversalTestId;
1398
1463
  /**
1399
- * Field 6 — priority, surfaced raw (e.g. `S` STAT, `R` routine, `A` ASAP). Vendor letters vary;
1464
+ * Field 6: priority, surfaced raw (e.g. `S` STAT, `R` routine, `A` ASAP). Vendor letters vary;
1400
1465
  * the code set is `[OSS-derived]` (the exact enumeration is in the paywalled CLSI LIS02-A2), so the
1401
1466
  * value is surfaced verbatim and **never mapped to a guessed meaning**.
1402
1467
  */
1403
1468
  readonly priority?: string;
1404
1469
  /**
1405
- * Field 12 — action code, surfaced raw (e.g. `C` cancel, `A` add, `N` new). The exact field index
1406
- * (`~12`) and the code set are `[OSS-derived]` (paywalled) — surfaced verbatim, never interpreted.
1470
+ * Field 12: action code, surfaced raw (e.g. `C` cancel, `A` add, `N` new). The exact field index
1471
+ * (`~12`) and the code set are `[OSS-derived]` (paywalled): surfaced verbatim, never interpreted.
1407
1472
  */
1408
1473
  readonly actionCode?: string;
1409
1474
  /**
1410
- * Field 26 — report type, surfaced raw (e.g. `F` final, `P` preliminary, `X` cancel). The exact
1411
- * field index (`~26`) and the code set are `[OSS-derived]` (paywalled) — surfaced verbatim.
1475
+ * Field 26: report type, surfaced raw (e.g. `F` final, `P` preliminary, `X` cancel). The exact
1476
+ * field index (`~26`) and the code set are `[OSS-derived]` (paywalled): surfaced verbatim.
1412
1477
  */
1413
1478
  readonly reportType?: string;
1414
1479
  }
1415
1480
  /**
1416
- * The `R` (result) record — the value itself.
1481
+ * The `R` (result) record: the value itself.
1417
1482
  *
1418
1483
  * The raw safety-critical fields (value, `units`, `referenceRange`,
1419
1484
  * `abnormalFlags`, `resultStatus`) are always surfaced exactly as received.
1420
- * Phase 2 adds the **modeled, fail-safe** semantics alongside them: `flag`
1485
+ * The **modeled, fail-safe** semantics sit alongside them: `flag`
1421
1486
  * (Table 0078, `undefined` never coerced to normal), `status` (a `C`/`X` never
1422
1487
  * reads as active-final; an absent status is `unspecified`, never `final`), and
1423
1488
  * `range` (open/closed bounds surfaced verbatim, never fabricated). The raw
1424
- * strings and the modeled views coexist — nothing is collapsed or reconciled.
1489
+ * strings and the modeled views coexist: nothing is collapsed or reconciled.
1425
1490
  */
1426
1491
  interface ResultRecord extends RecordBase {
1427
1492
  readonly type: "R";
1428
- /** Field 2 — sequence number. */
1493
+ /** Field 2: sequence number. */
1429
1494
  readonly seq?: string;
1430
- /** Field 3 — Universal Test ID (local code in component 4 is the primary identifier). */
1495
+ /** Field 3: Universal Test ID (local code in component 4 is the primary identifier). */
1431
1496
  readonly universalTestId?: UniversalTestId;
1432
1497
  /**
1433
- * Field 4 — the measured value. In the ordinary single-component case this is the decoded scalar.
1498
+ * Field 4: the measured value. In the ordinary single-component case this is the decoded scalar.
1434
1499
  * When the value field carried an *unescaped* component delimiter (an ambiguity), this is the
1435
- * **full raw field text** — never a truncated component — and {@link ResultRecord.valueComponents}
1500
+ * **full raw field text**, never a truncated component, and {@link ResultRecord.valueComponents}
1436
1501
  * plus an `ASTM_RECORD_AMBIGUOUS_VALUE_SPLIT` warning are also present.
1437
1502
  */
1438
1503
  readonly value?: string;
1439
1504
  /**
1440
1505
  * Field 4 split into components, present only when the value field carried an *unescaped* component
1441
- * delimiter — i.e. it read as more than one component. Surfaced alongside the full raw value (and a
1506
+ * delimiter: i.e. it read as more than one component. Surfaced alongside the full raw value (and a
1442
1507
  * warning) so an ambiguous split is visible, never resolved silently.
1443
1508
  */
1444
1509
  readonly valueComponents?: readonly string[];
1445
- /** Field 5 — units (vendor free text; a missing unit is not defaulted). */
1510
+ /** Field 5: units (vendor free text; a missing unit is not defaulted). */
1446
1511
  readonly units?: string;
1447
- /** Field 6 — reference range, surfaced raw. */
1512
+ /** Field 6: reference range, surfaced raw. */
1448
1513
  readonly referenceRange?: string;
1449
1514
  /**
1450
- * Field 6 — the reference range parsed into low/high (or open-ended) bounds, present only when the
1515
+ * Field 6: the reference range parsed into low/high (or open-ended) bounds, present only when the
1451
1516
  * field carried a value. An unparseable range is `kind: "unparsed"` with the raw text preserved and
1452
1517
  * **no bound fabricated**. Bounds are verbatim numeric text, never coerced to floats.
1453
1518
  */
1454
1519
  readonly range?: ReferenceRange;
1455
- /** Field 7 — abnormal flags, surfaced raw (HL7 Table 0078 values). */
1520
+ /** Field 7: abnormal flags, surfaced raw (HL7 Table 0078 values). */
1456
1521
  readonly abnormalFlags?: string;
1457
1522
  /**
1458
- * Field 7 — the abnormal flag interpreted against HL7 Table 0078, present only when the field
1459
- * carried a value. An unrecognized flag is `{ recognized: false, meaning: "undefined" }` — surfaced,
1523
+ * Field 7: the abnormal flag interpreted against HL7 Table 0078, present only when the field
1524
+ * carried a value. An unrecognized flag is `{ recognized: false, meaning: "undefined" }`, surfaced,
1460
1525
  * never dropped, and **never coerced to `normal`**.
1461
1526
  */
1462
1527
  readonly flag?: AbnormalFlag;
1463
- /** Field 9 — result status, surfaced raw (`F`/`C`/`X`/…). */
1528
+ /** Field 9: result status, surfaced raw (`F`/`C`/`X`/…). */
1464
1529
  readonly resultStatus?: string;
1465
1530
  /**
1466
- * Field 9 — the modeled result status. **Always present** (an absent field yields a typed
1531
+ * Field 9: the modeled result status. **Always present** (an absent field yields a typed
1467
1532
  * `unspecified`, never assumed `final`), so `status.isActiveFinal` is always a reliable boolean: it
1468
1533
  * is `true` only for a plain `F`, and `false` for a correction (`C`), a cancellation (`X`), a
1469
1534
  * partial/preliminary/pending, an absent, or an unrecognized status.
1470
1535
  */
1471
1536
  readonly status: ResultStatus;
1472
- /** Field 11 — operator. */
1537
+ /** Field 11: operator. */
1473
1538
  readonly operator?: string;
1474
- /** Field 12 — test started timestamp. */
1539
+ /** Field 12: test started timestamp. */
1475
1540
  readonly startedAt?: AstmDate;
1476
- /** Field 13 — test completed timestamp. */
1541
+ /** Field 13: test completed timestamp. */
1477
1542
  readonly completedAt?: AstmDate;
1478
- /** Field 14 — instrument identifier. */
1543
+ /** Field 14: instrument identifier. */
1479
1544
  readonly instrument?: string;
1480
1545
  }
1481
1546
  /**
1482
- * The `C` (comment) record — free-text context attached to a parent record.
1547
+ * The `C` (comment) record: free-text context attached to a parent record.
1483
1548
  *
1484
1549
  * A comment is **attached by position** to the immediately-preceding `H`/`P`/`O`/`R` record
1485
1550
  * ({@link CommentRecord.parentIndex}); consecutive comments share that parent. **Fail-safe:** a
1486
- * comment with no valid preceding parent is an **orphan** — it is attached to the message root
1551
+ * comment with no valid preceding parent is an **orphan**: it is attached to the message root
1487
1552
  * ({@link CommentRecord.attachedToRoot} `true`) and a value-free `ASTM_RECORD_ORPHAN_COMMENT` warning
1488
- * fires — **never silently dropped**, so a comment carrying (e.g.) "QC / non-compliant" context can
1553
+ * fires, **never silently dropped**, so a comment carrying (e.g.) "QC / non-compliant" context can
1489
1554
  * never float away unnoticed.
1490
1555
  */
1491
1556
  interface CommentRecord extends RecordBase {
1492
1557
  readonly type: "C";
1493
- /** Field 2 — sequence number. */
1558
+ /** Field 2: sequence number. */
1494
1559
  readonly seq?: string;
1495
- /** Field 3 — comment source (who/what produced it), surfaced verbatim. */
1560
+ /** Field 3: comment source (who/what produced it), surfaced verbatim. */
1496
1561
  readonly source?: string;
1497
1562
  /**
1498
- * Field 4 — the comment text, surfaced as the **full** field text (all components), never
1563
+ * Field 4: the comment text, surfaced as the **full** field text (all components), never
1499
1564
  * truncated to the first component. The component structure is in {@link CommentRecord.textComponents}.
1500
1565
  */
1501
1566
  readonly text?: string;
1502
1567
  /**
1503
- * Field 4 — the comment text split into its decoded components (comment text is component-capable;
1568
+ * Field 4: the comment text split into its decoded components (comment text is component-capable;
1504
1569
  * multiple components are a normal structured comment, **not** an ambiguity). Present only when the
1505
1570
  * field carried more than one component.
1506
1571
  */
1507
1572
  readonly textComponents?: readonly string[];
1508
1573
  /**
1509
- * Field 5 — comment type code, surfaced verbatim. **`[OSS-derived]`:** `I` (instrument) is the only
1574
+ * Field 5: comment type code, surfaced verbatim. **`[OSS-derived]`:** `I` (instrument) is the only
1510
1575
  * value seen in the permissively-licensed real transcripts; other values (e.g. `G`/`T`/`P`) are
1511
- * defined only in the paywalled CLSI LIS02-A2 and are **not** interpreted here — the raw code is
1576
+ * defined only in the paywalled CLSI LIS02-A2 and are **not** interpreted here: the raw code is
1512
1577
  * surfaced, never mapped to a guessed meaning.
1513
1578
  */
1514
1579
  readonly commentType?: string;
@@ -1517,57 +1582,57 @@ interface CommentRecord extends RecordBase {
1517
1582
  * the comment is an orphan attached to the message root (see {@link CommentRecord.attachedToRoot}).
1518
1583
  */
1519
1584
  readonly parentIndex?: number;
1520
- /** `true` when no valid parent preceded — the comment is attached to the message root (and warned). */
1585
+ /** `true` when no valid parent preceded: the comment is attached to the message root (and warned). */
1521
1586
  readonly attachedToRoot: boolean;
1522
1587
  }
1523
1588
  /**
1524
- * The `Q` (Request Information) record — the host-query request.
1589
+ * The `Q` (Request Information) record: the host-query request.
1525
1590
  *
1526
1591
  * A `Q` record asks the LIS for information (e.g. outstanding orders for a
1527
1592
  * specimen); its **presence classifies the whole message as a request, never a
1528
1593
  * result set** (see {@link AstmMessage.classification}). Its safety-relevant
1529
- * fields — the starting/ending range ID and the request-information status — are
1594
+ * fields, the starting/ending range ID and the request-information status, are
1530
1595
  * surfaced **verbatim**; the field *positions* are the public ASTM E1394 layout,
1531
1596
  * but their internal structure and code meanings are **`[OSS-derived / paywalled]`**
1532
- * (roadmap §10 Q3) and are therefore **never interpreted or guessed**.
1597
+ * and are therefore **never interpreted or guessed**.
1533
1598
  */
1534
1599
  interface QueryRecord extends RecordBase {
1535
- /** Field 2 — sequence number. */
1600
+ /** Field 2: sequence number. */
1536
1601
  readonly seq?: string;
1537
1602
  readonly type: "Q";
1538
1603
  /**
1539
- * Field 3 — starting range ID number, surfaced as the **full** verbatim field text (never truncated
1604
+ * Field 3: starting range ID number, surfaced as the **full** verbatim field text (never truncated
1540
1605
  * to a component). Its caret component structure (e.g. patient ID ^ specimen ID) is
1541
- * **`[OSS-derived / paywalled]`** — the raw split is available via {@link RecordBase.fields}, but the
1606
+ * **`[OSS-derived / paywalled]`**: the raw split is available via {@link RecordBase.fields}, but the
1542
1607
  * *meaning* of each component is **never** assigned here.
1543
1608
  */
1544
1609
  readonly startingRangeId?: string;
1545
- /** Field 4 — ending range ID number, surfaced verbatim (same `[OSS-derived]` caveat as field 3). */
1610
+ /** Field 4: ending range ID number, surfaced verbatim (same `[OSS-derived]` caveat as field 3). */
1546
1611
  readonly endingRangeId?: string;
1547
1612
  /**
1548
- * Field 5 — Universal Test ID (the same caret structure as an `O`/`R` record's), recognized by
1613
+ * Field 5: Universal Test ID (the same caret structure as an `O`/`R` record's), recognized by
1549
1614
  * provenance only. When the field is the literal universal-query keyword, {@link QueryRecord.queriesAllTests}
1550
- * is set instead — see its caveat.
1615
+ * is set instead: see its caveat.
1551
1616
  */
1552
1617
  readonly universalTestId?: UniversalTestId;
1553
1618
  /**
1554
1619
  * `true` when field 5 is the literal `ALL` universal-query keyword (case-insensitive). **`[OSS-derived
1555
1620
  * / paywalled]`:** the token is surfaced because it appears in the OSS references, but its exact
1556
1621
  * host-query *behavior* (which tests a bare `ALL` selects, and whether the vendor answers with a full
1557
- * `H/P/O/L` or a `P/O`-only response) is paywalled and vendor-specific — **not decided here**.
1622
+ * `H/P/O/L` or a `P/O`-only response) is paywalled and vendor-specific, **not decided here**.
1558
1623
  */
1559
1624
  readonly queriesAllTests: boolean;
1560
1625
  /**
1561
- * Field 13 — request-information status code(s), surfaced **verbatim**. The status code *set* is
1562
- * **`[OSS-derived / paywalled]`** (roadmap §10 Q3): with no publicly-groundable enumeration, this
1563
- * parser recognizes **none** of them and interprets nothing — every present status is surfaced raw
1626
+ * Field 13: request-information status code(s), surfaced **verbatim**. The status code *set* is
1627
+ * **`[OSS-derived / paywalled]`**: with no publicly-groundable enumeration, this
1628
+ * parser recognizes **none** of them and interprets nothing, every present status is surfaced raw
1564
1629
  * and flagged with a value-free `ASTM_RECORD_UNINTERPRETED_QUERY_STATUS` warning. Never mapped to a
1565
1630
  * guessed meaning.
1566
1631
  */
1567
1632
  readonly requestInformationStatus?: string;
1568
1633
  }
1569
1634
  /**
1570
- * The `M` (manufacturer) record — vendor-defined free-form data
1635
+ * The `M` (manufacturer) record: vendor-defined free-form data
1571
1636
  * (QC / calibration / maintenance), surfaced **VERBATIM** and **never interpreted
1572
1637
  * into typed clinical fields**.
1573
1638
  *
@@ -1578,30 +1643,48 @@ interface QueryRecord extends RecordBase {
1578
1643
  */
1579
1644
  interface ManufacturerRecord extends RecordBase {
1580
1645
  readonly type: "M";
1581
- /** The record's exact wire text (terminator excluded), preserved byte-for-byte for round-trip. */
1646
+ /**
1647
+ * The record's exact wire text (terminator excluded), preserved byte-for-byte.
1648
+ *
1649
+ * Emit reproduces these bytes exactly whenever a reader using the delimiters being
1650
+ * emitted against would recover the fields this record models: always the case when
1651
+ * the record is already in those delimiters, and also when it carries no delimiter
1652
+ * either set would split on. Otherwise the record is re-encoded from
1653
+ * {@link RecordBase.fields}, so the row can never go out in delimiters the header does
1654
+ * not declare. To change a value, edit `fields`; editing this string has no effect on emit.
1655
+ */
1582
1656
  readonly rawLine: string;
1583
1657
  }
1584
1658
  /**
1585
- * The `S` (scientific) record — vendor-defined free-form data, surfaced
1659
+ * The `S` (scientific) record: vendor-defined free-form data, surfaced
1586
1660
  * **VERBATIM** and **never interpreted into typed clinical fields** (same posture
1587
1661
  * as {@link ManufacturerRecord}).
1588
1662
  */
1589
1663
  interface ScientificRecord extends RecordBase {
1590
1664
  readonly type: "S";
1591
- /** The record's exact wire text (terminator excluded), preserved byte-for-byte for round-trip. */
1665
+ /**
1666
+ * The record's exact wire text (terminator excluded), preserved byte-for-byte.
1667
+ *
1668
+ * Emit reproduces these bytes exactly whenever a reader using the delimiters being
1669
+ * emitted against would recover the fields this record models: always the case when
1670
+ * the record is already in those delimiters, and also when it carries no delimiter
1671
+ * either set would split on. Otherwise the record is re-encoded from
1672
+ * {@link RecordBase.fields}, so the row can never go out in delimiters the header does
1673
+ * not declare. To change a value, edit `fields`; editing this string has no effect on emit.
1674
+ */
1592
1675
  readonly rawLine: string;
1593
1676
  }
1594
1677
  /**
1595
- * The `L` (terminator) record — closes a message.
1678
+ * The `L` (terminator) record: closes a message.
1596
1679
  */
1597
1680
  interface TerminatorRecord extends RecordBase {
1598
1681
  readonly type: "L";
1599
1682
  }
1600
1683
  /**
1601
1684
  * Any record whose type letter is not modeled (a genuinely unknown letter).
1602
- * `H`/`P`/`O`/`R`/`C`/`L` (Phases 1–3) and `Q`/`M`/`S` (Phase 4) are all modeled;
1603
- * anything else is surfaced with its raw fields intact and flagged with an
1604
- * `ASTM_RECORD_UNKNOWN_TYPE` warning — never dropped.
1685
+ * `H`/`P`/`O`/`R`/`C`/`Q`/`M`/`S`/`L` are all modeled; anything else is surfaced
1686
+ * with its raw fields intact and flagged with an
1687
+ * `ASTM_RECORD_UNKNOWN_TYPE` warning, never dropped.
1605
1688
  */
1606
1689
  interface UnsupportedRecord extends RecordBase {
1607
1690
  readonly type: "unsupported";
@@ -1613,30 +1696,30 @@ type AstmRecord = HeaderRecord | PatientRecord | OrderRecord | ResultRecord | Co
1613
1696
  /**
1614
1697
  * How a message is classified by the host-query flow.
1615
1698
  *
1616
- * - `host-query` — the message carries at least one `Q` record: it is a **request
1699
+ * - `host-query` (the message carries at least one `Q` record): it is a **request
1617
1700
  * for information**, and **must never be read as a result set** (the load-bearing
1618
1701
  * safety distinction of this layer). `Q` **dominates**: a `Q` present classifies
1619
1702
  * the message as a request even if a result record is also present (an anomaly,
1620
- * separately warned) — so a `Q`-bearing message is never silently treated as a
1703
+ * separately warned), so a `Q`-bearing message is never silently treated as a
1621
1704
  * result upload.
1622
- * - `results` — no `Q`, at least one `R` (result) record: a result upload / response.
1623
- * - `orders` — no `Q`, no `R`, at least one `O` (order) record: an order download,
1705
+ * - `results` (no `Q`, at least one `R` result record): a result upload / response.
1706
+ * - `orders` (no `Q`, no `R`, at least one `O` order record): an order download,
1624
1707
  * or a query response before results are attached.
1625
- * - `indeterminate` — none of the above (e.g. header + terminator only). Not
1708
+ * - `indeterminate`: none of the above (e.g. header + terminator only). Not
1626
1709
  * guessed into one of the other kinds.
1627
1710
  */
1628
1711
  type AstmMessageKind = "host-query" | "results" | "orders" | "indeterminate";
1629
1712
  /**
1630
1713
  * The message-level classification from the host-query flow. `isHostQueryRequest`
1631
1714
  * is the single boolean a consumer should gate on before treating records as
1632
- * results — it is `true` **iff** a `Q` record is present.
1715
+ * results: it is `true` **iff** a `Q` record is present.
1633
1716
  *
1634
1717
  * @example
1635
1718
  * ```ts
1636
1719
  * import { parseAstmRecords } from "@cosyte/astm";
1637
1720
  * const req = parseAstmRecords("H|\\^&\rP|1\rQ|1|^SPEC-7||ALL\rL|1\r");
1638
1721
  * req.classification.kind; // "host-query"
1639
- * req.classification.isHostQueryRequest; // true — never read its records as results
1722
+ * req.classification.isHostQueryRequest; // true, never read its records as results
1640
1723
  * ```
1641
1724
  */
1642
1725
  interface AstmMessageClassification {
@@ -1649,7 +1732,7 @@ interface AstmMessageClassification {
1649
1732
  /** At least one `O` (order) record is present. */
1650
1733
  readonly hasOrders: boolean;
1651
1734
  /**
1652
- * `true` **iff** `kind === "host-query"` (a `Q` record is present) — the safety surface: gate on this
1735
+ * `true` **iff** `kind === "host-query"` (a `Q` record is present), the safety surface: gate on this
1653
1736
  * before treating records as results, so a query is never misread as a result upload.
1654
1737
  */
1655
1738
  readonly isHostQueryRequest: boolean;
@@ -1667,11 +1750,20 @@ interface AstmMessageClassification {
1667
1750
  * ```
1668
1751
  */
1669
1752
  interface AstmMessage {
1753
+ /** The **first** header in the stream. A multi-header stream's later headers are in {@link AstmMessage.records}. */
1670
1754
  readonly header: HeaderRecord;
1671
1755
  readonly records: readonly AstmRecord[];
1756
+ /**
1757
+ * The delimiters the **first** header declared.
1758
+ *
1759
+ * A stream may carry several messages (`H` … `L`), and a later header may declare a different set,
1760
+ * which is honored from that header onward. So this is not necessarily the set every record was
1761
+ * read with: each header reports its own on {@link HeaderRecord.delimiters}, and a change is
1762
+ * flagged `ASTM_RECORD_DELIMITERS_REDECLARED`.
1763
+ */
1672
1764
  readonly delimiters: Delimiters;
1673
1765
  /**
1674
- * The host-query classification of this message — whether it is a request (`Q`
1766
+ * The host-query classification of this message: whether it is a request (`Q`
1675
1767
  * present), a result upload, an order download, or indeterminate. Gate on
1676
1768
  * {@link AstmMessageClassification.isHostQueryRequest} before reading records as
1677
1769
  * results.
@@ -1679,7 +1771,7 @@ interface AstmMessage {
1679
1771
  readonly classification: AstmMessageClassification;
1680
1772
  readonly warnings: readonly AstmRecordWarning[];
1681
1773
  /**
1682
- * The vendor profile that was active for this parse, when one applied — its
1774
+ * The vendor profile that was active for this parse, when one applied: its
1683
1775
  * `name` and resolved `lineage` (attribution only; the profile object itself is
1684
1776
  * not embedded). Absent when no profile applied. A profile that tolerated a
1685
1777
  * deviation shows up as a `PROFILE_QUIRK_APPLIED` entry in {@link warnings}, not
@@ -1697,7 +1789,7 @@ interface AstmParseOptions {
1697
1789
  /**
1698
1790
  * When `true`, escalate any Tier-2 deviation to a thrown {@link AstmStrictError}
1699
1791
  * instead of accumulating a warning. Off by default. A deviation a profile
1700
- * *expected* (a `PROFILE_QUIRK_APPLIED`) does **not** escalate — it is known,
1792
+ * *expected* (a `PROFILE_QUIRK_APPLIED`) does **not** escalate: it is known,
1701
1793
  * benign, and still recorded.
1702
1794
  */
1703
1795
  readonly strict?: boolean;
@@ -1714,11 +1806,11 @@ interface AstmParseOptions {
1714
1806
  /**
1715
1807
  * The record-layer entry point: {@link parseAstmRecords}.
1716
1808
  *
1717
- * Phase 1 assumes **already-de-framed** record bytes (the E1381/LIS01 framing
1718
- * layer is a later phase). It reads the four delimiters from the header, tokenizes
1719
- * every record escape-aware, and builds the immutable {@link AstmMessage} — lenient
1720
- * by default: vendor quirks become typed warnings, and only three unrecoverable
1721
- * structural conditions are fatal.
1809
+ * It assumes **already-de-framed** record bytes; {@link parseFramedAstm} composes
1810
+ * the E1381/LIS01 framing layer with this one. It reads the four delimiters from each
1811
+ * `H` record, tokenizes every record escape-aware, and builds the immutable
1812
+ * {@link AstmMessage}, lenient by default: vendor quirks become typed warnings, and
1813
+ * only three unrecoverable structural conditions are fatal.
1722
1814
  */
1723
1815
 
1724
1816
  /**
@@ -1746,8 +1838,18 @@ declare class AstmStrictError extends Error {
1746
1838
  *
1747
1839
  * The stream is a sequence of records separated by `CR` (with `LF`/`CRLF`
1748
1840
  * tolerated); the first record must be an `H` header, which declares the
1749
- * delimiters used by the rest. Lenient by default — set `strict` to reject any
1750
- * tolerated deviation.
1841
+ * delimiters. Lenient by default: set `strict` to reject any tolerated deviation.
1842
+ *
1843
+ * **Several messages in one stream.** A message runs `H` … `L`, so a stream may carry
1844
+ * more than one, and each `H` declares the delimiters for the records that follow it.
1845
+ * A later header that declares a **different** set is honored from that header onward
1846
+ * and flagged `ASTM_RECORD_DELIMITERS_REDECLARED`; records already read keep the set
1847
+ * they were read with, so a redeclaration never reinterprets earlier bytes. A header
1848
+ * that merely restates the set in force is a no-op and warns nothing. A later header
1849
+ * that cannot declare a usable set keeps the delimiters already in force and warns
1850
+ * `ASTM_RECORD_UNREADABLE_REDECLARATION`: a set is never guessed, and no record is
1851
+ * dropped. Per-header sets are on each {@link HeaderRecord.delimiters};
1852
+ * {@link AstmMessage.delimiters} is the first header's.
1751
1853
  *
1752
1854
  * @param raw - The de-framed record bytes, as a string or `Uint8Array` (decoded
1753
1855
  * latin1 so byte values survive 1:1).
@@ -1765,9 +1867,9 @@ declare class AstmStrictError extends Error {
1765
1867
  */
1766
1868
  declare function parseAstmRecords(raw: string | Uint8Array, options?: AstmParseOptions): AstmMessage;
1767
1869
  /**
1768
- * Attach every `C` (comment) record to its parent by position — the immediately-preceding
1870
+ * Attach every `C` (comment) record to its parent by position: the immediately-preceding
1769
1871
  * `H`/`P`/`O`/`R` record; consecutive comments share that parent. **Fail-safe:** a comment with no
1770
- * valid preceding parent is an **orphan** — it is attached to the message root (`attachedToRoot:
1872
+ * valid preceding parent is an **orphan**, it is attached to the message root (`attachedToRoot:
1771
1873
  * true`) with an `ASTM_RECORD_ORPHAN_COMMENT` warning, **never dropped**. Returns a new array; the
1772
1874
  * non-comment records pass through unchanged.
1773
1875
  *
@@ -1789,33 +1891,63 @@ declare function parseAstmRecords(raw: string | Uint8Array, options?: AstmParseO
1789
1891
  declare function attachComments(records: readonly AstmRecord[], warnings: AstmRecordWarning[]): AstmRecord[];
1790
1892
 
1791
1893
  /**
1792
- * The record-layer **emit** side: {@link serializeAstmRecords} — Phase 7.
1894
+ * The record-layer **emit** side: {@link serializeAstmRecords}.
1793
1895
  *
1794
1896
  * The inverse of `parseAstmRecords`. Where the parser is liberal (vendor quirks
1795
1897
  * become warnings), the serializer is **conservative**: it always emits a
1796
1898
  * spec-clean stream with the **canonical** delimiter set (`H|\^&`), re-escapes
1797
1899
  * every embedded delimiter so an embedded `|`/`^`/`\`/`&` in a value can never
1798
- * break framing (the exact inverse of the Phase-1 escape codec), and terminates
1900
+ * break framing (the exact inverse of the escape codec), and terminates
1799
1901
  * each record with a `CR`.
1800
1902
  *
1801
- * **Round-trip by construction.** Serialization emits from a record's **decoded**
1802
- * component tree (`AstmField.repeats`) and re-escapes each leaf, so
1803
- * `parseAstmRecords(serializeAstmRecords(msg))` reproduces the same modelled
1804
- * fields — the same components, the same typed accessors, and the canonical
1805
- * delimiter set (a non-canonical source is normalized to `H|\^&`, a documented
1806
- * Phase-7 behavior; round-tripping a vendor's own delimiters is a Phase-8
1807
- * profile concern).
1903
+ * **Round-trip.** `parseAstmRecords(serializeAstmRecords(msg))` reproduces the modeled
1904
+ * records: the same components, the same typed accessors, and the canonical delimiter
1905
+ * set (a non-canonical source is normalized to `H|\^&` by default). Normalizing sets the
1906
+ * four delimiter **roles**; it does not delete a declaration's surplus characters, so a
1907
+ * canonically-declared header that arrived carrying surplus keeps it on the default path
1908
+ * too.
1909
+ *
1910
+ * **One delimiter set for the whole stream.** Every record (including `H`, `M` and `S`)
1911
+ * is emitted against the delimiters being emitted with, and the header declares that same
1912
+ * set. `M` and `S` carry vendor free-form data and are reproduced **byte-for-byte** from
1913
+ * their preserved `rawLine` whenever a reader using those delimiters would recover exactly
1914
+ * the fields they model, which is the case for any stream already in the emit delimiter
1915
+ * set. When it is not, the record arrived under one delimiter set and is being emitted
1916
+ * under another, the record is re-encoded from its decoded tree instead, so its fields
1917
+ * survive as fields rather than collapsing into one on the next read.
1918
+ *
1919
+ * **Emit follows the model.** Every record type, `H` included, is emitted from its decoded
1920
+ * `AstmField.repeats` tree, so an edit to a record's `fields` is reflected on emit. The one
1921
+ * part of a record that is never taken from the model is the header's delimiter
1922
+ * declaration, and only in the three positions that carry a role: those always state the
1923
+ * repeat, component and escape characters actually in use, whatever the model says. Any
1924
+ * further characters the declaration carried, a reader assigns them no role, come from
1925
+ * the model like everything else, so a header that arrived as `H|\^&#` goes back out as
1926
+ * `H|\^&#` rather than losing the `#`. That holds on the **default** canonical path as
1927
+ * well as when a set is passed explicitly: the surplus of a canonically-declared header
1928
+ * survives normalization, because normalization is about the four roles and the surplus
1929
+ * has none.
1808
1930
  *
1809
1931
  * **Never break framing.** A component leaf that contains a record terminator
1810
1932
  * (`CR`/`LF`) cannot be escaped by the ASTM escape codec (only the four declared
1811
1933
  * delimiters have mnemonics), so emitting it would silently corrupt the wire.
1812
1934
  * The serializer refuses: an embedded `CR`/`LF` is a typed {@link AstmSerializeError},
1813
1935
  * never emitted raw.
1936
+ *
1937
+ * **Check the delimiter set before writing.** Three conditions are required for the
1938
+ * emitted bytes to read back as the records that produced them (each separator exactly
1939
+ * one character, no separator a `CR`/`LF`, no two separators the same character) and a
1940
+ * set failing any of them is a typed {@link AstmSerializeError}
1941
+ * (`ASTM_EMIT_INVALID_DELIMITERS`) rather than output this library's own parser would
1942
+ * reject or silently re-read with a different field tree. These are **necessary**
1943
+ * conditions, not a guarantee of readback: a set can pass all three and still produce a
1944
+ * stream that reads back wrong, a separator that collides with a record's type letter
1945
+ * is the known case, and it corrupts identically with or without this check.
1814
1946
  */
1815
1947
 
1816
1948
  /**
1817
1949
  * Thrown by the record/frame emit side when a value cannot be serialized into a
1818
- * spec-clean stream — specifically when a component contains a record terminator
1950
+ * spec-clean stream: specifically when a component contains a record terminator
1819
1951
  * (`CR`/`LF`), which the ASTM escape codec cannot encode and which would break
1820
1952
  * framing if emitted raw. Carries a stable code + positional context, never the
1821
1953
  * offending value (PHI discipline).
@@ -1832,27 +1964,53 @@ declare function attachComments(records: readonly AstmRecord[], warnings: AstmRe
1832
1964
  * ```
1833
1965
  */
1834
1966
  declare class AstmSerializeError extends Error {
1835
- /** Stable discriminant; `ASTM_EMIT_UNENCODABLE_VALUE` for a `CR`/`LF` in a value. */
1836
- readonly code: "ASTM_EMIT_UNENCODABLE_VALUE";
1967
+ /**
1968
+ * Stable discriminant. `ASTM_EMIT_UNENCODABLE_VALUE` for a `CR`/`LF` in a value;
1969
+ * `ASTM_EMIT_INVALID_DELIMITERS` for a delimiter set that cannot be emitted
1970
+ * reversibly.
1971
+ */
1972
+ readonly code: AstmSerializeErrorCode;
1837
1973
  /** 0-based ordinal of the record within the message, when known. */
1838
1974
  readonly recordIndex?: number;
1839
1975
  /** @internal */
1840
- constructor(message: string, recordIndex?: number);
1976
+ constructor(message: string, recordIndex?: number, code?: AstmSerializeErrorCode);
1841
1977
  }
1842
1978
  /**
1843
- * Escape-encode one component leaf for spec-clean emit — the inverse of
1979
+ * The reasons emit refuses rather than writing a stream that cannot be read back.
1980
+ *
1981
+ * - `ASTM_EMIT_UNENCODABLE_VALUE`: a component holds a record terminator
1982
+ * (`CR`/`LF`), which the escape codec has no mnemonic for.
1983
+ * - `ASTM_EMIT_INVALID_DELIMITERS`: the delimiter set to emit against failed one
1984
+ * of the three conditions readback requires (see {@link serializeAstmRecords}).
1985
+ * Those conditions are necessary rather than sufficient, so this code means a
1986
+ * set was rejected, not that every unreversible set is.
1987
+ *
1988
+ * @example
1989
+ * ```ts
1990
+ * import type { AstmSerializeErrorCode } from "@cosyte/astm";
1991
+ * const code: AstmSerializeErrorCode = "ASTM_EMIT_INVALID_DELIMITERS";
1992
+ * ```
1993
+ */
1994
+ type AstmSerializeErrorCode = "ASTM_EMIT_UNENCODABLE_VALUE" | "ASTM_EMIT_INVALID_DELIMITERS";
1995
+ /**
1996
+ * Escape-encode one component leaf for spec-clean emit: the inverse of
1844
1997
  * `decodeEscapes`. The **escape character itself is encoded first** (`&` → `&E&`)
1845
1998
  * so a later delimiter substitution can never double-encode the `&` it just
1846
1999
  * introduced; then the field / component / repeat delimiters map to their
1847
2000
  * mnemonics.
1848
2001
  *
1849
2002
  * A `CR`/`LF` in the leaf has no escape mnemonic and would break framing, so it
1850
- * is rejected with an {@link AstmSerializeError} rather than emitted raw.
2003
+ * is rejected with an {@link AstmSerializeError} rather than emitted raw, as is a
2004
+ * delimiter set the result could not be read back with.
1851
2005
  *
1852
2006
  * @param leaf - One already-decoded component string.
1853
2007
  * @param d - The delimiters to emit against (canonical for spec-clean output).
1854
2008
  * @param recordIndex - The enclosing record's index, for error context.
1855
2009
  * @returns The escaped component text.
2010
+ * @throws {@link AstmSerializeError} for a `CR`/`LF` in the leaf
2011
+ * (`ASTM_EMIT_UNENCODABLE_VALUE`), or for a delimiter set failing one of the
2012
+ * three conditions readback requires (`ASTM_EMIT_INVALID_DELIMITERS`), which
2013
+ * are necessary, not sufficient, so a set can pass them and still not reverse.
1856
2014
  * @example
1857
2015
  * ```ts
1858
2016
  * import { encodeComponent, CANONICAL_DELIMITERS } from "@cosyte/astm";
@@ -1865,15 +2023,20 @@ declare function encodeComponent(leaf: string, d: Delimiters, recordIndex?: numb
1865
2023
  * terminator). Emits with the given delimiters, defaulting to the canonical set.
1866
2024
  *
1867
2025
  * The header (`H`) is special-cased: its delimiter-definition field is emitted as
1868
- * the **literal** canonical declaration (`\^&`), never escaped — escaping it would
1869
- * corrupt the very declaration a reader depends on. Manufacturer (`M`) and
1870
- * scientific (`S`) records are surfaced **byte-identically** from their preserved
1871
- * `rawLine` — verbatim in, verbatim out.
2026
+ * the **literal** declaration of `d`, never escaped, escaping it would corrupt the
2027
+ * very declaration a reader depends on, followed by any characters the modeled
2028
+ * declaration carried beyond the three a reader takes its roles from. Manufacturer
2029
+ * (`M`) and scientific (`S`) records are reproduced **byte-identically** from their
2030
+ * preserved `rawLine` when they are already in `d`, and re-encoded from their fields
2031
+ * when they are not, so their fields never collapse into one on the next read.
1872
2032
  *
1873
2033
  * @param record - The record to serialize.
1874
2034
  * @param d - The delimiters to emit against; defaults to `H|\^&`.
1875
2035
  * @returns The record's wire text, terminator excluded.
1876
- * @throws {@link AstmSerializeError} when a component contains an unencodable `CR`/`LF`.
2036
+ * @throws {@link AstmSerializeError} when a component contains an unencodable `CR`/`LF`
2037
+ * (`ASTM_EMIT_UNENCODABLE_VALUE`), or when `d` fails one of the three conditions
2038
+ * readback requires (`ASTM_EMIT_INVALID_DELIMITERS`): necessary conditions, not
2039
+ * sufficient ones, so a set can pass them and still not reverse.
1877
2040
  * @example
1878
2041
  * ```ts
1879
2042
  * import { serializeAstmRecord, parseAstmRecords } from "@cosyte/astm";
@@ -1884,16 +2047,36 @@ declare function encodeComponent(leaf: string, d: Delimiters, recordIndex?: numb
1884
2047
  declare function serializeAstmRecord(record: AstmRecord, d?: Delimiters): string;
1885
2048
  /**
1886
2049
  * Serialize a whole ASTM message (or a bare record list) to a spec-clean,
1887
- * `CR`-terminated record stream — the inverse of `parseAstmRecords`.
2050
+ * `CR`-terminated record stream: the inverse of `parseAstmRecords`.
1888
2051
  *
1889
2052
  * Emit is **conservative**: the canonical `H|\^&` delimiters, every embedded
1890
2053
  * delimiter re-escaped, each record closed with a `CR`. A message parsed with
1891
- * non-canonical delimiters is **normalized** to the canonical set on emit (a
1892
- * documented Phase-7 behavior — vendor-delimiter round-tripping is Phase 8).
2054
+ * non-canonical delimiters is **normalized** to the canonical set on emit: every
2055
+ * record, `M` and `S` included, so the emitted stream is in one delimiter set and
2056
+ * re-parsing it recovers every field. Passing `d` explicitly emits against that set
2057
+ * instead, and the header declares it. Normalization replaces the four delimiter
2058
+ * **roles**; a header declaration carrying characters beyond the three that hold a
2059
+ * role keeps them, on this path as on any other, rather than being truncated.
2060
+ *
2061
+ * **`d` is checked before anything is written.** Each of the four separators must be
2062
+ * exactly one character, none may be a record terminator, and no two may share a
2063
+ * character: otherwise the emitted bytes cannot be read back as the records that
2064
+ * produced them, and emit has no warning channel with which to say so. A set that
2065
+ * fails is an {@link AstmSerializeError} with code `ASTM_EMIT_INVALID_DELIMITERS`.
2066
+ * This is stricter than the parser, which reads some sets it cannot reverse, so
2067
+ * `serializeAstmRecords(msg, msg.delimiters)` can refuse a message that parsed:
2068
+ * in exactly the cases where it used to emit a stream that read back wrong. The
2069
+ * three conditions are necessary rather than sufficient: a set that passes them can
2070
+ * still read back wrong if a separator collides with a record's type letter, which
2071
+ * this check does not claim to catch.
1893
2072
  *
1894
2073
  * @param input - A parsed {@link AstmMessage} or a list of {@link AstmRecord}s.
2074
+ * @param d - The delimiters to emit against; defaults to the canonical `H|\^&` set.
1895
2075
  * @returns The serialized record stream (`CR` after every record).
1896
- * @throws {@link AstmSerializeError} when a component contains an unencodable `CR`/`LF`.
2076
+ * @throws {@link AstmSerializeError} when a component contains an unencodable `CR`/`LF`
2077
+ * (`ASTM_EMIT_UNENCODABLE_VALUE`), or when `d` fails one of the three conditions
2078
+ * readback requires (`ASTM_EMIT_INVALID_DELIMITERS`): necessary conditions, not
2079
+ * sufficient ones, so a set can pass them and still not reverse.
1897
2080
  * @example
1898
2081
  * ```ts
1899
2082
  * import { parseAstmRecords, serializeAstmRecords } from "@cosyte/astm";
@@ -1910,6 +2093,10 @@ declare function serializeAstmRecords(input: AstmMessage | readonly AstmRecord[]
1910
2093
  * @param field - The field to serialize.
1911
2094
  * @param d - The delimiters to emit against; defaults to `H|\^&`.
1912
2095
  * @returns The escaped field text.
2096
+ * @throws {@link AstmSerializeError} when a component contains an unencodable `CR`/`LF`
2097
+ * (`ASTM_EMIT_UNENCODABLE_VALUE`), or when `d` fails one of the three conditions
2098
+ * readback requires (`ASTM_EMIT_INVALID_DELIMITERS`): necessary conditions, not
2099
+ * sufficient ones, so a set can pass them and still not reverse.
1913
2100
  * @example
1914
2101
  * ```ts
1915
2102
  * import { serializeField, tokenizeRecord, CANONICAL_DELIMITERS } from "@cosyte/astm";
@@ -1920,20 +2107,20 @@ declare function serializeAstmRecords(input: AstmMessage | readonly AstmRecord[]
1920
2107
  declare function serializeField(field: AstmField, d?: Delimiters): string;
1921
2108
 
1922
2109
  /**
1923
- * The record-layer **builder**: {@link buildAstmMessage} — Phase 7.
2110
+ * The record-layer **builder**: {@link buildAstmMessage}.
1924
2111
  *
1925
2112
  * A builder produces spec-clean E1394 records **by construction** from typed
1926
2113
  * caller input, under two disciplines:
1927
2114
  *
1928
2115
  * - **Never fabricate.** A builder emits **only** the values the caller supplied.
1929
2116
  * An unsupplied field is left **empty**, never filled with a default clinical
1930
- * value — no defaulted result status, abnormal flag, units, value, or patient
2117
+ * value: no defaulted result status, abnormal flag, units, value, or patient
1931
2118
  * identifier. (Leaving a field empty is not a guess; inventing an `F` status or
1932
2119
  * an `N` flag would be.) A consumer that parses a built result whose status the
1933
2120
  * caller omitted reads `status.meaning === "unspecified"`, never `"final"`.
1934
2121
  * - **Compute, don't guess, the structure.** The record type letters, the
1935
2122
  * delimiter declaration (canonical `H|\^&`), the message terminator (`L`), and
1936
- * the per-record sequence counters are **structural** — computed by the builder,
2123
+ * the per-record sequence counters are **structural**: computed by the builder,
1937
2124
  * not clinical values. The caller may override any sequence number.
1938
2125
  *
1939
2126
  * Every value is escape-encoded on emit (via the serializer), so an embedded
@@ -1946,98 +2133,98 @@ interface PatientNameInput {
1946
2133
  readonly first?: string;
1947
2134
  readonly middle?: string;
1948
2135
  }
1949
- /** Input for a `P` (patient) record. The three IDs stay distinct — none defaults from another. */
2136
+ /** Input for a `P` (patient) record. The three IDs stay distinct: none defaults from another. */
1950
2137
  interface PatientInput {
1951
2138
  readonly type: "P";
1952
2139
  /** Structural sequence number; auto-computed when omitted. */
1953
2140
  readonly seq?: string;
1954
- /** Field 3 — practice-assigned patient ID. */
2141
+ /** Field 3: practice-assigned patient ID. */
1955
2142
  readonly practiceAssignedId?: string;
1956
- /** Field 4 — laboratory-assigned patient ID. */
2143
+ /** Field 4: laboratory-assigned patient ID. */
1957
2144
  readonly laboratoryAssignedId?: string;
1958
- /** Field 5 — a third patient identifier. */
2145
+ /** Field 5: a third patient identifier. */
1959
2146
  readonly patientIdThree?: string;
1960
- /** Field 6 — patient name (`Last^First^Middle`). */
2147
+ /** Field 6: patient name (`Last^First^Middle`). */
1961
2148
  readonly name?: PatientNameInput;
1962
- /** Field 7 — mother's maiden name. */
2149
+ /** Field 7: mother's maiden name. */
1963
2150
  readonly mothersMaidenName?: string;
1964
- /** Field 8 — birthdate (`YYYYMMDDHHMMSS`), emitted verbatim, never reformatted. */
2151
+ /** Field 8: birthdate (`YYYYMMDDHHMMSS`), emitted verbatim, never reformatted. */
1965
2152
  readonly birthDate?: string;
1966
- /** Field 9 — sex, emitted verbatim (never defaulted). */
2153
+ /** Field 9: sex, emitted verbatim (never defaulted). */
1967
2154
  readonly sex?: string;
1968
2155
  }
1969
2156
  /** Input for an `O` (order) record. */
1970
2157
  interface OrderInput {
1971
2158
  readonly type: "O";
1972
2159
  readonly seq?: string;
1973
- /** Field 3 — specimen / accession ID. */
2160
+ /** Field 3: specimen / accession ID. */
1974
2161
  readonly specimenId?: string;
1975
- /** Field 4 — instrument specimen ID. */
2162
+ /** Field 4: instrument specimen ID. */
1976
2163
  readonly instrumentSpecimenId?: string;
1977
- /** Field 5 — Universal Test ID components (verbatim, in order; e.g. `["", "", "", "687"]`). */
2164
+ /** Field 5: Universal Test ID components (verbatim, in order; e.g. `["", "", "", "687"]`). */
1978
2165
  readonly universalTestId?: readonly string[];
1979
- /** Field 6 — priority, emitted verbatim. */
2166
+ /** Field 6: priority, emitted verbatim. */
1980
2167
  readonly priority?: string;
1981
- /** Field 12 — action code, emitted verbatim. */
2168
+ /** Field 12: action code, emitted verbatim. */
1982
2169
  readonly actionCode?: string;
1983
- /** Field 26 — report type, emitted verbatim. */
2170
+ /** Field 26: report type, emitted verbatim. */
1984
2171
  readonly reportType?: string;
1985
2172
  }
1986
2173
  /** Input for an `R` (result) record. No clinical field is defaulted; unsupplied ⇒ empty. */
1987
2174
  interface ResultInput {
1988
2175
  readonly type: "R";
1989
2176
  readonly seq?: string;
1990
- /** Field 3 — Universal Test ID components (verbatim). */
2177
+ /** Field 3: Universal Test ID components (verbatim). */
1991
2178
  readonly universalTestId?: readonly string[];
1992
- /** Field 4 — the measured value, emitted verbatim (never defaulted). */
2179
+ /** Field 4: the measured value, emitted verbatim (never defaulted). */
1993
2180
  readonly value?: string;
1994
- /** Field 5 — units (vendor free text; never defaulted, guessed, or converted). */
2181
+ /** Field 5: units (vendor free text; never defaulted, guessed, or converted). */
1995
2182
  readonly units?: string;
1996
- /** Field 6 — reference range, emitted verbatim. */
2183
+ /** Field 6: reference range, emitted verbatim. */
1997
2184
  readonly referenceRange?: string;
1998
- /** Field 7 — abnormal flags, emitted verbatim (never defaulted to `N`). */
2185
+ /** Field 7: abnormal flags, emitted verbatim (never defaulted to `N`). */
1999
2186
  readonly abnormalFlags?: string;
2000
- /** Field 9 — result status, emitted verbatim (never defaulted to `F`). */
2187
+ /** Field 9: result status, emitted verbatim (never defaulted to `F`). */
2001
2188
  readonly resultStatus?: string;
2002
- /** Field 11 — operator. */
2189
+ /** Field 11: operator. */
2003
2190
  readonly operator?: string;
2004
- /** Field 12 — test-started timestamp (`YYYYMMDDHHMMSS`), verbatim. */
2191
+ /** Field 12: test-started timestamp (`YYYYMMDDHHMMSS`), verbatim. */
2005
2192
  readonly startedAt?: string;
2006
- /** Field 13 — test-completed timestamp, verbatim. */
2193
+ /** Field 13: test-completed timestamp, verbatim. */
2007
2194
  readonly completedAt?: string;
2008
- /** Field 14 — instrument identifier. */
2195
+ /** Field 14: instrument identifier. */
2009
2196
  readonly instrument?: string;
2010
2197
  }
2011
2198
  /** Input for a `C` (comment) record. */
2012
2199
  interface CommentInput {
2013
2200
  readonly type: "C";
2014
2201
  readonly seq?: string;
2015
- /** Field 3 — comment source. */
2202
+ /** Field 3: comment source. */
2016
2203
  readonly source?: string;
2017
- /** Field 4 — comment text (a single component). Use {@link CommentInput.textComponents} for a structured comment. */
2204
+ /** Field 4: comment text (a single component). Use {@link CommentInput.textComponents} for a structured comment. */
2018
2205
  readonly text?: string;
2019
- /** Field 4 — comment text as explicit components (takes precedence over `text` when set). */
2206
+ /** Field 4: comment text as explicit components (takes precedence over `text` when set). */
2020
2207
  readonly textComponents?: readonly string[];
2021
- /** Field 5 — comment type code, emitted verbatim. */
2208
+ /** Field 5: comment type code, emitted verbatim. */
2022
2209
  readonly commentType?: string;
2023
2210
  }
2024
2211
  /** Input for a `Q` (request-information) record. */
2025
2212
  interface QueryInput {
2026
2213
  readonly type: "Q";
2027
2214
  readonly seq?: string;
2028
- /** Field 3 — starting range ID, emitted verbatim. */
2215
+ /** Field 3: starting range ID, emitted verbatim. */
2029
2216
  readonly startingRangeId?: string;
2030
- /** Field 4 — ending range ID, emitted verbatim. */
2217
+ /** Field 4: ending range ID, emitted verbatim. */
2031
2218
  readonly endingRangeId?: string;
2032
- /** Field 5 — Universal Test ID components; ignored when {@link QueryInput.queriesAllTests} is set. */
2219
+ /** Field 5: Universal Test ID components; ignored when {@link QueryInput.queriesAllTests} is set. */
2033
2220
  readonly universalTestId?: readonly string[];
2034
- /** Field 5 — emit the literal `ALL` universal-query keyword instead of a Universal Test ID. */
2221
+ /** Field 5: emit the literal `ALL` universal-query keyword instead of a Universal Test ID. */
2035
2222
  readonly queriesAllTests?: boolean;
2036
- /** Field 13 — request-information status, emitted verbatim. */
2223
+ /** Field 13: request-information status, emitted verbatim. */
2037
2224
  readonly requestInformationStatus?: string;
2038
2225
  }
2039
2226
  /**
2040
- * Input for an `M` (manufacturer) or `S` (scientific) record — vendor-defined
2227
+ * Input for an `M` (manufacturer) or `S` (scientific) record: vendor-defined
2041
2228
  * free-form data. Emitted verbatim from the caller's fields; never interpreted.
2042
2229
  */
2043
2230
  interface VerbatimInput {
@@ -2059,7 +2246,7 @@ interface MessageInput {
2059
2246
  /** The body records, in order. `H` and `L` are supplied by the builder. */
2060
2247
  readonly records: readonly AstmRecordInput[];
2061
2248
  /**
2062
- * Field 3 of the auto-appended `L` (terminator) record — the termination code
2249
+ * Field 3 of the auto-appended `L` (terminator) record: the termination code
2063
2250
  * (e.g. `N` normal). Omitted by default (not defaulted to a value the caller
2064
2251
  * did not choose); the `L` seq is always emitted.
2065
2252
  */
@@ -2071,7 +2258,7 @@ interface MessageInput {
2071
2258
  * The builder emits the canonical `H|\^&` header (plus any header fields), then
2072
2259
  * every body record with each supplied field at its correct 1-based position and
2073
2260
  * every value escape-encoded, then an `L` terminator. **Nothing clinical is
2074
- * defaulted** — an unsupplied field is empty, never a guessed value — and the
2261
+ * defaulted** (an unsupplied field is empty, never a guessed value) and the
2075
2262
  * structural pieces (delimiters, record types, sequence counters, terminator) are
2076
2263
  * computed. The result parses back to an equal message: build → parse fidelity by
2077
2264
  * construction.
@@ -2091,20 +2278,32 @@ interface MessageInput {
2091
2278
  declare function buildAstmMessage(input: MessageInput): string;
2092
2279
 
2093
2280
  /**
2094
- * Typed extractors over an {@link AstmMessage} — the one-line "pull the value out"
2281
+ * Typed extractors over an {@link AstmMessage}: the one-line "pull the value out"
2095
2282
  * surface that is the package's north star.
2096
2283
  *
2097
- * They are thin, total, and immutable: they read the already-parsed model and
2098
- * never re-parse, never mutate, and never fabricate. `results()` returns every
2099
- * `R` record in order; `patient()` returns the first `P` record (the identity a
2100
- * result files against), or `undefined` when the message carries none.
2284
+ * They are thin and immutable: they read the already-parsed model and never re-parse,
2285
+ * never mutate, and never fabricate.
2286
+ *
2287
+ * **They are scoped to a single message, and they enforce it.** An `AstmMessage` models a
2288
+ * whole record **stream**, which may carry several `H` … `L` messages back to back. These
2289
+ * extractors read across the whole stream, so on a multi-message stream `patient()` used to
2290
+ * answer with the first `P` in the stream while `results()` answered with every `R` in it:
2291
+ * pairing the two attributed one patient's results to another, silently and with no warning.
2292
+ * They now **refuse** that stream: an {@link AstmAmbiguousStreamError} instead of a confident
2293
+ * wrong answer, which is the same rule the rest of this parser follows. {@link messages} is
2294
+ * the path that works on any stream, and `commentsFor()` keeps working on every stream
2295
+ * because its parent record already names the message.
2296
+ *
2297
+ * @see {@link messages}
2101
2298
  */
2102
2299
 
2103
2300
  /**
2104
2301
  * Every result (`R`) record in the message, in wire order.
2105
2302
  *
2106
- * @param msg - A parsed message.
2303
+ * @param msg - A parsed **single-message** stream.
2107
2304
  * @returns The result records (possibly empty).
2305
+ * @throws {@link AstmAmbiguousStreamError} (`ASTM_AMBIGUOUS_MULTI_MESSAGE`) when the stream
2306
+ * carries more than one message: see {@link messages}.
2108
2307
  * @example
2109
2308
  * ```ts
2110
2309
  * import { parseAstmRecords, results } from "@cosyte/astm";
@@ -2114,13 +2313,18 @@ declare function buildAstmMessage(input: MessageInput): string;
2114
2313
  */
2115
2314
  declare function results(msg: AstmMessage): readonly ResultRecord[];
2116
2315
  /**
2117
- * The first patient (`P`) record, or `undefined` when the message has none.
2316
+ * The message's patient (`P`) record, or `undefined` when the message carries none.
2118
2317
  *
2119
- * A message can carry several `P` records (multiple patients in a batch); this
2120
- * returns the first. Consumers needing all of them can filter `msg.records`.
2318
+ * This is the identity a result files against, so it is answered only when the stream
2319
+ * determines exactly one. It **refuses** the two shapes where "the patient" is a guess: a
2320
+ * stream carrying several messages, and a single message carrying several `P` records. In
2321
+ * both, the old behaviour, the first `P` in the stream, is the wrong-patient path itself.
2121
2322
  *
2122
- * @param msg - A parsed message.
2123
- * @returns The first patient record, or `undefined`.
2323
+ * @param msg - A parsed single-message stream carrying at most one patient.
2324
+ * @returns The patient record, or `undefined` when the message carries none.
2325
+ * @throws {@link AstmAmbiguousStreamError} (`ASTM_AMBIGUOUS_MULTI_MESSAGE`) when the stream
2326
+ * carries more than one message, or (`ASTM_AMBIGUOUS_MULTI_PATIENT`) when its one message
2327
+ * carries more than one `P`. Use {@link messages} and read `patients` in both cases.
2124
2328
  * @example
2125
2329
  * ```ts
2126
2330
  * import { parseAstmRecords, patient } from "@cosyte/astm";
@@ -2132,8 +2336,10 @@ declare function patient(msg: AstmMessage): PatientRecord | undefined;
2132
2336
  /**
2133
2337
  * Every order (`O`) record in the message, in wire order.
2134
2338
  *
2135
- * @param msg - A parsed message.
2339
+ * @param msg - A parsed **single-message** stream.
2136
2340
  * @returns The order records (possibly empty).
2341
+ * @throws {@link AstmAmbiguousStreamError} (`ASTM_AMBIGUOUS_MULTI_MESSAGE`) when the stream
2342
+ * carries more than one message: see {@link messages}.
2137
2343
  * @example
2138
2344
  * ```ts
2139
2345
  * import { parseAstmRecords, orders } from "@cosyte/astm";
@@ -2145,10 +2351,12 @@ declare function orders(msg: AstmMessage): readonly OrderRecord[];
2145
2351
  /**
2146
2352
  * Every comment (`C`) record in the message, in wire order. Each carries the
2147
2353
  * `parentIndex` of the `H`/`P`/`O`/`R` it attaches to (or `attachedToRoot` when
2148
- * it is an orphan) — use {@link commentsFor} to get the comments of one record.
2354
+ * it is an orphan): use {@link commentsFor} to get the comments of one record.
2149
2355
  *
2150
- * @param msg - A parsed message.
2356
+ * @param msg - A parsed **single-message** stream.
2151
2357
  * @returns The comment records (possibly empty).
2358
+ * @throws {@link AstmAmbiguousStreamError} (`ASTM_AMBIGUOUS_MULTI_MESSAGE`) when the stream
2359
+ * carries more than one message: see {@link messages}.
2152
2360
  * @example
2153
2361
  * ```ts
2154
2362
  * import { parseAstmRecords, comments } from "@cosyte/astm";
@@ -2159,8 +2367,11 @@ declare function orders(msg: AstmMessage): readonly OrderRecord[];
2159
2367
  declare function comments(msg: AstmMessage): readonly CommentRecord[];
2160
2368
  /**
2161
2369
  * The comment (`C`) records attached to a given parent record, in wire order.
2370
+ *
2371
+ * Unlike the other extractors this works on **any** stream, single- or multi-message: the
2372
+ * parent record it is handed already names the message, so there is nothing to disambiguate.
2162
2373
  * Returns the comments whose `parentIndex` is that record's `recordIndex`, so a
2163
- * comment carrying (e.g.) QC context is read against the record it modifies —
2374
+ * comment carrying (e.g.) QC context is read against the record it modifies:
2164
2375
  * never floated to the wrong one.
2165
2376
  *
2166
2377
  * @param msg - A parsed message.
@@ -2176,11 +2387,13 @@ declare function comments(msg: AstmMessage): readonly CommentRecord[];
2176
2387
  declare function commentsFor(msg: AstmMessage, parent: AstmRecord): readonly CommentRecord[];
2177
2388
  /**
2178
2389
  * Every request-information (`Q`) record in the message, in wire order. A non-empty
2179
- * result means the message is a **host-query request**, not a result set — see
2390
+ * result means the message is a **host-query request**, not a result set: see
2180
2391
  * {@link AstmMessage.classification} (`isHostQueryRequest`).
2181
2392
  *
2182
- * @param msg - A parsed message.
2393
+ * @param msg - A parsed **single-message** stream.
2183
2394
  * @returns The query records (possibly empty).
2395
+ * @throws {@link AstmAmbiguousStreamError} (`ASTM_AMBIGUOUS_MULTI_MESSAGE`) when the stream
2396
+ * carries more than one message: see {@link messages}.
2184
2397
  * @example
2185
2398
  * ```ts
2186
2399
  * import { parseAstmRecords, query } from "@cosyte/astm";
@@ -2190,9 +2403,195 @@ declare function commentsFor(msg: AstmMessage, parent: AstmRecord): readonly Com
2190
2403
  */
2191
2404
  declare function query(msg: AstmMessage): readonly QueryRecord[];
2192
2405
 
2406
+ /**
2407
+ * Message grouping: the safe replacement for the stream-scoped extractors.
2408
+ *
2409
+ * A parsed {@link AstmMessage} models a whole **record stream**, and a stream may carry
2410
+ * several messages back to back: a message runs from an `H` header to its `L` terminator.
2411
+ * The flat extractors in `./extractors.js` read the whole stream, so on a multi-message
2412
+ * stream `patient(msg)` answered with the first `P` in the stream while `results(msg)`
2413
+ * answered with every `R` in it: pairing the two, which is exactly what the package's
2414
+ * one-line north star does, attributed one patient's results to another. It needed no
2415
+ * redeclaration and no unusual delimiters, and it warned about nothing.
2416
+ *
2417
+ * {@link messages} is the fix: it splits the stream into the messages it actually
2418
+ * contains, and each one carries only its own records, so a patient and a result are only
2419
+ * ever paired inside the message that carried both.
2420
+ *
2421
+ * **Where a message begins.** A new message opens at every `H` record and runs to the
2422
+ * record before the next `H` (or the end of the stream). That is deliberately the *same*
2423
+ * boundary the parser already uses to scope delimiters: the active delimiter set is
2424
+ * re-read at each `H` and governs that header and the records that follow it, so message
2425
+ * boundaries and delimiter scope cannot disagree. The `L` terminator **closes** a message
2426
+ * but does not open a scope, so anything between an `L` and the next `H` stays with the
2427
+ * message its header opened rather than being dropped. Grouping is therefore **total**:
2428
+ * every record of the stream lands in exactly one message, none duplicated, none lost.
2429
+ *
2430
+ * **What the standard grounds, and what it does not.** CLSI LIS02-A2 §2 is definitional
2431
+ * about the unit itself: a message is bounded by the `H` record at one end and the `L`
2432
+ * record at the other. That clause is in CLSI's free sample and is the whole of what is
2433
+ * claimed from the standard here. It does **not** settle which record *opens* a new scope
2434
+ * partway through a stream, so treating a second `H` as the start of the next message
2435
+ * (rather than, say, requiring an intervening `L`) is a reasoned choice, not a citation. It
2436
+ * is made this way because it is the boundary this parser already enforces for delimiters,
2437
+ * and because it never drops a record.
2438
+ *
2439
+ * **What is deliberately not modeled.** ASTM's within-message record hierarchy, which `P`
2440
+ * a given `R` files against when one message carries several patients, is not modeled
2441
+ * here. The clauses that would ground it (the message-level structure diagram, and the `P`
2442
+ * sequence-number rule) are withheld from the free sample and are paywalled, so rather than
2443
+ * guess a scoping rule this layer refuses to answer: a message carrying more than one `P`
2444
+ * leaves {@link AstmStreamMessage.patient} `undefined` and surfaces all of them on
2445
+ * {@link AstmStreamMessage.patients}. Multi-patient messages are real, at least one
2446
+ * openly-published vendor interface grammar makes the patient group repeatable on the
2447
+ * download direction, so this is a deferral with a known shape, not a claim that the case
2448
+ * does not arise, and not a claim that the standard is silent about it.
2449
+ *
2450
+ * **The OSS corpus was checked and offers no prior art.** It is informative here, unlike
2451
+ * for delimiter questions, but only negatively: the two commonly-cited reference
2452
+ * implementations both dissolve the message unit. One dispatches records to per-type
2453
+ * callbacks one at a time with no carried state, leaving the grouping to the integrator;
2454
+ * the other merges every message of a transport session into one envelope of flat
2455
+ * per-record-type buckets, from which the association cannot be recovered at all. A flat
2456
+ * model is the incumbent shape, and the misattribution above is what it costs.
2457
+ */
2458
+
2459
+ /**
2460
+ * Stable codes for the two ways a flat, stream-scoped accessor can fail to name a single
2461
+ * answer. Renaming a code is a **breaking change**.
2462
+ *
2463
+ * @example
2464
+ * ```ts
2465
+ * import { parseAstmRecords, patient, AMBIGUOUS_CODES, AstmAmbiguousStreamError } from "@cosyte/astm";
2466
+ * try {
2467
+ * patient(parseAstmRecords(raw));
2468
+ * } catch (err) {
2469
+ * if (err instanceof AstmAmbiguousStreamError) {
2470
+ * err.code === AMBIGUOUS_CODES.ASTM_AMBIGUOUS_MULTI_MESSAGE;
2471
+ * }
2472
+ * }
2473
+ * ```
2474
+ */
2475
+ declare const AMBIGUOUS_CODES: {
2476
+ /** The stream carries more than one `H` … `L` message, so a stream-wide answer spans patients. */
2477
+ readonly ASTM_AMBIGUOUS_MULTI_MESSAGE: "ASTM_AMBIGUOUS_MULTI_MESSAGE";
2478
+ /** The one message in the stream carries more than one `P`, so "the patient" is not determined. */
2479
+ readonly ASTM_AMBIGUOUS_MULTI_PATIENT: "ASTM_AMBIGUOUS_MULTI_PATIENT";
2480
+ };
2481
+ /** A value from {@link AMBIGUOUS_CODES}: the discriminant on {@link AstmAmbiguousStreamError}. */
2482
+ type AmbiguousCode = (typeof AMBIGUOUS_CODES)[keyof typeof AMBIGUOUS_CODES];
2483
+ /**
2484
+ * Thrown by a flat extractor when the stream it was handed does not determine a single
2485
+ * answer: either because it carries several messages, or because its one message carries
2486
+ * several patients.
2487
+ *
2488
+ * The throw **is** the fix. A caller who gets this error is strictly better off than one
2489
+ * who silently received another patient's results, and the remedy is mechanical: walk
2490
+ * {@link messages} and read each message's own records.
2491
+ *
2492
+ * **PHI:** carries a stable code, a position, and two counts. Never a field value, never a
2493
+ * patient identifier.
2494
+ *
2495
+ * @example
2496
+ * ```ts
2497
+ * import { parseAstmRecords, messages, results, AstmAmbiguousStreamError } from "@cosyte/astm";
2498
+ * const msg = parseAstmRecords(raw);
2499
+ * try {
2500
+ * results(msg);
2501
+ * } catch (err) {
2502
+ * if (err instanceof AstmAmbiguousStreamError) {
2503
+ * for (const m of messages(msg)) m.results.length;
2504
+ * }
2505
+ * }
2506
+ * ```
2507
+ */
2508
+ declare class AstmAmbiguousStreamError extends Error {
2509
+ /** The stable discriminant: see {@link AMBIGUOUS_CODES}. */
2510
+ readonly code: AmbiguousCode;
2511
+ /** Where the ambiguity became visible: the second header, or the second `P`. Value-free. */
2512
+ readonly position: AstmPosition;
2513
+ /** How many `H` … `L` messages the stream carries. */
2514
+ readonly messageCount: number;
2515
+ /** How many `P` records the message in question carries. */
2516
+ readonly patientCount: number;
2517
+ /** @internal */
2518
+ constructor(code: AmbiguousCode, message: string, position: AstmPosition, counts: {
2519
+ readonly messageCount: number;
2520
+ readonly patientCount: number;
2521
+ });
2522
+ }
2523
+ /**
2524
+ * One `H` … `L` message inside a parsed stream, with its own records and nothing else.
2525
+ *
2526
+ * This is the unit the flat extractors were always assumed to be reading. `AstmMessage`
2527
+ * models the whole stream; this models a message within it.
2528
+ */
2529
+ interface AstmStreamMessage {
2530
+ /** 0-based ordinal of this message within the stream. */
2531
+ readonly index: number;
2532
+ /** This message's header record. */
2533
+ readonly header: HeaderRecord;
2534
+ /**
2535
+ * The delimiter set in force for this message: the header's own resolved set, which is
2536
+ * the set its records were actually read with. When a later header's declaration was
2537
+ * unusable, this is the set that stayed in force, never a guessed one.
2538
+ */
2539
+ readonly delimiters: Delimiters;
2540
+ /** Every record of this message in wire order, the header first. */
2541
+ readonly records: readonly AstmRecord[];
2542
+ /**
2543
+ * This message's patient, but **only when the message determines one**: the single `P`
2544
+ * when it carries exactly one, and `undefined` when it carries none **or** several.
2545
+ * `patients.length` distinguishes those two cases; there is no third meaning.
2546
+ *
2547
+ * A message carrying several patients is not answered with the first one, because
2548
+ * "the first `P`" is precisely the guess that files a result against the wrong person.
2549
+ */
2550
+ readonly patient: PatientRecord | undefined;
2551
+ /** Every `P` record in this message, in wire order (usually zero or one). */
2552
+ readonly patients: readonly PatientRecord[];
2553
+ /** Every `R` (result) record in this message, in wire order. */
2554
+ readonly results: readonly ResultRecord[];
2555
+ /** Every `O` (order) record in this message, in wire order. */
2556
+ readonly orders: readonly OrderRecord[];
2557
+ /** Every `C` (comment) record in this message, in wire order. */
2558
+ readonly comments: readonly CommentRecord[];
2559
+ /**
2560
+ * Every `Q` (request-information) record in this message, in wire order.
2561
+ *
2562
+ * For this message's host-query classification, call `classifyMessage(m.records)`. The
2563
+ * `classification` on the parsed model is folded over the **whole stream**, so it is not
2564
+ * per-message and is not mirrored here; deriving it from a message's own records is.
2565
+ */
2566
+ readonly queries: readonly QueryRecord[];
2567
+ }
2568
+ /**
2569
+ * Split a parsed stream into the `H` … `L` messages it carries.
2570
+ *
2571
+ * This is the safe path and it never throws: a single-message stream yields exactly one
2572
+ * entry, and pairing a patient with a result inside one entry is correct by construction.
2573
+ *
2574
+ * Returns an **array**, not an iterator: the stream is already fully parsed and frozen, so
2575
+ * there is no work to defer, and every other extractor in this package returns a
2576
+ * `readonly` array.
2577
+ *
2578
+ * @param msg - A parsed stream.
2579
+ * @returns The messages, in wire order; never empty for a parsed stream.
2580
+ * @example
2581
+ * ```ts
2582
+ * import { parseAstmRecords, messages } from "@cosyte/astm";
2583
+ * const stream = parseAstmRecords("H|\\^&\rP|1|PRAC-1\rR|1|^^^687|1.0|U/L||N||F\rL|1\r");
2584
+ * for (const m of messages(stream)) {
2585
+ * m.patient?.practiceAssignedId; // "PRAC-1"
2586
+ * m.results[0]?.value; // "1.0"
2587
+ * }
2588
+ * ```
2589
+ */
2590
+ declare function messages(msg: AstmMessage): readonly AstmStreamMessage[];
2591
+
2193
2592
  /**
2194
2593
  * The **host-query flow**: classify a message as a request, a result upload, an
2195
- * order download, or indeterminate — Phase 4.
2594
+ * order download, or indeterminate.
2196
2595
  *
2197
2596
  * On real analyzers the host-query mode is first-class and sometimes mandatory
2198
2597
  * (on the Roche cobas 4800 there is *no* results-sending-only option): the
@@ -2201,14 +2600,14 @@ declare function query(msg: AstmMessage): readonly QueryRecord[];
2201
2600
  * order flow, so this module makes the distinction **explicit**:
2202
2601
  *
2203
2602
  * **The `Q` record dominates.** Any message carrying a `Q` (request-information)
2204
- * record is classified `host-query` — a request — and is **never** treated as a
2603
+ * record is classified `host-query`, a request, and is **never** treated as a
2205
2604
  * result set, even if a result record is also present (an anomaly, which the
2206
2605
  * parser flags separately with `ASTM_RECORD_AMBIGUOUS_MESSAGE_KIND`). This is the
2207
2606
  * whole point: a `Q`-bearing message can never silently read as a result upload.
2208
2607
  */
2209
2608
 
2210
2609
  /**
2211
- * Classify a record stream by the host-query flow. Pure and total — it only reads
2610
+ * Classify a record stream by the host-query flow. Pure and total: it only reads
2212
2611
  * the record type letters, never a field value.
2213
2612
  *
2214
2613
  * `Q` dominates: a message with any `Q` record is a `host-query` request even when
@@ -2254,10 +2653,40 @@ declare function classifyMessage(records: readonly AstmRecord[]): AstmMessageCla
2254
2653
  * ```
2255
2654
  */
2256
2655
  declare function tokenizeRecord(record: string, d: Delimiters, onUnknownEscape?: (fieldIndex: number) => void): AstmField[];
2656
+ /**
2657
+ * Tokenize an `H` (header) record into its fields.
2658
+ *
2659
+ * A header cannot go through {@link tokenizeRecord}: its second field is the
2660
+ * **delimiter declaration**, which carries the escape character *literally*
2661
+ * rather than as an escape sequence. The generic escape-aware split treats that
2662
+ * lone escape character as the opening of an unterminated escape and swallows
2663
+ * the whole remainder of the record into one field, so every data field after
2664
+ * the declaration is lost. This tokenizer instead takes the declaration verbatim
2665
+ * as one opaque field and applies the ordinary escape-aware tokenizer to the
2666
+ * data portion that follows it.
2667
+ *
2668
+ * `fields[0]` is the type-letter field and `fields[1]` is the delimiter
2669
+ * declaration (verbatim, never escape-decoded); the header's ASTM data fields
2670
+ * follow from `fields[2]`.
2671
+ *
2672
+ * @param record - The raw `H` record text (terminator already stripped).
2673
+ * @param d - The delimiters declared by this header.
2674
+ * @param onUnknownEscape - Called with the 0-based whole-record field index for
2675
+ * each unrecognized escape sequence in the data portion.
2676
+ * @returns The header's fields.
2677
+ * @example
2678
+ * ```ts
2679
+ * import { tokenizeHeader, CANONICAL_DELIMITERS } from "@cosyte/astm";
2680
+ * const fields = tokenizeHeader("H|\\^&|||sender", CANONICAL_DELIMITERS);
2681
+ * fields[1].raw; // "\\^&"
2682
+ * fields[4].raw; // "sender"
2683
+ * ```
2684
+ */
2685
+ declare function tokenizeHeader(record: string, d: Delimiters, onUnknownEscape?: (fieldIndex: number) => void): AstmField[];
2257
2686
  /**
2258
2687
  * The primary scalar of a field: its first repeat's first component, decoded.
2259
2688
  * Returns `undefined` for a truly empty field so callers can distinguish
2260
- * "absent" from a value — never defaulting a missing value.
2689
+ * "absent" from a value: never defaulting a missing value.
2261
2690
  *
2262
2691
  * @example
2263
2692
  * ```ts
@@ -2274,9 +2703,13 @@ declare function fieldScalar(field: AstmField | undefined): string | undefined;
2274
2703
  * Tier-3 **fatal** codes mark input the parser cannot recover into a structured
2275
2704
  * `AstmMessage`; anything less severe is a Tier-2 warning (see `./warnings.ts`).
2276
2705
  * `AstmParseError` is thrown directly and consumers narrow via the `code`
2277
- * discriminant. The set is additions-only across phases — the record layer
2278
- * introduces three, one of which (`EMPTY_INPUT`) is shared with every future
2279
- * layer (the frame codec adds its own `ASTM_FRAME_*` fatals later).
2706
+ * discriminant. {@link FatalCode} is **closed**: these three are all of it. The frame
2707
+ * codec reuses `EMPTY_INPUT` (an empty stream is fatal in both its lenient and strict
2708
+ * modes) but does not widen the union, its own thrown errors are separate types with
2709
+ * their own discriminants: {@link AstmFrameEncodeError} carries
2710
+ * `ASTM_FRAME_EMPTY_RECORD`, and {@link AstmFrameStrictError} carries the rejected
2711
+ * warnings rather than a `code`. So narrowing an `AstmParseError` on `code` will only
2712
+ * ever see a value from this set.
2280
2713
  */
2281
2714
 
2282
2715
  /**
@@ -2297,15 +2730,15 @@ declare function fieldScalar(field: AstmField | undefined): string | undefined;
2297
2730
  * ```
2298
2731
  */
2299
2732
  declare const FATAL_CODES: {
2300
- /** Input was empty or whitespace-only — there is nothing to parse. Shared across layers. */
2733
+ /** Input was empty or whitespace-only: there is nothing to parse. Shared across layers. */
2301
2734
  readonly EMPTY_INPUT: "EMPTY_INPUT";
2302
- /** The first record is not an `H` (header) record — an ASTM message must lead with `H`. */
2735
+ /** The first record is not an `H` (header) record: an ASTM message must lead with `H`. */
2303
2736
  readonly ASTM_RECORD_NO_HEADER: "ASTM_RECORD_NO_HEADER";
2304
2737
  /** The `H` record is too short to declare the four delimiters (field/repeat/component/escape). */
2305
2738
  readonly ASTM_RECORD_UNDECLARED_DELIMITERS: "ASTM_RECORD_UNDECLARED_DELIMITERS";
2306
2739
  };
2307
2740
  /**
2308
- * A value from {@link FATAL_CODES} — the type carried by a thrown {@link AstmParseError}.
2741
+ * A value from {@link FATAL_CODES}: the type carried by a thrown {@link AstmParseError}.
2309
2742
  */
2310
2743
  type FatalCode = (typeof FATAL_CODES)[keyof typeof FATAL_CODES];
2311
2744
  /**
@@ -2341,11 +2774,11 @@ declare class AstmParseError extends Error {
2341
2774
  }
2342
2775
 
2343
2776
  /**
2344
- * The ASTM escape codec — the Phase-1 fix for a documented silent-misread class.
2777
+ * The ASTM escape codec: the fix for a documented silent-misread class.
2345
2778
  *
2346
2779
  * ASTM escapes an embedded delimiter with the escape character on both sides of
2347
- * a single mnemonic letter (the InterSystems mnemonics, cross-verified in the
2348
- * roadmap's OSS references):
2780
+ * a single mnemonic letter (the InterSystems mnemonics, cross-verified against
2781
+ * the OSS reference implementations):
2349
2782
  *
2350
2783
  * ```
2351
2784
  * &F& → field delimiter (default "|")
@@ -2355,18 +2788,18 @@ declare class AstmParseError extends Error {
2355
2788
  * ```
2356
2789
  *
2357
2790
  * **Why this is a requirement, not a nicety.** A result value can legitimately
2358
- * contain a component delimiter — e.g. a titre written `1^40`. On the wire that
2791
+ * contain a component delimiter: e.g. a titre written `1^40`. On the wire that
2359
2792
  * is escaped as `1&S&40`. A parser that splits a field into components on the
2360
2793
  * raw component delimiter and *never decodes the escape* is safe here (the
2361
2794
  * escape body `&S&` contains no literal `^`), but a parser that **decodes first
2362
2795
  * and splits second** turns `1&S&40` into `1^40` and then mis-splits it into two
2363
- * components — the silent misread `python-astm` / `Chistousov` exhibit. The
2796
+ * components: the silent misread `python-astm` / `Chistousov` exhibit. The
2364
2797
  * correct order, implemented here, is **escape-aware split, then decode each
2365
2798
  * leaf**: the escape sequence survives the split intact and only then decodes to
2366
2799
  * a single literal, so `1&S&40` reads as exactly one component.
2367
2800
  *
2368
- * Re-escaping (the inverse, for spec-clean emit) belongs to the serialize phase
2369
- * (P7) and is deliberately not implemented here.
2801
+ * Re-escaping (the inverse, for spec-clean emit) lives in the serializer and is
2802
+ * deliberately not implemented here.
2370
2803
  */
2371
2804
 
2372
2805
  /**
@@ -2399,7 +2832,7 @@ declare function decodeEscapes(leaf: string, d: Delimiters, onUnknown?: UnknownE
2399
2832
  /**
2400
2833
  * Split `text` on `delimiter`, treating any escape sequence (`escape …
2401
2834
  * escape`) as an opaque atom so a delimiter that appears *inside* an escape body
2402
- * never causes a split. Returns the raw (still-encoded) segments — decoding is
2835
+ * never causes a split. Returns the raw (still-encoded) segments: decoding is
2403
2836
  * the caller's next step, per the escape-aware-split-then-decode contract.
2404
2837
  *
2405
2838
  * For the four canonical mnemonics this is belt-and-suspenders (their bodies are
@@ -2444,12 +2877,12 @@ declare function splitEscapeAware(text: string, delimiter: string, escape: strin
2444
2877
  declare function deepFreeze<T>(value: T): T;
2445
2878
 
2446
2879
  /**
2447
- * `defineAstmProfile()` — the public factory for building immutable
2880
+ * `defineAstmProfile()`: the public factory for building immutable
2448
2881
  * {@link AstmProfile} objects with the safety rules enforced and `describe()`
2449
2882
  * attached. Mirrors the sibling `@cosyte/hl7` `defineProfile()` / `@cosyte/ccda`
2450
2883
  * `defineCcdaProfile()` shape (name / lineage / `extends`-merge / `describe`) while
2451
- * modelling ASTM quirks — a tolerated warning code with provenance, plus the
2452
- * optional raw-vs-framed transport override — rather than HL7 v2 Z-segments or
2884
+ * modelling ASTM quirks (a tolerated warning code with provenance, plus the
2885
+ * optional raw-vs-framed transport override) rather than HL7 v2 Z-segments or
2453
2886
  * C-CDA template deviations.
2454
2887
  *
2455
2888
  * Zero runtime deps. No `any`; immutability at the return boundary via
@@ -2459,7 +2892,7 @@ declare function deepFreeze<T>(value: T): T;
2459
2892
  /**
2460
2893
  * Build a frozen {@link AstmProfile} from a validated options object. Throws
2461
2894
  * {@link AstmProfileDefinitionError} on a bad name, an unknown option key, an
2462
- * invalid `transport`, or an invalid `tolerate` entry — including the **safety
2895
+ * invalid `transport`, or an invalid `tolerate` entry, including the **safety
2463
2896
  * rule**: a profile may never tolerate a safety-critical warning code (default-deny
2464
2897
  * across all three registries).
2465
2898
  *
@@ -2492,18 +2925,18 @@ declare function defineAstmProfile(opts: DefineAstmProfileOptions): AstmProfile;
2492
2925
  /**
2493
2926
  * The definition-time error for the `@cosyte/astm` vendor-profile subsystem.
2494
2927
  *
2495
- * A profile is validated when it is *defined*, not when it is applied — so a
2928
+ * A profile is validated when it is *defined*, not when it is applied, so a
2496
2929
  * misconfigured profile (a bad name, an unknown option key, a tolerated code that
2497
2930
  * is unknown or **safety-critical**) fails loudly at construction, long before it
2498
2931
  * could quiet a real deviation. This is the class that failure throws. It is a
2499
2932
  * programming error (a bad profile literal), distinct from `AstmParseError` (bad
2500
- * input) — so consumers narrow on it separately.
2933
+ * input), so consumers narrow on it separately.
2501
2934
  */
2502
2935
  /**
2503
2936
  * Thrown by `defineAstmProfile` when a profile definition is invalid: a missing or
2504
2937
  * empty `name`, an unknown option key, an invalid `transport` value, or a
2505
- * `tolerate` entry whose code is unknown, whose `rationale` is empty, or — the
2506
- * load-bearing safety rule — whose code is **safety-critical** (a result value,
2938
+ * `tolerate` entry whose code is unknown, whose `rationale` is empty, or, the
2939
+ * load-bearing safety rule, whose code is **safety-critical** (a result value,
2507
2940
  * abnormal flag, result status, reference range, units, patient/comment context,
2508
2941
  * message-kind, code system, or any frame/LTP integrity warning). Carries the
2509
2942
  * offending profile's `name` when it is known, so a multi-profile build names the
@@ -2543,14 +2976,14 @@ declare class AstmProfileDefinitionError extends Error {
2543
2976
  * option always wins, and `profile: null` opts out of the default for a single call.
2544
2977
  *
2545
2978
  * The single mutable module-scoped `let` is the only such state in the profile
2546
- * subsystem — an intentional, documented trade-off (identical to the siblings).
2979
+ * subsystem: an intentional, documented trade-off (identical to the siblings).
2547
2980
  * Tests that set a default MUST clear it in teardown (`setDefaultAstmProfile(null)`)
2548
2981
  * to avoid cross-test bleed.
2549
2982
  */
2550
2983
 
2551
2984
  /**
2552
2985
  * Look up a built-in profile by name. Returns `undefined` when no built-in has that
2553
- * name (a user-defined profile is not in this registry — pass it directly).
2986
+ * name (a user-defined profile is not in this registry, pass it directly).
2554
2987
  *
2555
2988
  * @param name - The built-in profile name.
2556
2989
  * @returns The profile, or `undefined`.
@@ -2578,7 +3011,7 @@ declare function listAstmProfiles(): readonly string[];
2578
3011
  * clear. An explicit `parseAstmRecords(raw, { profile })` always wins;
2579
3012
  * `{ profile: null }` opts out of the default for a single call.
2580
3013
  *
2581
- * **Test hygiene:** the only mutable module-scoped state here — tests that call this
3014
+ * **Test hygiene:** the only mutable module-scoped state here, tests that call this
2582
3015
  * MUST clear it in teardown or default-profile bleed infects later tests.
2583
3016
  *
2584
3017
  * @param profile - The profile to register as default, or `null` to clear.
@@ -2611,7 +3044,7 @@ declare function getDefaultAstmProfile(): AstmProfile | undefined;
2611
3044
  * plus the transport-override accessor the framing detector consumes.
2612
3045
  *
2613
3046
  * A profile **never touches an extracted value**. The warning transform operates
2614
- * purely at the warning layer — downgrading a deviation it *expects* into a
3047
+ * purely at the warning layer: downgrading a deviation it *expects* into a
2615
3048
  * `PROFILE_QUIRK_APPLIED` warning (flagged `expected`, carrying the original
2616
3049
  * `toleratedCode`) while leaving every un-expected warning untouched. Because the
2617
3050
  * transform only ever rewrites a warning object (never a record, field, or value),
@@ -2643,7 +3076,7 @@ declare function applyAstmProfile(profile: AstmProfile, warning: AstmRecordWarni
2643
3076
  * Apply a profile across a whole warning list, returning a NEW array (the input is
2644
3077
  * never mutated). Returned unchanged (same reference contents, a shallow copy) when
2645
3078
  * `profile` is `undefined`, so the no-profile path pays only a copy. Every tolerated
2646
- * warning is re-badged; every other warning passes through by identity — nothing is
3079
+ * warning is re-badged; every other warning passes through by identity, nothing is
2647
3080
  * dropped, reordered, or reallocated beyond the re-badge.
2648
3081
  *
2649
3082
  * @param warnings - The accumulated record warnings.
@@ -2657,7 +3090,7 @@ declare function applyAstmProfile(profile: AstmProfile, warning: AstmRecordWarni
2657
3090
  */
2658
3091
  declare function applyAstmProfileToWarnings(warnings: readonly AstmRecordWarning[], profile: AstmProfile | undefined): AstmRecordWarning[];
2659
3092
  /**
2660
- * The profile's transport override, if any — the value a consumer feeds to
3093
+ * The profile's transport override, if any: the value a consumer feeds to
2661
3094
  * `detectFraming(bytes, { override })` to force framed/raw and bypass leading-byte
2662
3095
  * auto-detection. `undefined` means "let detection decide."
2663
3096
  *
@@ -2675,47 +3108,47 @@ declare function resolveProfileTransport(profile: AstmProfile | undefined): Astm
2675
3108
  /**
2676
3109
  * The safety spine of the profile subsystem: the set of warning codes a profile is
2677
3110
  * **forbidden** to tolerate. A vendor profile exists to quiet known, benign
2678
- * structural noise — never to hide a deviation that could change a clinical
3111
+ * structural noise: never to hide a deviation that could change a clinical
2679
3112
  * reading or corrupt the wire.
2680
3113
  *
2681
3114
  * **Design: default-deny.** Rather than enumerate the *forbidden* codes (a list you
2682
- * can forget to extend when a new warning ships — the exact incompleteness a
3115
+ * can forget to extend when a new warning ships, the exact incompleteness a
2683
3116
  * safety gate must not have), this module enumerates the tiny set of **explicitly
2684
3117
  * tolerable** codes and treats *every other* code, across all three registries
2685
3118
  * (record `ASTM_RECORD_*`, frame `ASTM_FRAME_*`, protocol `ASTM_LTP_*`), as
2686
- * safety-critical. A warning code added in a future phase is therefore
3119
+ * safety-critical. A warning code added later is therefore
2687
3120
  * safety-critical **by default** until someone deliberately, reviewably adds it to
2688
3121
  * the allow-list below. Widening the allow-list can only ever be an explicit,
2689
3122
  * argued act; forgetting to touch this file can never *weaken* the gate.
2690
3123
  *
2691
3124
  * The four tolerable codes are the only deviations that (a) are structural/
2692
3125
  * syntactic vendor noise and (b) **cannot alter, drop, or fabricate an extracted
2693
- * value** — because a profile only ever re-badges a *warning*, never re-parses. In
3126
+ * value**: because a profile only ever re-badges a *warning*, never re-parses. In
2694
3127
  * every one, the underlying datum is already surfaced verbatim regardless of the
2695
3128
  * profile:
2696
3129
  *
2697
- * - `ASTM_RECORD_UNKNOWN_TYPE` — an unmodeled record type, surfaced verbatim as an
3130
+ * - `ASTM_RECORD_UNKNOWN_TYPE`: an unmodeled record type, surfaced verbatim as an
2698
3131
  * `unsupported` record carrying no interpreted clinical field.
2699
- * - `ASTM_NONSTANDARD_DELIMITERS` — the header's own declared delimiters are read
3132
+ * - `ASTM_NONSTANDARD_DELIMITERS`: the header's own declared delimiters are read
2700
3133
  * and honored either way; this only notes they differ from the canonical set.
2701
- * - `ASTM_UNKNOWN_ESCAPE_SEQUENCE` — an unrecognized `&…&` body is **preserved
3134
+ * - `ASTM_UNKNOWN_ESCAPE_SEQUENCE`: an unrecognized `&…&` body is **preserved
2702
3135
  * byte-for-byte** in the decoded value (the escape codec never guesses); the
2703
3136
  * warning is a purely syntactic advisory, the value is identical with or without
2704
3137
  * the profile.
2705
- * - `ASTM_RECORD_UNINTERPRETED_QUERY_STATUS` — a `Q`-record request-information
3138
+ * - `ASTM_RECORD_UNINTERPRETED_QUERY_STATUS`: a `Q`-record request-information
2706
3139
  * status surfaced verbatim on a *request* record; the code set is paywalled and
2707
3140
  * is never interpreted, profile or not.
2708
3141
  *
2709
- * Everything a wrong value could hide from — a result value split ambiguity, an
3142
+ * Everything a wrong value could hide from: a result value split ambiguity, an
2710
3143
  * undefined abnormal flag or result status, an unparseable reference range, absent
2711
3144
  * units, a mis-attached comment, a partial timestamp, a query-vs-result ambiguity,
2712
3145
  * a bad frame checksum / sequence gap / unterminated / oversize frame, an
2713
- * ambiguous transport, an unexpected protocol event, or a rejected frame — is
3146
+ * ambiguous transport, an unexpected protocol event, or a rejected frame: is
2714
3147
  * forbidden.
2715
3148
  */
2716
3149
 
2717
3150
  /**
2718
- * The **only** warning codes a profile may list in its `tolerate` set — benign,
3151
+ * The **only** warning codes a profile may list in its `tolerate` set: benign,
2719
3152
  * structural/syntactic vendor noise that cannot alter, drop, or fabricate an
2720
3153
  * extracted value. Frozen so it cannot be mutated at runtime to smuggle a code in.
2721
3154
  * Adding to this set is a deliberate, reviewable act (and each addition must be an
@@ -2741,7 +3174,7 @@ declare const TOLERABLE_CODES: ReadonlySet<AnyAstmWarningCode>;
2741
3174
  */
2742
3175
  declare const ALL_ASTM_WARNING_CODES: ReadonlySet<AnyAstmWarningCode>;
2743
3176
  /**
2744
- * The forbidden set — computed as **every known code minus the tolerable
3177
+ * The forbidden set: computed as **every known code minus the tolerable
2745
3178
  * allow-list**, so it is complete by construction. Frozen. A code appears here iff
2746
3179
  * it is a real warning code that is not in {@link TOLERABLE_CODES}.
2747
3180
  *
@@ -2754,7 +3187,7 @@ declare const ALL_ASTM_WARNING_CODES: ReadonlySet<AnyAstmWarningCode>;
2754
3187
  */
2755
3188
  declare const SAFETY_CRITICAL_CODES: ReadonlySet<AnyAstmWarningCode>;
2756
3189
  /**
2757
- * True when `code` is a known warning code that is **not** tolerable — i.e. a
3190
+ * True when `code` is a known warning code that is **not** tolerable: i.e. a
2758
3191
  * profile may never list it. A code that is not a real warning code at all returns
2759
3192
  * `false` here (the validator reports "unknown code" separately, a distinct
2760
3193
  * failure with a distinct message).
@@ -2772,14 +3205,14 @@ declare function isSafetyCriticalCode(code: string): boolean;
2772
3205
 
2773
3206
  /**
2774
3207
  * The `default` conservative baseline profile. Tolerates **nothing** and forces no
2775
- * transport — every deviation surfaces as its own warning, unmodified, and framing
3208
+ * transport: every deviation surfaces as its own warning, unmodified, and framing
2776
3209
  * is auto-detected. It exists so "no profile" and "the default profile" are the
2777
3210
  * same, explicit behaviour, and so a consumer can name the baseline when composing
2778
3211
  * (`extends: astmProfiles.default`) without importing a special sentinel. Absence of
2779
- * a profile means this conservative default applied — not that a stream was fully
3212
+ * a profile means this conservative default applied: not that a stream was fully
2780
3213
  * understood.
2781
3214
  *
2782
- * Authored through the public `defineAstmProfile()` API — zero privileged coupling;
3215
+ * Authored through the public `defineAstmProfile()` API: zero privileged coupling;
2783
3216
  * it is exactly what a user would write.
2784
3217
  */
2785
3218
 
@@ -2796,14 +3229,13 @@ declare function isSafetyCriticalCode(code: string): boolean;
2796
3229
  declare const defaultProfile: AstmProfile;
2797
3230
 
2798
3231
  /**
2799
- * The `referenceCorpus` profile — a **non-vendor**, evidence-backed tolerance
3232
+ * The `referenceCorpus` profile: a **non-vendor**, evidence-backed tolerance
2800
3233
  * grounded firsthand in the **redistributable OSS reference corpus**, not an
2801
3234
  * invented per-vendor deviation matrix.
2802
3235
  *
2803
- * Grounding (firsthand-verified 2026-07-21; the corpus is permissively licensed and
2804
- * in scope for grounding, per the roadmap):
3236
+ * Grounding (firsthand-verified 2026-07-21; the corpus is permissively licensed):
2805
3237
  *
2806
- * - **`kxepal/python-astm` `codec.py`** (BSD) — its `decode_record` /
3238
+ * - **`kxepal/python-astm` `codec.py`** (BSD), its `decode_record` /
2807
3239
  * `decode_component` / `decode_repeated_component` split fields, components, and
2808
3240
  * repeats on the raw delimiters and **never un-escape** ASTM escape sequences: the
2809
3241
  * source contains zero handling of `&F&`/`&S&`/`&R&`/`&E&`, and the escape
@@ -2814,27 +3246,26 @@ declare const defaultProfile: AstmProfile;
2814
3246
  * or non-standard `&…&` body inside a free-text field (units, comments, test
2815
3247
  * names) that our **escape-aware** tokenizer flags as
2816
3248
  * `ASTM_UNKNOWN_ESCAPE_SEQUENCE`. We are deliberately stricter than the OSS
2817
- * references here (roadmap §6 differential note), so the warning is *expected*
2818
- * noise when talking to that ecosystem — recognizable and non-clinical.
3249
+ * references here, so the warning is *expected*
3250
+ * noise when talking to that ecosystem: recognizable and non-clinical.
2819
3251
  *
2820
3252
  * **Why this is safe.** The escape codec **preserves an unrecognized `&…&` body
2821
3253
  * byte-for-byte** in the decoded value (`decodeEscapes` never guesses); the value a
2822
3254
  * consumer reads is identical whether or not this profile is active. The profile
2823
- * only re-badges the *warning* — no value, flag, status, or identifier is altered
3255
+ * only re-badges the *warning*: no value, flag, status, or identifier is altered
2824
3256
  * or dropped. And by the safety gate in `defineAstmProfile`, it **cannot** tolerate
2825
3257
  * a result value / flag / status / range / units, a patient or comment context, a
2826
- * message-kind ambiguity, or any frame / LTP integrity warning — those always
3258
+ * message-kind ambiguity, or any frame / LTP integrity warning: those always
2827
3259
  * surface, profile or not.
2828
3260
  *
2829
3261
  * **Honesty.** This is a *reference-corpus* profile, not a claim about any named
2830
3262
  * analyzer vendor. No per-vendor ("cobas does X") behaviour is asserted; a named
2831
- * vendor profile awaits a firsthand vendor-attributed quirk document and stays
2832
- * deferred (`REAL-CORPUS`).
3263
+ * vendor profile awaits a firsthand vendor-attributed quirk document.
2833
3264
  */
2834
3265
 
2835
3266
  /**
2836
3267
  * Tolerates the non-standard-escape noise that streams from the escape-agnostic OSS
2837
- * reference stack (`python-astm` / `senaite`) carry — the value is preserved
3268
+ * reference stack (`python-astm` / `senaite`) carry: the value is preserved
2838
3269
  * verbatim; only the syntactic warning is expected.
2839
3270
  *
2840
3271
  * @example
@@ -2854,7 +3285,7 @@ declare const referenceCorpus: AstmProfile;
2854
3285
  * set, the definition-time error, and the profile types.
2855
3286
  *
2856
3287
  * The individual built-ins are reached via `astmProfiles.referenceCorpus` etc., not
2857
- * as top-level named exports — mirrors the sibling `profiles` namespace convention
3288
+ * as top-level named exports: mirrors the sibling `profiles` namespace convention
2858
3289
  * ("default" is too generic for a top-level export).
2859
3290
  */
2860
3291
 
@@ -2864,7 +3295,7 @@ declare const referenceCorpus: AstmProfile;
2864
3295
  * redistributable OSS reference corpus), each authored via the public
2865
3296
  * `defineAstmProfile()` API and carrying its cited provenance. Named per-vendor
2866
3297
  * profiles (cobas / Sysmex / …) are **deferred** pending a firsthand
2867
- * vendor-attributed quirk document — the engine fully supports them; we do not ship
3298
+ * vendor-attributed quirk document: the engine fully supports them; we do not ship
2868
3299
  * ungrounded ones.
2869
3300
  *
2870
3301
  * @example
@@ -2883,18 +3314,18 @@ declare const astmProfiles: {
2883
3314
  * The **consumer-supplied** LIVD catalog: a vendor-test-code → LOINC index built
2884
3315
  * from an IICC LIVD ("LOINC to Vendor IVD") mapping the consumer provides.
2885
3316
  *
2886
- * **Bring-your-own, by design (roadmap §5, Phase 9).** `@cosyte/astm` bundles
2887
- * **no** LOINC, SNOMED, or LIVD data — LOINC is © Regenstrief (redistributable
3317
+ * **Bring-your-own, by design.** `@cosyte/astm` bundles
3318
+ * **no** LOINC, SNOMED, or LIVD data: LOINC is © Regenstrief (redistributable
2888
3319
  * only with its attribution notice) and the public CDC LIVD publication is a
2889
3320
  * SARS-CoV-2-specific file that also carries SNOMED CT (separately licensed), not
2890
3321
  * a general-analyte, public-domain catalog. So the package stays a **structural
2891
3322
  * recognizer, not a dictionary**: it recognizes the Universal Test ID's LOINC slot
2892
- * and surfaces vendor codes verbatim (Phase 1), and this module lets a consumer
2893
- * *supply* their own LIVD file to map those codes — the terminology data, and its
3323
+ * and surfaces vendor codes verbatim, and this module lets a consumer
3324
+ * *supply* their own LIVD file to map those codes, the terminology data, and its
2894
3325
  * license obligations, are the consumer's.
2895
3326
  *
2896
3327
  * **Grounded firsthand on the IICC LIVD digital format / HL7 LIVD IG.** The
2897
- * mapping key is the **Vendor Analyte Code** — the vendor *transmission code* the
3328
+ * mapping key is the **Vendor Analyte Code**: the vendor *transmission code* the
2898
3329
  * instrument sends for an automated test, which is exactly the local code an ASTM
2899
3330
  * analyzer puts in the Universal Test ID (`R`/`O` field, component 4). The mapping
2900
3331
  * target is the **LOINC Code** (with the LOINC Long Common Name as an optional
@@ -2911,7 +3342,7 @@ declare const astmProfiles: {
2911
3342
  * a catalog-hygiene concern the consumer owns.
2912
3343
  */
2913
3344
  /**
2914
- * One LIVD mapping row a consumer supplies — a vendor test code and the LOINC it
3345
+ * One LIVD mapping row a consumer supplies: a vendor test code and the LOINC it
2915
3346
  * maps to, plus optional human-readable / provenance fields. Modeled on the IICC
2916
3347
  * LIVD digital format's data elements.
2917
3348
  *
@@ -2923,20 +3354,20 @@ declare const astmProfiles: {
2923
3354
  */
2924
3355
  interface LivdEntry {
2925
3356
  /**
2926
- * The **Vendor Analyte Code** — the vendor transmission code the instrument sends (the local code
3357
+ * The **Vendor Analyte Code**: the vendor transmission code the instrument sends (the local code
2927
3358
  * in an ASTM Universal Test ID, component 4). The mapping key; compared **verbatim** (exact,
2928
- * case-sensitive) against the reported code — never normalized or fuzzy-matched.
3359
+ * case-sensitive) against the reported code, never normalized or fuzzy-matched.
2929
3360
  */
2930
3361
  readonly vendorCode: string;
2931
3362
  /**
2932
3363
  * The **LOINC Code** this vendor code maps to (e.g. `"1920-8"`). Taken from the consumer's catalog
2933
- * as-is and **never validated, altered, or invented** — the parser does not ship a LOINC table and
3364
+ * as-is and **never validated, altered, or invented**: the parser does not ship a LOINC table and
2934
3365
  * cannot check it; it only carries what the catalog says.
2935
3366
  */
2936
3367
  readonly loinc: string;
2937
- /** The **LOINC Long Common Name**, when the catalog supplies it — an optional human-readable label. */
3368
+ /** The **LOINC Long Common Name**, when the catalog supplies it: an optional human-readable label. */
2938
3369
  readonly loincLongName?: string;
2939
- /** The **Vendor Analyte Name** — the vendor's human-readable analyte label, when supplied. */
3370
+ /** The **Vendor Analyte Name**: the vendor's human-readable analyte label, when supplied. */
2940
3371
  readonly vendorAnalyteName?: string;
2941
3372
  /** The device **Manufacturer**, when the catalog scopes the mapping to a device (optional provenance). */
2942
3373
  readonly manufacturer?: string;
@@ -2945,7 +3376,7 @@ interface LivdEntry {
2945
3376
  }
2946
3377
  /**
2947
3378
  * The outcome of looking a vendor code up in a {@link LivdCatalog}. A distinct
2948
- * value per safe disposition — a hit is `mapped`; a miss is `unmapped`; a code that
3379
+ * value per safe disposition: a hit is `mapped`; a miss is `unmapped`; a code that
2949
3380
  * matched more than one **distinct** LOINC is `ambiguous` with the candidates
2950
3381
  * surfaced but **none chosen**. There is deliberately no "guessed" case.
2951
3382
  */
@@ -2956,11 +3387,11 @@ type LivdLookup =
2956
3387
  readonly loinc: string;
2957
3388
  readonly loincLongName?: string;
2958
3389
  }
2959
- /** No entry for this code — a miss. The code stays verbatim; no LOINC is invented. */
3390
+ /** No entry for this code: a miss. The code stays verbatim; no LOINC is invented. */
2960
3391
  | {
2961
3392
  readonly status: "unmapped";
2962
3393
  }
2963
- /** More than one distinct LOINC — surfaced for inspection, never resolved to one. */
3394
+ /** More than one distinct LOINC: surfaced for inspection, never resolved to one. */
2964
3395
  | {
2965
3396
  readonly status: "ambiguous";
2966
3397
  readonly candidates: readonly string[];
@@ -2976,7 +3407,7 @@ interface LivdCatalog {
2976
3407
  /**
2977
3408
  * Look a vendor code up, verbatim (exact, case-sensitive). Returns `mapped` on a
2978
3409
  * single-LOINC hit, `unmapped` on a miss, and `ambiguous` when the code carries
2979
- * more than one distinct LOINC — never a guess.
3410
+ * more than one distinct LOINC: never a guess.
2980
3411
  *
2981
3412
  * @param vendorCode - The reported vendor/local test code.
2982
3413
  * @returns The lookup outcome.
@@ -3011,14 +3442,14 @@ interface LivdCatalog {
3011
3442
  declare function defineLivdCatalog(entries: readonly LivdEntry[]): LivdCatalog;
3012
3443
 
3013
3444
  /**
3014
- * The **terminology** warning registry (`ASTM_LIVD_*`) for the Phase-9
3015
- * LIVD-aware LOINC recognition layer — the fourth of the package's registries,
3445
+ * The **terminology** warning registry (`ASTM_LIVD_*`) for the LIVD-aware LOINC
3446
+ * recognition layer: the fourth of the package's registries,
3016
3447
  * alongside the record layer's `ASTM_RECORD_*`, the frame codec's `ASTM_FRAME_*`,
3017
3448
  * and the protocol reducer's `ASTM_LTP_*`.
3018
3449
  *
3019
3450
  * A terminology warning is the record of a **safe non-mapping**: a reported
3020
3451
  * vendor test code that a consumer-supplied LIVD catalog did not map to a single
3021
- * LOINC. It never carries a LOINC the catalog did not vouch for — the whole point
3452
+ * LOINC. It never carries a LOINC the catalog did not vouch for: the whole point
3022
3453
  * of the layer is that an unmapped or ambiguous code is surfaced as *unmapped*,
3023
3454
  * **never a guessed LOINC** (mis-identifying a test is safety-critical). Every
3024
3455
  * warning carries a stable code plus positional context ({@link AstmPosition}) and
@@ -3029,7 +3460,7 @@ declare function defineLivdCatalog(entries: readonly LivdEntry[]): LivdCatalog;
3029
3460
  * profile safety gate's universe (`ALL_ASTM_WARNING_CODES`): a LIVD non-mapping is
3030
3461
  * a post-parse advisory produced by the opt-in {@link applyLivd} helper, not a
3031
3462
  * parse-time deviation a vendor profile could ever "tolerate". A profile referencing
3032
- * one is rejected as an unknown code — correctly, since there is nothing here to
3463
+ * one is rejected as an unknown code: correctly, since there is nothing here to
3033
3464
  * quiet.
3034
3465
  */
3035
3466
 
@@ -3046,14 +3477,14 @@ declare function defineLivdCatalog(entries: readonly LivdEntry[]): LivdCatalog;
3046
3477
  declare const LIVD_WARNING_CODES: {
3047
3478
  /**
3048
3479
  * A record carried a vendor/local test code, but the consumer-supplied LIVD catalog held **no**
3049
- * entry for it. The code stays surfaced verbatim and the mapping is `unmapped` — a LOINC is
3480
+ * entry for it. The code stays surfaced verbatim and the mapping is `unmapped`: a LOINC is
3050
3481
  * **never** guessed. Purely advisory: the raw code and value are untouched.
3051
3482
  */
3052
3483
  readonly ASTM_LIVD_UNMAPPED_CODE: "ASTM_LIVD_UNMAPPED_CODE";
3053
3484
  /**
3054
3485
  * A vendor/local test code matched **more than one distinct LOINC** in the catalog (e.g. the same
3055
3486
  * transmission code used by two devices for different analytes). The mapping is `ambiguous` and the
3056
- * candidate LOINCs are surfaced for inspection, but **none is chosen** — refusing to pick is the
3487
+ * candidate LOINCs are surfaced for inspection, but **none is chosen**: refusing to pick is the
3057
3488
  * fail-safe, since guessing wrong mis-identifies the test.
3058
3489
  */
3059
3490
  readonly ASTM_LIVD_AMBIGUOUS_MAPPING: "ASTM_LIVD_AMBIGUOUS_MAPPING";
@@ -3072,7 +3503,7 @@ type LivdWarningCode = (typeof LIVD_WARNING_CODES)[keyof typeof LIVD_WARNING_COD
3072
3503
  * import type { AstmLivdWarning } from "@cosyte/astm";
3073
3504
  * const w: AstmLivdWarning = {
3074
3505
  * code: "ASTM_LIVD_UNMAPPED_CODE",
3075
- * message: "Reported test code had no LIVD mapping — surfaced unmapped, never a guessed LOINC.",
3506
+ * message: "Reported test code had no LIVD mapping, surfaced unmapped, never a guessed LOINC.",
3076
3507
  * position: { recordIndex: 3, recordType: "R" },
3077
3508
  * };
3078
3509
  * ```
@@ -3111,15 +3542,15 @@ declare function livdUnmappedCode(position: AstmPosition): AstmLivdWarning;
3111
3542
  declare function livdAmbiguousMapping(position: AstmPosition): AstmLivdWarning;
3112
3543
 
3113
3544
  /**
3114
- * The **additive, advisory** LIVD annotation layer (Phase 9). {@link applyLivd}
3545
+ * The **additive, advisory** LIVD annotation layer. {@link applyLivd}
3115
3546
  * reads a parsed {@link AstmMessage} and a consumer-supplied {@link LivdCatalog}
3116
- * and returns a **separate** layer of per-record LOINC annotations — it **never
3547
+ * and returns a **separate** layer of per-record LOINC annotations: it **never
3117
3548
  * mutates, alters, or drops** the raw reported code or value. Recognition is an
3118
3549
  * annotation, not a rewrite: the wire stays exactly as parsed.
3119
3550
  *
3120
3551
  * **The never-fabricate rule (the safety-critical point).** A vendor code with no
3121
3552
  * single LIVD mapping surfaces as a typed `unmapped`/`ambiguous`/`no-code`
3122
- * annotation — **never** a guessed LOINC. A wrong LOINC mis-identifies a test, so
3553
+ * annotation: **never** a guessed LOINC. A wrong LOINC mis-identifies a test, so
3123
3554
  * the layer only ever reports a LOINC the wire itself carried (an inline LOINC
3124
3555
  * candidate) or one the consumer's catalog vouched for (labeled `derived`), and
3125
3556
  * otherwise reports nothing but the miss.
@@ -3129,14 +3560,14 @@ declare function livdAmbiguousMapping(position: AstmPosition): AstmLivdWarning;
3129
3560
  * The outcome of annotating one record's Universal Test ID against a LIVD catalog.
3130
3561
  * A distinct case per disposition; there is no case in which a LOINC is guessed.
3131
3562
  *
3132
- * - `mapped` — the vendor/local code resolved to a single LOINC **via the catalog**
3563
+ * - `mapped`, the vendor/local code resolved to a single LOINC **via the catalog**
3133
3564
  * (labeled `derived: true`, `source: "livd"`).
3134
- * - `inline-loinc` — the wire itself carried a LOINC in the Universal Test ID's slot
3565
+ * - `inline-loinc`, the wire itself carried a LOINC in the Universal Test ID's slot
3135
3566
  * (component 1); surfaced `source: "wire"`, **not** derived and **not** validated.
3136
- * - `unmapped` — a vendor/local code with no catalog entry.
3137
- * - `ambiguous` — a vendor/local code matching more than one distinct LOINC; the
3567
+ * - `unmapped`: a vendor/local code with no catalog entry.
3568
+ * - `ambiguous`: a vendor/local code matching more than one distinct LOINC; the
3138
3569
  * candidates are surfaced but **none is chosen**.
3139
- * - `no-code` — the record carried no usable test code at all (name-only/empty), so
3570
+ * - `no-code`: the record carried no usable test code at all (name-only/empty), so
3140
3571
  * there was nothing to map.
3141
3572
  */
3142
3573
  type LivdMapping = {
@@ -3158,7 +3589,7 @@ type LivdMapping = {
3158
3589
  readonly status: "no-code";
3159
3590
  };
3160
3591
  /**
3161
- * One record's LIVD annotation — which record, the code that was looked up
3592
+ * One record's LIVD annotation, which record, the code that was looked up
3162
3593
  * (verbatim), how it was recognized, and the mapping outcome. Additive: it points
3163
3594
  * at the record by index and never replaces it.
3164
3595
  *
@@ -3183,7 +3614,7 @@ interface LivdAnnotation {
3183
3614
  readonly reportedCode?: string;
3184
3615
  /** How the reported code was recognized in the Universal Test ID (provenance only, never a lookup). */
3185
3616
  readonly provenance: UniversalTestIdProvenance;
3186
- /** The mapping outcome — never a guessed LOINC. */
3617
+ /** The mapping outcome: never a guessed LOINC. */
3187
3618
  readonly mapping: LivdMapping;
3188
3619
  }
3189
3620
  /**
@@ -3194,7 +3625,7 @@ interface LivdAnnotation {
3194
3625
  interface LivdResult {
3195
3626
  /** One annotation per `R`/`O` record, in wire order. */
3196
3627
  readonly annotations: readonly LivdAnnotation[];
3197
- /** A value-free warning per `unmapped`/`ambiguous` code — never per `mapped`/`inline-loinc`/`no-code`. */
3628
+ /** A value-free warning per `unmapped`/`ambiguous` code: never per `mapped`/`inline-loinc`/`no-code`. */
3198
3629
  readonly warnings: readonly AstmLivdWarning[];
3199
3630
  }
3200
3631
  /**
@@ -3270,7 +3701,7 @@ interface FrameChecksum {
3270
3701
  }
3271
3702
  /**
3272
3703
  * One decoded ASTM frame. `text` is the frame's record-byte payload (the bytes
3273
- * between the frame number and the terminator, escapes untouched — this is the
3704
+ * between the frame number and the terminator, escapes untouched, this is the
3274
3705
  * framing layer, not the record layer). `trusted` is the single flag a consumer
3275
3706
  * gates on: it is `true` only for a fully-terminated frame whose checksum
3276
3707
  * validated, and such frames are the only ones reassembled into `records`.
@@ -3308,7 +3739,7 @@ interface AstmFrame {
3308
3739
  */
3309
3740
  interface DecodeAstmFramesResult {
3310
3741
  /**
3311
- * The reassembled record byte-strings — one entry per complete record (a run of
3742
+ * The reassembled record byte-strings: one entry per complete record (a run of
3312
3743
  * frames closed by an `ETX`), in wire order. **Only** clean reassemblies appear:
3313
3744
  * a record whose frames included a bad checksum, a sequence gap, or an
3314
3745
  * unterminated frame is **omitted** (its frames are still in {@link DecodeAstmFramesResult.frames},
@@ -3336,16 +3767,16 @@ interface FrameOptions {
3336
3767
  * The value model for the pure LTP protocol reducer ({@link ltpReduce}).
3337
3768
  *
3338
3769
  * The reducer models the **receiver** side of an ASTM/CLSI-LIS01 (LIS01-A2)
3339
- * session — the host/LIS receiving an upload — because that is where the one
3770
+ * session, the host/LIS receiving an upload, because that is where the one
3340
3771
  * safety decision lives: whether to `ACK` (accept) or `NAK` (reject) a frame that
3341
3772
  * just arrived. It is a **pure function** of the current {@link LtpState} and one
3342
3773
  * inbound {@link LtpEvent}, returning an {@link LtpTransition} (the next state plus
3343
3774
  * the actions the consumer should take and any warnings). No sockets, no timers,
3344
- * no I/O — the consumer owns the wire and the clock; the reducer owns the logic.
3775
+ * no I/O: the consumer owns the wire and the clock; the reducer owns the logic.
3345
3776
  */
3346
3777
 
3347
3778
  /**
3348
- * The protocol phase. The line is either **neutral** (idle — awaiting an `ENQ` to
3779
+ * The protocol phase. The line is either **neutral** (idle, awaiting an `ENQ` to
3349
3780
  * establish, or reset there after an `EOT`) or in **transfer** (establishment
3350
3781
  * accepted; frames and their per-frame `ACK`/`NAK` flow until the sender's `EOT`).
3351
3782
  * The classic three-phase LIS01 model (establishment → transfer → termination)
@@ -3383,7 +3814,7 @@ interface LtpState {
3383
3814
  /** `true` when a record is mid-reassembly (an `ETB` frame was accepted, awaiting its `ETX`). */
3384
3815
  readonly recordOpen: boolean;
3385
3816
  /**
3386
- * The bytes accumulated so far for the in-progress record — the concatenation of
3817
+ * The bytes accumulated so far for the in-progress record: the concatenation of
3387
3818
  * the `ETB` frames accepted since the last `ETX`. Empty when no record is open.
3388
3819
  * These bytes are **never** delivered on their own: only an `ETX` completes a
3389
3820
  * record, at which point they (plus the final frame's text) become one entry in
@@ -3395,7 +3826,7 @@ interface LtpState {
3395
3826
  * An inbound protocol event the consumer feeds the reducer: one of the four LTP
3396
3827
  * control signals (`ENQ`/`ACK`/`NAK`/`EOT`) read off the wire, or a `frame` the
3397
3828
  * consumer already decoded with `decodeAstmFrames` (the reducer reuses the codec's
3398
- * `trusted`/`checksum` verdict — it never re-derives it).
3829
+ * `trusted`/`checksum` verdict, it never re-derives it).
3399
3830
  */
3400
3831
  type LtpEvent = {
3401
3832
  readonly type: "enq";
@@ -3442,14 +3873,14 @@ interface LtpTransition {
3442
3873
  * The pure LTP protocol reducer: {@link ltpReduce} and {@link ltpInitialState}.
3443
3874
  *
3444
3875
  * A deterministic, socket-free receiver-side state machine for ASTM/CLSI-LIS01
3445
- * (LIS01-A2). The consumer owns the wire — it reads bytes, decodes frames with
3446
- * `decodeAstmFrames`, and drives this reducer one {@link LtpEvent} at a time — and
3876
+ * (LIS01-A2). The consumer owns the wire (it reads bytes, decodes frames with
3877
+ * `decodeAstmFrames`, and drives this reducer one {@link LtpEvent} at a time) and
3447
3878
  * the reducer decides, purely, what happens next: whether to `ACK` or `NAK`, when a
3448
3879
  * record is complete, and when the session returns to neutral.
3449
3880
  *
3450
3881
  * **The one inviolable rule (borrowed from `mllp`'s ACK-failsafe discipline):** a
3451
- * frame the codec did not vouch for — a bad checksum, an unterminated frame, or one
3452
- * out of sequence — is answered with `NAK`, **never** a fabricated positive `ACK`,
3882
+ * frame the codec did not vouch for (a bad checksum, an unterminated frame, or one
3883
+ * out of sequence) is answered with `NAK`, **never** a fabricated positive `ACK`,
3453
3884
  * and its bytes are **never** appended to a record or delivered. A `NAK` drives
3454
3885
  * retransmit, not acceptance. This is the protocol-level twin of the frame codec's
3455
3886
  * "a bad-checksum frame is never merged into a record."
@@ -3472,14 +3903,14 @@ interface LtpTransition {
3472
3903
  */
3473
3904
  declare function ltpInitialState(): LtpState;
3474
3905
  /**
3475
- * Advance the LTP session by one inbound event — a pure function, no I/O.
3906
+ * Advance the LTP session by one inbound event: a pure function, no I/O.
3476
3907
  *
3477
3908
  * Behaviour by phase:
3478
- * - **neutral** — `enq` accepts establishment (`sendAck`, enter transfer); `eot` is
3909
+ * - **neutral**: `enq` accepts establishment (`sendAck`, enter transfer); `eot` is
3479
3910
  * a benign line reset (no-op); `ack`/`nak` are unexpected at a receiver (surfaced,
3480
3911
  * never read as acceptance); a `frame` before establishment is tolerated
3481
- * (Postel's Law) — the session auto-establishes and processes it, with a warning.
3482
- * - **transfer** — a `frame` is accepted (`sendAck`, appended, sequence advanced) only
3912
+ * (Postel's Law), the session auto-establishes and processes it, with a warning.
3913
+ * - **transfer**: a `frame` is accepted (`sendAck`, appended, sequence advanced) only
3483
3914
  * when it is trusted *and* carries the expected number; a duplicate of the last
3484
3915
  * accepted frame is idempotently re-`ACK`ed without re-appending; a bad or
3485
3916
  * out-of-sequence frame is `NAK`ed and dropped. `eot` terminates the transfer and
@@ -3505,18 +3936,18 @@ declare function ltpReduce(state: LtpState, event: LtpEvent): LtpTransition;
3505
3936
  * (was E1381), plus the bare-record-letter set the transport detector keys on.
3506
3937
  *
3507
3938
  * The frame codec (`../frames`) owns the *inside* of a frame (`STX FN text
3508
- * ETB/ETX CS CRLF`); this module owns the *handshake around* frames — the
3939
+ * ETB/ETX CS CRLF`); this module owns the *handshake around* frames, the
3509
3940
  * establishment/transfer/termination signalling that the {@link ltpReduce}
3510
3941
  * reducer consumes. These are the raw byte values; the reducer and the transport
3511
3942
  * detector are the only consumers.
3512
3943
  */
3513
- /** Enquiry (`0x05`) — the sender's request to establish a transfer. */
3944
+ /** Enquiry (`0x05`): the sender's request to establish a transfer. */
3514
3945
  declare const ENQ = 5;
3515
- /** Acknowledge (`0x06`) — the receiver accepted the last establishment or frame. */
3946
+ /** Acknowledge (`0x06`): the receiver accepted the last establishment or frame. */
3516
3947
  declare const ACK = 6;
3517
- /** Negative acknowledge (`0x15`) — the receiver rejected the last frame; retransmit, do not accept. */
3948
+ /** Negative acknowledge (`0x15`): the receiver rejected the last frame; retransmit, do not accept. */
3518
3949
  declare const NAK = 21;
3519
- /** End of transmission (`0x04`) — the sender terminated the transfer; the line returns to neutral. */
3950
+ /** End of transmission (`0x04`): the sender terminated the transfer; the line returns to neutral. */
3520
3951
  declare const EOT = 4;
3521
3952
 
3522
3953
  /**
@@ -3530,7 +3961,7 @@ declare const EOT = 4;
3530
3961
  * **both** modes: a bad-checksum frame is surfaced flagged untrusted and **never**
3531
3962
  * merged into a record, and a sequence gap is **never** silently bridged.
3532
3963
  *
3533
- * This is the framing layer only — it does not parse record grammar. Hand a
3964
+ * This is the framing layer only: it does not parse record grammar. Hand a
3534
3965
  * reassembled record to `parseAstmRecords`, or use `parseFramedAstm` to compose
3535
3966
  * both layers at the edge.
3536
3967
  */
@@ -3540,8 +3971,8 @@ declare const EOT = 4;
3540
3971
  *
3541
3972
  * Bytes outside a frame (before the first `STX`, or between a frame's trailing
3542
3973
  * `LF` and the next `STX`) are skipped: in ASTM they are low-level transfer
3543
- * control (`ENQ`/`ACK`/`NAK`/`EOT` — the Phase 6 protocol layer), never record
3544
- * content, so skipping them is not data loss.
3974
+ * control (`ENQ`/`ACK`/`NAK`/`EOT`), never record content, so skipping them is
3975
+ * not data loss.
3545
3976
  *
3546
3977
  * @param bytes - The framed byte stream.
3547
3978
  * @param options - Decode options; lenient unless `strict` is set.
@@ -3551,7 +3982,7 @@ declare const EOT = 4;
3551
3982
  * @example
3552
3983
  * ```ts
3553
3984
  * import { decodeAstmFrames } from "@cosyte/astm";
3554
- * // One final frame carrying the record text "R|1|" — checksum "AF" over FN..ETX (mod 256).
3985
+ * // One final frame carrying the record text "R|1|": checksum "AF" over FN..ETX (mod 256).
3555
3986
  * const bytes = new Uint8Array([0x02, 0x31, 0x52, 0x7c, 0x31, 0x7c, 0x03, 0x41, 0x46, 0x0d, 0x0a]);
3556
3987
  * const { records, frames } = decodeAstmFrames(bytes);
3557
3988
  * frames[0]?.frameNumber; // 1
@@ -3561,10 +3992,10 @@ declare const EOT = 4;
3561
3992
  declare function decodeAstmFrames(bytes: Uint8Array, options?: FrameOptions): DecodeAstmFramesResult;
3562
3993
 
3563
3994
  /**
3564
- * The frame-layer **emit** side: {@link composeAstmFrames} — Phase 7.
3995
+ * The frame-layer **emit** side: {@link composeAstmFrames}.
3565
3996
  *
3566
3997
  * The exact inverse of {@link decodeAstmFrames}. Given the reassembled record
3567
- * byte-strings (one entry per complete record — the same shape `decodeAstmFrames`
3998
+ * byte-strings (one entry per complete record, the same shape `decodeAstmFrames`
3568
3999
  * *produces*), it wraps each into one or more `<STX> FN text <ETB|ETX> CS <CR><LF>`
3569
4000
  * frames, so `decodeAstmFrames(composeAstmFrames(records)).records` reproduces the
3570
4001
  * input records exactly.
@@ -3576,7 +4007,7 @@ declare function decodeAstmFrames(bytes: Uint8Array, options?: FrameOptions): De
3576
4007
  * - the **frame number** `0`–`7`, starting at `1` and rolling over `7 → 0 → 1`,
3577
4008
  * continuous across every frame in the stream (as the decoder's sequence check
3578
4009
  * expects);
3579
- * - the **240-byte text split** — a record longer than 240 bytes is split across
4010
+ * - the **240-byte text split**: a record longer than 240 bytes is split across
3580
4011
  * frames closed by `ETB` (intermediate) with a final `ETX`; the seven framing
3581
4012
  * control bytes are never counted toward the 240.
3582
4013
  *
@@ -3585,7 +4016,7 @@ declare function decodeAstmFrames(bytes: Uint8Array, options?: FrameOptions): De
3585
4016
  */
3586
4017
  /**
3587
4018
  * Thrown by {@link composeAstmFrames} when the input cannot be framed into a
3588
- * spec-clean stream — an empty record list, or a record with no bytes (a frame
4019
+ * spec-clean stream: an empty record list, or a record with no bytes (a frame
3589
4020
  * must carry a record; an empty one is a structural error, never an empty frame).
3590
4021
  * Carries a stable code + the offending record index, never record bytes.
3591
4022
  *
@@ -3611,12 +4042,12 @@ declare class AstmFrameEncodeError extends Error {
3611
4042
  interface ComposeFramesOptions {
3612
4043
  /**
3613
4044
  * The frame number to start the sequence at (`0`–`7`). Defaults to
3614
- * {@link FIRST_FRAME_NUMBER} (`1`) — the ASTM convention.
4045
+ * {@link FIRST_FRAME_NUMBER} (`1`): the ASTM convention.
3615
4046
  */
3616
4047
  readonly startFrameNumber?: number;
3617
4048
  }
3618
4049
  /**
3619
- * Frame reassembled record bytes into a spec-clean ASTM/CLSI-LIS01 byte stream —
4050
+ * Frame reassembled record bytes into a spec-clean ASTM/CLSI-LIS01 byte stream:
3620
4051
  * the inverse of {@link decodeAstmFrames}.
3621
4052
  *
3622
4053
  * Each record is split at 240 text bytes into `ETB`-closed intermediate frames
@@ -3643,11 +4074,11 @@ declare function composeAstmFrames(records: readonly (Uint8Array | string)[], op
3643
4074
  * Composing the two layers at the edge: {@link parseFramedAstm}.
3644
4075
  *
3645
4076
  * The framing layer ({@link decodeAstmFrames}) and the record layer
3646
- * (`parseAstmRecords`) share nothing but the payload boundary — a frame carries
4077
+ * (`parseAstmRecords`) share nothing but the payload boundary: a frame carries
3647
4078
  * record bytes; a record knows nothing about frames. This helper joins them at the
3648
4079
  * one point they meet: decode a framed stream into trusted, reassembled record
3649
4080
  * bytes, then parse those bytes into an {@link AstmMessage}. It is deliberately
3650
- * thin — a consumer that already holds de-framed record bytes calls
4081
+ * thin: a consumer that already holds de-framed record bytes calls
3651
4082
  * `parseAstmRecords` directly, and a consumer that only wants frames calls
3652
4083
  * `decodeAstmFrames` directly.
3653
4084
  */
@@ -3684,7 +4115,7 @@ interface FramedAstmResult {
3684
4115
  * @example
3685
4116
  * ```ts
3686
4117
  * import { parseFramedAstm } from "@cosyte/astm";
3687
- * // A single final frame carrying "H|\^&\r" — checksum "E5" over FN..ETX (mod 256).
4118
+ * // A single final frame carrying "H|\^&\r": checksum "E5" over FN..ETX (mod 256).
3688
4119
  * const bytes = new Uint8Array([
3689
4120
  * 0x02, 0x31, 0x48, 0x7c, 0x5c, 0x5e, 0x26, 0x0d, 0x03, 0x45, 0x35, 0x0d, 0x0a,
3690
4121
  * ]);
@@ -3696,7 +4127,7 @@ interface FramedAstmResult {
3696
4127
  declare function parseFramedAstm(bytes: Uint8Array, options?: FrameOptions & AstmParseOptions): FramedAstmResult;
3697
4128
  /**
3698
4129
  * Serialize an ASTM message (or a bare record list) and frame it into a spec-clean
3699
- * byte stream in one call — the inverse of {@link parseFramedAstm}, composing the
4130
+ * byte stream in one call: the inverse of {@link parseFramedAstm}, composing the
3700
4131
  * two emit layers at the edge.
3701
4132
  *
3702
4133
  * Each record is serialized to spec-clean, `CR`-terminated wire text (canonical
@@ -3724,15 +4155,15 @@ declare function serializeFramedAstm(input: AstmMessage | readonly AstmRecord[],
3724
4155
  * The ASTM/CLSI-LIS01 modulo-256 frame checksum.
3725
4156
  *
3726
4157
  * The checksum is the sum, modulo 256, of every byte **after** the `STX` up to
3727
- * and **including** the `ETB`/`ETX` terminator — i.e. the frame number, the record
4158
+ * and **including** the `ETB`/`ETX` terminator: i.e. the frame number, the record
3728
4159
  * text, and the terminator byte. It is carried on the wire as **two hexadecimal
3729
4160
  * characters**. This module computes it (`computeChecksum`), formats it for emit
3730
- * (`toChecksumHex` — always **uppercase**), and reads it back leniently
3731
- * (`parseChecksumHex` — **accepts lowercase**), matching the real-vendor quirk that
4161
+ * (`toChecksumHex`, always **uppercase**), and reads it back leniently
4162
+ * (`parseChecksumHex`, **accepts lowercase**), matching the real-vendor quirk that
3732
4163
  * some analyzers emit a lowercase checksum.
3733
4164
  */
3734
4165
  /**
3735
- * Sum bytes `[start, endInclusive]` of `bytes` modulo 256 — the ASTM frame
4166
+ * Sum bytes `[start, endInclusive]` of `bytes` modulo 256: the ASTM frame
3736
4167
  * checksum span (frame number through the terminator, inclusive).
3737
4168
  *
3738
4169
  * @param bytes - The full decoded byte stream.
@@ -3763,7 +4194,7 @@ declare function toChecksumHex(checksum: number): string;
3763
4194
  /**
3764
4195
  * Read the two-hex-char checksum at `bytes[i0]`/`bytes[i1]`, **case-insensitively**
3765
4196
  * (a lowercase checksum is a tolerated real-vendor quirk). Returns `undefined` when
3766
- * either position is out of range or not a hex digit — the caller then treats the
4197
+ * either position is out of range or not a hex digit: the caller then treats the
3767
4198
  * frame as having no readable declared checksum, never as a match.
3768
4199
  *
3769
4200
  * @param bytes - The full decoded byte stream.
@@ -3780,7 +4211,7 @@ declare function toChecksumHex(checksum: number): string;
3780
4211
  declare function parseChecksumHex(bytes: Uint8Array, i0: number, i1: number): number | undefined;
3781
4212
 
3782
4213
  /**
3783
- * The frame codec's thrown error — the `strict`-mode counterpart to its lenient
4214
+ * The frame codec's thrown error: the `strict`-mode counterpart to its lenient
3784
4215
  * warnings.
3785
4216
  *
3786
4217
  * The frame layer is lenient by default (byte-level deviations become
@@ -3815,37 +4246,55 @@ declare class AstmFrameStrictError extends Error {
3815
4246
  }
3816
4247
 
3817
4248
  /**
3818
- * Public entry point for the `@cosyte/astm` package — the ASTM/CLSI-LIS02
4249
+ * Public entry point for the `@cosyte/astm` package: the ASTM/CLSI-LIS02
3819
4250
  * **record** layer.
3820
4251
  *
3821
4252
  * The north star: hand a de-framed ASTM record stream to {@link parseAstmRecords}
3822
- * and pull a result's value, units, and flag out in one line — lenient on parse
3823
- * (vendor quirks become typed warnings, never silent loss), and — the whole point
3824
- * — **never a confident wrong value**. Delimiters are read from each header,
4253
+ * and pull a result's value, units, and flag out in one line. Parsing is lenient
4254
+ * (vendor quirks become typed warnings, never silent loss) and, the whole point,
4255
+ * **never returns a confident wrong value**. Delimiters are read from each header,
3825
4256
  * embedded escapes are decoded before a value is split, the practice- and
3826
4257
  * laboratory-assigned patient IDs stay distinct, and every deviation is a stable,
3827
- * value-free warning. Result flag/status semantics (P2), patient/order identity
3828
- * depth + the `C` comment record + partial-timestamp hardening (P3), and the
4258
+ * value-free warning. Result flag/status semantics, patient/order identity
4259
+ * depth + the `C` comment record + partial-timestamp hardening, and the
3829
4260
  * request-information (`Q`) record + host-query classification + verbatim `M`/`S`
3830
- * records (P4) are all modeled — the **record-content layer is now feature-complete**.
4261
+ * records are all modeled: the **record-content layer is now feature-complete**.
4262
+ *
4263
+ * A parsed model is a whole record **stream**, and a stream may carry several `H` … `L`
4264
+ * messages. {@link messages} splits it into them, so a patient and a result are only ever
4265
+ * paired inside the message that carried both; the flat extractors ({@link patient},
4266
+ * {@link results}, …) read the whole stream and therefore **refuse** one they cannot answer
4267
+ * for, with an {@link AstmAmbiguousStreamError}, rather than answering across patients.
3831
4268
  *
3832
- * The E1381/CLSI-LIS01 **framing** layer (P5) lives alongside the record layer and
4269
+ * The E1381/CLSI-LIS01 **framing** layer lives alongside the record layer and
3833
4270
  * shares nothing but the payload boundary: {@link decodeAstmFrames} decodes a framed
3834
4271
  * byte stream (`<STX> FN text <ETB|ETX> CS <CR><LF>`) into frames + reassembled
3835
- * record bytes — verifying the modulo-256 checksum (a bad frame is surfaced
4272
+ * record bytes: verifying the modulo-256 checksum (a bad frame is surfaced
3836
4273
  * untrusted, never merged), tracking frame-number sequencing (a gap is never
3837
4274
  * silently bridged), and reassembling the 240-byte-limited multi-frame records.
3838
4275
  * {@link parseFramedAstm} composes the two layers at the edge.
3839
4276
  *
3840
- * The LTP **protocol** layer (P6) sits above the frame codec: {@link detectFraming}
3841
- * auto-detects the transport reality — framed (serial / cobas 4800 / Iguana) vs raw
3842
- * (cobas b121, framing dropped) — from the stream's leading byte, and
4277
+ * The LTP **protocol** layer sits above the frame codec: {@link detectFraming}
4278
+ * auto-detects the transport reality from the stream's leading byte, framed
4279
+ * (serial / cobas 4800 / Iguana) vs raw (cobas b121, framing dropped).
3843
4280
  * {@link ltpReduce} is a **pure, socket-free** receiver-side state machine over
3844
4281
  * `ENQ`/`ACK`/`NAK`/`EOT` + frame-received events. The consumer owns the wire; the
3845
4282
  * reducer decides. Its inviolable rule mirrors `mllp`'s ACK-failsafe: a frame the
3846
4283
  * codec did not vouch for is answered with `NAK`, **never** a fabricated positive
3847
- * `ACK`, and never merged into a record — a `NAK` drives retransmit, not acceptance.
3848
- * Serialize/build (P7) is deferred.
4284
+ * `ACK`, and never merged into a record, a `NAK` drives retransmit, not acceptance.
4285
+ *
4286
+ * The **emit** half is the conservative inverse of the record and framing layers.
4287
+ * {@link buildAstmMessage} constructs records from typed caller input, emitting only
4288
+ * the values the caller supplied; {@link serializeAstmRecords} writes them out with the
4289
+ * canonical `H|\^&` delimiters by default, re-escaping every embedded delimiter in a
4290
+ * decoded component leaf (every record type is emitted from its decoded `fields`, except that
4291
+ * `M`/`S` bytes pass through untouched when they already read correctly under the emit set, so
4292
+ * an edit to the model is reflected on emit and the whole stream goes out in the one
4293
+ * delimiter set the header declares); {@link composeAstmFrames} wraps them into frames whose
4294
+ * modulo-256 checksum is
4295
+ * computed rather than accepted; and {@link serializeFramedAstm} composes the last two.
4296
+ * The protocol layer has no emit inverse by design: {@link ltpReduce} returns the actions
4297
+ * to take, and the consumer owns the wire.
3849
4298
  */
3850
4299
  /**
3851
4300
  * Library version string, kept in lockstep with `package.json#version`.
@@ -3863,4 +4312,4 @@ declare class AstmFrameStrictError extends Error {
3863
4312
  */
3864
4313
  declare const VERSION: string;
3865
4314
 
3866
- export { ALL_ASTM_WARNING_CODES, ACK as ASTM_ACK, ENQ as ASTM_ENQ, EOT as ASTM_EOT, NAK as ASTM_NAK, type AbnormalFlag, type AbnormalFlagCode, type AbnormalFlagMeaning, type AnyAstmWarningCode, type AstmDate, type AstmDatePrecision, type AstmField, type AstmFrame, AstmFrameEncodeError, type AstmFramePosition, AstmFrameStrictError, type AstmFrameWarning, type AstmFraming, type AstmLivdWarning, type AstmLtpWarning, type AstmMessage, type AstmMessageClassification, type AstmMessageKind, AstmParseError, type AstmParseOptions, type AstmPosition, type AstmProfile, AstmProfileDefinitionError, type AstmProfileProvenance, type AstmQuirkMatch, type AstmQuirkTolerance, type AstmRecord, type AstmRecordInput, type AstmRecordWarning, AstmSerializeError, AstmStrictError, CANONICAL_DELIMITERS, type CommentInput, type CommentRecord, type ComposeFramesOptions, type DecodeAstmFramesResult, type DefineAstmProfileOptions, type Delimiters, type DetectFramingOptions, type DetectFramingResult, FATAL_CODES, FRAME_WARNING_CODES, type FatalCode, type FrameChecksum, type FrameOptions, type FrameTerminator, type FrameWarningCode, type FramedAstmResult, type HeaderInput, type HeaderRecord, LIVD_WARNING_CODES, LTP_WARNING_CODES, type LivdAnnotation, type LivdCatalog, type LivdEntry, type LivdLookup, type LivdMapping, type LivdResult, type LivdWarningCode, type LtpAction, type LtpEvent, type LtpPhase, type LtpState, type LtpTransition, type LtpWarningCode, type ManufacturerRecord, type MessageInput, type OrderInput, type OrderRecord, type PatientInput, type PatientName, type PatientNameInput, type PatientRecord, type QueryInput, type QueryRecord, type ReferenceRange, type ReferenceRangeKind, type ResultInput, type ResultRecord, type ResultStatus, type ResultStatusCode, type ResultStatusMeaning, SAFETY_CRITICAL_CODES, type ScientificRecord, TOLERABLE_CODES, type TerminatorRecord, type UniversalTestId, type UniversalTestIdProvenance, type UnknownEscapeSink, type UnsupportedRecord, VERSION, type VerbatimInput, WARNING_CODES, type WarningCode, ambiguousMessageKind, ambiguousValueSplit, applyAstmProfile, applyAstmProfileToWarnings, applyLivd, astmDateToLocalISO, astmProfiles, attachComments, buildAstmMessage, classifyMessage, comments, commentsFor, composeAstmFrames, computeChecksum, decodeAstmFrames, decodeEscapes, deepFreeze, defineAstmProfile, defineLivdCatalog, detectFraming, encodeComponent, fieldScalar, frameBadChecksum, frameOversize, frameSequenceGap, frameUnterminated, getAstmProfile, getDefaultAstmProfile, interpretAbnormalFlag, interpretResultStatus, isNonStandard, isSafetyCriticalCode, listAstmProfiles, livdAmbiguousMapping, livdUnmappedCode, lookupLivdForRecord, ltpAmbiguousTransport, ltpFrameRejected, ltpInitialState, ltpReduce, ltpUnexpectedEvent, nonStandardDelimiters, orders, orphanComment, parseAstmDate, parseAstmRecords, parseChecksumHex, parseFramedAstm, parseReferenceRange, partialTimestamp, patient, primaryCode, profileQuirkApplied, query, readDelimiters, recognizeUniversalTestId, resolveProfileTransport, results, serializeAstmRecord, serializeAstmRecords, serializeField, serializeFramedAstm, setDefaultAstmProfile, splitEscapeAware, toChecksumHex, tokenizeRecord, undefinedAbnormalFlag, undefinedResultStatus, uninterpretedQueryStatus, unitsAbsent, unknownEscapeSequence, unknownRecordType, unparseableReferenceRange };
4315
+ export { ALL_ASTM_WARNING_CODES, AMBIGUOUS_CODES, ACK as ASTM_ACK, ENQ as ASTM_ENQ, EOT as ASTM_EOT, NAK as ASTM_NAK, type AbnormalFlag, type AbnormalFlagCode, type AbnormalFlagMeaning, type AmbiguousCode, type AnyAstmWarningCode, AstmAmbiguousStreamError, type AstmDate, type AstmDatePrecision, type AstmField, type AstmFrame, AstmFrameEncodeError, type AstmFramePosition, AstmFrameStrictError, type AstmFrameWarning, type AstmFraming, type AstmLivdWarning, type AstmLtpWarning, type AstmMessage, type AstmMessageClassification, type AstmMessageKind, AstmParseError, type AstmParseOptions, type AstmPosition, type AstmProfile, AstmProfileDefinitionError, type AstmProfileProvenance, type AstmQuirkMatch, type AstmQuirkTolerance, type AstmRecord, type AstmRecordInput, type AstmRecordWarning, AstmSerializeError, type AstmSerializeErrorCode, type AstmStreamMessage, AstmStrictError, CANONICAL_DELIMITERS, type CommentInput, type CommentRecord, type ComposeFramesOptions, type DecodeAstmFramesResult, type DefineAstmProfileOptions, type Delimiters, type DetectFramingOptions, type DetectFramingResult, FATAL_CODES, FRAME_WARNING_CODES, type FatalCode, type FrameChecksum, type FrameOptions, type FrameTerminator, type FrameWarningCode, type FramedAstmResult, type HeaderInput, type HeaderRecord, LIVD_WARNING_CODES, LTP_WARNING_CODES, type LivdAnnotation, type LivdCatalog, type LivdEntry, type LivdLookup, type LivdMapping, type LivdResult, type LivdWarningCode, type LtpAction, type LtpEvent, type LtpPhase, type LtpState, type LtpTransition, type LtpWarningCode, type ManufacturerRecord, type MessageInput, type OrderInput, type OrderRecord, type PatientInput, type PatientName, type PatientNameInput, type PatientRecord, type QueryInput, type QueryRecord, type ReferenceRange, type ReferenceRangeKind, type ResultInput, type ResultRecord, type ResultStatus, type ResultStatusCode, type ResultStatusMeaning, SAFETY_CRITICAL_CODES, type ScientificRecord, TOLERABLE_CODES, type TerminatorRecord, type UniversalTestId, type UniversalTestIdProvenance, type UnknownEscapeSink, type UnsupportedRecord, VERSION, type VerbatimInput, WARNING_CODES, type WarningCode, ambiguousMessageKind, ambiguousValueSplit, applyAstmProfile, applyAstmProfileToWarnings, applyLivd, astmDateToLocalISO, astmProfiles, attachComments, buildAstmMessage, classifyMessage, comments, commentsFor, composeAstmFrames, computeChecksum, decodeAstmFrames, decodeEscapes, deepFreeze, defineAstmProfile, defineLivdCatalog, delimitersRedeclared, detectFraming, encodeComponent, fieldScalar, frameBadChecksum, frameOversize, frameSequenceGap, frameUnterminated, getAstmProfile, getDefaultAstmProfile, interpretAbnormalFlag, interpretResultStatus, isNonStandard, isSafetyCriticalCode, listAstmProfiles, livdAmbiguousMapping, livdUnmappedCode, lookupLivdForRecord, ltpAmbiguousTransport, ltpFrameRejected, ltpInitialState, ltpReduce, ltpUnexpectedEvent, messages, nonStandardDelimiters, orders, orphanComment, parseAstmDate, parseAstmRecords, parseChecksumHex, parseFramedAstm, parseReferenceRange, partialTimestamp, patient, primaryCode, profileQuirkApplied, query, readDelimiters, recognizeUniversalTestId, resolveProfileTransport, results, serializeAstmRecord, serializeAstmRecords, serializeField, serializeFramedAstm, setDefaultAstmProfile, splitEscapeAware, toChecksumHex, tokenizeHeader, tokenizeRecord, undefinedAbnormalFlag, undefinedResultStatus, uninterpretedQueryStatus, unitsAbsent, unknownEscapeSequence, unknownRecordType, unparseableReferenceRange, unreadableRedeclaration };