Skip to content

fix(app-shell): entitlement 错误上下文只读 error.details.*,删除平铺双方言回退(#3329) - #3353

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-3329-entitlement-details-strict
Aug 4, 2026
Merged

fix(app-shell): entitlement 错误上下文只读 error.details.*,删除平铺双方言回退(#3329)#3353
os-zhuang merged 1 commit into
mainfrom
claude/issue-3329-entitlement-details-strict

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Fixes #3329

cloud#1046 两仓协调变更的 objectui 先行半边(strict 版,按 issue 里的默认执行,否决窗口未被使用)。

改了什么

packages/app-shell/src/environment/entitlements.ts

  • entitlement 业务上下文(upgrade_url / contact_url / plan / current / limit)全部改从 error.details.KEY 这一个位置读取(原先是 error.KEY 顶层)。那些键坐在 error 上是未声明的兄弟键 —— 它们能「解析通过」纯粹是因为 ApiErrorSchema 是普通 z.object、会剥掉未知键,而这条路径消费的是 parse 之前的裸 wire body:合规是靠蒸发,不是靠声明。details 是 ADR-0112 为结构化错误上下文声明的槽(framework#4224、cloud#930 的 AiErrorExtra 先例)。

  • code / messageApiErrorSchema声明字段,仍留在 error 自身上,未随迁。

  • 删除 entitlementErrorFields()(body?.error ?? body)。变更后只有一种可接受形状:

    { "success": false,
      "error": { "code": "DEV_ENV_LIMIT", "message": "", "httpStatus": 403,
                 "details": { "current": 3, "limit": 3, "upgrade_url": "" } } }

    没有新增任何 ?? 跨形状回退 —— 一个宽容的消费端在这里买不到任何东西(见下方时序),却会把已退役的方言固化成第二份事实契约,正是 cloud#944 在退役的多方言漂移。

扫读点结果(issue 第 4 条)

按真实键名全仓扫了 upgrade_url / contact_url / seatCount / plan / current / limit / entitlement 与三个错误码。除本 PR 改的这一条外,没有第二个错误体读点:

  • useConsoleActionRuntime.tsx:331 是唯一调用方(喂进去的是 res.json() 的裸 body),不含独立读点,只更新了它的测试;
  • EnvironmentEntitlementDialog.tsx 渲染的是已成形的 EntitlementDialogSpec,不碰 wire 键;
  • useEnvironmentEntitlements.ts / CloudOnboardingNext.tsx / provisionEnvironment.ts 读的是 GET /cloud/environment-entitlements成功体 data.*(含 seatCount),不是 failWithCode 的错误体 —— 不在本次迁移范围;它们各自的 json?.data ?? json 成功包络容错另行记录为 app-shell: /cloud/environment-entitlements 成功包络仍有 json?.data ?? json 双方言容错(3 处),与 cloud#944/#1046 的收口方向相反 #3352(未认领),本 PR 不动
  • entitlement 一词全仓只作 i18n key 前缀(environment.entitlement.*),无 wire 读点。

测试

packages/app-shell/src/environment/__tests__/entitlements.test.ts 新增 reads error.details and nowhere else 一组严格性 pin,现有用例全部改写为 details 形状:

  • 正向:details 里的 upgrade_url / contact_url / plan / current / limit 被读到;
  • 反向(证明确实严格):
    • 键坐在 code 的兄弟位 → 弹窗照开(code 没搬),但每个上下文键都降级到默认值(CTA 落回 /settings/billing、不出「3 of 3」计数、plan 降回 free);
    • PRODUCTION_ENV_LIMIT 的兄弟位 contact_url → CTA 直接 undefined;
    • 平铺 body(error 是字符串、code 在顶层)→ 返回 null,走调用方的普通报错路径;
    • 完全没有 error 包络的裸 body → null;
    • details 不是对象 → 不去别处捞,取默认值。

useConsoleActionRuntime.test.tsx 补一条端到端 pin:退役的平铺形状 不开弹窗,而是返回 { success: false, error: '…' }(红 toast 路径)。

实跑输出:

$ npx vitest run packages/app-shell/src/environment/__tests__/entitlements.test.ts \
                 packages/app-shell/src/hooks/__tests__/useConsoleActionRuntime.test.tsx
 Test Files  2 passed (2)
      Tests  66 passed (66)

$ npx vitest run packages/app-shell        # 整包
 Test Files  270 passed (270)
      Tests  2339 passed | 1 skipped (2340)
   Duration  409.98s

$ pnpm --filter @object-ui/app-shell type-check
> tsc --noEmit && tsc -p tsconfig.typetests.json     # 干净退出,无输出

$ pnpm --filter @object-ui/app-shell lint
✖ 2152 problems (0 errors, 2152 warnings)           # 全部为存量 warning
# 仅本 PR 三个文件:0 errors(123 warnings,均为该目录既有的 no-explicit-any 风格)

时序 —— 合并后请知会 cloud 分片

本 PR 合入 objectui main托管 Console 不会立刻变:cloud 镜像按 .objectui-sha 钉扎。cloud#1046 的后半(收口 extra + 5 个调用点改 details + 断言重写)将与 pin bump 同一个 PR 落地,生产者/消费者在托管产品里原子切换,无中间窗口。本 PR 合并即解除 cloud#1046 的 Blocked-by。

Breaking(已写进 changeset)

.changeset/entitlement-error-context-reads-details.md(minor —— 按 AGENTS.md 版本策略,objectui 自身的破坏性变更也标 minor,语义写在正文)。

跟踪 objectui main 但控制面落后于 cloud#1046 的自托管部署:弹窗仍会打开、标题/正文不变(code 没搬),但会丢上下文 —— 升级 CTA 落回 /settings/billingPRODUCTION_ENV_LIMIT 掉「Contact sales」、DEV_ENV_PLAN_LOCKED 一律说「free plan」、DEV_ENV_LIMIT 不再报「using X of Y」。把控制面升过 cloud#1046 即恢复。这是 issue 里给出否决窗口后按 strict 默认执行的结果。

🤖 Generated with Claude Code

https://claude.ai/code/session_015W6nhsDrz6zWQc8je12a1t

cloud#1046 两仓变更的 objectui 先行半边。entitlement 键
(upgrade_url / contact_url / plan / current / limit)从 `error.<key>`
顶层改为 `error.details.<key>` 唯一位置,并删除
`entitlementErrorFields()` 的 `body?.error ?? body` 平铺容错 ——
变更后只接受一种形状,不留 `??` 回退。

`code` / `message` 是 ApiErrorSchema 的声明字段,仍留在 `error` 上。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015W6nhsDrz6zWQc8je12a1t
@vercel

vercel Bot commented Aug 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectui Ignored Ignored Aug 4, 2026 6:07am

Request Review

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 28.1 KB 350 KB
Entry file index-CIHoTpVY.js
Status PASS

📦 Bundle Size Report

Package Size Gzipped
app-shell (index.js) 8.47KB 3.09KB
app-shell (runtime-config.js) 7.42KB 2.32KB
app-shell (types.js) 0.01KB 0.04KB
app-shell (urlParams.js) 7.57KB 2.97KB
auth (AuthContext.js) 0.31KB 0.24KB
auth (AuthGuard.js) 1.17KB 0.53KB
auth (AuthProvider.js) 22.10KB 4.37KB
auth (AuthShell.js) 3.49KB 1.40KB
auth (ForgotPasswordForm.js) 12.21KB 3.45KB
auth (LoginForm.js) 18.13KB 5.39KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.64KB 2.21KB
auth (SocialSignInButtons.js) 9.60KB 3.89KB
auth (UserMenu.js) 3.40KB 1.22KB
auth (auth-gate-events.js) 1.29KB 0.66KB
auth (authStyles.js) 5.04KB 1.72KB
auth (createAuthClient.js) 35.76KB 9.11KB
auth (createAuthenticatedFetch.js) 4.37KB 1.69KB
auth (index.js) 2.35KB 1.07KB
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) 4.91KB 0.87KB
auth (useIsWorkspaceAdmin.js) 1.61KB 0.85KB
collaboration (CommentThread.js) 18.38KB 4.49KB
collaboration (LiveCursors.js) 3.17KB 1.27KB
collaboration (PresenceAvatars.js) 3.65KB 1.42KB
collaboration (PresenceProvider.js) 2.79KB 1.13KB
collaboration (index.js) 1.25KB 0.53KB
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) 476.68KB 104.68KB
core (index.js) 2.25KB 0.80KB
create-plugin (index.js) 9.28KB 2.98KB
data-objectstack (index.js) 136.23KB 34.75KB
fields (index.js) 226.52KB 55.50KB
i18n (LocalizationContext.js) 1.76KB 0.96KB
i18n (currency.js) 1.22KB 0.64KB
i18n (i18n.js) 4.32KB 1.77KB
i18n (index.js) 2.46KB 0.96KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 5.37KB 1.72KB
i18n (useObjectLabel.js) 26.14KB 6.07KB
i18n (useSafeTranslation.js) 3.26KB 1.44KB
layout (index.js) 38.53KB 10.71KB
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.74KB
mobile (index.js) 1.50KB 0.62KB
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.71KB 0.42KB
mobile (useResponsiveConfig.js) 1.36KB 0.63KB
mobile (useSpecGesture.js) 4.05KB 1.53KB
mobile (useTouchTarget.js) 1.01KB 0.54KB
permissions (MePermissionsProvider.js) 8.75KB 3.06KB
permissions (PermissionContext.js) 0.31KB 0.25KB
permissions (PermissionGuard.js) 0.89KB 0.45KB
permissions (PermissionProvider.js) 3.67KB 1.12KB
permissions (evaluator.js) 4.41KB 1.44KB
permissions (index.js) 0.91KB 0.41KB
permissions (store.js) 0.91KB 0.42KB
permissions (useFieldPermissions.js) 1.28KB 0.52KB
permissions (usePermissions.js) 1.55KB 0.71KB
plugin-ai (index.js) 15.71KB 3.79KB
plugin-calendar (index.js) 44.98KB 12.37KB
plugin-charts (index.js) 60.54KB 17.13KB
plugin-chatbot (index.js) 180.09KB 42.72KB
plugin-dashboard (index.js) 112.01KB 28.86KB
plugin-designer (index.js) 210.51KB 42.51KB
plugin-detail (index.js) 230.61KB 56.81KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 111.54KB 26.97KB
plugin-gantt (index.js) 162.25KB 39.55KB
plugin-grid (index.js) 185.08KB 49.04KB
plugin-kanban (index.js) 47.89KB 13.18KB
plugin-list (index.js) 104.94KB 25.32KB
plugin-map (index.js) 16.81KB 5.24KB
plugin-markdown (index.js) 13.72KB 4.69KB
plugin-report (index.js) 40.55KB 10.59KB
plugin-timeline (index.js) 25.76KB 7.33KB
plugin-tree (index.js) 8.34KB 2.82KB
plugin-view (index.js) 83.67KB 20.43KB
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.71KB 3.53KB
providers (index.js) 0.44KB 0.22KB
providers (types.js) 0.01KB 0.04KB
react-runtime (index.js) 5.67KB 2.37KB
react (LazyPluginLoader.js) 3.77KB 1.33KB
react (SchemaRenderer.js) 19.28KB 6.38KB
react (data-invalidation.js) 5.05KB 2.08KB
react (index.js) 1.02KB 0.55KB
react (spec-input.js) 0.20KB 0.18KB
sdui-parser (codegen.js) 4.09KB 1.74KB
sdui-parser (index.js) 4.47KB 2.03KB
sdui-parser (parse.js) 10.04KB 2.82KB
sdui-parser (types.js) 0.29KB 0.24KB
sdui-parser (validate.js) 4.69KB 1.48KB
types (ai.js) 0.20KB 0.17KB
types (api-types.js) 0.20KB 0.18KB
types (app.js) 2.87KB 0.99KB
types (base.js) 0.20KB 0.18KB
types (blocks.js) 0.20KB 0.18KB
types (complex.js) 0.20KB 0.18KB
types (crud.js) 0.20KB 0.18KB
types (data-display.js) 0.20KB 0.18KB
types (data-protocol.js) 0.20KB 0.19KB
types (data.js) 0.20KB 0.18KB
types (designer.js) 1.87KB 0.85KB
types (disclosure.js) 0.20KB 0.18KB
types (error-code.js) 1.54KB 0.88KB
types (feedback.js) 0.20KB 0.18KB
types (field-types.js) 0.20KB 0.18KB
types (form.js) 0.20KB 0.18KB
types (http-retry.js) 4.32KB 2.02KB
types (index.js) 2.46KB 1.21KB
types (layout.js) 0.20KB 0.18KB
types (managed-by.js) 0.19KB 0.18KB
types (mobile.js) 0.20KB 0.18KB
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 (spec-report.js) 5.05KB 1.93KB
types (system-fields.js) 3.33KB 1.54KB
types (theme.js) 0.20KB 0.18KB
types (ui-action.js) 3.40KB 1.71KB
types (views.js) 0.20KB 0.18KB
types (widget.js) 0.20KB 0.18KB

Size Limits

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

@os-zhuang
os-zhuang marked this pull request as ready for review August 4, 2026 06:11
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 4, 2026
Merged via the queue into main with commit 8ec4067 Aug 4, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-3329-entitlement-details-strict branch August 4, 2026 06:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

app-shell: entitlement 错误上下文改读 error.details.* 并删除平铺双方言回退(cloud#1046 两仓变更的 objectui 先行半边)

2 participants