Skip to content

Reject requests and policies that can detect but never redact - #421

Merged
martsokha merged 1 commit into
mainfrom
fix/empty-scope
Aug 29, 2026
Merged

Reject requests and policies that can detect but never redact#421
martsokha merged 1 commit into
mainfrom
fix/empty-scope

Conversation

@martsokha

@martsokha martsokha commented Aug 29, 2026

Copy link
Copy Markdown
Member

Problem

An empty label scope read oppositely on the two halves of the pipeline. Scopes flatten into a request-wide LabelCatalog for analysis, but each policy's own scope gates its redaction — a rule matches an entity only when the scope contains its label. A scope that named nothing therefore detected without redacting, and the request returned Ok with a document that looked processed.

Two distinct defects hide behind that, at two different granularities:

  1. Request-wide — no policies, or none naming a label, compiles to an empty catalog.
  2. Per-policy — one policy declares scopes, names no labels in them, and still carries an operator.

Fix

Request-wide, in compile_catalog. Since elide#239 an empty catalog is a request for no entity types, so the analyzer short-circuits and detects nothing. Such a request can only ever return an empty report — an answer a caller cannot distinguish from a clean document. It is now refused, naming the cause.

Checking the compiled catalog rather than the policy slice catches every route to empty, not just &[]. That is what surfaced anonymize_succeeds_when_policies_supply_catalog_afresh, whose policy had scopes: Vec::new() and contributed nothing.

Per-policy, in validate_scope_references, via a new PolicyDefinition::scopes_nothing_it_redacts. The policy's rules match nothing, while any other policy's labels still reach it as entities it cannot act on. The request-wide check cannot catch this: add one scoped policy beside it and the union is non-empty, so the request compiles and the hole stays open.

Deliberately still legal: a policy with no scopes at all (says nothing about coverage) and one that names labels but no operator (detect-only, which unhandled() exists to report). Only "declares scopes, names no labels, carries an operator" is incoherent.

Upstream

Cargo.lock moves elide to 7da9b52 for #239. That PR fixes the all-empty case upstream — converting "detect everything, redact none" into "detect nothing" — but it does not reach either defect from our side: the first still returns a silent empty report, and the second it does not touch at all.

Tests

Every test keeps its subject and gains the policies it needs; none was weakened.

  • audit.rs, multimodal.rs — a shared detect_only() fixture. These exercise export writers and part round-tripping, not detection defaults.
  • anonymize_rejects_an_audit_that_never_ran_analyze — the one that mattered. It asserts a specific message ("analyze must run first"), so with no policies it would have gone on passing while failing for the new reason instead. Now passes a real policy, so it still tests its own guard.
  • review.rs:unhandled_names_a_detection_no_policy_acted_on — premise was "no policies at all"; now a policy with fallback: None, the same shape (detected, nothing acted on).
  • catalog.rs:empty_policy_set_yields_empty_cataloga_policy_set_naming_no_labels_is_rejected.
  • New a_request_naming_no_labels_is_rejected; a_policy_scoping_no_labels_is_rejected and a_policy_with_no_scopes_or_no_operators_is_fine each pair their shape with a label-contributing policy, so the request-level check cannot mask what is under test.

Verification

114 passing (was 113). clippy --workspace --all-targets --all-features, +nightly fmt --check, RUSTDOCFLAGS="--cfg docsrs -D warnings" +nightly doc, cargo machete, cargo deny check, and a --no-default-features build all clean.

Note for review

Engine::analyze now requires at least one label-naming policy, so exploratory "analyze with no policies" no longer works. The shape for that use case is an explicit detect-everything policy (scopes the builtins, no operators) rather than a special case in the empty check — not assumed here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LjYewekAhx4ZK7BReLSzj7

Summary by CodeRabbit

  • New Features

    • Added validation to reject policies and requests that identify no labels for detection.
    • Added safeguards for policies that define operators but cannot detect or redact any labels.
    • Configuration errors now clearly identify invalid policies and explain the issue.
  • Bug Fixes

    • Prevented empty label catalogs from being accepted during request compilation.
    • Improved handling of policy scope references to avoid unredacted data.

