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 1 package(s): 112 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 |
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