symfonic.tools.corpus_scrub.scrubber¶
scrubber ¶
v7.8.0 corpus-scrub engine.
:class:CorpusScrubber is the main entry point. It walks recursive
JSON-shaped values (dict / list / str) and applies the redaction rules
defined in :mod:symfonic.tools.corpus_scrub.patterns, replacing
matched PII / credential substrings with opaque, counter-indexed
tokens that stay stable WITHIN a single CorpusScrubber instance.
The token mapping is INSTANCE-SCOPED, not module-scoped: each corpus export gets its own scrubber and its own mapping so the same email in a different export does not leak its token cross-corpus.
Drop-on-doubt: rows whose unresolved-match count exceeds
unresolved_threshold are returned as None so the caller can
filter them out. An "unresolved match" is a substring that fits the
GENERIC PII shape but doesn't match a more specific pattern -- the
scrubber emits a warning-shaped sentinel and counts it. v7.8.0 ships
a conservative default: no heuristic unresolved detection beyond the
patterns themselves (count is always 0). Adopters can subclass to
add domain-specific unresolved scanners (e.g. seller-name allowlist).
CorpusScrubber ¶
CorpusScrubber(
*,
value_credential_patterns: Iterable[
tuple[str, Pattern[str]]
] = DEFAULT_VALUE_CREDENTIAL_PATTERNS,
pii_patterns: Iterable[
tuple[str, Pattern[str]]
] = DEFAULT_PII_PATTERNS,
amazon_keep_patterns: Iterable[
tuple[str, Pattern[str]]
] = AMAZON_KEEP_PATTERNS,
amazon_redact_patterns: Iterable[
tuple[str, Pattern[str]]
] = AMAZON_REDACT_PATTERNS,
unresolved_threshold: int = 3,
)
Apply redaction rules to a corpus row.
Token-mapping state is instance-scoped. Construct one scrubber
per corpus export so co-occurrence tokens (<REDACTED-EMAIL-1>)
are stable within the export and not leaked across exports.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
value_credential_patterns
|
Iterable[tuple[str, Pattern[str]]]
|
Credential-shaped substring
patterns applied to VALUES. Default:
:data: |
DEFAULT_VALUE_CREDENTIAL_PATTERNS
|
pii_patterns
|
Iterable[tuple[str, Pattern[str]]]
|
Generic-PII patterns. Default:
:data: |
DEFAULT_PII_PATTERNS
|
amazon_keep_patterns
|
Iterable[tuple[str, Pattern[str]]]
|
Amazon structural identifiers to KEEP
as-is. Default: :data: |
AMAZON_KEEP_PATTERNS
|
amazon_redact_patterns
|
Iterable[tuple[str, Pattern[str]]]
|
Amazon account-id patterns to REDACT.
Default: :data: |
AMAZON_REDACT_PATTERNS
|
unresolved_threshold
|
int
|
Maximum unresolved substring count
allowed per row before |
3
|
Source code in src/symfonic/tools/corpus_scrub/scrubber.py
scrub_row ¶
Scrub a single corpus row.
When drop_on_doubt is True and the unresolved count
exceeds unresolved_threshold, the row is dropped and
ScrubResult.scrubbed is None.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
row
|
Any
|
The JSON-shaped row to scrub. Typically a dict. |
required |
drop_on_doubt
|
bool
|
When True (default), enforce
|
True
|
Source code in src/symfonic/tools/corpus_scrub/scrubber.py
scrub_string ¶
Redact a single string.
Returns:
| Type | Description |
|---|---|
tuple[str, dict[str, int], int]
|
|
Algorithm
- Find all keep-pattern spans; mark them as protected.
- For each redact pattern, replace matches OUTSIDE the protected spans with stable per-label tokens.
- Unresolved count is held at 0 in v7.8.0 -- adopters who want heuristic unresolved detection subclass and override.
Source code in src/symfonic/tools/corpus_scrub/scrubber.py
scrub_value ¶
Recursively scrub a JSON-shaped value.
Strings are passed to :meth:scrub_string. Dicts and lists
recurse. Other types (int, float, bool, None) pass through.
Source code in src/symfonic/tools/corpus_scrub/scrubber.py
ScrubResult
dataclass
¶
ScrubResult(
scrubbed: Any | None,
match_counts: dict[str, int] = dict(),
unresolved: int = 0,
dropped_reason: str | None = None,
)
Per-row scrub outcome.
Attributes:
| Name | Type | Description |
|---|---|---|
scrubbed |
Any | None
|
The redacted row, or |
match_counts |
dict[str, int]
|
Mapping |
unresolved |
int
|
Count of unresolved suspicious substrings (drop
threshold check is the caller's responsibility -- the
scrubber returns the count but only enforces threshold on
|
dropped_reason |
str | None
|
Human-readable reason when |
scrub_jsonl_file ¶
scrub_jsonl_file(
*,
input_path: Path,
output_path: Path,
scrubber: CorpusScrubber | None = None,
drop_on_doubt: bool = True,
) -> dict[str, Any]
Stream a JSONL file through :class:CorpusScrubber.
Input may be plain .jsonl or gzipped .jsonl.gz. Output
extension determines compression (.gz -> gzip). Per architect
spec, the canonical output shape is .jsonl.gz so the file is
transferable without further compression.
Returns a summary dict
{
"input_rows": int,
"output_rows": int,
"dropped_rows": int,
"match_counts": dict[str, int],
}
The summary is also the natural shape for a manifest sidecar.