Skip to content

Commit b9279ee

Browse files
msureshkumar88Suresh Kumar Moharajan
andauthored
feat(plugins): emit result.metadata metrics from 5 remaining bundled plugins (#131)
* feat(secrets_detection): emit namespaced OpenTelemetry metrics on result.metadata Port pii_filter's trace-gated metrics pattern (#128) to secrets_detection per issue #129. prompt_pre_fetch, tool_post_invoke, and resource_post_fetch now accept an optional extensions param carrying OTel trace context; when extensions.request.trace_id is present, the plugin writes a single namespaced dict at result.metadata["secrets_detection"] with total_detections, total_masked, total_blocked, and secret_types (bounded, deduped, sorted). tool_pre_invoke is unchanged (out of scope for #129). Removes the two legacy flat result.metadata writes (secrets_redacted/count and secrets_findings/count), which were ungated and are superseded by the namespaced channel. No trace_id means no metadata write at all, matching pii_filter's zero-overhead-when-untraced contract. Bumps cpex_secrets_detection 0.3.6 -> 0.3.7 and documents the breaking metadata-key migration in the plugin README. Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com> * feat(rate_limiter): emit namespaced OpenTelemetry metrics on result.metadata Adds an `extensions` parameter to `prompt_pre_fetch` and `tool_pre_invoke` so a trace_id from the gateway can gate emission of per-call metrics (allowed/throttled/backend) under result.metadata["rate_limiter"], matching the pii_filter/secrets_detection contract for issue #129. Reconciles the plugin's existing un-namespaced, un-gated `result.metadata` write (the whole engine `meta` dict) into a single namespaced write per hook call: the early-return-not-limited, allowed, and throttled/not-allowed branches all now emit metrics when trace_id is present, and none do without it. The throttled branch previously emitted no metadata at all; it now carries the same metrics as the other branches. The namespaced dict folds in the engine's own operational fields (limited/remaining/reset_in/dimensions) via an explicit allowlist that deliberately excludes user_id/tenant_id — those identifiers remain in PluginViolation.details for debugging but are kept out of the metrics/telemetry channel (S1). Bumps rate_limiter 0.1.6 -> 0.1.7 (Cargo.toml + plugin-manifest.yaml). Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com> * feat(retry_with_backoff): emit namespaced OpenTelemetry metrics on result.metadata Adds an `extensions` parameter to `tool_post_invoke` only (issue #129 scopes `resource_post_fetch` out — left untouched, no extensions param, no metadata change) so a trace_id from the gateway can gate emission of per-call metrics under result.metadata["retry_with_backoff"]. Emits exactly two per-call counters: `retry_count` (ToolRetryState::consecutive_failures after this call's outcome is recorded) and `retry_delay_ms` (the per-attempt delay compute_delay_ms just computed, already returned as the top-level retry_delay_ms field — 0 on success and once the retry budget is exhausted). No cumulative total_backoff_ms is tracked or emitted: no new state accumulator is added, matching the gateway's allowlisted field name and the resolved plan decision. Reconciles the existing un-namespaced, un-gated `result.metadata["retry_policy"]` config echo: `tool_post_invoke`'s three branches (success, within-budget, exhausted) now write the namespaced dict instead, gated on trace_id, with exactly one metadata write per call. `resource_post_fetch` keeps the legacy `retry_policy` echo exactly as before via the unchanged `build_metadata` helper. Bumps retry_with_backoff 0.3.5 -> 0.3.6 (Cargo.toml + plugin-manifest.yaml). Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com> * feat(encoded_exfil_detection): migrate to namespaced, gated result.metadata Replaces the legacy flat, un-gated result.metadata write (encoded_exfil_count, encoded_exfil_findings, implementation — already dead-on-arrival at the gateway sanitizer, and an S1 leak risk since encoded_exfil_findings could carry raw matched/decoded content) with a single namespaced write per hook: result.metadata["encoded_exfil_detection"] = {total_detections, encoding_types}. Adds an optional `extensions` parameter to all 3 in-scope hooks (prompt_pre_fetch, tool_post_invoke, resource_post_fetch). Emission is gated on both extensions.request.trace_id being present and at least one detection occurring, matching the pii_filter contract precedent. The redact branch folds a `redacted: True` bool into the same namespaced dict, gated the same way, only when the redact branch actually fires. _findings_for_metadata() is unchanged and remains scoped to PluginViolation.details on the blocking path only — a separate, pre-existing channel out of scope for this migration. Bumps encoded_exfil_detection 0.3.5 -> 0.3.6 (Cargo.toml, Cargo.lock, plugin-manifest.yaml) and documents the breaking change in the README. Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com> * feat(url_reputation): emit namespaced OpenTelemetry metrics on result.metadata url_reputation wrote no result.metadata prior to this change, so this is a pure additive contract, not a migration. Adds an optional `extensions` parameter to `resource_pre_fetch` (the only in-scope hook). When `extensions.request.trace_id` is present, the plugin emits result.metadata["url_reputation"] = {total_checked, total_blocked, reputation_categories}. resource_pre_fetch checks exactly one URL per call with no running counter, so -- mirroring rate_limiter's per-call allowed/throttled semantics -- metrics are gated on trace_id alone (every call has a meaningful outcome to report): total_checked is always 1, total_blocked is a 0/1 outcome for this call, and reputation_categories is empty when allowed or a single category slug when blocked. Category slugs are derived from the Rust engine's (and the Python wrapper's) static, hardcoded PluginViolation.reason strings via a fixed mapping (e.g. blocked_domain, insecure_scheme, high_entropy_domain) -- never the raw URL or domain, which stays confined to PluginViolation.details on the blocking path (S1). Bumps url_reputation 0.3.4 -> 0.3.5 (Cargo.toml, Cargo.lock, plugin-manifest.yaml) and documents the new metrics contract in the README. Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com> * docs(contracts): add metrics-schema contracts for tasks 1-5 plugins Adds the 5 per-plugin metrics-schema contract docs (secrets_detection, rate_limiter, retry_with_backoff, encoded_exfil_detection, url_reputation) that the OTel plugin metrics rollout plan called for alongside each task, mirroring pii_filter-metrics-schema.md's structure. Each doc reflects the actual landed field names/semantics from the merged commits (bc43deb, a33716e, f060ef2, c6d3b0a, 28d2fa2), not the plan's original illustrative examples -- notably retry_with_backoff's retry_delay_ms (no cumulative total_backoff_ms) and url_reputation's 9 actual violation-reason category slugs plus an "other" fallback. Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com> * docs(contracts): fix rate_limiter dimensions gating and encoded_exfil blocking-path gap Review of 6ea8f8e found two doc-vs-code mismatches: - rate_limiter-metrics-schema.md claimed `dimensions` is present only when 2+ dimensions are evaluated. Per build_meta_dict (src/engine.rs:502-533) and EvalResult::from_dims (src/types.rs:105-159), it's actually set whenever any dimension (1 or more) is evaluated -- i.e. whenever `limited` is true. Corrected the allow-list/semantics/bounds text and updated the Example (a single by_user dimension) to show the `dimensions` key it would actually produce. - encoded_exfil_detection-metrics-schema.md's Emission Criteria omitted that the blocking path (count >= min_findings_to_block and block_on_detection enabled) returns a PluginViolation before _build_metrics is ever called, across all 3 hooks -- so no metrics are emitted at all when this plugin blocks, unlike secrets_detection and url_reputation which do emit on their blocking paths. Added this as an explicit, named exception since flagging cross-plugin inconsistencies is this task's purpose. Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com> * docs(plans): add implementation plan for issue #129 plugin metrics rollout Records the plan this branch executed, mirroring the pii_filter pilot's committed plan doc for future reference. Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com> * fix(ci): resolve secrets-detection doc false-positive and mutation gaps - docs(secrets_detection): break up the illustrative AWS key example string in secrets_detection-metrics-schema.md so it no longer matches the AKIA[0-9A-Z]{16} pattern; detect-secrets flagged it as an unaudited secret since the doc is new on this branch. - test(secrets_detection): add resource_post_fetch/tool_post_invoke coverage for the count > 0 branch (trace_id present, block/redact both disabled) plus a count == 0 companion case, killing the previously-missed `count > 0` mutants at plugin.rs:141 and :229. - chore(mutants): exclude src/bin/stub_gen.rs from mutation testing workspace-wide. These binaries require the `stub-gen` feature, which the CI mutation job never enables, so they (and their own tests) never compile during a mutation run - every mutant in them is structurally untestable via this pipeline, not just the one this branch's diff happened to touch. Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com> * chore: untrack docs/superpowers planning/contract docs These are working notes for agentic implementation, not shipped documentation - keep them local, out of git history going forward. Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com> * docs(rate_limiter,retry_with_backoff): document OTel metrics contract in README rate_limiter had no metrics documentation at all. retry_with_backoff's Returned Metadata section was stale, claiming both hooks still emit the legacy retry_policy echo when tool_post_invoke actually switched to the new namespaced, trace-gated retry_count/retry_delay_ms metrics. Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com> * fix(encoded_exfil_detection): emit metrics on the blocking path too All 3 hooks built result.metadata metrics AFTER the block_on_detection check, so under the default config (block_on_detection=True, min_findings_to_block=1) every real detection returned the blocking violation before metrics were ever built. Move metrics construction before the blocking check and attach it to the blocked result too. Adds regression tests exercising the default-config blocking path across all 3 hooks, asserting metrics are present on the violation result. Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com> * fix(rate_limiter): drop metric fields the gateway sanitizer can't accept build_rate_limiter_metrics folded the engine's own limited/remaining/ reset_in/dimensions fields into the result.metadata["rate_limiter"] dict. dimensions is a nested dict, which the gateway's S4 sanitizer (scalar/list[str] only) drops unconditionally regardless of allowlisting; remaining/reset_in are plain ints not on the sanitizer's numeric allowlist. Emitting fields the consumer can never accept is misleading dead weight, so drop the META_METRIC_KEYS passthrough entirely and keep only allowed/throttled/backend. Updates the README's Returned Metadata section and the existing Rust unit tests to assert the metrics dict's key set is exactly {"allowed", "throttled", "backend"}. Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com> * fix(rate_limiter,retry_with_backoff): regenerate stale PyO3 stubs The checked-in .pyi stubs still advertised the pre-issue-129 2-argument method signatures, missing the extensions parameter the Rust code already accepts. make stub-gen failed against pyo3-stub-gen 0.22.3 + pyo3 0.29.0 (downcast -> cast rename, pyo3-stub-gen#... incompatibility). pyo3-stub-gen 0.23.0 adds pyo3 0.29 support, so bump the workspace dependency and regenerate the 2 affected stub files. secrets_detection's stub is regenerated in the following commit alongside its Finding 4 code fix, since that hook's signature is changing there too. Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com> * fix(secrets_detection): tool_pre_invoke now emits metrics like the other hooks tool_pre_invoke hardcoded None for scan_payload_attr's trace_id and had no extensions parameter at all, so it could never emit result.metadata["secrets_detection"] regardless of configuration or trace context — a silent observability regression versus the other 3 hooks. Add extensions=None to the Rust method signature (mirroring prompt_pre_fetch/tool_post_invoke/resource_post_fetch), read trace_id via the same read_trace_id helper, and pass it through to scan_payload_attr. Also fix the Python SecretsDetectionPlugin shim, which still called the Rust core with only 2 positional args and would have silently swallowed the new parameter otherwise. Regenerates the stub file for this plugin (also picks up the extensions param on the other 3 hooks, which the stub had drifted on too), and updates the README/contract doc's "out of scope" language now that tool_pre_invoke is in scope. Adds regression tests (Rust unit test + Python integration test) confirming tool_pre_invoke with a trace_id and findings present emits the expected metrics dict. Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com> --------- Signed-off-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com> Co-authored-by: Suresh Kumar Moharajan <suresh.kumar.m@ibm.com>
1 parent 329a744 commit b9279ee

38 files changed

Lines changed: 2565 additions & 1113 deletions

File tree

‎.cargo/mutants.toml‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,13 @@
33
test_tool = "nextest"
44
additional_cargo_test_args = ["--profile=mutants"]
55
cap_lints = false
6+
7+
# `stub_gen` binaries (one per plugin, under `src/bin/stub_gen.rs`) are
8+
# gated behind the `stub-gen` feature (see each plugin's Cargo.toml
9+
# `required-features = ["stub-gen"]`), which the CI mutation-testing job
10+
# never enables. That means these binaries — and their own unit tests —
11+
# are never compiled during a mutation run, so every mutant generated in
12+
# them is structurally untestable via this pipeline regardless of code
13+
# quality. Exclude them so cargo-mutants doesn't report unkillable mutants
14+
# here.
15+
exclude_globs = ["**/src/bin/stub_gen.rs"]

‎Cargo.lock‎

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ log = "0.4.29"
2323
pyo3-async-runtimes = { version = "0.29.0", features = ["tokio-runtime"] }
2424
pyo3 = { version = "0.29.0", features = ["abi3-py311"] }
2525
pyo3-log = "0.13.4"
26-
pyo3-stub-gen = "0.22.3"
26+
pyo3-stub-gen = "0.23.0"
2727
regex = "1.12.3"
2828
serde_json = "1.0.149"
2929
thiserror = "2.0.18"

‎docs/superpowers/contracts/pii_filter-metrics-schema.md‎

Lines changed: 0 additions & 117 deletions
This file was deleted.

0 commit comments

Comments
 (0)