car-sync 0.34.0

Multi-device sync core for Common Agent Runtime — replica-tagged append-only oplog + deterministic CRDT fold
Documentation
//! What syncs across a user's devices, what stays device-local, and how a
//! synced policy is reconciled when the devices run different operating systems.
//!
//! # The problem
//!
//! "Onboard once" must copy the config you *set* — not the secrets and grants
//! that are bound to a specific machine or OS. Sync the wrong thing and you
//! either leak a bearer token to every device (and the relay) or push a macOS
//! TCC grant onto a Linux box where it's meaningless. So every piece of CAR's
//! on-device state is classified once, here, and the daemon's tee consults this
//! before it puts anything on the oplog.
//!
//! # The two rules
//!
//! 1. **Portable = policy you authored.** Which tier each agent gets, which
//!    handles may approve, your agent definitions, routing priors, the
//!    knowledge graph, and the HITL decisions you already made (recorded by a
//!    stable *fingerprint*, so "I approved this" applies on every device). These
//!    are OS-agnostic *intent* — they ride the E2E oplog and land on every
//!    device.
//! 2. **Device-local = secrets and machine/OS-bound facts.** OS keychain
//!    material (Slack bot tokens, the Parslee tokens themselves), TCC/permission
//!    *grants* (the OS's yes/no, re-obtained per device), machine paths, the
//!    device pairing code, and voiceprints (bound to this device's mic). These
//!    **never** leave the device — syncing them would leak a secret or break on
//!    a different OS.
//!
//! # Cross-OS reconciliation
//!
//! A *portable* posture can name a capability that only exists on one OS —
//! e.g. an agent granted `Automation`/`Calendar`/`HealthRead` (macOS TCC
//! domains). The posture still syncs verbatim (it's intent). What differs per
//! device is the **grant**, which is device-local and resolved by the platform
//! backend: `car-permissions`' Linux/Windows backends already report such a
//! domain as `NotApplicable`. So the split is clean: sync the *policy*, let each
//! OS resolve the *capability*. Nothing here re-implements that resolution — it
//! only guarantees the grant itself is never teed.

/// How a CAR config domain relates to multi-device sync.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SyncClass {
    /// OS-agnostic policy/intent — replicates verbatim to every device.
    Portable,
    /// A secret or machine/OS-bound fact — never leaves the device.
    DeviceLocal,
}

impl SyncClass {
    pub fn is_portable(self) -> bool {
        matches!(self, SyncClass::Portable)
    }
}

/// A named on-device state domain and its sync classification.
#[derive(Debug, Clone, Copy)]
pub struct SurfacePolicy {
    /// Stable identifier (matches the daemon's config/tee surface name).
    pub domain: &'static str,
    pub class: SyncClass,
    /// Why — the load-bearing rationale (secret? OS-bound? pure policy?).
    pub note: &'static str,
}

use SyncClass::{DeviceLocal, Portable};

/// The canonical partition of CAR's on-device state. The daemon's tee syncs
/// exactly the [`Portable`] domains and never the [`DeviceLocal`] ones.
pub const SURFACE_POLICIES: &[SurfacePolicy] = &[
    // --- Portable policy (rides the E2E oplog) ---
    SurfacePolicy {
        domain: "agent_permissions",
        class: Portable,
        note: "per-agent × per-tier posture (always-allow/require-approval/deny) — OS-agnostic intent; \
               the OS-level grant it may reference is device-local and resolved per platform",
    },
    SurfacePolicy {
        domain: "messaging_allowlist",
        class: Portable,
        note: "which handles/channels may approve — policy, not credentials",
    },
    SurfacePolicy {
        domain: "agent_definitions",
        class: Portable,
        note: "declarative agents (agents.json / declagents) — portable definitions",
    },
    SurfacePolicy {
        domain: "routing_priors",
        class: Portable,
        note: "learned capability-routing success priors — improves every device",
    },
    SurfacePolicy {
        domain: "memory_graph",
        class: Portable,
        note: "the knowledge/identity/skill graph — the point of shared memory across devices",
    },
    SurfacePolicy {
        domain: "approvals_ledger",
        class: Portable,
        note: "HITL decisions keyed by a stable fingerprint — a decision made on one device is \
               honored on all (no re-prompting the same hazard per machine)",
    },
    // --- Device-local (never teed) ---
    SurfacePolicy {
        domain: "parslee_tokens",
        class: DeviceLocal,
        note: "each device mints its own via login (correct); a token is a secret and per-device",
    },
    SurfacePolicy {
        domain: "keychain_secrets",
        class: DeviceLocal,
        note: "Slack bot/app tokens et al. live in the OS keychain — a secret; the portable \
               messaging config references them by a device-local keychain ref, re-provisioned per device",
    },
    SurfacePolicy {
        domain: "permission_grants",
        class: DeviceLocal,
        note: "the OS's actual yes/no for a capability (macOS TCC, etc.) — re-obtained per device; \
               syncing a grant across OSes is meaningless and unsafe",
    },
    SurfacePolicy {
        domain: "machine_paths",
        class: DeviceLocal,
        note: "cwd/home/worktree/model-cache paths differ per machine and OS",
    },
    SurfacePolicy {
        domain: "device_pairing",
        class: DeviceLocal,
        note: "the messaging pairing code binds one channel to this device",
    },
    SurfacePolicy {
        domain: "voiceprints",
        class: DeviceLocal,
        note: "voice enrollment is bound to this device's microphone/enrollment audio",
    },
];

/// The policy for `domain`, if known.
pub fn policy_for(domain: &str) -> Option<&'static SurfacePolicy> {
    SURFACE_POLICIES.iter().find(|p| p.domain == domain)
}

/// Does `domain` sync across devices? **Fails closed**: an *unknown* domain is
/// treated as device-local, so a newly-added config surface never leaks onto
/// the relay until it's been deliberately classified here.
pub fn is_portable(domain: &str) -> bool {
    policy_for(domain)
        .map(|p| p.class.is_portable())
        .unwrap_or(false)
}

/// Every domain that syncs — the tee's allowlist.
pub fn portable_domains() -> impl Iterator<Item = &'static str> {
    SURFACE_POLICIES
        .iter()
        .filter(|p| p.class.is_portable())
        .map(|p| p.domain)
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn secrets_and_os_grants_are_never_portable() {
        // The security-critical invariant: nothing that is a secret or an
        // OS-bound grant may ride the oplog.
        for d in [
            "parslee_tokens",
            "keychain_secrets",
            "permission_grants",
            "machine_paths",
            "device_pairing",
            "voiceprints",
        ] {
            assert!(!is_portable(d), "{d} must stay device-local");
        }
    }

    #[test]
    fn authored_policy_is_portable() {
        for d in [
            "agent_permissions",
            "messaging_allowlist",
            "agent_definitions",
            "routing_priors",
            "memory_graph",
            "approvals_ledger",
        ] {
            assert!(is_portable(d), "{d} is authored policy — should sync");
        }
    }

    #[test]
    fn unknown_domains_fail_closed_to_device_local() {
        // A new config surface someone forgot to classify must NOT leak.
        assert!(!is_portable("some_new_unclassified_surface"));
        assert!(policy_for("some_new_unclassified_surface").is_none());
    }

    #[test]
    fn every_policy_has_a_rationale() {
        for p in SURFACE_POLICIES {
            assert!(!p.note.is_empty(), "{} needs a rationale", p.domain);
        }
        // The tee's allowlist is exactly the portable set.
        assert_eq!(portable_domains().count(), 6);
    }
}