Skip to content

docs(roadmap): mark 1.P Done + pre-1.Q hardening (condition-edge validation + Sonnet-reviewed) - #21

Merged
cemililik merged 5 commits into
mainfrom
development
Jun 14, 2026
Merged

docs(roadmap): mark 1.P Done + pre-1.Q hardening (condition-edge validation + Sonnet-reviewed)#21
cemililik merged 5 commits into
mainfrom
development

Conversation

@cemililik

@cemililik cemililik commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

A post-1.P-merge bookkeeping + pre-1.Q hardening batch. Docs-and-validation only on the engine side
(one small dag.ts parse-time check + its test); no engine-behavior change to a run.

Commits

ec99615 — roadmap: 1.P ✅ Done

Marks the node-type handlers (1.P, PR #20) Done across phase-1-engine-and-llm.md, current.md, CLAUDE.md,
README.md; 1.Q (human gate) named as the next workstream.

2b19f7f — deferred-tasks hardening (1.M validation)

A pre-1.Q triage of deferred-tasks.md. Most open items are legitimately blocked (a future workstream,
an external key/license, or a recorded decision); two were actionable now:

  • dag.ts (validateStructuralEdge): reject a plain (handle-less) edge whose from is a condition
    node — it would wire a dependent the handler's selected never names (a silently-dead node), or be
    redundant with a branch target. Reuses the invalid_handle issue; safe (no fixture/spec/test used one).
    Pinned by dag.test.ts + documented in workflow-yaml-spec.md §edges.
  • Bookkeeping: the secret-into-run.outputs runtime-taint item is closed at the source by 1.P
    (buildExpressionScope masks secret inputs, so a transform/condition/merge_fn can never read a raw
    secret to launder). The ctx.*-threading item is annotated as a run-lifecycle + seam change (async
    resolveContext at run start) — best folded into 1.Q/1.R, not a drive-by.

d3eca05 + 01b5933 — Sonnet pre-merge review, folded

Two multi-dimensional Sonnet reviews (each finding adversarially verified) audited this batch with a
focus on verifying every ✅-marked item is genuinely resolved. Verdict MERGE-WITH-NITS — zero
blocker/high, zero real defects. The secret-taint closure was confirmed airtight by an exhaustive path
trace (no pipe/filter unwrap, no scope bypass), and all audited resolved-items match the code. Folded:

  • The handle-less-edge rejection message + the §edges note were reworded (they overstated "dead node" —
    a plain edge to a branch target is also rejected, but that target IS activated, so it's redundant).
  • A test pinning that the rejection is unconditional (even to the branch's own target).
  • run-plan.md invalid_handle enumeration updated.
  • scope.ts now documents the load-bearing secret invariant (outputsRecord does not re-mask; a future
    handler emitting ctx.inputs-derived data must maskSecretInputs first).
  • sse-event-schema.md: a MaskedSecret marker can appear in node:completed.output / run:completed.outputs
    (surfaces must render it as a redacted placeholder).

Validation

  • pnpm turbo run lint typecheck test build format:check: green — 578 core tests.
  • Leakwatch: 0. Engine-deps allowlist: unchanged. Seam holds; zero platform imports.

🤖 Generated with Claude Code

Summary by Sourcery

Mark node-type handlers roadmap item 1.P as completed and harden pre-1.Q validation and contracts around condition edges and secret masking in outputs.

Bug Fixes:

  • Prevent handle-less edges originating from condition nodes by rejecting them at DAG validation time, including edges to branch targets.

Enhancements:

  • Clarify and document the invariant that secret-typed inputs are masked at ingress and never re-masked in outputs, and require future handlers to preserve this.
  • Document that MaskedSecret markers can appear in node and run outputs and must be treated as redacted placeholders.
  • Extend the run-plan invalid_handle error definition and workflow YAML spec to describe the new condition-edge validation behavior.

Documentation:

  • Update roadmap, README, and CLAUDE-facing docs to record 1.P (node-type handlers) as done and identify 1.Q (human gate) as the next workstream.
  • Annotate deferred-tasks items to close the secret-into-run.outputs taint follow-up and clarify the async context-resolution plan for future phases.

Tests:

  • Add DAG validation tests asserting that plain edges from condition nodes are always rejected, including edges to the condition's own branch target.

Summary by CodeRabbit

  • Documentation

    • Clarified workflow validation rules requiring condition nodes to use specific handle forms rather than plain edges
    • Enhanced security documentation regarding masked secret markers in workflow outputs
    • Updated Phase 1 completion status reflecting AgentRunner and node-type handlers
  • Bug Fixes

    • Fixed potential secret exposure in run outputs through masking gates
  • Tests

    • Added validation test cases for condition node edge constraints

cemililik and others added 4 commits June 14, 2026 16:00
…e next workstream

PR #20 merged — the six non-agent NodeExecutor arms (condition / transform / fan_out / fan_in /
input / output) behind a dispatching executor, executor-only, with the pre-merge BLOCKER secret-leak
fixed via a secretInputNames masking gate on NodeExecContext.

- phase-1-engine-and-llm.md: §1.P → ✅ Done (PR #20); top status block updated; 1.Q/1.R/1.S/1.AC remain.
- current.md: 1.P ✅ Done in both status passages; the human gate (1.Q) named as next.
- CLAUDE.md + README.md: status lines advanced to 1.P done, 1.Q next.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…condition (1.M)

A pre-1.Q triage of deferred-tasks.md. Most open items are legitimately blocked (a future
workstream, an external key/license, or a recorded decision); two were actionable now.

- dag.ts (1.M validation): `validateStructuralEdge` now rejects a plain (handle-less) edge whose
  `from` is a `condition` node — such an edge would wire a dependent the handler's `selected` never
  names, a silently-dead downstream. Reuses the `invalid_handle` issue; safe (no fixture/spec/test
  used one — conditions route via `branches` + `nodeId:when` handles). Pinned by dag.test.ts and
  documented in workflow-yaml-spec.md §edges.
- Bookkeeping: the **secret-into-`run.outputs` runtime-taint** item (ADR-0029(c)) is closed at the
  source by 1.P — `buildExpressionScope` masks secret inputs, so a transform/condition/merge_fn can
  never read a raw secret to launder; no runtime taint needed. Checked off.

deferred-tasks.md triage recorded: `ctx.*` threading is the highest-value open engine gap but is a
run-lifecycle + seam change (async `resolveContext` at run start) — best folded into 1.Q/1.R, not a
drive-by. Everything else stays legitimately deferred.

pnpm turbo run lint typecheck test build format:check: green (577 core tests).
Leakwatch: 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-NITS)

The Sonnet review of the hardening pass returned MERGE-WITH-NITS (zero blocker/high; the secret-taint
closure confirmed substantively correct). Folds the confirmed mediums/lows.

- DOC-1 (medium): the handle-less-condition-edge rejection message + the workflow-yaml-spec §edges note
  overstated "silently dead node" — a plain edge to a node that IS a branch target is ALSO rejected, but
  that target IS activated (via materialization), so it's redundant, not dead. Reworded both (dag.ts
  message + the spec note) to cover both cases (redundant-with-a-branch-target OR a never-activated node).
- T-1 (medium): added a dag.test.ts case pinning that a plain edge from a condition to its OWN branch
  target is also rejected — the rejection is unconditional (unlike the `parallel_of` "redundant-if-agrees"
  leniency); the existing test only covered the dead-downstream (stray) topology.
- DOC-2 (low): run-plan.md's `invalid_handle` enumeration now lists the new plain-edge-from-condition case.
- TRIAGE-02 (nit): clarified `merge_fn` → fan_in `merge_fn` in the secret-taint closure entry.

Skipped (with reason): the double-push of unknown_edge_target + invalid_handle for a missing `to` (both
true, harmless, parse fails regardless); assertion-strength + test-title nits (subsumed by the new test);
TRIAGE-01/03/04 doc-annotation polish (the substance is already accurate).

pnpm turbo run lint typecheck test build format:check: green (578 core tests).
Leakwatch: 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…findings)

