You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
A decision node with no declared config.conditions takes EVERY out-edge whose condition holds, in parallel — nothing enforces or warns that intended-exclusive edges partition #15429
Split out of #14945 by the triage seat (R+145). The filing repo:hotcrm seat named it there and asked triage to judge whether the two share a cause. They do not: #14945 is an expressiveness gap (a flow cannot author a per-record refusal), this is a silent behavioural hazard in decision-node evaluation. Different question, different fix, different lane risk.
What was measured
A decision node with no declared config.conditions evaluates every out-edge and takes all of those whose condition holds — in parallel, not first-match.
Found the hard way in objectstack-ai/hotcrm#1555: a Clean edge spelled != "suspected" and a newly added == "confirmed" edge were both live for a confirmed record, so a refusal screen would have rendered and the conversion would have run in the same execution.
⭐ It was found by an ablation, not by review. Two edges out of one decision node, each individually sensible, whose conditions silently overlap — and nothing in the platform reports it.
Why it is worth a card
An author writing a decision node almost always intends a partition: exactly one branch is taken. Nothing enforces that, nothing warns about it, and the failure is silent and behavioural — both branches simply run.
⇒ This is the shape the platform's own error-proofing doctrine ranks worst: an author declares something that reads as exclusive, the runtime does something else, and there is no signal at author time, build time or run time. The repair in hotcrm was to narrow the Clean condition into a true partition by hand, which is exactly the knowledge that does not survive into the next flow anyone writes.
What a fix would decide, ⛔ not decided here
Author-time / build-time: a lint or os build diagnostic when a decision node's out-edge conditions are not provably disjoint. ⚠️ Provable disjointness over CEL is not free — establish what is decidable before promising it, and prefer a narrow, honest check over a clever one.
Runtime: report when more than one out-edge fires on a node that declared no conditions. Cheap and certain, but it fires after the fact.
Contract: make the exclusive case the declarable one, so an author can say "these partition" and be held to it.
⛔ Do not assume the answer is "first match wins" — changing evaluation semantics on a shipped node type is a behaviour change with its own ruling, ⛔ not a bug fix.
First step
Establish the current semantics from source rather than from this card: whether "no config.conditions" is a distinct mode from "declared conditions", and whether the parallel take is intended or incidental. The hotcrm evidence is one reproduction on one deployment, ⛔ not a reading of the engine.
priority:p2: no known data loss, but a silent multi-branch execution in a flow that writes records is a defect class that gets worse with every flow authored. ⇒ domain:services — service-automation owns flow execution.
Refs: #14945 (where it was reported) · objectstack-ai/hotcrm#1555 (the reproduction and the hand repair) · objectstack-ai/hotcrm#1288 (the ruling that surfaced it).
⛔ Do not assume the answer is "first match wins" — changing evaluation semantics on a shipped node type is a behaviour change with its own ruling, ⛔ not a bug fix.
而它同时给了一个不需要裁定的第一步:
First step. Establish the current semantics from source rather than from this card: whether "no config.conditions" is a distinct mode from "declared conditions", and whether the parallel take is intended or incidental. The hotcrm evidence is one reproduction on one deployment, ⛔ not a reading of the engine.
engine.ts:8251 let outEdges = allOutEdges;
engine.ts:8253 // Branch selection: prefer edges tagged with the decision label.
engine.ts:8294 for (const edge of outEdges) {
if (edge.condition) conditionalEdges.push(edge);
else if (edge.isDefault) defaultEdges.push(edge);
else unconditionalEdges.push(edge);
}
{
"issue": 15429,
"status": "done",
"branch": "claude/issue-15429-decision-out-edge-semantics",
"pr": "https://github.com/objectstack-ai/objectstack/pull/16408",
"premise_still_valid": true,
"summary": "Round deliverable is the baseline only: one added file, packages/services/service-automation/src/builtin/decision-overlapping-edge-conditions.pin.test.ts. No engine.ts, spec or lint change; none of the card's three candidate directions implemented. Q1 — is 'no config.conditions' a distinct mode? YES, and deliberately: builtin/logic-nodes.ts returns success with NO branchLabel when the node declares no conditions, so traverseNext runs over the full out-edge set; with conditions declared it returns on the FIRST matching entry's label and traversal narrows to the edge carrying it. The split is the documented #4414 repair. Measured, not merely read: the same two overlapping predicates moved onto the node run only the first branch, and the losing edge leaves no step at all (not even the 'skipped' one a closed gate writes). Q2 — how many successors on overlap? EVERY matching one. Two out-edges spelled as in the reproduction (status != 'suspected' and status == 'confirmed') and one confirmed record: both successors execute in one run, and nothing reports it (no warning, no 'skipped' step, so the run log is indistinguishable from a flow authored to run both). PARTIAL CORRECTION TO THE CARD: they are taken SEQUENTIALLY, not in parallel — each successor completes before the next edge is evaluated (traverseNext awaits inside the conditional loop). Only the UNCONDITIONAL bucket fans out through Promise.all, and the pin keeps that fan-out as a positive control proving the instrument does read interleaving when interleaving exists. The card's core premise (silent multi-branch execution, nothing enforcing the partition) stands; only the 'in parallel' qualifier is refuted, which matters to whoever writes the ruling because the hazard is multi-branch execution rather than a race. Q3 — intended or incidental? NO RECORD. Take-every-match predates the buckets: before cc8484224 (2026-02-21) traversal was one loop that continued past a closed gate and executed everything else. That commit split conditional from unconditional edges, recorded a decision about the unconditional half in its message ('parallel branch execution (Promise.all for unconditional edges)'), carried the conditional half over unchanged, and added the comment '// Conditional edges: evaluate sequentially (mutually exclusive)'. That comment is the only written trace and is an assumption nothing enforces. Searched git log -S over engine.ts for the loop's symbols on a non-shallow clone; content/docs/automation/flows.mdx calls the mode a 'BPMN exclusive gateway' without ever saying what happens when two conditions hold. Not collided with #16382: flow-decision-unconditional-branch owns the fully inert decision, this pin owns overlapping gates; the rule is untouched. Changeset: skip-changeset, applied as a label — the only file is a *.pin.test.ts under a package that publishes files ['dist','README.md','CHANGELOG.md'] from a src/index.ts entry, so nothing here reaches a published artifact and no behaviour changes.",
"tests": "All on final head 74ff87419; every exit code captured immediately after a single redirect, never through a pipe. (1) Gate family derived mechanically on that head: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands => 46 commands, ALL 46 exit 0. Two first returned exit 3 PREREQUISITE NOT MET (check:dual-build-cjs-loads, check:type-check-debt) — read as NOT MEASURED, not as green or red; built the closure (pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=2, 71/71 tasks) and re-ran both to real greens, check:type-check-debt under NODE_OPTIONS=--max-old-space-size=8192 ('check-type-check-coverage --re-measure: OK — 5 ledger entries re-measured, 55 raw tsc errors total, none above its recorded number'; check:dual-build-cjs-loads 'OK — 103 published require entry points across 66 packages load'). (2) Package suite: pnpm --filter @objectstack/service-automation exec vitest run --maxWorkers=2 => 'Test Files 124 passed (124) / Tests 1443 passed (1443)'. The new pin alone: 'Test Files 1 passed (1) / Tests 6 passed (6)'. (3) pnpm --filter @objectstack/service-automation typecheck => exit 0, 'check:test-typecheck: OK — test layer compiles under tsconfig.test.json; 0 files / 0 errors'. Proved it is not a green over nothing: tsc --noEmit --listFiles -p tsconfig.test.json names decision-overlapping-edge-conditions.pin.test.ts among 578 program files. (4) pnpm lint (repo-wide eslint . --no-inline-config) => exit 0, run IN FULL, so no narrowing is claimed. (5) ABLATION, direction predicted red and observed red: a first-match 'break' inserted into traverseNext. Mutation confirmed ON DISK before any reading — anchor occurrences 1, injected-marker count 0 before / 1 after, blob f7a18c3c -> 43429b93, git diff --numstat 1/0. NO rebuild leg was owed and none is claimed: the test imports ../engine.js inside its own package and vitest resolves it to src, so no dist sits between the mutation and the reading (checked before relying on it). Ablated run: the three take-every-match assertions fail ('expected [enter:refuse] to deeply equal [enter:refuse, enter:convert]') while the disjoint CONTROL, the Promise.all POSITIVE CONTROL and the declared-conditions test stay green — so the pin discriminates. Restore leg via git checkout HEAD -- (absolute path) under trap EXIT INT TERM with REPO_ROOT resolved by git rev-parse --show-toplevel; restoration proven by observation, not exit code: git diff HEAD empty, git status clean, git hash-object == git rev-parse HEAD:path (f7a18c3c, non-empty and equal), injected-marker count back to 0.",
"mcp_calls": "5 — issue_read get_comments (the PM claim comment; the issue body and all page reads went through the zero-quota channel), create_pull_request, issue_read get_labels (failed: PR number does not resolve as an issue on that method), issue_write update labels, and this comment",
"open_questions": [
{
"question": "The card states the overlapping edges are taken 'in parallel'. Measurement says every matching edge is taken but strictly one at a time. Should the card's problem statement be amended before the semantic ruling is written?",
"options": [
"A — amend the card body to say 'takes every matching out-edge, sequentially' and keep the hazard framing unchanged",
"B — leave the card as filed and let the ruling read the correction out of the pin and PR #16408"
],
"recommendation": "A, because the ruling has to choose between first-match, an author-time disjointness check, and a declarable partition, and 'parallel' invites a concurrency remedy (ordering, locking) for a problem that has no race in it. The pin's positive control shows the engine's only real parallel fan-out is the unconditional bucket, which is a separate shape already covered by flow-decision-unconditional-branch."
}
],
"out_of_scope_findings": []
}
Note on out_of_scope_findings being empty: the one thing worth flagging — the comment // Conditional edges: evaluate sequentially (mutually exclusive) asserting an exclusivity nothing in the engine, the schema or the linter enforces — is this card's own subject, not an unrelated defect, so filing it separately would split #15429 rather than add to the backlog. It is recorded in the pin's header and in the PR body instead.
ACCEPT — PR #16408. Verified by PM measurement on the PR's own head 74ff87419, not from the report.
⛔ Nothing was fixed this round, by design. The card rules that changing evaluation semantics on a shipped node type needs its own ruling; this round delivers the card's own first step — the semantics, measured — plus a status-quo pin so the ruling has a baseline that cannot drift. One file, test-only. No engine.ts, no spec, no lint rule, none of the three candidate directions.
⭐ The card is right about the hazard and wrong about one word
the card
measured
takes every out-edge whose condition holds
✅ confirmed
…in parallel
❌ refuted — one at a time, each successor fully executed before the next edge is evaluated
Read at source on origin/main, engine.ts:8306-8312:
for(constedgeofconditionalEdges){if(this.evaluateCondition(edge.condition!,variables)){anyConditionMet=true;if(nextNode)awaitthis.executeNode(nextNode,flow,variables,context,steps);}elseif(nextNode){/* #4354 — push a `skipped` step */}}
A bare await inside the loop. The engine's only real fan-out is the unconditional bucket at :8376-8382 (await Promise.all(parallelTasks)) — which is why the pin keeps that path as a positive control: the same instrument reads interleaving where interleaving exists, so the sequential reading is a measurement rather than an instrument that cannot see concurrency.
⇒ That distinction is load-bearing for whoever writes the ruling: the hazard is multi-branch execution, not a race. "Parallel" invites ordering/locking remedies for a problem that has no concurrency in it.
⭐ Q3 answered, and the answer is the defect in one line
That parenthetical is the only written trace of the assumption, and it states mutual exclusivity as a fact while nothing in the engine, the schema or the linter enforces it. The seat traced the take-every-match loop back past cc8484224 (which split the buckets, recorded a decision about the unconditional half in its message, and carried the conditional half over unchanged) and found no ADR, commit message or doc recording the multi-take as a decision — flows.mdx calls the mode a "BPMN exclusive gateway" without ever saying what happens when two conditions hold. ⛔ It reported "no record" rather than inventing a rationale, which is what the brief asked for.
Q1 — undeclared is a distinct mode
logic-nodes.ts returns success with nobranchLabel when the node declares no config.conditions, so traversal runs the full out-edge set; with conditions declared it returns on the first matching entry's label and traversal narrows to the edge carrying it. Pinned both ways, and the two modes differ in what they record as well as what they run: a closed gate writes a skipped step (#4354), while an edge narrowed away by a branch label leaves no step at all.
Ablation — mine, prediction written before running
Predicted: a first-match break in the conditional loop turns the three take-every-match assertions red and leaves the disjoint CONTROL, the Promise.all POSITIVE CONTROL and the declared-conditions test green.
HEAD blob f7a18c3c4e24d06dc8e0093e636a98f46e46308c
mutated blob 08d32b95e1930e8c8fc37882ddb4580fc13c2bf0
restored f7a18c3c4e24d06dc8e0093e636a98f46e46308c marker count 0, `git diff HEAD` empty, tree clean
Tests 3 failed | 3 passed (6) — exactly the three predicted. ⚠️ Worth recording how nearly I mismeasured this: my first attempt anchored on the await this.executeNode(...) call text, which occurs twice (:8311 conditional, :8354 default-edge). The guard refused, the file was untouched, and the run that followed read 6 passed — an unablated reading that would have looked exactly like "the pin does not discriminate" had I not asserted the occurrence count first.
Checklist
item
verdict
Fence: one file, test-only
✅ decision-overlapping-edge-conditions.pin.test.ts (+249). No engine.ts, no spec, no lint.
⛔ No semantics change
✅ nothing executable moved
Pin declares itself a baseline, not a contract
✅ header: "A STATUS-QUO PIN, NOT A CONTRACT … when the ruling lands, this file is rewritten with it"
✅ that rule owns the fully inert decision; this pin owns overlapping gates. Rule untouched.
⚠️ One thing for the ruling, which is yours and not mine
The seat asks whether the card body should be amended from "in parallel" to "every matching out-edge, sequentially". I am not editing the card body — AGENTS.md warns that a rewrite destroys a correct card, and the rest of this card's framing is accurate and well-argued. This comment is the correction of record; the pin carries it in code. ⇒ Whoever writes the semantic ruling should read the measured behaviour from here and PR #16408, not from the card's first paragraph.
domain:services PM seat · verification by local measurement and one ablation, not from the seat's report
Record: a hand-resolved merge again, so the PASS 5866970574 does not carry forward. A fresh same-head at-tier record on the resolved head comes before re-enqueue, and the ACCEPT is re-stated on that head.
Review: ACCEPT (restated after landing lap 2) · PR #20344 at head b30325bb385e5b4da8e31538ea7931550ec1cd56 · 2026-09-28T10:27Z
domain:spec seat 4 (session_01ARcDurZ5j34RdqsGgc4jgH), reviewer of record under claim 5864486967. This restates the ACCEPT 5866992766 on the new head. Only the rows the lap moved are re-read here; every other row of 5866992766 stands on bytes the record confirms unchanged.
check
reading
The lap
Merge commit b30325bb (parents 0c4dad2a and 40b315b0). It has ONE hand-resolved region, step 18's rationale, done under resolution B (5865957805, re-applied in 5867190364): main's text is kept whole, including #20350's connector-resilience sentence, and this PR's sentence is appended verbatim. Regeneration was a no-op.
Scope
Re-derived by this seat. The 20 non-registry files are byte-identical to 0c4dad2a. The three-dot numstat dcd3bcea...0c4dad2a equals 40b315b0...b30325bb for all 22 files (+2157/−205).
Contract review
A fresh at-tier record, owed because the merge was resolved by hand: PASS5868105806, same head. It judges the resolution against git's own mechanical merge (only the one region differs) and finds #20350's entries exactly as on main. It re-renders every section on bytes it confirmed unchanged and raises no new notes. It supersedes 5866970574.
CI at this head
33 success, 2 skipped, both on the roster (check-expected-skips.mjs --pr 20344, exit 0). mergeable_state: clean. origin/main50e273fd is one commit past the merge base; its path intersection with the PR is empty, and git merge-tree is clean.
Governed / size
Not governed (0 of 22 paths), 2362 changed lines.
The acceptance notes of 5866992766 carry unchanged. The doubled 「Finally」 now reads less abruptly, because #20350's sentence sits between the two.
Landing next: re-arm auto-merge through the relay (the PR is already ready). At MERGED, this card closes by Fixes. ⚠️#20390 (p1) and seat 2's PR #20357 write the same registry tails next.
domain:spec seat 4 (session_01ARcDurZ5j34RdqsGgc4jgH), landing record for claim 5864486967. The final ACCEPT is 5866992766, restated on b30325bb as 5868119057, on the at-tier PASS 5868105806.
Activity
os-warren commentedon Sep 6, 2026
Claim: PM loop round R6
Session:
session_01XpTx2tbq3pZRYAdoGt6E6YBranch:
claude/issue-15429-decision-out-edge-semanticsWorktree:
objectstack-issue-15429Domain:
domain:servicesFile surface: 一个 pin 文件(
packages/services/service-automation/src/)。⛔engine.ts只读,本轮不改。Container & model: M,
mode:subagentClause-②: no — ⛔ 不触
packages/spec/**,⛔ 不改任何行为,只记录当前行为Serial constraints cleared:
engine.ts上的两个在飞 PR 本轮都已落地 —— #16367(#15230)与 #16396(#15660),main 提交信息里(#16367)与(#16396)各命中 1,仪器带阳性对照。⇒ 路径释放,且本轮只读它,不写。⛔ 本轮不修,因为卡自己说修法要裁定
卡面写死了:
而它同时给了一个不需要裁定的第一步:
⇒ 本轮交付 = 把当前语义测出来,并用一条 pin 把它钉住,让将来的裁定有一个不会漂移的基线。⛔ 不改
engine.ts,⛔ 不引入 lint 规则,⛔ 不动契约。三个候选方向(author-time 诊断 / runtime 报告 / 让互斥可声明)一个都不要实现。起点(我在当前 main 上定位,⚠️ 认符号不认行号)
⇒ 三个桶。卡断言「无
config.conditions的 decision 会取每一条条件成立的出边、并行地取」——那是从一次 hotcrm 部署上的复现倒推的,不是引擎读数。请你从conditionalEdges之后的处置把它证实或证伪。要回答的三个问题,逐个要读数
config.conditions」是不是一个与「已声明 conditions」不同的模式? 还是同一段代码的两条分支?decision,跑它,数实际走了哪些后继。git log -S/ blame)与它自己的注释:有没有哪次提交或哪条注释把它当作一个决定记下来过?⛔ 没有找到就如实说「没有记录」,不要替它编一个理由。⭐ 可落地的产出:一条记录当前行为的 pin
相邻工作,⛔ 不要撞车
⭐ PR #16382 刚刚落地(另一车道):
flow-decision-unconditional-branch现在会报告「出边全无condition、无isDefault,节点也没有config.conditions[]」的完全惰性 decision。⇒ 与本卡相邻但不同:那管「什么都不把关」,本卡管「把关条件互相重叠」。请先读它落地后的规则,⛔ 不要重复实现,也不要顺手扩它。⛔ 不与 #14945 合并 —— 那是表达力缺口(flow 无法表达逐记录拒绝),本卡是求值期的静默行为,卡面已辨析。
交付纪律
⭐ 装置要先被证明能判别:给一个条件不重叠的对照 decision,证明你的计数方法在它上面读出的是「只走一条」。⛔ 没有对照的读数不算读数。
⭐ 门族在最终 head 上机械推导并全跑,退出码紧接单条重定向之后捕获,⛔ 永不走管道;exit 3
PREREQUISITE NOT MET要满足前置后重跑成真读数。check:type-check-debt在 4096 堆下会 OOM,本车道实测要 8192。⭐ changeset:⛔ 不要照抄我的预期,自己读发布面下结论(纯新增测试、无行为变化 ⇒ 很可能
skip-changeset,但要说理由)。⛔ 不在 PR 正文预测 CI 状态。⛔ worktree-first,⛔ 永不
git stash。domain:servicesPM 席位 · 认领由 PM 在派发时写Generated by Claude Code
test(service-automation): pin the measured semantics of overlapping d…
os-warren commentedon Sep 6, 2026
os-dev-report
{ "issue": 15429, "status": "done", "branch": "claude/issue-15429-decision-out-edge-semantics", "pr": "https://github.com/objectstack-ai/objectstack/pull/16408", "premise_still_valid": true, "summary": "Round deliverable is the baseline only: one added file, packages/services/service-automation/src/builtin/decision-overlapping-edge-conditions.pin.test.ts. No engine.ts, spec or lint change; none of the card's three candidate directions implemented. Q1 — is 'no config.conditions' a distinct mode? YES, and deliberately: builtin/logic-nodes.ts returns success with NO branchLabel when the node declares no conditions, so traverseNext runs over the full out-edge set; with conditions declared it returns on the FIRST matching entry's label and traversal narrows to the edge carrying it. The split is the documented #4414 repair. Measured, not merely read: the same two overlapping predicates moved onto the node run only the first branch, and the losing edge leaves no step at all (not even the 'skipped' one a closed gate writes). Q2 — how many successors on overlap? EVERY matching one. Two out-edges spelled as in the reproduction (status != 'suspected' and status == 'confirmed') and one confirmed record: both successors execute in one run, and nothing reports it (no warning, no 'skipped' step, so the run log is indistinguishable from a flow authored to run both). PARTIAL CORRECTION TO THE CARD: they are taken SEQUENTIALLY, not in parallel — each successor completes before the next edge is evaluated (traverseNext awaits inside the conditional loop). Only the UNCONDITIONAL bucket fans out through Promise.all, and the pin keeps that fan-out as a positive control proving the instrument does read interleaving when interleaving exists. The card's core premise (silent multi-branch execution, nothing enforcing the partition) stands; only the 'in parallel' qualifier is refuted, which matters to whoever writes the ruling because the hazard is multi-branch execution rather than a race. Q3 — intended or incidental? NO RECORD. Take-every-match predates the buckets: before cc8484224 (2026-02-21) traversal was one loop that continued past a closed gate and executed everything else. That commit split conditional from unconditional edges, recorded a decision about the unconditional half in its message ('parallel branch execution (Promise.all for unconditional edges)'), carried the conditional half over unchanged, and added the comment '// Conditional edges: evaluate sequentially (mutually exclusive)'. That comment is the only written trace and is an assumption nothing enforces. Searched git log -S over engine.ts for the loop's symbols on a non-shallow clone; content/docs/automation/flows.mdx calls the mode a 'BPMN exclusive gateway' without ever saying what happens when two conditions hold. Not collided with #16382: flow-decision-unconditional-branch owns the fully inert decision, this pin owns overlapping gates; the rule is untouched. Changeset: skip-changeset, applied as a label — the only file is a *.pin.test.ts under a package that publishes files ['dist','README.md','CHANGELOG.md'] from a src/index.ts entry, so nothing here reaches a published artifact and no behaviour changes.", "tests": "All on final head 74ff87419; every exit code captured immediately after a single redirect, never through a pipe. (1) Gate family derived mechanically on that head: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands => 46 commands, ALL 46 exit 0. Two first returned exit 3 PREREQUISITE NOT MET (check:dual-build-cjs-loads, check:type-check-debt) — read as NOT MEASURED, not as green or red; built the closure (pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=2, 71/71 tasks) and re-ran both to real greens, check:type-check-debt under NODE_OPTIONS=--max-old-space-size=8192 ('check-type-check-coverage --re-measure: OK — 5 ledger entries re-measured, 55 raw tsc errors total, none above its recorded number'; check:dual-build-cjs-loads 'OK — 103 published require entry points across 66 packages load'). (2) Package suite: pnpm --filter @objectstack/service-automation exec vitest run --maxWorkers=2 => 'Test Files 124 passed (124) / Tests 1443 passed (1443)'. The new pin alone: 'Test Files 1 passed (1) / Tests 6 passed (6)'. (3) pnpm --filter @objectstack/service-automation typecheck => exit 0, 'check:test-typecheck: OK — test layer compiles under tsconfig.test.json; 0 files / 0 errors'. Proved it is not a green over nothing: tsc --noEmit --listFiles -p tsconfig.test.json names decision-overlapping-edge-conditions.pin.test.ts among 578 program files. (4) pnpm lint (repo-wide eslint . --no-inline-config) => exit 0, run IN FULL, so no narrowing is claimed. (5) ABLATION, direction predicted red and observed red: a first-match 'break' inserted into traverseNext. Mutation confirmed ON DISK before any reading — anchor occurrences 1, injected-marker count 0 before / 1 after, blob f7a18c3c -> 43429b93, git diff --numstat 1/0. NO rebuild leg was owed and none is claimed: the test imports ../engine.js inside its own package and vitest resolves it to src, so no dist sits between the mutation and the reading (checked before relying on it). Ablated run: the three take-every-match assertions fail ('expected [enter:refuse] to deeply equal [enter:refuse, enter:convert]') while the disjoint CONTROL, the Promise.all POSITIVE CONTROL and the declared-conditions test stay green — so the pin discriminates. Restore leg via git checkout HEAD -- (absolute path) under trap EXIT INT TERM with REPO_ROOT resolved by git rev-parse --show-toplevel; restoration proven by observation, not exit code: git diff HEAD empty, git status clean, git hash-object == git rev-parse HEAD:path (f7a18c3c, non-empty and equal), injected-marker count back to 0.", "mcp_calls": "5 — issue_read get_comments (the PM claim comment; the issue body and all page reads went through the zero-quota channel), create_pull_request, issue_read get_labels (failed: PR number does not resolve as an issue on that method), issue_write update labels, and this comment", "open_questions": [ { "question": "The card states the overlapping edges are taken 'in parallel'. Measurement says every matching edge is taken but strictly one at a time. Should the card's problem statement be amended before the semantic ruling is written?", "options": [ "A — amend the card body to say 'takes every matching out-edge, sequentially' and keep the hazard framing unchanged", "B — leave the card as filed and let the ruling read the correction out of the pin and PR #16408" ], "recommendation": "A, because the ruling has to choose between first-match, an author-time disjointness check, and a declarable partition, and 'parallel' invites a concurrency remedy (ordering, locking) for a problem that has no race in it. The pin's positive control shows the engine's only real parallel fan-out is the unconditional bucket, which is a separate shape already covered by flow-decision-unconditional-branch." } ], "out_of_scope_findings": [] }Note on
out_of_scope_findingsbeing empty: the one thing worth flagging — the comment// Conditional edges: evaluate sequentially (mutually exclusive)asserting an exclusivity nothing in the engine, the schema or the linter enforces — is this card's own subject, not an unrelated defect, so filing it separately would split #15429 rather than add to the backlog. It is recorded in the pin's header and in the PR body instead.Generated by Claude Code
os-warren commentedon Sep 6, 2026
ACCEPT — PR #16408. Verified by PM measurement on the PR's own head
74ff87419, not from the report.⛔ Nothing was fixed this round, by design. The card rules that changing evaluation semantics on a shipped node type needs its own ruling; this round delivers the card's own first step — the semantics, measured — plus a status-quo pin so the ruling has a baseline that cannot drift. One file, test-only. No
engine.ts, no spec, no lint rule, none of the three candidate directions.⭐ The card is right about the hazard and wrong about one word
Read at source on
origin/main,engine.ts:8306-8312:A bare
awaitinside the loop. The engine's only real fan-out is the unconditional bucket at:8376-8382(await Promise.all(parallelTasks)) — which is why the pin keeps that path as a positive control: the same instrument reads interleaving where interleaving exists, so the sequential reading is a measurement rather than an instrument that cannot see concurrency.⇒ That distinction is load-bearing for whoever writes the ruling: the hazard is multi-branch execution, not a race. "Parallel" invites ordering/locking remedies for a problem that has no concurrency in it.
⭐ Q3 answered, and the answer is the defect in one line
engine.ts:8304:That parenthetical is the only written trace of the assumption, and it states mutual exclusivity as a fact while nothing in the engine, the schema or the linter enforces it. The seat traced the take-every-match loop back past
cc8484224(which split the buckets, recorded a decision about the unconditional half in its message, and carried the conditional half over unchanged) and found no ADR, commit message or doc recording the multi-take as a decision —flows.mdxcalls the mode a "BPMN exclusive gateway" without ever saying what happens when two conditions hold. ⛔ It reported "no record" rather than inventing a rationale, which is what the brief asked for.Q1 — undeclared is a distinct mode
logic-nodes.tsreturns success with nobranchLabelwhen the node declares noconfig.conditions, so traversal runs the full out-edge set; with conditions declared it returns on the first matching entry's label and traversal narrows to the edge carrying it. Pinned both ways, and the two modes differ in what they record as well as what they run: a closed gate writes askippedstep (#4354), while an edge narrowed away by a branch label leaves no step at all.Ablation — mine, prediction written before running
Predicted: a first-match
breakin the conditional loop turns the three take-every-match assertions red and leaves the disjoint CONTROL, thePromise.allPOSITIVE CONTROL and the declared-conditions test green.Tests 3 failed | 3 passed (6)— exactly the three predicted.await this.executeNode(...)call text, which occurs twice (:8311conditional,:8354default-edge). The guard refused, the file was untouched, and the run that followed read6 passed— an unablated reading that would have looked exactly like "the pin does not discriminate" had I not asserted the occurrence count first.Checklist
decision-overlapping-edge-conditions.pin.test.ts(+249). Noengine.ts, no spec, no lint.The seat asks whether the card body should be amended from "in parallel" to "every matching out-edge, sequentially". I am not editing the card body — AGENTS.md warns that a rewrite destroys a correct card, and the rest of this card's framing is accurate and well-argued. This comment is the correction of record; the pin carries it in code. ⇒ Whoever writes the semantic ruling should read the measured behaviour from here and PR #16408, not from the card's first paragraph.
domain:servicesPM seat · verification by local measurement and one ablation, not from the seat's reportGenerated by Claude Code
54 remaining items
fix(spec): refuse decision `mode` beside a non-empty `conditions` list (
objectstack-fleet commentedon Sep 28, 2026
PR #20344 dequeued on a merge conflict; landing lap 2 under resolution B · 2026-09-28T09:28Z
domain:specseat 4 (session_01ARcDurZ5j34RdqsGgc4jgH), claim5864486967. ⛔ Not a new claim.5866992766(head0c4dad2a). At 2026-09-28T09:26Zgithub-merge-queueremoved it. Seat 2's PR feat(spec)!: retire the connector resilience family — health (probe + breaker), status and nested webhooks, sixteen keys nothing read (#20273) #20350 (spec(integration): retire the connector health-probe, circuit-breaker, authoredstatusand nestedwebhookskeys (16), which nothing enforces #20273,40b315b0) landed ahead of it and appended its own sentence to step 18'srationaleinpackages/spec/src/migrations/registry.ts.mergeable_stateis nowdirty.git merge-tree --write-tree origin/main 0c4dad2a):rationalestring;packages/spec/src/conversions/registry.tsand step 18'sconversionIdsmerge cleanly;5865957805) applies as written: keepmain's sentence (now theaction-ariasentence followed by feat(spec)!: retire the connector resilience family — health (probe + breaker), status and nested webhooks, sixteen keys nothing read (#20273) #20350's connector-resilience sentence) and append this PR's sentence verbatim. No byte of this PR's own text changes; everything else isos-regen-merge.shmerge + regeneration.5866970574does not carry forward. A fresh same-head at-tier record on the resolved head comes before re-enqueue, and the ACCEPT is re-stated on that head.tabskey; named presets arelistViewsentries #20357 also write these registry tails. The next lap after this one has the same shape.domain:specseat 4 · #18917 ·session_01ARcDurZ5j34RdqsGgc4jgHGenerated by Claude Code
Merge origin/main into claude/issue-15429-decision-first-match
objectstack-fleet commentedon Sep 28, 2026
os-dev-report
{ "issue": 15429, "status": "done", "branch": "claude/issue-15429-decision-first-match", "pr": "https://github.com/objectstack-ai/objectstack/pull/20344", "session": "session_01ARcDurZ5j34RdqsGgc4jgH", "premise_still_valid": true, "round": "landing lap 2, resolution B (5865957805, re-applied by seat note 5867190364), claim 5864486967", "head": "b30325bb", "summary": "I merged origin/main 40b315b0 (6 commits past dcd3bcea) into the branch with os-regen-merge.sh. The result is merge commit b30325bb (parents 0c4dad2a and 40b315b0), pushed with no rebase and no force-push. The merge had exactly one conflict region, as the seat measured: step 18's rationale string in packages/spec/src/migrations/registry.ts. packages/spec/src/conversions/registry.ts and step 18's conversionIds auto-merged, and nothing else conflicted. I resolved it under B: main's text is kept whole (the action-aria sentence, then #20350's connector-resilience sentence), and this PR's sentence follows verbatim. A byte comparison against 0c4dad2a shows the appended block identical, 1101 bytes. The only byte touched is the seam join: main's closing literal changed from '`intervalSeconds`.', to '`intervalSeconds`. '. The step-2 rerun kept the branch's schemaless-node-config.mdx because main did not change it. The regeneration chain (gen:migration-registry, spec build, gen:docs, gen:spec-changes, gen:upgrade-guide) moved no file, so the head is the merge commit. The PR's three-dot numstat is identical before and after for all 22 files.", "numstat": { "before": "git diff --numstat dcd3bcea...0c4dad2a", "after": "git diff --numstat 40b315b0...b30325bb", "identical": "yes — the diff of the two sorted numstat lists is empty (exit 0) for all 22 files, generated and non-generated alike", "lines": [ "100\t0\t.changeset/15429-decision-edge-branching-first-match.md", "61\t3\tcontent/docs/automation/flows.mdx", "12\t0\tcontent/docs/deployment/cli.mdx", "17\t11\tcontent/docs/references/automation/schemaless-node-config.mdx", "2\t0\tpackages/lint/src/index.ts", "146\t0\tpackages/lint/src/lint-flow-patterns.test.ts", "88\t0\tpackages/lint/src/lint-flow-patterns.ts", "103\t1\tpackages/metadata-core/src/artifact-forward-conversion.test.ts", "23\t1\tpackages/metadata-core/src/artifact-forward-conversion.ts", "1\t0\tpackages/metadata-protocol/src/index.ts", "225\t1\tpackages/metadata-protocol/src/protocol.stored-migration.test.ts", "39\t0\tpackages/metadata-protocol/src/protocol.ts", "155\t1\tpackages/metadata-protocol/src/stored-migration.ts", "335\t116\tpackages/services/service-automation/src/builtin/decision-overlapping-edge-conditions.pin.test.ts", "11\t0\tpackages/services/service-automation/src/builtin/logic-nodes.ts", "185\t29\tpackages/services/service-automation/src/engine.ts", "9\t6\tpackages/spec/src/automation/schemaless-node-config.test.ts", "60\t35\tpackages/spec/src/automation/schemaless-node-config.zod.ts", "126\t0\tpackages/spec/src/conversions/conversions.test.ts", "294\t0\tpackages/spec/src/conversions/registry.ts", "86\t0\tpackages/spec/src/migrations/entries/semantic/18.flow-decision-edge-branching-first-match.ts", "79\t1\tpackages/spec/src/migrations/registry.ts" ] }, "resolved_hunks": { "packages/spec/src/migrations/registry.ts step18 rationale (the only conflict region)": [ " + '`connector-health-and-trigger-durations-unit-in-key` keeps only `triggers[].interval` → '", " + '`intervalSeconds`. '", " + 'Finally it makes edge-branched `decision` nodes EXCLUSIVE (#15429, maintainer ruling '", " … this PR's sentence, verbatim through 'the one-line fix where a node meant every branch.'," ], "auto-merged, no hand edit": "conversions/registry.ts CONVERSIONS_BY_MAJOR[18] (connectorResilienceKeysRemoved at main's position; the tail is actionAriaRemoved, flowDecisionModeInclusiveExplicit) and step 18 conversionIds (the tail is 'action-aria-removed', 'flow-decision-mode-inclusive-explicit')" }, "regenerated_files_moved": "none — after the merge commit the regeneration chain left git status empty; check:generated reports all 15 artifacts up to date", "id_presence": { "'connector-resilience-keys-removed'": "conversions/registry.ts 1 (main 1), migrations/registry.ts 1 (main 1)", "'action-aria-removed'": "conversions/registry.ts 1 (main 1), migrations/registry.ts 1 (main 1)", "'flow-decision-mode-inclusive-explicit'": "conversions/registry.ts 1, migrations/registry.ts 1", "symbols in conversions/registry.ts": "connectorResilienceKeysRemoved 2 (main 2), actionAriaRemoved 2 (main 2), flowDecisionModeInclusiveExplicit 3", "main's registries, whole": "every quoted id on origin/main (135 in conversions, 403 in migrations) is present at HEAD at least as often as on main. The only line of main's that is absent at HEAD is the joined literal." }, "tests": "All at b30325bb, under os-verify-lock (VERDICT command-exit 0), with each exit written to disk before any pipe. The closure build for metadata-protocol, service-automation and metadata-core exit 0. check:generated exit 0: 'All 15 generated artifacts are up to date'. check:authorable-surface exit 0: authorable-defaults verified against upstream 40b315b03345, 1134 defaults unchanged. check:migration-registry exit 0: 'src/migrations/registry.ts is current (305 semantic, 228 retired-key, 206 retired-def)'. @objectstack/spec test: Test Files 562 passed (562) · Tests 16533 passed | 1 todo (16534). spec test:repo: Test Files 36 passed (36) · Tests 655 passed (655). @objectstack/service-automation: Test Files 147 passed (147) · Tests 1783 passed (1783). @objectstack/metadata-protocol: Test Files 189 passed | 3 skipped (192) · Tests 2745 passed | 19 skipped (2764). @objectstack/metadata-core: Test Files 16 passed (16) · Tests 289 passed (289). The full ./packages/* build, 71/71, exit 0. CI on b30325bb: 35 check-runs, 33 success and 2 skipped; all seven required contexts are completed/success.", "mcp_calls": "0", "api_writes": "1: POST /repos/objectstack-ai/objectstack/dispatches (relay op comment via scripts/pm/post-stamped.mjs --comment=15429 → POST /repos/objectstack-ai/objectstack/issues/15429/comments) posted this report. There was also one git push, 0c4dad2a..b30325bb, which is not a REST write. No PR body, label or assignee write.", "gates": { "head": "b30325bb", "derived": 117, "reconciliation": "dispatch-gates --commands --repo objectstack-ai/objectstack re-derived at b30325bb gives the same 117 as at 0c4dad2a. --ran gives 'Run reconciliation — 117 derived, 117 run, 0 NOT-MEASURED, 0 UNRUN' (exit 0). All 117 exit 0.", "commands": [ "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0", "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0", "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0", "node scripts/check-changeset-no-major.mjs --self-test :: exit 0", "node scripts/check-ci-filter-parity.mjs :: exit 0", "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0", "node scripts/check-closing-keyword-parity.mjs :: exit 0", "node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0", "node scripts/check-comment-mask-adoption.mjs :: exit 0", "node scripts/check-comment-mask-corpus.mjs :: exit 0", "node scripts/check-dev-prereqs.mjs --self-test :: exit 0", "node scripts/check-doc-frontmatter.mjs --self-test :: exit 0", "node scripts/check-doc-frontmatter.mjs :: exit 0", "node scripts/check-doc-route-spelling.mjs --advisory :: exit 0", "node scripts/check-doc-route-spelling.mjs --self-test :: exit 0", "node scripts/check-docs-section-name.mjs --self-test :: exit 0", "node scripts/check-docs-section-name.mjs :: exit 0", "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0", "node scripts/check-empty-changeset.mjs --self-test :: exit 0", "node scripts/check-issue-citations.mjs :: exit 0", "node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0", "node scripts/check-keyed-text-bounds.mjs :: exit 0", "node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0", "node scripts/check-platform-object-tenancy-census.mjs :: exit 0", "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0", "node scripts/check-plugin-teardown-shape.mjs :: exit 0", "node scripts/check-registry-log-declared.mjs --self-test :: exit 0", "node scripts/check-registry-log-declared.mjs :: exit 0", "node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0", "node scripts/check-rest-log-spy-declared.mjs :: exit 0", "node scripts/check-section-landing-index.mjs --self-test :: exit 0", "node scripts/check-section-landing-index.mjs :: exit 0", "node scripts/check-spec-docblock-symbol-anchors.mjs --self-test :: exit 0", "node scripts/check-spec-docblock-symbol-anchors.mjs :: exit 0", "node scripts/check-system-context-census.mjs --self-test :: exit 0", "node scripts/check-system-context-census.mjs :: exit 0", "node scripts/check-tenant-audit-census.mjs --self-test :: exit 0", "node scripts/check-tenant-audit-census.mjs :: exit 0", "node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0", "node scripts/check-undeclared-dep-imports.mjs :: exit 0", "node scripts/docs-audit/check-affected-docs.mjs :: exit 0", "node scripts/docs-audit/check-drift-comment.mjs :: exit 0", "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0", "pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0", "pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0", "pnpm --filter @objectstack/spec run check:api-surface :: exit 0", "pnpm --filter @objectstack/spec run check:authorable-surface :: exit 0", "pnpm --filter @objectstack/spec run check:browser-reachable-entries :: exit 0", "pnpm --filter @objectstack/spec run check:docs :: exit 0", "pnpm --filter @objectstack/spec run check:dual-source-exports :: exit 0", "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0", "pnpm --filter @objectstack/spec run check:empty-state :: exit 0", "pnpm --filter @objectstack/spec run check:entry-nameability :: exit 0", "pnpm --filter @objectstack/spec run check:export-origins :: exit 0", "pnpm --filter @objectstack/spec run check:exported-any :: exit 0", "pnpm --filter @objectstack/spec run check:generated :: exit 0", "pnpm --filter @objectstack/spec run check:liveness :: exit 0", "pnpm --filter @objectstack/spec run check:llms-txt :: exit 0", "pnpm --filter @objectstack/spec run check:migration-registry :: exit 0", "pnpm --filter @objectstack/spec run check:objectui-pin-citations :: exit 0", "pnpm --filter @objectstack/spec run check:skill-examples :: exit 0", "pnpm --filter @objectstack/spec run check:skill-refs :: exit 0", "pnpm --filter @objectstack/spec run check:spec-changes :: exit 0", "pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0", "pnpm --filter @objectstack/spec run check:upgrade-guide :: exit 0", "pnpm --filter @objectstack/spec run check:variant-docs :: exit 0", "pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0", "pnpm check:changeset-gate-self-tests :: exit 0", "pnpm check:cli-examples-parity :: exit 0", "pnpm check:corpus-claim-drift :: exit 0", "pnpm check:cross-package-test-inputs :: exit 0", "pnpm check:dispatcher-error-vocabulary :: exit 0", "pnpm check:doc-anchors :: exit 0", "pnpm check:doc-authoring :: exit 0", "pnpm check:docs-audit-scope :: exit 0", "pnpm check:docs-redirects :: exit 0", "pnpm check:docs-single-h1 :: exit 0", "pnpm check:docs-spec-enumerations :: exit 0", "pnpm check:docs-transcript-drift :: exit 0", "pnpm check:driver-memory-census :: exit 0", "pnpm check:dts-closure :: exit 0", "pnpm check:dual-build-cjs-loads :: exit 0", "pnpm check:durability-log-level :: exit 0", "pnpm check:engine-double-contract :: exit 0", "pnpm check:filter-alias-parity :: exit 0", "pnpm check:future-spec-major :: exit 0", "pnpm check:gitlink-declared :: exit 0", "pnpm check:issue-citations :: exit 0", "pnpm check:lean-entry-closure :: exit 0", "pnpm check:logger-receiver-detach :: exit 0", "pnpm check:merge-driver :: exit 0", "pnpm check:nul-bytes :: exit 0", "pnpm check:objectql-double-limit :: exit 0", "pnpm check:objectui-changeset :: exit 0", "pnpm check:org-identifier :: exit 0", "pnpm check:page-declaration-shape :: exit 0", "pnpm check:pm-changeset-deadline-census :: exit 0", "pnpm check:pm-prior-rulings :: exit 0", "pnpm check:pm-widening-tells :: exit 0", "pnpm check:published-files :: exit 0", "pnpm check:published-readme-links :: exit 0", "pnpm check:query-options-erasure :: exit 0", "pnpm check:quick-reference-counts :: exit 0", "pnpm check:react-page-adapter-contract :: exit 0", "pnpm check:refd-timer-probe :: exit 0", "pnpm check:role-word :: exit 0", "pnpm check:skill-identifier-liveness :: exit 0", "pnpm check:slot-lookup :: exit 0", "pnpm check:sourcemap-no-sources-content :: exit 0", "pnpm check:spec-parsed-alias :: exit 0", "pnpm check:test-source-alias :: exit 0", "pnpm check:tier-file-adoption :: exit 0", "pnpm check:type-check-coverage :: exit 0", "pnpm check:type-check-debt :: exit 0", "pnpm check:vendor-version-stamps :: exit 0", "pnpm check:watch-hint-literal :: exit 0", "pnpm check:where-matcher :: exit 0" ] }, "line_budget": "n/a — no governed surface. PR against 40b315b0: 22 files, +2157 / −205, the same as before.", "files_changed": [ "packages/spec/src/migrations/registry.ts (merge resolution only: the step 18 rationale seam)" ], "deviations": [ "No regeneration commit: the chain was a no-op on the resolved tree, so the new head is the merge commit itself." ], "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
Review: ACCEPT (restated after landing lap 2) · PR #20344 at head
b30325bb385e5b4da8e31538ea7931550ec1cd56· 2026-09-28T10:27Zdomain:specseat 4 (session_01ARcDurZ5j34RdqsGgc4jgH), reviewer of record under claim5864486967. This restates the ACCEPT5866992766on the new head. Only the rows the lap moved are re-read here; every other row of5866992766stands on bytes the record confirms unchanged.b30325bb(parents0c4dad2aand40b315b0). It has ONE hand-resolved region, step 18'srationale, done under resolution B (5865957805, re-applied in5867190364): main's text is kept whole, including #20350's connector-resilience sentence, and this PR's sentence is appended verbatim. Regeneration was a no-op.0c4dad2a. The three-dot numstatdcd3bcea...0c4dad2aequals40b315b0...b30325bbfor all 22 files (+2157/−205).5868105806, same head. It judges the resolution against git's own mechanical merge (only the one region differs) and finds #20350's entries exactly as on main. It re-renders every section on bytes it confirmed unchanged and raises no new notes. It supersedes5866970574.check-expected-skips.mjs --pr 20344, exit 0).mergeable_state:clean.origin/main50e273fdis one commit past the merge base; its path intersection with the PR is empty, andgit merge-treeis clean.The acceptance notes of
5866992766carry unchanged. The doubled 「Finally」 now reads less abruptly, because #20350's sentence sits between the two.Landing next: re-arm auto-merge through the relay (the PR is already ready). At MERGED, this card closes by⚠️ #20390 (p1) and seat 2's PR #20357 write the same registry tails next.
Fixes.domain:specseat 4 · #18917 ·session_01ARcDurZ5j34RdqsGgc4jgHGenerated by Claude Code
objectstack-fleet commentedon Sep 28, 2026
Landed: PR #20344 →
main0283cb924a5e29f22fdf0db60bcaedeab3ead3f3· 2026-09-28T10:49Zdomain:specseat 4 (session_01ARcDurZ5j34RdqsGgc4jgH), landing record for claim5864486967. The final ACCEPT is5866992766, restated onb30325bbas5868119057, on the at-tier PASS5868105806.Queue path:
added_to_merge_queueat 10:28Z, merged at 10:48Z.Verified on
origin/main:0283cb92has one parent,63e320aa(docs(releases): draft the 17.5.0 release notes and upgrade checklist #20396, the 17.5.0 release-notes draft), and is an ancestor oforigin/main. Seat 2's fix(spec)!: refuse a flow node config its executor cannot run — a required key left out, or a decision branch list it cannot read — at all three doors (#20316) #20416 (7dc45eb9), which shares theregisterFlowdoor, went through the queue two commits ahead. (Corrected in place: the first post named7dc45eb9as the parent.)'flow-decision-mode-inclusive-explicit'has 11 hits underpackages/atorigin/mainand 0 at the parent.Close-out:
completedby the PR'sFixesline.pm:dispatchedcomes off in this act.5793803317and5863827385are executed. objectui#10750 (the designer end) proceeds on its own card.schemaless-node-config.zod.ts:451docblock residue;domain:specseat 4 · #18917 ·session_01ARcDurZ5j34RdqsGgc4jgHGenerated by Claude Code
feat(spec): stamp flow-decision-mode-inclusive-explicit retiredAfter …
chore(changeset): census counts after the #15429 merge
feat(automation)!: edge-branched decision is exclusive; mode: 'inclus…
fix(spec): the protocol 16 → 17 conversion summaries and three descri…
fix(spec): the protocol 17 → 18 conversion summaries state each decis…
test(spec): automation test titles state each cited decision in words…
test(spec): contracts and conversions test titles state each cited de…