Fix failure-isolation event attempt_index#155
Merged
Conversation
FailureIsolatedEvent.attempt_index now reports the wrapped node's final / exhausting attempt (spec ruling), not the post-reset baseline. An isolation-scoped terminal-attempt ContextVar carries it: RetryMiddleware records the final attempt on give-up, and FailureIsolationMiddleware reads it (falling back to the live attempt index when no retry exhausted). Un-defers conformance fixture 061.
There was a problem hiding this comment.
Pull request overview
Fixes FailureIsolatedEvent.attempt_index so that when FailureIsolationMiddleware wraps RetryMiddleware, the event reports the wrapped node’s final/exhausting retry attempt index (per proposal 0050 §6.3), rather than the post-reset baseline.
Changes:
- Add an isolation-scoped “terminal attempt index”
ContextVarin correlation primitives and plumb it through failure isolation event emission. - Record the terminal attempt in
RetryMiddlewareon give-up so outer failure isolation can read it after retry resetsattempt_index. - Un-defer and extend tests to assert the corrected
attempt_indexbehavior (including conformance fixture coverage).
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit/test_failure_isolation_middleware.py | Adds an observer capture and assertion that the failure-isolation event reports the exhausting attempt index. |
| tests/conformance/test_pipeline_utilities.py | Removes the deferral for the failure-isolation three-piece composition fixture. |
| src/openarmature/observability/correlation.py | Introduces terminal-attempt ContextVar helpers and exports them for use by retry/isolation middleware. |
| src/openarmature/graph/middleware/retry.py | Records the terminal attempt index on give-up before re-raising. |
| src/openarmature/graph/middleware/failure_isolation.py | Establishes/reset terminal-attempt scope and uses it when emitting FailureIsolatedEvent. |
| conformance.toml | Updates proposal 0050 “partial” commentary to reflect passing failure-isolation fixtures and the new attempt-index behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Rename current_terminal_attempt_index to the underscore-prefixed _current_terminal_attempt_index (it is internal; only FailureIsolationMiddleware reads it) and move it to the internal __all__ section. Reword the ContextVar + scope comments: a retry recording without an enclosing isolation scope does leave a non-None ambient value, but it is never observed because the sole reader shadows it with None on entry.
This was referenced Jun 15, 2026
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
Fix
FailureIsolatedEvent.attempt_indexto report the wrapped node's final / exhausting attempt whenFailureIsolationMiddlewarewrapsRetryMiddleware, per spec's ruling on the attempt-index reconciliation. Previously it reported the post-reset baseline (0), becauseRetryMiddlewareresets the attempt-index ContextVar in its per-iterationfinallyas the exhausted exception unwinds, before the outer isolation middleware catches it.This is the last v0.14.0 build piece tied to the failure-isolation conformance work; it un-defers fixture 061.
How
An isolation-scoped
_terminal_attempt_indexContextVar carries the final attempt past retry's reset:FailureIsolationMiddleware.__call__establishes a fresh scope (Noneon entry, reset in afinally).RetryMiddleware, on give-up (exhaustion or a non-transient error), records the finalattemptinto that scope before re-raising._emit_eventreads the recorded terminal index, falling back to the live attempt index when no retry exhausted (so the no-retry fixtures stay at 0).The blast radius is limited to isolation's attempt sourcing: retry's control flow, the live per-attempt scoping, and the nested-retry token stack are all unchanged.
Spec
The contract was already in the spec (§6.3 lineage correlation + fixture 061); the prior impl comment claiming the baseline was "spec-confirmed" was unsubstantiated and is removed. No spec change or proposal was needed.
Validation
attempt_index == 2(final of attempts 0/1/2); the no-retry fixtures (058-063) confirm the fallback to 0.