fix(showcase): declare multi: true on the inquiry purge flow's delete node (#5225) - #5534
Merged
Merged
Conversation
…te node (#5225) `InquiryPurgeFlow`'s `purge` node deletes by the predicate `{ status: 'closed' }` but declared no bulk intent. The data engine accepts a write without `options.multi` only when `filter` names ONE row by a scalar `id`, so every run of this flow failed on that node: Node 'purge' failed: delete_record(showcase_inquiry) failed: Delete requires an ID or options.multi=true with `acted: 0` — identically on both paths, the declarative endpoint `POST /api/v1/apps/showcase/inquiries/purge` and the built-in trigger route `POST /api/v1/automation/showcase_inquiry_purge/trigger`. The delete half of the CRUD quartet `src/coverage.ts` claims this flow demonstrates had therefore never executed once (declared != enforced, PD #10); #5112's boot probes are what finally reached it. The fix is a DECLARATION, not a rewrite. Until #5393 (PR #5485) no spelling of bulk intent existed on the node config at all, which is why the third triage round correctly refused a get-then-loop-then-delete-by-id rewrite as a PD #5 workaround and escalated instead. `filter` stays: `multi: true` with an absent or empty filter is a declared whole-object delete, and this node is meant to be #5482's zero-warning sample for exactly that distinction. Verified on a real `--fresh` boot, both probes, with row counts: endpoint selected 2 -> purge acted 2, success true, 4 rows -> 2 (both `closed` rows gone, both non-closed survived) trigger selected 3 -> purge acted 3, success true, 5 rows -> 2 Reverse-verified by stripping the declaration and rebooting: both probes returned to the byte-identical original failure above with `acted: 0` and the row count unchanged. The new example test states the rule as a two-sided invariant over EVERY `delete_record` / `update_record` node rather than asserting one node, and walks ADR-0031 structured containers — `showcase_task_crm_sync`'s `catch` region holds an `update_record` a flat scan of `flow.nodes` misses. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016FNvXhtSdnEGEfLEsMmvxh
…wcase-purge-multi
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
baozhoutao
marked this pull request as ready for review
August 5, 2026 15:51
baozhoutao
enabled auto-merge
August 5, 2026 15:53
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #5225
前提复核(先证后改)
按 os-dev 规程,动手前先对
origin/main核实 issue 前提 —— 两条都成立:multi已可用:packages/spec/src/automation/builtin-node-config.zod.ts的DeleteRecordConfigSchema/UpdateRecordConfigSchema已声明multi: z.boolean().optional()(flow 的
delete_record/update_record无法表达批量意图 —— 节点 schema 无键、执行器不传options.multi,谓词批量写对所有 flow 平台级不可达,而节点描述符宣称支持 #5393 / PR feat(spec,automation): flow 的 update_record / delete_record 可以声明批量意图multi#5485,已在168f60f1a合入 main);执行器packages/services/service-automation/src/builtin/crud-nodes.ts:493以multi: cfg.multi === true转发给data.delete。InquiryPurgeFlow的purge节点仍是config: { objectName, filter: { status: 'closed' } },一行批量意图都没有。第 3 轮 dev 的 needs_decision 遗产(两条探针 byte-identical 失败记录、C 方案否决
理由)按派发口径直接沿用,未重做。
改动
purge节点声明multi: true,保留filter:这是「补一个声明」,不是改写流程。#5393 之前节点 config 上根本不存在任何批量意图的
拼写,这正是第 3 轮分诊拒绝 get→loop→逐 id 删的原因(PD #5 workaround)。
filter在这里不是修饰:multi: true而 filter 缺失/为空 =声明式整表删除。本节点是「批量意图 + 谓词边界」的参考样本,也是 #5482 lint 规则未来
的「必须零告警」验收样本。
同时核过清扫流内没有其它谓词批量节点;全 app 另外 3 个
update_record节点都用标量filter: { id: '{...}' }点名单行,不需要也不应该声明multi。验收:真机 boot 复跑两条探针
pnpm dev -- --fresh -p 39457,admin 登录后打 05:15Z 记录的同样两条路径。探针 1 —— 声明式端点
POST /api/v1/apps/showcase/inquiries/purge行数实测 4 → 2:两条
closed行真删,两条非 closed 行(new / contacted)原样存活 ——filter 确实兜住了删除范围。
探针 2 —— 内建触发路由
POST /api/v1/automation/showcase_inquiry_purge/trigger行数实测 5 → 2(3 条 closed 全删)。两条路径均
acted > 0,原失败签名消失。反向验证(方向先判后跑)
预判:去掉
multi: true应恰好回到 issue 记录的原始失败签名。去掉后重启真机复跑两条探针,结果与预判一致、逐字相同:
行数前后不变。随后已还原声明。
测试
新增
examples/app-showcase/test/predicate-write-bulk-intent.test.ts(17 例)。该 example 的测试 harness 不 boot 真实 stack(无
@objectstack/verify依赖,全部测试读静态元数据),所以真删断言由上面的真机探针承担,example 层加的是静态钉。钉的写法有
两点是刻意的:
delete_record/update_record节点的双向不变量 —— 谓词写必须声明multi: true;multi: true必须带非空
filter(multi: true且filter为空的 delete_record / update_record 是「按声明清空整个对象」,authoring 期零诊断 —— #3810 的守卫按「条件被抹掉」判定,不按「条件为空」判定 #5482 的那一面)。只钉一个节点的话,将来有人加第二个谓词写时它会以「什么都没查」的方式继续绿。
showcase_task_crm_sync的catch区里就藏着一个update_record,只扫顶层flow.nodes会漏掉它 —— 正是这个守卫要防的那一类缺陷藏在下一层。走法对对象图通用,不枚举
try/catch/body之类容器键名,免得新容器形状悄悄落在覆盖外;并单钉
record_failure必须被收集到,防止回退成平扫。节点 config 走真实 spec schema 的
safeParse:此处判的是值的裁决(multi为true、filter为非空谓词),不是「键是否可写」,所以要求 full parse green 才是对的档位。静态反向验证同样先判后跑:去掉
multi: true后恰好 2 例转红(sweep 规则 + purge专项断言),而
parses green一例保持绿 —— 因为multi按设计是 optional,不写它是合法的、刻意的选择。也就是说这条回归在执行层,静态层由 sweep 规则兜住,测试里把
这个方向明写了下来,没有硬套「改前绿/改后红」的模板。
范围
仅
examples/app-showcase/**+ changeset,未动packages/**(spec/执行器已就位)。changeset 按 #5438 先例:private 包,空 frontmatter,不发布任何包。
无越界发现需要另行立项。
Generated by Claude Code