@cosyte/hl7 0.0.8 → 0.0.10
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 +363 -24
- package/README.md +6 -4
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,17 +1,246 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
3
|
+
## 0.0.10
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 68898a3: The README lockup now links to cosyte.com (`ASSETS`).
|
|
8
|
+
|
|
9
|
+
The `<picture>` block above the H1 is wrapped in an anchor to https://cosyte.com, per the founder
|
|
10
|
+
requirement of 2026-08-06. Nothing inside the block moved: the `<source>`, the `<img>`, the alt text
|
|
11
|
+
and both tile URLs are byte-identical.
|
|
12
|
+
|
|
13
|
+
What the anchor does was measured on both surfaces by `fhir`, not assumed, because fourteen READMEs
|
|
14
|
+
carry this shape. On GitHub the anchor works and the colour-scheme switch keeps working, because the
|
|
15
|
+
`<img>` stays a direct child of `<picture>`, which is the condition the HTML spec puts on `<source>`
|
|
16
|
+
applying at all. On an npm package page the anchor is lost: npm wraps a README image in its own
|
|
17
|
+
anchor to the image file, a nested anchor is not representable, so the parser closes ours early and
|
|
18
|
+
the image ends up linked to the image file rather than to cosyte.com. Shipped anyway by founder
|
|
19
|
+
decision of 2026-08-07: on npm that is no worse than the unlinked lockup it replaces, and GitHub is
|
|
20
|
+
where these READMEs are read.
|
|
21
|
+
|
|
22
|
+
## 0.0.9
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- 3ac0c28: `CHANGELOG.md`, which ships inside the tarball, is now written by the release instead of by hand, so it stops describing already-published code as unreleased.
|
|
27
|
+
|
|
28
|
+
`.changeset/config.json` set `"changelog": false`, so no release ever wrote a version heading
|
|
29
|
+
into `CHANGELOG.md` and nothing ever rolled `[Unreleased]` over. Every published version of this
|
|
30
|
+
package therefore carried a changelog with **no version headings at all**: one `[Unreleased]`
|
|
31
|
+
heading over the whole history, and a preamble stating that the first pre-alpha release "will
|
|
32
|
+
ship" the API surface listed below it, in a tarball that had shipped that surface several
|
|
33
|
+
versions earlier. `CHANGELOG.md` is listed in `package.json` `files`, so this was text on the
|
|
34
|
+
disk of everyone who installed the package, not internal bookkeeping.
|
|
35
|
+
|
|
36
|
+
**The flag is what changed, not the prose.** Correcting the sentence by hand would have left the
|
|
37
|
+
mechanism that wrote it, and the next release would have drifted the same way. `changelog` now
|
|
38
|
+
names the generator that ships with Changesets, so a release writes its own version heading and
|
|
39
|
+
its own entry from the changesets it consumed, and **the changeset summary is now the changelog
|
|
40
|
+
entry**. Nothing new is depended on: the generator is an entry point of `@changesets/cli`, which
|
|
41
|
+
was already a dev dependency.
|
|
42
|
+
|
|
43
|
+
**The file's shape changed with it, deliberately.** Changesets prepends a release by replacing
|
|
44
|
+
the first newline in the file, so exactly one line can sit above generated output. The
|
|
45
|
+
hand-written preamble sat on line 3, which means a release would have inserted itself between the
|
|
46
|
+
heading and the preamble and split the header in two. The hand-maintained history has therefore
|
|
47
|
+
moved under a `## Released before this file was generated` heading, with the false preamble
|
|
48
|
+
replaced by an accurate one. Three pieces of hand-workflow scaffolding were dropped and no entry
|
|
49
|
+
was reworded: the `[Unreleased]` heading, its link definition at the foot of the file, and the
|
|
50
|
+
five empty section stubs waiting for the next hand-written entry. The history itself is left as
|
|
51
|
+
it was written rather than re-sorted into version sections, because the file never recorded which
|
|
52
|
+
release each entry went out in and the text is already on disk in published copies.
|
|
53
|
+
|
|
54
|
+
Pinned by `test/scripts/changelog-generation.test.ts`, which runs the real `changeset version`
|
|
55
|
+
against the real `CHANGELOG.md` and the real config in a throwaway package rather than
|
|
56
|
+
reimplementing where the tool inserts text. Eight of its ten cases are red against the previous
|
|
57
|
+
state, measured on the tree this change was written against rather than recalled. **The rule it enforces is that nothing but the H1 sits above the first heading, and it is
|
|
58
|
+
asserted on the released document as well as the committed one**: a rule phrased as "the archive
|
|
59
|
+
heading comes second" holds only until the first release writes its own version heading there,
|
|
60
|
+
which would have redded the first Version PR this configuration ever opened. The archived history
|
|
61
|
+
is asserted byte identical across a release, so the run exercises this repo's own Prettier rather
|
|
62
|
+
than the one Changesets bundles as a fallback. Two further controls: the same inputs with
|
|
63
|
+
`"changelog": false` must write no version heading at all, so the flag is proved load-bearing
|
|
64
|
+
rather than incidental; and the old file shape must reproduce the split header, so the rule is
|
|
65
|
+
demonstrated rather than asserted.
|
|
66
|
+
|
|
67
|
+
One upstream behaviour is worth knowing before debugging a release, and is recorded in that
|
|
68
|
+
file: Changesets wraps the changelog write in a try/catch that only warns. A tree whose declared
|
|
69
|
+
Prettier config cannot be resolved bumps the version, consumes the changeset, and writes no
|
|
70
|
+
changelog at all. Reproduced here on this repo's inputs. A release that publishes with an
|
|
71
|
+
unchanged changelog is that failure, not a setting that quietly reverted.
|
|
72
|
+
|
|
73
|
+
`CONTRIBUTING.md` and `.changeset/README.md` said to add the entry to `CHANGELOG.md` by hand and
|
|
74
|
+
now say to write it in the changeset. No runtime code, no public API, no warning code and no
|
|
75
|
+
parse behaviour changed.
|
|
76
|
+
|
|
77
|
+
- d8d3be3: Repository PHI commit-gate only, with no runtime impact: the gate no longer reports a clean sweep over a scan root it never opened, where an absent or dangling `test/fixtures` used to print `OK: no hits` and exit 0 on the strength of `src/` alone.
|
|
78
|
+
|
|
79
|
+
`pnpm phi-scan` with no arguments is what CI runs. It walks two roots, `test/fixtures` and `src`,
|
|
80
|
+
and refused a sweep only when it had read **zero files in total**. That global rule is satisfied by
|
|
81
|
+
any one surviving file, so a single `src/` module vouched for the entire fixture corpus, and one
|
|
82
|
+
fixture vouched for the whole of `src`.
|
|
83
|
+
|
|
84
|
+
Reproduced before the fix, on this checkout, three ways, each printing `[phi-scan] OK: no hits` and
|
|
85
|
+
exiting **0**: with `test/fixtures` moved aside, with `test/fixtures` replaced by a **dangling
|
|
86
|
+
symlink**, and with `src` moved aside. The first two leave all 106 non-markdown fixtures unread;
|
|
87
|
+
the third leaves all 95 source files unread.
|
|
88
|
+
|
|
89
|
+
**The dangling case is the sharpest, and nothing else in the scanner can see it.** `existsSync`
|
|
90
|
+
**follows** the link and answers false, so `walk()` returns before `readdirSync` and the
|
|
91
|
+
not-a-regular-file refusal never fires. That rule only ever classifies entries found INSIDE a root,
|
|
92
|
+
and this is the root itself, so absent, dangling and empty are one state to the walk. This reporter
|
|
93
|
+
prints no file count either, so unlike the sibling this remedy was ported from there was not even a
|
|
94
|
+
suspicious denominator to notice.
|
|
95
|
+
|
|
96
|
+
**The rule is now per-root.** All-mode refuses (exit 2) unless every declared root yielded at least
|
|
97
|
+
one file that was actually READ, and the refusal names the starved roots. Observing nothing at all
|
|
98
|
+
is the all-starved case of that one rule rather than a second rule beside it, so the existing
|
|
99
|
+
wording carries. Hits found under the roots that DID yield are printed before the refusal, so an
|
|
100
|
+
incomplete sweep never swallows a real finding, and the exit code is still 2 regardless.
|
|
101
|
+
|
|
102
|
+
**Deliberately unchanged, and pinned:** `--staged` and named-path mode make no per-root promise,
|
|
103
|
+
because neither enumerates a root. `--staged` legitimately has nothing to scan when a commit
|
|
104
|
+
touches only markdown, and named-path mode is bounded by the caller's argv. Widening the rule to
|
|
105
|
+
either was run as a mutation rather than argued: widening it to `--staged` reds 7 cases, and
|
|
106
|
+
dropping the mode guard altogether reds 29.
|
|
107
|
+
|
|
108
|
+
**The granularity is the declared root and nothing finer**, which is a real bound and is recorded
|
|
109
|
+
rather than glossed. Three states still exit 0, each measured with the rule in place, each now in
|
|
110
|
+
the scanner's limits list with the command that produced it, and each pinned by a characterization
|
|
111
|
+
test so a change that closed one would have to say so: a directory missing from INSIDE a root
|
|
112
|
+
(`mv test/fixtures/canonical ..` still prints `OK: no hits` with 27 fixtures unread); a root that
|
|
113
|
+
is ITSELF a symlink, which is followed, so the rule is satisfied by whatever is on the other side
|
|
114
|
+
of it; and the rule is a floor of ONE file, whatever the root used to hold.
|
|
115
|
+
|
|
116
|
+
One shape often listed beside those is NOT open here, and it is written down so it is not ported in
|
|
117
|
+
from elsewhere as though it were: a root that is a regular file, or a link to one, refuses at exit
|
|
118
|
+
**2**, not the **1** this gate reserves for hits. `walk()` already wraps `readdirSync` and rethrows
|
|
119
|
+
the `ENOTDIR` as an invocation error on the scanner's own channel, and an existing test pins it.
|
|
120
|
+
|
|
121
|
+
Twelve tests added, on a throwaway repository so no starved root, no dangling link and no violator
|
|
122
|
+
is ever written into the committed corpus. Five are red against the superseded global rule; the
|
|
123
|
+
all-starved case is green on both, because it asserts the new rule SUBSUMES the old one rather than
|
|
124
|
+
sitting beside it; three assert the rule did not widen; three are characterizations of the limits
|
|
125
|
+
above. The scratch-repo helper now seeds both walk roots, which is load-bearing rather than
|
|
126
|
+
tidiness: with an empty `test/fixtures` every case built on it would refuse before reaching what it
|
|
127
|
+
meant to assert.
|
|
128
|
+
|
|
129
|
+
- be842bd: Repository PHI commit-gate only, with no runtime impact: the gate now reads tracked files directly under `test/`, reads an inline `PID|...` literal in hand-written source with the same field map a `.hl7` fixture gets, and no longer drops to a shape-only pass over a blob staged at exactly a scan root.
|
|
130
|
+
|
|
131
|
+
`pnpm phi-scan` with no arguments is what CI runs; `pnpm phi-scan --staged` is the pre-commit hook.
|
|
132
|
+
Four separate holes, each pre-existing and each measured on this checkout before the change.
|
|
133
|
+
|
|
134
|
+
**One: a tracked file directly under `test/` was enumerated by neither route.** The walk was rooted
|
|
135
|
+
at `test/fixtures` and `src`, and `--staged` matched `test/fixtures/**` plus `src/**.ts`. That
|
|
136
|
+
leaves **115 tracked files** in this repo scanned by nothing at all. A file at `test/<name>.ts`
|
|
137
|
+
carrying a live `PID-3`/`PID-5`/`PID-7`/`PID-11`/`PID-13` printed `OK: no hits` and exited **0** on
|
|
138
|
+
both routes, over the same bytes that give six segment-aware hits at a `.hl7` path.
|
|
139
|
+
|
|
140
|
+
**Two, and it is the half that a wider walk root does NOT buy: the recogniser assumes the file IS
|
|
141
|
+
the document, and an inline fixture is a TypeScript string literal.** Bringing `test/**` into scope
|
|
142
|
+
bought the conservative shape floor -- a dashed SSN and a non-test email domain -- over those 115
|
|
143
|
+
files and nothing else: no name, DOB, MRN, address or phone detection. **55 of the 115 carry an
|
|
144
|
+
inline `PID|` literal.** Widening the scope and widening the recogniser are two changes, and the
|
|
145
|
+
second is in addition to the first, never instead of it. So there is now an embedded-literal pass
|
|
146
|
+
that pulls `PID|...` runs out of source and hands them the same field map the whole-file parse uses.
|
|
147
|
+
Both halves are pinned with a case that is red before and green after, and the residual is pinned
|
|
148
|
+
too: the same identifying values written as PROSE in the same file are still not found, because a
|
|
149
|
+
name in narrative is not separable from clinical vocabulary without NLP.
|
|
150
|
+
|
|
151
|
+
The embedded pass is deliberately narrower than the whole-file parse, and each of its four limits
|
|
152
|
+
is stated rather than left to be discovered. Two of them are here because review refuted the first
|
|
153
|
+
draft of this paragraph, which is the reason to read them rather than trust the summary.
|
|
154
|
+
|
|
155
|
+
It uses the default delimiters only, because an embedded run has no reachable `MSH-2`: the encoding
|
|
156
|
+
characters are themselves source-escaped. It runs no unknown-segment backstop, which is right for a
|
|
157
|
+
known message and a noise cannon over program text, so an inline `Z...` segment carrying a name is
|
|
158
|
+
missed. It erases a `${...}` interpolation rather than reading the variable's identifier as a
|
|
159
|
+
person's name, and the bound there is any value reached through an interpolation, INCLUDING one
|
|
160
|
+
whose literal sits in the same file: measured, a surname in a same-file constant interpolated into a
|
|
161
|
+
PID line exits 1 on the MRN and the DOB and is silent on the name. This pass reads literals in field
|
|
162
|
+
position and evaluates nothing; the shape floor still reads the whole file, so a dashed SSN or a
|
|
163
|
+
non-test email in such a constant is caught either way. And a run is cut at the first source quote,
|
|
164
|
+
which cuts HL7 data as well as program text: the two-character literal `""` is HL7 v2's
|
|
165
|
+
explicit-null field value, so an inline fixture exercising null handling loses everything after it,
|
|
166
|
+
and an apostrophe inside a name truncates identically. No clause number is quoted for that, on
|
|
167
|
+
purpose: the behaviour is grounded in this package's own parser, whose null indicator is true iff
|
|
168
|
+
the field was exactly those two characters. That last one is a MISS, and it is written down rather than
|
|
169
|
+
patched, because the enclosing quote style is not knowable from a single line and a wrong guess
|
|
170
|
+
reads program text as patient data.
|
|
171
|
+
|
|
172
|
+
`src/` changed intent and not only scope, so the comment that said otherwise is corrected rather
|
|
173
|
+
than left standing: the embedded pass runs on EVERY non-fixture target, so a JSDoc `@example`
|
|
174
|
+
carrying a PID line with a non-allow-listed name now reds where it did not before. This package
|
|
175
|
+
requires an `@example` on every public export, so a new example must use allow-listed tokens or add
|
|
176
|
+
its own, exactly as a fixture does. The committed corpus is green as it stands.
|
|
177
|
+
|
|
178
|
+
**Three: a regular blob staged at exactly `test/fixtures` got the shape floor only, and `--staged`
|
|
179
|
+
is the pre-commit hook.** The scope and the mode check already passed it through; the DETECTOR gate
|
|
180
|
+
dropped it, because the fixture test wanted a `test/fixtures/` prefix or an `.hl7` suffix and the
|
|
181
|
+
root's own path has neither. Measured twice independently: the same payload gives six segment-aware
|
|
182
|
+
hits at `test/fixtures/<name>.hl7` and exit **0** at `test/fixtures`. A dashed SSN in that blob
|
|
183
|
+
exited **1** either way, so the floor held and only the segment-aware tier was missing -- which is
|
|
184
|
+
why the remedy is one clause and not a new rule.
|
|
185
|
+
|
|
186
|
+
**And a fourth, found by a sibling and reproduced here: `-B` is not inert, and this scanner's own
|
|
187
|
+
comment said it was.** That was the permissive half of a "do not add these flags" paragraph, so it
|
|
188
|
+
told the next reader that injecting `-B` was safe. The mechanism is sharper than "a `B` record the
|
|
189
|
+
filter drops": under `-B` a complete rewrite prints status letter **`M`** with a break score, one
|
|
190
|
+
path, which the raw-record parser handles happily -- but `--diff-filter` classifies a broken pair as
|
|
191
|
+
**`B`** whatever letter it prints, so `AMTU` deleted the record before anything saw it. Through the
|
|
192
|
+
scanner, over a staged dashed SSN in a wholly rewritten in-scope file: the shipped argv exits 1, the
|
|
193
|
+
same argv plus `-B` printed `OK: no hits` and exited 0. `B` is now in the filter. It costs the
|
|
194
|
+
enumeration nothing, because git cannot emit a broken pair without the flag. **The pin that let the
|
|
195
|
+
false claim survive was a pin on a rename**, the one shape `-B` really does leave alone: a case that
|
|
196
|
+
cannot fail proves nothing.
|
|
197
|
+
|
|
198
|
+
**Two behaviour changes that follow from `test` being a walk root, recorded rather than left to be
|
|
199
|
+
found.** The fixture root is now an ENTRY INSIDE another root, so a symlink there is classified by
|
|
200
|
+
`Dirent.isSymbolicLink()` and refused by name at exit 2, dangling or not: that closes, for
|
|
201
|
+
`test/fixtures` only, the residual the previous entry recorded as "a root that is itself a symlink
|
|
202
|
+
is followed". It does not generalise -- `src` and `test` have no walked parent, so both are still
|
|
203
|
+
followed, and the general rule is the parent, not the root. And the two roots nest, so coverage now
|
|
204
|
+
credits EVERY root a file sits under rather than the first: crediting one would leave the other
|
|
205
|
+
permanently starved, and which one depended on list order with nothing saying so. Walked entries are
|
|
206
|
+
de-duplicated for the same reason, or every fixture would be reported twice.
|
|
207
|
+
|
|
208
|
+
The scanner's own test file was the one place this could not be resolved with the allow-list, and it
|
|
209
|
+
is worth saying why: it holds deliberate violators, every one of them asserted on, so allow-listing
|
|
210
|
+
them would have made the positive tests vacuous and carving the file out of the scan would have been
|
|
211
|
+
the gate excusing itself from the rule it enforces. Its violator identities are assembled at runtime
|
|
212
|
+
instead -- the discipline the file already used for its SSN sentinel -- so the temp files it builds
|
|
213
|
+
still carry the full violator bytes and nothing in the tracked file is a PHI-shaped run. One
|
|
214
|
+
registrable `.org` email in it became a permanently unregistrable `.invalid` one on the way past.
|
|
215
|
+
Every other new allow-list token was surfaced by the gate rather than guessed at, and they are the
|
|
216
|
+
same species as the placeholder surnames already listed.
|
|
217
|
+
|
|
218
|
+
Twenty-three net new tests in the scanner's own suite (78 to 101), plus several existing ones
|
|
219
|
+
rewritten where the widening inverted their verdict, each change of verdict recorded rather than
|
|
220
|
+
deleted. All of them run on throwaway repositories, so no violator is ever written into the
|
|
221
|
+
committed corpus. Nothing here changes a single byte of the published package.
|
|
222
|
+
|
|
223
|
+
## Released before this file was generated
|
|
224
|
+
|
|
225
|
+
Every release section above this heading is written by
|
|
226
|
+
[Changesets](https://github.com/changesets/changesets) from the changesets in `.changeset/`, newest
|
|
227
|
+
release first. The release writes its own version heading, so nothing above this line is maintained
|
|
228
|
+
by hand, and a change is recorded by adding a changeset rather than by editing this file.
|
|
229
|
+
|
|
230
|
+
Everything below this heading was maintained by hand. It sat under a single `[Unreleased]` heading
|
|
231
|
+
that no release ever rolled over, which is why it went on describing already-shipped code as
|
|
232
|
+
unreleased, inside the published tarball, for as long as it did. It is left as it was written
|
|
233
|
+
rather than re-sorted into version sections: the file never recorded which release each entry went
|
|
234
|
+
out in, and this is the text that installed copies already carry on disk. Only three things were
|
|
235
|
+
dropped, all of them scaffolding for the hand-written workflow that no longer runs: the
|
|
236
|
+
`[Unreleased]` heading itself, its link definition at the foot of the file, and the five empty
|
|
237
|
+
section stubs that existed to receive the next hand-written entry. No entry was reworded.
|
|
238
|
+
|
|
239
|
+
The entries below follow [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); the generated
|
|
240
|
+
sections above use the format Changesets writes, which is a version heading and a list of the
|
|
241
|
+
changes that release consumed. Versions follow the cosyte pre-alpha ladder, `0.0.x` until first
|
|
242
|
+
alpha, rather than [Semantic Versioning](https://semver.org/spec/v2.0.0.html) alone. An earlier
|
|
243
|
+
`0.1.0` tag was prepared but never published, which is why the public history begins at `0.0.x`.
|
|
15
244
|
|
|
16
245
|
### Added
|
|
17
246
|
|
|
@@ -605,6 +834,128 @@ kind, value)`** sets one at a field or `field[rep]` dot-path. Component values a
|
|
|
605
834
|
|
|
606
835
|
### Fixed
|
|
607
836
|
|
|
837
|
+
- **The PHI scan reached neither `test/**` nor an inline fixture, and dropped to a shape-only pass
|
|
838
|
+
at a scan root's own path (`PHI-SCAN-WALK-ROOT-SCOPE`, plus `PHI-SCAN-RENAME-BLIND-AT-PRECOMMIT`▶(b)).** Four`PRE-EXISTING`holes, each measured on`d8d3be3` before the change.
|
|
839
|
+
|
|
840
|
+
**1. A tracked file directly under `test/` was enumerated by NEITHER route.** The walk was rooted
|
|
841
|
+
at `test/fixtures` + `src`; `--staged` matched `test/fixtures/**` + `src/**.ts`. That left **115
|
|
842
|
+
tracked files** scanned by nothing. A `test/<name>.ts` carrying a live
|
|
843
|
+
`PID-3`/`PID-5`/`PID-7`/`PID-11`/`PID-13` printed `OK: no hits` and exited **0** on both routes,
|
|
844
|
+
over bytes that give six segment-aware hits at a `.hl7` path. `SCAN_ROOTS` is now
|
|
845
|
+
`test/fixtures` + `test` + `src`, and `--staged` adds `test/**` (markdown excluded, copying
|
|
846
|
+
`walk()`'s rule) as a **union** clause, so nothing it enumerated before stops being enumerated.
|
|
847
|
+
|
|
848
|
+
**2. THE HALF A WIDER ROOT DOES NOT BUY: the recogniser assumes the file IS the document, and an
|
|
849
|
+
inline fixture is a `.ts` string literal.** Scope alone bought the dashed-SSN + email floor over
|
|
850
|
+
those 115 files and nothing else: no name, DOB, MRN, address or phone. **55 of the 115 carry an
|
|
851
|
+
inline `PID|` literal.** So there is now an embedded-literal pass (`scanEmbeddedHl7`) that pulls
|
|
852
|
+
`PID|…` runs out of source and hands them the **same field map** the whole-file parse uses; the
|
|
853
|
+
detectors are one shared function, not a second copy. Both halves are pinned RED-before /
|
|
854
|
+
GREEN-after, **and so is the residual**: the same values written as PROSE in the same file are
|
|
855
|
+
still not found. The pass is deliberately narrower than the whole-file parse and each limit is
|
|
856
|
+
stated, and two of them only after review refuted the first draft. Default delimiters only (an
|
|
857
|
+
embedded run has no reachable `MSH-2`); no unknown-segment backstop (a noise cannon over program
|
|
858
|
+
text, so an inline `Z…` name is missed); a `${…}` interpolation is **erased, not read**
|
|
859
|
+
(`familyName`, `content`, `marker` and `pidSurname` were each reported as a patient name), and the
|
|
860
|
+
bound there is **any value reached through an interpolation, including one whose literal is in the
|
|
861
|
+
same file** (measured: `const fam = "<surname>"` interpolated into a PID line exits 1 on the MRN
|
|
862
|
+
and DOB and is **silent on the name**); and **a run is cut at the first source quote, which cuts
|
|
863
|
+
HL7 data too** (the two-character literal `""` is HL7 v2's explicit-null field value, grounded in
|
|
864
|
+
this repo's own parser rather than a clause number nobody verified, so
|
|
865
|
+
`PID|1|""|MRN001||<surname>^<given>||<dob>|F` exits **1** at a `.hl7` path and **0** inline,
|
|
866
|
+
because the extracted run is `PID|1|`; `O'Brien` truncates identically). The last one is a MISS
|
|
867
|
+
and is written down rather than patched: the enclosing quote style is not knowable from one line,
|
|
868
|
+
and a wrong guess reads program text as patient data.
|
|
869
|
+
|
|
870
|
+
**`src/` changed intent, not just scope**, and the comment that said otherwise is corrected: the
|
|
871
|
+
embedded pass runs on **every** non-fixture target, so a JSDoc `@example` carrying a PID line with
|
|
872
|
+
a non-allow-listed name now reds (measured: exit 0 on `d8d3be3`, exit 1 here). This repo
|
|
873
|
+
hard-requires an `@example` on every public export, so a new example must use allow-listed tokens
|
|
874
|
+
or add its own, exactly like a fixture. The committed corpus is green as it stands.
|
|
875
|
+
|
|
876
|
+
**3. ▶(b): a regular blob staged at exactly `test/fixtures` got the shape floor only, and
|
|
877
|
+
`--staged` IS the pre-commit hook.** Scope and the mode check passed it through; the **detector
|
|
878
|
+
gate** dropped it, because `looksLikeHl7` wanted a `test/fixtures/` prefix or an `.hl7` suffix and
|
|
879
|
+
the root's own path has neither. Measured twice: six segment-aware hits at
|
|
880
|
+
`test/fixtures/<name>.hl7`, exit **0** at `test/fixtures`; a dashed SSN in the same blob exited
|
|
881
|
+
**1** either way, so the floor held and only the segment-aware tier was missing, so it is one clause, not
|
|
882
|
+
a new rule.
|
|
883
|
+
|
|
884
|
+
**4. `-B` IS NOT INERT, AND THIS SCANNER SAID IT WAS** (found by `terminology#45`, reproduced
|
|
885
|
+
here). The permissive half of a "do not add these flags" paragraph. The mechanism is sharper than
|
|
886
|
+
"a `B` record `AMTU` drops": a complete rewrite under `-B` prints status letter **`M`** with a
|
|
887
|
+
break score (the digits move with how much old content survives, so none are quoted here), one
|
|
888
|
+
path, which `RAW_RECORD` parses happily. But `--diff-filter`
|
|
889
|
+
classifies a broken pair as **`B`** whatever letter it prints. `--diff-filter=AMTU` returns empty;
|
|
890
|
+
`B` and `AMTUB` return the record. Through the scanner over a staged dashed SSN in a wholly
|
|
891
|
+
rewritten in-scope file: shipped argv exit **1**, same argv plus `-B` exit **0**. The filter is
|
|
892
|
+
now `AMTUB`, which costs the enumeration nothing (git cannot emit a broken pair without the flag).
|
|
893
|
+
**The pin that let the false claim live was a pin on a RENAME**, the one shape `-B` leaves alone.
|
|
894
|
+
|
|
895
|
+
**Two behaviour changes from `test` becoming a root.** `test/fixtures` is now an ENTRY INSIDE a
|
|
896
|
+
walked root, so a symlink there is refused by name at exit **2**, dangling or not: that closes,
|
|
897
|
+
**for `test/fixtures` only**, the "a root that is itself a symlink is followed" residual below.
|
|
898
|
+
It does not generalise: `src` and `test` have no walked parent, so both are still followed, and
|
|
899
|
+
the boundary is the PARENT, not the root. And the roots now NEST, so coverage credits **every**
|
|
900
|
+
root a file sits under rather than the first (crediting one would starve the other permanently,
|
|
901
|
+
and which one depended on list order with nothing saying so); walked entries are de-duplicated
|
|
902
|
+
for the same reason.
|
|
903
|
+
|
|
904
|
+
**The scanner's own test file could not be resolved with the allow-list**, and that is the
|
|
905
|
+
interesting part: it holds deliberate violators, every one asserted on, so allow-listing them
|
|
906
|
+
would make the positive tests vacuous and carving it out of the scan would be the gate excusing
|
|
907
|
+
itself from its own rule. Its violator identities are **assembled at runtime** instead (the
|
|
908
|
+
discipline the file already used for its SSN sentinel), so the temp files it builds still carry
|
|
909
|
+
the full violator bytes and the tracked file holds no PHI-shaped run. One registrable `.org`
|
|
910
|
+
email in it became a permanently unregistrable `.invalid` one on the way past. Every other new
|
|
911
|
+
allow-list token was **surfaced by the gate**, not guessed at. **23 net new tests** in
|
|
912
|
+
`test/scripts/phi-scan.test.ts` (78 to 101), plus several existing ones rewritten where the
|
|
913
|
+
widening inverted their verdict, each change of verdict recorded rather than deleted.
|
|
914
|
+
|
|
915
|
+
**Two `PRE-EXISTING` minors folded in with this item are ALREADY CLOSED here and were not
|
|
916
|
+
reopened**, measured rather than assumed: a missing or unreadable allow-list exits **2** (not the
|
|
917
|
+
**1** reserved for hits), `readdirSync` failing on a root exits **2**, and unmerged (`U`) entries
|
|
918
|
+
are already in the filter.
|
|
919
|
+
|
|
920
|
+
- **The PHI scan's observation rule was GLOBAL, so one root vouched for the other
|
|
921
|
+
(`PHI-SCAN-OBSERVED-NOTHING-IS-GLOBAL`).** `pnpm phi-scan` with no arguments (what CI runs) walks
|
|
922
|
+
two roots, `test/fixtures` and `src`, and refused a sweep only when it had read **zero files in
|
|
923
|
+
total**. Any one surviving file satisfied that, so a single `src/` module vouched for the whole
|
|
924
|
+
fixture corpus and vice versa. **Reproduced on `04e4bc6` before the fix, three ways**, each
|
|
925
|
+
printing `[phi-scan] OK: no hits` and exiting **0**: `test/fixtures` moved aside, `test/fixtures`
|
|
926
|
+
replaced by a **dangling symlink**, and `src` moved aside. The first two leave all 106
|
|
927
|
+
non-markdown fixtures unread; the third leaves all 95 source files unread.
|
|
928
|
+
|
|
929
|
+
**The dangling case is the sharpest, and nothing else in the scanner can see it.** `existsSync`
|
|
930
|
+
**follows** the link and answers false, so `walk()` returns before `readdirSync` and the
|
|
931
|
+
not-a-regular-file refusal never fires: that rule only ever classifies entries found **inside** a
|
|
932
|
+
root, and this is the root itself. Absent, dangling and empty are one state to the walk. This
|
|
933
|
+
reporter prints no file count either, so there was not even a suspicious denominator to notice.
|
|
934
|
+
|
|
935
|
+
**The rule is now per-root.** All-mode refuses (exit **2**) unless **every** declared root
|
|
936
|
+
yielded at least one file that was actually read, and the refusal names the starved roots.
|
|
937
|
+
Observing nothing at all is the all-starved case of that one rule, not a second rule beside it,
|
|
938
|
+
so the previous wording carries. Hits found under the roots that **did** yield are printed before
|
|
939
|
+
the refusal, so an incomplete sweep never swallows a real finding; the exit code is still 2.
|
|
940
|
+
|
|
941
|
+
**Deliberately unchanged:** `--staged` and named-path mode make no per-root promise, because
|
|
942
|
+
neither enumerates a root. `--staged` legitimately has nothing to scan when a commit touches only
|
|
943
|
+
markdown, and named-path mode is bounded by the caller's argv. Both boundaries are pinned by
|
|
944
|
+
tests, and widening the rule to either was run as a mutation rather than argued.
|
|
945
|
+
|
|
946
|
+
**The granularity is the declared root and nothing finer, which is a real bound, not a slogan.**
|
|
947
|
+
Three states still exit **0**, each measured with the rule in place and each now recorded in the
|
|
948
|
+
scanner's own limits list with the command that produced it and pinned by a characterization
|
|
949
|
+
test: a directory missing from **inside** a root (`mv test/fixtures/canonical ..` still prints
|
|
950
|
+
`OK: no hits`, 27 fixtures unread); a root that is **itself a symlink**, which is followed, so
|
|
951
|
+
the rule is satisfied by whatever is on the other side of it; and the floor is **one file**,
|
|
952
|
+
whatever the root used to hold. One shape often listed with those is **not** open here and is
|
|
953
|
+
written down so it is not ported in as if it were: a root that is a regular file, or a link to
|
|
954
|
+
one, refuses at exit **2** rather than the 1 this gate reserves for hits, because `walk()`
|
|
955
|
+
already wraps `readdirSync` and rethrows the `ENOTDIR` on the scanner's own channel.
|
|
956
|
+
|
|
957
|
+
Repository commit-gate only. No runtime or API change.
|
|
958
|
+
|
|
608
959
|
- **A staged RENAME bypassed the PHI scan's pre-commit route entirely
|
|
609
960
|
(`PHI-SCAN-RENAME-BLIND-AT-PRECOMMIT`).** `git diff --cached --raw --diff-filter=AMT` returns
|
|
610
961
|
neither `R` (rename) nor `C` (copy), and rename detection is on by git's default, so
|
|
@@ -1640,15 +1991,3 @@ real JS object (literal,`Map`, `Proxy`, or otherwise) can hold two same-named en
|
|
|
1640
1991
|
`src/helpers/`, `src/serialize/`, `src/builder/`. Lint, format, and
|
|
1641
1992
|
TypeScript settings come from the shared `@cosyte/*` config packages
|
|
1642
1993
|
(ESLint 9 + `typescript-eslint`). CI across Node 22 / 24.
|
|
1643
|
-
|
|
1644
|
-
### Changed
|
|
1645
|
-
|
|
1646
|
-
### Deprecated
|
|
1647
|
-
|
|
1648
|
-
### Removed
|
|
1649
|
-
|
|
1650
|
-
### Fixed
|
|
1651
|
-
|
|
1652
|
-
### Security
|
|
1653
|
-
|
|
1654
|
-
[Unreleased]: https://github.com/cosyte/hl7/commits/main
|
package/README.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
<
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
<a href="https://cosyte.com">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://cosyte.com/tile/cosyte-lockup-tile-on-dark-1200x300.png">
|
|
4
|
+
<img alt="Cosyte: a plus mark set in two overlapping rounded squares, one solid and one outlined, beside the Cosyte wordmark" src="https://cosyte.com/tile/cosyte-lockup-tile-on-light-1200x300.png">
|
|
5
|
+
</picture>
|
|
6
|
+
</a>
|
|
5
7
|
|
|
6
8
|
# @cosyte/hl7
|
|
7
9
|
|
package/dist/index.cjs
CHANGED