Three new tables + cache/sweep wiring per docs/fof-spec/
layer-5-prefilter-and-cache.md:
vouch_unlock_cache (reader_persona, author) -> winning V_x
- Records the V_x that last unlocked a post from this author. Next
post from same author: hot path is 1 HMAC + 1 AEAD attempt.
vouch_unreadable_posts (reader_persona, post_id)
- Queue of FoF posts that no held V_x currently unlocks. Swept when
a new V_x lands in the persona's keyring.
own_fof_post_ceks (author_persona, post_id) -> (CEK, slot_binder_nonce)
- Author-direct decrypt fast path. Populated at publish so authors
skip wrap-slot trial entirely when reading their own posts.
find_unlock_for_post:
- Cache fast path: look up winning V_x, prefilter, single AEAD.
- Full scan fallback on cache miss; record cache hit on success;
record unreadable on full miss.
read_fof_closed_body:
- Author-direct fast path: lookup_own_fof_post_cek before trial-unlock.
sweep_unreadable_on_new_v_x:
- Walks ALL unreadable for the persona (the new V_x can unlock posts
authored by anyone — the V_x owner may be a chain-link in some
third party's keyring). Wired into the vouch-grant scan path so
every new V_x triggers a sweep automatically.
Both FoF publish paths (Mode 1 + Mode 2) now cache CEK at publish.
Bug fix found by the sweep test: storage::get_post_with_visibility
wasn't loading fof_gating_json (always returned None). Fixed; reader
paths through that function now see the gating block.
16 fof:: tests pass (2 new: cache populates+hits, sweep after V_x
arrival). 150 total tests pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>