@cosyte/astm 0.0.14 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -11,6 +11,54 @@ this file is maintained by hand (Changesets handles the version bump and publish
11
11
 
12
12
  ### Fixed
13
13
 
14
+ - **The escape encoder protected the escape character it introduced but not the `E`/`F`/`S`/`R`
15
+ mnemonics, so a delimiter set naming one of those letters in another role altered the values it
16
+ emitted** (`ASTM-FRAME-RESIDUALS` follow-up, defect 12). `PRE-EXISTING`, byte-identical on
17
+ `4bb62f1`. `encodeLeaf` ran as four chained whole-string substitutions (escape, then field,
18
+ component, repeat), and each pass could see what the previous ones had written. Going first
19
+ protected the escape character; nothing protected the mnemonic letters. Under
20
+ `{ field: "E", escape: "R" }` a leaf of `R` went out as `RRFRR` and read back as `RER`.
21
+
22
+ **Re-measured here rather than carried over, because the recorded figures came from a sweep whose
23
+ parameters were not written down.** Over the whole four-role space drawn from the 18-character
24
+ alphabet `|` `\` `^` `&` `E` `F` `S` `R` `*` `~` `:` `#` `+` `@` `!` `$` `%` `/` (the four
25
+ canonical delimiters, the four mnemonic letters, a common vendor set, neutral punctuation), that is
26
+ P(18,4) = 73,440 sets, against **exactly the `STREAM` constant in
27
+ `test/records/escape-mnemonic-roles.test.ts`**, a synthetic ten-record
28
+ `H`/`P`/`O`/`R`/`R`/`R`/`C`/`M`/`S`/`L` message that parses with `warnings: []`, the serializer
29
+ refused 23,040 and accepted **50,400**. Of those, **10,450** emitted
30
+ a stream this library re-read with a **different field tree**, and **9,287** reported nothing
31
+ outside `TOLERABLE_CODES`, so a gate-legal profile plus `{ strict: true }` accepted an altered
32
+ value.
33
+
34
+ **The "0 silent" reading is confirmed and is weaker than it looks, which is worth more than the
35
+ number.** Zero of the 10,450 re-parsed with `warnings: []`, but a non-canonical set always reports
36
+ `ASTM_NONSTANDARD_DELIMITERS`, and that code is itself tolerable, so `warnings: []` was never
37
+ reachable for any set that could exhibit this defect. Silence was not available as a measure here.
38
+ The tier that does discriminate is the strict-accepted one above.
39
+
40
+ **The fix is one left-to-right pass.** Each character is read once and written either as itself or
41
+ as a whole `escape` + mnemonic + `escape` triple, and nothing the encoder writes is examined again,
42
+ which makes it the **exact** inverse of `decodeEscapes` rather than approximately so. No accepted
43
+ set in that sweep diverges after it. Bytes change for **exactly** the 10,450 sets that were
44
+ diverging and for no others (measured both directions: 0 sets diverged with unchanged bytes, 0 sets
45
+ changed bytes without having diverged), so a canonical stream and any set naming none of the four
46
+ mnemonic letters is byte-identical. The accept/refuse partition is unchanged over all 73,440 sets:
47
+ `ASTM_EMIT_TYPE_LETTER_COLLISION` reads the first character actually written, so it needed no
48
+ change when the encoder underneath it did, which is the argument for testing bytes rather than
49
+ enumerating dangerous roles. **No clause is claimed**: the grounding is this package's own decoder.
50
+
51
+ Pinned in `test/records/escape-mnemonic-roles.test.ts`, which transcribes the four chained
52
+ substitutions (nothing shipped still reproduces them) and **checks that transcription against the
53
+ shipped encoder on all 1,680 sets where the two must agree**, so it cannot decay into a strawman.
54
+ 8 of its assertions are red against a `git archive` of `4bb62f1`. Five of them pin the **emitted bytes as literals**, each derived in a comment rather than snapshotted, because every other assertion here reads the stream back through this package's own parser and so cannot see an encoder and decoder that agree with each other and are both wrong. The committed test pins a
55
+ 12-character subspace (P(12,4) = 11,880) rather than the 18, because the full space costs about a
56
+ minute of suite time and the extra six characters are neutral punctuation already represented. The
57
+ corpus is the same in both: the 73,440-set figures above differ from the test's only by alphabet. The claim in
58
+ `test/records/type-letter-collision.test.ts` that a type letter equal to the escape character
59
+ "does not always encode as letter + E + letter" was pinned to `RRFRR`/`RRSRR` and is now false:
60
+ it encodes that way for every set, and the assertion was updated to say so rather than deleted.
61
+
14
62
  - **`composeAstmFrames` wrote an out-of-range `startFrameNumber` straight into the frame-number
15
63
  position, so a value that was not a frame number produced a stream whose records the decoder
16
64
  refused to emit** (`ASTM-FRAME-RESIDUALS`, defect 13). `PRE-EXISTING`, byte-identical on `64c2fd5`.
