Docs

api-ops

Source: crates/lvcs-api/src/ops.rs.

/// The JSON Schema of `op`'s params (decision 116).
///
/// What `lvcs mcp` lists as the `inputSchema` of `op`'s tool. Exhaustive
/// over `Op`, so a new op value doesn't compile without its params. A
/// [`retried`] op's params also take the optional [`REQUEST_PARAM`] string
/// (decision 163).
pub fn params_schema(op: Op) -> Schema {
    let mut schema = op_schema(op);
    if retried(op) {
        let field = schema
            .ensure_object()
            .entry("properties")
            .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
        if let Some(properties) = field.as_object_mut() {
            properties.insert(
                REQUEST_PARAM.to_owned(),
                serde_json::json!({
                    "type": "string",
                    "maxLength": REQUEST_ID_MAX_BYTES,
                    "description": REQUEST_DESCRIPTION,
                }),
            );
        }
    }
    schema
}

/// The params key of a retried request's id (decision 163): the shell
/// takes it out of the params before the op parses them.
pub const REQUEST_PARAM: &str = "request";

/// What a retried op's `request` param is for (decision 163). At most
/// `REQUEST_ID_MAX_BYTES` bytes.
pub const REQUEST_DESCRIPTION: &str = "a fresh id for this call; send the same id again only to retry a call whose answer you did not get";

/// Whether `op` is retried safely under a `request` id (decision 163).
///
/// True for every op that signs, publishes, appends, or delivers, so
/// sending one again with the same `request` gets the first answer and
/// applies once. `notifications` lists and consumes nothing, so it is not
/// (decision 187). Exhaustive over `Op`.
pub const fn retried(op: Op) -> bool {
    match op {
        Op::Edit
        | Op::Announce
        | Op::Note
        | Op::Hold
        | Op::Ask
        | Op::Answer
        | Op::Say
        | Op::WsFork
        | Op::WsMerge
        | Op::Grant
        | Op::Revoke
        | Op::Approve
        | Op::Checkpoint
        | Op::SecretConfirm
        | Op::TrackEdit
        | Op::TrackWrite
        | Op::NotificationsRead
        | Op::Run
        | Op::IssueCreate
        | Op::IssueEdit
        | Op::IssueClose
        | Op::IssueDevelop
        | Op::IssueCheckpoint
        | Op::AuthConfirm
        | Op::AuthLogout => true,
        // A login is a new pairing each time; waiting, pairing, and the
        // device list change nothing a second time.
        Op::AuthLogin | Op::AuthWait | Op::AuthPair | Op::AuthStatus | Op::AuthSign => false,
        Op::HostEnable | Op::HostDisable => false,
        // `key.new` makes a fresh key each time; the host's ops answer
        // from the registry's state, so sending one again is safe.
        Op::KeyNew | Op::Init | Op::Join | Op::Forget | Op::Project => false,
        Op::Hello
        | Op::Subscribe
        | Op::Open
        | Op::Read
        | Op::Snapshot
        | Op::ChannelRead
        | Op::TrackRead
        | Op::Notifications
        | Op::StopGate
        | Op::Status
        | Op::ChannelList
        | Op::RunList
        | Op::PeerList
        | Op::PresenceList
        | Op::WsList
        | Op::WsDetail
        | Op::Watch
        | Op::ProjectSummary
        | Op::Inbox
        | Op::Blame
        | Op::ChannelHistory
        | Op::Search
        | Op::Resources
        | Op::ThreadList
        | Op::ThreadRead
        | Op::RunView
        | Op::FilesList
        | Op::IssueList
        | Op::IssueView
        | Op::IssueRevisions
        | Op::CheckpointList => false,
    }
}

/// The JSON Schema of `op`'s own params type.
fn op_schema(op: Op) -> Schema {
    match op {
        Op::Hello => schema_for!(HelloParams),
        Op::Open => schema_for!(OpenParams),
        Op::Read => schema_for!(ReadParams),
        Op::Edit => schema_for!(EditParams),
        Op::Subscribe => schema_for!(SubscribeParams),
        Op::Announce => schema_for!(AnnounceParams),
        Op::Ask => schema_for!(AskParams),
        Op::Answer => schema_for!(AnswerParams),
        Op::Snapshot => schema_for!(SnapshotParams),
        Op::WsFork => schema_for!(WsForkParams),
        Op::WsMerge => schema_for!(WsMergeParams),
        Op::Note | Op::Hold => schema_for!(RespondParams),
        Op::Grant => schema_for!(GrantParams),
        Op::Revoke => schema_for!(RevokeParams),
        Op::SecretConfirm => schema_for!(SecretConfirmParams),
        Op::TrackRead => schema_for!(TrackReadParams),
        Op::TrackEdit => schema_for!(TrackEditParams),
        Op::TrackWrite => schema_for!(TrackWriteParams),
        Op::StopGate => schema_for!(StopGateParams),
        Op::Notifications => schema_for!(NotificationsParams),
        Op::NotificationsRead => schema_for!(NotificationsReadParams),
        Op::Status => schema_for!(StatusParams),
        Op::Say => schema_for!(SayParams),
        Op::Approve => schema_for!(ApproveParams),
        Op::Checkpoint => schema_for!(CheckpointParams),
        Op::ChannelRead => schema_for!(ChannelReadParams),
        Op::Run => schema_for!(RunParams),
        Op::Init => schema_for!(InitParams),
        Op::Join => schema_for!(JoinParams),
        Op::Forget => schema_for!(ForgetParams),
        Op::Project => schema_for!(ProjectParams),
        Op::KeyNew => schema_for!(KeyNewParams),
        Op::ChannelList => schema_for!(ChannelListParams),
        Op::RunList => schema_for!(RunListParams),
        Op::PeerList => schema_for!(PeerListParams),
        Op::PresenceList => schema_for!(PresenceListParams),
        Op::WsList => schema_for!(WsListParams),
        Op::WsDetail => schema_for!(WsDetailParams),
        Op::Watch => schema_for!(WatchParams),
        Op::ProjectSummary => schema_for!(ProjectSummaryParams),
        Op::Inbox => schema_for!(InboxParams),
        Op::Blame => schema_for!(BlameParams),
        Op::ChannelHistory => schema_for!(ChannelHistoryParams),
        Op::Search => schema_for!(SearchParams),
        Op::Resources => schema_for!(ResourcesParams),
        Op::ThreadList => schema_for!(ThreadListParams),
        Op::ThreadRead => schema_for!(ThreadReadParams),
        Op::RunView => schema_for!(RunViewParams),
        Op::FilesList => schema_for!(FilesListParams),
        Op::IssueCreate => schema_for!(IssueCreateParams),
        Op::IssueList => schema_for!(IssueListParams),
        Op::IssueView => schema_for!(IssueViewParams),
        Op::IssueEdit => schema_for!(IssueEditParams),
        Op::IssueClose => schema_for!(IssueCloseParams),
        Op::IssueDevelop => schema_for!(IssueDevelopParams),
        Op::IssueRevisions => schema_for!(IssueRevisionsParams),
        Op::IssueCheckpoint => schema_for!(IssueCheckpointParams),
        Op::CheckpointList => schema_for!(CheckpointListParams),
        Op::AuthLogin => schema_for!(AuthLoginParams),
        Op::AuthWait => schema_for!(AuthWaitParams),
        Op::AuthConfirm => schema_for!(AuthConfirmParams),
        Op::AuthPair => schema_for!(AuthPairParams),
        Op::AuthStatus => schema_for!(AuthStatusParams),
        Op::AuthLogout => schema_for!(AuthLogoutParams),
        Op::HostEnable | Op::HostDisable => schema_for!(HostToggleParams),
        Op::AuthSign => schema_for!(AuthSignParams),
    }
}

