# Phase 3: Vault - Context

**Gathered:** 2026-02-26
**Status:** Ready for planning

<domain>
## Phase Boundary

Complete the PII protection workflow: placeholder vault accumulation from scan_input(), client-side deanonymize() to restore originals, clear_cache() to reset, and scan_output() for scanning LLM responses. No second server call for deanonymize — vault is client-side on the Guard instance.

</domain>

<decisions>
## Implementation Decisions

### Placeholder format
- Use whatever format the server returns — SDK does not generate placeholders
- Server format is `[LABEL_NAME-HASH]` (e.g. `[EMAIL_ADDRESS-A1B2]`)
- Placeholders are **opaque tokens** to developers — no documented format, just pass through deanonymize()
- No developer access to the raw placeholder mapping (vault is fully internal)

### Deanonymize behavior
- **Exact match only** — if LLM modifies a placeholder, it stays unreplaced
- **Restore all occurrences** — if LLM repeats a placeholder, every occurrence gets restored
- Returns a **plain string** — no metadata, no result object
- If no placeholders found in text, **log a debug warning** and return text unchanged

### Session lifecycle
- Vault lives on the Guard instance (already scaffolded: `self._vault: dict[str, str]`)
- Multiple scan_input() calls accumulate into the same vault (already coded: `self._vault.update()`)
- One vault per Guard instance — one Guard per session/developer/API key
- No concurrent/thread-safety needed
- clear_cache() resets the vault (already coded: `self._vault.clear()`)

### scan_output()
- **Included in Phase 3** — previously raised NotImplementedError, now fully implemented
- Same signature as scan_input(): `guard.scan_output(text, scanners=[...])`
- Calls the same `/api/security/scan` server endpoint
- Also accumulates placeholders into the vault (consistent with scan_input())

### Claude's Discretion
- Per-scanner detection breakdown: whether placeholders appear in detections or only in processed_text
- Implementation details of the debug warning logger
- Any internal refactoring needed to support scan_output() alongside scan_input()

</decisions>

<specifics>
## Specific Ideas

- Vault logic is already scaffolded in guard.py (lines 56-57, 153, 164-179, 181-187) — Phase 3 completes and verifies this
- scan_output() is essentially the same server call as scan_input() with the same response handling

</specifics>

<deferred>
## Deferred Ideas

None — discussion stayed within phase scope

</deferred>

---

*Phase: 03-vault*
*Context gathered: 2026-02-26*
