@cosyte/mllp 0.0.6 → 0.0.7

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
@@ -76,6 +76,76 @@ begins its public history at `0.0.x`, per the cosyte version ladder (`0.0.x` unt
76
76
 
77
77
  ### Security
78
78
 
79
+ - **The PHI scanner refuses a non-regular entry under a scan root, on both routes
80
+ (`PHI-SCAN-SYMLINK-BLIND-ON-BOTH-ROUTES`).** A symbolic link under `test/` or `src/` read CLEAN on
81
+ **both** enumerating routes, so a link pointing at a PHI-bearing file passed the gate twice over.
82
+ Reproduced on `d854e81` with a synthetic name-bearing payload kept outside both walk roots: a link
83
+ to it under `test/` gave `all` mode exit 0 "OK, no hits"; a link to its **directory** did the same
84
+ and took the whole subtree with it; `--staged`, which is this repo's pre-commit gate, exited 0
85
+ after `git add`; and naming the target explicitly exited 1 with every hit. The payload was always
86
+ detectable, the two routes never looked at it.
87
+
88
+ Two mechanisms, two fixes. `walk()` enumerates `Dirent.isFile()`, an **lstat** answer, so a link is
89
+ neither a file nor a directory and fell out of the loop; `isDirectory()` is an lstat answer too,
90
+ which is how a linked directory vanished wholesale. `--staged` read content with
91
+ `git show :<path>`, and git stores a symbolic link as its **target path** under mode `120000`, so
92
+ it scanned the path text.
93
+
94
+ **Neither route follows a link it finds INSIDE a scan root.** Following would read bytes the
95
+ enumeration does not control (outside the repo, a loop, a device, a FIFO that blocks the gate
96
+ forever), and git does not carry those bytes anyway, so a hit on them would be a claim about
97
+ something no commit contains. The enumeration is narrowed instead: a non-regular in-scope entry
98
+ **refuses the scan** (exit 2, the existing "could not complete" code), naming **every** offender.
99
+ `--staged` now reads `git diff --cached --raw -z` so the destination mode is visible. The scan
100
+ roots' own names are matched as well as the prefix, because an entry named exactly `test` or `src`
101
+ does not sit inside a root, it **replaces** one, and a prefix test alone let a staged mode-`120000`
102
+ blob named `test` through at exit 0.
103
+
104
+ **The root itself is the one thing still followed, and a flat "neither route follows a link" would
105
+ be false.** `walk()` opens `test/` and `src/` with `existsSync` + `readdirSync`, and both follow,
106
+ so replacing a walk root with a link to a directory outside the repo makes the walk read straight
107
+ through it. Pre-existing, and the precise reading is **not** "its PHI is reported": the tree beyond
108
+ the link is scanned exactly as the root it replaced would have been, **with that root's own
109
+ limits**. A fixture-like payload behind a linked `test/` is reported (measured, exit 1); the same
110
+ payload behind a linked `src/` gets only the conservative pass and can read clean, exactly as it
111
+ would through a real `src/`. It is link-**neutral**, which is why it is disclosed rather than
112
+ closed: refusing a linked root is a decision about repo layout, not this defect.
113
+
114
+ **`--diff-filter` admits `T` (typechange), and leaving it out would have made the mode check
115
+ unreachable whenever the file being replaced was already tracked.** Replacing a **tracked** regular
116
+ file with a link is neither an add nor a modify; git raises it as `T`, so `--diff-filter=AM` deleted
117
+ the record before any mode could be read and the hook passed a mode-`120000` blob green. Measured on
118
+ git 2.39.5 against a tracked `test/differential/fixtures/*.frame.bin`: with `AM` the raw output for
119
+ that stage is **empty**. Admitting `T` also scans the reverse typechange, a link replaced by a real
120
+ file bearing PHI. Both premises are asserted in the tests rather than trusted.
121
+
122
+ A refusal names the entry's own repo-relative path and an engine-owned kind token, **never the link
123
+ target**, which is working-tree text that can itself carry PHI. Pinned with a synthetic name-bearing
124
+ payload whose target **filename** also carries a name, so the never-echo assertion is not a claim
125
+ about an empty string. 10 of the 14 new cases are red on `d854e81`; the other four are boundary
126
+ controls that must stay green (a gitignored entry stays exempt, an entry outside both roots is not
127
+ refused, the committed corpus still passes, a staged regular file still scans).
128
+
129
+ **Scope, stated rather than left to be read charitably.** "In scope" is each route's own existing
130
+ **root**; the `.ts` / `.md` **name** exemptions deliberately do not carry over to a non-regular
131
+ entry, because they are judgements about a file whose bytes the route could have read. `R`/`C`
132
+ rename and copy are still not enumerated by `--staged` at all: **pre-existing** (`AM` excluded them
133
+ too), disclosed rather than fixed. Explicit-path mode still reads **through** a link and is
134
+ unchanged. The gitlink (mode `160000`) arm is **not** a hole this closes: `--staged`'s scope already
135
+ reached a staged submodule under both roots and `git show` on one fails with `bad object`, so the
136
+ base commit already refused it, and what changed is that the diagnostic now says what the entry is.
137
+ The refuse-a-sweep-that-observed-nothing rule and the vanished-transient tolerance are untouched:
138
+ a non-regular entry that goes away mid-sweep therefore still **refuses**, because that tolerance is
139
+ scoped to the read and these entries are refused at enumeration. That asymmetry is deliberate in
140
+ direction, fail-closed being the correct way for a PHI gate to be wrong, and it is not reachable
141
+ here: nothing in this repo creates a non-regular entry under a scan root, and every one the tests
142
+ create lives in a throwaway repo under `tmpdir()`.
143
+
144
+ **This corrects a claim made under this same heading by the previous scanner change**, which called
145
+ the symlink gap "bounded, because git cannot commit content through a symlink and `--staged` reads
146
+ the index". The first half is true and the conclusion does not follow: the gate's promise is that it
147
+ observed what is under its roots, and both routes reported clean over an entry neither had read.
148
+
79
149
  - **BREAKING: ACK-correlation diagnostics no longer carry the control ID (`PHI-WARNING-MESSAGE-LEAK`).**
80
150
  The opt-in `correlateByControlId` path built its diagnostics by interpolation, and both ends were
81
151
  consumer-controlled and unbounded. The unmatched-ACK path read the **peer's** MSA-2 straight off an
@@ -185,6 +255,61 @@ begins its public history at `0.0.x`, per the cosyte version ladder (`0.0.x` unt
185
255
 
186
256
  ### Fixed
187
257
 
258
+ - **The `attw` publish gate no longer passes on a tarball that carries no types.** `pnpm attw` was
259
+ `attw --pack . --profile node16`, and `@arethetypeswrong/cli@0.18.4`'s `getExitCode.js` opens with
260
+ `if (!analysis.types) return 0`, so the problem list is never read and no `--profile`,
261
+ `--ignore-rules` or config setting reaches that early return. For a package that ships types, "does
262
+ not contain types" means the declarations were not in the tarball, which is a broken publish
263
+ reported as a pass. A false red costs an hour; a false green merges.
264
+ `ATTW-FALSE-GREEN-PORT`, porting the remedy that shipped in `terminology#28` (`bf153cb`).
265
+
266
+ **Measured on this package at `0.0.6` with zero concurrency**, under the old invocation:
267
+ `rm -rf dist && pnpm attw` printed "This package does not contain types." and exited **0**.
268
+ Concurrency supplies only the condition, so the remedy is not a lock, a lease or a build queue: the
269
+ gate has to be able to say its own inputs were missing, whatever removed them.
270
+
271
+ **The exit-0 path needs the tarball to carry no declaration at all**, which is a boundary specific
272
+ to how this package is built and is stated here because it is easy to get wrong. `tsup` emits a
273
+ shared type chunk (`dist/index-<hash>.d.ts`) beside the three entry declarations, so deleting only
274
+ the six entry `.d.ts`/`.d.cts` files leaves that chunk in the tarball and `attw` reds honestly
275
+ (`❌ No types`, UntypedResolution, exit 1); the same removal **plus** `dist/index-*.d.*ts` is what
276
+ exits 0. The build window lands in the exit-0 state regardless, because `tsup` writes JS in one
277
+ pass and every declaration in a later one, so no moment exists where only some are present: polling
278
+ two real clean builds here for `dist/index.mjs` and then the first declaration of any kind gave
279
+ windows of **4.25s and 3.31s**, holding JS and zero declarations throughout. The absolute timings
280
+ move run to run, so no single figure is quoted as "the" window; the stable claim is that the gap is
281
+ seconds rather than milliseconds, which is wide enough for a concurrent build or `pnpm clean` to
282
+ land `attw` in it.
283
+
284
+ `pnpm attw` is now `node scripts/attw.mjs --profile node16`. The wrapper hardcodes `--pack .` and
285
+ forwards the rest, so **`--profile node16` is preserved**, not dropped. Two nets, which catch
286
+ different things: a **preflight** that every relative path `package.json` promises (`main`,
287
+ `module`, `types`, `typings`, and every string leaf of `exports`) exists and is non-empty, which
288
+ catches the build window and names the missing file; and a **post-check** that promotes `attw`'s
289
+ untyped sentence to a failure, which catches declarations present on disk but excluded from the
290
+ tarball by `files`/`.npmignore` (no instance of that is on record here). This package declares
291
+ three subpaths, so the preflight has **twelve** artifact paths to check. Because the post-check
292
+ reads a string, the options that would hide it are refused by name and wholesale rather than by
293
+ value: `--quiet`/`-q`, `--format`/`-f`, `--config-path`, and a `.attw.json` setting `quiet` or
294
+ `format`. The first three of those were re-measured here, on this package's own untyped pack and
295
+ under its own `--profile node16`, each handing back exit 0 with the sentence unreadable;
296
+ `--config-path` is refused by inference, not measurement.
297
+
298
+ **Disclosed gap:** the preflight cannot see that shared type chunk go missing, because
299
+ `package.json` names it nowhere. It is left to the post-check and to `attw` itself rather than
300
+ papered over with a glob. For the same reason the preflight's failure message does not promise
301
+ which way `attw` would have gone, since it cannot tell the two states apart.
302
+
303
+ `test/scripts/attw-gate.test.ts` pins both nets against the real binary, including **the upstream
304
+ exit 0 itself** (so an `attw` upgrade that rewords the sentence or fixes the exit code reds the
305
+ suite rather than letting the net go slack), that the preflight reaches a **subpath's `require`
306
+ branch** and not just the root entry, **the boundary above** (a tarball keeping a shared chunk reds
307
+ on `attw`'s own account, so the failure message must not claim the exit-0 counterfactual), that
308
+ **`--profile node16` survives the wrapper** (the fixture is red under the default profile and green
309
+ under `node16`), a **negative control** on a well-formed package, and that a **real `attw` failure
310
+ still fails** with `attw`'s own exit status. Build and
311
+ packaging only: no runtime, framing, transport, or public-API change.
312
+
188
313
  - **The PHI scanner no longer refuses an entire sweep because a transient file went away while it
189
314
  was reading.** `all` mode lists `test/` + `src/`, then reads each file; anything created and
190
315
  removed inside that window made a read throw `ENOENT` and took the whole scan down with it
@@ -261,8 +386,11 @@ begins its public history at `0.0.x`, per the cosyte version ladder (`0.0.x` unt
261
386
 
262
387
  **Two scope notes surfaced by the same review, one pre-existing and one introduced here.**
263
388
  PRE-EXISTING: `walk()` tests `e.isFile()`, which is false for a symlink Dirent, so a symlinked
264
- file under `test/` or `src/` is never swept in `all` mode; it is bounded, because git cannot
265
- commit content through a symlink and `--staged` reads the index. INTRODUCED by this change: a
389
+ file under `test/` or `src/` is never swept in `all` mode. **The "bounded" reading recorded here
390
+ was wrong and is corrected under Security above (`PHI-SCAN-SYMLINK-BLIND-ON-BOTH-ROUTES`): git
391
+ not committing content through a link does not make a route that reports clean over an unread
392
+ entry safe, and `--staged` reading the index is exactly how the link's target path text got
393
+ scanned in place of any content.** INTRODUCED by this change: a
266
394
  tolerated skip exits **0** with only a stderr line, including in CI, where the tree is at rest and
267
395
  a skip is therefore anomalous by definition. On the base commit that same input exited 2. Both are
268
396
  stated rather than changed: the first is not this defect, and the second **is** the shipped design
package/dist/index.cjs CHANGED
@@ -3438,7 +3438,7 @@ async function createStarterClient(opts) {
3438
3438
  }
3439
3439
 
3440
3440
  // src/index.ts
3441
- var VERSION = "0.0.6";
3441
+ var VERSION = "0.0.7";
3442
3442
 
3443
3443
  exports.Connection = Connection;
3444
3444
  exports.FrameReader = FrameReader;