Skip to content

chore(scripts): give the recorder-wait census an AST matcher, and its fixtures a home - #8719

Merged
os-justin merged 6 commits into
mainfrom
claude/issue-8704-census-matcher-ast
Sep 9, 2026
Merged

chore(scripts): give the recorder-wait census an AST matcher, and its fixtures a home#8719
os-justin merged 6 commits into
mainfrom
claude/issue-8704-census-matcher-ast

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #8704

⚠️ Stacked on PR #8706 (which is stacked on PR #8702) — land those first

scripts/census-recorder-wait-shape.mjs exists only on those branches, so there is nothing
on main to 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; main has also advanced past
da5e4f69e independently, so a main merge is owed once #8706 lands — ⛔ never a rebase.

Measurements below are anchored at da5e4f69e (the tree objectui#8690 and objectui#8703
published 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/..., blob 3732191f), in a throwaway git repo
so its git ls-files population is the six files and nothing else. Both modes:

fixture --recorder-match=ident --recorder-match=path what is wrong
f1 member push / member read miss :19 mode-dependent (M1)
f2 member push / bare read :21 miss mode-dependent, the mirror (M1)
f3 bare push / member read miss miss blind in BOTH — a genuine read (M2)
f4 runaway window 6 wrong flags 6 wrong flags D1 + D2
f5 hazard behind one await miss miss blind — the mirror loss (D1)
f6 absence read :29 :29 correct — the control

f4 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:

30  const second: number[] = [];      D1+D2  (the card's own case)
36  const third: number[] = [];       D1+D2
37  const scratch: number[] = [];     D1+D2
40  scratch.length = 0;               D2 ALONE — a reset, same test, window rule irrelevant
45  const shared: number[] = [];      D1+D2
55  expect(shared[0]).toBe(4);        D1 ALONE — a genuine READ, in the wrong test

The repair

Default matcher is now an AST pass over typescript's parser (no type checker, no program):

  1. Identity, not spelling. Identifiers resolve to bindings over a scope chain; six forms
    union onto one key — const a = b, const p from obj destructured, both object-literal
    property 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 server has to reach server.savedOpts below it.
  2. Test-scoped windows, in statements. The window is the statements after the wait inside
    its enclosing function body; it can never reach the next it. It ends at the next awaited
    settling anchor (waitFor, waitForElementToBeRemoved, findBy / findAllBy
    queries) — ⚠️ NOT at any await. await Promise.resolve() settles nothing, and treating
    it as an anchor is exactly what made the old matcher blind on f5.
  3. Read / write / declaration. Only reads flag. Conservative on purpose: pop, shift
    and splice observe contents, so they count as reads.

⭐ The original census is kept, behind --matcher=regex, so objectui#8690's and
objectui#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 / 10
files for ident and 167 / 18 / 12 for path — 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:

matcher total flags strict files
regex ident 159 15 10
regex path 167 18 12
AST 138 20 12

The strict delta against path decomposes exactly, with no residue:

  • −7. Precisely the seven objectui#8703 read and found were not reads at all — the
    form-onchange-wiring / two DatasetWidget / optionLabelI18n / two
    ObjectView.tableColumnsForwarding declarations and resets, and the
    providerCtxIdentity.discarded function parameter. Not one other flag was removed.
  • +9. Seven recorders the name matcher could not see — reached through a host object, a
    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.enumeration behind await settle(), rowRecordCrudVerdict behind
    await act(...)). Two of the nine are hand-verified GENUINE reads the old matcher had
    lost.

18 − 7 + 9 = 20. At this branch's base (da5e4f69e + #8702 + #8706) the same run reads
167 / 17 / 11 for path and 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 proves
that absence is #8702's REPAIR and not blindness: run over the da5e4f69e copy of that one
file the AST matcher flags :380 — objectui#8690's single real defect, at the hand-verified
line — 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 extra
observation, the makeFetcher parameter at providerCtxIdentity.discarded:515, is absent too.

