Skip to content

graph: phase 6.1 PR-C.1 — proposal 0012 + v0.9.0#25

Merged
chris-colinsky merged 1 commit into
mainfrom
feature/phase-6-1-pr-c1-routing-error-swap
May 9, 2026
Merged

graph: phase 6.1 PR-C.1 — proposal 0012 + v0.9.0#25
chris-colinsky merged 1 commit into
mainfrom
feature/phase-6-1-pr-c1-routing-error-swap

Conversation

@chris-colinsky
Copy link
Copy Markdown
Member

Summary

PR-C.1 of Phase 6.1: 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.

The existing observer _handle_completed ERROR-mapping path picks
up the new error categories automatically — no observer change
required
by the swap.

Mechanism

New _StepResult(state, finalize_completed) returned by the
per-step dispatchers:

  • Failure-path dispatches stay inline. node_exception /
    reducer_error / state_validation_error fire from inside
    innermost before the raise propagates — those errors short-
    circuit before edge eval can run, preserving §3's "before the
    failure propagates to the caller" MUST.
  • Success-path dispatch deferred via closure. 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).
  • Subgraph wrappers + middleware short-circuits. Both return
    _StepResult(state, _no_op_finalize): edge errors after a unit
    that emitted no started/completed pair propagate silently per §4
    without an associated observer event. Spec-confirmed in coord
    thread; the inline _StepResult docstring + the
    _step_subgraph_node docstring anchor the contract for future
    readers.

Submodule + version pins

Three-place sync to v0.9.0 per CLAUDE.md:

  • openarmature-spec submodule pointer → 78fe9b4 (v0.9.0).
  • tool.openarmature.spec_version in pyproject.toml.
  • __spec_version__ in src/openarmature/__init__.py.
  • Drift-guard literal in tests/test_smoke.py.

OTelObserver.spec_version updates automatically via PR-A's
_read_spec_version — every invocation span now reports
openarmature.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_event
    • test_edge_exception_lands_on_preceding_node_completed_event

    Both verify exactly one started + one completed pair for the
    preceding node, completed has error populated +
    post_state absent + the right category, downstream node
    never fires events.

  • Fixture 020 (graph-engine/020-observer-edge-error-events)
    driven via custom _run_fixture_020 in test_conformance.py.
    The fixture's edge-condition callable: directives
    (state_field_read / edge_raises) don't match the
    adapter DSL's if_field/equals/then/else shape; the driver
    translates 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_FIXTURES and driven via
    _run_fixture_004 in test_observability.py. Verifies the
    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.

  • Expectations schema extension:
    GraphEngineExpected.invariants field added so fixture 020's
    invariants: block parses cleanly.

Test plan

  • uv run pytest -q — 398 passed (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).
  • uv run pyright — 0 errors.
  • uv run ruff check . && uv run ruff format — clean.
  • All existing fixtures stay green under the swap:
    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

  • PR-A (concurrency-safe observer) ✅ merged
  • PR-B (Logs SDK migration + filter fix) ✅ merged
  • PR-C (4 conformance fixtures) ✅ merged
  • PR-C.1 (this PR): proposal 0012 + fixtures 004/020
  • PR-C.2 (fan-out per-instance dispatch + fixture 006) — pending; no spec gate
  • PR-C.3 (observer prepare_sync + fixture 010) — pending; no spec gate

PR-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.

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.
Copilot AI review requested due to automatic review settings May 9, 2026 20:22
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

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-path completed dispatch 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.0 across 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.

@chris-colinsky chris-colinsky merged commit 5775fc5 into main May 9, 2026
9 checks passed
@chris-colinsky chris-colinsky deleted the feature/phase-6-1-pr-c1-routing-error-swap branch May 9, 2026 20:28
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.

2 participants