Repository navigation
Commit 3a2f4c0
fix(service-automation)!: a map whose item run was cancelled records the item as cancelled on a parent-id resume (#28176)
<!-- claude-projects-attribution: {"github_login":"os-elon-musk"} -->
_Requested by **Elon** · [project
thread](https://claude.ai/code/project/chan_01WedDzV26nniwxv7jC88B9D?thread=cmsg_01WedDzV26nniwxv7jC88B9DG3gN5hhvu8HEmLydwk8EsW)_
Fixes #27971
Clause-②: yes (widening)
Metadata-protocol: added
Ruling-ref: 6523833308
Skills-docs: none — `skills/**` names neither `RESUME_CHILD_PENDING` nor
a map's cancelled item, so no skill text describes the shape that
changes; the hand-written guide `content/docs/automation/flows.mdx`
(batch-approval paragraph) is updated here, and the generated
`content/docs/references/automation/builtin-node-config.mdx` is
regenerated from the spec describe.
## 维护者速读
- 改了什么:批量节点(map)里某一项的子流程被取消后,再用 map 自己的 run id 恢复,map 会继续往下走,并在该项的位置记下
`{ $status: 'cancelled', $itemRunId }` 标记;`count`
只算真正跑过的项。子项挂起记录没了、运行历史又不是 cancelled 的,直接 409 拒绝,什么都不动。
- 为什么改:以前取消一项后恢复批次,map 会悄悄跳过这一项,results 少一项、下标错位,批次还显示 completed。按你的裁决 A
修。
- 风险与回滚:只影响停在内置 map 节点、且子项挂起记录已不在的那次恢复;正常完成的项、仍挂起的项(409)不变。回滚即 revert 本
PR。
-
先看:`packages/services/service-automation/src/run-resume.ts`(continuePastCancelledMapItem
的判定与拒绝)、`src/builtin/map-node.ts`(标记写入与
count)、`src/map-cancelled-item-resume.test.ts`。
- 两分钟验证:`pnpm --filter @objectstack/service-automation exec vitest run
--maxWorkers=2 src/map-cancelled-item-resume.test.ts`,预期 7 个用例全绿。
- 需要你做的:无,席位在 fable 契约复审通过后落地。
## What changes
- **The resume gate reads the item's run history** (`run-resume.ts`,
`continuePastCancelledMapItem`). When a run's linked-run chain ends on a
`map:` link whose item suspension row is gone, and the run is parked at
the built-in `map` node, the gate calls `getRun(itemRunId)`:
- status `cancelled` (written by `cancelRun`, which never touches the
parent): `resume` continues with an engine-built signal that sets the
handoff variable `MAPNODE.$mapItemCancelled` to the item run id;
- anything else — no history row, an unreadable store (`getRun` degrades
to `null`), a status other than `cancelled`: refused `409
RESUME_CHILD_PENDING` (#27877's code) before any state is touched, with
a message saying the item is no longer suspended and what its history
says.
- **The map records the marker** (`builtin/map-node.ts`). On re-entry
with that handoff it pushes `{ $status: 'cancelled', $itemRunId }` into
the item's slot, so `results[i]` is still item `i`. `count` is the items
that ran (`isMapItemCancelledMarker` filters the markers); the cancelled
item adds nothing to the step totals. The marker is closed and
`$`-keyed: a child flow's output keys are its declared variable names,
which `flowBoundVariableNameSchema` keeps off `$`, so no real output can
look like it.
- **Through a `subflow` link too.** The engine-built signal is delegated
down to the map one level below, the same way a caller's signal is.
- **The caller still cannot write the handoff.** A caller signal that
writes the engine's `$` namespace gets no continuation; `resumeInternal`
refuses it `400 INVALID_SIGNAL` with the run left parked, as before.
- **Only the built-in `map` node.** Another executor that mints a `map:`
correlation keeps its resume unchanged (the concurrent-replica test's
`map_items` node is one).
- **Spec and docs.** `MapConfigSchema.outputVariable`'s describe and the
map node's own config-schema description say an element can be the
marker; `flows.mdx`'s batch paragraph describes the marker, `count`, and
both 409 arms; the reference page is regenerated.
- Changeset: `@objectstack/service-automation` minor,
`@objectstack/spec` minor (`Metadata-protocol: added`), with
`Ruling-ref: 6523833308` and the ADR-0087 answer `not-required
(no-migration-prescription)`.
## Tests
New `src/map-cancelled-item-resume.test.ts` (7 cases): the cancelled
item's slot holds the marker, the map parks on item 2, and the
downstream node reads `[marker, {done: 'ran t2'}]` with `count` 1; the
same one level down through a `subflow` parent; an item row gone without
a `cancelled` history row is refused 409 with nothing moved; an
unreadable history is refused 409; a caller signal writing
`$mapItemCancelled` is refused `INVALID_SIGNAL`; controls: a
still-suspended item is still refused naming it, and items that complete
are recorded as before with `count` 2.
<details><summary>Ablations (fix committed first; the tests import
./engine.js and ./builtin/map-node.js from src, no dist on the
path)</summary>
Each via `scripts/ablation-replace.mjs` (anchor x1 → x0 on disk, then
the run, then restore to the HEAD blob with `git diff HEAD` empty):
- status check `item?.status !== 'cancelled'` → `item == null`: *refuses
… without a cancelled history row* red (1 failed, 6 passed).
- the caller-signal guard (`applyResumeSignal(new Map(), signal,
link.nodeId)` line) deleted: *still refuses a caller signal that writes
the engine namespace* red (1 failed, 6 passed).
- `count` filter → `state.results.length`: the two marker cases red (2
failed, 5 passed).
- the marker push deleted: the two marker cases red (2 failed, 5
passed).
- the `nodeType !== 'map'` guard deleted:
`concurrent-replica-resume-race.test.ts` *THE CONDITION (correlation)*
red (1 failed, 11 passed). That pin is how the guard was found: without
it the gate refused that test's synthetic `map:item_1` correlation.
</details>
## Acceptance notes
- Measured on `main` f6adf48 before the fix: `cancelRun(item1)`
answered `true` and `getRun(item1).status` read `cancelled`;
`resume(parentId)` answered `success`/`paused`, `$mapState` became
`{started: 2, results: []}`, and after item 2 completed the parent was
`completed` with a downstream node reading `results` = [item 2's output]
and `count` 1.
- The marker's keys (`$status`, `$itemRunId`) are the dev's proposal
under ruling A; the ruling leaves their exact spelling to the contract
review.
- The approval revise-window recall (`plugin-approvals` calling
`cancelRun`) is a producer of a cancelled item in a batch sign-off;
read, not measured here.
## Verification
<details><summary>Package suites</summary>
- `pnpm --filter @objectstack/service-automation test`: Test Files 231
passed (231), Tests 2911 passed (2911). `typecheck`:
check:test-typecheck OK, 0 errors.
- `pnpm --filter @objectstack/spec test`: Test Files 756 passed (756),
Tests 22513 passed, 1 todo.
- `pnpm --filter @objectstack/spec check:generated`: all 14 generated
artifacts up to date, after `gen:schema` + `gen:docs` and a spec build.
</details>
<details><summary>Full dogfood (the title carries a bang)</summary>
Closure built with `pnpm --filter '@objectstack/dogfood^...' build`,
then `pnpm --filter @objectstack/dogfood test` under the verify lock:
VERDICT command-exit 0; Test Files 358 passed, 3 skipped (361); Tests
2883 passed, 20 skipped (2903). Run on 0dfb617; the later commit only
adds the changeset's ADR-0087 line.
</details>
<details><summary>Derived gate families (dispatch-gates --commands,
reconciled with --ran)</summary>
107 derived, 107 run with an exit code, 0 NOT MEASURED. They ran at
0dfb617. `check-adr-0087-registration --base origin/main` first exited
1 (no disposition line), fixed in the changeset and re-run at 84b980b
with exit 0; the five changeset families were also re-run at 84b980b,
all exit 0. `check-platform-checklist --changed-from origin/main`: OK,
17 anchors judged, 0 lost. `git merge-tree` against the current
`origin/main`: clean. `pnpm lint` is CI's.
</details>
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(describe text only)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` passes. (CI)
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_016WV4d1oZadHHFdEZXANxiG
---
_Generated by [Claude
Code](https://claude.ai/code/session_016WV4d1oZadHHFdEZXANxiG)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 3efccfd commit 3a2f4c0
13 files changed
Lines changed: 449 additions & 19 deletions
File tree
- .changeset
- content/docs
- automation
- references/automation
- docs/qa/platform-checklist/areas
- packages
- client/src
- services/service-automation
- src
- builtin
- spec/src
- api
- automation
- contracts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1512 | 1512 | | |
1513 | 1513 | | |
1514 | 1514 | | |
1515 | | - | |
| 1515 | + | |
| 1516 | + | |
| 1517 | + | |
| 1518 | + | |
| 1519 | + | |
| 1520 | + | |
| 1521 | + | |
| 1522 | + | |
| 1523 | + | |
| 1524 | + | |
| 1525 | + | |
| 1526 | + | |
| 1527 | + | |
| 1528 | + | |
| 1529 | + | |
| 1530 | + | |
1516 | 1531 | | |
1517 | 1532 | | |
1518 | 1533 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
251 | 251 | | |
252 | 252 | | |
253 | 253 | | |
254 | | - | |
| 254 | + | |
255 | 255 | | |
256 | 256 | | |
257 | 257 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1634 | 1634 | | |
1635 | 1635 | | |
1636 | 1636 | | |
1637 | | - | |
| 1637 | + | |
1638 | 1638 | | |
1639 | 1639 | | |
1640 | 1640 | | |
| |||
1728 | 1728 | | |
1729 | 1729 | | |
1730 | 1730 | | |
1731 | | - | |
| 1731 | + | |
1732 | 1732 | | |
1733 | 1733 | | |
1734 | 1734 | | |
| |||
1800 | 1800 | | |
1801 | 1801 | | |
1802 | 1802 | | |
| 1803 | + | |
| 1804 | + | |
| 1805 | + | |
| 1806 | + | |
| 1807 | + | |
| 1808 | + | |
1803 | 1809 | | |
1804 | 1810 | | |
1805 | 1811 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5951 | 5951 | | |
5952 | 5952 | | |
5953 | 5953 | | |
5954 | | - | |
| 5954 | + | |
| 5955 | + | |
| 5956 | + | |
5955 | 5957 | | |
5956 | 5958 | | |
5957 | 5959 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
309 | 309 | | |
310 | 310 | | |
311 | 311 | | |
312 | | - | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
313 | 315 | | |
314 | 316 | | |
315 | 317 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
16 | 50 | | |
17 | 51 | | |
18 | 52 | | |
| |||
87 | 121 | | |
88 | 122 | | |
89 | 123 | | |
90 | | - | |
| 124 | + | |
91 | 125 | | |
92 | 126 | | |
93 | 127 | | |
| |||
142 | 176 | | |
143 | 177 | | |
144 | 178 | | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
145 | 188 | | |
146 | 189 | | |
147 | 190 | | |
| |||
329 | 372 | | |
330 | 373 | | |
331 | 374 | | |
332 | | - | |
| 375 | + | |
| 376 | + | |
| 377 | + | |
333 | 378 | | |
334 | 379 | | |
335 | 380 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
165 | 165 | | |
166 | 166 | | |
167 | 167 | | |
| 168 | + | |
168 | 169 | | |
169 | 170 | | |
170 | 171 | | |
| |||
1547 | 1548 | | |
1548 | 1549 | | |
1549 | 1550 | | |
1550 | | - | |
| 1551 | + | |
1551 | 1552 | | |
1552 | 1553 | | |
1553 | 1554 | | |
| |||
6467 | 6468 | | |
6468 | 6469 | | |
6469 | 6470 | | |
| 6471 | + | |
| 6472 | + | |
| 6473 | + | |
| 6474 | + | |
| 6475 | + | |
| 6476 | + | |
| 6477 | + | |
6470 | 6478 | | |
6471 | 6479 | | |
6472 | 6480 | | |
| |||
6503 | 6511 | | |
6504 | 6512 | | |
6505 | 6513 | | |
6506 | | - | |
| 6514 | + | |
| 6515 | + | |
| 6516 | + | |
| 6517 | + | |
6507 | 6518 | | |
6508 | 6519 | | |
6509 | 6520 | | |
| |||
6517 | 6528 | | |
6518 | 6529 | | |
6519 | 6530 | | |
6520 | | - | |
| 6531 | + | |
6521 | 6532 | | |
6522 | 6533 | | |
6523 | 6534 | | |
6524 | 6535 | | |
6525 | 6536 | | |
6526 | 6537 | | |
| 6538 | + | |
6527 | 6539 | | |
6528 | 6540 | | |
6529 | 6541 | | |
| |||
0 commit comments