Skip to content

Commit f1cc3a3

Browse files
os-zhuangclaude
andauthored
fix(spec): app 表单摘掉八个已退役的墓碑键输入,#3786 对账门改判「真实可授权面」(#5280) (#5318)
`app.form.ts` 仍在给作者渲染八个 `retiredKey()` 墓碑键的输入控件 —— `version` / `homePageId` / `objects` / `apis` / `sharing` / `embed` / `mobileNavigation` / `aria`(issue 只点了后三个,全量扫出八个)。墓碑是 `z.never().optional()`,填进去不是静默丢弃,是整条 save 硬失败并抛出该键 的退役处方。八个控件全部移除,按套件惯例在原位留注释说明能力去向。 真正的价值在门这一侧:#3786 的对账断言用 `key ∈ shape` 判定「Zod 接受该 键」。在 `retiredKey()` 出现之前这两件事同义,之后不是了 —— 墓碑**按设计** 把键留在 walked shape 里(退役套件的 liveness 行正因为键还在才保留),于是 八个墓碑键对该断言永远为真,门绿着而作者拿到 422。 谓词收紧为「∈ shape 且不是墓碑」,双向都改: - form-only 方向拆成两条独立断言,失败信息能分清「未声明→静默丢弃」与 「墓碑→硬失败」,后者更响也更早; - zod-only 方向把墓碑键排除出「必须提供或立 ledger」的集合 —— 墓碑键的唯一 正确处置就是不提供。此前 `object.fields.conditionalRequired` 不被要求回填 纯属被一条无关的 `subset` ledger 条目挡住,是运气不是设计; - ledger 的 `omit`/`subset` 自检改用 authorable 键计数,避免墓碑撑住一条 已失效的豁免。 检测器判 **schema 节点**(optional 内层是否 `z.never`),不匹配键名 —— 键名 清单本身就是 #3786 要消灭的手抄列表。这是 `build-schemas.ts` 里 JSON Schema 侧 `isRetired()`(`{ not: {} }`)的 zod 侧孪生。四条合成 schema 上的自检把 机制钉住(含 union 成员仍 live 时不算退役的安全方向),不依赖任何当期墓碑, 免得墓碑随版本退场后这条门悄悄变空转。 RED-first 实证:谓词收紧、表单未改时,`app` 那条用例红,恰好点名八个键; 摘掉输入后 41/41 绿。 表单输入变动会带走 i18n 抽取键,四个 metadata-forms bundle 按 merge 模式 重生成(纯删除 8 组);两个分区描述原文仍在宣传已退役能力(zh-CN 的 basics/navigation 甚至点名「版本」「首页」),按 bundle 值可手写的惯例一并 订正为真实语义。 Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB Co-authored-by: Claude <noreply@anthropic.com>
1 parent 37e38d1 commit f1cc3a3

7 files changed

Lines changed: 263 additions & 152 deletions