@@ -392,6 +440,34 @@ this file is maintained by hand (Changesets handles the version bump and publish
392
440
  that only ever fails is not a gate and one that swallows the status is not one either. **No
393
441
  runtime code changed and the built output is identical.**
394
442
 
443
+ ### Documented
444
+
445
+ - **A header delimiter-declaration surplus loses all 31 of the C0/`DEL` characters that can reach
446
+ it, and the published surfaces stated the preservation rule without that exception.**
447
+ `PRE-EXISTING`; the
448
+ behaviour is unchanged and argued at `declarationResidual`. What changed is that it is measured,
449
+ pinned and stated, and that **one of the recorded reasons for it is now known to be false**:
450
+ "carrying the byte through would turn a spec-clean header into a stream the frame layer refuses"
451
+ holds for `STX`, `ETX` and `ETB` only. Measured, the other **28 of the 31** round-trip byte-exactly
452
+ through `composeAstmFrames` into `decodeAstmFrames` with `warnings: []`, and all 31 round-trip at
453
+ the record layer. The disposition still stands, on the reason the code site actually gives: rather
454
+ than enumerate the bytes each layer happens to reserve and re-derive that list every time a layer
455
+ is added, no control character is carried at all. Do not restate the frame-layer reason. Probing one character at a time in the surplus of an otherwise spec-clean
456
+ `H|\^&` header: **31 of the 33** C0/`DEL` characters reach the rule and are dropped, each with
457
+ `warnings: []` and no error; the other two are `CR` and `LF`, which end the record while it is being
458
+ read, so no surplus ever holds them. The drop is **all-or-nothing**, so a surplus of `#` + `US` + `$`
459
+ loses the `#` and the `$` as well, and that is deliberate: a subsequence of an opaque run whose
460
+ meaning is unresolved is a different run, not a shorter version of the same one, and choosing which
461
+ characters to keep is the guess this library declines everywhere else. `docs-content/quickstart.md`
462
+ attached the control-character drop to the transcoding case alone, which is wrong: it fires on the
463
+ **default canonical path** too. The `serializeAstmRecords` and `serializeAstmRecord` doc comments
464
+ and this module's own doc block, all of which compile into the published declarations, stated the
465
+ preservation rule unqualified. All are
466
+ widened rather than rewritten, and `docs-content/limitations.md`, which already disclosed the drop,
467
+ gains the all-or-nothing shape and the count. Pinned in
468
+ `test/records/declaration-surplus-residual.test.ts`, which passes on `4bb62f1` by design: it
469
+ measures behaviour this entry does not change.
470
+
395
471
  ### Added
396
472
 
397
473
  - **`ASTM_RECORD_FIELDS_UNSEPARATED`: a record that lost its fields no longer loses them in
package/dist/index.cjs CHANGED
@@ -1435,6 +1435,13 @@ function encodeComponent(leaf, d, recordIndex) {
1435
1435
  assertEmittableDelimiters(d, recordIndex);
1436
1436
  return encodeLeaf(leaf, d, recordIndex);
1437
1437
  }
1438
+ function escapeMnemonic(ch, d) {
1439
+ if (ch === d.escape) return "E";
1440
+ if (ch === d.field) return "F";
1441
+ if (ch === d.component) return "S";
1442
+ if (ch === d.repeat) return "R";
1443
+ return void 0;
1444
+ }
1438
1445
  function encodeLeaf(leaf, d, recordIndex) {
1439
1446
  if (leaf.includes("\r") || leaf.includes("\n")) {
1440
1447
  throw new AstmSerializeError(
@@ -1442,7 +1449,13 @@ function encodeLeaf(leaf, d, recordIndex) {
1442
1449
  recordIndex
1443
1450
  );
1444
1451
  }
1445
- return leaf.split(d.escape).join(d.escape + "E" + d.escape).split(d.field).join(d.escape + "F" + d.escape).split(d.component).join(d.escape + "S" + d.escape).split(d.repeat).join(d.escape + "R" + d.escape);
1452
+ let out = "";
1453
+ for (let i = 0; i < leaf.length; i += 1) {
1454
+ const ch = leaf.charAt(i);
1455
+ const mnemonic = escapeMnemonic(ch, d);
1456
+ out += mnemonic === void 0 ? ch : d.escape + mnemonic + d.escape;
1457
+ }
1458
+ return out;
1446
1459
  }
1447
1460
  function encodeField(repeats, d, recordIndex) {
1448
1461
  return repeats.map((rep) => rep.map((c) => encodeLeaf(c, d, recordIndex)).join(d.component)).join(d.repeat);
@@ -2358,7 +2371,7 @@ function concatBytes3(chunks) {
2358
2371
  }
2359
2372
 
2360
2373
  // src/index.ts
2361
- var VERSION = "0.0.14";
2374
+ var VERSION = "0.0.15";
2362
2375
 
2363
2376
  exports.ALL_ASTM_WARNING_CODES = ALL_ASTM_WARNING_CODES;
2364
2377
  exports.AMBIGUOUS_CODES = AMBIGUOUS_CODES;