Skip to content

fix: number redaction tokens in document order#165

Merged
sidmohan0 merged 2 commits into
devfrom
fix/redact-token-document-order
Jul 6, 2026
Merged

fix: number redaction tokens in document order#165
sidmohan0 merged 2 commits into
devfrom
fix/redact-token-document-order

Conversation

@sidmohan0

Copy link
Copy Markdown
Contributor

Summary

  • redact() numbered per-type tokens in reverse document order: counters incremented during the right-to-left span-replacement loop, so the last occurrence of each entity type got _1 (e.g. two emails redacted as [EMAIL_2] ... [EMAIL_1]). Replacements (and the mapping) are now precomputed in ascending document order, then spans are applied right-to-left so offsets stay valid — the first occurrence always gets _1 for the token and pseudonymize strategies.
  • RedactResult.entities is now ordered by document position (ascending, was descending). All in-repo consumers (agent.py, LiteLLM guardrail, Claude Code hook) are order-independent.
  • Mapping originals are now read from the immutable input text rather than the partially-replaced working string, preventing an already-inserted token from leaking into a mapping value when spans overlap.
  • Behavior change documented under [Unreleased] in CHANGELOG.MD, including the pre-existing mask mapping-collision limitation (being addressed separately).

Note: the datafog-lp in-browser demo intentionally still mirrors the old numbering; it gets flipped (plus parity fixtures regenerated) once this ships in a release.

Test plan

  • New regression tests test_redact_token_numbering_follows_document_order and test_redact_pseudonymize_numbering_follows_document_order (written first, failed with the reversed output before the fix)
  • tests/test_engine_api.py — 16 passed
  • Full suite minus OCR/image/Spark/accuracy: 385 passed; remaining failures reproduce on clean dev (CLI smoke test, missing spaCy model)
  • pre-commit (isort, black, flake8, ruff, secrets) passes on changed files

sidmohan0 added 2 commits July 5, 2026 20:42
redact() assigned per-type counters during the right-to-left span
replacement loop, so with multiple entities of the same type the last
occurrence received _1 (e.g. [EMAIL_2] ... [EMAIL_1]). Replacements are
now precomputed in ascending document order and applied right-to-left,
so the first occurrence of each type always gets _1 for the token and
pseudonymize strategies.

Also: RedactResult.entities is now ordered by document position
(ascending), and mapping originals are read from the immutable input
text instead of the partially-replaced string, preventing token leakage
into mapping values on overlapping spans.
- redact() now suppresses overlapping/duplicate entity spans before
  applying replacements: longest span wins, ties broken by entity type
  priority then confidence (same rule as the regex scan pipeline)
- mask strategy mapping is keyed by per-type indexed keys
  ([EMAIL_MASK_1]) instead of the mask string, which collided for
  distinct same-length values and silently dropped originals
@sidmohan0 sidmohan0 merged commit 12ea2d4 into dev Jul 6, 2026
50 checks passed
@sidmohan0 sidmohan0 mentioned this pull request Jul 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant