Commit Graph

75 Commits

Author SHA1 Message Date
d91820b867 feat(core): id-centric targeted approve/receive with output sinks
Stop returning grant strings across UniFFI; approve yields typed outcomes and
pull/resume use transfer id plus path or ReceiveOutputSink. Document the
pairing/targeted event catalog and cover Android MediaStore-style sink contracts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 16:50:10 +02:00
3b7fd3d468 refactor(core): deepen domain stores and invitation-only persistence
Peel relationships, eligibility, and secrets off the shared pool into
AppDataStores adapters, split pairing service/protocol, and move the
invitation Repository into its own module so open_all owns schemas.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 15:51:35 +02:00
5c66c2ca65 refactor(core): open AppDataStores instead of exporting SqlitePool
Introduce persistence::open_all so domain stores (invitation, targeted,
blocked) are constructed once. Extract TargetedTransferStore and own it on
CoreInner; soft-close raw pool access for new callers.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 08:50:16 +02:00
a2385912c2 refactor(core): remove prototype contact paths for experimental saved devices
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 06:05:31 +02:00
0a6ecb5c3b merge: ticket 14 Apple saved-device core contract
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 05:39:40 +02:00
9c7697c90b feat(apple): exercise saved-device core contract harness
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 05:39:10 +02:00
039d3f942b merge: ticket 17 Linux saved-device core contract
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 05:13:00 +02:00
1e601e118a feat(linux): exercise saved-device core contract harness
Prove Secret Service-backed identity restart, the public saved-device
lifecycle, fault isolation, event recovery, and binding hygiene without
product UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 05:12:20 +02:00
e79aba7bcf merge: ticket 16 Windows saved-device core contract
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 05:11:51 +02:00
f603f5fb8d feat(windows): exercise saved-device core contract harness
Prove the DPAPI-backed Windows bridge can drive the full public saved-device
and targeted-transfer contract via an injectable API fake on non-Windows hosts
and real DPAPI under cfg(windows), without product UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 05:11:26 +02:00
0c317efe59 feat(android): exercise saved-device core contract harness
Prove Keystore-backed Android secret storage can drive the full public
saved-device and targeted-transfer contract without product UI.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 05:10:04 +02:00
8e8cab9b24 feat(core): add event revisions and saved-device local labels
Platform contract harnesses need monotonic event revisions for at-least-once
dedup and a typed rename API that survives listing.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 04:47:28 +02:00
89c2206f0f feat(core): harden saved-device control plane against hostile peers
Bound pending offers, enforce pre-approval limits and max_saved_devices,
add per-identity cooldowns, silently reject invalid traffic, and redact
sensitive values from production events and errors without inventing
accepted-transfer quotas.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 04:33:38 +02:00
a01d8328c9 merge: integrate resumable transfers with network profile compatibility
Combine ticket 11 resume/cancel/delete/idempotency with ticket 12 relay
profiles, protocol floors, and typed offer outcomes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 04:02:07 +02:00
8798edf0f2 feat(core): make targeted transfers resumable and idempotent
Preserve approved transfer auth and state across restart, resume without
re-approval, and keep cancel/delete from leaving usable orphan authorization.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 03:56:46 +02:00
4a83b18d42 feat(core): honor network profiles and protocol floors for saved devices
Targeted offers and pairing now validate relay-policy compatibility and
reject protocol downgrades with typed unavailable/timeout/incompatibility
errors, without falling back to ordinary shares.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 03:54:13 +02:00
e2cfad7fb3 fix(core): tear down shares on forget/block and fail-closed blocks
Cancel targeted protocol shares synchronously before await, and treat block
lookup errors as denied so store failures cannot admit blocked peers.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 03:35:18 +02:00
22c842acb1 merge: integrate targeted transfers with revoke/forget/block
Combine ticket 09 lifecycle APIs with ticket 10 targeted-transfer protocol
and wire forget/block to real cancel_targeted_transfers_for_peer.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 03:30:33 +02:00
11fae16391 feat(core): revoke, forget, block, and rotate device relationships
Give saved-device owners immediate local control over grants and identity-wide
denies, with minimal tombstones for replay rejection and a ticket-10 hook for
targeted-transfer cancellation.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 03:27:28 +02:00
f4cec1415c feat(core): complete one approved targeted transfer between saved devices
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 03:22:38 +02:00
193fe7c757 feat(core): save devices through mutual consent and grants
Establish PendingOutgoing/PendingIncoming relationships over a token-bound
pairing protocol, exchange directional grants with challenge-response proofs
and a final ack before Saved, and merge simultaneous initiations without a
second prompt.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 03:03:43 +02:00
04a31e8a24 feat(core): create pairing eligibility after completed transfers
Add the experimental eligibility control plane so either endpoint of a
fully completed authenticated invitation transfer can start one single-use
pairing attempt within 24 hours, with secrets held in custody and invalid
requests rejected silently.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-11 02:34:18 +02:00
eefedd0cb0 fix(core): harden protected identity startup 2026-08-09 20:25:16 +02:00
429987785e feat(core): protect secrets on native platforms 2026-08-09 19:34:34 +02:00
931b297321 test(core): harden secret custody recovery 2026-08-09 18:44:58 +02:00
1cd09a2ec3 feat(core): add recoverable secret custody 2026-08-09 18:37:54 +02:00
b128457137 docs(core): clarify experimental domain versions 2026-08-09 18:07:03 +02:00
564b86c28c feat(core): establish saved device domain seam 2026-08-09 18:01:02 +02:00
225ff9ad22 fix: stop the device picker hanging on an offer
Two causes. The connect step had no timeout, so an unreachable device was
retried indefinitely instead of falling through to hold-for-later; it now
gives up after 15s and holds the offer as designed.

The picker also waited on the whole exchange, which includes a person on the
other device deciding — up to two minutes. It now closes on tap and reports
the outcome as a message, and a decline or an unanswered offer is shown as
information rather than an error, since the offer did arrive.
2026-08-07 10:49:32 +02:00
d8587851a3 feat(core): offer an existing share to a remembered device
Another way to deliver an invitation the user already created, alongside the
QR code, rather than a second share of the same files: the ticket handed over
is the stored one and the transfer id is unchanged.

Only an active share can be offered. A stopped one no longer serves its
content, so handing out its ticket would promise nothing.
2026-08-07 10:14:53 +02:00
94a8ba2103 feat(core): hold undeliverable offers and collect them on demand
An unreachable device is a delay, not a failure: the share stays here and
the ticket waits in held_offers (schema 8 -> 9) until that device comes and
collects it. No server and no push, per the design.

Polling needs no grant proof. iroh has already authenticated the remote
endpoint key, and a device is only handed offers addressed to precisely that
endpoint, so a stranger polling learns nothing. Offers are consumed on
delivery, so polling twice does not re-deliver, and cancelling the transfer
withdraws the waiting ticket.

Polling is rate limited per device: it tells every contact the app was
opened, so it must never become a presence beacon.
2026-08-06 18:47:29 +02:00
e041fbeda2 feat(core): send transfers straight to a paired device
Adds SubmitOffer to the contacts ALPN: the sender creates an ordinary share
and pushes the ticket over an authenticated connection, replacing the QR
code without changing the transfer itself.

Only the receiving user is prompted. The sender pre-authorises the target
endpoint before offering, and the approval service now honours an existing
access session, so the handshake the receiver runs next does not ask the
sender to approve a transfer they initiated. An unsolicited ticket receive
still prompts as before.

The ticket leaves the core only when the user accepts; declining yields
nothing. Offer-created shares are never public, one prompt per device is
pending at a time, a decline starts a cooldown, and forgetting a device
clears any prompt it left on screen.
2026-08-06 16:56:52 +02:00
7aa99304b2 feat(core): add the contacts protocol and grant exchange
New /vnidrop/offer/1 ALPN carrying grant delivery and revocation, with a
per-connection challenge so a captured proof cannot be replayed onto another
connection. Unlike the transfer handshake, this serves nobody without a
grant, so an unpaired device cannot raise a prompt on the far side.

A delivered grant is never stored on arrival: it waits for the local user's
consent, so an unsolicited grant cannot create a contact. Forgetting a
contact revokes locally first and notifies the peer best effort. A blocked
endpoint is refused indistinguishably from any other refusal.

Adds the UniFFI surface for listing, pairing, forgetting, blocking, labels,
and grant lifetime.
2026-08-06 16:35:36 +02:00
9fbcf653e8 feat(core): persist contacts, grants, and the block list
Schema 7 -> 8 adds contacts, grants_issued, grants_held, and
blocked_endpoints. Kept in their own module so repository.rs does not grow
further; the tables migrate with the rest of the schema through the shared
pool.

Revocation tombstones rather than deletes, so a returning peer is answered
Revoked instead of Unknown and can drop its dead entry. Blocking revokes any
outstanding grant, and unblocking does not hand access back.
2026-08-06 16:12:31 +02:00
4cfee786fc feat(core): add grant primitives for device history
Grants are the capability a device issues so a known peer may reach it. The
issuer is the only party that can validate one, which is what makes consent
and revocation enforceable without the peer's cooperation.

Pure module: proof construction and constant-time verification bound to the
challenge and both endpoint ids, idle expiry renewed on use, and secrets
redacted in Debug output.
2026-08-06 16:01:20 +02:00
407a0d2d60 feat(release): unify cross-platform versioning 2026-07-28 08:22:58 +02:00
b8e8dd8644 test(core): wait for delivery event visibility 2026-07-24 21:05:37 +02:00
425500ecf2 fix(core): report receiver failures to sender 2026-07-24 14:52:09 +02:00
0448137d84 fix(core): abort send when provider stream closes 2026-07-24 00:11:58 +02:00
4074f4bee8 feat(storage): clear inactive transfer cache 2026-07-23 22:22:30 +02:00
7592d49a59 fix(deps): update iroh to 1.0.3 2026-07-23 16:50:27 +02:00
a0bcc5dbff feat(network): add relay connection policies 2026-07-23 15:14:03 +02:00
cbace73908 feat(network): support custom relay servers
Add strict custom Iroh relay profiles with safe restart and rollback across the Rust core, Compose apps, and Apple apps. Preserve multi-relay invitations and fail closed on configuration or recovery mismatches.
2026-07-23 14:27:40 +02:00
f0b06ad1cf fix(transfer): finalize delivery completion 2026-07-22 21:34:33 +02:00
82fb549576 fix(core): preserve typed transfer failures 2026-07-22 20:43:15 +02:00
b46c5e7d72 fix(storage): reclaim transfer cache and track received files 2026-07-22 16:03:37 +02:00
a8a873c83d build: unify project development commands 2026-07-21 18:07:06 +02:00
9e8564e013 refactor(platform): remove Apple targets from KMP 2026-07-21 17:09:59 +02:00
ea376a382b feat(apple): native SwiftUI app for iOS and macOS
Add a native SwiftUI VniDrop app (Send/Receive/Settings) talking to the
Rust core via generated UniFFI Swift bindings, plus the uniffi-bindgen
helper crate. iOS uses a TabView, macOS a NavigationSplitView sidebar.
2026-07-18 22:06:30 +02:00
d2d3c45180 fix(core): make import cancellation test deterministic 2026-07-15 23:20:22 +02:00