⚠️ Two of those agreements were bought, not free. The first draft LOST five hand-verified
genuine reads — the three PermissionMatrixEditor server.savedOpts sites and
ObjectChart.optionColors:285 / DatasetWidget.relabel:206 — because binding identity is
narrower 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:

  • R1 — no type checker, one file at a time, and each interprocedural rule is ONE HOP. An
    array from another module, through two helpers, or out of a factory with more than one
    return, is still invisible. Unmeasurable by construction.
  • R1' — the parameter hop OVER-MERGES when a helper is called with different arrays. It
    can only ever LOSE a flag, never invent one, and the test-scoped window keeps it from
    crossing tests.
  • R2 — the window rule is a JUDGEMENT, not a fact. Measured, not asserted: adding
    act to the anchor set takes the strict bucket 19 → 18 and total flags 138 → 132, removing
    exactly rowRecordCrudVerdict.test.tsx:235. Neither choice is provably right.
  • R3 — and this is the one that matters. A flag is still not a defect. All 16 labels
    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 TERM and absolute paths; each
mutation is proved on disk in both directions (anchor count 1 → 0, injected marker 0 → 1,
git hash-object against the HEAD blob, plus a line-total gate) and restored by state
(git diff HEAD empty and the on-disk blob equal to the HEAD blob, both checked). Per-test
classification from vitest's JSON reporter.

leg mutation result
A1 read/write/declare classification disabled REDexpected [ 'scratch.length = 0;' ] to deeply equal []
A2 window ends at any awaited call again REDexpected [] to deeply equal [ 'expect(payloads[0]).toBe(2);' ]
A3 alias/identity resolution disabled RED — f2 and f3 both collapse to []
A4 ⭐ flag nothing (the caricature) RED on f6 onlyexpected [] 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

  • Not wired into CI. The pin test runs the matcher over the six committed FIXTURES only,
    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 act changes category is not a gate.
  • ⛔ Not one of the ~151 non-recorder-wait flags was re-litigated, and no site was re-audited.
    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.
  • No test file was edited. Every tree mutation in the evidence above was an ablation, and
    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 deliberate
    failure-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:scripts green — and not vacuous: tsc --listFiles confirms all nine
    new .ts files, including every fixture, are in the checked program.
  • pnpm exec eslint on 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-changeset is a
    phantom 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 --test over all ten changed paths:
    "NOT GOVERNED — 10 path(s) checked against 5 governed surface(s); none matched."
    ⚠️ Kept in draft regardless, and neither auto-merge nor the queue was touched: this PR
    is stacked on two unlanded PRs.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S


Generated by Claude Code

…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
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3479.1 KB 3512.7 KB
Main entry chunk (gzip) 143.9 KB 350 KB
Entry file index-TG0Fo8gd.js
Status PASS

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

