Skip to content

fix(observed): correct enrichment and test-harness behavior - #712

Merged
Evgenii (Vaiz) merged 5 commits into
mainfrom
u/vaiz/2026/08/28/observed-api-fixes
Sep 2, 2026
Merged

fix(observed): correct enrichment and test-harness behavior#712
Evgenii (Vaiz) merged 5 commits into
mainfrom
u/vaiz/2026/08/28/observed-api-fixes

Conversation

@Vaiz

@Vaiz Evgenii (Vaiz) commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

🤖 Clawpilot here! Posted automatically by Clawpilot (an AI agent), not by a human. Please verify before acting.

Problem

Behavioral and ergonomics defects in observed and observed_testing, each confirmed against main. Unlike the sibling documentation pull requests, this one changes code.

What changes

Runtime

  • Slot::push, Sink::push_enrichment and EnrichmentTransfer installed an EnrichmentNode and touched every captured slot even when the enrichment layer was empty. An empty layer now returns a no-op guard and leaves the slot untouched. push_for takes the same early return, so an empty targeted layer allocates nothing either. Guard merge and drop semantics are unchanged; new tests cover the empty push, the merged-guard case, and the transfer path.
  • The early return lives in push and push_for, where the layer is still owned. The private EnrichmentTransfer::push_entries briefly repeated the check, but by then both callers had already returned, so that half of its guard could never be true; the mutation gate caught it as a surviving ||/&& mutant and it is removed.
  • Each guard now records which cost it avoids. The transfer guards run while the entries are still an owned Vec, so they also skip the shared entry slice. The Sink and Slot guards run after .enrich(...) has already built that slice, so they avoid composite fan-out, thread-local initialization and slot writes, but not the slice itself.

Test harness

  • ExpectedEvent stored the log expectation twice: once in severity: Option<Severity> and once in a separate expect_log flag. A captured event carries a severity exactly when it carries a log signal, so the flag added no valid state, and without_severity().log() was an expectation no event could satisfy. expect_log and the log() builder are removed, the log expectation now follows the severity, and the .log() call sites go with them. A fieldless-log test pins the behavior.
  • The type claimed partial matching while PartialEq compares body, dimensions, metric and disabled exactly. The documentation now says so, and scopes the claim: source location and metric instrument metadata are outside the comparison, and are reached through the CapturedEvent accessors. observed_testing is not published, so this reaches no external consumer.

Examples and benchmarks

  • support/otel.rs exported the emitting crate as code.namespace, an attribute OpenTelemetry has deprecated. The mapping is removed, the test that pinned it now asserts absence, and a comment beside the mapper records why file and line stay while the crate does not.
  • layered_app/token_issuer.rs hand-built DataClass::new("microsoft", "PublicNonPersonalData") instead of using the shared taxonomy constant, and recorded validation failures as an up-down counter carrying a constant failure_count: 1. A failure count only increases, so it is now a fieldless counter.
  • The emit_context benchmark group named operations its bodies do not perform: attach_emitter constructs and drops a Sink, and emit_event_no_emitter emits through Sink::noop(). The group, functions, identifiers and the module overview are renamed to match. This changes benchmark identifiers, so historical series under the old names do not carry over.

Withdrawn after review

Two changes were reverted in b9639949:

  • Default no-op EventProcessor::flush. A default body lets a processor that buffers telemetry silently skip flushing it, with no error and no compile-time signal. The repeated Ok(()) in stateless processors is the cheaper cost. flush stays a required method, and no processor code is touched by this pull request.
  • Documenting EnrichmentEntry. The type is public only because Rust cannot hide a type the derive expansion must reference. It is not a stable construction API, and #[derive(Enrichment)] remains the single supported route, so it is back to #[doc(hidden)].

Effects

  • An empty enrichment layer installs no chain node and writes no slot. Pushed through a captured Transfer it also costs no entry-slice allocation; .enrich(...) and .enrich_for(...) still build their Arc<[EnrichmentEntry]> before the guard is reached.
  • ExpectedEvent::new matches its documentation and ExpectedEvent::log no longer exists; assertions that relied on the previous default change meaning, and call sites that used .log() no longer compile.
  • Exported telemetry no longer carries the deprecated code.namespace attribute.
  • Benchmark identifiers in the sink-operations group change; comparisons against earlier runs need remapping.
  • No published crate changes its API. The observed public surface, including EventProcessor and the enrichment re-exports, is as it was on main. The removed ExpectedEvent::log is a behavioral and signature change to the unpublished observed_testing harness contract.

Validation

just anvil-fmt, just anvil-clippy, just anvil-readme-check, just anvil-spellcheck, cargo +1.96.1 check --workspace --all-targets, cargo +1.96.1 test -p observed -p observed_testing -p fetch_winhttp_impl, and a cargo mutants run scoped to crates/observed/src/enrichment/slot.rs all pass. The branch is rebased on main.

Copilot AI lite review requested due to automatic review settings August 28, 2026 14:27
@Vaiz
Evgenii (Vaiz) marked this pull request as draft August 28, 2026 14:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

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 fixes several behavioral and ergonomics issues across the observed runtime and the observed_testing harness, with additional cleanup to examples/benchmarks and OpenTelemetry log-record mapping to remove deprecated metadata.

