Repository navigation
Commit b9279ee
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
- docs/superpowers
- contracts
- plans
- plugins
- rust/python-package
- encoded_exfil_detection
- cpex_encoded_exfil_detection
- rate_limiter
- cpex_rate_limiter
- rate_limiter_rust
- src
- bin
- retry_with_backoff
- cpex_retry_with_backoff
- retry_with_backoff_rust
- src
- secrets_detection
- cpex_secrets_detection
- secrets_detection_rust
- src
- url_reputation
- cpex_url_reputation
- tests
- encoded_exfil_detection
- rate_limiter
- retry_with_backoff
- secrets_detection
- url_reputation
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
3 | 3 | | |
4 | 4 | | |
5 | 5 | | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
23 | 23 | | |
24 | 24 | | |
25 | 25 | | |
26 | | - | |
| 26 | + | |
27 | 27 | | |
28 | 28 | | |
29 | 29 | | |
| |||
This file was deleted.
0 commit comments