Skip to content

feat(spec,automation): flow 的 update_record / delete_record 可以声明批量意图 multi - #5485

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-5393-flow-bulk-intent
Aug 5, 2026
Merged

feat(spec,automation): flow 的 update_record / delete_record 可以声明批量意图 multi#5485
os-zhuang merged 2 commits into
mainfrom
claude/issue-5393-flow-bulk-intent

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #5393

前提复核(rule 6,对 origin/main @ ed0d2aa)

issue 的三条事实全部成立,逐条实测:

  • DeleteRecordConfigSchema / UpdateRecordConfigSchema(packages/spec/src/automation/builtin-node-config.zod.ts)是 strictObject,前者只声明 objectName / filter,后者只多一个 fields —— 任何批量意图拼法都被 unrecognized_keys 拒。
  • packages/services/service-automation/src/builtin/crud-nodes.ts 的两个执行器调用 data.update / data.delete 时不传 options.multi
  • 引擎契约不变:resolveEngineDeleteDispatch(标量 where.id → 按 id;否则 options.multideleteMany;否则抛 Delete requires an ID or options.multi=true),update 侧在 engine.ts 有同款内联 throw。

所以谓词批量写从任何 app 的任何 flow 都不可达,而节点描述符写着 Delete Records / Delete records matching a filter. —— declared ≠ enforced(PD #10)。按在册裁定 A 实现。

改了什么

spec —— 两个 config 各声明一个同名键 multi(z.boolean().optional(),默认 false):

  • 缺省 / false:执行器传 multi: false,写入必须以标量 id 点名一行,谓词(含 id: { $in: [...] })由引擎拒绝。拒绝即契约,不是要绕过的缺陷。
  • true:执行器传 options.multi: true,落到 driver.updateMany / deleteMany,步骤的 acted 指标报出命中行数。
  • 名字复用引擎既有的词(EngineUpdateOptions.multi / EngineDeleteOptions.multi)—— 一概念一名(PD Add comprehensive test suite for Zod schema validation #12),从节点 config 到 driver 调用全程可 grep,不发明第二个词。

guidance(#4001 纪律,两道门都写) —— bulk / all / multiple 拿到点名处方,options: { multi: true } 被当作写错层回答(那是引擎的 options 包,不是节点 config)。这四个拼法是 #5225 诊断期真喂过 safeParse 的,编辑距离从它们中的任何一个都够不到 multi,没有这些条目拒绝就只会报个键名。同一份处方复制到 service-automation 引擎的注册门(FLOW_NODE_UNKNOWN_KEY_GUIDANCE),因为注册是作者在 boot 时撞到的第一道门,而 #4001 的发现正是:检测面会随默认值翻转自动泛化,处方不会

描述符表单 —— 两个节点的 configSchema 各加 multi,所以 form ↔ Zod 台账对得上,键在 Studio 里可授权,而不是只能手写元数据。

执行器 —— multi: cfg.multi === true每次调用上都写出来,而不是 true 时才 spread:multi: false 是让引擎拒绝谓词写的那一半契约,调用点的读者应该直接看见问的是哪一半,而不是从一个缺席的键推断。

packages/objectql 零改动。#3810 的抹除守卫零改动,并新增用例钉住它:声明 multi 不会解除它。

测试

新增 packages/services/service-automation/src/builtin/crud-bulk-intent.test.ts(10 例)。它的 delete 替身以 assertEngineDeleteDispatch(从 @objectstack/objectql import 的生产者自己的判定)开头,所以在任何输入上都不可能比真引擎松 —— 包括手抄守卫必漏的 id: { $in: [...] }(看着像 id,其实是谓词)。check-engine-double-contract 已把该文件认作 pinned

这正是 issue 点名的 #5197 盲点:run-summary.test.ts 的内联 async delete() { return false; } 既接受真引擎拒绝的谓词删除,又因为零参而躲过门禁的 arity 判定(isEngineDeleteShape 要求引擎的 (object, options) 形状),连被发现都做不到。

为此给 @objectstack/service-automation 加了 @objectstack/objectql devDependency —— 无环:objectql 的传递依赖闭包(12 包)不含 service-automation,turbo run build --filter=@objectstack/service-automation --dry 正常解析。scripts/engine-double-contract.baseline.json 里 8 条 service-automation 条目的 why 原本写着「不依赖 objectql,加依赖是另一次可评审动作」—— 那句话被本 PR 变成假的,所以按 plugin-auth 的 #3585 先例把它们改成 MEASURED 记述,并给 crud-filter-guard.test.ts 多留一句:它有几条 #3810 fixture 断言谓词删除成功,真引擎会拒;#5393 之后那是可表达的,所以将来 pin 它时那些 fixture 应该multi: true,而不是删掉。

update 侧刻意不 pin,并在文件头写明原因:objectql 没有导出 update 的判定函数(engine.ts 里是内联 throw),门禁自己的文件头也把 "update's twin dispatch" 列为待抽取后再覆盖的切片。所以 update 用例只断言执行器交给引擎的 options 包——执行器的全部义务——并明确不对引擎会不会接受它发表第二份意见;在 fake 里手抄一份 update 判定,恰恰是那道门禁存在的理由。已另开 issue 记录。

反向验证(方向是先判定后跑的:红)

预判:把执行器的 multi 接线撤掉,multi: true 的用例应当变红,因为替身钉在生产者判定上,谓词删除会拿到引擎真实的拒绝。实测撤线后 10 例中 7 例红:

AssertionError: expected { where: { stage: 'stale' }, …(1) } to match object { multi: false }
AssertionError: expected false to be true // Object.is equality
AssertionError: expected { where: { id: 'd2' }, …(1) } to match object { where: { id: 'd2' }, multi: false }
Tests  7 failed | 3 passed (10)

三例仍绿,各有诚实的理由,其中一条据此改强了:「multi: false 写出来与不写是同一个拒绝」原本只断言拒绝本身,而拒绝在接线前后都成立 —— 它会穿过本文件存在的意义所在的那次 revert 保持绿。已补上对 options 包的断言。另两条(#3810 守卫、描述符表单)本就与执行器接线无关,绿在两边是设计使然,不是空断言。

命令与真实输出

pnpm --filter @objectstack/spec test                 → Test Files 310 passed  / Tests 7941 passed
pnpm --filter @objectstack/service-automation test   → Test Files  56 passed  / Tests  675 passed
pnpm --filter @objectstack/lint test                 → Test Files  58 passed  / Tests 1229 passed
pnpm --filter @objectstack/objectql test             → Test Files 118 passed  / Tests 1911 passed
pnpm --filter @objectstack/metadata-protocol test    → Test Files  42 passed  / Tests  388 passed
pnpm --filter @objectstack/cli test                  → Test Files  79 passed  / Tests  767 passed
pnpm --filter @objectstack/spec typecheck            → Done

消费半径按规则的调用方扫过,不是按被改的包:CRUD node config 被 cli(validate/lint/migrate)、lint、metadata-protocol(canonicalization)、studio flow-builder 一并消费,全部跑过。cli 首轮 30 红,原因是新 worktree 里 @objectstack/setup 等依赖没构建(Failed to resolve entry for package),pnpm --filter '@objectstack/cli^...' build 后 79/79 全绿 —— 与本改动无关。

门禁:

check:generated                → ✓ All 9 generated artifacts are up to date
check-engine-double-contract   → OK — 23 pinned, 32 in the DEBT ledger, 1 exempt
check-nul-bytes                → OK (5457 files; no raw C0 control bytes)
check-i18n-bundles             → OK (9 packages, all in sync)
check-i18n-coverage            → OK (12 configs, 660 baselined, none new)
check:merge-driver / check:adr-anchors / check:durability-log-level /
check:startup-registry-verdict / check:type-check-coverage /
check:published-files / check:release-notes / check:stall-guard / … → OK

生成物随行:authorable-surface.json(+2 键)、content/docs/references/automation/builtin-node-config.mdx(+2 行),两者都由 gen:schema / gen:docs 整体重生成,未手改。strictness 台账重生成后无变化(往已 strict 的 shape 上加键不动计数)。

两处刻意留下的行为,写在这里而不是留给下一个读者

  1. multi: true 且没有 filter = 按声明清空整个对象。 引擎的派发表本就把「multi 且完全没有谓词」列为合法,Flow node filters silently blank date macros: the template engine consumes {…} before the query engine sees it #3810 的守卫按「作者写过的条件被抹掉」判定而不是按「filter 为空」判定(那是它注释里的原话,也有专门用例钉住)。flow 的 delete_record / update_record 无法表达批量意图 —— 节点 schema 无键、执行器不传 options.multi,谓词批量写对所有 flow 平台级不可达,而节点描述符宣称支持 #5393 之前这条路径不可达,现在可达了,但它是显式、可 grep 的声明。是否要在 authoring 期加告警,已另开 issue 交 PM 定级,不在本 PR 内发明。
  2. update_record 的破坏性并不比 delete_record 低多少(谓词 update 覆盖整表字段),但它的测试替身在结构上不能被绑到生产者契约。同上,另开 issue。

范围外发现(PD #10,均已建单、未指派)

后续

#5225(showcase 清扫流)挂 Blocked-by 本单,本单落地后由它以声明批量意图收尾。示例目录本 PR 未动。


Generated by Claude Code

claude added 2 commits August 5, 2026 12:47
…record (#5393)

`UpdateRecordConfigSchema` / `DeleteRecordConfigSchema` are strictObjects and
neither declared any spelling of bulk intent, while the CRUD executors never
passed `options.multi`. The data engine accepts a write only when `where.id` is
a scalar or `options.multi` is truthy and throws otherwise, so a predicate
update/delete was unreachable from any flow in any app — while the node
descriptors advertised "Delete records matching a filter." Declared != enforced
(PD #10); #5225's showcase sweep flow was the site it surfaced at.

Adds `multi` (boolean, default false) to both config contracts and wires the
executors to forward it as `options.multi`. One name for one concept (PD #12):
`multi` is the engine's own word for it, so the concept is greppable from node
config to driver call. The engine's rejection path is untouched — refusing an
undeclared predicate write IS the contract.

- spec: `multi` on both schemas, with `bulk`/`all`/`multiple` prescriptions and
  a wrong-layer answer for `options: { multi: true }` (edit distance reaches
  `multi` from none of them); the same curation is copied to the registration
  door in service-automation's engine, per #4001's finding that detection
  generalizes for free while prose does not.
- descriptors: `multi` declared on both designer forms, so the form<->Zod
  ledger reconciles and the key is authorable in Studio, not only by hand.
- tests: `crud-bulk-intent.test.ts`, whose delete double opens with
  `assertEngineDeleteDispatch` from @objectstack/objectql — the producer's own
  predicate — so it cannot accept a call the real engine refuses. That is the
  #5197 blind spot closed for this file: `run-summary.test.ts`'s zero-parameter
  `async delete() { return false }` accepts predicate deletes AND is invisible
  to check-engine-double-contract's arity test. Needed a devDependency on
  @objectstack/objectql (acyclic — objectql's closure does not contain
  service-automation); the eight service-automation baseline entries citing
  that missing dep as their blocker are updated to say so.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018fxLGQdatPbBUvCgiVxg6D
@vercel

vercel Bot commented Aug 5, 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 Aug 5, 2026 1:49pm

Request Review

@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file tests tooling labels Aug 5, 2026
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

109 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 @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/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 @objectstack/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/tenancy-modes.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/service-automation, @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/http-protocol.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/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/apps.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.

@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

⛔ merge queue 构建失败 — 先分诊,再决定要不要重排

队列构建 31015413774 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集),
所以失败的测试可能在本 PR 没碰过的包里 —— 那不是重排能修的。每次盲目重排都会让排在后面的所有 PR 重建一轮。

失败的 job(日志抽取,best effort):

  • Test Core (3/3) — 失败步骤: Run this shard's tests

    �[41m�[1m FAIL �[22m�[49m src/commands/serve-email-config-parity.contract.test.ts�[2m > �[22ma config the schema accepts reaches the plugin intact�[2m > �[22mspreads defaultTemplateContext OVER the re
    

历史信号:

  • 本 PR 过去 24h 无队列失败记录(首次)。
  • 过去 24h 队列共有 9 个失败构建(不含本次)。

分诊清单:

  1. 失败测试在本 PR 改动的包里 → 真回归,修 PR。
  2. 失败测试与本 PR 无关 → 在其他 PR 的同类评论里搜同名测试;出现过 ⇒ flaky 实锤,开 issue 修/隔离那条测试。修好前重排只会再烧一轮全队列。
  3. 两者都不是 → 可能与同组 PR 语义冲突;等前面的 PR 落地或失败出队后再重排一次即可,不要连续重排。

Generated by Claude Code · merge-queue-triage workflow (#4859)

Copy link
Copy Markdown
Contributor Author

分诊(spec 车道 PM):第 3 型同批连坐,非本 PR 回归,非 flaky。 失败测试 serve-email-config-parity.contract.test.ts 不在本 PR 改动内 —— 本次构建链在 #5465 之后,踩的是 #5465×#5498(appName 优先级翻转)的语义互锁,定因见 #5465 的分诊#5465 已出队对齐中;队列会自动把本 PR 重建到不含它的基线上,无需人工重排。flaky 检索者请勿把本次计入该测试的 flaky 记录。


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

2 participants