Skip to content

fix(app-shell): action runtime builds sameOriginOnly authenticated fetch - #5767

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-5702-action-runtime-same-origin-v2
Aug 23, 2026
Merged

fix(app-shell): action runtime builds sameOriginOnly authenticated fetch#5767
os-zhuang merged 1 commit into
mainfrom
claude/issue-5702-action-runtime-same-origin-v2

Conversation

@os-zhuang

@os-zhuangos-zhuang commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Fixes#5702

Maintainer ruling 2026-08-22 (「同意所有」, item 17), re-affirmed 2026-08-23 as A, sequenced: useConsoleActionRuntime builds createAuthenticatedFetch({ sameOriginOnly: true }), matching the provider: 'api' data-source lane (ConsoleShell). A metadata type: 'api' action whose resolved target is off-origin now goes out through the bare global fetch — no Authorization, no X-Tenant-ID, no Accept-Language. Same-origin actions are unchanged. Quiet fix, no advisory, per the ruling.

The sequencing prerequisite has landed: #5745 (merged as PR #5765) converged the dev stacks on one origin — verified on origin/mainbc21c704b that all four committed env files ship VITE_SERVER_URL empty and examples/console-starter/vite.config.ts:80 carries the /api dev proxy — so this change no longer de-authenticates the standard dev stack. Blocked-by: objectui#5745 is discharged (#5745 is itself already closed, completed by PR #5765).

What changed

  • packages/app-shell/src/hooks/useConsoleActionRuntime.tsx — the one ruled line at :316 plus lane comments stating the off-origin behaviour (the old apiHandler comment described the wrapper as unconditional "Bearer + X-Tenant-ID + same-origin cookies").
  • packages/app-shell/src/hooks/__tests__/useConsoleActionRuntime.sameOriginOnly-5702.test.tsx — the regression pin, deliberately a PAIR through apiHandler with the real createAuthenticatedFetch (partial auth mock; the wrapper, TokenStorage, ActiveOrganizationStorage stay real): a same-origin target still carries Authorization and X-Tenant-ID; an absolute off-origin target carries neither and the request still executes (pass-through, not a refusal). Either half alone proves nothing — the off-origin half is green on a wrapper that attaches nothing, the same-origin half on the bare wrapper this site built before.
  • .changeset/action-runtime-same-origin-only-5702.md — patch, states the behaviour change plainly, including the split-host note and the Dev and split-host configs point VITE_SERVER_URL at an origin the page is not served from — the ruled prerequisite for #5702's sameOriginOnly default #5745 convergence.

