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,
}
}