Skip to content

gate(tests): reject a test that resolves a path below the process cwd - #9190

Merged
baozhoutao merged 4 commits into
mainfrom
claude/issue-8953-cwd-rooted-test-gate
Sep 11, 2026
Merged

gate(tests): reject a test that resolves a path below the process cwd#9190
baozhoutao merged 4 commits into
mainfrom
claude/issue-8953-cwd-rooted-test-gate

Conversation

@claude

@claude claude Bot commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Fixes #8953

Root AGENTS.md teaches that a test reading the filesystem in an assertion roots on its own file and ⛔ never on process.cwd(), and the bullet ends by declaring that nothing enforces it. objectui#7799 repaired 13 instances of that class in a single day; it did not close the class. This is the gate, in scripts/, per the triage ruling — ⛔ no eslint-rules/ rule, ⛔ no AGENTS.md edit.

1. The population, derived — ⛔ process.cwd is one spelling, not the class

The crude grep the card and the claim both carry (packages/*/src test files mentioning cwd()) re-measures at 26 on 87f174c00, with controls (a nonsense token → 0, describe( → 2704). ⛔ That number is a floor, not a population, in both directions:

  • it counts files that only mention cwd() in prose;
  • it misses the evasive spelling the card itself names, and it misses a bare relative path (readFileSync('e2e/live/.auth/state.json')), which is 100% cwd-rooted and carries no cwd token at all;
  • it stops at packages/*/src, and the class is repo-wide.

The honest population is every filesystem call made by a test, classified by what its path argument RESOLVES from. Measured on the merged tree: 1884 filesystem calls in 387 of 3037 test files — 957 self-rooted, 518 absolute, 26 module-resolved, 7 ambient, and 376 whose root this gate cannot classify.

⚠️ What the detector CANNOT see — named, counted, and printed on every run

The census line ends with 376 root(s) NOT CLASSIFIED, and --blind enumerates them. That is deliberate: a gate that prints only what it checked reads as a claim about everything.

  • a root arriving as a function parameter or from an import. Binding resolution stops at the module edge, so a helper resolving a repository path on the test's behalf is invisible. This is the largest blind spot and it is structural — and it is not hypothetical: examples/schema-catalog/test/catalog-gallery-render.test.tsx handed process.cwd() straight to deriveRegistryKeys(), which does the reads. Found by reading the file while repairing its neighbours, ⛔ not by the gate. Repaired here and labelled in place as the shape the gate misses.
  • any environment variable other than PWD / INIT_CWD;
  • a dynamically-reached filesystem call (fs[name](p)), or one made by a helper module;
  • a cwd-rooted path handed to something that is not a filesystem call (a spawnSync cwd option, a glob library, import());
  • files that do not match TEST_FILE, and anything git does not track.

2. The detector's shape — ⛔ why it is not a one-spelling grep

  1. Sinks, not spellings. The scan starts at the filesystem call and asks what its path is rooted at.
  2. Sinks resolved by IMPORT PROVENANCE, not by name. packages/cli/src/__tests__/check-jsonc-parse.test.ts declares its own function writeFile(name, body) writing into a mkdtemp dir; a name match reports 12 violations there and every one is wrong. Measured: name-matching gave 28 violations, provenance-matching gives 8.
  3. Roots RESOLVED through the file's own bindings. Both examples/schema-catalog instances are const siteDir = path.join(process.cwd(), …) read three screens later through path.join(siteDir, f)the read line holds no cwd at all. This gate found them; a text search does not.
  4. Any expression that EVALUATES to the cwd counts — bare, via globalThis, via an as unknown as cast, via an alias — because the AST's call target is read, not the source text. process.env.PWD, resolve() with a relative argument, and a bare relative string are the same root under other names.

The verdict, stated exactly: an ambient root with at least one segment appended — resolving a path below a cwd that moves. existsSync(cwd) is not a violation; existsSync(join(cwd, 'pnpm-workspace.yaml')) is. That is the shape PR objectui#7806 rewrote browser-process-shim-scope.test.ts into, and the rule gets it right with no entry in any list. ⛔ The appended segments are not required to look like a repository path — requiring packages/-ish literals would be the grep thinking back in through the window.

3. The two-sided ablation, with the mutation proved on disk

Every leg: mutate → prove it landed (anchor counts + blob hash, an empty hash coded to read as FAILURE) → measure → restore under trap … EXIT INT TERM with git checkout HEAD -- ABSOLUTE_PATH → prove restore by blob equality + empty git diff HEAD.

FIRES on the real historical defect. gridArrayArmOrderby-8973.test.tsx put back to join(process.cwd(), …): anchors self-rooted 1→0, cwd-rooted 0→1, blob 8e129178…→4013af0c…. Gate exit 1, naming the site; the pin exit 1, 2 failed | 21 passed.

⭐ FIRES on the spelling a grep loses — the same file mutated to join((globalThis as unknown as { process: { cwd(): string } }).process.cwd(), …), blob 8e129178…→61c5b6c7…. On that one line:

instrument hits
objectui#7799's own census regex 0
a plain process.cwd grep 1
this gate exit 1, rooted at (globalThis as unknown as { process: { cwd(): string } }).process.cwd()

SILENT on the measured-immune files. ⚠️ The card and the claim both say six; PR objectui#7806's own per-file table names four (cli/app-generator, plugin-view ×3) — see §6. None is an unregistered violation. ⚠️ And the reason is not one reason: the three plugin-view probes are silent because the gate cannot classify them (their root comes out of Array#find), which is pinned as such so silence is never read as a clean verdict.

The non-vacuity floor. TEST_FILE mutated to a valid regex matching nothing (blob e6170de0…→d2f92c7b…): gate exit 1the population COLLAPSED — this run measured nothing, all four floors named, plus both registry entries reported stale. The pin: 6 failed | 17 passed. ⛔ A broken glob cannot read as clean. (A first attempt mutated the regex into a SyntaxError; that is NOT MEASURED, not a red gate, and the leg was redone.)

4. Three live defects repaired, and the prose that caused them

The gate was red on arrival with 8 reads in 3 files. All three are repaired under the bounded in-place exemption (same defect class; shape pinned by PR objectui#7796 / objectui#7806; no other claim on the files; same gate family, no new verification surface):

file reads
examples/schema-catalog/test/catalog-gallery-render.test.tsx 3 + 1 +1 = the deriveRegistryKeys(process.cwd()) the gate cannot see
examples/schema-catalog/test/plugin-dashboard-gallery-render.test.tsx 4
packages/plugin-grid/src/__tests__/gridArrayArmOrderby-8973.test.tsx 1 landed after objectui#7799's sweep

⭐ Each carried, in its own comment, a premise that had already been falsified when it was written. Two repeat "process.cwd() is the repo root by construction: scripts/vitest-invocation-guard.mjs refuses any run whose Vitest root is not it" — the guard checks vitest's root, which --root ../.. sets correctly while leaving the cwd in the package. The third says "this project's import.meta.url is not a file URL" — falsified by six readings on objectui#7800. Those comments are corrected, not moved: the defect was reasoned from written prose, so leaving the prose reproduces it.

Green under both invocations after the repair — the package-directory column is the acceptance reading:

repo root package directory
the three repaired files 3 files / 611 passed 2 + 1 files / 599 + 12 passed

5. Gate verdicts by context name

context command CI runs exit
Test (shard N/4) pnpm test --shard=N/4 — the new pin, run alone: scripts/__tests__/check-test-path-roots.test.ts 0 — 1 file / 23 tests
Test (shard N/4) affected packages: vitest run examples/schema-catalog/ packages/plugin-grid/ 0 — 155 files / 3245 tests
Test (shard N/4) the workflow-wiring pins my lint.yml edit could move (lint-workflow, dependabot-merge-gate, merge-queue-reporting, entry-guard-wiring, workflow-cache-save-bound, check-lint-rule-coverage) 0 — 7 files / 137 tests
Lint node scripts/check-test-path-roots.mjs (the step added here) 0
Lint node scripts/check-entry-guard.mjs 0 — 92 files, 0 hand-typed guards
Lint pnpm lint:root — the whole 336-file population, not a narrowing 0 — 0 errors, 32 warnings
Type Check pnpm type-check:scripts 0; --listFiles shows both new files in the program, so the green covers them
Changeset Declaration node scripts/check-changeset-presence.mjs 0 (was 1 before the changeset — quoted verbatim in the report)
Control Byte Scan node scripts/check-control-bytes.mjs 0 — 7385 files scanned

The gate list was checked against REQUIRED_CONTEXTS in scripts/dependabot-merge-gate.mjs (26 names, derived and tested), ⛔ not against any hand-counted phrase. No new check context is created: the Lint step lands inside an existing required context that already subscribes merge_group, so the queue's required set does not move.

6. What the card, the triage and the claim got wrong

  1. ⚠️ The premise flagged as "already moved" had not moved. The claim reads eslint-rules/ rule files 21, "more than doubled" from the card's 10. Both numbers are of the same tree: eslint-rules/ holds 21 .js files = 10 rule modules + 10 co-located .test.js + index.js. The rule count is 10, exactly the card's figure, and index.js registers exactly those ten. The process.cwd reading of 0 re-runs as 0 with the export control returning 1.
  2. ⚠️ The six measured-immune files are four. The census was 16, plus the 17th the regex could not see, of which 13 were repaired ⇒ 4 immune, each with a per-file two-cwd reading in PR objectui#7806's report. 19 − 13 = 6 is arithmetic on a total (19) that no measurement produced; it entered via a PM comment on objectui#7800 and was inherited by this card.
  3. ⚠️ new URL(REL, import.meta.url) does NOT throw here. The card states fileURLToPath throws ERR_INVALID_URL_SCHEME on it under both cwds. Re-measured: the tree carries 21 live call sites of that form and they pass — packages/types + packages/app-shell from the repo root (2 files / 21 tests) and packages/app-shell from its own directory via --root ../.. (2 files / 13 tests). ⇒ the gate treats it as self-rooted and does not flag it; reddening 21 green files over a hazard that did not reproduce would be a false-positive engine. ⛔ The gate still prescribes only the bare-import.meta.url spelling AGENTS.md names, so no third spelling is introduced — but the boundary of the claim is now measured rather than inherited. Whether the hazard still holds somewhere is a separate question this card does not answer.
  4. The population has grown, and here is the instance. gridArrayArmOrderby-8973.test.tsx landed after objectui#7799 swept the class, carrying both falsified premises as its stated justification. That is this card's argument with a file attached.

7. Filed, ⛔ not folded in

  • objectui#9188 — the remaining cwd-rooted read, e2e/live/inline-edit-polish-2572.spec.ts:34. Playwright, not Vitest: it runs only in live-e2e.yml against a real backend, so the repair cannot be verified from here. Registered in KNOWN_CWD_ROOTED, which is ⛔ SHRINK-ONLY and pinned at ≤ 1.
  • packages/cli/src/__tests__/app-generator.test.ts:1161 is registered in SUBJECT_IS_THE_CWD with its reason: the cwd mirrors on both sides of the assertion deliberately, and re-rooting one side desyncs the mirror and reddens a passing test. Its real cost is already filed as objectui#7807. That list is ⛔ not a debt list, and a stale entry in either list fails the run.

维护者速读(草稿)

改了什么 — 新增 scripts/check-test-path-roots.mjs(门)与 scripts/__tests__/check-test-path-roots.test.ts(钉),把 AGENTS.md 已经写下、但至今无人执行的一条规则变成机械门:测试在断言里读文件系统,根不得落在 process.cwd() 之下。顺带修掉树上仅存的 3 个该类缺陷,并改正它们注释里那三句早已被证伪的前提。

为什么改 — objectui#7799 一天里修掉 13 个实例,但没关掉这一类。规则写在文档里、运行时不兑现,正是本仓反复付账的形态;而 gridArrayArmOrderby-8973.test.tsx 是那次普查之后新写的同类缺陷,证明这一类确实还在长。

风险与代价(含回滚) — 门只读、不改任何东西;两张登记表各自带理由,写错会被自己的 stale 检查判红。⚠️ 它有已声明的盲区(376 个无法归类的根,每次运行都打印出来),⛔ 所以它的绿不是对整类的背书。回滚成本低:删掉两个新文件、package.json 一行与 lint.yml 一步即可;三个测试文件的重新定根是独立的、两种 cwd 下都已实测转绿的改动,可单独保留。

席位意见 — (留空,交维护者)

你要做的 — 确认两件事:① KNOWN_CWD_ROOTED 里那条 e2e 条目是否接受先登记后扫(它需要一次 live-e2e 运行才能验证修法);② 本 PR §6 第 3 条的读数与 AGENTS.md 里关于 new URL(rel, import.meta.url) 的警告存在张力 —— ⛔ 本卡按裁决未动 AGENTS.md,那句话是否需要一张治理面的卡,归你定。


🤖 Generated with Claude Code

https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr


Generated by Claude Code

Root AGENTS.md teaches that a test reading the filesystem in an assertion
roots on its own file and never on `process.cwd()`, and the bullet ends by
declaring that nothing enforces it. objectui#7799 repaired 13 instances of
that class in a single day; it did not close the class.

`scripts/check-test-path-roots.mjs` closes it. The detector is deliberately
not a `process.cwd` grep: one of objectui#7799's own 13 defects was invisible
to that card's census regex, spelled through
`(globalThis as unknown as {…}).process.cwd()`. The scan starts at the
FILESYSTEM CALL, resolved by import provenance rather than by name, and asks
what its path argument is rooted at, following the file's own bindings. A
violation is an ambient root with a segment appended to it — resolving a path
BELOW a cwd that moves. Reading the cwd itself is not, which is the shape
PR objectui#7806 rewrote `browser-process-shim-scope.test.ts` into.

Three live instances are repaired with it, all found by binding resolution and
two of them by nothing else — their read lines carry no `cwd` at all. Each
carried, in its own prose, a premise objectui#7791 and objectui#7800 had
already falsified; those comments are corrected rather than moved.

The gate prints the number of roots it CANNOT classify on every run, so its
silence is never read as a claim about the whole class, and `--blind`
enumerates them. Its pin lives in `scripts/__tests__/`, which runs inside
`Test (shard N/4)` — a required context that subscribes `merge_group` — and
carries the non-vacuity floor, so a walk that collapses fails instead of
reporting clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
`no-irregular-whitespace` is an ESLint error in this repo and both were
U+200B, typed to keep a `**/` inside a block comment from closing it. The
sentences are reworded instead; a zero-width character in a comment is
invisible to every reader and to both spellings of a search for it.

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

`KNOWN_CWD_ROOTED`'s single entry said "tracked for the sweep" without
naming where. A registry entry whose repair has no card is how a
SHRINK-ONLY list stops shrinking.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 51 chunks) 3091.1 KB 3134.8 KB
Main entry chunk (gzip) 144.2 KB 350 KB
Entry file index-B7Pwzp-5.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) 16.69KB 6.21KB
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) 500.22KB 114.68KB
core (index.js) 8.28KB 3.31KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 209.38KB 57.92KB
fields (index.js) 247.41KB 62.43KB
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) 8.87KB 3.64KB
i18n (index.js) 5.21KB 2.26KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 32.15KB 10.49KB
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.95KB
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) 14.81KB 3.63KB
plugin-calendar (index.js) 49.03KB 13.93KB
plugin-charts (index.js) 71.52KB 19.98KB
plugin-chatbot (index.js) 195.32KB 46.51KB
plugin-dashboard (index.js) 131.18KB 34.62KB
plugin-designer (index.js) 215.68KB 44.27KB
plugin-detail (index.js) 252.19KB 65.42KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.94KB 41.04KB
plugin-grid (index.js) 211.56KB 57.50KB
plugin-kanban (index.js) 46.10KB 14.33KB
plugin-list (index.js) 112.58KB 27.65KB
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.54KB 3.31KB
plugin-view (index.js) 84.42KB 20.80KB
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) 94.03KB 31.02KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
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.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
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