File tree

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
---
2+
"@objectstack/spec": patch
3+
"@objectstack/platform-objects": patch
4+
---
5+
6+
fix(spec): stop offering retired `app` keys in the metadata form, and make the reconciliation gate see tombstones (#5280)
7+
8+
The `app` authoring form rendered **eight** controls for keys `AppSchema` had
9+
already retired to `retiredKey()` tombstones in 17.0.0 — `version`, `homePageId`,
10+
`objects`, `apis`, `sharing`, `embed`, `mobileNavigation` and `aria`. A tombstone
11+
is `z.never().optional()`, so filling one of those controls did not lose the
12+
value quietly: it failed the **entire save** with the key's removal
13+
prescription. The controls are gone, each with a comment in place naming where
14+
the capability went (`manifest.version`; the first `navigation` item by `order`
15+
plus `isDefault`; `defineStack({ objects })`; `defineStack({ apis })`;
16+
`FormView.sharing` for both public access and embedding; the component that
17+
renders the DOM node for `aria`).
18+
19+
Nothing about the contract changes — every one of these keys was already
20+
rejected at parse. What changes is that an author is no longer shown a control
21+
that can only produce a 422.
22+
23+
**The reconciliation gate now judges the right thing.** #3786's
24+
`metadata-form-zod-reconciliation.test.ts` asked whether an offered key was
25+
`∈ shape`. That was the same question as "may the author write this" until
26+
`retiredKey()` existed: a tombstone **deliberately stays in the shape** so the
27+
removal can carry its own upgrade prescription, so every one of those eight keys
28+
read as "the Zod accepts it" and the gate stayed green over all of them. It now
29+
asserts `∈ shape` **and not a tombstone**, in both directions — a retired key
30+
may not be offered, and its absence needs no ledger entry to excuse it. The
31+
detector reads the schema node (`z.never()` under the optional wrapper), never a
32+
list of key names, mirroring `isRetired()` on the JSON-Schema side of
33+
`build-schemas.ts`. The next `retiredKey()` retirement that forgets a form now
34+
fails this test instead of reaching an author.
35+
36+
Retiring an authorable key already required pruning its form input; that step is
37+
now enforced rather than remembered.

packages/platform-objects/src/apps/translations/en.metadata-forms.generated.ts

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -937,15 +937,15 @@ export const enMetadataForms: NonNullable<TranslationData['metadataForms']> = {
937937
},
938938
content: {
939939
label: "Content",
940-
description: "Objects and APIs this app uses."
940+
description: "Ambient assistant binding. Objects and endpoints are declared on the stack, not the app."
941941
},
942942
branding: {
943943
label: "Branding",
944944
description: "Theme colors and logo."
945945
},
946946
access_and_sharing: {
947947
label: "Access & sharing",
948-
description: "Who can access this app and how it can be embedded."
948+
description: "Who can access this app. Public access and embedding are per form view, not app-level."
949949
}
950950
},
951951
fields: {
@@ -959,9 +959,6 @@ export const enMetadataForms: NonNullable<TranslationData['metadataForms']> = {
959959
description: {
960960
label: "Description"
961961
},
962-
version: {
963-
label: "Version"
964-
},
965962
icon: {
966963
label: "Icon",
967964
helpText: "Lucide icon name (e.g. \"users\", \"briefcase\")"
@@ -981,22 +978,6 @@ export const enMetadataForms: NonNullable<TranslationData['metadataForms']> = {
981978
label: "Areas",
982979
helpText: "Group items into collapsible areas"
983980
},
984-
homePageId: {
985-
label: "Home Page Id",
986-
helpText: "Landing page when app opens"
987-
},
988-
mobileNavigation: {
989-
label: "Mobile Navigation",
990-
helpText: "Bottom tab bar config for mobile"
991-
},
992-
objects: {
993-
label: "Objects",
994-
helpText: "Object names this app exposes"
995-
},
996-
apis: {
997-
label: "Apis",
998-
helpText: "API endpoint definitions"
999-
},
1000981
defaultAgent: {
1001982
label: "Default Agent",
1002983
helpText: "AI agent for the ambient assistant button"
@@ -1008,18 +989,6 @@ export const enMetadataForms: NonNullable<TranslationData['metadataForms']> = {
1008989
requiredPermissions: {
1009990
label: "Required Permissions",
1010991
helpText: "Permissions needed to access this app"
1011-
},
1012-
sharing: {
1013-
label: "Sharing",
1014-
helpText: "Public/internal/restricted access control"
1015-
},
1016-
embed: {
1017-
label: "Embed",
1018-
helpText: "iFrame embed configuration"
1019-
},
1020-
aria: {
1021-
label: "Aria",
1022-
helpText: "Accessibility labels"
1023992
}
1024993
}
1025994
},

packages/platform-objects/src/apps/translations/es-ES.metadata-forms.generated.ts

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -937,15 +937,15 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
937937
},
938938
content: {
939939
label: "Contenido",
940-
description: "Objetos y API que usa esta app."
940+
description: "Asignación del asistente ambiental. Los objetos y endpoints se declaran en el stack, no en la app."
941941
},
942942
branding: {
943943
label: "Marca",
944944
description: "Colores de tema y logotipo."
945945
},
946946
access_and_sharing: {
947947
label: "Acceso y uso compartido",
948-
description: "Quién puede acceder a esta app y cómo puede incrustarse."
948+
description: "Quién puede acceder a esta app. El acceso público y la incrustación son por vista de formulario, no a nivel de app."
949949
}
950950
},
951951
fields: {
@@ -959,9 +959,6 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
959959
description: {
960960
label: "Descripción"
961961
},
962-
version: {
963-
label: "Versión"
964-
},
965962
icon: {
966963
label: "Icono",
967964
helpText: "Nombre de icono Lucide (p. ej. \"users\", \"briefcase\")"
@@ -981,22 +978,6 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
981978
label: "Áreas",
982979
helpText: "Agrupa elementos en áreas plegables"
983980
},
984-
homePageId: {
985-
label: "ID de página inicial",
986-
helpText: "Página de inicio al abrir la app"
987-
},
988-
mobileNavigation: {
989-
label: "Navegación móvil",
990-
helpText: "Configuración de barra de pestañas inferior para móvil"
991-
},
992-
objects: {
993-
label: "Permisos de objeto",
994-
helpText: "Nombres de objeto que expone esta app"
995-
},
996-
apis: {
997-
label: "API",
998-
helpText: "Definiciones de endpoints API"
999-
},
1000981
defaultAgent: {
1001982
label: "Agente predeterminado",
1002983
helpText: "Agente de IA para el botón de asistente ambiental"
@@ -1008,18 +989,6 @@ export const esESMetadataForms: NonNullable<TranslationData['metadataForms']> =
1008989
requiredPermissions: {
1009990
label: "Permisos requeridos",
1010991
helpText: "Permisos necesarios para acceder a esta app"
1011-
},
1012-
sharing: {
1013-
label: "Compartición",
1014-
helpText: "Control de acceso público/interno/restringido"
1015-
},
1016-
embed: {
1017-
label: "Incrustación",
1018-
helpText: "Configuración de incrustación iFrame"
1019-
},
1020-
aria: {
1021-
label: "Accesibilidad",
1022-
helpText: "Etiquetas de accesibilidad"
1023992
}
1024993
}
1025994
},

packages/platform-objects/src/apps/translations/ja-JP.metadata-forms.generated.ts

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -937,15 +937,15 @@ export const jaJPMetadataForms: NonNullable<TranslationData['metadataForms']> =
937937
},
938938
content: {
939939
label: "コンテンツ",
940-
description: "このアプリが使用するオブジェクトと API。"
940+
description: "アンビエントアシスタントの割り当て。オブジェクトとエンドポイントはアプリではなくスタックで宣言します。"
941941
},
942942
branding: {
943943
label: "ブランディング",
944944
description: "テーマカラーとロゴ。"
945945
},
946946
access_and_sharing: {
947947
label: "アクセスと共有",
948-
description: "このアプリにアクセス可能なユーザーと埋め込み方法。"
948+
description: "このアプリにアクセスできるユーザー。公開アクセスと埋め込みはアプリ単位ではなくフォームビュー単位です。"
949949
}
950950
},
951951
fields: {
@@ -959,9 +959,6 @@ export const jaJPMetadataForms: NonNullable<TranslationData['metadataForms']> =
959959
description: {
960960
label: "説明"
961961
},
962-
version: {
963-
label: "バージョン"
964-
},
965962
icon: {
966963
label: "アイコン",
967964
helpText: "Lucide アイコン名(例: \"users\", \"briefcase\")"
@@ -981,22 +978,6 @@ export const jaJPMetadataForms: NonNullable<TranslationData['metadataForms']> =
981978
label: "領域",
982979
helpText: "項目を折りたたみ可能なエリアにグループ化"
983980
},
984-
homePageId: {
985-
label: "ホームページ ID",
986-
helpText: "アプリ起動時のランディングページ"
987-
},
988-
mobileNavigation: {
989-
label: "モバイルナビゲーション",
990-
helpText: "モバイル用ボトムタブバー設定"
991-
},
992-
objects: {
993-
label: "オブジェクト権限",
994-
helpText: "このアプリが公開するオブジェクト名"
995-
},
996-
apis: {
997-
label: "API",
998-
helpText: "API エンドポイント定義"
999-
},
1000981
defaultAgent: {
1001982
label: "既定エージェント",
1002983
helpText: "常駐アシスタントボタン用 AI エージェント"
@@ -1008,18 +989,6 @@ export const jaJPMetadataForms: NonNullable<TranslationData['metadataForms']> =
1008989
requiredPermissions: {
1009990
label: "必要な権限",
1010991
helpText: "このアプリへのアクセスに必要な権限"
1011-
},
1012-
sharing: {
1013-
label: "共有",
1014-
helpText: "公開/内部/制限付きアクセス制御"
1015-
},
1016-
embed: {
1017-
label: "埋め込み",
1018-
helpText: "iFrame 埋め込み設定"
1019-
},
1020-
aria: {
1021-
label: "アクセシビリティ",
1022-
helpText: "アクセシビリティラベル"
1023992
}
1024993
}
1025994
},

