docs(roadmap): mark 1.P Done + pre-1.Q hardening (condition-edge validation + Sonnet-reviewed) - #21
Conversation
…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>
Reviewer's GuideMarks 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 validationsequenceDiagram
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)
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Warning Review limit reached
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 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 configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthroughAdds a runtime guard in ChangesCondition node edge validation
Phase 1 completion documentation
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- The new
invalid_handleerror message for plain condition edges invalidateStructuralEdgeis 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.tsshare 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>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
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.
| } | ||
| return; // a handled edge's dependency comes from branch materialization, never a second edge here | ||
| } | ||
| if (fromNode?.type === 'condition') { |
There was a problem hiding this comment.
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.
| 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>
|



A post-1.P-merge bookkeeping + pre-1.Q hardening batch. Docs-and-validation only on the engine side
(one small
dag.tsparse-time check + its test); no engine-behavior change to a run.Commits
ec99615— roadmap: 1.P ✅ DoneMarks 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:
validateStructuralEdge): reject a plain (handle-less) edge whosefromis aconditionnode — it would wire a dependent the handler's
selectednever names (a silently-dead node), or beredundant with a branch target. Reuses the
invalid_handleissue; safe (no fixture/spec/test used one).Pinned by
dag.test.ts+ documented in workflow-yaml-spec.md §edges.run.outputsruntime-taint item is closed at the source by 1.P(
buildExpressionScopemasks secret inputs, so a transform/condition/merge_fn can never read a rawsecret to launder). The
ctx.*-threading item is annotated as a run-lifecycle + seam change (asyncresolveContextat run start) — best folded into 1.Q/1.R, not a drive-by.d3eca05+01b5933— Sonnet pre-merge review, foldedTwo 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:
a plain edge to a branch target is also rejected, but that target IS activated, so it's redundant).
invalid_handleenumeration updated.outputsRecorddoes not re-mask; a futurehandler emitting
ctx.inputs-derived data mustmaskSecretInputsfirst).MaskedSecretmarker can appear innode: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.🤖 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:
Enhancements:
Documentation:
Tests:
Summary by CodeRabbit
Documentation
Bug Fixes
Tests