graph: phase 6.1 PR-C.1 — proposal 0012 + v0.9.0#25
Merged
Conversation
Implement spec graph-engine v0.9.0 (proposal 0012): the engine's ``completed`` observer event for the just-completed node now dispatches AFTER edge evaluation completes, not between merge and edge eval. Edge-resolution failures (``routing_error``, ``edge_exception``) populate the ``error`` field of the preceding node's ``completed`` event, sharing the started/completed pair rather than producing a separate event pair (per §3 step 3 + §6 revisions in proposal 0012). The existing observer ``_handle_completed`` ERROR-mapping path picks up the new error categories automatically; no observer change required. Mechanism: introduce ``_StepResult(state, finalize_completed)`` returned by the per-step dispatchers. Failure-path dispatches (``node_exception`` / ``reducer_error`` / ``state_validation_error``) stay inline inside ``innermost`` — those errors short-circuit before edge eval can run, preserving §3's "before the failure propagates to the caller" MUST. On success, ``innermost`` stores ``(attempt_index, pre_state, merged)`` for the FINAL successful attempt; the outer scope builds a ``finalize_completed`` closure that ``_invoke`` calls AFTER edge eval, passing either ``None`` (success → dispatch completed with ``post_state``) or the edge error (dispatch completed with ``error`` populated). ``_step_subgraph_node`` is unchanged in behavior — the wrapper is transparent per fixture 013. Returns ``_StepResult(state, _no_op_finalize)`` so edge errors after a transparent wrapper propagate silently per §4 without an observer event pair (proposal 0012's "share the preceding node's pair" MUST is conditional on the pair existing). Same for middleware that short-circuits without invoking ``next``. Inline docstring at the wrapper's _StepResult return anchors the contract for future readers. Pin sites bumped to v0.9.0 (three-place sync per CLAUDE.md): ``openarmature-spec`` submodule pointer to ``78fe9b4``, ``pyproject.toml`` ``tool.openarmature.spec_version``, ``src/openarmature/__init__.py`` ``__spec_version__``, ``tests/test_smoke.py`` drift-guard literal. The ``OTelObserver.spec_version`` attribute updates automatically via ``_read_spec_version`` (per PR-A). Tests: - New unit tests in ``tests/unit/test_runtime_errors.py`` verify the new event shape: exactly one started + one completed for the preceding node, completed has ``error`` populated and ``post_state`` absent, downstream node never fires events. - ``tests/conformance/test_conformance.py`` gains a ``_run_fixture_020`` driver for the new graph-engine fixture ``020-observer-edge-error-events`` (two sub-cases: routing_error and edge_exception). The fixture's edge-condition ``callable:`` directives don't match the adapter DSL; the driver translates the semantics directly. Cases-shape support added to the generic harness for future cases-shape graph-engine fixtures. - ``tests/conformance/test_observability.py`` removes ``004-otel-routing-error-attribution`` from ``_DEFERRED_FIXTURES`` (was deferred through Phase 6.1 PR-C pending this proposal) and adds a ``_run_fixture_004`` driver. Verifies preceding node's span ends ERROR with ``status_description == "routing_error"`` + recorded exception event + ``openarmature.error.category`` attribute, no separate edge-function span, invocation span propagates ERROR. - ``tests/conformance/harness/expectations.py``: ``GraphEngineExpected`` gains an ``invariants`` field so fixture 020's ``invariants:`` block parses cleanly. 398 tests pass (was 392; net +6 from new fixture drivers + unit tests + fixture-020 sub-cases that previously errored). 4 skipped (was 5; fixture 004 now driven). Pyright clean. PR-C.2 (fan-out per-instance dispatch + fixture 006) and PR-C.3 (observer ``prepare_sync`` + fixture 010) sit independently behind their respective architectural pieces; land in either order after this PR ships. Phase 6.1 closes when all three merge.
There was a problem hiding this comment.
Pull request overview
Implements graph-engine spec v0.9.0 (proposal 0012) by changing observer event timing so a node’s completed event is dispatched after edge evaluation, and edge-resolution failures (routing_error, edge_exception) are attributed to the preceding node’s completed.error instead of emitting a separate event pair.
Changes:
- Introduces a
_StepResult(state, finalize_completed)mechanism to defer success-pathcompleteddispatch until after edge evaluation. - Updates conformance/observability harnesses and adds new tests to validate edge-error attribution and fixture support (
004,020). - Bumps pinned spec version strings to
0.9.0across package metadata and smoke tests.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
src/openarmature/graph/compiled.py |
Defers completed dispatch via _StepResult.finalize_completed and attributes edge errors to the preceding node’s completed event. |
tests/unit/test_runtime_errors.py |
Adds unit tests asserting routing/edge errors land on the preceding node’s completed.error. |
tests/conformance/test_conformance.py |
Adds cases: support and a custom driver for fixture 020 edge-error observer semantics. |
tests/conformance/test_observability.py |
Enables and drives fixture 004 to validate OTel routing-error attribution behavior. |
tests/conformance/harness/expectations.py |
Extends expected schema with an invariants field for fixture parsing. |
pyproject.toml |
Updates tool.openarmature.spec_version to 0.9.0. |
src/openarmature/__init__.py |
Updates __spec_version__ to 0.9.0. |
tests/test_smoke.py |
Updates the spec version drift-guard assertion to 0.9.0. |
💡 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
PR-C.1 of Phase 6.1: implement spec graph-engine v0.9.0 (proposal
0012). The engine's
completedobserver event for thejust-completed node now dispatches after edge evaluation
completes, not between merge and edge eval. Edge-resolution failures
(
routing_error,edge_exception) populate theerrorfieldof the preceding node's
completedevent, sharing thestarted/completed pair rather than producing a separate event pair.
The existing observer
_handle_completedERROR-mapping path picksup the new error categories automatically — no observer change
required by the swap.
Mechanism
New
_StepResult(state, finalize_completed)returned by theper-step dispatchers:
node_exception/reducer_error/state_validation_errorfire from insideinnermostbefore the raise propagates — those errors short-circuit before edge eval can run, preserving §3's "before the
failure propagates to the caller" MUST.
innermoststores(attempt_index, pre_state, merged)forthe FINAL successful attempt. The outer scope builds a
finalize_completedclosure that_invokecalls AFTER edgeeval, passing either
None(success → dispatch completed withpost_state) or the edge error (dispatch completed witherrorpopulated)._StepResult(state, _no_op_finalize): edge errors after a unitthat emitted no started/completed pair propagate silently per §4
without an associated observer event. Spec-confirmed in coord
thread; the inline
_StepResultdocstring + the_step_subgraph_nodedocstring anchor the contract for futurereaders.
Submodule + version pins
Three-place sync to v0.9.0 per CLAUDE.md:
openarmature-specsubmodule pointer →78fe9b4(v0.9.0).tool.openarmature.spec_versioninpyproject.toml.__spec_version__insrc/openarmature/__init__.py.tests/test_smoke.py.OTelObserver.spec_versionupdates automatically via PR-A's_read_spec_version— every invocation span now reportsopenarmature.graph.spec_version = "0.9.0".Tests
New unit tests in
tests/unit/test_runtime_errors.py:test_routing_error_lands_on_preceding_node_completed_eventtest_edge_exception_lands_on_preceding_node_completed_eventBoth verify exactly one started + one completed pair for the
preceding node, completed has
errorpopulated +post_stateabsent + the right category, downstream nodenever fires events.
Fixture 020 (
graph-engine/020-observer-edge-error-events)driven via custom
_run_fixture_020intest_conformance.py.The fixture's edge-condition
callable:directives(
state_field_read/edge_raises) don't match theadapter DSL's
if_field/equals/then/elseshape; the drivertranslates the semantics directly to closure-based edge
functions. Cases-shape support added to the generic harness
for future cases-shape graph-engine fixtures.
Fixture 004 (
observability/004-otel-routing-error-attribution)removed from
_DEFERRED_FIXTURESand driven via_run_fixture_004intest_observability.py. Verifies thepreceding node's span ends ERROR with
status_description == "routing_error"+ recorded exceptionevent +
openarmature.error.categoryattribute, no separateedge-function span, invocation span propagates ERROR.
Expectations schema extension:
GraphEngineExpected.invariantsfield added so fixture 020'sinvariants:block parses cleanly.Test plan
uv run pytest -q— 398 passed (was 392; net +6 fromnew fixture drivers + unit tests + fixture-020 sub-cases that
previously errored), 4 skipped (was 5; fixture 004 now driven).
uv run pyright— 0 errors.uv run ruff check . && uv run ruff format— clean.graph-engine/008-routing-error.yaml,graph-engine/014-observer-error-event.yaml, fixture 013(transparent subgraph wrapper), the four PR-C-driven
observability fixtures + the five Phase 6.0 ones, Phase 5
fixture-031 span assertions.
Phase 6.1 status after this PR
prepare_sync+ fixture 010) — pending; no spec gatePR-C.2 and PR-C.3 are mutually independent and can land in either
order after this PR ships. Phase 6.1 closes when all three merge.