This is a re-derivation of the earlier pushed branch claude/issue-5702-action-runtime-same-origin (head 074121474, pre-#5744/#5765) onto current main; that branch is left untouched.

Re-measurements on bc21c704b (all line numbers re-derived)

  • sameOriginOnly short-circuits at createAuthenticatedFetch.ts:81beforeisApiCall (:84), Authorization (:87), X-Tenant-ID (:128) and Accept-Language (:141). The post-fix(auth): scope the active-organization key per user, and drop the previous user's client state on a session-user change #5744 rewrite (188 lines) preserves this ordering; the option stops all three headers.
  • The switch is shared: construction at useConsoleActionRuntime.tsx:308 (pre-change), consumed at :399 (apiHandler), :534, :592 (server-action env), :695 (child prop) and returned from the hook at :721 — five lanes, four building URLs from VITE_SERVER_URL (:326, :509, :593, :696). The change at the construction covers all of them; nothing is scoped to apiHandler alone.
  • Shipped-reliance sweep re-run (the ruling's conditional stop): clean, controlled. Absolute http(s) action targets in ts/tsx across packages/ apps/ examples/: 5 hits, every one a type: 'url' navigation action in core ActionRunner tests (no fetch, no headers). Controls: 108 relative /api/ targets by the same pattern family; JSON/YAML lane 0 files with an api-typed action (control: 268 type: 'api' occurrences in TS). The first pass's split-host reliance is repaired on main by Dev and split-host configs point VITE_SERVER_URL at an origin the page is not served from — the ruled prerequisite for #5702's sameOriginOnly default #5745, verified above. The stop does not re-fire.
  • withSettleSignal (the extra wrapper at ConsoleShell.tsx's site) is orthogonal here: it manages request settling for the data-source provider lane; the action runtime never had it and adding it is not part of the ruling.
  • Out of scope, per the original dispatch: the other bare createAuthenticatedFetch() construction sites take code-supplied first-party URLs, not author metadata — the axis this card turns on. The X-Tenant-ID gating question is Confirm whether X-Tenant-ID has a reader: the framework derives the tenant from the session, not the header #5279-side follow-up evidence, not this PR.

Verification (union at 17a273779, clean tree, sha echoed by the run itself)

  • Targeted suite (canonical root invocation): new pin + 3 sibling useConsoleActionRuntime files + packages/authcreateAuthenticatedFetch.test.tsx (the legacy-behaviour pin "without sameOriginOnly, cross-origin /api/ URLs keep the legacy attach behaviour" — stays green, fence not crossed): Test Files 5 passed (5) / Tests 72 passed (72), UNION-VITEST-EXIT=0, lock VERDICT command-exit 0.
  • pnpm --filter @object-ui/app-shell type-check after building the dependency closure: script name echoed (@object-ui/app-shell@17.6.0 type-check), TYPECHECK-EXIT=0.
  • Ablation (committed first; mutation script with trap restore EXIT INT TERM): reverted the one line, disk-confirmed by anchored counts both directions (pre: option-call 1 / bare-call 0 → mutated: 0 / 1 → restored: 1 / 0). No build leg exists on this path and none is needed: vitest aliases @object-ui/auth to packages/auth/src and the pin imports the hook relatively from source, so both legs execute on-disk source. Observed direction as predicted: off-origin pin RED (AssertionError: expected 'Bearer tok-5702' to be null), same-origin pin survived — correctly, same-origin attach is identical with and without the option — and the legacy wrapper file stayed green (it pins the wrapper, which the ablation never touches).
  • Targeted eslint (--no-inline-config --format json) on the two touched lintable files: ESLINT-EXIT=0, 2 files linted, 0 errors, 63 warnings all @typescript-eslint/no-explicit-any + pre-existing hook warnings — the lint gate is errors-only by the workflow's own header. Narrowing declared: population is the root flat eslint.config.js (files: ['**/*.{ts,tsx}']), file count from the JSON formatter, and the config enables no type-aware linting (no projectService / parserOptions.project), so this diff cannot move untouched files' verdicts. The repo-wide farm is CI's run.
  • node scripts/check-changeset-presence.mjs: "2 source file(s) of 1 released package(s) changed, and this change declares 1 changeset(s)", exit 0.

Generated by Claude Code

…tch (#5702)
useConsoleActionRuntime now builds createAuthenticatedFetch with
sameOriginOnly: true, matching the provider:'api' data-source lane
(ConsoleShell). A metadata type:'api' action whose resolved target is
off-origin goes out through the bare global fetch — no Authorization,
X-Tenant-ID, or Accept-Language. Same-origin actions are unchanged
(pinned by the new regression pair). Maintainer ruling 2026-08-22 /
2026-08-23 (A, sequenced); the #5745 config convergence this was
sequenced behind has landed.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EuPCi56cnGyykygi3z9w4m
@github-actions

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

MetricValueBudget
Eager closure (gzip, 52 chunks)3918.0 KB3990.2 KB
Main entry chunk (gzip)152.5 KB350 KB
Entry fileindex-7LZEmYYX.js
StatusPASS

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

PackageSizeGzipped
app-shell (consoleActionDispatch.js)0.20KB0.19KB
app-shell (index.js)10.04KB3.72KB
app-shell (runtime-config.js)12.80KB4.47KB
app-shell (types.js)0.01KB0.04KB
app-shell (urlParams.js)10.06KB3.86KB
auth (ActiveOrganizationStorage.js)22.94KB8.44KB
auth (AuthContext.js)0.31KB0.24KB
auth (AuthGuard.js)2.07KB1.00KB
auth (AuthProvider.js)33.99KB8.57KB
auth (AuthShell.js)3.49KB1.40KB
auth (ForgotPasswordForm.js)12.21KB3.45KB
auth (LoginForm.js)18.15KB5.39KB
auth (PreviewBanner.js)0.90KB0.50KB
auth (RegisterForm.js)6.65KB2.22KB
auth (SocialSignInButtons.js)9.61KB3.89KB
auth (UserMenu.js)3.41KB1.23KB
auth (auth-gate-events.js)1.29KB0.66KB
auth (authStyles.js)5.04KB1.72KB
auth (createAuthClient.js)40.21KB10.80KB
auth (createAuthenticatedFetch.js)8.46KB3.43KB
auth (index.js)3.19KB1.44KB
auth (invitation-status.js)1.22KB0.70KB
auth (org-roles.js)6.66KB2.78KB
auth (phone-identifier.js)1.11KB0.66KB
auth (types.js)0.59KB0.35KB
auth (useAuth.js)5.30KB1.02KB
auth (useWorkspaceAdminStatus.js)5.13KB2.35KB
collaboration (CommentThread.js)26.08KB7.56KB
collaboration (LiveCursors.js)3.17KB1.27KB
collaboration (PresenceAvatars.js)6.49KB2.64KB
collaboration (PresenceProvider.js)2.79KB1.13KB
collaboration (index.js)1.68KB0.73KB
collaboration (useCollaborationTranslation.js)6.05KB2.52KB
collaboration (useCommentSearch.js)1.98KB0.88KB
collaboration (useConflictResolution.js)7.75KB1.86KB
collaboration (useMentionNotifications.js)1.81KB0.68KB
collaboration (usePresence.js)6.33KB1.84KB
collaboration (useRealtimeSubscription.js)7.91KB2.01KB
components (index.js)510.39KB114.67KB
core (index.js)4.92KB1.97KB
create-plugin (index.js)10.08KB3.26KB
data-objectstack (index.js)164.55KB45.67KB
fields (index.js)238.40KB59.89KB
i18n (LocalizationContext.js)1.76KB0.96KB
i18n (currency.js)1.22KB0.64KB
i18n (i18n.js)4.28KB1.75KB
i18n (index.js)3.44KB1.39KB
i18n (pickLocalized.js)7.62KB3.26KB
i18n (provider.js)23.13KB7.63KB
i18n (useDisplayLocale.js)2.85KB1.45KB
i18n (useObjectLabel.js)33.40KB8.71KB
i18n (useSafeTranslation.js)7.77KB3.13KB
layout (index.js)38.95KB10.97KB
mobile (MobileProvider.js)0.92KB0.49KB
mobile (ResponsiveContainer.js)0.94KB0.38KB
mobile (breakpoints.js)1.51KB0.70KB
mobile (createOfflineDataSource.js)5.61KB1.75KB
mobile (index.js)1.55KB0.62KB
mobile (offlineQueue.js)3.91KB1.35KB
mobile (pwa.js)0.97KB0.49KB
mobile (serviceWorker.js)1.48KB0.62KB
mobile (serviceWorkerSource.js)3.41KB1.48KB
mobile (useBreakpoint.js)1.54KB0.65KB
mobile (useGesture.js)6.96KB1.98KB
mobile (useOfflineSync.js)1.99KB0.72KB
mobile (usePullToRefresh.js)2.53KB0.85KB
mobile (useResponsive.js)0.72KB0.42KB
mobile (useResponsiveConfig.js)1.37KB0.63KB
mobile (useSpecGesture.js)4.32KB1.64KB
mobile (useTouchTarget.js)1.01KB0.54KB
permissions (MePermissionsProvider.js)9.53KB3.38KB
permissions (PermissionContext.js)0.31KB0.25KB
permissions (PermissionGuard.js)0.89KB0.45KB
permissions (PermissionProvider.js)4.64KB1.50KB
permissions (evaluator.js)5.12KB1.74KB
permissions (index.js)0.93KB0.41KB
permissions (store.js)0.91KB0.42KB
permissions (useFieldPermissions.js)1.28KB0.53KB
permissions (usePermissions.js)1.93KB0.88KB
plugin-ai (index.js)15.75KB3.80KB
plugin-calendar (index.js)46.62KB12.83KB
plugin-charts (index.js)64.65KB18.32KB
plugin-chatbot (index.js)181.41KB43.22KB
plugin-dashboard (index.js)128.41KB32.95KB
plugin-designer (index.js)212.30KB42.80KB
plugin-detail (index.js)242.34KB60.98KB
plugin-editor (index.js)2.46KB1.10KB
plugin-form (index.js)125.63KB30.64KB
plugin-gantt (index.js)164.10KB39.87KB
plugin-grid (index.js)200.79KB54.26KB
plugin-kanban (index.js)52.93KB14.60KB
plugin-list (index.js)111.80KB27.20KB
plugin-map (index.js)20.06KB6.62KB
plugin-markdown (index.js)13.72KB4.69KB
plugin-report (index.js)43.49KB11.93KB
plugin-timeline (index.js)26.68KB7.66KB
plugin-tree (index.js)8.50KB2.88KB
plugin-view (index.js)84.61KB20.74KB
providers (DataSourceProvider.js)0.75KB0.39KB
providers (MetadataProvider.js)1.37KB0.59KB
providers (ThemeProvider.js)1.90KB0.85KB
providers (UploadProvider.js)11.66KB3.50KB
providers (index.js)0.45KB0.23KB
providers (types.js)0.01KB0.04KB
react-runtime (index.js)5.62KB2.34KB
react (LazyPluginLoader.js)3.77KB1.33KB
react (SchemaRenderer.js)44.39KB14.99KB
react (data-invalidation.js)5.05KB2.08KB
react (index.js)1.33KB0.69KB
react (schema-input.js)2.32KB1.24KB
react (spec-input.js)0.20KB0.18KB
sdui-parser (codegen.js)5.41KB2.34KB
sdui-parser (index.js)4.77KB2.16KB
sdui-parser (input-type.js)2.84KB1.40KB
sdui-parser (parse.js)12.13KB3.65KB
sdui-parser (provenance.js)3.66KB1.82KB
sdui-parser (types.js)0.28KB0.23KB
sdui-parser (validate.js)6.92KB2.40KB
types (ai.js)0.20KB0.17KB
types (api-types.js)0.20KB0.18KB
types (app.js)2.87KB0.99KB
types (base.js)0.20KB0.18KB
types (blocks.js)0.20KB0.18KB
types (complex.js)0.20KB0.18KB
types (crud.js)0.20KB0.18KB
types (dashboard-filter-alias.js)6.23KB2.74KB
types (data-display.js)0.20KB0.18KB
types (data-protocol.js)0.20KB0.19KB
types (data.js)0.20KB0.18KB
types (designer.js)1.87KB0.85KB
types (disclosure.js)0.20KB0.18KB
types (error-code.js)1.54KB0.88KB
types (feedback.js)0.20KB0.18KB
types (field-types.js)0.20KB0.18KB
types (form.js)0.20KB0.18KB
types (http-inflight.js)8.87KB3.73KB
types (http-retry.js)4.32KB2.02KB
types (index.js)3.88KB1.85KB
types (layout.js)0.20KB0.18KB
types (managed-by.js)0.19KB0.18KB
types (mobile.js)2.59KB1.31KB
types (navigation.js)0.20KB0.18KB
types (objectql.js)0.20KB0.18KB
types (overlay.js)0.20KB0.18KB
types (permissions.js)0.20KB0.18KB
types (plugin-scope.js)0.20KB0.18KB
types (record-components.js)0.20KB0.19KB
types (record-semantics.js)1.28KB0.67KB
types (registry.js)0.20KB0.18KB
types (reports.js)0.20KB0.18KB
types (spec-report.js)5.05KB1.93KB
types (spec-ui-namespace.js)0.20KB0.19KB
types (system-fields.js)3.33KB1.54KB
types (theme.js)3.40KB1.68KB
types (ui-action.js)3.40KB1.71KB
types (views.js)0.20KB0.18KB
types (widget.js)0.20KB0.18KB

Size Limits

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

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

Labels

Projects

None yet

2 participants

@os-zhuang@claude