Skip to content

Explicit policy precedence at the run boundary + first-class Action::Suppress#152

Merged
martsokha merged 5 commits into
mainfrom
feat/policy-precedence
May 20, 2026
Merged

Explicit policy precedence at the run boundary + first-class Action::Suppress#152
martsokha merged 5 commits into
mainfrom
feat/policy-precedence

Conversation

@martsokha
Copy link
Copy Markdown
Member

Summary

Two related changes to how policies are layered and how their actions execute:

  1. Explicit precedence at the run boundary. Replace the implicit "Vec position = precedence" rule with an explicit, typed precedence on each ref. Callers now declare layering instead of relying on insertion order they could accidentally sort.
  2. First-class `Action::Suppress`. Suppress now records suppressed entities in the audit trail and wins over equal-or-lower-priority Redact rules. Previously all non-Redact actions silently dropped the entity with a debug log.

Also folds in cleanup that came out of the design review: `RedactionMapping` slimmed to its useful core, `#[non_exhaustive]` dropped from the strategy/action enums.

Changes

Policy precedence (`72ec208`)

  • New `nvisy_ontology::policy::PolicyRef { id: Uuid, precedence: u32 }`. Lower wins (0 = highest precedence; higher numbers layer underneath as defaults). Required field — explicit declaration is the point.
  • `EngineInput.policy_ids: Vec` → `EngineInput.policies: Vec`. `NewRun` (`POST /runs`) request body matches.
  • `Pipeline.execute` sorts refs by precedence (stable; ties preserve input order) before resolving from cache. `Policies.policies` ends up in true precedence order — index 0 is highest precedence.
  • `Policies::all_strategies` doc spells out the tiebreaker rule explicitly: strategy `priority` first, then policy precedence (via Vec position), then within-policy insertion order.

Action::Suppress implementation (`67bfd0f`)

  • New `AuditEntryStatus::Suppressed` marks entries for entities matched by an `Action::Suppress` rule. The entry carries the suppressing `policy_id` and the original value but no replacement.
  • Evaluator rewrite: collects all matching strategies per entity, then Suppress wins iff `suppress.priority() <= best matching Redact's priority` (or no Redact matches). Compares the priority field directly rather than slice index, so ties go to Suppress regardless of insertion order.
  • Applicator skips Suppressed entries at the top of each `build_*_redactions` loop — they never reach the codec.
  • Four unit tests cover the precedence rules (suppress-only, suppress-at-higher, suppress-at-equal-tiebreak, redact-at-higher).
  • `Action::Review` / `Alert` / `Block` remain stubs; tracking separately.

Slimmed `RedactionMapping` (`67bfd0f`)

  • Dropped `original: String` / `replacement: Option` — they were structurally incapable of holding image/audio payloads and held degenerate placeholder strings for non-text modalities.
  • Audit values stay on `AuditEntry.value` as before; `RedactionMap` is now a thin entity-to-location index.
  • Future blob-backed reversibility extension tracked in Support reversibility for image/audio redactions in the redaction map #151.

Removed `#[non_exhaustive]` (`67bfd0f`)

  • Dropped from the four `TextStrategy` / `ImageStrategy` / `AudioStrategy` / `Action` enums. The codec matches them exhaustively; `non_exhaustive` forced silent catch-all arms that hid new variants from compile-time checks.
  • Cleared three dead `_ =>` arms in `apply.rs` after the change.

Test plan

  • `cargo check --workspace --all-features` — clean
  • `cargo test --workspace --all-features` — all green (380+ tests; nvisy-engine grew from 72 to 76 with the new Suppress tests)
  • `cargo clippy --workspace --all-features --no-deps` — clean
  • `cargo +nightly fmt --all` — clean
  • Manual smoke through a multi-policy run once an integration env is available

🤖 Generated with Claude Code

martsokha and others added 5 commits May 19, 2026 17:45
… boundary

Replace the implicit "Vec position = precedence" rule for run-time
policy layering with an explicit, typed precedence on each ref.

- New nvisy_ontology::policy::PolicyRef { id: Uuid, precedence: u32 }.
  Lower precedence wins (0 = most authoritative override; higher
  numbers layer underneath as defaults). Required, not optional —
  callers explicitly declare the layering for every ref.
- EngineInput.policy_ids: Vec<Uuid> → EngineInput.policies: Vec<PolicyRef>.
  NewRun (POST /runs) request body matches.
- Pipeline.execute sorts the refs by precedence (stable; ties preserve
  input order) before resolving from the policy cache, so Policies.policies
  ends up in true precedence order: index 0 is highest precedence.
- Policies::all_strategies now documents the layered tiebreaker
  explicitly: strategy `priority` first, then policy precedence (via
  the now-meaningful Vec position), then within-policy insertion order.

