Skip to content

fix(spec,service-automation): reconcile the CRUD/screen/map declared config with what the executors read (#4045) - #4228

Merged
os-zhuang merged 1 commit into
mainfrom
claude/amazing-darwin-2z3fab
Jul 30, 2026
Merged

fix(spec,service-automation): reconcile the CRUD/screen/map declared config with what the executors read (#4045)#4228
os-zhuang merged 1 commit into
mainfrom
claude/amazing-darwin-2z3fab

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

#4045 续 —— 接 #4210(notify / http / connector)之后,把对账扩到其余全部扁平内置节点。至此每个发布 configSchema 的内置节点都与其执行器对过账,不发布的也都有记录在案的理由。

六份执行器派生的 Zod

automation/builtin-node-config.zod.tsGetRecordConfigSchema / 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.fields 查询字段投影,直通 find/findOne
screen.recordId mode: 'edit' 打开的那条记录 —— 表单声明了 edit 模式,却没有任何地方能指定它编辑谁
screen.fields[].options select 型字段的选项,转发进客户端渲染的 ScreenSpec —— 意味着下拉选项在 Studio 里根本没法配
screen.fields[].defaultValue 预填值(支持 {var}
screen.fields[].placeholder 输入提示
map.indexVariable 索引变量绑定
map.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)。理由写在测试里。

验证

  • spec:7169 通过(含新 conversion fixture 走 registry)
  • service-automation:43 文件 / 476 通过(含 map.flowregisterFlow 缝的执行测试)
  • check:generated:8 个生成物全绿(4 个按 --fix 精确再生)
  • config-unknown-keys 的断言随新声明键更新 —— 该测试打印「已声明键清单」,正是本次变更要改的输出

已知环境噪声(非本 diff)

本地 pnpm test 131 并发下 cloud-connectionmarketplace-install-local-state-machine-exempt 30s 超时;单跑该包 64/64 全过,与本改动无交集(同一现象在 #4210 上也出现过)。

🤖 Generated with Claude Code

https://claude.ai/code/session_01UDhMtxPLLoFaGtdpNA7xTU


Generated by Claude Code

…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
@vercel

vercel Bot commented Jul 30, 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)
objectstack Ignored Ignored Jul 30, 2026 4:34pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/l labels Jul 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/service-automation, @objectstack/spec.

106 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/service-automation, @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/service-automation, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/service-automation, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/service-automation, @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Copy link
Copy Markdown
Contributor Author

docs-drift 的判定:无需改文档,但其中一篇反而是本 PR 的独立佐证

106 篇是 mapper 按「引用了 @objectstack/spec」的粗粒度命中(#4162 跟踪的信噪比问题),逐篇复核不成立。但我查了唯一可能真受影响的那篇 —— content/docs/automation/flows.mdx,因为本 PR 新声明了 7 个可授权键 —— 结论是不需要改,且它证明了这些键本来就该是可授权的

flows.mdx:270,object-form screen 的示例里:

mode: 'create',                    // 'create' (default) | 'edit'
// recordId: '{account_id}',        // required for mode: 'edit'

手写文档一直把 recordId 写成 mode: 'edit' 的必填项,执行器也一直读它 —— 只有设计器表单不提供。也就是说文档与执行器两侧本来就一致,表单是那个异类:一个作者照文档写就能用,照 Studio 点就配不出 edit 模式的目标记录。这比我在 PR 描述里给的「执行器读了但没人声明」更强一档 —— 它是声明在别处、唯独表单缺席

其余六个键(get_record.fieldsscreen.fields[].options/defaultValue/placeholdermap.indexVariable/input)在手写文档里没有出现,属于纯粹的读而未声明。

所以文档零改动:本 PR 是让表单追上文档和执行器早已达成的一致,而不是让文档追上代码。


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review July 30, 2026 17:22
@os-zhuang
os-zhuang merged commit 62a789b into main Jul 30, 2026
26 of 27 checks passed
@os-zhuang
os-zhuang deleted the claude/amazing-darwin-2z3fab branch July 30, 2026 17:22
os-zhuang pushed a commit that referenced this pull request Jul 30, 2026
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
os-zhuang added a commit that referenced this pull request Jul 31, 2026
…#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
os-zhuang added a commit that referenced this pull request Jul 31, 2026
)

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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants