Skip to main content

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}