Copy link
Copy Markdown
Contributor

ACCEPT — objectui#8953 / PR #9190

Reviewed against the diff at 429c9c7b3913731b96d2909673e83e6a349f74d6, not against the report.

0. First, the correction — and it is to me, not to the dev

My dispatch carried a ⚠️ flag saying the card's premise had already moved: that eslint-rules/ held 21 rule files against the card's 10, and had "more than doubled". That flag was manufactured out of my own miscount.

eslint-rules/ holds 21 .js files = 10 rule modules + 10 co-located .test.js + index.js. The rule count is 10 — exactly the card's figure. Nothing doubled; I compared a file count with a rule count and then built a warning on the difference.

This is the third instance of the same error in this shift (a grep -l line count read as an entity count; it( call sites read as a test count). It is the worst of the three, because the other two ended in a number that was merely wrong, and this one ended in a flagged instruction to a dev to treat a stable premise as moved. The dev measured it and disagreed, which is the only reason it did not become the card's framing. The order's conditional phrasing is what made that possible, and that is the part to keep. Logged to the errata ledger.

The dev's two other premise corrections both hold and both are recorded here:

  • "Six measured-immune files" is four. 19 − 13 = 6 is arithmetic on a total (19) that no measurement produced; PR objectui#7806's own per-file table names four. The 19 entered through a PM comment on objectui#7800 and was inherited by this card. Another figure this seat put into circulation without deriving.
  • §6.3 — the new URL(REL, import.meta.url) reading. Independently re-measured below; it is the one item I am routing rather than closing.

1. What I verified in the diff

claim how verdict
report sha = PR sha = ls-remote all three read 429c9c7b39 — three commits, +1125/−14, 8 files
no new required check context git diff of .github/workflows/lint.yml is one hunk at the existing job's steps: — no jobs:-level key added, every + line indented under it ✅ the step lands inside the already-required Lint; the queue's required set does not move
package.json is one line diff check:test-path-roots, alphabetically placed among its neighbours
KNOWN_CWD_ROOTED is shrink-only in fact, not in prose the pin asserts KNOWN_CWD_ROOTED.length).toBeLessThanOrEqual(1) ✅ mechanical, one number; a second entry reddens the pin
the registries cannot outlive what they excuse scan() computes stale from both lists and a non-empty stale exits 1 ✅ two-sided — the entry must still resolve from the cwd
the non-vacuity floor is per-counter FLOORS = { testFiles: 2000, sinkCalls: 800, selfRooted: 20, filesWithSinks: 100 }; the pin asserts the collapsed run marks all four vacuous, and separately that a no-sinks run still clears FLOORS.testFiles ✅ four counters, both directions, room above each — a collapse check, not a ratchet
the gate does not flag the two-argument new URL declared at the script's header and pinned silent in the self-test's own fixture ✅ declared and pinned, not merely omitted
the blind spot is printed, not buried the census line ends N root(s) NOT CLASSIFIED, --blind enumerates ✅ this is the single best thing in the PR: a gate that prints only what it checked reads as a claim about everything

