The persisted pin carried rendered session-context bytes but not the
privacy.redact_pii they were rendered under. After a restart, the first
internal event restored and reused them verbatim, so bytes rendered with
redaction off reached the model after redaction was switched on. Base
re-rendered under the current setting after a restart.
The pin now records redact_pii (the in-memory ephemeral_pin becomes
(change_key, text, redact_pii); the durable schema requires the flag).
Bytes from a different policy are never reused, including by internal
events in the same process, which had the same bypass until the next
human turn.
Simplified on the way:
- sanitize_prompt_pin is the one validator; the snapshot builder and
rehydrate no longer re-check the shape by hand, and use
PROMPT_PIN_VERSION instead of a literal.
- Rehydrate runs once, from the context path, and only when this process
holds no pins for the key (a restart). Eviction clears only the context
pin, so an evicted agent still re-renders instead of reviving the
snapshot. The channel path no longer rehydrates or takes a session id.
- _persist_prompt_pins drops its synchronous get_prompt_pin pre-check,
which took the store lock on the event loop; set_prompt_pin already
no-ops an unchanged snapshot. Its call-count test goes with it.
- set_prompt_pin builds its candidate with dataclasses.replace like
set_model_override, and no longer accepts None (no caller cleared).
- The read-fence test folds into the restart/reset test.