tsafe-collab 0.1.0

Collaboration service integration for tsafe — membership directory, DEK delivery, and recovery-share transport.
Documentation
use crate::error::CollabError;
use crate::types::{DekEnvelope, InviteToken, MemberEntry};

/// Abstraction over the collab-service transport layer (ADR-027).
///
/// Implementations are provided by integration crates (`tsafe-collab`).
/// `tsafe-core` MUST NOT import or depend on this trait — it is defined here
/// deliberately outside of core to enforce the architectural boundary.
///
/// Transport: synchronous HTTP via `ureq`, REST/JSON over HTTPS.  The base URL
/// is operator-configured at runtime; there is no compile-time default
/// (ADR-027 §"Paths foreclosed").
pub trait CollabRemote {
    /// Register `pubkey` as a member of `team_id`.
    ///
    /// Idempotent: calling `join` with an already-registered pubkey is not an
    /// error — the server returns success and the `joined_at` timestamp is
    /// unchanged.
    fn join(&self, team_id: &str, pubkey: &str) -> Result<(), CollabError>;

    /// List all current members of `team_id`.
    ///
    /// The caller must already be a member; non-members receive `NotMember`.
    fn members(&self, team_id: &str) -> Result<Vec<MemberEntry>, CollabError>;

    /// Store an age-encrypted DEK envelope on the server for later retrieval by
    /// the recipient.
    ///
    /// The caller must be a member of `team_id`.  The server stores the
    /// ciphertext opaquely — it cannot decrypt it.
    fn deliver_dek(
        &self,
        team_id: &str,
        recipient_pubkey: &str,
        envelope: DekEnvelope,
    ) -> Result<(), CollabError>;

    /// Retrieve the DEK envelope previously delivered to `recipient_pubkey`.
    ///
    /// Returns `None` if no envelope has been delivered yet.
    /// The caller must be a member of `team_id`.
    fn fetch_dek(
        &self,
        team_id: &str,
        recipient_pubkey: &str,
    ) -> Result<Option<DekEnvelope>, CollabError>;

    /// Create an invite token bound to `invitee_pubkey` for `team_id`.
    ///
    /// The returned `InviteToken` must be delivered to the invitee out-of-band.
    /// The invitee confirms their key matches `InviteToken::bound_pubkey` before
    /// calling `confirm_invite`.
    fn create_invite(
        &self,
        team_id: &str,
        invitee_pubkey: &str,
    ) -> Result<InviteToken, CollabError>;

    /// Confirm an invite and add `confirming_pubkey` to the team membership.
    ///
    /// Returns `PubkeyMismatch` if `confirming_pubkey` does not match
    /// `token.bound_pubkey`.  Returns `InviteNotFound` if the token is unknown
    /// or has expired.
    fn confirm_invite(
        &self,
        team_id: &str,
        token: &InviteToken,
        confirming_pubkey: &str,
    ) -> Result<(), CollabError>;

    /// Store an age-encrypted recovery-key share for `custodian_pubkey`.
    ///
    /// `share_ciphertext` is the output of `age_encrypt(share, custodian_pubkey)`.
    /// The server stores it opaquely.  The caller must be a member of `team_id`.
    fn deliver_recovery_share(
        &self,
        team_id: &str,
        custodian_pubkey: &str,
        share_ciphertext: &str,
    ) -> Result<(), CollabError>;

    /// Retrieve the recovery-key share stored for `custodian_pubkey`.
    ///
    /// Returns `None` if no share has been stored yet.
    /// The caller must be a member of `team_id`.
    fn fetch_recovery_share(
        &self,
        team_id: &str,
        custodian_pubkey: &str,
    ) -> Result<Option<String>, CollabError>;
}