Skip to content

test(plugin-charts): pin what the site-2 refusal settles to, not what it is not - #8711

Merged
os-justin merged 2 commits into
mainfrom
claude/issue-8708-chart-refusal-assertion-strength
Sep 9, 2026
Merged

test(plugin-charts): pin what the site-2 refusal settles to, not what it is not#8711
os-justin merged 2 commits into
mainfrom
claude/issue-8708-chart-refusal-assertion-strength

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #8708

What was wrong

ObjectChart.contractEnvelope-6839's site-2 refusal arm asserted .not.toBe('Apollo') — a negation over an open codomain. Every value except that one literal satisfied it: String(undefined), '', 'null', and every other value a resolver emits once it has stopped resolving anything.

Leg 1 — the vacuity, demonstrated rather than argued

Two read-site mutations were run against the pre-fix file. Both mutated resolveGroupByLabels in packages/plugin-charts/src/ObjectChart.tsx, never the pin; both ran from a committed tree with trap restore and were proved on disk by anchor counts, git hash-object against the HEAD blob, and a line-total gate; both restored by state (git diff HEAD empty).

Mutation A — "the read returns nothing, ever" (extractRecords(results) becomes []; on-disk cb9934d7 to 252cc4ad):

case verdict
site 1, all three PASSED
site 2 data FAILED
site 2 bare array FAILED
site 2 records (the refusal arm) PASSED

The refusal arm passes while the resolver resolves nothing at all. But this mutation is NEGATIVE, not a discriminator: the new pin passes under it too (re-run and confirmed, 4 passed / 2 failed, identical classification). That is structural — an arm asserting "this envelope resolves nothing" cannot, on its own, redden for an implementation that resolves nothing for everything. What catches mutation A is the file's two positive arms, not the refusal arm.

Mutation B — the sharp one (drop the || rawValue fallback, so an unresolved foreign key reaches the axis as the literal string undefined; on-disk cb9934d7 to 4e1f6df5):

All six cases GREEN. The file blessed a component that renders undefined on the axis for every unresolved lookup key — an implementation strictly worse than the bug the module docblock says every case must refuse.

The repair — assert the positive

expect(
  await labelThrough(asRecords),
  'a `records` envelope must leave the axis on the raw foreign key, unresolved',
).toBe('p1');

'p1', measured — not 'P1'. The card and the file's own helper docstring both claimed humanizeLabel uppercases the unresolved key. It does not: the lookup branch ends [groupByField]: idToName[rawValue] || rawValue, and humanizeLabel is not on that path at all. Probed directly (a deliberate toBe('___PROBE___') read, restored by state): expected 'p1' to be '___PROBE___'. A pin written for 'P1' would have reddened on a correct refusal. The docstring is corrected here for the same reason.

'p1' is not an accidental value — it is the raw-foreign-key axis the module docblock already describes as the silent-looking failure.

Legs 2-4

  • Leg 2 — new pin under mutation B: RED, and the only red in the file. AssertionError: expected 'undefined' to be 'p1' — exactly the String(undefined) family the card names.
  • Leg 3 — new pin, ordinary path: 6/6 green.
  • Leg 4, the control — under the identical mutation B (read-site hash 4e1f6df5, asserted equal to legs 1b/2), the pre-fix pin restored from the base blobgit checkout da5e4f69e -- FILE, on-disk git hash-object = c3d2540f8d44300a65e7c50e4d3ed284e787eaa9 = git rev-parse da5e4f69e:FILE, anchor counts .not.toBe('Apollo') = 1 and .toBe('p1') = 0 — passes 6/6. A strengthening, not a relocation.

The first leg-4 attempt was VOID (harness argument bug, exit 127, no JSON emitted) and is reported as void, not green; the run above is the fixed re-run.

Every verdict above is per-test classification from vitest's JSON reporter, never the text reporter.

Population sweep — reported, not batch-repaired