/// Whether `lvcs mcp` lists a tool for `op` (decisions 116 and 147):
/// every op but the connection mechanics, `hello` (sent once by `lvcs mcp`
/// itself), `subscribe` and `watch` (pushes have no MCP form). Authority comes
/// from capabilities alone, so `grant`, `revoke`, `secret.confirm`, and
/// `approve` are tools like any other op (decisions 147 and 148). The
/// host's ops (`project.summary` and `inbox` included) and `key.new` are CLI commands, not tools: an MCP session
/// acts in one project as one participant (decision 272).
/// Exhaustive over `Op`.
pub const fn mcp_tool(op: Op) -> bool {
    match op {
        // Pairing is a person's: `lvcs auth`, and the app.
        Op::Hello
        | Op::Subscribe
        | Op::Watch
        | Op::Init
        | Op::Join
        | Op::Forget
        | Op::Project
        | Op::KeyNew
        | Op::ProjectSummary
        | Op::Inbox
        | Op::Resources
        | Op::AuthLogin
        | Op::AuthWait
        | Op::AuthConfirm
        | Op::AuthPair
        | Op::AuthStatus
        | Op::AuthLogout
        | Op::HostEnable
        | Op::HostDisable
        | Op::AuthSign => false,
        Op::Open
        | Op::Read
        | Op::Edit
        | Op::Announce
        | Op::Ask
        | Op::Answer
        | Op::Snapshot
        | Op::WsFork
        | Op::WsMerge
        | Op::Note
        | Op::Hold
        | Op::Grant
        | Op::Revoke
        | Op::SecretConfirm
        | Op::TrackRead
        | Op::TrackEdit
        | Op::TrackWrite
        | Op::StopGate
        | Op::Notifications
        | Op::NotificationsRead
        | Op::Status
        | Op::Say
        | Op::Approve
        | Op::Checkpoint
        | Op::ChannelRead
        | Op::Run
        | Op::ChannelList
        | Op::RunList
        | Op::PeerList
        | Op::PresenceList
        | Op::WsList
        | Op::WsDetail
        | Op::Blame
        | Op::ChannelHistory
        | Op::Search
        | Op::ThreadList
        | Op::ThreadRead
        | Op::RunView
        | Op::FilesList
        | Op::IssueCreate
        | Op::IssueList
        | Op::IssueView
        | Op::IssueEdit
        | Op::IssueClose
        | Op::IssueDevelop
        | Op::IssueRevisions
        | Op::IssueCheckpoint
        | Op::CheckpointList => true,
    }
}

/// What a paired device may call (decision 300).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeviceScope {
    /// Any device, and an anonymous client where a `read` grant to
    /// `anyone` covers what it reads.
    Read,
    /// A `--write` device: coordination records it signs itself, never a
    /// file or workspace change.
    Write,
    /// No device: file and workspace changes, the hook ops, and the host's
    /// own ops.
    Never,
}

/// The device scope `op` needs (decision 300). Exhaustive over `Op`, so a
/// new op doesn't compile without its scope.
pub const fn device_scope(op: Op) -> DeviceScope {
    match op {
        Op::Hello
        | Op::Open
        | Op::Read
        | Op::Subscribe
        | Op::Snapshot
        | Op::ChannelRead
        | Op::Notifications
        | Op::NotificationsRead
        | Op::Status
        | Op::AuthPair
        | Op::ChannelList
        | Op::RunList
        | Op::PeerList
        | Op::PresenceList
        | Op::WsList
        | Op::WsDetail
        | Op::Watch
        | Op::Blame
        | Op::ChannelHistory
        | Op::Search
        | Op::ThreadList
        | Op::ThreadRead
        | Op::RunView
        | Op::FilesList
        | Op::IssueList
        | Op::IssueView
        | Op::IssueRevisions
        | Op::CheckpointList
        // On a host-level device stream, over the projects where the
        // device holds a grant (decision 300).
        | Op::ProjectSummary
        | Op::Inbox
        | Op::Resources => DeviceScope::Read,
        Op::Note
        | Op::Hold
        | Op::Ask
        | Op::Answer
        | Op::Say
        | Op::Run
        | Op::Grant
        | Op::Revoke
        | Op::AuthSign
        | Op::IssueCreate
        | Op::IssueEdit
        | Op::IssueClose
        | Op::IssueCheckpoint => DeviceScope::Write,
        Op::Edit
        | Op::Announce
        | Op::WsFork
        | Op::WsMerge
        | Op::Approve
        | Op::Checkpoint
        | Op::SecretConfirm
        | Op::TrackRead
        | Op::TrackEdit
        | Op::TrackWrite
        | Op::StopGate
        | Op::KeyNew
        | Op::Init
        | Op::Join
        | Op::Forget
        | Op::Project
        | Op::AuthLogin
        | Op::AuthWait
        | Op::AuthConfirm
        | Op::AuthStatus
        | Op::AuthLogout
        // Devices never edit, so never develop an issue in a workspace.
        | Op::IssueDevelop
        | Op::HostEnable
        | Op::HostDisable => DeviceScope::Never,
    }
}