Commit a2cf0f1
fix(scripts): check-bash32-floor refuses a census with a hole in it instead of printing a shrunken one (#18846)
Fixes #18465
Clause-②: no
`listPopulation` enumerates the population from the **git index** (`git
ls-files`) and judges
each member from the **disk** (`readFileSync`). Every path the index
listed and the disk could
not supply was dropped in a silent `catch { continue }` at exit 0, so a
sparse checkout, a
partially materialised worktree, a `--root` pointed at one, or a
deletion that is not staged
yet made the gate print a **shrunken-but-plausible census as its
verdict**.
The skip stays. A deleted-but-indexed path is genuinely not a script to
judge, and the existing
comment saying so is correct about the judgement. What goes is the
silence.
## What changed
`scripts/check-bash32-floor.mjs`, one file:
- `listPopulation` returns `unreadable` — one entry per path, carrying
the errno reason the OS
gave (`ENOENT: no such file or directory, open '...'`). ⛔ Per path with
its reason, never a
count: a count is the silent skip with a number attached, which is the
same quiet pass #4690
refuses. `unsupportedConstructs` in this file already takes exactly that
disposition toward a
skip, and `report()` names a file, a line and a spelling for the same
reason.
- `scanTree` carries it through; `main()` refuses on a non-empty
`unreadable`.
- The refusal is ordered **before** the empty-population branch,
deliberately. When every
indexed path is unreadable the population is empty too, and #4690's
message — "the walk found
nothing" — would then be a false sentence: the walk found plenty and
read none. Two different
answers, two different remedies, and a self-test case pins them apart.
- The docblock gains the index-vs-disk section, with the measurement
that chose refusal.
- One new self-test battery, 11 cases, and `SELF_TEST_BATTERY_FLOOR` 18
→ 19 so the new battery
cannot be deleted without reddening.
## Refuse, or speak and pass? The reading decided
Refusing reddens normal CI immediately if the skip count is ever
non-zero on a complete
checkout, so that was measured first rather than preferred:
| leg | reading |
|:---|:---|
| four independent **complete** checkouts (`objectstack`, this worktree,
`-18592`, `-18745`) | indexed 400/402/402/402 — **skipped 0** in every
one |
| every indexed path under the walk roots, at `origin/main`, `HEAD` and
the four refs the card names (`8a70e1bf6`, `f836fb209`, `ce7bae8b44`,
`e915c190e`) | **100644 / 100755 only** — no `120000` symlink, no
`160000` gitlink |
| `.gitmodules` | absent — the repo declares no submodule |
| who runs this gate | `lint.yml:678` alone (`pnpm check:bash32-floor`);
no hook, no other caller |
⇒ Nothing but a genuinely incomplete tree can produce a skip here, and
the only caller does a
fresh complete checkout. Refusal is safe, and it is what "Absence must
be loud — prefer failing
to falling back" asks for. The one shape where a developer sees a new
red is a local `rm` whose
deletion is not staged yet; the refusal names that cause first and says
how to clear it.
## The two controls
Both run the **same fixture** against both gates — 7 shell files indexed
(6 by `.sh`, 1 by
shebang), 4 removed from the **disk only**, index never touched (the
card's leg B).
**LIT — the behaviour flip.** Matching exit codes would prove nothing,
so here is the flip:
```
BEFORE index lists 7 EXIT 0
stdout: ✓ check-bash32-floor: 3 tracked shell file(s) ...
census: 2 by .sh extension, 1 by shebang alone; 19 constructs checked
stderr: (empty)
AFTER index lists 7 EXIT 1
stdout: (empty — the green census line is never printed)
stderr: ✗ check-bash32-floor: 4 path(s) ... are listed in the git index but could
not be read from disk, so this census has a hole in it and is not a verdict.
scripts/c.sh ENOENT: no such file or directory, open '.../scripts/c.sh'
scripts/d.sh ENOENT: ... (and e.sh, f.sh, each with its reason)
```
The lit control is non-zero — the shrunken census really was produced —
so the reading is not
void.
**DARK — must read 0, and does.** The identical fixture complete on
disk, plus the real
checkout, before and after:
```
fixture complete 7 tracked shell file(s), census 6 + 1, EXIT 0 identical
real checkout 31 tracked shell file(s), census 29 + 2, EXIT 0 identical
```
Byte-identical, not merely equivalent: `sha256` of both DARK transcripts
is
`4406e0e791002c16bafa4dc50f5c925cf8938ac1c7c97c9911f6e9e27dff1ca5`,
`diff` exit 0, 841 bytes
each. **No new output, no new refusal on a complete tree.**
## Ablation — the new battery can be shown to fail
Run from the committed state, mutation proven on disk before the run
(injected text present 1,
removed text present 0), restored with `git checkout HEAD -- THE-PATH`
under a `trap`:
- reverting the reporting half (`unreadable.push(...)` → a bare skip)
turns the self-test **red
at 6 of 177 cases**, naming exactly the lit half;
- the three DARK cases and "the SKIP itself is kept" stay **green** —
the ablation removes the
reporting, not the skip, and precisely the reporting cases speak. That
is the discrimination,
not just a red.
- restore proven by blob hash, not by an exit code: `git hash-object` =
`git rev-parse HEAD:...`
= `8c8eda638a116c118e2d8dab33ea1ace40473f7d`, `git diff HEAD` empty,
porcelain clean.
This ablation earned its keep: the **first** run crashed the battery at
its third case with a
`TypeError` instead of failing it, which killed every later case —
including both DARK legs —
before the battery floor and the verdict handshake could speak. The
assertions now read through
an optional binding, and the reason is written at the site.
## objectui#8694 — audited, both directions, no coupling
The card records this as UNAUDITED because that repo was not checked
out. It is checked out
here, so it was read rather than guessed:
- `scripts/upstream-port-pin.json` registers
`scripts/check-bash32-floor.mjs` at ref
`61362932b5ad4c85b39169e70cf9be64d4332ce5`, digest
`0da45049c680c2b2d58d52d637f17f7c45421a46a92a8dd0e6385dadb384dffa`.
Verified from this side:
`git show 6136293:scripts/check-bash32-floor.mjs | sha256sum` gives
that exact digest, so
the pin is real and points at an immutable blob.
- `check-upstream-port-parity.mjs` ⛔ **fetches nothing** — its own
docblock says so, and
`grep` for `fetch(`/`api.github`/network verbs finds only that sentence.
It verifies
objectui's ported bytes by reversing the declared divergences and
comparing against the
**pinned digest**; the pinned digest IS its input.
⇒ A change here at a **new** commit cannot redden that gate: ref
`61362932b` is untouched by
this PR (and is an ancestor of this branch, `merge-base --is-ancestor`
exit 0, with a control
commit also reading 0). Nothing in this repo reads objectui's ledger
either. The real
consequence is ordinary port drift — objectui's copy falls one change
further behind, and a
future `--resync` there carries this along with a re-declared divergence
list. That is
objectui's own maintenance; objectui#8694 is already closed, and this PR
deliberately does not
widen into that repo.
## Verification
`origin/main` was merged in before this PR opened; the delta versus the
merge base is still
exactly one file, and no regeneration was deferred (`os-regen-pending`
absent).
- `pnpm check:bash32-floor` (self-test + real run) — green both before
and after that merge:
**177 cases**, real tree 31 shell files (29 by extension, 2 by shebang),
0 findings.
- **All 28 derived gate families run, all green.** Derived by `node
scripts/pm/dispatch-gates.mjs
--commands --repo objectstack-ai/objectstack` and reconciled with
`--ran`, recording each
family's exit code captured **before** any pipe. Derivations taken
before and after the merge
produced an identical 28-command list, and the post-merge derivation
carries no STALE TREE
note. Two families needed a second run and are reported as such rather
than as passes:
`check-comment-mask-corpus` first exited **3** (PREREQUISITE NOT MET — a
fresh worktree with no
`node_modules`; NOT MEASURED, not a finding) and is green after `pnpm
install` (6846 files, 0
disagree); `check:pm-dispatch-gates` first exited **124** (a `timeout`
wrapper fired, so no
verdict was reached) and was re-run with a real budget.
- `grep -naP` over the changed file for control bytes: none, beside
`check:nul-bytes` itself.
- **Lint, as a declared narrowing.** The repo-wide `pnpm lint` (`eslint
. --no-inline-config`) is
CI's run. Targeted here, with the three readings that make the narrowing
a measurement rather
than a skip: ① the universe is **6846 files**, enumerated by ESLint's
own walk over its own
config (`new ESLint({ ruleFilter: () => false })`, so the walk runs and
no rule does), and this
file is in it; ② the targeted run linted **1 file**, counted from
`--format json`, **0 errors /
0 warnings**; ③ invariance — this repo enables no type-aware linting, in
the config's own
words: *"this repo runs one `eslint.config.mjs`, which never enables
type-aware linting (no
`parserOptions.project`, no typed `@typescript-eslint` rules) for ANY
file, test or not"*
(`eslint.config.mjs`). With no cross-file type program, a one-file diff
cannot move the verdict
on any untouched file, so the 6845 files not linted here could not have
changed.
## Acceptance notes
- **noted, not filed:** the `--root` flag takes `process.argv[rootFlag +
1]` with no check that
an argument follows, so `--root` as the last token reads `undefined` and
fails later with a
less direct message. Out of scope here, mechanically unrelated to the
census, and no PR or
person is heading for that line. Successor: none.
- Nothing publishes from this diff — measured, not assumed: the root
package is `private: true`,
no package's `files[]` escapes its own directory (so no tarball can
contain a repo-root
`scripts/` path), and `check-bash32-floor` has **0** references under
`packages/` against a
positive control (`defineStack`) that matches there. Hence
`skip-changeset` rather than a
changeset, and `Clause-②: no` above.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3)_
Co-authored-by: Claude <noreply@anthropic.com>1 parent 88aa326 commit a2cf0f1
1 file changed
Lines changed: 226 additions & 8 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
174 | 174 | | |
175 | 175 | | |
176 | 176 | | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
177 | 206 | | |
178 | 207 | | |
179 | 208 | | |
| |||
789 | 818 | | |
790 | 819 | | |
791 | 820 | | |
| 821 | + | |
| 822 | + | |
| 823 | + | |
| 824 | + | |
| 825 | + | |
| 826 | + | |
| 827 | + | |
| 828 | + | |
| 829 | + | |
| 830 | + | |
| 831 | + | |
| 832 | + | |
| 833 | + | |
| 834 | + | |
| 835 | + | |
| 836 | + | |
| 837 | + | |
792 | 838 | | |
793 | 839 | | |
794 | 840 | | |
| 841 | + | |
| 842 | + | |
| 843 | + | |
| 844 | + | |
| 845 | + | |
795 | 846 | | |
796 | 847 | | |
797 | 848 | | |
| |||
803 | 854 | | |
804 | 855 | | |
805 | 856 | | |
| 857 | + | |
| 858 | + | |
806 | 859 | | |
807 | 860 | | |
808 | 861 | | |
809 | 862 | | |
810 | 863 | | |
811 | 864 | | |
812 | | - | |
813 | | - | |
| 865 | + | |
| 866 | + | |
| 867 | + | |
| 868 | + | |
| 869 | + | |
| 870 | + | |
814 | 871 | | |
815 | 872 | | |
816 | 873 | | |
817 | 874 | | |
818 | 875 | | |
819 | 876 | | |
820 | 877 | | |
821 | | - | |
| 878 | + | |
822 | 879 | | |
823 | 880 | | |
824 | 881 | | |
825 | 882 | | |
826 | | - | |
| 883 | + | |
827 | 884 | | |
828 | 885 | | |
829 | | - | |
| 886 | + | |
830 | 887 | | |
831 | 888 | | |
832 | 889 | | |
| |||
846 | 903 | | |
847 | 904 | | |
848 | 905 | | |
| 906 | + | |
| 907 | + | |
| 908 | + | |
| 909 | + | |
| 910 | + | |
| 911 | + | |
| 912 | + | |
| 913 | + | |
| 914 | + | |
| 915 | + | |
| 916 | + | |
| 917 | + | |
| 918 | + | |
| 919 | + | |
| 920 | + | |
| 921 | + | |
| 922 | + | |
| 923 | + | |
| 924 | + | |
| 925 | + | |
| 926 | + | |
| 927 | + | |
| 928 | + | |
| 929 | + | |
| 930 | + | |
| 931 | + | |
| 932 | + | |
| 933 | + | |
| 934 | + | |
| 935 | + | |
| 936 | + | |
| 937 | + | |
| 938 | + | |
| 939 | + | |
| 940 | + | |
| 941 | + | |
| 942 | + | |
| 943 | + | |
| 944 | + | |
| 945 | + | |
| 946 | + | |
| 947 | + | |
849 | 948 | | |
850 | 949 | | |
851 | 950 | | |
| |||
902 | 1001 | | |
903 | 1002 | | |
904 | 1003 | | |
| 1004 | + | |
905 | 1005 | | |
906 | 1006 | | |
907 | 1007 | | |
908 | 1008 | | |
909 | 1009 | | |
910 | 1010 | | |
911 | 1011 | | |
912 | | - | |
| 1012 | + | |
913 | 1013 | | |
914 | 1014 | | |
915 | 1015 | | |
| |||
1276 | 1376 | | |
1277 | 1377 | | |
1278 | 1378 | | |
| 1379 | + | |
| 1380 | + | |
| 1381 | + | |
| 1382 | + | |
| 1383 | + | |
| 1384 | + | |
| 1385 | + | |
| 1386 | + | |
| 1387 | + | |
| 1388 | + | |
| 1389 | + | |
| 1390 | + | |
| 1391 | + | |
| 1392 | + | |
| 1393 | + | |
| 1394 | + | |
| 1395 | + | |
| 1396 | + | |
| 1397 | + | |
| 1398 | + | |
| 1399 | + | |
| 1400 | + | |
| 1401 | + | |
| 1402 | + | |
| 1403 | + | |
| 1404 | + | |
| 1405 | + | |
| 1406 | + | |
| 1407 | + | |
| 1408 | + | |
| 1409 | + | |
| 1410 | + | |
| 1411 | + | |
| 1412 | + | |
| 1413 | + | |
| 1414 | + | |
| 1415 | + | |
| 1416 | + | |
| 1417 | + | |
| 1418 | + | |
| 1419 | + | |
| 1420 | + | |
| 1421 | + | |
| 1422 | + | |
| 1423 | + | |
| 1424 | + | |
| 1425 | + | |
| 1426 | + | |
| 1427 | + | |
| 1428 | + | |
| 1429 | + | |
| 1430 | + | |
| 1431 | + | |
| 1432 | + | |
| 1433 | + | |
| 1434 | + | |
| 1435 | + | |
| 1436 | + | |
| 1437 | + | |
| 1438 | + | |
| 1439 | + | |
| 1440 | + | |
| 1441 | + | |
| 1442 | + | |
| 1443 | + | |
| 1444 | + | |
| 1445 | + | |
| 1446 | + | |
| 1447 | + | |
| 1448 | + | |
| 1449 | + | |
| 1450 | + | |
| 1451 | + | |
| 1452 | + | |
| 1453 | + | |
| 1454 | + | |
| 1455 | + | |
| 1456 | + | |
| 1457 | + | |
| 1458 | + | |
| 1459 | + | |
| 1460 | + | |
| 1461 | + | |
| 1462 | + | |
| 1463 | + | |
| 1464 | + | |
| 1465 | + | |
| 1466 | + | |
| 1467 | + | |
| 1468 | + | |
| 1469 | + | |
| 1470 | + | |
| 1471 | + | |
| 1472 | + | |
| 1473 | + | |
| 1474 | + | |
| 1475 | + | |
| 1476 | + | |
| 1477 | + | |
| 1478 | + | |
| 1479 | + | |
| 1480 | + | |
| 1481 | + | |
| 1482 | + | |
| 1483 | + | |
| 1484 | + | |
1279 | 1485 | | |
1280 | 1486 | | |
1281 | 1487 | | |
| |||
1449 | 1655 | | |
1450 | 1656 | | |
1451 | 1657 | | |
1452 | | - | |
| 1658 | + | |
| 1659 | + | |
| 1660 | + | |
1453 | 1661 | | |
1454 | 1662 | | |
1455 | 1663 | | |
| |||
1542 | 1750 | | |
1543 | 1751 | | |
1544 | 1752 | | |
1545 | | - | |
| 1753 | + | |
| 1754 | + | |
| 1755 | + | |
| 1756 | + | |
| 1757 | + | |
| 1758 | + | |
| 1759 | + | |
| 1760 | + | |
| 1761 | + | |
| 1762 | + | |
| 1763 | + | |
1546 | 1764 | | |
1547 | 1765 | | |
1548 | 1766 | | |
| |||
0 commit comments