Changes:

  • Optimize enrichment pushes so empty enrichment layers become true no-ops (avoiding unnecessary slot writes / guard work) and add coverage tests for the empty/merged/transfer cases.
  • Make EventProcessor::flush optional via a default no-op implementation and remove redundant flush implementations from examples.
  • Correct observed_testing::ExpectedEvent defaults/docs to match exact-match behavior and make ExpectedEvent::new actually default to expecting a log signal; update docs/tests accordingly.
  • Remove deprecated code.namespace emission from the OTel mapping and update the integration test assertion.
  • Rename the sink-related benchmark group and identifiers to match what the benchmarks actually do.

Reviewed changes

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

Show a summary per file
File Description
crates/observed/tests/otel_log_record.rs Updates OTel integration test to assert deprecated code.namespace is not exported.
crates/observed/src/sink/core.rs Returns a no-op enrichment guard early when the enrichment entry set is empty.
crates/observed/src/processing/processor.rs Provides a default no-op implementation for EventProcessor::flush.
crates/observed/src/enrichment/slot.rs Adds empty-layer short-circuits in enrichment push paths, introduces Guard::empty, and adds coverage tests.
crates/observed/src/enrichment/mod.rs Makes EnrichmentEntry publicly documented (removes #[doc(hidden)]).
crates/observed/src/enrichment/enrichment_trait.rs Adds a manual Enrichment implementation example to docs.
crates/observed/examples/*.rs Removes redundant flush implementations now covered by the trait default.
crates/observed/examples/support/otel.rs Removes deprecated code.namespace attribute mapping from OTel log records.
crates/observed/examples/layered_app/token_issuer.rs Uses shared taxonomy constant and switches failure metric to a monotonic counter.
crates/observed/benches/observed_benchmarks.rs Renames sink-operation benchmark group and identifiers for accuracy.
crates/observed_testing/src/mock_processor.rs Fixes ExpectedEvent defaults/docs to be exact-match and to expect log by default; adds a pinning test.
crates/observed_testing/src/lib.rs Updates crate docs example to remove redundant .log() usage.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread crates/observed/src/enrichment/slot.rs Outdated
@github-actions

github-actions Bot commented Aug 28, 2026

Copy link
Copy Markdown

⚠️ Potential breaking changes detected

cargo semver-checks flagged the following on this PR. This is informational -- breaking changes between commits are expected; the major-version bump happens at release time, not on every PR.

fetch_winhttp

     Cloning origin/main
    Building fetch_winhttp v0.1.1 (current)
       Built [   0.346s] (current)
     Parsing fetch_winhttp v0.1.1 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-fetch_winhttp-0_1_1-default-01666ec060466c14/target/doc/fetch_winhttp.json
(supported formats are v55, v56, v57)

fetch_winhttp_impl

     Cloning origin/main
    Building fetch_winhttp_impl v0.1.1 (current)
       Built [   0.359s] (current)
     Parsing fetch_winhttp_impl v0.1.1 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-fetch_winhttp_impl-0_1_1-default-f6bbf157605592e2/target/doc/fetch_winhttp_impl.json
(supported formats are v55, v56, v57)

observed

     Cloning origin/main
    Building observed v0.25.0 (current)
       Built [   5.392s] (current)
     Parsing observed v0.25.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-observed-0_25_0-default-d7b8c5ec6ce39049/target/doc/observed.json
(supported formats are v55, v56, v57)

observed_testing

     Cloning origin/main
    Building observed_testing v0.0.0 (current)
       Built [   5.779s] (current)
     Parsing observed_testing v0.0.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-observed_testing-0_0_0-default-01666ec060466c14/target/doc/observed_testing.json
(supported formats are v55, v56, v57)

observed_utils

     Cloning origin/main
    Building observed_utils v0.2.0 (current)
       Built [   5.751s] (current)
     Parsing observed_utils v0.2.0 (current)
error: unsupported rustdoc format v60 for file: /home/runner/work/oxidizer/oxidizer/target/semver-checks/local-observed_utils-0_2_0-default-01666ec060466c14/target/doc/observed_utils.json
(supported formats are v55, v56, v57)

Comment thread crates/observed/src/enrichment/enrichment_trait.rs Outdated
Comment thread crates/observed/src/processing/processor.rs Outdated
Comment thread crates/observed/src/sink/core.rs Outdated
@codecov

codecov Bot commented Aug 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.0%. Comparing base (7fc5cd5) to head (befe9d6).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #712   +/-   ##
=======================================
  Coverage   100.0%   100.0%           
=======================================
  Files         587      587           
  Lines       62997    63002    +5     
=======================================
+ Hits        62997    63002    +5     
Flag Coverage Δ
linux 64.3% <100.0%> (-35.7%) ⬇️
linux-arm 63.7% <100.0%> (-36.3%) ⬇️
scheduled ?
windows 63.5% <100.0%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Vaiz
Evgenii (Vaiz) force-pushed the u/vaiz/2026/08/28/observed-api-fixes branch from b963994 to 41920bf Compare August 28, 2026 15:36
@Vaiz
Evgenii (Vaiz) marked this pull request as ready for review August 28, 2026 16:31
Copilot AI review requested due to automatic review settings August 28, 2026 16:31

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated no new comments.

Comment thread crates/observed/examples/support/otel.rs
Comment thread crates/observed_testing/src/mock_processor.rs

@martin-kolinek martin-kolinek left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖: Approved.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[Copilot speaking]

Published 12 findings. No finding follows up on an existing discussion thread.

See diagnostics
Diagnostic Value
Cache Hit

Comment thread crates/observed/src/sink/core.rs
Comment thread crates/observed/src/enrichment/slot.rs
Comment thread crates/observed/benches/observed_benchmarks.rs
Comment thread crates/observed/src/enrichment/slot.rs
Comment thread crates/observed/src/enrichment/slot.rs
Comment thread crates/observed_testing/src/mock_processor.rs Outdated
Comment thread crates/observed_testing/src/mock_processor.rs Outdated
Comment thread crates/observed_testing/src/mock_processor.rs Outdated
Comment thread crates/observed_testing/src/mock_processor.rs Outdated
Comment thread crates/observed_testing/src/mock_processor.rs
@Vaiz Evgenii (Vaiz) changed the title fix(observed): correct enrichment, processor and test-harness behavior fix(observed): correct enrichment and test-harness behavior Sep 1, 2026
Copilot AI review requested due to automatic review settings September 1, 2026 15:37
@Vaiz
Evgenii (Vaiz) force-pushed the u/vaiz/2026/08/28/observed-api-fixes branch from 41920bf to 5c5ae97 Compare September 1, 2026 15:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 20 out of 20 changed files in this pull request and generated 2 comments.

Comment thread crates/observed_testing/tests/basic_emission.rs Outdated
Comment thread crates/observed_testing/tests/metric_routing.rs Outdated
Evgenii (Vaiz) and others added 5 commits September 2, 2026 09:35
Rename sink benchmarks to match measured operations
Use shared taxonomy and monotonic validation failure counter
Skip empty enrichment layers before mutating slots
Document manual enrichment entries and default no-op flush
Correct expected-event log defaults and OTel source attributes

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Roll back the two changes the crate owner rejected:

- EventProcessor::flush goes back to a required method. A default no-op
  lets a processor that buffers telemetry silently skip flushing it, and
  losing telemetry is worse than the repeated Ok(()).
- EnrichmentEntry returns to #[doc(hidden)] and the manual Enrichment
  implementation example is removed. The type is public only because
  Rust has no way to hide it from the derive expansion; the derive macro
  remains the single supported way to build an enrichment.

Also drop the "composites with zero children" note from
Sink::push_enrichment, which described an internal dispatch detail, and
give EnrichmentTransfer::push_for the same empty-layer early return as
push so a targeted layer with no entries allocates nothing either.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Both callers of `EnrichmentTransfer::push_entries` already return early
when the enrichment layer is empty, so the `entries.is_empty()` half of
its guard can never be true. It decided nothing, which the mutation gate
caught: replacing `||` with `&&` survived on all three platforms.

The empty-layer no-op is unchanged — it is enforced where the layer is
still owned, in `push` and `push_for`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
`ExpectedEvent` held the log expectation twice: `severity: Option<Severity>`
and a separate `expect_log` flag. A captured event carries a severity exactly
when it carries a log signal, so the flag could express states no event can
satisfy - `without_severity().log()` was unsatisfiable. Removes `expect_log`
and the `log()` builder, drops the now-redundant log comparison, and updates
the call sites.

Also addresses review feedback:

- Scopes the "exact-match" claim to what `ExpectedEvent` models, and says
  source location and metric instrument metadata are outside the comparison.
- Uses "signal" and "flag" consistently with the crate's event model.
- Notes at each empty-enrichment guard which cost it avoids, and that the
  entry slice is allocated by the caller of `Sink::push_enrichment`.
- Records beside the OTel mapper why the emitting crate is not exported while
  file and line are.
- Fixes the benchmark overview bullet left behind by the group rename.
- Groups the `FieldlessLog` fixture with the imports and imports `SinkId`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A trailing comma after the second argument of `assert_eq!` reads like an
empty custom-message argument. Both call sites take the two-argument form,
so the comma is removed.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 2, 2026 08:35
@Vaiz
Evgenii (Vaiz) force-pushed the u/vaiz/2026/08/28/observed-api-fixes branch from 5c5ae97 to befe9d6 Compare September 2, 2026 08:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟢 Approval recommended

The changes are targeted, internally consistent (empty-enrichment fast paths + updated test harness semantics), and are accompanied by updated tests covering the new behavior.

Review details
  • Files reviewed: 20/20 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@Vaiz
Evgenii (Vaiz) merged commit c6addf0 into main Sep 2, 2026
52 checks passed
@Vaiz
Evgenii (Vaiz) deleted the u/vaiz/2026/08/28/observed-api-fixes branch September 2, 2026 12:35
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.

6 participants