packages/platform-objects/src/apps/translations/zh-CN.metadata-forms.generated.ts

Lines changed: 4 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -929,23 +929,23 @@ export const zhCNMetadataForms: NonNullable<TranslationData['metadataForms']> =
929929
sections: {
930930
basics: {
931931
label: "基础信息",
932-
description: "标识、版本与图标"
932+
description: "应用标识与启用状态"
933933
},
934934
navigation: {
935935
label: "导航",
936-
description: "菜单结构与首页"
936+
description: "侧边栏菜单项与分区分组"
937937
},
938938
content: {
939939
label: "内容",
940-
description: "对象、API 与默认智能体"
940+
description: "环境助手绑定。对象与接口在 stack 上声明,不在 app 上"
941941
},
942942
branding: {
943943
label: "品牌",
944944
description: "主题色与 Logo"
945945
},
946946
access_and_sharing: {
947947
label: "访问与共享",
948-
description: "权限、共享与嵌入"
948+
description: "谁可以访问此应用。公开访问与嵌入按表单视图授予,不是应用级开关"
949949
}
950950
},
951951
fields: {
@@ -959,9 +959,6 @@ export const zhCNMetadataForms: NonNullable<TranslationData['metadataForms']> =
959959
description: {
960960
label: "描述"
961961
},
962-
version: {
963-
label: "版本"
964-
},
965962
icon: {
966963
label: "图标",
967964
helpText: "Lucide 图标名(如 users、briefcase)"
@@ -981,22 +978,6 @@ export const zhCNMetadataForms: NonNullable<TranslationData['metadataForms']> =
981978
label: "区域",
982979
helpText: "将菜单项组织为可折叠分组"
983980
},
984-
homePageId: {
985-
label: "首页 ID",
986-
helpText: "应用打开时跳转的页面"
987-
},
988-
mobileNavigation: {
989-
label: "移动端导航",
990-
helpText: "移动端底部 Tab 栏配置"
991-
},
992-
objects: {
993-
label: "对象权限",
994-
helpText: "此应用暴露的对象名"
995-
},
996-
apis: {
997-
label: "API",
998-
helpText: "API 端点定义"
999-
},
1000981
defaultAgent: {
1001982
label: "默认智能体",
1002983
helpText: "右下角浮动助手按钮调用的 AI 智能体"
@@ -1008,18 +989,6 @@ export const zhCNMetadataForms: NonNullable<TranslationData['metadataForms']> =
1008989
requiredPermissions: {
1009990
label: "所需权限",
1010991
helpText: "访问该应用需要的权限"
1011-
},
1012-
sharing: {
1013-
label: "共享",
1014-
helpText: "公开 / 内部 / 受限的访问控制"
1015-
},
1016-
embed: {
1017-
label: "嵌入",
1018-
helpText: "iFrame 嵌入配置"
1019-
},
1020-
aria: {
1021-
label: "无障碍",
1022-
helpText: "无障碍标签与角色"
1023992
}
1024993
}
1025994
},

0 commit comments

Comments
 (0)