The re-run of the two socket-failed Sonnet dimensions confirmed every ✅ claim GENUINE — an exhaustive
(a)–(g) trace verified the secret-taint closure is airtight (no pipe/filter unwrap, no scope bypass), and
the audited resolved-items (handle-less edge, six handlers + dispatcher + secretInputNames gate,
tool-output spill, Sonar fixes, concurrent-agent test, reportDuplicates) all match the code. No real
defects; the actionable items are doc/comment improvements that make implicit invariants explicit.

- AUDIT-9 (medium): scope.ts `outputsRecord` now documents the load-bearing secret invariant — it does
  NOT re-mask; `run.outputs` stays raw-secret-free solely because the `input` handler masks at the ingress
  and expression handlers read through `buildExpressionScope`. A future handler emitting `ctx.inputs`-derived
  data MUST `maskSecretInputs` first. (Defense against a future tool/human-gate/subworkflow handler.)
- SEC-3 (low): sse-event-schema.md §security now states a `MaskedSecret` marker can appear in
  `node:completed.output` / `run:completed.outputs` (the input node emits masked inputs) and surfaces must
  render it as a redacted placeholder.
- AUDIT-6 (nit): corrected the `readBracket` description in deferred-tasks.md (it's a thin dispatcher, not
  literally "3-line").

