You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
After #276 landed, `Policy` is generic over modality purely so `Action::Redact { operator: M::Redaction }` is statically typed. Everything else in `Policy` (name, version, description, retention, selector, conditions) is modality-agnostic.
The `M` generic forces:
An `AnyPolicy` sum-type wrapper at every public surface (`DetectionInput::policies`, request payloads, etc.)
A `PolicyStore` with `TypeMap`-keyed per-modality buckets and a `from_any_policies` dispatch step
Per-modality match arms scattered through the dispatch sites (`pipeline/detection/pipeline.rs`, `core/policy_store.rs`)
The audit's `Decision` enum carries `operator: M::Redaction` and stays modality-typed
`AnyRedaction` already exists in `crates/nvisy-engine/src/policy/redaction/any.rs` as a tagged sum over `TextRedaction` / `TabularRedaction` / `ImageRedaction` / `AudioRedaction`. If `Action` carried `AnyRedaction` directly the whole `` generic could go away.
`DetectionInput::policies: Vec`. `AnyPolicy` deleted. `PolicyStore`'s typed buckets reduce to a single `Vec<Arc>`. The resolver walks rules and skips ones whose `operator.modality()` doesn't match the entity's modality.
Trade-offs to investigate
Property
Today (typed `Policy`)
Proposed (untyped + `AnyRedaction`)
Author writes a text operator on an image rule
Deserialise fails
Deserialises; runtime silently skips
Surface area
`AnyPolicy` sum + per-modality match arms everywhere
One `Policy` type, one `Vec<Arc>`
Adding a new modality
`DocumentModality::Redaction` assoc + `AnyPolicy` arm + dispatch arm
`AnyRedaction` arm + matching check
Compile-time modality mismatch detection
Yes
No
The compile-time guarantee is the only real win. Everything else is overhead the current shape forces on consumers.
Mitigating the loss of compile-time check
Two ways to recover early validation:
Add a `modality` field on `PolicyRule`. Authors declare it explicitly; `validate_policy_namespace` (or a sibling pass) walks rules and rejects ones whose `operator.modality()` disagrees with the rule's declared modality. Re-introduces a per-rule tag at the data layer, not the type layer.
Reject silently at runtime; trust the author. Smallest code; relies on the author to keep the operator and the selector in sync. The audit's `PolicyDecisionRef` already names the rule, so misconfigurations are debuggable.
(1) feels right for a redaction engine where silent skips would be a compliance gap.
What to look at
Every call site that holds `Policy` / `PolicyRule` / `Action` — currently dozens of generic functions threaded through `phases/redaction/mod.rs`, `pipeline/redaction/applicator.rs`, `pipeline/detection/pipeline.rs`, `core/policy_store.rs`, the audit `Decision` enum.
How `Decision::Redact { operator: M::Redaction }` reads after the change. Probably becomes `Decision::Redact { operator: AnyRedaction }` and the apply phase pattern-matches.
Whether `RedactionRegistry` can stay typed if the policy layer drops typing. The toolkit's `Anonymizer` chain is the boundary where typing genuinely matters; the policy layer might be where it stops.
Does dropping the generic regress TOML deserialise quality? Today a typo in `operator.kind` for an `image` policy fails at parse-time with a clear message. Under the new shape it would parse fine and skip silently — unless we add option (1) above.
Background
After #276 landed, `Policy` is generic over modality purely so `Action::Redact { operator: M::Redaction }` is statically typed. Everything else in `Policy` (name, version, description, retention, selector, conditions) is modality-agnostic.
The `M` generic forces:
`AnyRedaction` already exists in `crates/nvisy-engine/src/policy/redaction/any.rs` as a tagged sum over `TextRedaction` / `TabularRedaction` / `ImageRedaction` / `AudioRedaction`. If `Action` carried `AnyRedaction` directly the whole `` generic could go away.
Proposal sketch
```rust
struct Policy {
name: HipStr<'static>,
version: Version,
description: Option,
rules: Vec,
default_action: Option,
retention: Vec,
}
struct PolicyRule {
name: HipStr<'static>,
selector: EntitySelector,
action: Action,
conditions: Vec,
enabled: bool,
}
enum Action {
Redact { operator: AnyRedaction },
Suppress,
}
```
`DetectionInput::policies: Vec`. `AnyPolicy` deleted. `PolicyStore`'s typed buckets reduce to a single `Vec<Arc>`. The resolver walks rules and skips ones whose `operator.modality()` doesn't match the entity's modality.
Trade-offs to investigate
The compile-time guarantee is the only real win. Everything else is overhead the current shape forces on consumers.
Mitigating the loss of compile-time check
Two ways to recover early validation:
(1) feels right for a redaction engine where silent skips would be a compliance gap.
What to look at
Out of scope
References