@cosyte/astm 0.0.10 → 0.0.11
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 +120 -0
- 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 +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,88 @@ this file is maintained by hand (Changesets handles the version bump and publish
|
|
|
11
11
|
|
|
12
12
|
### Fixed
|
|
13
13
|
|
|
14
|
+
- **A symbolic link under a scan root read CLEAN on BOTH of the PHI scanner's enumerating routes, so
|
|
15
|
+
a link pointing at a file full of real identifiers passed the gate twice over**
|
|
16
|
+
(`PHI-SCAN-SYMLINK-BLIND-ON-BOTH-ROUTES`). Development tooling only: `scripts/phi-scan.ts` ships in
|
|
17
|
+
no tarball and the package's public surface is unchanged.
|
|
18
|
+
|
|
19
|
+
Reproduced on `8e8012d` before any fix, with a synthetic name-bearing payload (an ASTM `P` record
|
|
20
|
+
carrying a patient name, a mother's maiden name and a birthdate, plus a dashed SSN and an email at
|
|
21
|
+
a non-test domain) placed outside the walk roots and a link to it at `src/leak.ts`. `pnpm phi-scan`
|
|
22
|
+
printed `OK: no hits` and exited **0**; `pnpm phi-scan --staged`, after `git add`, printed
|
|
23
|
+
`OK: no hits` and exited **0**. Naming the link's target explicitly on the command line reported
|
|
24
|
+
**six** hits and exited 1, so the payload was always detectable, and detectable by this package's
|
|
25
|
+
own structured `P`-record detectors rather than only by the cross-cutting SSN/email floor. The two
|
|
26
|
+
routes simply never looked at it.
|
|
27
|
+
|
|
28
|
+
The two blindnesses are separate mechanisms and needed separate fixes. The walk enumerates
|
|
29
|
+
`Dirent.isFile()`, which is an **lstat** answer, so a link is neither a file nor a directory and
|
|
30
|
+
fell out of the loop with no record that anything had been skipped, and `isDirectory()` answers
|
|
31
|
+
false for a linked directory too, so a whole subtree could disappear the same way. The `--staged`
|
|
32
|
+
route reads content with `git show :<path>`, and **git stores a symbolic link as its target path
|
|
33
|
+
under mode `120000`**, so it was handed the target's path text and scanned that. That second route
|
|
34
|
+
is this repo's `pre-commit` hook, which is exactly where the claim that `--staged` covers a link
|
|
35
|
+
would have been trusted.
|
|
36
|
+
|
|
37
|
+
**Neither route is made to follow the link.** Following would read bytes the enumeration does not
|
|
38
|
+
control (outside the repo, a loop, a device, a FIFO that blocks the gate forever) and git does not
|
|
39
|
+
carry those bytes anyway, so a hit on them would be a claim about something no commit contains.
|
|
40
|
+
Instead the enumeration is narrowed: an entry under a scan root that is not a regular file
|
|
41
|
+
**refuses the scan** (exit 2, the existing "could not complete" code), naming every offender rather
|
|
42
|
+
than the first. The walk classifies by `Dirent` (symbolic link, FIFO, socket, block device,
|
|
43
|
+
character device); `--staged` now reads `git diff --cached --raw -z` instead of `--name-only` so
|
|
44
|
+
the destination mode is visible, and refuses mode `120000` and `160000` (a gitlink) wherever that
|
|
45
|
+
route's own path scope reaches them. A `--raw` record that does not parse refuses as well, rather
|
|
46
|
+
than being skipped into a silently shortened list.
|
|
47
|
+
|
|
48
|
+
**`T` (typechange) is in the `--diff-filter`, and leaving it out would have made the mode check
|
|
49
|
+
unreachable whenever the file being replaced was already tracked.** Replacing a **tracked** regular
|
|
50
|
+
file with a link is neither an add nor a modify: git raises `:100644 120000 <sha> <sha> T`, so
|
|
51
|
+
`--diff-filter=AM` deletes the record before any mode can be read. Measured on git 2.39.5 on this
|
|
52
|
+
tree: with `AM` the raw output for that stage is empty, and the unfixed scanner passed that stage
|
|
53
|
+
green. Typechange carries a single path, exactly like `A` and `M`, so admitting it costs the record
|
|
54
|
+
stride nothing, and it means the reverse typechange, a link replaced by a real file, is now scanned
|
|
55
|
+
as the ordinary file it became.
|
|
56
|
+
|
|
57
|
+
**A refusal names the entry's own repo-relative path and an engine-owned token for its kind, and
|
|
58
|
+
never the link target**, because a target path is text off the working tree and can itself carry
|
|
59
|
+
PHI. That is asserted rather than argued: the pinning payload and the target's own filename both
|
|
60
|
+
carry a synthetic person name, and every refusal message is checked to contain none of it. 10 of
|
|
61
|
+
the file's 30 cases are red on `8e8012d`.
|
|
62
|
+
|
|
63
|
+
**What this does not cover, stated narrowly, each measured.** Explicit-path mode already read
|
|
64
|
+
through a link and reported the target's hits; it is unchanged. The `--staged` path scope is
|
|
65
|
+
unchanged, still `test/fixtures/**` and `src/**.ts`, so a staged link outside it is still not
|
|
66
|
+
looked at; narrowing what a scope admits is not widening the scope. That scope also bounds the
|
|
67
|
+
gitlink half: a submodule staged at `test/fixtures/nested` is refused, while one at `src/nested` is
|
|
68
|
+
a directory name that fails the `.ts` suffix and is not looked at. `R` (rename) and `C` (copy) are
|
|
69
|
+
still **not** enumerated by `--staged` at all, so a staged rename that also appends PHI passes that
|
|
70
|
+
route; pre-existing, unchanged here, and admitting it needs the two-path record shape handled,
|
|
71
|
+
which is a scope decision rather than this one. A tracked file **absent from the worktree** is
|
|
72
|
+
still caught at `git add` time only.
|
|
73
|
+
|
|
74
|
+
**And this scanner still has no rule that a sweep observing ZERO targets must refuse**, which is
|
|
75
|
+
worth naming rather than leaving to be discovered. Measured: run from a tree with no `src/` and no
|
|
76
|
+
`test/fixtures/`, all-mode prints `OK: no hits` and exits 0, so a clean report can be a report
|
|
77
|
+
about a tree the scan never looked at, and **nothing backstops it**: the all-mode sweep is the
|
|
78
|
+
route with the hole, so it cannot be its own compensating control, and neither the `pre-commit`
|
|
79
|
+
hook nor CI asserts that the sweep observed anything. Five of the sibling scanners carry that rule
|
|
80
|
+
(`ccda`, `hl7`, `mllp`, `ncpdp`, `synth`) and the rest, this one included, do not; do not read that
|
|
81
|
+
as a majority practice. It is unchanged by this slice in either direction, and adding it is its own
|
|
82
|
+
slice, because the threshold is a judgement about this repo's corpus rather than a consequence of
|
|
83
|
+
anything here.
|
|
84
|
+
|
|
85
|
+
**The enumerate-then-read race is deliberately NOT closed here, and the deferral is measured
|
|
86
|
+
rather than assumed.** The scanner still has no tolerance for a file that vanishes between
|
|
87
|
+
enumeration and read, so a transient appearing under a walk root can refuse a whole sweep (exit 2,
|
|
88
|
+
fails closed). This repo's walk roots are `test/fixtures/` and `src/`, and nothing was observed
|
|
89
|
+
landing in either: polling both roots continuously across a real `pnpm build` saw only the tracked
|
|
90
|
+
corpus, while `tsup.config.bundled_*.mjs` (the transient that blocked a sibling's publish) appeared
|
|
91
|
+
at the **repo root**, which is not a walk root here. This repo's own suite writes its throwaway
|
|
92
|
+
trees under the OS temp directory, not under a scan root. So the shape is present and its trigger
|
|
93
|
+
is not, which makes closing it a separate slice with its own argument rather than a rider on a PHI
|
|
94
|
+
blindness fix. It is a different defect and it fails in the safe direction.
|
|
95
|
+
|
|
14
96
|
- **The frame encoder no longer writes a different character than the one it was handed**
|
|
15
97
|
(`ASTM-FRAME-BYTE-RESIDUALS`, closing known defect 7). `composeAstmFrames` turned a record given
|
|
16
98
|
as a `string` into bytes with `charCodeAt(i) & 0xff`, so any character above `U+00FF` was replaced
|
|
@@ -76,6 +158,44 @@ this file is maintained by hand (Changesets handles the version bump and publish
|
|
|
76
158
|
refuse the record string or reproduce it byte for byte" is asserted over 2,000 generated cases,
|
|
77
159
|
with its bound written into the test and a non-vacuity check that both sides of it are reached.
|
|
78
160
|
|
|
161
|
+
- **The `attw` publish gate now fails when the packed tarball carries no types, where the CLI exits
|
|
162
|
+
`0`** (`ATTW-FALSE-GREEN-PORT`). The `attw` script was the bare `attw --pack .`.
|
|
163
|
+
`@arethetypeswrong/cli` opens `getExitCode()` with `if (!analysis.types) return 0`, returning
|
|
164
|
+
before it ever reads the problem list, because an untyped package is a legitimate npm package and
|
|
165
|
+
the CLI treats "no types at all" as a description rather than a problem. No `--profile`,
|
|
166
|
+
`--ignore-rules` or config setting reaches that branch. For a package that ships types the same
|
|
167
|
+
result means the declarations were **not in the tarball**, which is a broken publish reported as a
|
|
168
|
+
pass. A false red costs an hour; a false green merges.
|
|
169
|
+
|
|
170
|
+
Measured on this package at `0.0.9`, on a quiet box with **no** concurrency, both states printing
|
|
171
|
+
"This package does not contain types." and exiting `0`: `rm -rf dist`, and
|
|
172
|
+
`rm -f dist/index.d.ts dist/index.d.cts`. The second is the realistic one, and the trigger is the
|
|
173
|
+
build rather than any race: timed on one real `tsup` run here, `dist/index.mjs` and
|
|
174
|
+
`dist/index.cjs` appeared at 1,176 ms and `dist/index.d.ts` and `dist/index.d.cts` at 3,063 ms, a
|
|
175
|
+
**1,887 ms window** in which `dist/` held JS and no declarations. A concurrent build or `clean` in
|
|
176
|
+
the same working tree lands the gate in that window. This is deliberately **not** answered with a
|
|
177
|
+
lock or a build queue: the gate has to be able to report that its own inputs were missing,
|
|
178
|
+
whatever removed them.
|
|
179
|
+
|
|
180
|
+
`attw` is now `node scripts/attw.mjs`, which keeps two nets because they catch different things. A
|
|
181
|
+
**preflight** checks that every relative path `package.json` promises (`main`, `module`, `types`,
|
|
182
|
+
`typings`, and every string leaf of `exports`) exists and is non-empty, and names the missing file
|
|
183
|
+
rather than leaving it to be inferred. A **post-check** promotes `attw`'s untyped sentence to a
|
|
184
|
+
failure, catching what the preflight structurally cannot: declarations present on disk but
|
|
185
|
+
excluded from the tarball by `files` or an `.npmignore`. No instance of that second case is on
|
|
186
|
+
record in this repo. Because the post-check reads a printed string, the routes that would hide it
|
|
187
|
+
are refused rather than tolerated: `--quiet`, `--format`, a `.attw.json` setting either, and
|
|
188
|
+
`--config-path` at a file setting either were each measured here to restore the exact exit `0`. The
|
|
189
|
+
refusal is by option name, wholesale, not by value, and the list is a record of what is closed
|
|
190
|
+
rather than a proof that the post-check cannot be blinded at all: `--definitely-typed` suppresses
|
|
191
|
+
the sentence by making the analysis typed, is equally true of the bare CLI, and is deliberately not
|
|
192
|
+
refused. The preflight is the net that reads no string. Other arguments are forwarded, so
|
|
193
|
+
`--profile node16` still works. `test/scripts/attw-gate.test.ts` pins the upstream exit `0` itself,
|
|
194
|
+
so an `attw` upgrade that fixes it or rewords the sentence reds the suite instead of letting the
|
|
195
|
+
net go quietly slack, and it carries a negative control plus a real `attw` failure, because a gate
|
|
196
|
+
that only ever fails is not a gate and one that swallows the status is not one either. **No
|
|
197
|
+
runtime code changed and the built output is identical.**
|
|
198
|
+
|
|
79
199
|
### Added
|
|
80
200
|
|
|
81
201
|
- **`ASTM_RECORD_FIELDS_UNSEPARATED`: a record that lost its fields no longer loses them in
|
package/dist/index.cjs
CHANGED