Previously the precedence was hidden inside whatever order the caller
happened to pass IDs in — `vec.sort()` on the input would silently
reorder governance. Now the precedence number travels with each ref
and is load-bearing in the type.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ping

Suppress is now a first-class action that records suppressed entities
in the audit trail and wins over equal-or-lower-priority Redact rules.

- New AuditEntryStatus::Suppressed marks entries for entities matched
  by an Action::Suppress rule. The entry carries the suppressing
  policy_id and the original value but no replacement.
- Evaluator rewrite: collect all matching strategies per entity, then
  Suppress wins iff suppress.priority() <= best matching Redact's
  priority (or no Redact matches). Compares the priority field
  directly rather than slice index so ties go to Suppress regardless
  of insertion order.
- Applicator skips Suppressed entries at the top of each
  build_*_redactions loop — they never reach the codec.
- Four unit tests cover the precedence rules.
- Review/Alert/Block remain stubs that log and fall through to the
  default-threshold path; tracking them separately.

Also slimmed RedactionMapping to { entity_id, location }, dropping the
non-functional `original: String` / `replacement: Option<String>`
fields that couldn't hold image/audio payloads. Audit values stay on
AuditEntry.value as before. A future blob-backed reversibility
extension is tracked in #151.

Also dropped #[non_exhaustive] from the four *Strategy / Action enums.
The codec matches them exhaustively; non_exhaustive forced silent
catch-all arms that hid new variants from compile-time checks. Cleared
three dead `_ =>` arms in apply.rs after the change.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ames, file rename pass

Restructure of operation/ for consistent naming and clearer module
layout. No behavioural change.

Detection module:
- Combine entity_recognition and pattern_recognition under
  operation/detection/{entity_recognition,pattern_recognition,pattern_engine,rebase_entities}.rs
- New private RebaseEntities extension trait on Entities (formerly
  EntitiesExt) carries the per-span → document-relative offset shift
  shared by NER and pattern detection.
- New PatternEngineRef wraps the static singleton vs config-built
  engine resolution that PatternRecognition::new used to inline;
  PatternRecognition::new now just calls PatternEngineRef::new(cfg).
- Document::collect_text_spans hides the locations-then-read loop both
  detectors used to inline.

Deduplication file renames (verb_noun pattern):
- grouping.rs → group_entities.rs + group_key.rs (split the trait and
  the hash-key type).
- strategy.rs → fuse_entities.rs (DeduplicationStrategyExt::fuse)
- conflict.rs → resolve_conflicts.rs (ConflictResolutionExt::resolve)
- calibration.rs → calibrate_entities.rs (CalibrationExt)

Engine-wide *Op suffix removed:
- ImportFileOp, ExportFileOp, ExtractionOp, GenerateContextOp,
  ValidationOp, DeduplicationOp, RedactionOp → ImportFile, ExportFile,
  Extraction, GenerateContext, Validation, Deduplication, Redaction.
- Workflow config types of the same name aliased at import sites
  (e.g. `use nvisy_ontology::workflow::Validation as ValidationConfig`).
- validation.rs renamed to validate.rs to match verb pattern.

Visibility tightening:
- `pub mod envelope` → `mod envelope` (SharedData re-exported at
  operation::SharedData; consumers switched to the shorter path).
- `pub(crate) mod redaction` → `mod redaction` (Redaction re-exported
  at operation::Redaction).

Net: the operation/ tree now has consistent naming
(verb_noun.rs for trait helpers, verb.rs for ops, noun.rs for plain
types), private submodules with curated re-exports, and a detection/
folder mirroring the deduplication/ shape.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- graph/{retry,timeout,petgraph}.rs → graph/{retry_ext,timeout_ext,petgraph_ext}.rs
  (the files contain extension traits/impls; the `_ext` suffix matches
  the trait names RetryExt / TimeoutExt and reads better at import
  sites.)
- registry/{cache,content,key,store}.rs →
  registry/{resource_cache,content_handle,composite_key,registry_store}.rs
  (each file's primary type is the noun in the new name; the prefixed
  variants disambiguate from same-named types elsewhere.)
- detection/pattern_engine.rs: document the two PatternEngineRef
  variants (Shared = process-wide singleton via
  PatternEngine::instance, Owned = freshly compiled per-config
  engine). Manual Deref impl stays — derive_more::Deref doesn't
  support enums.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@martsokha martsokha self-assigned this May 20, 2026
@martsokha martsokha added feat request for or implementation of a new feature engine nvisy-engine: pipeline, registry, operations refactor code restructuring without behavior change labels May 20, 2026
@martsokha martsokha merged commit 6b0a3d3 into main May 20, 2026
5 checks passed
@martsokha martsokha deleted the feat/policy-precedence branch May 20, 2026 03:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

engine nvisy-engine: pipeline, registry, operations feat request for or implementation of a new feature refactor code restructuring without behavior change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant