Skip to content

chore(scripts): record what the recorder-wait census's two matchers disagree about - #8706

Merged
os-justin merged 3 commits into
mainfrom
claude/issue-8703-matcher-dependent-strict-bucket
Sep 9, 2026
Merged

chore(scripts): record what the recorder-wait census's two matchers disagree about#8706
os-justin merged 3 commits into
mainfrom
claude/issue-8703-matcher-dependent-strict-bucket

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #8703

⚠️ Stacked on PR #8702 — please land that one first

scripts/census-recorder-wait-shape.mjs exists only on claude/issue-8690-recorder-wait-audit, whose PR #8702 is not merged. That branch is based on the same commit as this one (da5e4f69e), so this branch was fast-forwarded onto it rather than duplicating the file. Until #8702 lands, this PR's diff shows its three files too. My own change is exactly two files: scripts/census-recorder-wait-shape.mjs (header, plus one printed caveat) and .changeset/issue-8703-matcher-dependent-strict-bucket.md. Nothing else on this branch is mine, and the plugin-designer test change belongs to #8702's card.

Deliverable A — seven readings, zero repairs

All seven sites re-located by content on da5e4f69e (the addresses held). Six of them are not reads at all, and the seventh was forced and measured sound.

site flagged line is verdict
form-onchange-wiring.test.tsx:207 const received: Record_[] = []; NOT A SITE — declaration in the next it; the wait's test closes at 203
DatasetWidget.localSelectI18n.test.tsx:356 const blobs: any[] = []; NOT A SITE — next it; block closes at 347
DatasetWidget.optionLabelI18n.test.tsx:313 const { requested } = installMetaRouter(...) NOT A SITE — a different describe; blocks close at 304/305
DatasetWidget.tableTotalsRow.test.tsx:319 const blobs: Blob[] = []; NOT A SITE — next it; block closes at 313
ObjectView.tableColumnsForwarding.test.tsx:136 const seen: any[] = []; NOT A SITE — a different helper; the wait's helper closes at 132
ObjectView.tableColumnsForwarding.test.tsx:149 gridSchemas.length = 0; NOT A SITE — a reset, in yet another helper
PermissionMatrixEditor.scope.test.tsx:177 expect(server.savedOpts[0]).toMatchObject(...) SOUND — genuine read, forced

For each of the six, the wait's enclosing it or helper closes before the flagged line and the flagged recorder is never mentioned inside the wait's own block — so there is no cross-recorder read in scope to repair. This is the objectui#8704 defect class, filed separately.

The one genuine read, forced

PermissionMatrixEditor.scope.test.tsx:177 waits server.saved and reads server.savedOpts[0], the same construction as the two packageDoorFacets sites objectui#8690 measured sound. Both pushes are consecutive synchronous statements in one save double (lines 72-73, no await between), so no ordering can exist between them — but that is a reading, and the bar is a forcing.

Leg 1 — split the co-located pair by 50ms (setTimeout(() => { server.savedOpts.push(opts); }, 50)), mutation proven on disk in both directions (blob d86134f3… to 5ef3556d… vs the HEAD blob, anchor counts 1→0 and 0→1, line-total gate 263 = 263), restored by state (git diff HEAD empty):

1 of 4 failed — "lists only the package objects, then merges the slice on save"
AssertionError: expected undefined to match object { mode: 'draft', packageId: 'app.a' }
  at .../PermissionMatrixEditor.scope.test.tsx:177:33

Exactly the flagged line, and only it; the three other tests and the server.saved reads above it stayed green. So the assertion is live — this is not "both are undefined and nobody notices" — and since the ordering it depends on cannot occur in the real double, the site is sound. No repair, therefore no legs 2-4: there is no new wait to check under the forced ordering and no pre-fix pin to restore from the base blob. Saying so is more honest than manufacturing them.

Baseline before the mutation: 4 of 4 passed. Per-test classification from vitest's JSON reporter in every leg.

⭐ Deliverable B — the disagreement, characterised

