feat: introduce the observed_utils crate - #677
Conversation
✅ Version increments look sufficient
|
7fcb040 to
f5a4841
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #677 +/- ##
=======================================
Coverage 100.0% 100.0%
=======================================
Files 554 557 +3
Lines 61509 61583 +74
=======================================
+ Hits 61509 61583 +74
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
c4a293c to
9a61d4b
Compare
There was a problem hiding this comment.
Pull request overview
Introduces a new observed_utils crate that centralizes OpenTelemetry conversion helpers and related utilities for observed ecosystem consumers, and wires it into the workspace (including observed_testing) while cleaning up cargo-deny advisory configuration.
Changes:
- Added the new
crates/observed_utilscrate (OTel conversions,SensitiveSlice,format_any_value,metric_number_of, examples/tests/docs scaffolding). - Updated workspace and downstream crates to depend on and reference
observed_utils(replacing priorobserved_helpers/local copies). - Refreshed dependency/advisory state (lockfile bump for
h2, removed staledeny.tomlignores, added.spellingentries).
Reviewed changes
Copilot reviewed 22 out of 23 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| deny.toml | Removes stale advisory ignore entries. |
| crates/observed/FEATURES.md | Updates documentation reference to observed_utils::metric_number_of. |
| crates/observed/examples/support/otel.rs | Updates doc comments to reference observed_utils conversions. |
| crates/observed_utils/src/sensitive_slice.rs | Adds SensitiveSlice implementation plus unit tests. |
| crates/observed_utils/src/otel.rs | Adds Value/Text/Severity → OpenTelemetry conversion helpers plus tests. |
| crates/observed_utils/src/metric_number.rs | Provides metric_number_of (including Value::U64) plus tests. |
| crates/observed_utils/src/lib.rs | New crate root, docs, lint config, and public re-exports. |
| crates/observed_utils/src/format_any_value.rs | Adds format_any_value Display adapter for AnyValue plus tests. |
| crates/observed_utils/README.md | Generated crate README for crates.io/docs.rs. |
| crates/observed_utils/logo.png | Adds crate logo asset (Git LFS pointer). |
| crates/observed_utils/favicon.ico | Adds crate favicon asset (Git LFS pointer). |
| crates/observed_utils/examples/support/taxonomy.rs | Example-only taxonomy support module for classification/redaction demos. |
| crates/observed_utils/examples/sensitive_collection.rs | Example demonstrating SensitiveSlice usage in events and processors. |
| crates/observed_utils/CHANGELOG.md | Adds initial changelog for the new crate. |
| crates/observed_utils/Cargo.toml | Defines the new package metadata and dependencies (workspace-inherited). |
| crates/observed_testing/tests/derive_enrichment.rs | Switches metric extraction call site to observed_utils::metric_number_of. |
| crates/observed_testing/tests/coverage_dyn_sink.rs | Switches metric extraction call sites to observed_utils::metric_number_of. |
| crates/observed_testing/src/mock_processor.rs | Switches internal metric extraction to observed_utils::metric_number_of. |
| crates/observed_testing/src/lib.rs | Removes local metric_number module export now sourced from observed_utils. |
| crates/observed_testing/Cargo.toml | Adds observed_utils dependency. |
| Cargo.toml | Adds workspace dependency entries for arrayvec, const-hex, and observed_utils. |
| Cargo.lock | Bumps h2 and adds new dependency resolutions for the new crate and deps. |
| .spelling | Adds new terms used by the moved/added docs. |
💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
8c4ed0b to
74417e9
Compare
| @@ -0,0 +1,37 @@ | |||
| // Copyright (c) Microsoft Corporation. | |||
There was a problem hiding this comment.
Is there a document or diagram somewhere that explains the intended end state of crates and dependencies in the observed space? Who is supposed to depend on observed_utils? I would really like to prevent having applications have to depend on something called "observed_utils" - for me, a utils crate is something that should generally be buried in the dependency tree.
There was a problem hiding this comment.
generally speaking, I put here all common functionality that often is required by observed users, but doesn't have place in observed crate itself due to increased risk of breaking changes
There was a problem hiding this comment.
I expected that certain percent of crates using observed, also use observed_util instead of reimplementing similar functionality
There was a problem hiding this comment.
If this is "ordinary" functionality, it sounds like we should stabilize it, so it could go in main crate. For "ultra-stable" things, maybe the "re-export critical types from observed_core that you never touch" pattern helps avoid accidental breakage? Just speaking in generalizations, I guess it is not something that has a universal pattern we have worked out so far but in general if we expect most users to use a _utils crate it stops having value on its own IMO and might as well get merged into the top-level crate. But probably any detailed analysis needs to be case by case - I know little about observed structure and what is needed by which users. Maybe opportunity for design discussion at future arch meeting?
There was a problem hiding this comment.
I would call it experimental. Let's see what people actually use, and where to put it. Right now it solves the problem of reinventing the wheel, and it's good enough for me.
for example, I also plan to add info! shortcut here that defines event struct inline, so you don't need to declare a separate struct for simple events.
Sander Saares (sandersaares)
left a comment
There was a problem hiding this comment.
[Copilot speaking]
Published 4 findings. No finding follows up on an existing discussion thread.
See diagnostics
| Diagnostic | Value |
|---|---|
| Cache | Miss |
74417e9 to
ec533c6
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 23 changed files in this pull request and generated no new comments.
Suppressed comments (2)
crates/observed_utils/src/sensitive_slice.rs:82
- The constructor requires
T: RedactedDisplay + Sync, but the doc comment only mentionsRedactedDisplay. Please document theSyncrequirement explicitly so callers aren't surprised by the additional bound.
/// Collects items from any iterator of references to [`RedactedDisplay`]
/// implementers.
crates/observed_utils/src/sensitive_slice.rs:22
- The docs say this stores
&dyn RedactedDisplayfat pointers, but the actual stored type is&(dyn RedactedDisplay + Sync). ThisSyncbound is part of the public contract and should be documented (or the bound removed if it's not required).
This issue also appears on line 81 of the same file.
/// to `N` `&dyn RedactedDisplay` fat pointers in an inline array. This makes
|
🤖 Clawpilot here! Posted automatically by Clawpilot (an AI agent), not by a human. Please verify before acting. Re the two suppressed comments in review #4971552344, both about Both are correct and they are the same gap seen from two angles. The type documentation said it stores Documented on both the type and the constructor rather than removing the bound. The bound is load-bearing: Documentation only — no behaviour change, and the generated README is unaffected since these docs live on the type rather than in |
Move `observed_helpers` (0.24.0) from the internal ox-sdk monorepo into this repository, following `observed`, `observed_macros` and `observed_testing`. It is the optional companion for consumers that export typed events to OpenTelemetry. `observed` itself no longer depends on `opentelemetry`, so an exporter has to convert values on its own. That conversion lives here once rather than being re-derived in every exporter: `any_value_of`, `otel_value_of` and `otel_severity_of`. The crate also carries `format_any_value`, which renders an `AnyValue` in human-readable form instead of its `Debug` shape, and `SensitiveSlice`, a bounded, type-erased collection of `RedactedDisplay` references that renders without allocating. Changes made during the move: - `any_value_of` and `otel_value_of` handle `Value::U64`. The variant was added in 0.24 after this crate was written, so a `u64` dimension used to fall through to the `#[non_exhaustive]` guard arm and be exported as a debug string. Values past `i64::MAX` still export as a decimal string, because neither `AnyValue` nor `opentelemetry::Value` has an unsigned variant. - `metric_number_of` returns to this crate. It was parked in `observed_testing` because that was its only consumer here; `observed_testing` now uses it from this crate instead of keeping a second copy. - The example uses the `#[path = "support/taxonomy.rs"]` stand-in taxonomy the `observed` examples use, instead of the ox-sdk-only `microsoft_enterprise_data_taxonomy` crate, and `expect` with a message instead of `unwrap`, to satisfy this workspace's `clippy::unwrap_used` policy. - The `#![doc(html_logo_url)]` and `#![cfg_attr(coverage_nightly, ...)]` attributes are enabled, matching the convention of the other crates here. - `arrayvec` and `const-hex` are added to `[workspace.dependencies]`. Effects: - A crate that emits typed events and exports them no longer has to write its own `Value` mapping, and the mapping is governed by this repository's gates. - `observed_testing` gains a dependency on `observed_helpers`; the two copies of `metric_number_of` can no longer drift apart. - Telemetry helper changes no longer need a manual transfer between two repositories. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- spellcheck: use the repo's `implementers` spelling and the US `signaled` in `SensitiveSlice::new` docs (both were rejected by cargo-spellcheck). - external types: approve `opentelemetry::logs::record::Severity`, the canonical path cargo-check-external-types reports, instead of the `opentelemetry::logs::Severity` re-export path. - coverage: mark every `mod tests` with `#[cfg_attr(coverage_nightly, coverage(off))]`, matching the convention in `observed`. This also consumes `feature(coverage_attribute)`, which was declared but unused and failed the coverage build under `-D warnings`. - coverage/mutants: cover the paths no test reached - `AnyValue::Map` rendering (which also kills the three surviving `i > 0` mutants in `DisplayAnyValue::fmt`), `metric_number_of` on `Value::U64`, `otel_value_of` on `Bool`/`F64`/`BoolArray`/`F64Array`, `Text::Shared`, and the `Debug`/`Clone` impls of `SensitiveSlice`. The four remaining uncovered lines are the intentionally unreachable wildcard arms that guard the foreign `#[non_exhaustive]` enums; they are kept deliberately and are not removed to satisfy the gate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… coverage The coverage gate reported observed_helpers at 96.8% against the 100% per-package threshold. The four uncovered lines are the wildcard arms that guard the foreign `#[non_exhaustive]` enums (`Value`, `Text`, `AnyValue`): no variant that exists today can reach them, and llvm-cov counts an arm that is never taken as an uncovered line. The guards stay - deleting them would turn an upstream variant addition into a compile error here, or silently drop the value. Instead the three dispatch functions carry `#[cfg_attr(coverage_nightly, coverage(off))]`, and the logic they delegate to is pulled out into helpers that remain measured: `unsigned_any_value` / `unsigned_otel_value` (the u64-past-i64::MAX string export), `list_of`, and `write_list` / `write_map`. Mutation testing still applies to the excluded dispatches.
`cargo deny check` emitted `advisory-not-detected` for both: - RUSTSEC-2025-0141 (bincode 1.3.3): bincode is no longer in `Cargo.lock` at all. - RUSTSEC-2026-0173 (proc-macro-error2): proc-macro-error2 2.0.1 is still in the graph, but the advisory no longer matches it. The quick-xml ignores stay: quick-xml 0.39.4 is still pulled in through the Azure SDK and those advisories still match. `cargo deny check all` now reports advisories, bans, licenses and sources ok with no `advisory-not-detected` warnings.
…etection - `SensitiveSlice::new` drops the post-loop `iter.next()` probe. It was unreachable: the `for` loop exits normally only after `next()` returned `None`, so the extra call could report `Some` only for an iterator that resumes after `None`. The loop already detects overflow correctly on its own, because `is_full()` is checked BEFORE the push, so the `N+1`-th item is pulled, observed and discarded. The comment now explains that instead of narrating the removed call. - The example's module doc said `SensitiveCollection`, a type that does not exist; the example uses `SensitiveSlice`. - `examples/support/taxonomy.rs` said it was shared by the `observed` crate's examples. It is a per-crate copy, because an example cannot include a file outside its own package directory; the doc now says so. - `format_any_value.rs` and `sensitive_slice.rs` gain `//!` module headers, matching `otel.rs`. - The coverage-exclusion comments overstated what stays measured. They now state exactly what the exclusion covers: the inline scalar and `Bytes` rendering is excluded along with the guard arm and is pinned by tests and mutation testing rather than by the coverage gate. `string_value_of` delegates nothing at all, so its comment no longer claims otherwise.
`cargo-spellcheck` rejected two words introduced by the module docs added in 722f308: "recursing" in `format_any_value.rs` and "redactable" in `sensitive_slice.rs`. Both are correct technical English, so they are added to `.spelling` rather than the prose being reworded around the checker. They go into the same trailing block as the other `observed`-family terms, keeping that block's alphabetical order. Verified with `cargo spellcheck --cfg spellcheck.toml check --code 1`, which now reports no findings.
Per review: `observed_utils` reads more idiomatically, and since nothing is published yet the crate starts its own version line at 0.1.0 rather than inheriting `observed`'s 0.24.0. Renamed in place: the directory, the package name, the `repository` and `html_logo_url` / `html_favicon_url` attributes, both `observed_testing` call sites and its dependency entry, the `[workspace.dependencies]` entry, the two prose references under `crates/observed` (FEATURES.md and the `observed` OTel example), and the lockfile. The README is regenerated by cargo-doc2readme, not hand-edited. Git records every file as a rename, so the move itself carries no content change. The shorter crate name lets the `html_favicon_url` attribute fit on one line again, which is the only formatting change. Gates on the renamed crate: tests, clippy -D warnings, doc, both rustfmt configs, doc2readme --check, spellcheck, and `cargo deny check all` are clean; coverage is 100 % with no uncovered lines, and `cargo mutants` over the package reports no surviving mutants.
…laim Two fixes from the CI run and review of the rename: - `anvil-cargo-sort` failed on all four `pr-fast` legs and on `static-analysis` with "Dependencies for oxidizer are not sorted". The rename left the entry in the slot `observed_helpers` used to occupy, which sorted before `observed_macros`; `observed_utils` sorts after `observed_testing`. Moved. `cargo sort --workspace --grouped --check --check-format` now passes. - The `format_any_value` module doc claimed rendering "allocates nothing beyond what the formatter itself needs". That is wrong for the `Bytes` arm: `const_hex::encode` returns an owned `String`. The doc now says that every arm writes straight to the formatter except `Bytes`, which builds one temporary hex string. The allocation itself is left alone. Writing hex directly into the formatter would change the behaviour of code this PR only moves, and the crate enables `const-hex`'s `alloc` feature for exactly this call.
Per review, the `AnyValue::Bytes` arm no longer builds a temporary `String`. `const_hex::encode` allocates; `const_hex::display` returns a `Display` adapter that writes through const-hex's own encoder straight into the formatter. Every arm of the renderer now writes directly, so the module doc can state plainly that rendering allocates nothing. `display` is not gated behind const-hex's `alloc` feature, and `encode` was the crate's only use of it, so the feature is dropped. That also removes `proptest`, `rand_xorshift` and `unarray` from `Cargo.lock`: const-hex enables proptest through `alloc`, and nothing else in the workspace pulled it in. Gates: tests, clippy -D warnings, doc, both rustfmt configs, doc2readme --check, spellcheck, cargo-sort, cargo-machete and `cargo deny check all` are clean; coverage stays at 100 % with no uncovered lines, and `cargo mutants` over the file reports no surviving mutants.
Review found three public-doc claims that the implementation does not support as written. `any_value_of` and `otel_value_of` described the result as the value's OpenTelemetry counterpart, but a `Value::U64` has no single output type: up to `i64::MAX` it becomes an integer, past it a decimal string. That boundary was documented only on the private helpers, which do not reach rustdoc. It is now on both public functions and in the crate overview. `SensitiveSlice`'s `N` was documented only as an upper bound, while `new` asserts `N > 0` at compile time. The lower bound is now stated on the type parameter and on the constructor. The module claimed items are "never rendered in the clear by this module" and the crate claimed rendering "without allocating". Both are absolute where the behaviour is delegated: items are rendered by their own `RedactedDisplay` implementation under the caller's `Redactor`, which may pass values through, and an item may allocate. The wording now scopes both promises to what the wrapper controls - inline storage of the collection - and names the delegation. Documentation only; no behaviour change. The README is regenerated. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The type documentation said it stores `&dyn RedactedDisplay` fat pointers and that `new` accepts implementers of `RedactedDisplay`. Both understate the contract: the stored type is `&(dyn RedactedDisplay + Sync)` and the constructor requires `T: RedactedDisplay + Sync`, so a caller with a non-`Sync` item type meets the documented bound and still fails to compile. The bound stays. `&(dyn Trait + Sync)` is itself `Send` and `Sync` while a bare `&dyn Trait` is neither, so it is what lets an event struct holding a `SensitiveSlice` cross threads. The documentation now states it on the type and on the constructor. Documentation only; no behaviour change. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
8db85fb to
3763be2
Compare
| "opentelemetry::common::Value", | ||
| "opentelemetry::logs::record::AnyValue", | ||
| "opentelemetry::logs::record::Severity", | ||
| ] |
There was a problem hiding this comment.
🤖 This crate combines independently usable OpenTelemetry conversion, metric extraction, formatting, and privacy collection APIs. A SensitiveSlice consumer therefore compiles the OpenTelemetry dependencies, while conversion users compile the privacy stack. Please split the independently usable surfaces (for example, a focused OpenTelemetry crate and a privacy collection crate) or otherwise avoid forcing unrelated dependency sets.
Ralf Biedert (ralfbiedert)
left a comment
There was a problem hiding this comment.
🤖 Requesting changes for the lossy integer-to-float metric conversion. The crate-boundary concern is secondary.
| #[test] | ||
| fn numeric_values_convert() { | ||
| assert_eq!(metric_number_of(&Value::from(42_i64)), Some(42.0)); | ||
| assert_eq!(metric_number_of(&Value::from(7_u64)), Some(7.0)); |
There was a problem hiding this comment.
🤖 The implementation still converts i64 and u64 to f64; this small-value test misses the boundary where adjacent counters above 2^53 collapse to the same observation. Please preserve the numeric kind until instrument selection and add boundary tests around 2^53.
There was a problem hiding this comment.
🤖 Clawpilot here! Posted automatically by Clawpilot (an AI agent), not by a human. Please verify before acting.
This point was already raised and answered on this PR — see #677 (comment) for the reasoning (the numerics are correct and acknowledged by the #[expect(clippy::cast_precision_loss, ...)] at both arms; changing the return type is a redesign of a helper this PR only relocates, so it is follow-up material along with the 2^53 boundary test). Flagging it to the PR owner rather than re-deciding it here.
Move the OpenTelemetry helper crate from the internal repo into this repository, following
observed,observed_macrosandobserved_testing. It is the optional companion for consumers that export typed events to OpenTelemetry.The crate is named
observed_utilsand starts at version 0.1.0. It ships internally asobserved_helpersat 0.24.0; both were changed on review here, while nothing is published and the change is therefore free.observeditself no longer depends onopentelemetry, so an exporter has to convert values on its own. That conversion lives here once rather than being re-derived in every exporter:any_value_of,otel_value_ofandotel_severity_of. The crate also carriesformat_any_value, which renders anAnyValuein human-readable form instead of itsDebugshape, andSensitiveSlice, a bounded, type-erased collection ofRedactedDisplayreferences that renders without allocating.Changes made during the move:
any_value_ofandotel_value_ofhandleValue::U64. The variant was added in 0.24 after this crate was written, so au64dimension used to fall through to the#[non_exhaustive]guard arm and be exported as a debug string. Values pasti64::MAXstill export as a decimal string, because neitherAnyValuenoropentelemetry::Valuehas an unsigned variant.metric_number_ofreturns to this crate. It was parked inobserved_testingbecause that was its only consumer here;observed_testingnow uses it from this crate instead of keeping a second copy.#[path = "support/taxonomy.rs"]stand-in taxonomy theobservedexamples use, instead of the ox-sdk-onlymicrosoft_enterprise_data_taxonomycrate, andexpectwith a message instead ofunwrap, to satisfy this workspace'sclippy::unwrap_usedpolicy.#![doc(html_logo_url)]and#![cfg_attr(coverage_nightly, ...)]attributes are enabled, matching the convention of the other crates here.arrayvecandconst-hexare added to[workspace.dependencies].Effects:
Valuemapping, and the mapping is governed by this repository's gates.observed_testinggains a dependency onobserved_utils; the two copies ofmetric_number_ofcan no longer drift apart.Review changes
Rename and version (review request)
observed_helpersbecomesobserved_utils, and the version becomes 0.1.0 instead of inheritingobserved's 0.24.0. Renamed in place: the directory, the package name, therepositoryand doc-URL attributes, bothobserved_testingcall sites and its dependency entry, the[workspace.dependencies]entry, two prose references undercrates/observed, and the lockfile. Git records every file as a rename, so the move carries no content change. The README is regenerated by cargo-doc2readme rather than hand-edited.Correctness: an unreachable overflow probe removed
SensitiveSlice::newended with a post-loopiter.next()used to detect overflow. It cannot be reached: aforloop exits normally only afternext()returnedNone, so the extra call could reportSomeonly for an iterator that resumes afterNone, which theIteratorcontract leaves unspecified. The loop already detects overflow on its own, becauseis_full()is checked before the push, so theN+1-th item is pulled, observed and discarded.The gates could not have caught this: the line executed on every non-overflowing call, so it counted as covered, and
is_some()was simply always false.Bytes render without allocating (review request)
format_any_value'sBytesarm usedconst_hex::encode, which returns an ownedString. It now usesconst_hex::display, aDisplayadapter that writes through const-hex's own encoder straight into the formatter, so every arm writes directly and rendering allocates nothing.displayis not gated behind const-hex'sallocfeature andencodewas the only user of it, so the feature is dropped. const-hex enablesproptestthroughalloc, and nothing else in the workspace pulled it in, soproptest,rand_xorshiftandunarrayleaveCargo.locktoo.Other review fixes
implementorsbecomesimplementersand the Britishsignalledbecomessignaled. Two genuinely new technical words introduced by the module docs,recursingandredactable, were added to.spelling.opentelemetry::logs::record::Severity, the canonical pathcargo-check-external-typesreports, instead of the re-export path.SensitiveCollection, a type that does not exist;examples/support/taxonomy.rsclaimed to be shared by theobservedcrate's examples, and now describes this crate's copy and why the copy exists;format_any_value.rsandsensitive_slice.rsgained//!headers, matchingotel.rs.AnyValue::Maprendering,metric_number_ofonValue::U64,otel_value_ofonBool/F64/BoolArray/F64Array,Text::Shared, and theDebugandCloneimpls ofSensitiveSlice.Deliberate decision: the
#[non_exhaustive]guard arms stayThe only lines no test reaches are the wildcard arms guarding the foreign
#[non_exhaustive]enumsobserved::Value,observed::Textandopentelemetry::logs::AnyValue. All variants that exist today are matched explicitly, so those arms are unreachable and cannot be covered by a test. They are kept on purpose: they are what makes a variant added upstream degrade to a readable debug form instead of failing to compile or silently dropping data.Rather than delete them for the 100 % line-coverage gate, the four dispatch functions carry
#[cfg_attr(coverage_nightly, coverage(off))]with a comment stating exactly what the exclusion covers - including that the inline scalar andBytesarms are excluded too, and are pinned by tests and mutation testing rather than by the gate. The work they delegate to (unsigned_any_value,unsigned_otel_value,list_of,write_list/write_map) stays measured.cargo-deny: h2 advisory fixed, stale ignores removedanvil-denyfailed on the advisory "h2 unbounded empty DATA frames" against h2 0.4.15, which this repository carries transitively. The advisory predates this branch, but the fix is a one-line lockfile bump, so it is included: h2 moves to the patched 0.4.16. Only the h2 entry is touched - a fullcargo update h2also re-resolved unrelatedwindows-sysedges downwards.Two ignores that no longer match any crate were removed from
deny.toml:RUSTSEC-2025-0141(bincode, no longer in the lockfile at all) andRUSTSEC-2026-0173(proc-macro-error2, still present but no longer matched). The quick-xml ignores stay, because quick-xml 0.39.4 is still pulled in through the Azure SDK.cargo deny check allnow reports advisories, bans, licenses and sources ok with noadvisory-not-detectedwarnings.Verification
On the renamed crate: tests, clippy
-D warnings, doc, both rustfmt configs,doc2readme --check, spellcheck, cargo-sort, cargo-machete andcargo deny check allare clean; coverage is 100 % with no uncovered lines;cargo mutantsover the package reports no surviving mutants.Second review round: public documentation corrected
Three claims in the public documentation were not supported by the implementation as written. All three are fixed as documentation; no behaviour changed.
u64threshold is now public.any_value_ofandotel_value_ofdescribed their result as the value's OpenTelemetry counterpart, but aValue::U64has no single output type: up toi64::MAXit becomes the integer variant, past it a decimal string. That boundary was documented only on the private helpers, which never reach rustdoc. It is now on both public functions and in the crate overview. The policy itself is unchanged - neither destination enum has an unsigned variant, so the alternatives are wrapping into a negative number or dropping the value.SensitiveSlice's capacity has a lower bound.Nwas documented only as the maximum number of stored items, whilenewassertsN > 0at compile time. The type parameter and the constructor now state it.SensitiveSlice's guarantees are scoped to what the wrapper controls. The module said items are "never rendered in the clear by this module" and the crate said rendering happens "without allocating". Rendering is delegated to each item's ownRedactedDisplayimplementation under the caller'sRedactor, which is permitted to pass values through, and an item may allocate. The wording now promises inline storage of the collection and names the delegation instead.Declined in this PR: changing
metric_number_ofto preserve the numeric kind, or returning it toobserved_testing. The precision analysis is correct and is already acknowledged by thecast_precision_lossexpectations at both call sites, but a new numeric contract is a redesign of a helper this PR only moves, and returning it toobserved_testingreverses the de-duplication that is part of the move. Follow-up material, together with the adjacent-u64boundary test suggested in review.Third review round: the
Syncbound is documentedSensitiveSlicewas documented as storing&dyn RedactedDisplayfat pointers, andnewas accepting implementers ofRedactedDisplay. The stored type is actually&(dyn RedactedDisplay + Sync)and the constructor requiresT: RedactedDisplay + Sync, so a caller with a non-Syncitem type satisfied the documented bound and still failed to compile.The bound is documented rather than removed.
&(dyn Trait + Sync)is itselfSendandSyncwhile a bare&dyn Traitis neither, so the bound is what lets an event struct holding aSensitiveSlicecross threads; dropping it would narrow where the type can be used. Documentation only, and the generated README is unaffected because these docs live on the type rather than inlib.rs.Rebase note (2026-08-19): rebased onto
mainafter it advanced; the branch had gone into a conflicting state. The only conflict was inCargo.lock, where upstream's newarty_executorpackage entry landed in the same alphabetical slot as this branch'sarrayvecentry. Both were kept, in alphabetical order;cargo metadata --lockedaccepts the merged lockfile. No source change.