Commit 10ea9eb
test(lint): skip a vanished entry in the startup-registry corpus walk (#20303)
Fixes #20269
Clause-②: no
## Rework round 2
The order of record is the seat's comment `5859739814` on #20269. It
rules **E**: the dev's option D (restore the fs walk and extend the
vanished-entry rule to the two legs round 1 measured open), plus `tmp`
in `SKIP_DIRS`. Head: `5327f65679`. Commits this round: `e3d892116a`,
`5327f65679`, both on top of the branch with no rebase and no
force-push.
### What changed
All changes are in
`packages/lint/src/lint-startup-registry-verdict.corpus.test.ts`:
- **The stage-1 walk is back** (from `606510b23c`), with
`statListedEntry` and its `stat`-and-`lstat` rule verbatim. The
ACCEPTED_WALK_RADII entry for this test stays exactly as it is.
- **The same rule now covers the two reads after the stat:**
- `readListedDirectory(dir)` covers a listed directory's `readdirSync`;
- `readCollectedFile(file)` covers the sweep's `readFileSync`, and the
`beforeAll` sweep now reads through it.
- Both ask `absentAtRead(err, path)`, which returns true only for
`ENOENT` when an `lstat` does not find a symlink.
- `ENOTDIR`, `EACCES` and every other code rethrow. So does a dangling
symlink, where the read answers `ENOENT` but `lstat` finds the link. So
does a missing ROOT: the root's own `readdirSync` is bare.
- There is no retry.
- One detail, fixed in `5327f65679`: an entry absent at the read and
back under the same name by the `lstat` is skipped as absent at the
read. Re-reading it would be a retry. Measured below: `e3d892116a` threw
on that case and the order's "skip on ENOENT only" does not.
- The stat leg alone keeps such an entry as present, as in stage 1,
because its `lstat` IS the stat.
- **`tmp` is in `SKIP_DIRS`.** The root `.gitignore` ignores `tmp/`, and
`git ls-files -- packages | grep -E '(^|/)tmp/'` counts **0** tracked
paths on this head. So nothing authored leaves the corpus, and the
`packages/cli/tmp` fixtures never enter it.
- The file header's false-green 1 and each helper's doc state the
tolerated error and what still throws.
### Corpus count
Head lists **2690** files. The set is identical to the stage-1 walk at
`606510b23c` on the same quiet tree (`comm -3` prints nothing).
### Probes on head `5327f65679`, 100 runs each
The harness extracts the walk verbatim per revision (`git show`),
transpiles it with the repo's TypeScript, walks the real tree, then
reads every collected file through the revision's own read helper, as
the suite's `beforeAll` does.
| probe | revision | threw | transient in corpus | race hit and absorbed
|
|---|---|---|---|---|
| (a) tsup create/unlink `packages/spec/tsup.config.bundled_probe.mjs` |
base `3f86dc52f2` (control) | 23/100, `ENOENT stat` | n/a | n/a |
| (a) tsup | **head** | **0/100** | n/a | 30 runs |
| (b) sibling mkdir / write `objectstack.config.ts` / `rm -r` under
`packages/cli/tmp/tmp-probe` | stage 1 `606510b23c` (control) | 31/100
(26 `ENOENT open`, 5 `ENOENT scandir`) | 67/100 | n/a |
| (b) fixture | **head** | **0/100** | **0/100** | never listed (`tmp`
skipped) |
| (c) the same loop under a NOT-ignored
`packages/cli/zz-probe-unignored` (the accepted residual) | stage 1
`606510b23c` (control) | 25/100 | 71/100 | n/a |
| (c) residual | `e3d892116a` | 15/100 (12 `ENOENT open`, 3 `ENOENT
scandir`: absent at the read, back by the `lstat`) | 64/100 | n/a |
| (c) residual | **head** | **0/100** | 57/100 (accepted, see notes) |
absent at the read skipped in 15 runs |
Every head run in (a) and (b) read exactly 2690 files. Probe (c) is the
only one that exercises the two new legs on head, since (b) is never
listed.
Cleanup: `ls` answers `No such file or directory` for all three probe
paths, `git status --porcelain` is empty, and `git status --ignored`
shows no probe leftovers.
### Still throws: one-time proof, not a permanent test
The head's walk, extracted verbatim, was run over a throwaway tree in
the scratchpad. Injected error codes stand in where running as root
cannot produce them: **10/10 ok**.
- The control collects `src/a.ts` and never anything under `tmp/`.
- A missing ROOT throws `ENOENT`.
- A dangling symlink throws `ENOENT` at the stat, at the read, and as a
dangling directory link at the `readdirSync`.
- `EACCES` throws at the stat, at a listed `readdirSync` and at the
read.
- `ENOTDIR` throws at a listed `readdirSync`.
- A plain file removed between collection and read is skipped (`null`).
### Tests (head `5327f65679`, one `os-verify-lock` hold, VERDICT
command-exit 0)
- `pnpm --filter '@objectstack/lint...' build` exit 0.
- The touched file alone: `pnpm --filter @objectstack/lint exec vitest
run --maxWorkers=2 --reporter=verbose
src/lint-startup-registry-verdict.corpus.test.ts` gives `Test Files 1
passed (1)` and `Tests 5 passed (5)`, with all five cases listed.
`packages/lint` declares no vitest projects.
- The whole package: `pnpm --filter @objectstack/lint exec vitest run
--maxWorkers=2` gives `Test Files 111 passed (111)` and `Tests 4273
passed (4273)`.
- `pnpm --filter @objectstack/lint typecheck` exit 0
(`check:test-typecheck: OK`).
### Gates
`dispatch-gates --commands` at `5327f65679` derives the same 52:
- **49 exit 0, `check:cross-package-test-inputs` included.** Its line:
"13 walked root(s) judged, 3 on ACCEPTED_WALK_RADII".
- 3 are NOT MEASURED, exit 3 with PREREQUISITE NOT MET:
- `check:dual-build-cjs-loads` and `check:type-check-debt` need the full
packages build;
- `check:lean-entry-closure` needs the objectql closure, which was not
built in this worktree. It was green in stage 1.
- `--ran`: "52 derived famil(ies) accounted for — 49 run, 3
NOT-MEASURED".
- CI on `5327f65679`: 30 success, 4 skipped, 0 failures. `Lint & Repo
Gates`, `Test Core`, `TypeScript Type Check`, `Build Core`, `Dogfood
Regression Gate` and `Governed Surface Queue Guard` all passed.
## Earlier rounds (superseded, kept for the record)
- **Round 1** (`5859311330`, ruling B, heads `f3b4bc2738` and
`b6fea03599`) built the corpus from `git ls-files`. Both probes were
0/100 and the count was 2690. But `check:cross-package-test-inputs` owes
this test's ACCEPTED_WALK_RADII entry only to a `readdirSync` /
`opendirSync` descent, and a git-listed corpus has none, so the gate
went red. Round 2 restores the walk.
- **Stage 1** (`606510b23c`) added `statListedEntry` (the rule
`scripts/check-error-status-conformance.mjs` pins, landed with PR
#20267, for #20225). The tsup probe went 23/100 on base to 0/100. It
left the `readdirSync` and `readFileSync` legs open, which round 2
closes.
## No changeset
`@objectstack/lint` publishes `["dist","README.md","CHANGELOG.md"]`.
Measured at stage 1: the change had 0 hits there, against 4 files for
the positive control `findStartupRegistryVerdicts`. Every round touches
only a `*.test.ts`. The PR ships nothing, and it carries
`skip-changeset`.
## Acceptance notes
**Accepted residual (seat `5859739814`):** an untracked, NOT-ignored
scratch directory under `packages/` would still be walked. Probe (c) on
head: 0/100 throws, but the transient `.ts` entered the audited corpus
in 57/100 runs. **Accepted**, because no such producer exists today. The
in-tree scratch roots measured are `packages/cli/tmp` (now skipped),
`packages/spec/.examples-build` (`check:skill-examples`, lint job only)
and `node_modules/.…` roots (skipped). All of them are gitignored.
**Family census:** `git grep -l readdirSync -- 'packages/**/*.test.ts' |
xargs grep -l statSync` at base `3f86dc52f2` returns **23 files**. The
card listed 9 at `4d7e740d`, and the dev had counted 12. Question A:
does it walk a tree a concurrent build writes into? Question B: does a
bare `statSync`/`lstatSync` run on a listed entry before a filter would
drop it? Fixed only where both answers are yes.
Where the transients land, read from source: bundle-require 5.1.0's
`defaultGetOutputFile` writes `tsup.config.bundled_ID.mjs` next to the
config it loads. That is the package root for the 21 packages with their
own `tsup.config.ts`, and the repo root for the 46 that build with
`--config` pointed at the root config. No `tsup`/`vite`/`vitest` config
lives under any `src/`. Vite 8.0.16 writes its bundled config under
`node_modules/.vite-temp/`. `packages/cli` builds with `tsc` into
`dist/`. `@objectstack/spec`'s generators write `json-schema/` and its
sibling artifact dirs at the package root, never into `src/`.
1.
`packages/cli/src/commands/migrate/multi-value-columns.no-auto-run.test.ts`
(`everyTsFile`): A no, it walks `packages/cli/src` only (B yes). Left
alone.
2. `packages/cli/src/utils/console-route-ledger.conformance.test.ts`
(`packageSourceFiles`): A no, `packages/cli/src` only. Left alone.
3. `packages/cli/src/utils/port-contract-single-source.test.ts`
(`everySourceFile`): A no, `packages/cli/src` only. Left alone.
4. `packages/cli/test/diff-usage-error-stream.e2e.test.ts`
(`commandFiles`): A no, `packages/cli/src/commands` only. Left alone.
5. `packages/cli/test/json-stdout-purity.e2e.test.ts` (`commandFiles`):
A no, `packages/cli/src/commands` only. Left alone.
6. `packages/cli/test/resolve-glob-lazy-walk.test.ts`: no walker of its
own. It spies on `fs.readdirSync`/`fs.statSync` to pin a product walker
over a fixture. Left alone.
7. `packages/client/src/envelope-caller-census.test.ts` (`walk`): A yes,
it walks the repo root. B no, because the stat sits in a catch-all
`continue` and is not bare. Left alone.
8. `packages/core/src/security/authz-store-unavailable.test.ts`
(`walk`): A yes, `packages/`. B no, because it uses `withFileTypes` and
stats only a symlink entry. Left alone.
9. `packages/core/src/security/operation-private-keys.pin.test.ts`: no
walker. The hit is prose explaining its move to git's file list. Left
alone.
10.
`packages/drivers/driver-sqlite-wasm/src/sqlite-wasm-driver-durability.test.ts`:
lists and stats a temp directory it created itself, a fixed fixture.
Left alone.
11. `packages/lint/src/lint-startup-registry-verdict.corpus.test.ts`
(`collectSourceFiles`): A yes, `packages/`. B yes, a bare `statSync`
before the `.ts` filter. **Fixed here**, with the rule on all three legs
plus `tmp` in `SKIP_DIRS`.
12. `packages/metadata/src/metadata-route-ledger.conformance.test.ts`
(`packageSourceFiles`): A no, `packages/metadata/src` only. Left alone.
13.
`packages/platform-objects/src/managed-api-method-affordance-sweep.test.ts`
(`walkObjectFiles`): A yes, `packages/`. B no, catch-all. Left alone.
14.
`packages/plugins/organizations/src/no-framework-dependents.pin.test.ts`
(`workspacePackageManifests`): A no. It lists `packages/` and the eight
group dirs one level deep, whose entries are package directories. The
transients land inside a package root or the repo root, which it never
lists. Left alone.
15.
`packages/plugins/organizations/src/open-only-wall-acceptance.test.ts`
(`workspaceManifests`): A no, the same shape as 14. Left alone.
16. `packages/plugins/plugin-auth/src/managed-extension-fields.test.ts`
(`walkObjectFiles`): A yes, `packages/`. B no, catch-all. Left alone.
17.
`packages/plugins/plugin-auth/src/platform-owner-email-reader-census.pin.test.ts`
(`sourceFiles`): A no, `plugin-auth/src` and `plugin-security/src` only.
Left alone.
18.
`packages/plugins/plugin-email/src/transports/smtp-port-contract.test.ts`
(`everySourceFile`): A no, `plugin-email/src` only. Left alone.
19. `packages/qa/downstream-contract/test/source-resolution.pin.test.ts`
(`literalSpecImports`): A no. It walks its own `src/` and `test/` in a
private package with no build script. Left alone.
20. `packages/spec/scripts/category-title.test.ts` (`moduleDirsOnDisk`,
and a docs `walk`): A no. It lists the top level of `packages/spec/src`,
and the docs walk uses `withFileTypes` with no stat. Left alone.
21. `packages/spec/src/data/api-methods-batch-conformance.test.ts`
(`walkObjectFiles`): A yes, `packages/`. B no, catch-all. Left alone.
22. `packages/spec/src/shared/retired-key-migrate-sentence.test.ts`
(`walk`, `walkMarkdown`): A no. It walks `spec/src`, `lint/src` and the
published `skills/` tree (with `lstat`), and no build writes into any of
them. Left alone.
23. `packages/spec/src/system/constants/platform-object-names.test.ts`
(`walk`): A yes, `packages/`. B no, catch-all. Left alone.
Totals: 23 hits. 1 answers yes to both and is fixed here. 22 do not and
are untouched. No file in `packages/cli`, `packages/core`,
`packages/plugins/plugin-email` or `packages/spec` is touched.
**Findings, as the seat disposed of them:**
- The `packages/cli/tmp` fixture overlap: **folded in** and closed by
`tmp` in `SKIP_DIRS` (probe (b)).
- The five catch-all walkers (7, 13, 16, 21, 23): **dropped — no
measured reach**.
- `withFileTypes` on a filesystem reporting `DT_UNKNOWN` (8): **dropped
— no measured reach**.
- The gate's blind spot for git-listed descents (round 1): **dropped —
no measured victim**.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01QcAS3qiYYZNezaxZxaUdMV)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 53a5fef commit 10ea9eb
1 file changed
Lines changed: 128 additions & 14 deletions
Lines changed: 128 additions & 14 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
19 | 19 | | |
20 | 20 | | |
21 | 21 | | |
22 | | - | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
23 | 27 | | |
24 | 28 | | |
25 | 29 | | |
| |||
37 | 41 | | |
38 | 42 | | |
39 | 43 | | |
40 | | - | |
| 44 | + | |
41 | 45 | | |
42 | 46 | | |
43 | 47 | | |
| |||
66 | 70 | | |
67 | 71 | | |
68 | 72 | | |
69 | | - | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
70 | 81 | | |
71 | 82 | | |
72 | | - | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
73 | 88 | | |
74 | | - | |
75 | | - | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
76 | 123 | | |
77 | | - | |
78 | | - | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
79 | 188 | | |
80 | 189 | | |
81 | | - | |
82 | | - | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
83 | 196 | | |
84 | 197 | | |
85 | 198 | | |
| |||
89 | 202 | | |
90 | 203 | | |
91 | 204 | | |
92 | | - | |
93 | 205 | | |
94 | 206 | | |
95 | 207 | | |
| |||
207 | 319 | | |
208 | 320 | | |
209 | 321 | | |
210 | | - | |
211 | | - | |
212 | | - | |
| 322 | + | |
| 323 | + | |
| 324 | + | |
| 325 | + | |
| 326 | + | |
213 | 327 | | |
214 | 328 | | |
215 | 329 | | |
| |||
0 commit comments