A malicious server could re-serve an old but valid envelope of an item under
a new, higher revision: the AAD does not bind the revision and the Merkle
root was only checked when head_revision == cursor. The client took it as
the latest version, rolled the item back and signed over it in its next
manifest.
* kv-model Item: `seq: u64` (serde default 0) and `restores: Option<u64>`.
* kv-sync replay module: pure decision rules (check_successor,
next_version, stamp, same_content). Every pulled live record must
authenticate and have seq > the base's seq, unless it carries
restores = t with base_revision < t < revision (edit beat a delete this
device never saw) or is identical to the base. Otherwise: Integrity alarm.
Tombstones and items without a base pass.
* engine: check on every pulled record; edit-beats-delete re-seals with
restores = tombstone revision; merges write seq = max + 1 and adopt the
remote version when only seq/restores differ. Public
engine::next_local_version gives the seq/restores for a local save
(used by kv-ffi save_item and the tests).
* verify_manifest checks the Merkle root whenever no held item is newer
than head_revision (previously only at head == cursor).
* Tests: replay alarm (clean and pending receivers, plus kv-ffi), harmless
replay of the current version, tampered envelope, concurrent equal-seq
edits across three devices, edit-beats-delete with a device that skipped
the tombstone and a later replay of the restore, legacy items without seq,
hidden item with cursor past the manifest head, and the shared
item_replay.json decision table.
* gen-vectors: envelope vector item payload now includes seq.
FFI surface unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>