Package Size Gzipped
app-shell (consoleActionDispatch.js) 0.20KB 0.19KB
app-shell (index.js) 15.67KB 5.75KB
app-shell (runtime-config.js) 20.68KB 7.36KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 10.06KB 3.86KB
auth (ActiveOrganizationStorage.js) 25.05KB 9.16KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 2.07KB 1.00KB
auth (AuthProvider.js) 40.18KB 10.59KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.15KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.65KB 2.22KB
auth (SocialSignInButtons.js) 9.61KB 3.89KB
auth (UserMenu.js) 3.41KB 1.23KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 40.21KB 10.80KB
auth (createAuthenticatedFetch.js) 8.46KB 3.43KB
auth (index.js) 3.19KB 1.44KB
auth (invitation-status.js) 1.22KB 0.70KB
auth (org-roles.js) 6.66KB 2.78KB
auth (phone-identifier.js) 1.11KB 0.66KB
auth (types.js) 0.59KB 0.35KB
auth (useAuth.js) 5.30KB 1.02KB
auth (useWorkspaceAdminStatus.js) 11.08KB 4.58KB
collaboration (CommentThread.js) 26.08KB 7.56KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 6.49KB 2.64KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.68KB 0.73KB
collaboration (useCollaborationTranslation.js) 6.05KB 2.52KB
collaboration (useCommentSearch.js) 1.98KB 0.88KB
collaboration (useConflictResolution.js) 7.75KB 1.86KB
collaboration (useMentionNotifications.js) 1.81KB 0.68KB
collaboration (usePresence.js) 6.33KB 1.84KB
collaboration (useRealtimeSubscription.js) 7.91KB 2.01KB
components (index.js) 499.42KB 114.32KB
core (index.js) 7.48KB 2.96KB
create-plugin (index.js) 10.12KB 3.28KB
data-objectstack (index.js) 198.39KB 55.29KB
fields (index.js) 244.96KB 61.76KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (builtinAggregateLabels.js) 0.86KB 0.49KB
i18n (currency.js) 1.22KB 0.64KB
i18n (fallbackInterpolation.js) 6.25KB 2.77KB
i18n (i18n.js) 6.57KB 2.76KB
i18n (index.js) 3.65KB 1.47KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 26.89KB 9.04KB
i18n (useDisplayLocale.js) 2.85KB 1.45KB
i18n (useObjectLabel.js) 34.34KB 9.17KB
i18n (useSafeTranslation.js) 5.60KB 2.33KB
layout (index.js) 38.84KB 10.94KB
mobile (MobileProvider.js) 0.92KB 0.49KB
mobile (ResponsiveContainer.js) 0.94KB 0.38KB
mobile (breakpoints.js) 1.51KB 0.70KB
mobile (createOfflineDataSource.js) 5.61KB 1.75KB
mobile (index.js) 1.99KB 0.87KB
mobile (offlineQueue.js) 3.91KB 1.35KB
mobile (pwa.js) 0.97KB 0.49KB
mobile (serviceWorker.js) 1.48KB 0.62KB
mobile (serviceWorkerSource.js) 3.41KB 1.48KB
mobile (useBreakpoint.js) 1.54KB 0.65KB
mobile (useGesture.js) 6.96KB 1.98KB
mobile (useOfflineSync.js) 1.99KB 0.72KB
mobile (usePullToRefresh.js) 2.53KB 0.85KB
mobile (useResponsive.js) 0.72KB 0.42KB
mobile (useSpecGesture.js) 4.39KB 1.66KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 13.52KB 4.88KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 6.24KB 2.16KB
permissions (discardProofCache.js) 1.04KB 0.55KB
permissions (evaluator.js) 8.39KB 3.10KB
permissions (index.js) 0.93KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.53KB
permissions (usePermissions.js) 4.83KB 2.27KB
plugin-ai (index.js) 15.16KB 3.68KB
plugin-calendar (index.js) 49.00KB 13.91KB
plugin-charts (index.js) 71.39KB 19.92KB
plugin-chatbot (index.js) 194.53KB 46.34KB
plugin-dashboard (index.js) 131.43KB 34.44KB
plugin-designer (index.js) 215.51KB 44.29KB
plugin-detail (index.js) 251.39KB 65.04KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 134.16KB 33.47KB
plugin-gantt (index.js) 167.16KB 40.99KB
plugin-grid (index.js) 208.18KB 56.62KB
plugin-kanban (index.js) 55.44KB 15.73KB
plugin-list (index.js) 112.73KB 27.69KB
plugin-map (index.js) 20.49KB 6.83KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 30.10KB 8.74KB
plugin-tree (index.js) 9.33KB 3.25KB
plugin-view (index.js) 84.54KB 20.84KB
providers (DataSourceProvider.js) 0.75KB 0.39KB
providers (MetadataProvider.js) 1.37KB 0.59KB
providers (ThemeProvider.js) 1.90KB 0.85KB
providers (UploadProvider.js) 11.66KB 3.50KB
providers (index.js) 0.45KB 0.23KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.62KB 2.34KB
react (LazyPluginLoader.js) 4.47KB 1.63KB
react (SchemaRenderer.js) 81.07KB 26.86KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 2.32KB 1.24KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 6.58KB 2.74KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 5.55KB 2.45KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (parse.js) 20.57KB 5.88KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 13.64KB 4.59KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 1.00KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 2.93KB 1.49KB
types (crud.js) 0.20KB 0.18KB
types (dashboard-filter-alias.js) 6.23KB 2.74KB
types (data-display.js) 3.75KB 1.85KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.85KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (expression.js) 0.20KB 0.18KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-inflight.js) 8.87KB 3.73KB
types (http-retry.js) 4.32KB 2.02KB
types (icon-key-migration.js) 4.26KB 1.63KB
types (index.js) 4.74KB 2.25KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 4.73KB 2.28KB
types (navigation.js) 0.20KB 0.18KB
types (objectql.js) 0.20KB 0.18KB
types (overlay.js) 0.20KB 0.18KB
types (permissions.js) 0.20KB 0.18KB
types (plugin-scope.js) 0.20KB 0.18KB
types (record-components.js) 0.20KB 0.19KB
types (record-semantics.js) 1.28KB 0.67KB
types (registry.js) 0.20KB 0.18KB
types (reports.js) 0.20KB 0.18KB
types (select-option.js) 0.20KB 0.19KB
types (spec-report.js) 5.05KB 1.93KB
types (spec-ui-namespace.js) 0.20KB 0.19KB
types (strict-authoring-face.js) 14.27KB 5.47KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 6.28KB 2.87KB
types (ui-action.js) 8.11KB 3.32KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

  • ✅ Core packages should be < 50KB gzipped
  • ✅ Component packages should be < 100KB gzipped
  • ⚠️ Plugin packages should be < 150KB gzipped

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
@github-actions github-actions Bot removed the plugin label Sep 9, 2026
@os-justin
os-justin marked this pull request as ready for review September 9, 2026 02:29
@os-justin
os-justin enabled auto-merge September 9, 2026 02:29