Across the seven contractEnvelope-6839 siblings, negated value matchers (.not.toBe / .not.toEqual / .not.toStrictEqual): charts 1, the other six 0. Control LIT — the same grep against this file's base blob finds the known .not.toBe('Apollo') at line 191, so the six zeros are readings.

Repo-wide the same shape occurs 541 times in packages/** test files. That is a pointer, not a diagnosis, and it is left untouched: most uses are legitimate (asserting two values differ), and telling those apart needs a read per site.

Scope and verification

  • Test-only. No production code changedObjectChart.tsx was mutated for ablation and restored by state each time.
  • pnpm exec vitest run packages/plugin-charts/52 files, 488 tests, all passed.
  • pnpm --filter @object-ui/plugin-charts run type-check — green, with the dependency closure built. Coverage measured, not assumed: tsc -p tsconfig.test.json --listFiles counts this test file 1, so the type-check really sees it.
  • node scripts/check-changeset-presence.mjs green on an empty-frontmatter changeset (the explicit "releases nothing" exemption; skip-changeset is a phantom label here). check-changeset-no-major green. check-control-bytes green, plus a direct control-byte scan of both changed files.
  • node scripts/check-governed-queue-guard.mjs --testNOT GOVERNED, 2 paths against 5 surfaces.
  • Verified at 5f18ba861. No overlap with objectui#8709 (plugin-map / plugin-timeline), objectui#8703 / PR chore(scripts): record what the recorder-wait census's two matchers disagree about #8706 (scripts/), or the PR feat(app-shell,i18n): announce inbox arrivals with a toast and a desktop notification (#7011) #8668 app-shell CI repair.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S


Generated by Claude Code

… it is not

`ObjectChart.contractEnvelope-6839`'s site-2 refusal arm asserted
`.not.toBe('Apollo')` — a negation over an open codomain. Every value except
that one literal satisfied it, including `String(undefined)`, `''` and `'null'`
— i.e. every value a resolver emits once it has stopped resolving anything.

Ablation on this file: deleting the `|| rawValue` fallback in
`resolveGroupByLabels`, so an unresolved foreign key reaches the axis as the
literal string `undefined`, left all six cases GREEN under the old pin. The
file blessed an implementation strictly worse than the bug it was written to
catch.

The arm now names the one value a correct refusal settles on: `'p1'`, the
foreign key verbatim, which is the raw-FK axis the module docblock already
describes. Measured, not assumed — the lookup branch ends
`idToName[rawValue] || rawValue`, so `humanizeLabel` is NOT on this path and
never uppercases the key. The helper docstring claimed `'P1'`; that claim is
corrected here, since a pin written for it would redden on a correct refusal.

Test-only. No production code changes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
…othing

Empty frontmatter — the explicit exemption for a test-only change, not a
workaround. `scripts/check-changeset-presence.mjs` goes green on it.

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) 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 5ece49b Sep 9, 2026
35 checks passed
@os-justin
os-justin deleted the claude/issue-8708-chart-refusal-assertion-strength branch September 9, 2026 01:15
os-justin pushed a commit that referenced this pull request Sep 9, 2026
… 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
akarma-synetal pushed a commit to akarma-synetal/objectui that referenced this pull request Sep 9, 2026
… fixtures a home (objectstack-ai#8719)

* test(plugin-designer): anchor the lookupKeying absence pin on the handler'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

* chore(scripts): keep objectui#8690's recorder-wait census as a script

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

* chore(scripts): record what the recorder-wait census's two matchers disagree 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

* chore(scripts): give the recorder-wait census an AST matcher, and its 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

* chore(scripts): resolve the census header claim by claim, not file by 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 objectstack-ai#8707/objectstack-ai#8711/objectstack-ai#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

---------

Co-authored-by: Claude <noreply@anthropic.com>
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.

ObjectChart's contractEnvelope-6839 site-2 refusal asserts .not.toBe('Apollo') — a shape undefined satisfies, so it cannot fail for the right reason

2 participants