tsafe_collab/remote.rs
1use crate::error::CollabError;
2use crate::types::{DekEnvelope, InviteToken, MemberEntry};
3
4/// Abstraction over the collab-service transport layer (ADR-027).
5///
6/// Implementations are provided by integration crates (`tsafe-collab`).
7/// `tsafe-core` MUST NOT import or depend on this trait — it is defined here
8/// deliberately outside of core to enforce the architectural boundary.
9///
10/// Transport: synchronous HTTP via `ureq`, REST/JSON over HTTPS. The base URL
11/// is operator-configured at runtime; there is no compile-time default
12/// (ADR-027 §"Paths foreclosed").
13pub trait CollabRemote {
14 /// Register `pubkey` as a member of `team_id`.
15 ///
16 /// Idempotent: calling `join` with an already-registered pubkey is not an
17 /// error — the server returns success and the `joined_at` timestamp is
18 /// unchanged.
19 fn join(&self, team_id: &str, pubkey: &str) -> Result<(), CollabError>;
20
21 /// List all current members of `team_id`.
22 ///
23 /// The caller must already be a member; non-members receive `NotMember`.
24 fn members(&self, team_id: &str) -> Result<Vec<MemberEntry>, CollabError>;
25
26 /// Store an age-encrypted DEK envelope on the server for later retrieval by
27 /// the recipient.
28 ///
29 /// The caller must be a member of `team_id`. The server stores the
30 /// ciphertext opaquely — it cannot decrypt it.
31 fn deliver_dek(
32 &self,
33 team_id: &str,
34 recipient_pubkey: &str,
35 envelope: DekEnvelope,
36 ) -> Result<(), CollabError>;
37
38 /// Retrieve the DEK envelope previously delivered to `recipient_pubkey`.
39 ///
40 /// Returns `None` if no envelope has been delivered yet.
41 /// The caller must be a member of `team_id`.
42 fn fetch_dek(
43 &self,
44 team_id: &str,
45 recipient_pubkey: &str,
46 ) -> Result<Option<DekEnvelope>, CollabError>;
47
48 /// Create an invite token bound to `invitee_pubkey` for `team_id`.
49 ///
50 /// The returned `InviteToken` must be delivered to the invitee out-of-band.
51 /// The invitee confirms their key matches `InviteToken::bound_pubkey` before
52 /// calling `confirm_invite`.
53 fn create_invite(
54 &self,
55 team_id: &str,
56 invitee_pubkey: &str,
57 ) -> Result<InviteToken, CollabError>;
58
59 /// Confirm an invite and add `confirming_pubkey` to the team membership.
60 ///
61 /// Returns `PubkeyMismatch` if `confirming_pubkey` does not match
62 /// `token.bound_pubkey`. Returns `InviteNotFound` if the token is unknown
63 /// or has expired.
64 fn confirm_invite(
65 &self,
66 team_id: &str,
67 token: &InviteToken,
68 confirming_pubkey: &str,
69 ) -> Result<(), CollabError>;
70
71 /// Store an age-encrypted recovery-key share for `custodian_pubkey`.
72 ///
73 /// `share_ciphertext` is the output of `age_encrypt(share, custodian_pubkey)`.
74 /// The server stores it opaquely. The caller must be a member of `team_id`.
75 fn deliver_recovery_share(
76 &self,
77 team_id: &str,
78 custodian_pubkey: &str,
79 share_ciphertext: &str,
80 ) -> Result<(), CollabError>;
81
82 /// Retrieve the recovery-key share stored for `custodian_pubkey`.
83 ///
84 /// Returns `None` if no share has been stored yet.
85 /// The caller must be a member of `team_id`.
86 fn fetch_recovery_share(
87 &self,
88 team_id: &str,
89 custodian_pubkey: &str,
90 ) -> Result<Option<String>, CollabError>;
91}