Copy link
Copy Markdown
Collaborator Author

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:

ident path ident−path path−ident
da5e4f69e 15 18 0 3
merged tree 12 15 0 the same 3

The three are the server.saved / server.savedOpts sites main's header named. Without that row, claim 1 reads as a reason to run both modes and union them — which on this tree buys exactly nothing. That is the difference between a caveat and a caveat that is still true.

The conflict was never a divergence, and that had to be measured rather than assumed. main's side of the add/add is blob 3732191fbbyte-identical to this branch's own parent commit 3778170a7. #8706 landed precisely the version this branch built its AST matcher on top of; git could not see the lineage only because the file is absent at the merge base. "Take ours" was right, but for a reason that says nothing about whether the claims survived — which is why the per-claim pass was the actual work and the file-level resolution was the easy part.

The per-claim verdicts are honest in both directions, and that is what makes the header trustworthy now:

  • Claim 1 kept but demoted — a fault of --matcher=regex, not of the file's output — and made executable rather than asserted: regex-path flags f1 not f2, regex-ident flags f2 not f1, both miss f3, the AST matcher flags all three. The erratum about the shared blind spot is carried forward verbatim rather than quietly dropped along with the claim it corrected.
  • Claim 3 split: D2 retired, the seven-non-reads kept, and the comparative framing upgraded from assertion to arithmetic — path−AST = 7 and AST−path = 9 exactly, so 18 → 20 is not "+2" but −7/+9, while the mode choice separates three sites. 16 versus 3.
  • ⭐ Claim 3's D1 half only half retired, with a residual neither card named: scoping the window to the enclosing function body kills the runaway direction and leaves truncation alive pointed the other way — a wait hosted in a helper covers none of the caller's statements. Forced on three probes with a control (pC flags, pA and pB zero), at a corpus scale of 503 of 3945 await waitFor sites.
  • ⚠️ And the place the repair loses ground is reported rather than buried: on the await act(...) shape the regex matcher flags and the AST one does not. Zero such sites today — recorded so it is not rediscovered as a surprise.
  • Claim 4's banner was wrong after the rewrite and is corrected rather than deleted — the reader running the script sees that line, not the header.
  • Claim 5 kept and re-verified mechanically: zero .github/workflows references, zero package.json script entries, the only executable caller being the fixture test. ⛔ Not promoted to a gate, which was the ruling.

The unplanned measurement is the best evidence in the report. With the matcher byte-identical, merging a9bc02996 moved the population 2776 → 2786 and every strict bucket down — AST 20 → 18, path 18 → 15, ident 15 → 12 — because #8707, #8711 and #8713 each anchored a wait. The header's thesis is that these counts are instrument readings rather than corpus facts; this is that thesis with a controlled measurement behind it, on a timescale of hours.

The fixture legs are shown red against a mutated matcher with on-disk proof in both directions and restore proven by state, and merge-tree re-runs clean against both the merged main and main as it stands now.

On the residual — I agree it should not be a card. R2′ and the act-callback asymmetry are properties of the instrument this card builds, the file is deliberately out of CI, and its header now tells every reader to quote its numbers as "sites this instrument points at". Filing an audit card for a documented residual of a non-gate would be the low-base-rate audit this seat has been declining all day. If someone wants the helper-hosted window scoped to the call site, that is a fresh card with its own fixture, since no corpus site exercises it today.


Generated by Claude Code

@os-justin
os-justin added this pull request to the merge queue Sep 9, 2026
Merged via the queue into main with commit 4857776 Sep 9, 2026
34 checks passed
@os-justin
os-justin deleted the claude/issue-8704-census-matcher-ast branch September 9, 2026 02:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

2 participants