@cosyte/mllp 0.0.5 → 0.0.6

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
@@ -185,6 +185,89 @@ begins its public history at `0.0.x`, per the cosyte version ladder (`0.0.x` unt
185
185
 
186
186
  ### Fixed
187
187
 
188
+ - **The PHI scanner no longer refuses an entire sweep because a transient file went away while it
189
+ was reading.** `all` mode lists `test/` + `src/`, then reads each file; anything created and
190
+ removed inside that window made a read throw `ENOENT` and took the whole scan down with it
191
+ (exit 2, the invocation-error code). The refusal was never wrong, the **enumeration** was, so the
192
+ enumeration is what changed. `PHI-SCAN-ENUMERATE-THEN-READ-CLASS`, porting the remedy that
193
+ shipped in `ccda#80`.
194
+
195
+ **This repo is genuinely reachable, and that was measured rather than read off the code.** With a
196
+ `git` shim first on `PATH` (the scanner runs `git` between the walk and the first read, so the
197
+ shim is a deterministic hook into exactly that gap), a `test/phi-scan-cap-*/capture.txt` removed
198
+ in the window made `pnpm phi-scan` on this checkout exit 2. Those directories are not
199
+ hypothetical: `test/scripts/phi-scan.test.ts` `mkdtemp`s two of them inside `test/`, which IS a
200
+ walk root, and polling the tree during a suite run measured each existing for about **510 ms**
201
+ (about 1.03 s of exposure per `pnpm test`). The siblings are spared only because their walk roots
202
+ are not the repo root, which is where a build transient lands. **The exposure is real but not
203
+ self-triggering:** nothing in this repo runs an `all`-mode sweep beside the suite (CI runs
204
+ `pnpm phi-scan` as a step strictly before `pnpm test` in one sequential job, the pre-commit hook
205
+ runs `--staged` and reads blobs from the index, and inside the test file the sweep is ordered
206
+ before both capture tests), so what is live is a hand-run or agent-run sweep during a test run.
207
+ Racing 88 sweeps against 6 suite runs produced 0 refusals, which is consistent with the measured
208
+ duty cycle: walk 2-4 ms, `git check-ignore` 6-9 ms, reads 11-19 ms, inside a ~4 s process.
209
+
210
+ **Exactly one case is now tolerated:** a file the walk enumerated **itself**, that **git does not
211
+ track**, failing with **`ENOENT`**. It is reported on stderr as skipped, naming the path, and is
212
+ never silent. **Still refusing:** a tracked file that cannot be read, any non-`ENOENT` failure, a
213
+ tolerated file back on disk at sweep end, a `git` that cannot report the tracked set, and an
214
+ **empty** tracked set (a removed `.git/index` exits 0 with no output, which would make every file
215
+ untracked and quietly delete the tracked-file bound; a corrupt one exits 128 and was already
216
+ caught). `all` mode additionally refuses when it observed **no** files, so the tolerance can never
217
+ decay into a clean report of nothing.
218
+
219
+ **Which bounds are pinned, and which are only disclosed.** Pinned by eight tests in
220
+ `test/scripts/phi-scan.test.ts`, each against a throwaway tree under `tmpdir()` (seven of them a
221
+ git repo, one deliberately not) and **five** of them driven by the `git`-shim technique, with no
222
+ sleep and no real build: the tolerance itself; the tracked-file bound; the `ENOENT`-only bound
223
+ (`EISDIR` via a decoy replaced by a directory); git unable to answer; the empty tracked set;
224
+ `observed no files`; a violator in an untracked file that does not vanish; and a violator inside a
225
+ transient that is still there when the read arrives. Each bound was **mutation-tested**: widening
226
+ `tolerateVanish` to `true` fails three of them, dropping the `ENOENT` check fails one, dropping
227
+ the `size > 0` guard fails one, and dropping the `observed === 0` refusal fails one. **Not pinned,
228
+ and measured as not pinned:** the back-on-disk re-check. Stubbing it out leaves all 40 tests
229
+ green. Reaching it needs a timed re-create against a deliberately slowed sweep, and a
230
+ load-sensitive sleep guarding a load-dependent race is the failure mode this defect itself
231
+ teaches; losing that branch loses the re-check, never the tolerance's bounds. **The gate drove
232
+ that branch by hand** (shim anchored on the second git call, `git ls-files`, against a 20,000-file
233
+ read loop with a tuned re-create delay) and confirmed the code right at all three delays, so the
234
+ branch is measured-correct and unpinned rather than unknown.
235
+
236
+ **Two residuals carried, not closed.** The post-sweep re-check is keyed on the enumerated PATH,
237
+ not on content, so an untracked file RENAMED inside the window goes unscanned under a clean
238
+ report; it is bounded (committing the file means `git add`, after which it is tracked and
239
+ untolerable) and closing it needs a content-addressed sweep, a different design rather than a
240
+ wider bound. And `walk()`'s own `existsSync` -> `readdirSync` race, one phase earlier, is
241
+ untouched: a directory removed in that window throws a plain `SystemError` `main()` does not
242
+ convert, so Node exits **1**, the code the contract reserves for "hits found". It matters more
243
+ here than in the siblings because this repo's transient is a **directory**, removed wholesale, and
244
+ it is unpinnable by the same technique because nothing runs before the walk.
245
+
246
+ **The suite keeps writing inside the walk root, deliberately.** The two capture tests exist to
247
+ prove that a file whose repo-relative path starts with `test/` earns the structured scan, so the
248
+ path is the fixture and moving them would delete what they test.
249
+
250
+ **Three things the gate checked hardest and could not break, recorded so they are not re-derived.**
251
+ The tracked-set spelling comparison holds: instrumented against this checkout, **zero** files come
252
+ out `tolerateVanish: true`, so the tolerance is inert on a clean tree, and a purpose-built repo of
253
+ 15 adversarial tracked filenames (space, tab, newline, quote, backslash, `$`, `*`, `;`, non-ASCII,
254
+ CJK, emoji, plus an invalid-UTF-8 name) never marked a tracked file untracked, because `-z`
255
+ suppresses `core.quotePath` on both sides. The tolerance cannot reach `paths` or `staged` mode,
256
+ and `--allow-fixture` forces `mode = "paths"`, so an allow-list subtraction can never be used to
257
+ drive `all` mode to zero observed files. And in `all` mode `report()` is unreachable while
258
+ `observed === 0`, while in every mode a sweep that observed nothing has no hits to publish.
259
+ (`staged` legitimately reaches `report()` at `observed === 0` and prints `OK, no hits` when a
260
+ commit stages nothing in scope, which is why the refusal is scoped to `all`.)
261
+
262
+ **Two scope notes surfaced by the same review, one pre-existing and one introduced here.**
263
+ 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
266
+ tolerated skip exits **0** with only a stderr line, including in CI, where the tree is at rest and
267
+ a skip is therefore anomalous by definition. On the base commit that same input exited 2. Both are
268
+ stated rather than changed: the first is not this defect, and the second **is** the shipped design
269
+ under review.
270
+
188
271
  - **Three doc comments that had outlived the code they described**, all of which shipped into the
189
272
  published type declarations. The `'reconnecting'` payload was documented as carrying
190
273
  `connectionId` only, while `MllpClient` populates `attempt` and `delayMs` when it schedules a
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.5";
3441
+ var VERSION = "0.0.6";
3442
3442
 
3443
3443
  exports.Connection = Connection;
3444
3444
  exports.FrameReader = FrameReader;