Skip to content

Add per-request purpose / role scope for policy predicates #355

Description

@martsokha

Regulatory driver

Two independent regulations demand the same request to produce different redaction outputs depending on who is asking and why:

  • PCI DSS v4.0.1 Req 3.4.1 — PAN display defaults to last-4 only, but "personnel with a legitimate business need" may see first-6 + last-4. Same document, two audiences, two masks.
  • CCPA §1798.105(d) — nine enumerated retention exceptions (fraud detection, legal obligation compliance, security incident detection, internal-uses-compatible-with-context, …). A deletion request must delete unless the retained data falls under one of these purposes. Same document, "delete-all" for most callers, "keep the fraud-relevant subset" for the anti-fraud pipeline.

Both cases share a shape: the policy is the same regulatory template; the predicate branches on request-scoped context.

Current gap

AuditContext currently carries languages, countries, tags, and correlation_id — but nothing that names the requester's purpose or role. Predicates today can key off entity facts (LabelOneOf, TagOneOf, Confidence, CoRef) or document-level facts (DocumentPredicate), but not off request-scoped context. Hosts today either:

  • submit different policy sets per role/purpose (denormalising the same regulatory template into N variants), or
  • pre-annotate reviewer overrides on the audit (moving compliance logic into the caller).

Both defeat the point of shipping a single canonical HIPAA/PCI/CCPA template.

Proposed shape

Add request-scoped context fields, mirrored to elide's Scope:

// nvisy-schema::plan::ScopeParams
pub struct ScopeParams {
    // ...existing fields...
    /// Named business purpose driving this request. Compared to
    /// `Predicate::PurposeIs`. Free-form string; policies define
    /// their own vocabulary (e.g. "fraud_detection", "audit",
    /// "customer_support"). Empty means unscoped.
    pub purpose: Option<String>,
    /// Named roles the requester holds. Compared to
    /// `Predicate::RoleContains`. Multi-value because a caller
    /// may hold several (e.g. `["support_agent", "fraud_reviewer"]`).
    pub roles: Vec<String>,
}

And two matching Predicate variants:

Predicate::PurposeIs { purpose: String },     // scope.purpose == purpose
Predicate::RoleContains { role: String },     // scope.roles.contains(role)

Composable via existing All/Any/Not. Threaded onto AuditContext so anonymize sees the same purpose/roles analyze did.

Elide-side dependency

None. This is a nvisy-schema/nvisy-policy/nvisy-engine change; the predicates evaluate on runtime-side context before compiling to elide's Anonymizer. Elide's scope stays as-is.

Acceptance

  • PCI DSS template ships a single policy whose PAN rule masks to last-4 by default and to first-6+last-4 when roles contains "pci_business_need".
  • CCPA template ships a single policy whose PII erase rule is bypassed when purpose is one of the nine §1798.105(d) exception values.
  • End-to-end test: same document + same policy, two requests differing only in purpose/roles, produce different redacted outputs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    engineredaction engine, pipeline runtime, orchestration, configurationfeatrequest for or implementation of a new featureontologyentities, policies, contexts

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions