Repository navigation
Commit 02bceb7
fix(service-automation): the region refusal walks the child's suspension chain strictly and names the link it cannot read (#29403)
<!-- claude-projects-attribution: {"github_login":"os-steve"} -->
_Requested by **Steve** · [project
thread](https://claude.ai/code/project/chan_01FdL1XeUyvVNxLreh3F4Qi9?thread=cmsg_01FdL1XeUyvVNxLreh3F4Qi9JeMXnsbKRJxMbW9ZUq9nDf)_
Fixes #29333
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:区域(loop / parallel /
try_catch)内子流程挂起被拒绝时,取消子运行挂起链改用严格读取;链中间某一环读不到时,已读到的环照常取消,读不到的那一环仍交给
`cancelRun`,并记一条 `error` 点名该运行。
- 为什么改:原来用降级读取,中间环读不到就当作"没有挂起",遍历停在那里,它和它下面的运行一直停在存储里,只留一条 warn。
- 风险与回滚:只改取消路径的读取方式;恢复门禁等其它读者仍用原来的降级遍历,未改。拒绝本身(文字、不可路由)不变。回滚即还原
`dag-traversal.ts` 这一段和 `engine.ts` 新增的方法。
-
先看:`packages/services/service-automation/src/dag-traversal.ts`(`runRegion`
的取消段),再看 `engine.ts` 的 `resolveSuspensionChainForCancel` /
`walkSuspensionChain`,最后看测试文件末尾新增的两例。
- 两分钟验证:`pnpm --filter @objectstack/service-automation exec vitest run
src/subflow-region-suspension.test.ts`,预期 24 例全过。
- 需要你做的:无,席位落地。
## What changes
- `engine.ts`: the chain walk behind `resolveSuspensionChain` moves into
`walkSuspensionChain(runId, load)`; only the loader is a parameter, so
there is still one walk with the same depth bound and cycle guard.
`resolveSuspensionChain` calls it with the degrading `loadSuspendedRun`,
exactly as before, so the resume gate and its other readers see no
change. New `resolveSuspensionChainForCancel` calls it with the strict
`loadSuspendedRunStrict` (the read `cancelRun` already takes) and
returns `{ chain, unreadable? }`, where `unreadable` names the link
whose read threw, with the cause. A link that is simply not found still
ends the chain.
- `dag-traversal.ts` `runRegion`: the region refusal's cancel now walks
through `resolveSuspensionChainForCancel`. Every link it read is
cancelled, deepest first. If a link could not be read, that run is still
handed to `cancelRun` first (it reads again, owns the `error` record for
that run if the store is still unreadable, and is a no-op if the run is
gone), and one `error` record names it: any run it is itself parked on
was never reached and may still be parked. The refusal is thrown after,
unchanged.
- #29329's empty-chain branch is subsumed, not dropped: an unreadable
child now comes back as `unreadable` = the child, so it gets the same
single `cancelRun`. A child that is truly not found (the strict store
answered "no row") gets no cancel, where #29329 sent a `cancelRun` that
returned `false`. #29329's two tests pass unedited.
- Changeset: `.changeset/29333-region-refusal-deeper-chain-cancel.md`,
`@objectstack/service-automation` patch.
## Tests
Two cases added at the end of `src/subflow-region-suspension.test.ts`
(no existing test edited), on a three-run chain caller → `loop` →
`deal_review` → `deal_mid` → `deal_ask` (parks at `wait`):
- **middle link unreadable** (the store's `load` always throws for
`deal_mid` rows): the refusal text is unchanged; `deal_review` ends
`cancelled`; exactly one chain `error` record names `deal_mid`'s run id;
`cancelRun` is called for `deal_mid` then `deal_review`, deepest first.
`deal_mid` and `deal_ask` are still in the store, asserted as such.
- **CONTROL, all readable**: all three runs end `cancelled`, `cancelRun`
is called deepest first, and nothing is logged at `error`.
## Acceptance notes
- `deal_ask` (below the unreadable link) stays parked. Its id is only
recorded in `deal_mid`'s row (`subflow:` correlation), and a
`SuspendedRun` carries no parent pointer, so nothing can reach it while
that row cannot be read. The `error` record says so and tells the
operator to cancel the remaining runs once the store answers.
- When the unreadable link's store is still down, two `error` records
appear for it: `cancelRun`'s own skipped-cancellation line for that run,
and the chain record for the runs below it. They say different things,
so both stay.
## Verification
<details><summary>Ablation (scripts/ablation-replace.mjs, on-disk
proof): the cancel walk read through the degrading loader
again</summary>
```
ablation-replace: anchor "return await this.loadSuspendedRunStrict(id);" x1 -> x0
ablation-replace: ok mutation landed: anchor 1 -> 0, blob c937469 -> ebabde87953c
× an unreadable store: … the child run gets exactly one cancelRun, … (#29166)
× a store that answers again by the cancel: … (#29166)
× the middle link cannot be read: the child is cancelled, one `error` record names the middle run, …
AssertionError: one record names the unreadable link: expected [] to have a length of 1 but got +0
Tests 3 failed | 21 passed (24)
ablation-replace: ok restored: blob == HEAD (c937469) and `git diff HEAD` is empty
```
The CONTROL stays green under the mutation, as it should. The test
imports `./engine.js` from `src`, so no `dist` leg applies.
</details>
<details><summary>Final head a06cb53</summary>
```
head a06cb53 (merge of main 5924e50, which carries #29329, onto e6d2696):
pnpm --filter '@objectstack/service-automation...' build, typecheck, test under os-verify-lock, VERDICT command-exit 0
typecheck exit 0 · Test Files 260 passed (260) · Tests 3342 passed (3342)
vitest run src/subflow-region-suspension.test.ts Tests 24 passed (24)
turbo build ./packages/** (78/78) then pnpm check:dual-build-cjs-loads and pnpm lint VERDICT command-exit 0
dispatch-gates --ran: 62 derived, 62 run, 0 NOT-MEASURED (all exit 0)
Two first answered exit 3 (prerequisite, nothing measured) and were re-run green:
check-plugin-teardown-shape --self-test (after git fetch origin 621a487…, its pinned fixture commit)
check:dual-build-cjs-loads (after the ./packages/** build above)
```
</details>
---
_Generated by [Claude
Code](https://claude.ai/code/session_01ADHkmS5997viAw9y7Jknnz)_
<!-- signed: domain:server#3 · os-steve ·
session_01ADHkmS5997viAw9y7Jknnz -->
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 8e6ab05 commit 02bceb7
4 files changed
Lines changed: 144 additions & 18 deletions
File tree
- .changeset
- packages/services/service-automation/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
142 | 142 | | |
143 | 143 | | |
144 | 144 | | |
145 | | - | |
| 145 | + | |
146 | 146 | | |
147 | 147 | | |
148 | 148 | | |
| |||
751 | 751 | | |
752 | 752 | | |
753 | 753 | | |
754 | | - | |
| 754 | + | |
755 | 755 | | |
756 | 756 | | |
757 | 757 | | |
| |||
858 | 858 | | |
859 | 859 | | |
860 | 860 | | |
861 | | - | |
| 861 | + | |
| 862 | + | |
| 863 | + | |
| 864 | + | |
| 865 | + | |
| 866 | + | |
| 867 | + | |
862 | 868 | | |
863 | | - | |
864 | | - | |
865 | | - | |
866 | | - | |
867 | | - | |
868 | | - | |
869 | | - | |
870 | | - | |
871 | | - | |
872 | | - | |
873 | | - | |
| 869 | + | |
| 870 | + | |
| 871 | + | |
| 872 | + | |
| 873 | + | |
| 874 | + | |
| 875 | + | |
| 876 | + | |
| 877 | + | |
| 878 | + | |
| 879 | + | |
| 880 | + | |
| 881 | + | |
| 882 | + | |
| 883 | + | |
| 884 | + | |
| 885 | + | |
| 886 | + | |
| 887 | + | |
| 888 | + | |
| 889 | + | |
874 | 890 | | |
875 | 891 | | |
876 | 892 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7045 | 7045 | | |
7046 | 7046 | | |
7047 | 7047 | | |
7048 | | - | |
| 7048 | + | |
| 7049 | + | |
| 7050 | + | |
| 7051 | + | |
| 7052 | + | |
| 7053 | + | |
| 7054 | + | |
| 7055 | + | |
| 7056 | + | |
| 7057 | + | |
| 7058 | + | |
| 7059 | + | |
| 7060 | + | |
| 7061 | + | |
| 7062 | + | |
| 7063 | + | |
| 7064 | + | |
| 7065 | + | |
| 7066 | + | |
| 7067 | + | |
| 7068 | + | |
| 7069 | + | |
| 7070 | + | |
| 7071 | + | |
| 7072 | + | |
| 7073 | + | |
| 7074 | + | |
| 7075 | + | |
| 7076 | + | |
| 7077 | + | |
| 7078 | + | |
| 7079 | + | |
| 7080 | + | |
| 7081 | + | |
| 7082 | + | |
| 7083 | + | |
| 7084 | + | |
| 7085 | + | |
| 7086 | + | |
| 7087 | + | |
7049 | 7088 | | |
7050 | 7089 | | |
7051 | | - | |
| 7090 | + | |
7052 | 7091 | | |
7053 | 7092 | | |
7054 | 7093 | | |
| |||
7059 | 7098 | | |
7060 | 7099 | | |
7061 | 7100 | | |
7062 | | - | |
| 7101 | + | |
7063 | 7102 | | |
7064 | 7103 | | |
7065 | 7104 | | |
| |||
9357 | 9396 | | |
9358 | 9397 | | |
9359 | 9398 | | |
9360 | | - | |
| 9399 | + | |
9361 | 9400 | | |
9362 | 9401 | | |
9363 | 9402 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
361 | 361 | | |
362 | 362 | | |
363 | 363 | | |
| 364 | + | |
| 365 | + | |
| 366 | + | |
| 367 | + | |
| 368 | + | |
| 369 | + | |
| 370 | + | |
| 371 | + | |
| 372 | + | |
| 373 | + | |
| 374 | + | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
| 378 | + | |
| 379 | + | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
| 386 | + | |
| 387 | + | |
| 388 | + | |
| 389 | + | |
| 390 | + | |
| 391 | + | |
| 392 | + | |
| 393 | + | |
| 394 | + | |
| 395 | + | |
| 396 | + | |
| 397 | + | |
| 398 | + | |
| 399 | + | |
| 400 | + | |
| 401 | + | |
| 402 | + | |
| 403 | + | |
| 404 | + | |
| 405 | + | |
| 406 | + | |
| 407 | + | |
| 408 | + | |
| 409 | + | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
| 421 | + | |
| 422 | + | |
| 423 | + | |
| 424 | + | |
0 commit comments