Skip to content

test(plugin-detail): serve batch 3's four probes from doubles (objectui#7307) - #8019

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-7307-network-escapes-batch3
Sep 6, 2026
Merged

test(plugin-detail): serve batch 3's four probes from doubles (objectui#7307)#8019
baozhoutao merged 1 commit into
mainfrom
claude/issue-7307-network-escapes-batch3

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Part of #7307 — batch 3 of the network-escape burn-down (batch 1 was #7999, batch 2 was #8013).

The four plugin-detail rows left in the ledger now serve their probes from the
recording double batches 1 and 2 landed, and their lines leave KNOWN_ESCAPES
and PINNED_LEDGER in the same commit.

Per file

file endpoint reader it escaped through double
src/__tests__/defaultFieldGroupsPage.sectionHeadings.test.tsx POST /api/v1/security/explain useRecordEditable.ts:76 (apiFetch ?? fetch) installExplainDouble() in beforeEach
src/__tests__/guideCrudAppRenders.test.tsx POST /api/v1/security/explain and GET /api/task/42 useRecordEditable.ts:76 · plus DetailView.tsx:616, which calls the global fetch with no seam at all installFetchDouble() in beforeEach — the one two-route router in this batch
src/__tests__/recordDetailsBodySource.test.tsx POST /api/v1/security/explain useRecordEditable.ts:76 installExplainDouble() in beforeEach
src/renderers/__tests__/record-details.emptySectionDefault.test.tsx POST /api/v1/security/explain useRecordEditable.ts:76 installExplainDouble() in beforeEach

Mechanism, measured rather than assumed. A trap-guarded stack probe on the
guard's own attribution point, restored by blob hash afterwards, attributes every
escape in all four files. Three of them render RecordDetailsRenderer, which
renders DetailView (record-details.tsx:302); the fourth renders the guide's
detail-view snippet directly. Either way DetailView calls useRecordEditable
twice per render — DetailView.tsx:290 for edit, :296 for delete — and at
useRecordEditable.ts:76 the hook degrades from the host's authenticated
apiFetch to the global fetch by design, so a standalone embed keeps rendering.
Under happy-dom that global is a real HTTP client whose document URL is
http://localhost:3000, so the relative path resolved to a live socket. The read
is best-effort, which is why these files stayed green while the request always
failed.

guideCrudAppRenders is the exception the dispatch flagged, now measured: one
extra call from DetailView.tsx:616, the api branch that does
fetch(SCHEMA_API + '/' + SCHEMA_RESOURCEID) with no apiFetch seam. Its counts
were 12 explain calls and exactly 1 record call. The probe was reverted with
git checkout HEAD --; the guard is byte-identical to HEAD afterwards
(git diff HEAD empty, git status --porcelain empty).

