Fix parallel-branches branch middleware to run in subgraph state space#157
Merged
chris-colinsky merged 1 commit intoJun 15, 2026
Merged
Conversation
Branch middleware wraps the branch's subgraph invocation (per pipeline-utilities 11.7), so it must operate in the branch subgraph's state space. The 11.4 outputs projection ran inside the middleware chain, so a middleware that short-circuits with a subgraph-space partial update (notably FailureIsolation's degraded_update) had it misapplied to the parent state and tripped extra-field validation. Move the projection outside the chain: the middleware now sees the subgraph-space partial, and the parent projection runs on whatever the chain produced (the real result on success, a degraded_update on isolation). A degraded_update that omits a projected field skips that field rather than raising, per the 11.4 partial-contribution model. The bug was latent because RetryMiddleware, the only branch middleware exercised before now, re-invokes rather than returning a cross-space update. The success path, fan-out instance middleware, and node-level placement are unchanged.
There was a problem hiding this comment.
Pull request overview
Fixes parallel_branches branch middleware execution so middleware runs in the branch subgraph’s state space (per pipeline-utilities §11.7) by moving the outputs projection outside the middleware chain, and adds coverage for degrade-path projection/leniency.
Changes:
- Run branch middleware on subgraph-space partials and apply
outputsprojection after the middleware chain. - Make degrade-path projection lenient by skipping
outputsfields not present in a degraded partial update. - Add unit tests covering degrade/success projection behavior and
(isolation, retry)chain behavior; document fix in changelog.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| tests/unit/test_parallel_branches.py | Adds regression and behavior tests for branch-middleware state-space and degrade-path projection semantics. |
| src/openarmature/graph/parallel_branches.py | Moves outputs projection outside branch middleware chain and skips missing sub-fields on degrade partials. |
| CHANGELOG.md | Notes the bugfix and the degrade-path leniency under Unreleased “Fixed”. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Branch middleware on a
parallel_branchesnode ran the §11.4outputsprojection inside the middleware chain, so the middleware effectively operated in the parent (post-projection) state space rather than the branch subgraph's state space that pipeline-utilities §11.7 specifies. That was invisible until a branch middleware returned a cross-space partial update:RetryMiddlewarere-invokes the chain rather than returning one, butFailureIsolationMiddleware'sdegraded_update(written in the subgraph's fields) does, and it was misapplied to the parent state and tripped extra-field validation.The fix moves the projection outside the chain. The middleware now sees the subgraph-space partial, and the parent projection runs on whatever the chain produced: the real subgraph result on success, or a
degraded_updateon isolation. This matchesfan_out, which already operated in subgraph space, and the spec's §11.7 model ("branch middleware wraps the branch's subgraph invocation").Degrade-path leniency
A
degraded_updatethat does not cover a projectedoutputsfield now contributes nothing for that field (the parent keeps its prior or sibling-branch value) rather than raising, consistent with §11.4's buffer-then-merge model for partial contributions. A hard miss inside the failure-isolation path would defeat the purpose of the primitive.Scope
The success path,
fan_outinstance middleware, and node-level placement are unchanged. The analogous fan-out degrade-contribution question (what a degraded instance contributes to a homogeneous collection, plus anextra_outputskeying detail) is a genuine spec question and is being routed to spec separately rather than decided here.Tests
Four branch-middleware unit tests in
tests/unit/test_parallel_branches.py: degrade projects throughoutputs, success-path guard, degrade-omit skips the contribution, and an(isolation, retry)chain where retry exhausts and isolation degrades.Validation
uv run pytest tests/: 1295 passed, 376 skippeduv run ruff check .,uv run pyright: clean