Class AuditingRedactor

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

public final class AuditingRedactor extends Object implements Redactor
Redactor decorator that emits a RedactionAuditEvent to a RedactionAuditListener every time the wrapped redactor matches at least one pattern (issue #80).

Why a decorator: keeps the Redactor SPI free of audit concerns. Audit emission is opt-in by wrapping:


 Redactor base    = PatternRedactor.withDefaults();
 Redactor audited = new AuditingRedactor(base, event -> {
     metrics.counter("redaction.applied",
             "pattern", event.patternCounts().keySet().toString(),
             "severity", event.highestSeverity().name())
            .increment(event.totalFindings());
     if (event.isCritical()) slack.alert("CRITICAL pii redaction in " + event.eventContext().tenantId());
 });
 

Composition: the audited redactor IS a Redactor, so it slots into anywhere a redactor is accepted — RedactingMemoryStore, RedactingAgentEventPublisher, CompositeRedactor, TenantPolicyRedactor per-tenant values. Stack order matters: wrap the audited redactor inside the tenant dispatcher to get per-tenant audit; wrap the dispatcher inside the audit decorator to get cross-tenant audit.

Hot-path safety: the listener is invoked synchronously, but any exception is caught and logged at WARN — a broken audit sink cannot break redaction itself. Listeners that need async dispatch should hand off to an executor inside RedactionAuditListener.onRedaction(com.tnsai.quality.redaction.RedactionAuditEvent).

scrubValue recurses through the SPI default impl, which calls back into scrubString(java.lang.String, com.tnsai.quality.redaction.RedactionContext) for every leaf String. Each leaf with findings produces one audit event. This may fan out heavily for large object trees — operators who want a single per-call audit should run the listener at a higher layer (e.g. inside their own decorator that wraps the RedactingMemoryStore).

Thread-safety: stateless beyond holding the immutable delegate + listener references.

Since:
0.9.0 (issue #80)
  • Constructor Details

  • Method Details

    • 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
    • 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