AUDIT-2 was a false positive (no `node-handlers.test.ts:618-655` citation exists in deferred-tasks.md).

pnpm turbo run lint typecheck test build format:check: green (578 core tests). Leakwatch: 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Reviewer's Guide

Marks roadmap item 1.P (node-type handlers) as Done across docs, and hardens the engine by adding structural validation to reject plain edges from condition nodes while documenting and pinning the secret-masking invariants and SSE/event behavior; no runtime behavior change beyond stricter parse-time validation.

Sequence diagram for new condition-edge structural validation

sequenceDiagram
  participant WorkflowYAMLParser
  participant buildRunPlan
  participant validateStructuralEdge
  participant Issues

  WorkflowYAMLParser->>buildRunPlan: buildRunPlan(workflowDoc)
  loop for each edge
    buildRunPlan->>validateStructuralEdge: validateStructuralEdge(edge, fromNode, locator, issues)
    alt edge.from is condition and edge.handle is absent
      validateStructuralEdge->>Issues: issues.push(invalid_handle)
      validateStructuralEdge-->>buildRunPlan: return
    else other edge
      validateStructuralEdge-->>buildRunPlan: addEdge(fromBase, edge.to)
    end
  end

  buildRunPlan-->>WorkflowYAMLParser: throw WorkflowGraphError(issues)
Loading

File-Level Changes

Change Details Files
Add structural validation to reject plain (handle-less) edges originating from condition nodes and test it.
  • Extend validateStructuralEdge to treat any handle-less edge from a condition node as an invalid_handle issue with an explanatory message.
  • Document in workflow-yaml-spec and run-plan docs that condition nodes must route via branches[].target_node or nodeId:when handles and that plain edges from conditions are rejected.
  • Add dag.test cases covering rejection of plain edges from condition nodes, including edges to their own branch targets.
packages/core/src/dag.ts
packages/core/src/dag.test.ts
docs/reference/contracts/workflow-yaml-spec.md
docs/reference/shared-core/run-plan.md
Clarify and lock in secret-masking invariants for inputs and outputs, and document MaskedSecret behavior in events.
  • Annotate outputsRecord in scope.ts with the invariant that it passes outputs through verbatim and relies on upstream masking of secret-typed inputs, noting the requirement for future handlers to mask ctx.inputs-derived data.
  • Update deferred-tasks to mark the secret-into-run.outputs taint item as closed via buildExpressionScope masking and to describe remaining secret-related items.
  • Document that MaskedSecret markers can appear in node:completed.output and run:completed.outputs/run:failed.partialOutputs and must be treated as redacted.
  • Clarify that the input handler emits masked inputs and ties this to run-plan output capture docs.
