chore(scripts): give the recorder-wait census an AST matcher, and its fixtures a home - #8719
Conversation
…dler's reload `expect(deletes).toEqual([])` was dated to the first PUT by `waitFor(puts)`. That covers the delete scan only because the scan happens to run before the save loop in `handleObjectsChange` — a property of the page, not one the file asserts. Anchor it on `reload()`, the handler's last statement, so the emptiness is read after every write the handler makes. Measured: with a stray `reset` issued after the saves, the `puts` wait leaves this assertion green while C0/H1/H2 in the same file — which already anchor on the reload — go red. objectui#8690 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
The detector answers where to look, never what is wrong, so it is a census tool and NOT wired into CI: a gate on this list would institutionalise the batch repair the card exists to prevent. Two recorder-matching modes, because the choice moves the numbers, and the header records what each one measured on the card's base commit rather than quoting the card's counts as reproduced. Changeset declares an empty frontmatter: test-only plus one repo script, nothing published moves. objectui#8690 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
…isagree about The census script's numbers move with an internal choice its header stated but never measured. objectui#8703 measured it, and the answer is wider than the mode choice: - The two `--recorder-match` modes are incomparable by construction — each flags a shape the other cannot see (forced on fixtures, both directions). - On da5e4f6 the buckets are nevertheless nested: ident's 15 is a subset of path's 18, ident-only is empty. "They do not contain each other" is an in-principle claim, false as a measurement of this tree. - The header's stated `path` blind spot (pushed bare, read as `host.inits[0]`) is wrong: both modes miss that shape. - Two mode-independent rules dominate the mode choice: the forward window ends at the next `await` in the FILE rather than at the end of the enclosing test, and any textual occurrence counts as a read. Seven of the eighteen strict flags on that tree point at a declaration, a destructuring, a reset or a function parameter — not at a read. Header rewritten to carry that, plus a caveat printed next to the counts. The detector's behaviour is unchanged: both modes print byte-identical site lists. Script and changeset only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
… fixtures a home objectui#8704. The census resolved a recorder by the SPELLING of its `.push(` site, ended its forward window at the next textual `await` IN THE FILE, and counted every textual occurrence as a read. Seven of the eighteen strict flags it reported at da5e4f6 were therefore a declaration, a destructuring, a reset or a function parameter — and the same truncation went blind on a genuine cross-recorder read one ordinary `await` further on. The default matcher is now an AST pass: - identity over bindings and aliases, not names, one hop through a helper's parameter and a same-file factory's return value; - windows scoped to the enclosing test body, in statements, ending at the next awaited SETTLING anchor rather than at any `await`; - every occurrence classified read / write / declaration. The original census is kept behind `--matcher=regex`, so objectui#8690's and objectui#8703's published numbers stay reproducible from this file rather than being claims about a deleted script. Verified: at da5e4f6 it still prints 159 / 15 strict for `ident` and 167 / 18 strict for `path`, exactly as published. objectui#8703's five throwaway fixtures are committed as the script's test suite, with the sixth objectui#8704 asked for — the absence read objectui#8690 repaired, which must STAY flagged, because "flag nothing" is strictly worse than the bug and passes every other case. Both matchers are pinned over the same six files, so the repair is pinned as a direction. The header's "no count printed here is a corpus fact" caveat STAYS, with its residuals restated: no type checker, a window rule that is a judgement rather than a fact, and a flag that is still a site to read and not a defect. The census remains out of CI; the pin test runs the matcher over the fixtures only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
One conflict, and it is an artefact of the merge base rather than a real divergence: `scripts/census-recorder-wait-shape.mjs` is absent at the merge base (da5e4f6), so both sides read as an ADD. Measured, not assumed: main's side of the conflict is blob 3732191, and that is byte-identical to this branch's own parent commit 3778170 — the version #8706 landed is exactly the version this branch built its AST matcher on top of. So resolving to this branch's side drops no byte main carries. git rev-parse MERGE_HEAD:scripts/census-recorder-wait-shape.mjs git rev-parse 3778170:scripts/census-recorder-wait-shape.mjs -> 3732191 (both) Byte identity is not claim identity, though: the header main carries makes five claims, and whether each still HOLDS after the AST matcher is a separate question this merge does not answer. The commit that follows answers it claim by claim. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
… file
The merge before this one took this branch's side of an add/add conflict and
proved that drops no BYTE main carries. It does not prove it drops no CLAIM:
the header main landed makes five, and each needed a verdict against the AST
matcher that now sits underneath it. Four survived the rewrite. One had not.
RESTORED — the two regex modes' strict buckets are NESTED on this tree.
The rewrite kept "the modes are incomparable by construction" (M1) and dropped
the measurement that says the union of the two is just `path`'s bucket. M1 is
about shapes; on this tree ident-only is EMPTY, so a reader could take M1 as a
reason to run both modes and union them, which buys nothing. Re-measured on
both trees rather than copied forward:
at da5e4f6 ident 15 ⊂ path 18 ident-only EMPTY, 3 path-only
with main @ a9bc029 ident 12 ⊂ path 15 ident-only EMPTY, the SAME 3
and the same three `PermissionMatrixEditor` sites are the path-only ones in
both. The companion claim — that the mode choice was never the largest source
of movement — is restored as arithmetic the file can be re-run to check: the
mode separates THREE sites, D1 and D2 separate SIXTEEN (−7/+9, both verified
by diffing the site lists at da5e4f6).
ADDED — R2', the half of D1 that did NOT get repaired.
The window is scoped to the enclosing FUNCTION body. That kills the runaway
half (it can never reach the next `it`) and leaves the truncation half alive,
pointed the other way: a wait hosted in a helper the test awaits covers none of
the CALLER's statements. Forced on three probes — the same cross-recorder read
draws a flag with the wait inline and ZERO with it moved into a helper. Not a
corner: 503 of this tree's 3945 `await waitFor(...)` sites have their window
owned by an inner function, 464 of those a helper. The regex matcher was blind
here too, differently, so this is a surviving blind spot rather than a
regression — the header says which, and says the one shape where the old
matcher's runaway window did reach the read and this one does not.
The banner the reader running the script actually sees said the three error
sources were removed. Two were, plus half of the third; it now says that.
Also recorded: the counts move with the TREE alone. Merging main moved the
population 2776 → 2786 and every strict bucket down (AST 20 → 18, path 18 → 15,
ident 15 → 12) with the matcher byte-identical, because #8707/#8711/#8713 each
anchored a wait. That is claim 4 demonstrating itself.
Unchanged and re-verified: the census stays OUT of CI. No workflow and no
package script runs it; the only caller is the pin test, over six fixtures.
objectui#8704
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
|
Contract review — accepted, flipped out of draft, auto-merge armed. 28 of 32 green with four shards running; nothing failed. I dispatched this as a conflict resolution and said the one thing that would make it fail was silently dropping a caveat that still holds. One was being dropped, and the merge caught it. Claim 2 — the nested-buckets measurement — had vanished in the branch's rewrite. It is restored and re-measured on two trees rather than copied forward:
The three are the The conflict was never a divergence, and that had to be measured rather than assumed. The per-claim verdicts are honest in both directions, and that is what makes the header trustworthy now:
The unplanned measurement is the best evidence in the report. With the matcher byte-identical, merging The fixture legs are shown red against a mutated matcher with on-disk proof in both directions and restore proven by state, and On the residual — I agree it should not be a card. R2′ and the Generated by Claude Code |
Fixes #8704
scripts/census-recorder-wait-shape.mjsexists only on those branches, so there is nothingon
mainto repair. This branch was cut from #8706's head rather than duplicating the file;until it lands, this PR's diff shows its files too. My own change is exactly ten files:
the script, its pin test, seven fixtures plus their local harness, and one changeset.
#8706 entered the merge queue while this was being written;
mainhas also advanced pastda5e4f69eindependently, so amainmerge is owed once #8706 lands — ⛔ never a rebase.Measurements below are anchored at
da5e4f69e(the tree objectui#8690 and objectui#8703published their numbers on) so they are directly comparable, and repeated at this branch's
base where noted.
Leg 1 — every defect observed producing its wrong answer, on the PRE-repair script
objectui#8703's five throwaway fixtures were rebuilt and run through the script taken from
the committed tree (
git show HEAD:scripts/..., blob3732191f), in a throwaway git reposo its
git ls-filespopulation is the six files and nothing else. Both modes:--recorder-match=ident--recorder-match=path:19:21await:29:29f4 carries the card's own a/b reproduction verbatim, plus two cases added to SEPARATE the
two defects, because the fixed versions mask each other on a/b alone. The six wrong f4 flags,
by the line each lands on:
The repair
Default matcher is now an AST pass over
typescript's parser (no type checker, no program):union onto one key —
const a = b,const p from obj destructured, both object-literalproperty forms, plus two one-hop interprocedural rules: a recorder reached through a
same-file helper's PARAMETER, and one reached out of a same-file factory's single returned
object literal (directly or through a destructuring). Canonicalisation is prefix-aware:
an alias declared on
serverhas to reachserver.savedOptsbelow it.its enclosing function body; it can never reach the next
it. It ends at the next awaitedsettling anchor (
waitFor,waitForElementToBeRemoved,findBy/findAllByqueries) —
await.await Promise.resolve()settles nothing, and treatingit as an anchor is exactly what made the old matcher blind on f5.
pop,shiftand
spliceobserve contents, so they count as reads.⭐ The original census is kept, behind
--matcher=regex, so objectui#8690's andobjectui#8703's published numbers stay reproducible from this file instead of becoming claims
about a deleted script. Verified at
da5e4f69e: it still prints 159 flags / 15 strict / 10files for
identand 167 / 18 / 12 forpath— the published numbers, exactly.Leg 2 — the repaired matcher, same six fixtures
f1
:19, f2:21, f3:22, f5:23, f6:29— five flags, each on the intended read.f4: zero. Every one of the six answers moved from wrong to right, and f3 and f5 moved
from an empty list to the correct one.
Leg 3 — the corpus, before and after, decomposed
At
da5e4f69e, 2776 tracked test files:identpathThe strict delta against
pathdecomposes exactly, with no residue:form-onchange-wiring/ twoDatasetWidget/optionLabelI18n/ twoObjectView.tableColumnsForwardingdeclarations and resets, and theproviderCtxIdentity.discardedfunction parameter. Not one other flag was removed.factory return, or a destructured factory return (
ObjectChart.optionColors×3,DatasetWidget.relabel,ObjectView.expandGate,providerCtxIdentity.discarded×2) —and two reads it had truncated away behind an ordinary
await(
anonSeedScope-5746.enumerationbehindawait settle(),rowRecordCrudVerdictbehindawait act(...)). Two of the nine are hand-verified GENUINE reads the old matcher hadlost.
18 − 7 + 9 = 20. At this branch's base (
da5e4f69e+ #8702 + #8706) the same run reads167 / 17 / 11 for
pathand 138 / 19 / 11 for the AST matcher — one fewer on each side,because #8702's own repair removed its site. Adding this PR's pin test moves the population
2776 → 2777 and contributes zero flags in every matcher.
⭐ Against the 16 hand-verified labels: 16 / 16 agree
objectui#8690's nine (all genuine reads; 1 repaired, 8 sound) — eight are flagged.
The ninth,
MetadataObjectsPage.lookupKeying.test.tsx:380, is absent, and a control provesthat absence is #8702's REPAIR and not blindness: run over the
da5e4f69ecopy of that onefile the AST matcher flags
:380— objectui#8690's single real defect, at the hand-verifiedline — and over this branch's copy it flags nothing.
objectui#8703's seven: all six "NOT A READ" verdicts are absent, and the one genuine read
(
PermissionMatrixEditor.scope.test.tsx:177) is flagged. objectui#8704's own extraobservation, the
makeFetcherparameter atproviderCtxIdentity.discarded:515, is absent too.genuine reads — the three
PermissionMatrixEditorserver.savedOptssites andObjectChart.optionColors:285/DatasetWidget.relabel:206— because binding identity isnarrower than name identity: the pushes happen through a helper parameter and a factory
return. That is a recall regression, it was caught only by checking against the hand verdicts,
and the two one-hop rules exist to answer it.
⛔ What the header caveat becomes: it STAYS
The three error sources the card names are gone and the fixtures prove it. That makes the
number better and still not quotable, so the caveat is restated rather than deleted, with
its residuals now measured:
array from another module, through two helpers, or out of a factory with more than one
return, is still invisible. Unmeasurable by construction.can only ever LOSE a flag, never invent one, and the test-scoped window keeps it from
crossing tests.
actto the anchor set takes the strict bucket 19 → 18 and total flags 138 → 132, removingexactly
rowRecordCrudVerdict.test.tsx:235. Neither choice is provably right.agreeing says the list is a good list of PLACES TO READ; it says nothing about any site
being wrong. 8 of the 9 genuine reads were, and remain, sound by construction, which no
matcher can see.
⇒ Quote a number from here as "sites this instrument points at", never as "sites of this
shape". The honest one-line answer to the card's question: the matcher earned a
decomposable number, not a quotable one.
Ablation — four legs, all discriminating
Every leg mutates a READ SITE inside the matcher, never a fixture (that would be
circular), from a committed tree, with
trap ... EXIT INT TERMand absolute paths; eachmutation is proved on disk in both directions (anchor count 1 → 0, injected marker 0 → 1,
git hash-objectagainst the HEAD blob, plus a line-total gate) and restored by state(
git diff HEADempty and the on-disk blob equal to the HEAD blob, both checked). Per-testclassification from vitest's JSON reporter.
expected [ 'scratch.length = 0;' ] to deeply equal []expected [] to deeply equal [ 'expect(payloads[0]).toBe(2);' ][]expected [] to deeply equal [ 'expect(deletes).toEqual([]);' ]⭐ A4 is the one that justifies the sixth fixture: under it, "the runaway window flags NOTHING
in f4" stays green, along with every other "must be empty" assertion. An implementation
strictly worse than the bug passes this suite everywhere except f6.
Fences honoured
never over the corpus, so nothing in CI depends on what the repository reads. Whether the
repaired matcher is now gate-worthy is reported, not implemented — and R2 alone says no:
a bucket that moves by one when
actchanges category is not a gate.The nine new corpus flags are characterised by what the flagged line IS, which is a property
of the instrument; none is claimed to be a defect. Four sit in
packages/permissions(objectui#8688 / PR test(permissions): wait on the array the assertion reads (objectui#8688) #8689 territory) and are left entirely alone.
every one was restored by state.
Verification
pnpm exec vitest run scripts/from the repo root with paths: 128 passed | 2 skipped(130 files), 3708 tests passed. The two
❌banners in that log are the deliberatefailure-path stdout of the population-collapse and governed-guard pin tests, not failures.
pnpm exec vitest run scripts/__tests__/census-recorder-wait-shape.test.ts: 11 passed.pnpm type-check:scriptsgreen — and not vacuous:tsc --listFilesconfirms all ninenew
.tsfiles, including every fixture, are in the checked program.pnpm exec eslinton the script, its pin test and the fixture directory: clean.⛔ No
--no-inline-config(an objectstack convention that manufactures errors here).node scripts/check-control-bytes.mjs,node scripts/check-changeset-presence.mjs: green.The changeset declares empty frontmatter — the real exemption;
skip-changesetis aphantom label here. The gate's own verdict line: "Every one of them has an EMPTY frontmatter
— declared as releasing nothing, which is the explicit exemption and a complete answer to
this gate."
node scripts/check-governed-queue-guard.mjs --testover all ten changed paths:"NOT GOVERNED — 10 path(s) checked against 5 governed surface(s); none matched."
is stacked on two unlanded PRs.
🤖 Generated with Claude Code
https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
Generated by Claude Code