Class TenantPolicyRedactor

java.lang.Object
com.tnsai.quality.redaction.TenantPolicyRedactor
All Implemented Interfaces:
Redactor

public final class TenantPolicyRedactor extends Object implements Redactor
Redactor that dispatches each call to a per-tenant Redactor based on the active RedactionContext (issue #80).

Resolution order on every call:

  1. RedactionContext.tenantPolicyId() — explicit override.
  2. EventContext.tenantId() from RedactionContext.eventContext() — implicit tenant scope carried by the framework's correlation context.
  3. The configured default redactor — used when neither id is set, or when no per-tenant override is registered for the resolved id.

Why dispatch per call (not per-thread cache): an agent may service multiple tenants in the same JVM, and a single SLF4J emit can cross tenant boundaries inside a multi-agent group. Looking up by RedactionContext on every call keeps the policy boundary exactly where the framework already enforces it.

Composition: each per-tenant value is itself a Redactor, so operators can stack CompositeRedactor (e.g. fast pattern + slow LLM-classifier) per tenant. The dispatcher is one layer above the composition, not a replacement for it.

Thread-safety: the registry is immutable after construction; dispatch is a single Map.get on each call.

Since:
0.9.0 (issue #80)
  • Constructor Details

    • TenantPolicyRedactor

      public TenantPolicyRedactor(Map<String,Redactor> byTenant, Redactor defaultRedactor)
      Parameters:
      byTenant - immutable mapping of tenant id → redactor; empty map makes every call fall through to defaultRedactor
      defaultRedactor - fallback used when no tenant id is resolvable, or no override is registered
      Throws:
      NullPointerException - if either arg is null
  • Method Details

    • builder

      public static TenantPolicyRedactor.Builder builder()
    • scrubString

      public RedactionResult scrubString(String input, RedactionContext ctx)
      Description copied from interface: Redactor
      Scrub sensitive patterns out of input, returning the redacted text + every match.
      Specified by:
      scrubString in interface Redactor
      Parameters:
      input - plaintext to scan; never null. "" is acceptable and returns an unchanged result.
      ctx - per-call context (sink, tenant policy)
      Returns:
      redacted text + findings; never null
    • scrubValue

      public Object scrubValue(Object input, RedactionContext ctx)
      Description copied from interface: Redactor
      Recursive scrub of a structured value — Map, List, or scalar. Walks Map<String, Object> and List<Object> (the framework's two carry-over container types from JSON deserialization), applying Redactor.scrubString(java.lang.String, com.tnsai.quality.redaction.RedactionContext) to every leaf String. Other scalars pass through unchanged.

      Default implementation handles Maps + Lists; redactors that need to peek into custom types override.

      Specified by:
      scrubValue in interface Redactor
    • maybeContainsSensitive

      public boolean maybeContainsSensitive(String input)
      Description copied from interface: Redactor
      Cheap pre-check: returns true when input might contain something worth scanning. Lets the framework short-circuit the regex loop on guaranteed-clean strings (UUIDs, numeric IDs, fixed-vocabulary enums). Default true because the safe fallback is "always scan"; override when you can do better.
      Specified by:
      maybeContainsSensitive in interface Redactor
    • resolve

      public Redactor resolve(RedactionContext ctx)
      Returns the Redactor chosen for ctx. Public for test inspection; the dispatch path uses it on every scrub call.
    • tenantOverrides

      public Map<String,Redactor> tenantOverrides()
      Snapshot of the registry for inspection / testing.
    • defaultRedactor

      public Redactor defaultRedactor()