packages/core/src/engine/node-handlers/scope.ts
docs/roadmap/deferred-tasks.md
docs/reference/contracts/sse-event-schema.md
docs/reference/shared-core/run-plan.md
Update roadmap and status documentation to mark node-type handlers (1.P) as complete and position 1.Q as the next workstream.
  • Mark 1.P as Done in the phase-1-engine-and-llm roadmap and describe its implementation as a dispatching NodeExecutor with six non-agent handlers and secretInputNames masking.
  • Update current roadmap, CLAUDE.md, and README.md to reflect that node-type handlers have landed (PR feat(core): node-type handlers (1.P) — condition/transform/fan-out/fan-in/input/output #20) and that the human gate (1.Q) is next.
  • Tighten wording around cognitive complexity fixes and other already-completed items in deferred-tasks to reflect their final state.
docs/roadmap/phases/phase-1-engine-and-llm.md
docs/roadmap/current.md
CLAUDE.md
README.md
docs/roadmap/deferred-tasks.md

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@cemililik, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 1 hour, 39 minutes, and 6 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more credits in the billing tab to continue.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d189f3c1-99fd-4ab4-a9e0-4ba26ae4223a

📥 Commits

Reviewing files that changed from the base of the PR and between 01b5933 and 5acd6f5.

📒 Files selected for processing (3)
  • docs/reference/contracts/sse-event-schema.md
  • packages/core/src/dag.test.ts
  • packages/core/src/dag.ts
📝 Walkthrough

Walkthrough

Adds a runtime guard in validateStructuralEdge (dag.ts) that rejects plain (handle-less) edges originating from condition nodes with an invalid_handle issue, backed by two new test cases. Accompanying documentation updates reflect 1.P completion, document the secret-masking invariant in scope.ts and the SSE schema, and advance the roadmap to 1.Q.

Changes

Condition node edge validation

Layer / File(s) Summary
DAG guard and tests
packages/core/src/dag.ts, packages/core/src/dag.test.ts
validateStructuralEdge now records an invalid_handle issue and returns early when a plain (handle-less) edge originates from a condition node. Two new Vitest cases assert rejection for a plain target and the condition's own branch target.
Spec and run-plan docs
docs/reference/contracts/workflow-yaml-spec.md, docs/reference/shared-core/run-plan.md, docs/roadmap/deferred-tasks.md
workflow-yaml-spec.md states handle-less edges from condition nodes are rejected at parse time; run-plan.md expands the invalid_handle bullet; deferred-tasks.md marks the validation gap as fixed with test and spec attribution.

Phase 1 completion documentation

Layer / File(s) Summary
Secret masking invariant
packages/core/src/engine/node-handlers/scope.ts, docs/reference/contracts/sse-event-schema.md, docs/roadmap/deferred-tasks.md
scope.ts adds a load-bearing comment stating run.outputs passthrough does not re-mask secrets. sse-event-schema.md clarifies MaskedSecret may appear in node:completed.output and must be treated as a redacted placeholder. Deferred-tasks marks the secret-into-run.outputs taint as resolved.
Roadmap and status updates
CLAUDE.md, README.md, docs/roadmap/current.md, docs/roadmap/phases/phase-1-engine-and-llm.md, docs/roadmap/deferred-tasks.md
All roadmap and status files are updated to mark 1.O (AgentRunner join) and 1.P (node-type handlers via createDispatchingNodeExecutor, secretInputNames masking) as complete and set 1.Q (human gate) as the next workstream.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • HodeTech/Relavium#16: Directly within the same DAG builder / RunPlan handle-validation workstream — the validateStructuralEdge guard and dag.test.ts assertions in this PR extend the same edge-validation logic introduced there.

Poem

🐇 Hop, hop — the condition gate is sealed tight,
No handle-less edges shall pass in the night!
The secret stays masked, run.outputs stays clean,
Six executor arms and a wait_first machine.
Milestone 1.P is done, the roadmap shines bright —
On to 1.Q, little rabbit, keep coding with might! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly summarizes the main changes: marking workstream 1.P as complete and applying pre-1.Q hardening through condition-edge validation and documentation updates. The title is specific, concise, and directly reflects the primary objectives.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch development

Comment @coderabbitai help to get the list of available commands and usage tips.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The new invalid_handle error message for plain condition edges in validateStructuralEdge is quite long and repeats explanation already present in docs; consider shortening it and/or extracting a shared constant so the error text and spec stay aligned.
  • The two new tests in dag.test.ts share almost identical graph setup; you could factor the shared YAML into a small helper or fixture to reduce duplication and make future edge-case additions easier.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The new `invalid_handle` error message for plain condition edges in `validateStructuralEdge` is quite long and repeats explanation already present in docs; consider shortening it and/or extracting a shared constant so the error text and spec stay aligned.
- The two new tests in `dag.test.ts` share almost identical graph setup; you could factor the shared YAML into a small helper or fixture to reduce duplication and make future edge-case additions easier.

## Individual Comments

### Comment 1
<location path="docs/reference/contracts/sse-event-schema.md" line_range="139" />
<code_context>
+The same `{ secret: true, ref }` **`MaskedSecret`** marker can also appear in **`node:completed.output`** (for an `input` node, which emits the masked inputs) and therefore in **`run:completed.outputs`** / **`run:failed.partialOutputs`** wherever a `secret`-typed input would otherwise surface — the engine masks `secret` inputs at the ingress so a raw secret never reaches an output payload (see [run-plan.md §output capture](../shared-core/run-plan.md)). **A surface rendering node/run outputs must treat a `MaskedSecret` object as a redacted placeholder, not displayable data.**
</code_context>
<issue_to_address>
**suggestion (typo):** Clarify the phrasing in “A surface rendering node/run outputs…” for grammatical correctness.

The phrase “A surface rendering node/run outputs must treat…” is ungrammatical. Please reword, e.g. “Any surface rendering of node/run outputs must treat a `MaskedSecret` object as a redacted placeholder…” or “A surface rendering of node/run outputs must treat…”.

```suggestion
The same `{ secret: true, ref }` **`MaskedSecret`** marker can also appear in **`node:completed.output`** (for an `input` node, which emits the masked inputs) and therefore in **`run:completed.outputs`** / **`run:failed.partialOutputs`** wherever a `secret`-typed input would otherwise surface — the engine masks `secret` inputs at the ingress so a raw secret never reaches an output payload (see [run-plan.md §output capture](../shared-core/run-plan.md)). **Any surface rendering of node/run outputs must treat a `MaskedSecret` object as a redacted placeholder, not displayable data.**
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread docs/reference/contracts/sse-event-schema.md Outdated

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements validation to reject plain (handle-less) edges originating from a condition node, ensuring routing from conditions is explicitly handled via branches or named handles. It also updates documentation regarding the landing of node-type handlers (1.P), the prevention of secret leaks via MaskedSecret placeholders in outputs, and adds corresponding unit tests. Feedback is provided to guard the new edge validation in packages/core/src/dag.ts to prevent redundant and confusing error messages when the edge endpoints themselves are invalid.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread packages/core/src/dag.ts Outdated
}
return; // a handled edge's dependency comes from branch materialization, never a second edge here
}
if (fromNode?.type === 'condition') {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If either the source node (fromNode) is undefined or the target node (edge.to) does not exist in the graph, the edge already has a critical structural error (unknown_edge_target) which is reported above. Checking fromNode?.type === 'condition' and pushing an invalid_handle error in those cases leads to redundant and confusing error messages for the user.

We should guard this check to ensure we only validate condition routing when both endpoints of the edge are valid.

Suggested change
if (fromNode?.type === 'condition') {
if (fromNode !== undefined && nodesById.has(edge.to) && fromNode.type === 'condition') {

…#21 review)

Addresses the PR #21 review of the new handle-less-condition-edge validation.

- dag.ts (the valid finding): the condition-routing check now also requires `nodesById.has(edge.to)`,
  so an edge with a missing `to` (or `from`) — already reported as `unknown_edge_target` — does NOT
  also push a redundant/confusing `invalid_handle` for the same edge. `addEdge` no-ops on a phantom
  endpoint, so the fall-through is safe. Pinned by a new dag.test.ts case (condition edge to a
  nonexistent target ⇒ only `unknown_edge_target`, never `invalid_handle`).
- dag.ts: shortened the `invalid_handle` message to the actionable instruction (route via
  `branches[].target_node` or the `nodeId:when` handle form); the redundant-vs-dead rationale lives in
  the code comment + workflow-yaml-spec.md §edges, not the error string.
- sse-event-schema.md: grammar fix ("A surface rendering node/run outputs" → "Any surface rendering of
  node/run outputs").

Skipped (with reason): a shared error-message constant (over-engineering for one call site); factoring
the two condition-edge tests into a fixture (dag.test.ts's idiom is self-contained inline YAML per test
— ~40 tests follow it; a 2-test helper would break that consistency for ~3 lines).

pnpm turbo run lint typecheck test build format:check: green (579 core tests). Leakwatch: 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cemililik
cemililik merged commit 0a0019b into main Jun 14, 2026
6 checks passed
@sonarqubecloud

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant