Redactions
from jes import Redactions
from jes.redactions import TOKEN_RE
The placeholder tokens of one conversation and the values they replace. Use one store per conversation, and pass it to each check. See Multi-turn conversations.
Redactions(
*,
scope: bytes | None = None, # None: a random in-memory scope
max_entries: int = 1_000,
max_bytes: int = 8_388_608,
)
scope names the conversation, for example
f"{tenant}:{conversation_id}".encode(). It must be 1 to 1,024 bytes. A saved
store loads only under the same scope. max_entries and max_bytes cap the
store and must be positive integers. A bad argument raises RedactionError.
Properties
| Name | Meaning |
|---|---|
scope_id | A SHA-256 hex digest of the scope. Safe to log, and equal for stores of the same conversation. |
len(store) | The number of entries |
dumps
store.dumps(key: bytes, *, associated_data: bytes) -> bytes
Encrypts the store with AES-256-GCM, using a fresh nonce each time. key must
be 32 bytes. associated_data must be 1 to 4,096 bytes, and must match on
load. Needs jes[crypto]; without it, dumps raises PolicyError.
loads
Redactions.loads(
blob: bytes,
key: bytes,
*,
scope: bytes,
associated_data: bytes,
max_entries: int = 1_000,
max_bytes: int = 8_388_608,
) -> Redactions
Decrypts a blob saved by dumps. Needs jes[crypto]. The limits bound the
loaded store. It raises RedactionError on a wrong key, scope, or associated
data, on a blob that isn't a store or has another version, and on a blob that
holds more than the limits allow.
Each blob records its format version, and loads rejects any other version.
TOKEN_RE
TOKEN_RE = re.compile(r"\[JES_PII_[A-Za-z0-9_-]{22}\]")
Matches a placeholder, for example [JES_PII_3q2-7wEjT0mYb1kXoA9xVw].
Behavior
- A placeholder is an HMAC of its value under the store's random secret. The same value always gets the same placeholder in one store, and no other store can produce or restore it.
- Values leave a store only through
check_outputrestoration anddumps. No API restores arbitrary text. - A store can't be copied or pickled:
copy.copy,copy.deepcopy, andpickleraiseTypeError. Save it withdumps. Itsreprshows only the start of the scope id and the entry count. - A check stages its new entries and commits them only when the result is complete and allowed. A block or error leaves the store unchanged.
- A check that would push the store past
max_entriesormax_bytesblocks withredaction_store_full. More redactions in one check than the guard'sLimits(max_redactions=...)block withtoo_many_redactions. Neither raises. - A store is safe to share between threads.
- Key rotation and replay protection are your responsibility.