Use flock for profile locks, hash secret record filenames under NAME_MAX,
and dismiss the starting overlay after the first init attempt.
Co-authored-by: Cursor <cursoragent@cursor.com>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
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.
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.
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.
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.
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.
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.
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.
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.