647acb5909
Extend the past-epoch dedup check to PrivateMessage commits too. Previously only the PublicMessage branch returned `GroupEventResult.Duplicate` for a stale commit echo; the PrivateMessage branch called `groupManager.decrypt()` directly, which consumed a generation on the sender's ratchet and then failed with `Message epoch X doesn't match current epoch Y` — polluting the harness log and (worse) burning the real commit's generation slot. Peek the epoch from the parsed PrivateMessage before touching the secret tree: past-epoch → Duplicate, future-epoch → Error, same-epoch falls through to the existing decrypt-and-dispatch path. https://claude.ai/code/session_016kAxdp6ubB5CnF9URhCEzP