refactor(spec)!: 按 ADR-0049 退役 FieldMapping.transform 与整个 FieldMappingTransform 联合 —— 五成员零执行者 (#5552) - #6078
Conversation
…gTransform 联合 (#5552) WIP: schema tombstone + D2 conversion + D3 chain step + 两张退役登记表首入。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
…gTransform 联合 (#5552) 测量结论:五个成员(constant/cast/lookup/javascript/map)无一存在执行者。 fieldMappings 只在 packages/spec 自身被拼写,connector 包/automation engine/REST/ objectui 均不读取,全仓无任何代码对 transform.type 分支。 退役套件:retiredKey() 墓碑(schema 与两个 extender 均为普通 z.object,直接删键会静默 strip)、D2 conversion field-mapping-transform-removed、D3 chain step、 RETIRED_KEYS_BY_MAJOR/RETIRED_DEFS_BY_MAJOR 两表首批条目、生成物重生成、changeset。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 2 package(s): 113 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
CI 状态(截至 19:41Z)两个必看门禁均已 completed / success,逐 job 读取(非聚合状态):
后三个仍在排队与本 PR 无关:runner 池今晚整体饱和(18:09 注册后曾出现全仓 90 runs queued / 0 in_progress 持续约一小时的窗口;此刻仍有 52 个排队),排队的是整个仓库的 PR 与 merge queue,不是这一个分支。作为替代证据,
顺带记录的范围外发现#6085 —— Generated by Claude Code |
…ldmapping-transform-retire # Conflicts: # packages/spec/api-surface.json # packages/spec/authorable-surface.json # packages/spec/json-schema.manifest.json
api-surface/shared.json −2 导出;authorable-surface 的 data/integration/shared 三个分片各一行转 [RETIRED];json-schema.manifest/shared.json −1 def。 manifest ratchet 在分片布局下再次自行开火并点名分片路径,处置同前:有意删除 + RETIRED_DEFS_BY_MAJOR 已登记。check:generated 10/10 绿。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
补圈同步完成:#5837 分片布局(main
|
| 生成物 | 分片 | 读数 |
|---|---|---|
json-schema.manifest/ |
shared.json |
−1 def(shared/FieldMappingTransform) |
api-surface/ |
shared.json |
−2 导出(FieldMappingTransform (type) / FieldMappingTransformSchema (const)) |
authorable-surface/ |
shared.json |
shared/FieldMapping:transform [RETIRED] |
authorable-surface/ |
integration.json |
integration/ConnectorFieldMapping:transform [RETIRED] |
authorable-surface/ |
data.json |
data/ExternalFieldMapping:transform [RETIRED] |
三个 ratchet 的总占地:5 个文件 / 3 insertions / 6 deletions —— 比单体布局下的同一改动小得多,正是 #5837 要拆掉的那份串行税。一个附带的好证据:data.json 里 data/ImportFieldMapping:transform 没有 [RETIRED] 标记,与同分片上一行的 data/ExternalFieldMapping:transform [RETIRED] 并排 —— 活的导入映射与已退役的字段映射变换,在同一个文件里逐行可对照。
manifest ratchet 在分片布局下再次自行开火,并点名了分片路径,处置与单体时一致(有意删除 + RETIRED_DEFS_BY_MAJOR 登记,后者在 merge 中原样存活):
❌ 1 previously published schema(s) disappeared from this build:
- json-schema/shared/FieldMappingTransform.json
… delete the key(s) from packages/spec/json-schema.manifest/<category>.json
in the same PR AND declare each one in RETIRED_DEFS_BY_MAJOR
重新验证(重活均持 flock 串行)
| 命令 | 结果 |
|---|---|
pnpm --filter @objectstack/spec check:generated |
✓ All 10 generated artifacts are up to date. |
pnpm --filter @objectstack/spec test |
326 files / 8332 passed(合并前 325/8305,增量来自 main 侧新增用例) |
pnpm --filter @objectstack/spec typecheck |
PASS(tsc --noEmit + test-typecheck 债本未增长) |
changeset 不变(@objectstack/spec major)。CI 会在新 head 上重跑;上一轮 head 的 ESLint / TypeScript Type Check 两个必看门禁均为 completed/success。
Generated by Claude Code
1. packages/spec/variant-docs.json:孤儿条目 "sync mapping transform" (key type:cast|constant|javascript|lookup|map) —— 该判别联合已随 FieldMapping.transform 一并退役,按 check:variant-docs 自身处方删除条目。 门读数:19 unions / 9 governed / exempt 11 → 10。 2. packages/qa/dogfood/test/expression-conformance.ledger.ts:cel-formula 行的 cover 'shared/mapping.zod.ts:expression' —— 该 expression 面由被退役的 javascript 成员携带,源码里已不存在,故删除该 cover(沿用同一行 #3605 kernel/feature.zod.ts 的处理先例并留注释);summary 同步去掉 "+ mapping expressions",因该侧已无可解释的东西。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
回派两处实红已修新 head: 两处都是退役收尾面 —— 剧本里「漏一件就有门开火」的那一类。它们此前没被我本地扫到的原因很具体,记录下来: 1.
|
…ldmapping-transform-retire
main 落了 6e82972(#5606/#6058):docs-gen 把 retiredKey() 墓碑从 any 渲染成 never。本分支的 FieldMapping.transform 墓碑正好出现在这三页上,故 merge ref 用新 渲染器重算与分支内旧产物不一致。按门自身处方 gen:schema + gen:docs 重生成。 三页 diff 即 any→never 形状(同页上先前就存在的 rateLimitConfig 墓碑 #4911 也一并 翻新,可证是渲染器变更而非本分支改动),外加同一渲染器变更的连带效果: fieldMappings 的内联形状摘要不再打印 never 化的已退役属性,腾出的位置显示真实键。 strictness ledger 的 cloud/ 82→83 来自本次 merge(本分支从未触及 cloud/)。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
补圈完成:按 #6058 新渲染器重生成三张墓碑页新 head: merge 干净无冲突(三个单体生成物在上一轮已随分片同步删除,本轮无可冲突面)。 三页 diff 确认为墓碑渲染 any→never 形状一条独立佐证归因的证据:同在 即该渲染器变更命中的是页面上的每一处墓碑,不是本分支的改动 —— 与 PM 的归因一致。 另有同一渲染器变更的连带效果,一并如实记录(不是额外改动,是 一处顺带转绿的门(非本分支引入)重生成后 复验本轮改动面恰好四个文件,无溢出:三张参考页 + strictness ledger。 changeset 不变( Generated by Claude Code |
`git merge origin/main` 把 counts.md **干净且错误**地合并了:本分支侧 的 `shared/ | 25` 胜出,而 main 侧 #6078 退役 `FieldMappingTransform` 五成员联合后该目录已是 20。两侧改动不重叠(本分支动分桶表,main 动 未分诊目录表),所以文本合并无冲突 —— 正是 #5107 记录的那种失败: 与谁都不冲突的数字合并干净、两边都错。 按台账自己的规矩从**合并树**整体重算(`gen:strictness-ledger`), `shared/` 25 → 20。分桶与 197 的总数不受影响。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011M7UwH25Unfi73UHim7ajY
Fixes #5552
按 01:26Z 维护者裁决执行 B(enforce-or-remove)两步走。A(只修 describe)已被否决,本 PR 不做 describe 镀金。
第一步:四成员消费面实测(裁决要求的前置测量)
对
constant/cast/lookup/map(以及报单已指出的javascript)三层测量,基线origin/main@efedd28:examples//skills// objectui 均无。showcase 唯一的映射写的是transform: 'map'—— 裸字符串,属另一个 schemaAutomationEngine.registerConnector/registerDegradedConnector→ConnectorSchema.parse,会走到fieldMappings[](service-automation/src/engine.ts:1692,1771)fieldMappings只在packages/spec内被拼写;四个 connector 包、automation engine、REST、objectui 都不读它;全仓无任何代码对transform.type分支反查证伪扫描器(零命中时的必要控制):成员专属词在树里都找得到 ——
targetType70 处、keyField66 处、valueField260 处。扫描器工作正常,消费者是真的不存在。cloud 面:未验,如实标注。 本会话对
objectstack-ai/cloud无读权限(add_repo返回 "you don't have access"),且 GitHub code search 不索引该仓 —— 控制查询repo:objectstack-ai/cloud objectstack返回 0 命中且incomplete_results: true,即"查不到"不等于"没有"。按 #5540 的处理方式记为未验证,不冒充已验。结论:全死。 五个成员一起 declared-but-unenforced,不只是报单指出的那一个 —— 于是按裁决走「整个
FieldMappingTransformSchema联合退役」。javascript成员仍是让缺口显形的那一个,报单的三处打架全部复现:describe 推荐的dialect: "js"被枚举拒收(js于 #3278 / ADR-0058 addendum 退役);唯一能过 parse 的裸字符串被ExpressionInputSchema包成dialect: 'cel';而同一行给的例子value.toUpperCase()作为 CEL 不成立。第二步:退役路线选择(裁决要求在正文论证)
这是 authorable 面,不是纯类型面 —— 两个判据都指向同一边:
.parse()消费:上表第二行,ConnectorSchema.parse是活的接收者。所以处方有人能收到,不适用 playbook 的「nothing parses it → 两者都不做」那一行(那是findStream/IStorageService.list的形状 —— 纯 TS 契约,没有任何东西跑过.parse())。shared/FieldMapping:transform、integration/ConnectorFieldMapping:transform、data/ExternalFieldMapping:transform三行都在。取
retiredKey()墓碑路线,理由是 schema 形态:FieldMappingSchema与两个 extender 全是普通z.object(非.strict())。直接删键会被 zod 静默 strip —— 用一个静默 no-op 替换另一个静默 no-op,正是 #3726 / #3733 那一类。墓碑给出两个通道:tsc拒绝赋值,parse 抛出处方本身。.extend(),会把该属性复制进各自的 shape,所以RETIRED_KEYS_BY_MAJOR按键逐条登记三条(网关按精确集合成员匹配,不从基类辐射)。这一点单独立了个 pin 测试。生成物读数 —— 按路线核对,不要反过来
playbook 明确:整 def 删除必须让四张 ratchet 动;枚举值收窄则仪器上不可见。本 PR 是前者,读数如下,并且
json-schema.manifest.json的 ratchet 先自己开火了,这串输出本身就是路线的证据:按要求有意删除 manifest 行,并在
RETIRED_DEFS_BY_MAJOR登记。最终读数:authorable-surface.json:三行转[RETIRED](墓碑路线的签名)api-surface.json:−2(FieldMappingTransform (type)/FieldMappingTransformSchema (const))json-schema.manifest.json:−1 defRETIRED_KEYS_BY_MAJOR/RETIRED_DEFS_BY_MAJOR:自 build-schemas.ts 检查 (b) 用叶名匹配 conversion surface —— 无关簇的.type就能让一个 tombstone 冒充「已登记迁移」 #4659 / json-schema.manifest.json 的「deliberate removal」删行仍是纪律而非门禁 —— #4650 的同类洞,上移一层(整 schema 级) #4725 建表以来两张表的首批条目toMajor: 17,照抄现行 protocol-17 chain step 先例(包版本17.0.0-rc.2,仍在 rc 窗口内)。反向验证(sabotage)—— 方向在跑之前就写死了
预测:恢复联合与键之后,
[#5552]系列 pin 全部转红(普通方向,不是反转方向 —— 因为墓碑是这些判决的唯一来源:FieldMappingSchema是普通z.object,没有墓碑就只有"接受"或"静默 strip",没有底下的 schema 级拒绝可以兜)。实测:7 条 pin 转红,失败文本正是"能力被恢复"的签名。其中两条值得单独说:
…and a member that used to be VALID fails identically报expected '' to contain 'TS2322'—— 恢复后该 probe 零诊断地编译通过。这条 pin 存在的唯一目的就是抓「联合被悄悄恢复」,而它给出的正是最强信号。transform是 retired —— 恢复后仍有诊断,但换成了另一条(Type '"custom"' is not assignable…,值判决)。所以这条 pin 断言的是具体文本而非"非空":只断言非空的话,它在 sabotage 下会保持绿色,是个 phantom check。成对 probe(一个已退役成员 + 一个原本合法成员)才是让这件事可测的设计。另有 1 条 pin 在 sabotage 下保持绿色,如实说明:
a mapping without the key parses and carries no transform at all—— 它是 strip 正路 pin,没写该键的映射两边都能解析,本来就不该变色。一处如实修正:墓碑的 tsc 通道不点名该键
第一版 probe 断言
toContain('transform'),实测失败。retiredKey()是z.never().optional(),其输入类型是undefined,tsc 报的是TS2322: Type '{ … }' is not assignable to type 'undefined'—— 拒绝了,但没有点出键名。处方完整地挂在 parse 通道上。测试里按实测形状钉住并写明了这个差别,而不是按想当然的形状写。未受影响(⛔ 未碰)
ExpressionDialect本体及其它 schema —— 按指令未动。ExternalLookup.transform—— lookup 级的{ request, response }管线,与字段映射的transform同名不同物。mapping.fieldMapping[].transform—— 这才是活的那条:扁平字符串枚举,由 REST 导入路径逐行执行,liveness ledger 逐键记录在案。它对自己的javascript值是直接 400 拒收(服务端无沙箱)。同一个词,相反的处置 —— 一边跑并且说清楚,另一边从来没跑过。data/mapping.zod.ts的三点差异注释已按此更新。验证
pnpm --filter @objectstack/spec testpnpm --filter @objectstack/spec typechecktsc --noEmit+ test-typecheck 债本未增长)pnpm --filter @objectstack/spec check:generatedcheck:liveness/check:empty-state/check:skill-examples/check:exported-any/check:dual-source-exports@objectstack/clitest(migrate-meta e2e / chain replay)@objectstack/metadatatest@objectstack/metadata-protocoltestnode scripts/check-nul-bytes.mjsliveness ledger 无需改动:ledger 按元数据类型建档,
shared/FieldMapping家族没有 ledger 行(liveness/mapping.json记的是MappingSchema,即上面那条活的导入映射)。check:liveness绿,既无 UNCLASSIFIED 也无 ORPHAN。Generated by Claude Code