The doubles are batch 1/2's shape verbatim: vi.stubGlobal('fetch', router)
in beforeEach, cleanup() before vi.unstubAllGlobals() in afterEach (the
#7439 ordering). Each is a router, not a sink — it records every URL it is handed
and afterEach fails on any URL outside the set it serves, so an escape elsewhere
reds here instead of vanishing into a best-effort catch.

What they answer, and why no assertion moves:

  • /api/v1/security/explain — the permissive verdict, in the two shapes the two
    explain hooks read: { record: { visible } } for a single recordId,
    { records: [{ recordId, visible }] } for a batched recordIds. Only the first
    is reached here; the batched branch is kept so the router stays byte-identical
    to its siblings rather than forking per file. useRecordEditable initialises
    allowed to true and its failure path leaves it there, so the permissive
    verdict and the absent verdict are the same value at every read site.
  • /api/task/42 — the record, which is the shape its reader consumes:
    DetailView does res.json() then setData(result?.data || result). The one
    case that reaches it asserts only that the "No data source resolved" panel stays
    absent, and that panel is a wiring gate in ElementDataSourceGate decided
    before any response arrives — absent because the block declares api, not
    because the request failed. Serving the record exercises the success path this
    route always had without moving that assertion.

Nothing is skipped, quarantined or silenced.

Ledger arithmetic

12 to 8, in both lists. The four names are deleted from KNOWN_ESCAPES in
vitest.setup.network-escape-guard.ts (each with its endpoint comment) and from
PINNED_LEDGER in scripts/__tests__/network-escape-ledger.test.ts, in this one
commit. Checked in lockstep by diffing the quoted paths of the two literals
against each other on the branch: empty diff, both at 8, zero plugin-detail
rows left in either. The pin's non-vacuity floor is length > 0, so 8 clears it.
Remaining 8: all app-shell, across four endpoint families.

No prose count moved. All three "21" references in the guard are provenance
claims about the original sweep on 67dadd6, not live counts of the current list
— batch 1 and batch 2 had already normalised the two sentences that were live.

Evidence

Per file, before then after (attribution lines / ECONNREFUSED lines, then the
post-fix run):

file before after
defaultFieldGroupsPage.sectionHeadings 6 / 30 0 / 0, Tests 3 passed
guideCrudAppRenders 13 / 63 0 / 0, Tests 10 passed
recordDetailsBodySource 6 / 30 0 / 0, Tests 3 passed
record-details.emptySectionDefault 22 / 110 0 / 0, Tests 6 passed

Test counts are unchanged in every file — the doubles add no cases and remove none.

pnpm exec vitest run packages/plugin-detail/ scripts/__tests__/network-escape-ledger.test.ts
on this head: exit 0, Test Files 133 passed (133), Tests 1202 passed (1202),
and zero lines matching network-escape or ECONNREFUSED in the whole run.
That run also covers the third reader of KNOWN_ESCAPES,
record-details.hideEmptyRetired-7129.test.tsx, whose mention is prose in a
comment rather than a read.

Ablation (on the committed tree, one script with trap ... EXIT INT TERM and
absolute paths throughout). guideCrudAppRenders.test.tsx — the two-route file —
had its double reverted to its pre-batch-3 blob while its line stayed deleted from
BOTH ledgers. Mutation proven on disk, not by exit code: blob 54f75c12 to
7f7951dd, asserted equal to the eeda78a7 base blob and unequal to HEAD's;
anchors installFetchDouble 2 to 0 and vi.stubGlobal 1 to 0; that path present
0 times in KNOWN_ESCAPES and 0 times in PINNED_LEDGER during the run.
Predicted direction stated before running: red naming the file. OBSERVED red
exit 1, Test Files 1 failed (1), Tests 6 failed | 4 passed (10), with
Network escape: this test reached a REAL socket at http://localhost:3000/api/v1/security/explain, http://localhost:3000/api/task/42
and file: packages/plugin-detail/src/__tests__/guideCrudAppRenders.test.tsx.
That the failure names both routes is the load-bearing half: it shows the
second route is genuinely served by this PR rather than incidentally quiet.
Restore proven by observation: blob back to 54f75c12, git diff HEAD on that
path empty, git status --porcelain empty.

No dist preflight leg: these are the vitest projects' own test files and the root
config aliases every package specifier to src — measured, in that all four
suites ran green on a fully unbuilt tree before any build happened.

Gates, exit codes captured by redirect-then-capture, never through a pipe:

  • node scripts/check-changeset-presence.mjs exit 0 — "4 source file(s) of 1
    released package(s) changed, and this change declares 1 changeset(s)"; the
    changeset has an EMPTY frontmatter, the explicit exemption for a test-only
    change under a released package's src/, and not a label.
  • pnpm check:control-bytes exit 0 (6444 tracked text files), plus a grep -naP
    self-scan of the control range over all 7 changed paths, no hits.
  • node scripts/check-governed-queue-guard.mjs --test over all 7 changed paths:
    exit 0, "NOT GOVERNED — 7 path(s) checked against 5 governed surface(s)".
  • pnpm type-check:scripts exit 0 · pnpm check:vi-mock-inherit exit 0 (226 call
    sites judged, 226 inherit) · pnpm check:vi-mock-specifiers exit 0.
  • Per-package type-check and lint for plugin-detail via
    turbo --concurrency=2 --force: exit 0, Tasks: 15 successful, 15 total,
    0 eslint errors. Proven non-vacuous rather than assumed:
    tsc -p tsconfig.test.json --listFiles names each of the four edited files
    exactly once, and eslint --format json lists each among the 183 linted files.
    Every warning in the touched files sits on a pre-existing line outside this
    diff's hunks (checked hunk range by hunk range), and the warning totals are
    unchanged. type-check dependsOn ^build, so turbo built the dependency
    closure first — the build was needed for the gate, not for the suites.

The repo-wide eslint . --no-inline-config run belongs to CI, and CI is not
awaited here: the report is delivered at draft-PR time per the dispatch contract.

Concurrency

origin/main was eeda78a7 when this branch was cut and was still eeda78a7
when this PR was opened (re-fetched into a private ref rather than reading a
shared remote-tracking name), so no merge was needed and every number above was
taken on the head that ships. Draft PR #7685 touches plugin-detail's
index.tsx and record-details.hideEmptyRetired-7129.test.tsx — neither is in
this diff. #5174's in-flight batch cannot land in this package (its README left
the ledger with batch 17), and PR #8016 is disjoint.