NOT MEASURED by me: the census figures (1884 calls in 387 of 3037 files; 376 unclassified) and every ablation leg. This seat has a bare checkout with no node_modules, and the gate parses with the TypeScript compiler — I structurally cannot run it, and I will not write a leg I cannot execute. The independent reading available to me is CI's: Lint completed / success on this head with the new step in it, so the gate runs and exits 0 on the real tree.

2. Why this is accepted rather than sent back

The card asked for a gate on a rule AGENTS.md has taught and nothing has enforced. What could have come back is a process.cwd grep with a suppression list. What came back starts at the filesystem call, resolves the sink by import provenance rather than by name — name-matching gave 28 violations, provenance 8, and all 20 of the difference were a test's own local writeFile into a mkdtemp dir — and resolves the root through the file's own bindings, which is why it found two reads whose own line holds no cwd token at all.

Three live defects were repaired, and the one that matters is gridArrayArmOrderby-8973.test.tsx: it landed after objectui#7799 swept the class, which is the card's argument with a file attached. The comments that justified it were corrected rather than deleted — the defect was reasoned from written prose, so leaving the prose in place reproduces it. That is the right call and I want it on the record as the pattern.

And the verdict is drawn at the right place: an ambient root with at least one segment appended. existsSync(cwd) is not a violation; existsSync(join(cwd, 'pnpm-workspace.yaml')) is. The appended segments are not required to look like a repository path — requiring packages/-ish literals would have let the grep back in through the window.