An empty label scope read oppositely on the two halves of the
pipeline, so a request could come back looking processed while
nothing was redacted. Two distinct defects, closed at the two
granularities they live at.

A request naming no labels anywhere now fails in
`compile_catalog`. Since elide #239 an empty catalog is a request
for no entity types, so such a request can only ever return an
empty report — a clean answer a caller cannot tell apart from a
clean document. Checking the compiled catalog rather than the
policy slice catches every route to empty: no policies, or
policies that name nothing.

A single policy that declares scopes, names no labels in them and
still carries an operator now fails in `validate_scope_references`
via `PolicyDefinition::scopes_nothing_it_redacts`. Its rules match
nothing, while any other policy's labels still reach it as
entities it cannot act on. The request-wide check cannot catch
this one: the union stays non-empty.

Tests keep their subjects and gain the policies they need. The
`anonymize_rejects_an_audit_that_never_ran_analyze` case mattered
most: it asserts a specific message, so with no policies it would
have kept passing while failing for the new reason instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LjYewekAhx4ZK7BReLSzj7
@coderabbitai

coderabbitai Bot commented Aug 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bd05b08a-5fb7-44b6-ba35-6093f301806a

📥 Commits

Reviewing files that changed from the base of the PR and between 9be3fce and 1e7b549.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (7)
  • crates/elide-governance/src/catalog.rs
  • crates/elide-governance/src/policy/mod.rs
  • crates/elide-pipeline/tests/audit.rs
  • crates/elide-pipeline/tests/multimodal.rs
  • crates/elide-pipeline/tests/policy_shapes.rs
  • crates/elide-pipeline/tests/review.rs
  • crates/elide-provider/src/orchestrator/mod.rs

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The change rejects policies with operators but no detectable labels. Catalog compilation also rejects empty label catalogs. Pipeline tests now use detection-only policies and cover the new configuration errors.

Changes

Catalog and policy validation

Layer / File(s) Summary
Policy scope validation
crates/elide-governance/src/policy/mod.rs, crates/elide-provider/src/orchestrator/mod.rs
Adds scopes_nothing_it_redacts and rejects policies that declare scopes but name no labels while defining redaction operators.
Empty catalog rejection
crates/elide-governance/src/catalog.rs
compile_catalog now returns a Configuration error for empty catalogs. Documentation and unit tests cover empty policy inputs and policies naming no labels.
Pipeline validation coverage
crates/elide-pipeline/tests/audit.rs, crates/elide-pipeline/tests/multimodal.rs, crates/elide-pipeline/tests/policy_shapes.rs, crates/elide-pipeline/tests/review.rs
Adds configuration-error tests and updates pipeline scenarios to use contact-label detection policies. Tests also cover valid policies without scopes or operators.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 1e7b5

The PR intentionally rejects policy configurations that cannot produce meaningful detection or redaction, preventing misleading successful results. No actionable merge-blocking risk remains beyond normal checks and review.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: rejecting requests and policies that can detect entities but cannot redact them.
Docstring Coverage ✅ Passed Docstring coverage is 82.61% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 23 functions across 7 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/empty-scope

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@martsokha martsokha self-assigned this Aug 29, 2026
@martsokha martsokha added bug something isn't working as intended engine redaction engine, pipeline runtime, orchestration, configuration ontology entities, policies, contexts dependencies dependency updates and version bumps labels Aug 29, 2026
@github-project-automation github-project-automation Bot moved this to Backlog in Nvisy OSS Aug 29, 2026
@martsokha
martsokha merged commit c50e467 into main Aug 29, 2026
9 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Nvisy OSS Aug 29, 2026
@martsokha
martsokha deleted the fix/empty-scope branch August 29, 2026 14:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug something isn't working as intended dependencies dependency updates and version bumps engine redaction engine, pipeline runtime, orchestration, configuration ontology entities, policies, contexts

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant