Items record sign_in_with {provider, account}; the browser bridge reports it
with the site's identities and saves it after the usual app prompt. Items
now keep fields they don't know, so editing in an older app never drops what
a newer one added.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
- kv-proto: email on setup, AccountInfo.email/is_admin, ServerInfo.registration, admin DTOs.
- kv-sync: prelogin by email or account id (setup QR / Emergency Kit), register with email,
set_email, /v1/admin/* client calls.
- kv-ffi: register_account(code, email), login(.., email), account_details, set_account_email,
admin_create_invite. kv-wasm: the same plus the full admin API for the web vault.
- Live test: two users on one server stay isolated; a new device signs in by email.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
VaultCore::delete_account verifies the password against the keyset (Secret Key from its sealed copy),
calls DELETE /v1/account when a server is configured (needs a session; a failure deletes nothing),
then wipes the local store and keeps only the device identity. Unit test plus a live test against a
real server.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Ignored like the other live test: a phone denied while pending keeps its
identity over a reinstall and a fresh Add-a-device QR signs it straight in as
a new device. Passed against kestrove-server reenroll 62fe35d.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
kv-sync: SyncError::DeviceRevoked for 403 device_revoked (drops the session);
the test server answers it after OPAQUE (before the ticket) and for valid
signatures by a revoked device's key.
kv-ffi: CoreError::DeviceRevoked; the foreign DeviceSigner gains reset() (new key
and id). On device_revoked the core resets the identity, forgets the session
and the kept OPAQUE input (no silent re-enrolment) and keeps the vault data; a
login carrying a ticket retries once as the new device. reset_device_identity()
runs the reset on demand. Signed calls (sync, devices, tickets, join) reset too.
kv-wasm: DeviceRevoked; the browser binding replaces the WebCrypto key and id in
IndexedDB (same name and platform), loginWithSetup retries once with a ticket;
resetDeviceIdentity().
Tests: end to end revoked -> reset -> re-enrol with the same ticket (kv-ffi and
web core), no-ticket path, in-browser IndexedDB reset.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
* kv-proto: ticket and join wire types, LoginFinish.ticket, b64_opt.
* kv-crypto: pairing.rs (HPKE AuthPSK seal/open with the QR secret as PSK, SAS code).
* kv-sync: onboarding.rs (kestrove://setup/v1 and kestrove://join/v1 codec with
strict validation), ApiClient ticket/join calls and login_with_ticket, test
server support for tickets and rendezvous.
* kv-ffi: parse/encode_setup_payload, setup_payload_for_kit, add_device_payload,
revoke_enrollment_ticket, login_with_setup, join_start/poll/cancel,
join_approve/send. The Secret Key is kept locally, HPKE-sealed to enc_pk.
* Tests: codec round trips and rejections, AuthPSK tamper cases, end-to-end
Add-device QR and scan-to-join between two clients, forged bundles, bound
tickets; live test extended with scan-to-join.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Tombstones carried no ciphertext, so the server could forge a delete (hiding
an item), and a device that pulled one accepted the next live version without
a seq comparison (delete-then-replay rollback).
* A delete is sealed like an item (same AAD and padding) with plaintext
{id, seq, deleted: true}; seq = working/base seq + 1. DELETE carries it as
the ItemPut body; the test server stores and serves it.
* replay: Base is None | LegacyTombstone | Sealed(plaintext). check_pulled
refuses a record without ciphertext, a deleted flag that disagrees with the
plaintext, or a tombstone naming another item, then requires seq to grow
for tombstones too. The `restores` exception is gone: edit beats delete
re-seals with max(edit, tombstone) + 1. next_version returns just seq.
* Legacy: a pulled tombstone without ciphertext is an integrity alarm (cannot
be told apart from a forgery). A pending legacy delete is sealed on push; a
legacy tombstone already held as base is accepted as unknown seq.
* engine: open_sealed, seal_tombstone; next_local_version -> u64. Merkle leaf
hashes a sealed tombstone's envelope like a live record's.
* kv-model Item: `restores` removed. kv-ffi delete_item seals the tombstone;
FFI surface unchanged.
* Tests: forged (unsealed and relabelled) tombstones raise an integrity alarm
(kv-sync and kv-ffi), replay after a tombstone, replays of the tombstone and
of the restore, legacy pending delete, shared item_replay.json table; the
live test now deletes an item through the real server.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
kv-store: SQLite (bundled) and in-memory Store holding only ciphertext
envelopes, with base copies for three-way merges and a 10-version history.
kv-ffi: VaultCore::open(path); persisted account record; local unlock key
(enable/unlock_with_local_key/disable); async sync API (configure_server,
set_device_signer, register_account, login, sync_now, logout, devices);
DeviceSigner foreign trait; IntegrityAlarm/PinMismatch/... error variants.
kv-sync: reqwest + rustls (TLS 1.3 only, ring) with SPKI pinning; OPAQUE
client on kv-proto's OpaqueSuite; RFC 9421 signing per kv_proto::httpsig;
pull/push with If-Match, field-level three-way merge, edit-beats-delete;
signed manifest (canonical CBOR, Merkle root, prev chain, Ed25519) with
rollback/fork/Merkle checks as hard integrity alarms. Tested against a
wiremock fake server, plus an #[ignore] test against kestrove-server.
kv-crypto: gen-vectors example and tests reproducing kestrove-spec/vectors.
Co-Authored-By: Claude Opus 5.5 <[email protected]>