@cosyte/hl7 0.0.6 → 0.0.8

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
@@ -536,6 +536,16 @@ kind, value)`** sets one at a field or `field[rep]` dot-path. Component values a
536
536
 
537
537
  Neither limit is changed here, and widening the walk to close either is a separate slice.
538
538
 
539
+ **CLOSED for the first bullet by the "not a regular file" entry below; the second bullet still
540
+ stands.** The paragraphs above are left exactly as they shipped rather than rewritten, because
541
+ they described a real state the package passed through and they shipped inside a published
542
+ tarball, so they are text a consumer already has on disk. What is no longer true of the current
543
+ code is the whole of the first bullet, in three places rather than one: a symbolic link under a
544
+ walk root, or staged into `test/fixtures/**` or `src/**.ts`, now **refuses the scan** on both
545
+ routes instead of reading clean on both; **a FIFO refuses too** rather than being skipped; and the
546
+ shape pass no longer runs on a link's target _path_, because the entry is refused before anything
547
+ is read. The second bullet, working-tree-only, still stands exactly as written.
548
+
539
549
  - **`README.md` now opens with the shared Cosyte lockup in a `<picture>` block, replacing the
540
550
  per-package banner (`1aee04b`).** The dark-ground org tile sits behind a
541
551
  `prefers-color-scheme: dark` media query with the light-ground tile as the inner `<img>`, so on a
@@ -595,6 +605,219 @@ kind, value)`** sets one at a field or `field[rep]` dot-path. Component values a
595
605
 
596
606
  ### Fixed
597
607
 
608
+ - **A staged RENAME bypassed the PHI scan's pre-commit route entirely
609
+ (`PHI-SCAN-RENAME-BLIND-AT-PRECOMMIT`).** `git diff --cached --raw --diff-filter=AMT` returns
610
+ neither `R` (rename) nor `C` (copy), and rename detection is on by git's default, so
611
+ `git mv <link> test/fixtures/<name>` staged as `:120000 120000 <sha> <sha> R100` with **two**
612
+ paths and the filter deleted the record outright. **Reproduced on `ae4a35f` before the fix**, in
613
+ a throwaway repo: an ordinary `git mv` put a mode-`120000` entry under a scan root and
614
+ `pnpm phi-scan --staged` printed `OK: no hits` and exited **0**. A rename that also
615
+ **substitutes a real name** into the moved file passed the same way (measured at `R098`, mode
616
+ `100644`, a real-looking surname in the staged blob). The gap is at **pre-commit**; the all-mode
617
+ sweep is the backstop, and it saw both.
618
+
619
+ **The remedy is `--no-renames`, and it needs no stride work and no scope decision.** An earlier
620
+ reading of this gap said admitting `R`/`C` required the two-path `--raw` record shape handled;
621
+ that reading was wrong, and the previous entry in this file repeated it. Turning detection off
622
+ makes the destination arrive as an ordinary single-path `A`
623
+ (`:000000 120000 0000000 <sha> A`) and the source a `D` the filter already drops. Verified under
624
+ `diff.renames=true|copies|false|1` and `diff.renameLimit=1`: every one yields the same
625
+ single-path `A`, so the two-field stride is **structural** rather than conditional on the
626
+ caller's git config, and the new enumeration **contains** the old one (pinned by
627
+ a test that compares the two path sets under each of those settings).
628
+
629
+ - **A staged COPY bypassed the same route, and no rename fixture reaches it.** With
630
+ `diff.renames=copies`, copying an out-of-scope PHI-bearing file INTO a scan root stages as a
631
+ genuine `C100` two-path record and was dropped exactly as a rename was: measured **exit 0**
632
+ before and **exit 1** after. One precondition is
633
+ recorded because it is easy to state this too broadly: config-only copy detection also needs the
634
+ COPY SOURCE modified in the same staged diff. Without that, git finds no copy source, the record
635
+ arrives as an ordinary `A` that even the old argv enumerated, and a naive copy fixture passes on
636
+ both scanners while proving nothing.
637
+
638
+ - **A scan ROOT'S OWN path was outside the `--staged` filter, on both roots.** An index entry at
639
+ exactly `test/fixtures` or exactly `src` is never a directory, because git records no entry for
640
+ one, so it is a scan root REPLACED by a blob, a link or a gitlink. The prefix test alone let that
641
+ through: measured **exit 0** over a staged mode-`120000` `test/fixtures`, and again over `src`.
642
+ The filter now matches each root's own path as well as the prefix. This is the one **addition to
643
+ the path scope** in this change, and it is stated as such rather than filed under "narrowing".
644
+ The `.ts` suffix rule is deliberately not applied to `src`'s own name: that rule is a judgement
645
+ about bytes the route could have read, and the name of an entry that replaced a walk root is no
646
+ evidence about what is on the other side of it.
647
+
648
+ - **The PHI scan reported clean over an UNMERGED path.** `U` is returned by neither `AM` nor
649
+ `AMT`, so a conflicted in-scope path made `--staged` print `OK: no hits` and exit **0** over an
650
+ index it structurally cannot read (measured, with a real-looking surname in one of the stages).
651
+ Such a path is recorded at one or more of stages 1/2/3 and never at stage 0, so `git show :<path>`
652
+ fails outright
653
+ and there is no one staged blob to scan. It is now enumerated and **refused** (exit 2), with its
654
+ own message: a `U` record's destination mode is `000000`, and the existing mode refusal's
655
+ sentence about `git show` handing back a target path is false for an unmerged regular file.
656
+ **The bound, stated precisely:** git itself refuses to commit while a path is unmerged, so this
657
+ was never a route to a committed leak. What it was is the gate attesting clean over a state it
658
+ never observed, and `pnpm phi-scan --staged` is run by hand and from scripts as well as from the
659
+ hook.
660
+
661
+ - **The PHI scan exited 1, its code for HITS FOUND, when it had not scanned anything.** A missing
662
+ or unreadable `scripts/phi-allow-list.txt` and a walk root `readdirSync` could not list both
663
+ threw out of `main` uncaught, and node reports an uncaught throw as exit **1**, with a v8 stack
664
+ trace in place of the scanner's own diagnostic. Both now refuse with exit **2** and a named
665
+ `[phi-scan]` line, and a top-level backstop catches whatever else throws so nothing can reach
666
+ node's default handler again. Nothing downstream reads the difference today (both codes are
667
+ non-zero and the gate blocks either way); a caller that ever split them would have read a scan
668
+ that never started as a scan that found PHI.
669
+
670
+ - **The PHI scan read a symbolic link as clean on BOTH of its enumerating routes
671
+ (`PHI-SCAN-SYMLINK-BLIND-ON-BOTH-ROUTES`).** A link under a scan root pointing at a PHI-bearing
672
+ file passed the gate twice over. **Reproduced on `5debd18` before the fix**, in a throwaway repo,
673
+ with a synthetic name-bearing HL7 message placed outside the walk roots and a link to it at
674
+ `test/fixtures/leak.hl7`: all-mode printed `OK: no hits` and exited **0**, `--staged` after
675
+ `git add` did the same, and naming the target file explicitly exited **1** with seven hits
676
+ (`PID-5` twice, `PID-7`, `PID-11`, `PID-3`, and the shape pass's `(ssn)` and `(email)`). The
677
+ payload was always detectable; the two routes never looked at it.
678
+
679
+ **Two mechanisms, two fixes, and neither route is made to follow the link.** `walk()` enumerates
680
+ `Dirent.isFile()`, which is an lstat answer, so a link is neither a file nor a directory and fell
681
+ out of the loop silently; `isDirectory()` answers false for a **linked directory** too, so a whole
682
+ subtree left the same way. `--staged` reads content with `git show :<path>`, and git stores a link
683
+ as its **target path** under mode `120000`, so that route was handed the path text and never the
684
+ target's bytes. That route is this package's pre-commit hook. Following a link would read bytes
685
+ the enumeration does not control (outside the repo, a loop, a device, a FIFO that blocks the gate
686
+ forever), and git does not carry those bytes anyway, so a hit on them would be a claim about
687
+ something no commit contains. **The enumeration is narrowed instead:** an in-scope entry that is
688
+ not a regular file **refuses the scan** (exit 2), naming every offender rather than the first.
689
+ `--staged` reads `git diff --cached --raw -z` so the destination mode is visible, and refuses
690
+ `120000` and `160000`; an unparseable `--raw` record refuses too, rather than scanning a list that
691
+ may be short.
692
+
693
+ **`--diff-filter=AMT`, and the `T` is load-bearing.** Replacing a **tracked** regular file with a
694
+ link is neither an add nor a modify: git raises it as a typechange
695
+ (`:100644 120000 <sha> <sha> T`), so `--diff-filter=AM` deleted the record before any mode could
696
+ be read and the mode check was unreachable for exactly the files the repo already had. Measured
697
+ here: with `AM` the raw output for that stage is empty. Admitting `T` also scans the **reverse**
698
+ typechange, a link replaced by a real file carrying PHI, as the file it became.
699
+
700
+ **A refusal names the entry's own repo-relative path and an engine-owned kind token, never the
701
+ link target**, which is working-tree text that can itself carry PHI. The scanner's docblock states
702
+ the risky target shape in the abstract rather than showing one, because a diagnostic about a PHI
703
+ leak is itself a PHI surface and that applies to the prose explaining it.
704
+
705
+ **Two behaviour changes that fall out of the same narrowing, both measured, both intended.** A
706
+ FIFO under a walk root used to be skipped (base exit 0) and now refuses (exit 2, named as
707
+ `a FIFO`). And an SSN or email shape sitting in a link's target _path_ used to fire on the shape
708
+ pass under `test/fixtures/` and now never runs, because the entry is refused before anything is
709
+ read. A refusal that names the entry is a better answer than a hit that only describes a filename,
710
+ so both are the intended trade rather than a loss.
711
+
712
+ **What this does NOT close, stated so it is not read as wider than it is.** `R` (rename) and `C`
713
+ (copy) are still not enumerated by `--staged` at all, so a staged rename that appends PHI passes
714
+ that route; admitting them needs the two-path `--raw` record shape handled, which is a scope
715
+ decision rather than this one. If such a record ever reached the parser the field stride would
716
+ desync and it refuses, which is the safe outcome.
717
+ **CORRECTED 2026-08-04, ON BOTH CLAUSES, and left in place rather than edited because it
718
+ shipped. Read this note before acting on the paragraph above it: the paragraph now describes a
719
+ hole that is CLOSED.** (a) `R` and `C` really were not enumerated by `--staged`, and a staged
720
+ rename into a scan root really did pass that route, but that is no longer true: the route reads
721
+ `--no-renames`, so the destination arrives as an ordinary single-path `A` and is scanned. (b)
722
+ Closing it needed neither the two-path `--raw` record shape nor a scope decision, which is what
723
+ the second clause asserted; `--no-renames` does it under every `diff.renames` and
724
+ `diff.renameLimit` setting, with the record stride untouched. A first draft of this note rebutted
725
+ only (b) and left (a) reading as a live hole under a "What this does NOT close" heading, which a
726
+ reviewer caught. See the `PHI-SCAN-RENAME-BLIND-AT-PRECOMMIT` entry at the top of this section
727
+ for the measurement.
728
+ `--staged`'s path scope is unchanged
729
+ (`test/fixtures/**` and `src/**.ts`), and the walk's gitignore exemption is unchanged: an ignored
730
+ link is out of scope by the same rule that already excludes an ignored file, though an entry in
731
+ the index cannot be excused that way. That prefix is literal, so a link staged at exactly
732
+ `test/fixtures` (the root itself, not an entry under it) is outside the `--staged` filter and is
733
+ unchanged here; it is not both-routes-blind, because `readdirSync` resolves a symlinked walk root
734
+ and all-mode reads straight through it (measured, exit 1 on the PHI). **(c) CLOSED 2026-08-04:
735
+ the `--staged` filter now matches each root's own path as well as the prefix, on both roots.**
736
+ A tracked file absent from the worktree is still caught at `git add` time only. The enumerate-then-read tolerance and the
737
+ refuse-a-sweep-that-observed-nothing rule are untouched.
738
+
739
+ Pinned by 19 cases in `test/scripts/phi-scan.test.ts`, **8 of them red on `5debd18`**, all against
740
+ throwaway git repositories so no link and no violator is ever written into the committed corpus.
741
+ The payload is name-bearing and the link target's own filename carries a name, so the
742
+ "never echo the target" assertions are not vacuous.
743
+
744
+ - **The `attw` publish gate reported a pass on a tarball that carried no types (`ATTW-FALSE-GREEN-PORT`).**
745
+ `package.json` ran `attw --pack .`, and that command prints "This package does not contain types."
746
+ and **exits 0**. Every caller that reads the status, including `prepublishOnly` and the shared CI
747
+ pipeline, read the 0. A false red costs an hour; a false green merges.
748
+
749
+ **The defect is `attw`'s exit code, not the invocation and not concurrency.** In this repo's own
750
+ `@arethetypeswrong/cli@0.18.4`, `dist/getExitCode.js` opens with `if (!analysis.types) return 0`,
751
+ so the problem list is never consulted. No `--profile`, `--ignore-rules` or config setting reaches
752
+ that early return. For a package that ships types, "does not contain types" does not mean "fine,
753
+ untyped"; it means the declarations were not in the tarball, which is a broken publish.
754
+
755
+ **Reproduced here with no concurrency at all** (2026-08-03, Node 22): `rm -f dist/index.d.ts
756
+ dist/index.d.cts && pnpm attw` and `rm -rf dist && pnpm attw` both printed the sentence and exited 0. A race only supplies the condition. `tsup` emits the JS bundles in one pass and the declaration
757
+ files in a later one, so there is a window in every build of this package where `dist/` holds
758
+ `.mjs`/`.cjs` and no `.d.ts`: timed on two clean builds here, `dist/index.mjs` appeared at 3.57s
759
+ and 3.58s and `dist/index.d.ts` at 5.81s and 5.65s, a window of 2.24s and 2.07s. A concurrent
760
+ build or `pnpm clean` in the same working tree lands `attw` in it. **That is deliberately not
761
+ answered with a lock, a lease or a build queue**: the gate has to be able to say its own inputs
762
+ were missing, whatever removed them.
763
+
764
+ **The fix is `scripts/attw.mjs`, and it carries two nets that catch different things.** A
765
+ **preflight** checks that every relative artifact path `package.json` promises (`main`, `module`,
766
+ `types`, `typings`, and every string leaf of `exports`) exists and is non-empty before `attw` runs;
767
+ it is structural, does no string matching, and names the missing file. A path written **without** a
768
+ leading `./` counts: `"types": "dist/index.d.ts"` is legal for those four fields and is normalized
769
+ rather than skipped, since skipping it would leave the preflight quietly narrower than every
770
+ sentence describing it. Wildcard `exports` patterns (they name a set, not a file), absolute paths
771
+ and `package.json` itself are skipped. A manifest that declares **nothing** is refused rather than
772
+ passed, because a preflight with no inputs is not a preflight that succeeded. A **post-check** fails
773
+ when
774
+ `attw` still reports an untyped package, which is the case the preflight structurally cannot see:
775
+ declarations present on disk but excluded from the tarball by `files` or `.npmignore`. **No
776
+ instance of that second case is on record in this repo.** The post-check reads a printed sentence,
777
+ so the routes that would hide it are refused rather than tolerated: `--quiet`, `-q`,
778
+ `--format json`, `-f json`, `--format=json`, `-fjson`, `-qP`, `-Pf json`, a `.attw.json` setting
779
+ `quiet` or `format`, and `--config-path`. Each of the first nine was measured against this
780
+ package's own untyped tree to return exit 0 with the sentence absent; `--config-path` is refused by
781
+ inference, not measurement. The refusal is by option name, wholesale, not by value. Other arguments
782
+ are still forwarded, so `--profile node16`, `--no-summary`, `--no-emoji` and `--no-color` work.
783
+
784
+ **Short options are matched by letter anywhere in the cluster, not by whole token, and that is not
785
+ cosmetic.** `commander` bundles short options and lets a value ride on the end, so `-fjson`, `-qP`
786
+ and `-Pf json` are `--format json` and `--quiet` in another spelling. Measured against a package
787
+ whose declarations sit on disk but are excluded from `files`, which is the one shape only the
788
+ post-check can see, a token-matching guard returned **exit 0** on `-fjson` and on `-Pf json`.
789
+ `-qP` was caught, but only by the empty-transcript net rather than by the refusal.
790
+
791
+ **A second class of argument is refused for a different reason, and calling it blinding would be
792
+ wrong.** `--help`, `-h`, `--version` and `-V` print and exit 0 without analysing anything: measured
793
+ on the untyped fixture at 2766 and 48 bytes of output, exit 0, no untyped sentence. The transcript
794
+ is non-empty, so the empty-transcript net does not fire either, and the gate would have read a pass
795
+ off a run that checked nothing. `--definitely-typed` is refused alongside them **by inference, not
796
+ by measurement**, and the distinction is kept because the measurement was attempted and did not
797
+ reproduce: with a version range the untyped sentence still printed. It is refused on a reading of
798
+ the CLI, where a value that looks like a path merges external declarations into the analysis, which
799
+ would let a package that ships none come back typed. `--no-definitely-typed` is a distinct option
800
+ name and is deliberately not refused.
801
+
802
+ **Both of this repo's manifests are fixed, because the census that matters is manifests rather
803
+ than repo roots.** `package.json` and `examples/profile-starter-kit/package.json` each ran the
804
+ bare CLI, and the kit is a template a consumer copies out and publishes from, so leaving it would
805
+ have re-minted the false green in every generated package. The kit gets its own copy, whose code is
806
+ **byte-identical** to the root one below the docblock and is pinned that way by a test; only the
807
+ comment differs, because one is written for a maintainer here and the other for a consumer who
808
+ copied the kit out. Two copies of a gate drift, and the kit's copy is executed by nothing in this
809
+ repo's CI, so the identity pin is what makes the behavioural tests true of both.
810
+
811
+ `test/scripts/attw-gate.test.ts` pins the defect and the remedy against the real binary: that bare
812
+ `attw` exits 0 on both shapes (declarations unpacked, and declarations never built) and on
813
+ `-fjson`, that the wrapper reds each of them, that the preflight names a missing, an empty and a
814
+ `./`-less artifact and refuses a manifest that declares none, that a real `attw` failure still
815
+ propagates `attw`'s own status, that the wrapper is transparent on a well-formed package and does
816
+ not refuse the options that blind nothing, and that each refusal holds. If a future `attw` fixes
817
+ the exit code or rewords the sentence, the suite reds and sends the reader to the wrapper rather
818
+ than letting the net go quietly slack. No runtime or API change; this is a build and publish gate
819
+ only.
820
+
598
821
  - **A warning or error message could carry clinical text into a log line (`HL7-WARN-MSG-PHI`).**
599
822
  No options were required to reach it: plain `parseHL7(raw)` on lenient defaults.
600
823
 
package/dist/index.cjs CHANGED
@@ -6874,7 +6874,7 @@ async function* parseStream(source, optionsOrProfile) {
6874
6874
  }
6875
6875
 
6876
6876
  // src/index.ts
6877
- var VERSION = "0.0.6";
6877
+ var VERSION = "0.0.8";
6878
6878
 
6879
6879
  exports.ACK_CODES = ACK_CODES;
6880
6880
  exports.ACK_CONDITIONS = ACK_CONDITIONS;