Recorded in the script header. Summary of what was forced on fixtures (a throwaway git repo, five files, run through the real script in both modes — never read off the regexes):

  1. Incomparable by construction, both directions demonstrated. A recorder's identity is its spelling at the .push( site, matched textually at the read site by a name regex whose lookbehind forbids a preceding ..
    • path only: pushed and read as the same member path (fixture f1 — flagged by path, invisible to ident).
    • ident only: pushed as a member, read under a bare alias (fixture f2 — flagged by ident, invisible to path).
  2. On this corpus the buckets are nevertheless nested. At da5e4f69e: ident 15 strict, path 18 strict, ident minus path = the empty set. The three extra are the app-shell server.saved / server.savedOpts sites. So "their strict buckets do not contain each other" is true in principle and false as a measurement of this tree — the union is just path's bucket, and no third mode is needed to cover it.
  3. The header's stated path blind spot was wrong. A recorder pushed bare and read as host.inits[0] is missed by both modes (fixture f3, zero flags in either) — the lookbehind blocks the dotted read regardless of mode. Shared blind spot, not a path one. Corrected in the header.
  4. The mode choice is not the largest source of movement. Two mode-independent rules dominate it — the forward window ending at the next await in the FILE rather than at the end of the enclosing test, and every textual occurrence counting as a read. Seven of the eighteen strict flags at da5e4f69e point at a declaration, a destructuring, a reset or a function parameter. Fixture f4 reproduces the class in isolation; fixture f5 shows the mirror loss — a genuine cross-recorder hazard one await further on draws zero flags in both modes.

Reported outcome: the counts are not corpus facts. objectui#8690's 9, this file's 15/18, and the 14/17 this branch prints (one site fewer, because #8702's own repair removed it) are all readings of (matcher mode × window rule × occurrence class × tree). The script now prints that caveat next to its own numbers, and the header says what a quotable matcher would have to do instead — resolve recorder identity over an AST, scope the window to the enclosing test body, classify occurrences as read/write/declaration. Writing it was out of scope; it is filed as objectui#8704. The fence stands: this instrument stays out of CI.

Reproduction of the base numbers: a detached comparison worktree at da5e4f69e prints 159/15 and 167/18, matching the header exactly; the only delta on this branch is #8702's repaired plugin-designer site.

The card's second, unmeasured observation — measured

The provider: 'value' control in ListView.objectProviderBinding-7477 was checked, since the card left that to its reader. A probe that starts a query 50ms after mount leaves both find-absence pins green (7 of 7 pass), while a temporary in-test diagnostic proves the deferred query really landed inside that same test — and the identical diagnostic fails with the probe removed, so it can fail. Green-while-meaningless against a deferred query, confirmed. Filed as objectui#8705, not folded in here: it is a mock-call absence, outside this detector's population.

Fences honoured

  • Not one of the ~151 non-recorder-wait flags was re-litigated.
  • The detector is not wired into CI.
  • objectui#8665's contractEnvelope-6839 family: no overlap. Its plugin-dashboard file is ObjectPivotTable; mine were the three DatasetWidget.* tests, and no test file was edited at all — every test-tree mutation in this PR's evidence was an ablation, restored by state.

Verification

  • node scripts/census-recorder-wait-shape.mjs in both modes: byte-identical site lists before and after the header edit; behaviour unchanged.
  • pnpm exec eslint scripts/census-recorder-wait-shape.mjs — clean.
  • node scripts/check-changeset-presence.mjs — passes; node scripts/check-changeset-no-major.mjs — passes. The changeset has empty frontmatter (the real exemption; skip-changeset is a phantom label here).
  • Control-character scan over the edited file: clean.
  • No package source changed, so no package test suite is affected; the ablation legs above ran the two touched test files from the repo root with paths, and both trees were restored (git diff HEAD empty, verified by state).

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

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3477.6 KB 3512.7 KB
Main entry chunk (gzip) 143.9 KB 350 KB
Entry file index-BkOatu4S.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.36KB 61.61KB
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.25KB 65.00KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 131.01KB 32.32KB
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

@os-justin
os-justin added this pull request to the merge queue Sep 9, 2026
Merged via the queue into main with commit 28efdcb Sep 9, 2026
35 checks passed
@os-justin
os-justin deleted the claude/issue-8703-matcher-dependent-strict-bucket branch September 9, 2026 01:12
os-justin pushed a commit that referenced this pull request Sep 9, 2026
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(tests): objectui#8690's strict bucket is matcher-dependent — 7 further strict-shape sites were never read

2 participants