fix(spec,service-automation): reconcile the CRUD/screen/map declared config with what the executors read (#4045) - #4228
Conversation
…config with what the executors read (#4045) Completes the declared-vs-read reconciliation across the flat builtins, after notify/http/connector in #4210. Six executor-derived Zod contracts land in automation/builtin-node-config.zod.ts (get/create/update/delete_record, screen + its field item, map), each written by READING the executor rather than transcribing the descriptor literal — so the new bidirectional ledger test is evidence, not a tautology. Contract exports only; nothing parses with them yet (step 3b, gated on the #4059 warning data). Writing them against the executors surfaced seven live capabilities that no form offered, online or offline — authorable only by hand-written metadata: get_record.fields (the projection passed into find/findOne), screen.recordId (the record `mode: 'edit'` opens — the form declared the mode but not its target), screen.fields[].options/defaultValue/placeholder (all three forwarded into the ScreenSpec the client renders, so a select field's choices could not be authored in Studio at all — the same nested repeater position as #3528), and map.indexVariable/map.input. All are declared on their descriptors now. map's undeclared `flow` alias went the other route: the executor carried `cfg.flowName ?? cfg.flow` for a spelling no schema ever described — the notify.source shape (PD #12). The bare fallback is deleted and flow-node-map-flow-alias (protocol 17, retires at 18) renames it at load, including the registerFlow rehydration seam. assignment is pinned as deliberately un-reconcilable with its reason on record: with no `assignments` wrapper its top-level config keys ARE the author's variable names, so no fixed key set describes it and a catchall Zod would reconcile vacuously. The ledger pins what CAN be pinned — the form offers exactly the canonical assignments map, and that map stays open. Every builtin publishing a configSchema is now reconciled against its executor; those publishing none each have a recorded reason. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UDhMtxPLLoFaGtdpNA7xTU
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 2 package(s): 106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
docs-drift 的判定:无需改文档,但其中一篇反而是本 PR 的独立佐证106 篇是 mapper 按「引用了
mode: 'create', // 'create' (default) | 'edit'
// recordId: '{account_id}', // required for mode: 'edit'手写文档一直把 其余六个键( 所以文档零改动:本 PR 是让表单追上文档和执行器早已达成的一致,而不是让文档追上代码。 Generated by Claude Code |
The ledger gate did its job before it merged. `main` landed automation/builtin-node-config.zod.ts (#4045/#4228) — eight new sites, sibling to io-node-config.zod.ts — and merging main turned the gate red on a branch whose own diff touches no schema at all. That is the intended behaviour: a new authorable surface arrived, so somebody has to classify it rather than let it sit in the ratchet's blind spot. Classified `authorable`, same family as io-node-config: the CRUD quartet, `screen` and `map` config contracts. Worth noting on the row — these were written from what the executors actually read rather than from the descriptors' configSchema literals, and `builtin-node-form-zod-ledger.test.ts` reconciles the two bidirectionally, so this row already carries a drift check of its own. Every pre-existing count survived the merge unchanged, so the failure was exactly as narrow as it should have been: one new file, one new row, one section total (automation 81 → 89). Verified after merging: spec suite 7163 green, tsc clean, and the strictness-ledger / liveness / variant-docs / docs gates all green with content/docs/references unchanged. Refs #4001 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0147tNF4Snk7Ry1KGt4a5PY4
…#4232) The ledger is the campaign's map — which z.object sites are authorable (the .strict() ratchet target), which are wire, which are deliberately open. Every step reads it to pick the next move, and nothing kept it honest. It went stale twice in one week: a classification verification disproved (hook.zod.ts carried a blanket `authorable (p)` covering a runtime shape, #4207), and a "next step" that had shipped months earlier (the unknown-key warning layer, #4218), which sent the next reader off to rebuild something already in production. A map that drifts is worse than no map, because it gets followed. CHECKS. Site counts (the ledger states its own method — "z.object( occurrences per file" — so every number is verifiable); coverage (every *.zod.ts with sites in a triaged directory needs a row); section totals; and that a row claiming "strict as of" names a file that really contains .strict(). The Class column is deliberately NOT checked. Authorable vs wire vs open is a judgement about who writes the input, and this campaign's rule is verify-before-tightening — encoding it would automate exactly the call that must stay human. The gate protects the arithmetic and coverage so that judgement is always made against current code. Zero-site files are skipped: five of the six the first run flagged hold no z.object at all (enum/token modules). Not a hole — the day one grows its first z.object( it becomes undeclared and the gate says so. FIRST RUN: 11 DRIFTS, in a file being actively edited by the campaign that owns it. Six counts had moved (ui/app.zod.ts 11 → 18, ui/touch.zod.ts 4 → 7, ui/action.zod.ts 8 → 9, ui/view.zod.ts 51 → 50, ui/responsive.zod.ts 6 → 4, automation/flow.zod.ts 12 → 11), two section totals no longer summed, and automation/io-node-config.zod.ts had no row. The app.zod.ts gap was self-inflicted: the app step (#4165) added seven schemas and updated the row's prose without touching its count. Then it worked for real before merging: main landed automation/builtin-node-config.zod.ts (#4045/#4228, eight sites), and merging main turned the gate red on a branch whose own diff touches no schema. Now classified. Every pre-existing count survived that merge unchanged, so the failure was exactly as narrow as it should have been. Verified the gate bites rather than just passes: a moved count fails (plus its section total), a deleted row resurfaces as an undeclared file, and stripping .strict() from approval.zod.ts fails its "strict as of" claim. Refs #4001
) Lands objectui#3082 in the platform — the flow inspector no longer lets a published `configSchema` delete a node's sibling-block editors (#4045). The server half of that fix shipped in #4210/#4228; this is what puts the client half in front of an author. Scope note for review: the pin was 76 commits stale (96ee72e, and objectui has been landing steadily), so this advances the vendored console by that whole range, not by one fix. There is no smaller option — the vendored build is a snapshot of a single objectui commit, and pinning #3082's own merge commit instead of the tip would drop only 3 of the 76. The generated changeset enumerates the range; beyond the #4045 fix it carries real feature and behaviour work, notably the notification surfaces mounting in the console, per-displayType notification presentation, the curated page:*/element:*/action:* SDUI contract, If-Match on form edit saves (409 instead of a silent overwrite), and searched-list export no longer downloading the unsearched superset. `packages/console/dist` is gitignored — the release pipeline rebuilds it from .objectui-sha — so the tracked diff is the pin plus its changeset. The build was run locally anyway (`scripts/build-console.sh`): it worktrees objectui at the pinned SHA, builds @object-ui/console, and asserts the framework-client bundle canary, which is what proves the SHA is actually buildable rather than merely newer. Co-authored-by: Claude <noreply@anthropic.com>
#4045 续 —— 接 #4210(notify / http / connector)之后,把对账扩到其余全部扁平内置节点。至此每个发布
configSchema的内置节点都与其执行器对过账,不发布的也都有记录在案的理由。六份执行器派生的 Zod
automation/builtin-node-config.zod.ts:GetRecordConfigSchema/CreateRecordConfigSchema/UpdateRecordConfigSchema/DeleteRecordConfigSchema/ScreenConfigSchema(含ScreenFieldConfigSchema)/MapConfigSchema。全部从读执行器写出,不是照
configSchema抄的(issue 里那条纪律)。与前两批一样是纯契约导出,运行时零变化;接parse属 3b,等 #4059 的分布数据。新增
builtin-node-form-zod-ledger.test.ts做双向键集对账。screen额外下钻一层比对fields仓面的条目键 —— #3528 就住在那个嵌套位置,只比顶层不够。抓到的 7 个「读而未声明」(本轮主要发现)
这 7 个键执行器一直在读、且都是活的能力,但线上线下两套表单都没提供,只能手写 metadata 才碰得到:
get_record.fieldsfind/findOnescreen.recordIdmode: 'edit'打开的那条记录 —— 表单声明了 edit 模式,却没有任何地方能指定它编辑谁screen.fields[].optionsscreen.fields[].defaultValue{var})screen.fields[].placeholdermap.indexVariablemap.input按 #4045 对「当前有效的键」定的路线处置:声明进 configSchema。
map.flow:走另一条路(转换层)执行器带着
cfg.flowName ?? cfg.flow—— 后者是任何 schema 都没描述过的拼法,notify.source同型(PD #12)。裸??删除,新增flow-node-map-flow-alias(ADR-0087 D2,protocol 17,18 退役)在加载时改名,含registerFlow再水化缝。新增测试证明的是转换生效,不是执行器容忍。assignment:入账为不可对账按 issue 早前的预判确认:没有
assignments包装时顶层 config 键就是作者自选的变量名,固定键集描述不了,加 catchall 的 Zod 会让对账空转(什么都匹配)。账本改为钉住能钉的部分:表单恰好提供 canonical 的assignments一个键,且该映射保持开放(additionalProperties: true、无固定 properties)。理由写在测试里。验证
map.flow过registerFlow缝的执行测试)check:generated:8 个生成物全绿(4 个按--fix精确再生)config-unknown-keys的断言随新声明键更新 —— 该测试打印「已声明键清单」,正是本次变更要改的输出已知环境噪声(非本 diff)
本地
pnpm test131 并发下cloud-connection的marketplace-install-local-state-machine-exempt30s 超时;单跑该包 64/64 全过,与本改动无交集(同一现象在 #4210 上也出现过)。🤖 Generated with Claude Code
https://claude.ai/code/session_01UDhMtxPLLoFaGtdpNA7xTU
Generated by Claude Code