Skip to content

fix(app-shell): entitlement 成功包络严格只读 data,删除 3 处双方言容错 (#3352) - #3377

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-3352-entitlements-envelope-strict
Aug 5, 2026
Merged

yinlianghui merged 1 commit into
mainfrom
claude/issue-3352-entitlements-envelope-strict

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Fixes #3352

做了什么

控制面的成功响应按契约一律包成 { success, data },Console 侧却有 3 处读点「两种形状都收」。本 PR 把它们改成严格只读 data,拿不到就走各自既有的失败分支——逐处保留原语义,不统一成一种:

读点 原写法 改后拿不到 data 时
environment/useEnvironmentEntitlements.ts json?.data ?? json 返回 null → fromRows() 行派生回退
console/home/CloudOnboardingNext.tsx json?.data ?? json phase: 'unknown' → 降级为两个按钮都给
console/organizations/provisionEnvironment.ts 'data' in body ? body.data : body 抛错 → 调用方既有的 lazy provision 兜底

为什么

这是 #3329(错误体半边,删 entitlementErrorFields() 的 body?.error ?? body)的同族收尾,方向一致:一种形状,严格读。

?? 容错把一个事实上不存在的第二方言固化成了第二份事实契约,而且失败是静默的:一旦生产者漏包 data,裸 body 的字段全是 undefined,hasProductionEnv 直接读成 false——一个明明有生产环境的用户会被告知「还没有生产环境」,并被推到错误的 "Create your environment" CTA 上,没有任何一处会响。provisionEnvironment 那处更直接:原本会把裸 body(甚至 {})当成功的 env 返回,报告一次「provision 成功」却什么都没带回来。

契约优先(AGENTS.md 准则 #0.1):这类漏包是生产者侧的 bug,消费端的宽容只会把它藏起来。

契约核实(第 1 步)

cloud 仓不在本会话凭证范围内,改用 in-scope 证据链定案,三条互相印证:

  1. 本仓内的契约声明——environment/entitlements.ts:181 上的注释就写着 EnvironmentEntitlementsSummary 是 “GET /cloud/environment-entitlements → data”。summary 的宿主位置在类型声明层面本来就只有 data 一个,裸 body 分支从来没有 spec 依据。
  2. cloud#1046 已落地——objectstack#4604 的 08-04 注销交班总账把 “chore(deps): bump the patch-updates group with 7 updates #1046(两仓原子切换)” 列进已合入清单,并记 “错误信封收敛完成”。
  3. app-shell: entitlement 错误上下文改读 error.details.* 并删除平铺双方言回退(cloud#1046 两仓变更的 objectui 先行半边) #3329 已按 strict 合入本仓——其 PM 验收评论明确记录「无任何 ?? 跨形回退」,并把本单点名为「同一清理方向的下一单」。

线程内未发现任何相悖事实(例如「某端点未包信封」)。控制面是 SaaS 单版本部署,不存在 #3366 那种客户端跨代服务端存活的兼容窗口。

测试

三处各补了裸 body 的负向 pin,另新建了 useEnvironmentEntitlements 的测试文件(此前该 hook 无测试)。

反向验证方向事先预判为「全红」,实测与预判一致:把三条 ?? / 'data' in body 限枝恢复成 origin/main 的写法后,6 条新 pin 全部转红,9 条既有/形状无关的测试保持绿:

× does NOT read a bare (un-enveloped) body as a summary; falls back to rows
× does NOT read an envelope whose `data` payload is missing
× does NOT read a bare (un-enveloped) 2xx body as the provisioned env
× throws when a 2xx body carries the envelope but no data payload
× does NOT read a bare (un-enveloped) body as a usable summary
× never strands a user behind a wrong "create" CTA when the envelope is missing
Test Files  3 failed (3)  |  Tests  6 failed | 9 passed (15)

恢复实现后:

  • 三个受影响文件:Test Files 3 passed (3) / Tests 15 passed (15)
  • app-shell 整包(走仓根 config):Test Files 276 passed (276) / Tests 2414 passed | 1 skipped (2415)
  • type-check(tsc --noEmit + typetests):干净退出
  • 六个改动文件 eslint:0 errors(11 条存量 warning,均在未触碰的行上)

其中 useEnvironmentEntitlements 那条 pin 特意让裸 body 声称 hasProductionEnv: false,而行数据里确有生产环境——严格读之后由行派生回退解析出 true,正好把「静默读错」这个后果本身钉住。

关于 changeset

不带 changeset。按仓库约定纯 bug 修复不需要;且在生产者遵守契约的前提下无用户可见行为变化——三处的失败分支都是既有路径,没有新增/改写任何 UI 文案。provisionEnvironment 新增的抛错被调用方 CreateWorkspaceDialog 既有的 try/catch(console.warn 后继续 onCreated)吞掉,不产生新的用户可见面。

范围

严格限于 issue 指定的 3 个读点 + 相应测试,未越界。


Generated by Claude Code

…itlement read-points (#3352)

The control plane wraps every success body as `{ success, data }` (cloud#1046),
and `EnvironmentEntitlementsSummary` is declared as that `data` payload. Three
Console read-points still accepted a bare body as a second dialect:

  - useEnvironmentEntitlements.ts  `json?.data ?? json`
  - CloudOnboardingNext.tsx        `json?.data ?? json`
  - provisionEnvironment.ts        `'data' in body ? body.data : body`

A missing envelope therefore degraded silently: every field reads `undefined`,
so `hasProductionEnv` becomes `false` and a user with a live production
environment is told they have none. Each site now reads `data` only and falls
to its own existing failure branch when it is absent — row-derived fallback,
`phase: 'unknown'`, and a throw into the caller's lazy-provision path
respectively. Same direction as #3329, which removed the error-body half.

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

vercel Bot commented Aug 5, 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 5, 2026 11:00am

Request Review

@github-actions github-actions Bot added the tests label Aug 5, 2026
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 28.1 KB 350 KB
Entry file index-BsO5Ggu7.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) 477.09KB 104.71KB
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) 227.16KB 55.71KB
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.65KB 1.06KB
i18n (pickLocalized.js) 1.70KB 0.83KB
i18n (provider.js) 9.48KB 3.27KB
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.83KB 17.25KB
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) 231.11KB 56.98KB
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) 105.02KB 25.36KB
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

@yinlianghui
yinlianghui marked this pull request as ready for review August 5, 2026 11:04
@yinlianghui
yinlianghui added this pull request to the merge queue Aug 5, 2026
Merged via the queue into main with commit cc4ca29 Aug 5, 2026
15 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-3352-entitlements-envelope-strict branch August 5, 2026 11:05
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: /cloud/environment-entitlements 成功包络仍有 json?.data ?? json 双方言容错(3 处),与 cloud#944/#1046 的收口方向相反

2 participants