Skip to content

Fix observer augmentation scoping, wire 039 case 3#193

Merged
chris-colinsky merged 3 commits into
mainfrom
feature/wire-039-nested-lineage
Jun 25, 2026
Merged

Fix observer augmentation scoping, wire 039 case 3#193
chris-colinsky merged 3 commits into
mainfrom
feature/wire-039-nested-lineage

Conversation

@chris-colinsky

Copy link
Copy Markdown
Member

What

Two observability observer corrections plus the first slice of conformance fixture 039 (nested-lineage augmentation, proposal 0045).

  1. Augmentation MUST-NOT (both observers). The augmentation walk's same-namespace arm now skips shared-parent fan-out / parallel-branches NODE spans. A key set via set_invocation_metadata inside a fan-out instance or a parallel-branches branch was incorrectly applied to the shared fork NODE when the augmenting context executed at that node's own namespace, in addition to the per-instance / per-branch dispatch span where it belongs. This violated observability 3.4; the strict-ancestor arm already skipped shared parents. The bug was pre-existing and cross-observer (029/030 carried it latently, masked by the subset-based conformance asserter).

  2. Langfuse nested fan-out dispatch. The Langfuse per-instance dispatch synthesis and parent resolution are now prefix-general, so a fan-out nested below the top namespace level (inside a serial subgraph wrapper) gets its dispatch observation synthesized and its inner observations parented under it, matching the OTel observer. Also closes the nested-dispatch limitation flagged on Synthesize Langfuse per-branch dispatch spans #190.

  3. 039 case 3 wired. Case 3 (a fan-out inside a serial subgraph wrapper) runs through a dedicated hand-built runner, since the generic adapter cannot construct nested fan-out graphs, and is asserted against the fixture's expected trace. A ContextVar-scoped negative check enforces 0045's MUST-NOT (an augmented key absent from an observation's expected metadata must be absent in the actual), which the subset matcher alone cannot catch.

Temporarily deferred

039 cases 1 (fan-out in fan-out) and 2 (parallel-branches in fan-out) are deferred via _DEFERRED_CASES. Both need one shared observer fix: dispatch keys do not encode the enclosing fan-out instance, so a dispatch inside an outer fan-out instance collides across outer instances. That fix is its own focused effort (tracked) and warrants spec coordination. 039's suite result therefore covers case 3 only.

Test plan

  • uv run pytest tests/ -> 1469 passed, 410 skipped.
  • 039 case 3 passes; the negative MUST-NOT assertion was verified to fail when the observer fix is reverted.
  • ruff + pyright clean.

Two observer corrections surfaced while wiring the nested-lineage
conformance fixture (proposal 0045):

- The augmentation walk's same-namespace arm now skips shared-parent
  fan-out / parallel-branches NODE spans, in both the OTel and Langfuse
  observers. A key set via set_invocation_metadata inside a fan-out
  instance or branch was wrongly applied to the shared fork NODE when
  the augmenting context executed at that node's own namespace, in
  addition to the dispatch span where it belongs. This violated the
  observability 3.4 MUST-NOT; the strict-ancestor arm already skipped
  shared parents. Per-instance / per-branch dispatch spans and lineage
  ancestors are unaffected.

- The Langfuse per-instance fan-out dispatch synthesis and parent
  resolution are now prefix-general, so a fan-out nested below the top
  namespace level (inside a serial subgraph wrapper) gets its dispatch
  observation synthesized and its inner observations parented under it,
  matching the OTel observer.
Activate the nested-lineage fixture (039) in the Langfuse conformance
runner. Case 3 (a fan-out inside a serial subgraph wrapper) is built by
a dedicated hand-built runner -- the generic adapter cannot construct
nested fan-out graphs -- and asserted against the fixture's expected
trace. A ContextVar-scoped negative check enforces proposal 0045's
MUST-NOT: an augmented key absent from an observation's expected
metadata must be absent in the actual, which the subset matcher alone
cannot catch.

Cases 1 and 2 are temporarily deferred via _DEFERRED_CASES. Both need a
shared observer fix: dispatch keys do not encode the enclosing fan-out
instance, so a dispatch inside an outer instance collides across
instances. Tracked as a separate effort.
Copilot AI review requested due to automatic review settings June 25, 2026 22:28

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR updates both the OTel and Langfuse observability observers to correct metadata augmentation scoping (proposal 0045) and extends the Langfuse observer’s dispatch synthesis/parenting so nested fan-outs under serial wrappers behave like the OTel observer. It also wires conformance fixture 039 case 3 through a dedicated hand-built runner and strengthens the Langfuse conformance assertions with a MUST-NOT “no leaked augmented keys” check.

Changes:

  • Fix augmentation scoping so shared-parent fan-out / parallel-branches NODE spans are not augmented when the augmenter executes at the shared NODE namespace (OTel + Langfuse).
  • Make Langfuse fan-out instance dispatch synthesis and parent resolution prefix-general to support nested fan-outs (e.g., under serial subgraph wrappers).
  • Add fixture 039 case 3 support to the Langfuse conformance harness, including a negative MUST-NOT assertion for augmented metadata leakage.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
tests/conformance/test_observability_langfuse.py Wires fixture 039 case 3 via a hand-built graph runner and adds ContextVar-scoped MUST-NOT checks for augmentation leakage.
src/openarmature/observability/otel/observer.py Adjusts augmentation target collection to skip shared-parent fan-out/pb NODEs even when ns == aug_ns.
src/openarmature/observability/langfuse/observer.py Mirrors augmentation scoping fix; generalizes fan-out dispatch synthesis and parent resolution to work at arbitrary namespace depths.
CHANGELOG.md Documents the observer fixes and nested-dispatch Langfuse behavior change.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/openarmature/observability/langfuse/observer.py
From CoPilot review of #193: the prefix-general change over-generalized
the detached subgraph / fan-out synthesis arms to any depth, but
_trace_id_for still routes detached events by namespace[:1]. A nested
detached fan-out would partially detach -- its dispatch in the new Trace
but inner nodes in the main one. Re-gate both detached arms to depth ==
1; only the non-detached fan-out arm and the dedup need to be
prefix-general (what case 3 exercises). A nested detached fan-out now
gets no synthesis, consistent with the prior behavior, until the
deferred nested-dispatch-keying fix generalizes _trace_id_for too.
@chris-colinsky chris-colinsky merged commit 3d29f00 into main Jun 25, 2026
6 checks passed
@chris-colinsky chris-colinsky deleted the feature/wire-039-nested-lineage branch June 25, 2026 23:02
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