3. One item routed, ⛔ not folded in — and re-measured before routing

§6.3 puts a measurement in tension with a sentence on a governed surface. AGENTS.md ⛔ forbids new URL(<相对路径>, import.meta.url), on the stated mechanism that this repo's test transform rewrites it to http://localhost:3000/@fs/… and fileURLToPath then throws ERR_INVALID_URL_SCHEME under both cwds.

I re-measured the population myself on origin/main, stating the predicate rather than inheriting a number:

  • predicate: a new URL( whose second argument is import.meta.url and whose first argument is a quoted string not beginning with / or a scheme, over *.ts,tsx,mjs,js,cjs;
  • 27 call sites. Controls: a nonsense token → 0; bare import.meta.url repo-wide → 688 (so the narrowing is a narrowing, not a collapse);
  • of the 27, 25 sit in test files — i.e. inside the transform the AGENTS.md sentence names — and 2 are in scripts/*.mjs, run by plain node and therefore outside the stated mechanism entirely.

So the tension is real and it is 25 files wide, not a corner case: a governed surface declares a spelling fatal while the tree carries 25 live instances of it inside exactly the transform the declaration invokes, and the dev measured a sample of them passing under both cwds. ⛔ The dev correctly did not touch AGENTS.md — triage ruled that out of this card — and correctly did not file it either, because the placement is triage's call and not a dev's.

It is now filed bare, as a skills-lane candidate, carrying the predicate and the two numbers and no proposed resolution. Three outcomes are open and I am not choosing between them: the sentence is wrong and should be corrected; the sentence is right about a narrower condition that these 25 do not meet and should say which; or the 25 are a latent hazard and want a gate. Deciding that is not this seat's and not this card's.

4. What I am NOT endorsing

The gate's green is not an endorsement of the class. 376 roots come out unclassified, the largest blind spot is structural (a root arriving as a function parameter or from an import — binding resolution stops at the module edge), and the PR says so in its own body and on every single run. examples/schema-catalog/test/catalog-gallery-render.test.tsx handed process.cwd() straight to a helper that does the reads; the gate does not see it and never will without cross-module resolution. It was found by a human reading the file. That file is repaired here and labelled in place as the shape the gate misses, which is the honest disposition.

Likewise: the three plugin-view files are silent because the gate cannot classify them (their root comes out of Array#find), and the PR pins that reason so the silence is not read as a clean verdict. Silence with a stated cause is worth more than a green.

Proceeding to the merge queue.


Generated by Claude Code

@baozhoutao
baozhoutao marked this pull request as ready for review September 11, 2026 16:07

Copy link
Copy Markdown
Contributor

Correction to my own ACCEPT, §3 — the dev's number was right and mine was wrong

The ACCEPT above says 27 call sites, 25 in test files, and calls the tension "25 files wide". That is wrong in two ways and the second one matters more than the first.

The numbers. Four of those 27 grep hits are comment prose, not call sites. Separating lines whose code part begins with *, // or /*:

reading sites distinct files
raw grep hits, relative first argument 27 21
— comment prose 4 4
live call sites 23 17
— — in test files (inside the Vite transform) 21 15
— — in scripts/*.mjs (plain node, outside the mechanism) 2 2

21 live call sites in test files. That is exactly the figure PR §6.3 reported. The dev measured it correctly; I "re-measured" it into a larger number and presented the larger number as the independent reading. Also corrected: "spread over 21 files" — 21 is the file count of the raw grep, not of the sites; the live test-file population is 15 files.

Why this one is worse than the three in §0. I committed the same error class — a grep's line count read as an entity count — for the fourth time in this shift, inside the comment whose §0 was an apology for the third. And the mechanism is the one this very PR's §2 exists to argue against: a text search does not know what it is looking at. The gate being accepted here starts at the filesystem call precisely because a name match reported 12 violations in a file that had none. I then reviewed it with a grep that counted four comments as code.

The correction is one grep -vE and it belonged in the first pass.

What does not change. Nothing in the ACCEPT's verdict moves: the routed item is still routed, still bare, still ⛔ not folded into this PR, and the diff verifications in §1 are reads of the diff, not of a grep. objectui#9191 has been corrected in place — and the four comment lines turned out to be the most useful evidence in it, since three of them are authors writing the hazard down as their reason for avoiding the form while 21 live sites of it measurably pass. That contradiction is now the card's centre rather than a discarded artifact of a bad filter.

This is logged to the errata ledger as its own entry, not as a repeat of the earlier three: the distinguishing feature is that the guard against it was on the page in front of me, in the work I was reviewing.


Generated by Claude Code

Copy link
Copy Markdown
Contributor

Armed — auto-merge SQUASH, and the probe I will run afterwards, written first

Armed at 2026-09-11T16:10:10Z on head 429c9c7b3913731b96d2909673e83e6a349f74d6. Governed Surface Queue Guard re-ran after the ready-flip and is success; four Test (shard N/4) are still in_progress and the queue will hold on them.

ⓘ The arm call echoed method: MERGE although SQUASH was requested. ⛔ I did not take that echo at face value — the queue's own method governs, so I read the tree instead: origin/main's last 8 commits each have exactly one parent, and 7ae8cd8d86 (a branch commit from the PR that landed as 87f174c00a) is not an ancestor of origin/main. The queue squashes; branch commits do not survive. One commit will land, not three.

The probe, written and posted BEFORE the merge

Every expected value below was read off 429c9c7b39 just now, not recalled. Post-merge legs are scoped to <merge>^ <merge> — ⛔ never to origin/main, which moves (that mistake picked up two unrelated PRs' files earlier this shift). Every leg is a git tree read: this seat has a bare checkout with no node_modules, and ⛔ I will not write a leg I cannot execute.

L1 — the file set, two-sided. git diff --name-only <merge>^ <merge> = exactly 8 paths, and the set EQUALS: the changeset, .github/workflows/lint.yml, package.json, the two examples/schema-catalog/test/*.test.tsx, packages/plugin-grid/src/__tests__/gridArrayArmOrderby-8973.test.tsx, scripts/__tests__/check-test-path-roots.test.ts, scripts/check-test-path-roots.mjs. A 9th path is a FAIL, not a note.

L2 — nothing moved in transit, by blob. A squash preserves blobs exactly, so equality is the assertion, not an approximation:

path expected blob on the merge commit
scripts/check-test-path-roots.mjs ccc430f25421172a949ea282de5aec3b06874318
scripts/__tests__/check-test-path-roots.test.ts 288a73b8ff7831a9f70b013d8d9c50a5f9289e57
.changeset/8953-test-path-roots-gate.md 66840e62ab21a09bf54a5766fb2328d87d8ce22c

Paired with a COUNT and a STRING, because a hash alone tells you nothing about what it is a hash of: the gate file is 728 added lines, and line 208 reads verbatim

export const FLOORS = Object.freeze({ testFiles: 2000, sinkCalls: 800, selfRooted: 20, filesWithSinks: 100 });

⛔ A floor silently lowered between review and merge is the failure this leg exists for, and it is the one a blob-only check would still pass while I wasn't looking at what changed.

L3 — the leg that checks what must STAY, which is the load-bearing one. The ACCEPT's central structural claim is no new required check context. Measured across the diff, both sides:

reading of .github/workflows/lint.yml base 87f174c00a head 429c9c7b39 must be
jobs:-level keys 5 5 EQUAL — a new key is a new context
- name: steps 17 18 +1 exactly

Across <merge>^ <merge> the same two readings must give EQUAL and +1. The +1 is ambiguous on its own — another PR could land a lint step in the same window — so it is disambiguated by a STRING leg: the merge commit must contain the step name Verify no test resolves a repository path from the process cwd, and <merge>^ must not. That is a control with a known direction in both halves.

L4 — the shrink-only ratchet did not widen. KNOWN_CWD_ROOTED = 1 entry and SUBJECT_IS_THE_CWD = 1 entry on the merge commit, and scripts/__tests__/check-test-path-roots.test.ts still contains the literal toBeLessThanOrEqual(1). Two registries, so each is the other's control: a leg that only counted one could not tell "shrink-only held" from "I read the wrong array".

L5 — the three repairs stayed repaired, with a control that HITS. ⛔ An absence claim is worth nothing without a positive in the same run. Per file, counting code lines only — lines whose code part begins with *, // or /* are excluded, which is the filter whose absence produced the correction two comments up:

file cwd() in code import.meta.url in code (the control)
examples/schema-catalog/test/catalog-gallery-render.test.tsx 0 1:174
examples/schema-catalog/test/plugin-dashboard-gallery-render.test.tsx 0 1:94
packages/plugin-grid/src/__tests__/gridArrayArmOrderby-8973.test.tsx 0 1:95

All three control lines are the same prescribed spelling, and the probe asserts the text, not just the count:

const REPO_ROOT = decodeURIComponent(new URL(import.meta.url).pathname)

⛔ Floors are per file, not a total — a summed 0 across three files cannot distinguish three repairs from two repairs and one file the grep stopped matching.

What the probe does NOT claim. It does not re-run the gate, the pin, or any ablation leg — I cannot, and CI's Lint success on this head is the reading that covers those. It says nothing about the 376 unclassified roots. And a green probe is a statement about what landed, ⛔ never an endorsement of the class the gate covers.

A leg that comes back amber will be published with its cause. ⛔ It will not be re-run into green.


Generated by Claude Code

Copy link
Copy Markdown
Contributor

⚠️ Test (shard 3/4) is RED — root-caused, this PR's, fix in flight

Completed 2026-09-11T16:12:56Z: 1 failed | 751 passed (752 files). The other three shards, Lint, Type Check, Build & E2E, Build Docs and every satellite context are green on this head. The merge did not happen because of this one file.

The failure, quoted

FAIL  unit  scripts/__tests__/ci-cd-pipeline-doc.test.ts
      > ci-cd-pipeline.md — lint.yml step list
      > names every first-party command the lint job actually runs

AssertionError: .github/workflows/lint.yml runs commands that the "## Lint (`lint.yml`)"
section of content/docs/guide/ci-cd-pipeline.md does not name:
  - lint.yml `lint`: scripts/check-test-path-roots.mjs

Add each one to that section, in the order lint.yml runs it. Every gate in this job blocks
a merge on a required check, so one nobody wrote down is a build failure contributors meet
with no way to learn from this page what produced it — objectui#8015.

Not a flake, ⛔ not the base branch's, and ⛔ not getting a re-run. The assertion names scripts/check-test-path-roots.mjs, a file that exists only on this branch — the failure is caused by this diff and could not reproduce anywhere else. Three PRs have merged through the queue since this shard finished, so main's shards are green. ⛔ There is no "not mine" reading available here and none was looked for.

A repair is in flight on this same branch: one commit adding the missing bullet to the ## Lint (\lint.yml`)section, in the orderlint.ymlruns the step — aftercheck-vi-mock-override-shape.mjs, before check-cross-repo-closer-outcome.mjs`. ⛔ No rebase, ⛔ no amend, ⛔ no force-push; auto-merge stays armed and the probe posted above stands unchanged except for L1, which will read 9 paths instead of 8 once the doc file joins the diff. I will say so explicitly in the LANDED note rather than quietly adjusting the expected value.

⛔ The pin is not being weakened, skipped or quarantined. It is correct and it caught a real omission.

⭐ The interesting part, and it is a finding about the round rather than about the code

§5 of this PR enumerated "the workflow-wiring pins my lint.yml edit could move" and named six: lint-workflow, dependabot-merge-gate, merge-queue-reporting, entry-guard-wiring, workflow-cache-save-bound, check-lint-rule-coverage. All six ran green. ci-cd-pipeline-doc.test.ts is not among them, and it is the one that fired.

Look at what those six have in common: every one of them is named after the workflow, the queue, or the lint machinery. The pin that caught this is named after a documentation page. ⇒ the set was assembled by name similarity to the thing being changed, not by asking the only question that decides it — which tracked tests actually READ .github/workflows/lint.yml? A gate whose subject is a document does not advertise itself in its filename as a reader of a workflow, and so it was invisible to the way the list was built.

That is the same shape as the defect this PR exists to close, one level up: the round reasoned about coverage from names rather than from what the code reads, which is precisely why the gate it built starts at the filesystem call instead of at a spelling. ⭐ The instrument was right and the process that verified it was not.

The repair round has been asked to measure the real set — every tracked test that reads lint.yml by any spelling, including through a fixture or a shared helper — and to say whether that set can be derived rather than recalled. ⛔ It is not building that instrument on this branch; if one is wanted it gets filed bare and graded by triage.

For the record, this seat's share of it

⛔ I did not catch it either. My ACCEPT verified that the lint.yml edit adds no new required contextjobs:-level keys 5 → 5, - name: steps 17 → 18 — and I treated the step count as the whole of what a workflow edit can move. It is not: a step that runs a new command moves a documentation pin as well as the job's shape. The two-sided reading I was proud of answered the question I asked and ⛔ not the one the tree asks.


Generated by Claude Code

`ci-cd-pipeline-doc.test.ts` pins the `## Lint (lint.yml)` section against the
job's actual `run:` steps, and this branch added a step without adding its
bullet: `1 failed | 79 passed`, naming `scripts/check-test-path-roots.mjs`.

The bullet goes between `check-vi-mock-override-shape.mjs` and
`check-cross-repo-closer-outcome.mjs`, where lint.yml runs it, and carries what
the neighbouring bullets carry: what the gate judges, the measurement that made
it necessary (objectui#7791's `7 passed` vs `2 failed / 5 passed` on one file
with the cwd as the only variable; objectui#7799's thirteen repairs in a day
that did not close the class), and why it resolves sinks by import provenance
rather than by name.

The second bullet states the gate's declared blind spot -- the census line's
`N root(s) NOT CLASSIFIED`, 376 today -- because a page that describes a gate
as covering a class it only partly sees reproduces the defect this card is
about.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FhBNJcLRZLe8M87VcUgpKr
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 51 chunks) 3112.1 KB 3134.8 KB
Main entry chunk (gzip) 144.2 KB 350 KB
Entry file index-r_U7yDvF.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) 16.69KB 6.21KB
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) 500.68KB 114.78KB
core (index.js) 8.52KB 3.41KB
create-plugin (index.js) 27.94KB 9.51KB
data-objectstack (index.js) 209.38KB 57.92KB
fields (index.js) 247.41KB 62.43KB
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) 8.87KB 3.64KB
i18n (index.js) 5.21KB 2.26KB
i18n (pickLocalized.js) 7.62KB 3.26KB
i18n (provider.js) 32.15KB 10.49KB
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.95KB
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) 14.81KB 3.63KB
plugin-calendar (index.js) 49.03KB 13.93KB
plugin-charts (index.js) 71.52KB 19.98KB
plugin-chatbot (index.js) 195.32KB 46.51KB
plugin-dashboard (index.js) 131.18KB 34.62KB
plugin-designer (index.js) 215.68KB 44.27KB
plugin-detail (index.js) 251.95KB 65.34KB
plugin-editor (index.js) 2.23KB 1.05KB
plugin-form (index.js) 136.79KB 34.19KB
plugin-gantt (index.js) 166.94KB 41.04KB
plugin-grid (index.js) 211.56KB 57.50KB
plugin-kanban (index.js) 46.10KB 14.33KB
plugin-list (index.js) 112.58KB 27.65KB
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.54KB 3.31KB
plugin-view (index.js) 84.42KB 20.80KB
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) 94.03KB 31.02KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 4.63KB 2.18KB
react (schema-input.js) 4.25KB 2.04KB
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.66KB 2.50KB
sdui-parser (input-type.js) 2.84KB 1.40KB
sdui-parser (kanban-quick-add.js) 3.89KB 1.87KB
sdui-parser (parse.js) 25.28KB 7.80KB
sdui-parser (provenance.js) 3.66KB 1.82KB
sdui-parser (types.js) 0.28KB 0.23KB
sdui-parser (validate.js) 14.82KB 4.99KB
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

Copy link
Copy Markdown
Contributor

Repair pushed — e629fc825bf9013bc065242c62c01c996fab6c39, and the probe's L1 amended in the open

Verified against the tree, not the report

check reading
report sha = pushed sha = ls-remote e629fc825b…
no rebase, no amend, no force-push git merge-base --is-ancestor 429c9c7b39 <head>true. The reviewed commit is still an ancestor; the repair sits on top of it
scope 1 file, +29 / −0content/docs/guide/ci-cd-pipeline.md only
placement one hunk at the file's line ~318, inside ## Lint (\lint.yml`), between the check-vi-mock-override-shape.mjsandcheck-cross-repo-closer-outcome.mjsbullets — the orderlint.yml` runs them
nothing the ACCEPT reviewed moved all three blobs byte-identical to the reviewed head: ccc430f254… · 288a73b8ff… · 66840e62ab…
collision risk with PR objectui#9141 that PR's hunks in this file are near lines 218 / 476 / 525; this one is at 318. No overlap
auto-merge still armed from 16:10:10Z — a push does not disarm it

⭐ And it did not settle for satisfying the pin. The second bullet it added says the quiet part out loud:

⚠️ Read that gate's green for what it is: it declares its own blind spot on every run. … A clean run is a verdict on the roots this gate can classify, never a clean bill of health for the class — which is the same over-reading the card itself is about.

That sentence is worth more to the next contributor than the gate is.

⚠️ Probe amendment, stated rather than silently patched

The probe posted before the merge expects L1 = exactly 8 paths. The repair adds a ninth, content/docs/guide/ci-cd-pipeline.md. ⇒ L1's expected set is now 9, listed here so the change is on the record before the merge rather than discovered inside a LANDED note:

.changeset/8953-test-path-roots-gate.md
.github/workflows/lint.yml
content/docs/guide/ci-cd-pipeline.md          ← added by the repair
examples/schema-catalog/test/catalog-gallery-render.test.tsx
examples/schema-catalog/test/plugin-dashboard-gallery-render.test.tsx
package.json
packages/plugin-grid/src/__tests__/gridArrayArmOrderby-8973.test.tsx
scripts/__tests__/check-test-path-roots.test.ts
scripts/check-test-path-roots.mjs

⛔ L2, L3, L4 and L5 are unchanged and their expected values still stand — the blob equalities above are the reason I can say that rather than assume it.

⭐ The answer to the question I asked, and it is worse than I guessed

I asked the repair round to measure which tracked tests actually read .github/workflows/lint.yml, rather than which ones sound like they might. It did it properly — an fs-surface probe preloaded into every Vitest worker via NODE_OPTIONS=--import, attributing each read to the first repository frame, with subprocesses inherited so an out-of-process gate script is caught too, over all 79 tracked test files mentioning .github or workflows (0 hits outside that candidate set).

instrument readers found error
the original round's recall (§5) 6 all six are genuine readers — ⛔ recall was not wrong, it was 6 of 35
a text search for the string lint.yml 21 hits 8 false positives, and — the bigger half — 22 misses
the runtime probe 35 the real set

The 22 that never spell lint.yml reach it three ways: enumerating .github/workflows/; a shared helper (scripts/__tests__/workflow-checks.ts) reading on the test's behalf; or the gate script the test itself runs walking the tree.

The second and third of those are the same structural blind spot this very PR's gate declares for path roots — resolution stopping at the module edge. The card is about a detector that cannot see past a helper, and the process that verified the detector failed in exactly that way, at exactly that boundary. I do not think that is a coincidence; I think it is what the class actually is.

Filed bare as objectui#9198, with both derivation sketches (the runtime probe, and a static extension of this gate's own resolver across the module edge) ⛔ deliberately not built here. Grading is triage's.


Generated by Claude Code

@baozhoutao
baozhoutao added this pull request to the merge queue Sep 11, 2026
Merged via the queue into main with commit 6a8e2a3 Sep 11, 2026
37 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-8953-cwd-rooted-test-gate branch September 11, 2026 17:19

Copy link
Copy Markdown
Contributor

LANDED — 6a8e2a319a683b5523106a68776b3524814b4982

One squash commit. Probe run scoped to 6a8e2a319a^ 6a8e2a319a (parent 7696daac04) — ⛔ never against origin/main. All five legs green, each against the value posted before the merge, ⚠️ with L1 at the amended 9 rather than the originally-posted 8.

L1 — the file set ✅ 9, exactly the amended list

.changeset/8953-test-path-roots-gate.md
.github/workflows/lint.yml
content/docs/guide/ci-cd-pipeline.md          ← the CI repair
examples/schema-catalog/test/catalog-gallery-render.test.tsx
examples/schema-catalog/test/plugin-dashboard-gallery-render.test.tsx
package.json
packages/plugin-grid/src/__tests__/gridArrayArmOrderby-8973.test.tsx
scripts/__tests__/check-test-path-roots.test.ts
scripts/check-test-path-roots.mjs

⭐ The 8 → 9 amendment was published before the merge, not discovered here. That is the only reason this reads as a pass rather than as a leg I quietly retuned.

L2 — nothing moved in transit ✅

path expected on the merge commit
scripts/check-test-path-roots.mjs ccc430f254… ccc430f25421172a949ea282de5aec3b06874318
scripts/__tests__/check-test-path-roots.test.ts 288a73b8ff… 288a73b8ff7831a9f70b013d8d9c50a5f9289e57
.changeset/8953-test-path-roots-gate.md 66840e62ab… 66840e62ab21a09bf54a5766fb2328d87d8ce22c

Paired count: the gate file is 728 lines. Paired string, read on the merge commit:

export const FLOORS = Object.freeze({ testFiles: 2000, sinkCalls: 800, selfRooted: 20, filesWithSinks: 100 });

⛔ No floor was lowered between review and merge. That is what the string half of this leg exists for; a hash alone would have told you nothing about what it was a hash of.

L3 — the leg that carried the ACCEPT's central claim ✅, positive in both halves

reading of .github/workflows/lint.yml <merge>^ <merge> required
jobs:-level keys 5 5 ⛔ EQUAL — ✅
- name: steps 17 18 +1 exactly — ✅
the step-name string Verify no test resolves a repository path from the process cwd 0 1 the disambiguator — ✅

no new check context was created. The step landed inside the already-required Lint, and the queue's required set did not move.

L4 — the shrink-only ratchet ✅

KNOWN_CWD_ROOTED = 1, SUBJECT_IS_THE_CWD = 1, and the pin still carries toBeLessThanOrEqual(1). Two registries, each the other's control.

L5 — the three repairs, per file, each with a control that HITS ✅

file cwd() in code import.meta.url in code the prescribed string
examples/schema-catalog/test/catalog-gallery-render.test.tsx 0 1 1
examples/schema-catalog/test/plugin-dashboard-gallery-render.test.tsx 0 1 1
packages/plugin-grid/src/__tests__/gridArrayArmOrderby-8973.test.tsx 0 1 1

Code lines only — comment lines excluded before counting, which is the filter whose absence cost this seat a correction earlier on this very PR. ⛔ Per file, never summed: a summed 0 cannot tell three repairs from two repairs and one file the matcher stopped matching. The positive column is the prescribed spelling itself, asserted as text:

const REPO_ROOT = decodeURIComponent(new URL(import.meta.url).pathname)

⛔ What this green does NOT say

It says what landed. ⛔ It does not re-run the gate, the pin, the four ablation legs or the census — bare checkout, no node_modules, and this seat writes no leg it cannot execute. CI is the reading that covers those, including the shard that went red and then green.

And ⛔ it is not an endorsement of the class. The gate reports 376 roots it cannot classify on every run and says so in its own summary line. Its largest blind spot is structural — a root arriving as a function parameter or from an import, because resolution stops at the module edge — and one of the three files repaired here handed process.cwd() straight to a helper that did the reads and was found by a human reading the file, ⛔ not by the gate.

⭐ That blind spot then reached out and bit the round that built it: the CI failure on this PR was a documentation pin that reads lint.yml through a helper, invisible to a list assembled from names. The measurement it prompted — 35 tracked test files read lint.yml, against 6 recalled and 21 text-search hits of which 8 are false — is filed as objectui#9198. The card's thesis and the round's own failure are the same shape, one level apart.

Cost of the round, stated

One red shard, one repair commit, one merge. The red was mine to have caught: my ACCEPT measured the workflow edit's shape (jobs: keys, step count) and never asked what the step runs. ⇒ for any workflow edit, read what the step runs, not only where the step sits.

Closing objectui#8953.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

2 participants