Repository navigation
Commit 7a25b39
fix(lint): flow-region-shape-invalid points at the region container at fault (#29821)
<!-- claude-projects-attribution: {"github_login":"os-warren"} -->
_Requested by **Warren** · [project
thread](https://claude.ai/code/project/chan_01U5RdnAAmkSQmZbqh6FtqZ8?thread=cmsg_01U5RdnAAmkSQmZbqh6FtqZ8Ja6arWBs4nMY35SgDhwmfH)_
Fixes #29558
Clause-②: no
Metadata-protocol: none
## 维护者速读
- 改了什么:`flow-region-shape-invalid`
的发现不再只指向整个流程,而是指向引擎拒收时点名的那个区域容器(并行、循环、try_catch),路径与 where 都落到该节点。
- 为什么改:作者只看到「这个流程有问题」,找不到是哪个容器形状不对;引擎现在随拒收抛出带路径和节点 id 的
`FlowRegionShapeError`,lint 直接读它。
- 风险与回滚:只动发现的定位,消息原文一字未改;读不到该错误类型时退回整流程定位。回滚即 revert 本 PR。
- 先看:`packages/lint/src/validate-flow-region-shape.ts` 的
`containerAtFault()`,其后是同目录测试文件里新增的三个容器用例。
- 两分钟验证:`pnpm --filter @objectstack/lint exec vitest run
src/validate-flow-region-shape.test.ts`,预期 10/10 通过。
- 需要你做的:无,席位落地。
## What changes
`validateFlowRegionShape`
(`packages/lint/src/validate-flow-region-shape.ts`) now anchors its
finding on the region container the engine's refusal names. Since #29719
(PR #29789), `validateControlFlow` throws the exported
`FlowRegionShapeError`, carrying the container's `nodeId` and its `path`
inside the flow. The new `containerAtFault()` reads both with one
`instanceof FlowRegionShapeError` check:
- `path` becomes the flow path plus the container path, in lint
spelling: `flows[1]` + `['nodes', 1, 'config', 'try', 'nodes', 0]` gives
`flows[1].nodes[1].config.try.nodes[0]` (`spellPath()`).
- `where` becomes `flow 'NAME' › node 'ID'`.
- `message` is unchanged: still the engine's sentence behind the flow's
name. Still one rule, one judge, one spelling: there is no second call
and nothing parses the sentence.
- Any other error the engine throws names no container, so the flow
stays the anchor, as before. A flow the flow schema refuses is unchanged
too: it was never judged, so no container exists.
**One finding per flow, which is one finding per container.** The engine
throws once, at a flow's first malformed region, and the rule makes
exactly the engine's call. Reporting further containers would need a
second walk beside the engine's, which the card rules out. A flow with
two malformed regions therefore still takes two fixes, as the rule's
explanation already says.
**Forced path-only pin moves**, each limited to the asserted path (claim
6593246940, amendments 6593285300 and the one adding the dogfood file):
- `packages/cli/test/validate-flow-region-shape.test.ts`: the
assembled-artifact case's `flows[1]` → `flows[1].nodes[1]`.
-
`packages/metadata-protocol/src/runtime-authoring-gate.flow-region.test.ts`:
three cases move from `flows[0]` to the container.
-
`packages/metadata-protocol/src/protocol.draft-publish-verdict.test.ts`:
the draft read's item-relative path `''` → `nodes.1.config.try.nodes.0`.
-
`packages/qa/dogfood/test/flow-region-malformed-publish.dogfood.test.ts`:
the draft read's `"path":""` → `"path":"nodes.1"`, its comment, and one
docblock phrase.
A `@objectstack/lint` patch changeset carries the move.
## Why
The card: an author reading `flow-region-shape-invalid` learned only
that the flow was refused, not which `parallel`, `loop` or `try_catch`
was malformed. The location now comes from the contract the spec half
(#29719) added, not from the sentence.
## Tests
- `packages/lint/src/validate-flow-region-shape.test.ts`: three new
cases under `[#29558]` (a top-level `try_catch`, a one-branch `parallel`
nested in a try region, a `loop` inside the second branch of a
`parallel`), plus the first case and the non-record-entry case moved to
the container path. 10/10.
All at head `ccbd58d4`, each under `scripts/pm/os-verify-lock.sh`,
verdict line quoted:
<details><summary>Test runs</summary>
- `pnpm --filter @objectstack/lint typecheck && pnpm --filter
@objectstack/lint test`: VERDICT command-exit 0; Test Files 192 passed
(192), Tests 7945 passed (7945). The lint typecheck covers its tests
(`check:test-typecheck`).
- `pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2
src/validate-flow-region-shape.test.ts`: VERDICT command-exit 0; Tests
10 passed (10).
- `pnpm --filter @objectstack/cli exec vitest run --project unit
--maxWorkers=2 test/validate-flow-region-shape.test.ts`: VERDICT
command-exit 0; Tests 7 passed (7).
`authoring-rule-command-parity.test.ts` is integration tier and
unedited, so it is left to CI.
- `pnpm --filter @objectstack/metadata-protocol exec vitest run
--maxWorkers=2 src/runtime-authoring-gate.flow-region.test.ts
src/protocol.draft-publish-verdict.test.ts` (after a lint build):
VERDICT command-exit 0; Test Files 2 passed (2), Tests 57 passed (57).
- `pnpm --filter '@objectstack/dogfood^...' build && pnpm --filter
@objectstack/dogfood exec vitest run --maxWorkers=2
test/flow-region-malformed-publish.dogfood.test.ts`: VERDICT
command-exit 0; Tests 3 passed (3).
</details>
<details><summary>Ablation (one, on the reader)</summary>
`node scripts/ablation-replace.mjs` replaced the reader's one line (the
`instanceof FlowRegionShapeError` return) with `return undefined;`:
anchor x1 to x0, blob `37494f85f3c3` to `122265b776ae`. The rule's test
file then went red, Tests 5 failed, 5 passed: the first case, the three
`[#29558]` container cases and the non-record-entry case. Restored by
the tool: blob after restore `37494f85f3c3` equals HEAD, `git diff HEAD`
empty. The test imports the rule from source, so no build sits between
the mutation and the run.
</details>
## Acceptance notes
- The resume commit (`56820bac`, cherry-picked as `b9930969`) missed one
in-file pin, the non-record-entry case (`flows[2]` →
`flows[2].nodes[1]`); fixed in its own commit.
- The rule's `os explain` entry (`rule-explanations.ts`) says nothing
about the location, so it was left alone.
## Verification
- `node scripts/pm/dispatch-gates.mjs --commands` at `ccbd58d4` derived
63 families; all were run with exit codes recorded before any pipe;
`--ran` reconciliation: 63 derived, 62 run, 1 NOT MEASURED, 0 unrun.
- NOT MEASURED: `pnpm check:dual-build-cjs-loads`, reason: it reads
built output of every package (PREREQUISITE NOT MET, exit 3, seven
packages without `dist/`); CI's `Lint & Repo Gates` runs it.
- `node scripts/check-plugin-teardown-shape.mjs --self-test` first
exited 3 because its pinned fixture commit was outside this shallow
clone; after fetching that commit it exited 0 (48 cases pass).
- Every other derived family exited 0.
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
(No `packages/spec` change.)
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; `pnpm lint` runs in 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: n/a.
- [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.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01JQwxbWp3uyRGHgDR5oyquZ)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 64646ae commit 7a25b39
7 files changed
Lines changed: 98 additions & 21 deletions
File tree
- .changeset
- packages
- cli/test
- lint/src
- metadata-protocol/src
- qa/dogfood/test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
224 | 224 | | |
225 | 225 | | |
226 | 226 | | |
227 | | - | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
228 | 230 | | |
229 | 231 | | |
230 | 232 | | |
| |||
243 | 245 | | |
244 | 246 | | |
245 | 247 | | |
246 | | - | |
| 248 | + | |
247 | 249 | | |
248 | 250 | | |
249 | 251 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
55 | 55 | | |
56 | 56 | | |
57 | 57 | | |
58 | | - | |
| 58 | + | |
59 | 59 | | |
60 | 60 | | |
61 | 61 | | |
| |||
67 | 67 | | |
68 | 68 | | |
69 | 69 | | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
70 | 104 | | |
71 | 105 | | |
72 | 106 | | |
| |||
84 | 118 | | |
85 | 119 | | |
86 | 120 | | |
87 | | - | |
| 121 | + | |
88 | 122 | | |
89 | 123 | | |
90 | 124 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
17 | 17 | | |
18 | 18 | | |
19 | 19 | | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
20 | 27 | | |
21 | 28 | | |
22 | 29 | | |
| |||
49 | 56 | | |
50 | 57 | | |
51 | 58 | | |
52 | | - | |
| 59 | + | |
53 | 60 | | |
54 | 61 | | |
55 | 62 | | |
| |||
63 | 70 | | |
64 | 71 | | |
65 | 72 | | |
66 | | - | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
67 | 77 | | |
68 | | - | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
69 | 82 | | |
70 | 83 | | |
71 | 84 | | |
| |||
80 | 93 | | |
81 | 94 | | |
82 | 95 | | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
83 | 110 | | |
84 | 111 | | |
85 | 112 | | |
| |||
112 | 139 | | |
113 | 140 | | |
114 | 141 | | |
| 142 | + | |
115 | 143 | | |
116 | 144 | | |
117 | 145 | | |
118 | | - | |
119 | | - | |
| 146 | + | |
| 147 | + | |
120 | 148 | | |
121 | 149 | | |
122 | 150 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
634 | 634 | | |
635 | 635 | | |
636 | 636 | | |
637 | | - | |
| 637 | + | |
638 | 638 | | |
639 | 639 | | |
640 | 640 | | |
641 | | - | |
642 | | - | |
643 | | - | |
| 641 | + | |
| 642 | + | |
| 643 | + | |
644 | 644 | | |
645 | 645 | | |
646 | 646 | | |
| |||
656 | 656 | | |
657 | 657 | | |
658 | 658 | | |
659 | | - | |
| 659 | + | |
660 | 660 | | |
661 | 661 | | |
662 | 662 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
82 | 82 | | |
83 | 83 | | |
84 | 84 | | |
85 | | - | |
86 | | - | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
87 | 88 | | |
88 | 89 | | |
89 | 90 | | |
| |||
92 | 93 | | |
93 | 94 | | |
94 | 95 | | |
95 | | - | |
| 96 | + | |
96 | 97 | | |
97 | 98 | | |
98 | 99 | | |
| |||
120 | 121 | | |
121 | 122 | | |
122 | 123 | | |
123 | | - | |
| 124 | + | |
124 | 125 | | |
125 | 126 | | |
126 | 127 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
19 | | - | |
| 19 | + | |
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
| |||
117 | 117 | | |
118 | 118 | | |
119 | 119 | | |
120 | | - | |
121 | | - | |
| 120 | + | |
| 121 | + | |
122 | 122 | | |
123 | 123 | | |
124 | 124 | | |
| |||
0 commit comments