#7996 is untouched and stays open: the { allowed: true } stub it reports lives
in record-details.hideEmptyRetired-7129.test.tsx, which this PR deliberately
does not edit. Its shape was not copied — these four routers answer the keys
the hooks actually read.


Generated by Claude Code

…ui#7307)

The four `plugin-detail` files in the network-escape ledger each reached a
REAL socket on every run. Each now serves its probe from a recording double,
and its line leaves `KNOWN_ESCAPES` and `PINNED_LEDGER` in this same commit
(both 12 -> 8, verified by diffing the two literals' quoted paths).

Measured with a stack probe on the guard's attribution point, not inferred:
all four reach `POST /api/v1/security/explain` through `useRecordEditable`'s
`apiFetch ?? fetch` fallback, twice per render (edit, then delete).
`guideCrudAppRenders` additionally reaches `GET /api/task/42` through
`DetailView`'s `api` branch, so its router serves that route too and answers
the record, the shape `setData(result?.data || result)` consumes.

The doubles are routers, not sinks: each records every URL it is handed and
its `afterEach` fails on any URL outside the set it serves, so an escape
somewhere else reds here instead of vanishing into a best-effort `catch`.
Teardown keeps the objectui#7439 ordering — `cleanup()` before
`vi.unstubAllGlobals()`. Nothing is skipped, quarantined or silenced.

Part of #7307

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

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Eager closure (gzip, 50 chunks) 3186.0 KB 3191.4 KB
Main entry chunk (gzip) 143.5 KB 350 KB
Entry file index-BO0uFEv5.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) 5.13KB 2.35KB
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) 497.06KB 113.79KB
core (index.js) 6.96KB 2.79KB
create-plugin (index.js) 10.08KB 3.26KB
data-objectstack (index.js) 182.08KB 50.62KB
fields (index.js) 242.44KB 61.25KB
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) 4.28KB 1.75KB
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) 11.71KB 4.29KB
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) 5.12KB 1.74KB
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) 47.35KB 13.21KB
plugin-charts (index.js) 70.31KB 19.62KB
plugin-chatbot (index.js) 193.53KB 46.05KB
plugin-dashboard (index.js) 131.41KB 34.43KB
plugin-designer (index.js) 211.51KB 43.01KB
plugin-detail (index.js) 247.59KB 63.48KB
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.56KB 56.63KB
plugin-kanban (index.js) 52.30KB 14.49KB
plugin-list (index.js) 113.24KB 27.66KB
plugin-map (index.js) 20.35KB 6.77KB
plugin-markdown (index.js) 13.88KB 4.80KB
plugin-report (index.js) 43.42KB 11.92KB
plugin-timeline (index.js) 29.95KB 8.67KB
plugin-tree (index.js) 9.16KB 3.18KB
plugin-view (index.js) 84.33KB 20.75KB
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) 5.41KB 2.34KB
sdui-parser (dashboard-widget-options.js) 3.08KB 1.30KB
sdui-parser (index.js) 4.93KB 2.24KB
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) 10.35KB 3.60KB
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.74KB 1.41KB
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 (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 Author

Standing-down note — Live E2E (informational) is red on main and on every branch today: the upstream base-red anchored on #7990 (objectstack#16186). It is continue-on-error by design, never blocks the queue, and touches nothing this diff changes (four test files, the two escape ledgers, one empty changeset). Not this PR's to fix; no re-run requested. ACCEPTed on the card (#7307); flip and arming follow once the remaining checks conclude.


Generated by Claude Code

@baozhoutao
baozhoutao marked this pull request as ready for review September 6, 2026 10:36
@baozhoutao
baozhoutao added this pull request to the merge queue Sep 6, 2026
Merged via the queue into main with commit 83a9d22 Sep 6, 2026
33 of 34 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-7307-network-escapes-batch3 branch September 6, 2026 10:51
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.

2 participants