Skip to content

fix(console): drop retired ToolSchema keys from the tool preview sample (#3257) - #3269

Merged
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-3257-console-tool-sample-retired-keys
Aug 3, 2026
Merged

fix(console): drop retired ToolSchema keys from the tool preview sample (#3257)#3269
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-3257-console-tool-sample-retired-keys

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

Fixes #3257

改了什么

apps/console/src/preview-samples.tstool 样例删掉三行:category / active / requiresConfirmation。样例其余部分(name / label / description / objectName / parameters / outputSchema)全部保留 —— 实测这份删完之后 ToolSchema 直接 ACCEPT

另外新增一条机器守卫 apps/console/src/__tests__/preview-samples-spec-valid.test.ts,把「样例过期」从静默腐烂变成 CI 报红。

为什么这不只是清理

这份样例不是测试夹具,是设计器预览画廊里给作者看的范本。范本的隐含承诺是「照抄就能用」,而它已经违约了:三个键都被 .strict()ToolSchema 逐个具名拒收,谁照抄谁就得到一份 ToolSchema.parse() 当场拒绝的元数据。这正是 AI 生成元数据最容易照抄并放大的位置 —— 坏范本不是错一次,是持续传播。

#3236 是同一次三仓扫描的两半,但危害形态不同:#3236 是「UI 宣传了不存在的能力」,本单是「范本在教人写错」。#3236 之后 ToolPreview 也不再渲染这三个徽标,这三行成了纯噪音。

守卫怎么写的(这是本 PR 的大头)

校验方式是把样例塞进一整个 stack,交给 ObjectStackSchema.safeParse():

ObjectStackSchema.safeParse({ tools: [SAMPLES.tool] })

而不是去 import 一个手挑的 XxxSchema。两个理由,都是写的过程中踩出来的:

  1. ObjectStackSchema 本身就是作者契约 —— 作者能发布的形态。按名字猜 schema 会猜错:email_template 最顺手的那个 EmailTemplateSchema 其实是运行时发信载荷(id / body / bodyType),真正可编写的记录是 EmailTemplateDefinitionSchema(name / label / subject / bodyHtml)。读集合不可能挑错。
  2. 不碰任何 Zod 内部。手写 ._def 去剥 optional > array > … 迟早会在 Zod 换版本时静默解错(或者更糟:空过)。让 wrapper 自己剥,这条测试问的就恰好是那个该问的问题 —— 这份样例如果被作者发布进真实 stack,会怎样?

守卫分四层:

断言 作用
classifies every sample exactly once 每个样例必须落在 SPEC_CLEAN / KNOWN_STALE / NO_AUTHORING_SCHEMA 之一。明天新加的样例逃不掉分类,否则报红
%s sample is valid metadata 6 个干净样例必须过 .parse()(含本次修好的 tool)
%s sample still fails as recorded 反向断言(与 #3212 同思路):台账里的样例一旦开始通过就报红,要求提升到 SPEC_CLEAN —— 台账只会缩短,不会变成垃圾堆
tool sample does not resurrect retired key %s 三个键按名钉死,让「别再加回来」对读代码的人可见

破坏性验证(sabotage)

category: 'data'requiresConfirmation: false 加回样例后重跑,三层同时报红:

FAIL  tool sample is valid metadata
AssertionError: expected [ { path: 'tools.0', …(1) } ] to deeply equal []

FAIL  tool sample does not resurrect retired key `category`
AssertionError: expected { name: 'query_orders', …(7) } to not have property "category"
- Expected: undefined
+ Received: "data"

FAIL  tool sample does not resurrect retired key `requiresConfirmation`

 Tests  3 failed | 21 passed (24)

已还原,当前 24/24 绿。

顺带核对了同文件其它全部 19 个样例(派发要求第 2 点)

结论:tool 不是唯一一个 —— 20 个样例里 12 个当前过不了 .parse(),其中 4 个带的是和本单完全同型的已退役键(action.bulkEnabledagent.tools/agent.knowledgeskill.triggerPhrasesflow.waitEventConfig.onTimeout)。

  • 合法(6):viewjobtool(本 PR 修完)、permissionpositionemail_template
  • 过不了(12):objectpagereportdashboardappactionflowagentskilldatasourcevalidationtranslation
  • spec 里根本没有对应 schema(2):workflowapproval(spec 只有 ApprovalNodeConfigSchema,那是 flow 节点配置)

这 12 个本 PR 没修,而是进了测试里的 KNOWN_STALE 台账(每条写明具体原因),并立了 objectui#3266 跟踪。没顺手修的理由不是偷懒:

  1. object.fields数组形态是 app-shell 有意支持的,不是笔误 —— previews/object-fields-io.tsreadFields() 显式分支 shape: 'array' | 'record' 并原样保留。把样例改成 record 会让画廊不再覆盖 array 那条分支;真正该先裁决的是这个双形态要不要留(AGENTS.md #0.1 的典型场景),而不是先改样例。
  2. dashboard / app / flow 样例会改变画廊渲染出来的东西,而预览画廊是本仓共用的浏览器验证台,并行 agent 正拿它验证别的改动。
  3. 本单 scope fence 是 apps/consoletool 样例 + 审计 + 守卫;PD#3 要求越界发现另立单。

translation 那条要特别说明:ObjectStackSchema.translationsArray< Record< locale, TranslationData > >,而 console 这份样例是 { name, label, locale, language, description, data } 的元数据记录形态 —— 更可能是映射选错而非样例过期,已在测试注释和 #3266 里如实标注,没有硬套。

守卫的强度上限(如实记录,别过度信任)

守卫有多严取决于 spec 有多严:tools / apps / flows / permissions / positions / datasources 的元素 schema 是 .strict(),而 views / jobs / emailTemplates 不是 —— 后者把未知键悄悄剥掉而不报错。也就是说这三类样例即便混进一个已退役的键,守卫照样绿。这一点写在测试文件头部,免得后来者误以为「绿 = 没有退役键」。这是 spec 仓的事(objectstack#4001 / #3896 同一条线),已记在 #3266 备查。

为什么没有 changeset

preview-samples.ts 文件头就写着 DEV-ONLY;实测 apps/console/vite.config.ts 没有 input: 配置,Vite 默认只以 index.html 为入口,preview-gallery.html 不是生产构建入口 —— 这份样例根本进不了发布的 dist,对 @object-ui/console 的消费者不可见。加上 AGENTS.md §9「纯 bug 修复不需要 changeset」,故不加。

验证

pnpm --filter @object-ui/console type-check   → 0 errors(先 turbo build 了 34 个依赖包)
pnpm --filter @object-ui/console test         → Test Files 20 passed (20) / Tests 177 passed (177)
pnpm --filter @object-ui/console lint         → 0 errors(187 条既有 warning,两个改动文件一条都没有)

相关

🤖 Generated with Claude Code

https://claude.ai/code/session_01NVPjPzmmAJ2Ngtvgg5MSRa


Generated by Claude Code

The `tool` sample in the metadata-designer gallery declared `category`,
`active` and `requiresConfirmation` — three keys `ToolSchema` retired and
now rejects by name (objectstack#3896; objectstack#3715 / ADR-0033 §2).

The sample is not a fixture: it is the worked example an author sees in
the designer's preview gallery, so it was teaching authors — and, more
often, models generating metadata — to write a tool definition that
`ToolSchema.parse()` refuses outright. Since objectui#3236 `ToolPreview`
no longer renders these keys either, so they were pure noise.

Also adds the guard that makes this class of rot a CI failure instead of
a lesson taught to the next author: each sample is embedded in a whole
stack and handed to `ObjectStackSchema.safeParse()`, which is the
authoring contract itself — no hand-picked schema export (the obvious
pick for `email_template` is the runtime send payload, not the authorable
record) and no Zod internals.

The audit it came from found `tool` was not alone: 12 of 20 samples fail
today, 4 of them carrying retired keys of exactly this kind. Those are
recorded in the test's `KNOWN_STALE` ledger with a reverse assertion —
an entry that starts passing fails the build and demands promotion, so
the list can only shrink — and tracked in objectui#3266.

No changeset: `preview-samples.ts` is dev-only. `preview-gallery.html` is
not a Vite build input, so none of this reaches the published `dist`.

Fixes #3257

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

vercel Bot commented Aug 3, 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 3, 2026 9:21am

Request Review

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

✅ Console Performance Budget

Metric Value Budget
Main entry (gzip) 28.1 KB 350 KB
Entry file index-CfHWHxqB.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.12KB 3.41KB
auth (LoginForm.js) 17.86KB 5.29KB
auth (PreviewBanner.js) 0.90KB 0.50KB
auth (RegisterForm.js) 6.43KB 2.09KB
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.24KB 104.56KB
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) 223.53KB 54.78KB
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) 37.96KB 10.54KB
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.54KB 56.80KB
plugin-editor (index.js) 2.46KB 1.10KB
plugin-form (index.js) 111.49KB 26.95KB
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

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.

console preview-samples 的 tool 样例仍带三个已退役的 ToolSchema 键(category / active / requiresConfirmation)

2 participants