Every existing truncation guard validates that the anchor exists and matches.
In #133716 the anchor was valid but named the wrong row (a stale Desktop
binding), and the gateway archived 107 live messages (430 -> 323) with no
friction — nothing bounded how much history one submit may drop.
_truncate_history_for_submit now refuses (4033, data: archived_messages,
archived_user_turns) when the cut archives more than one user turn and the
submit lacks confirm_deep_truncate. One turn is the legit implicit-target
allowance: tail regenerate and edit-last-message drop exactly that turn.
History and DB stay untouched on refusal.
Desktop: restore and edit name their target explicitly, so runRewindSubmit
sends confirm_deep_truncate by default (via truncateSubmitParams); regenerate
sends it only after the Half-A confirm. The TUI sends no truncation params.
Contract field added to PromptSubmitParams and the generated TS/OpenRPC
regenerated with scripts/gen_gateway_contracts.py.
Trade-off: an older Desktop build against this backend gets 4033 on
restore/edit of an older turn until it updates.
Fixes#133716
Reported-by: @Enferlain