api-host
Source: crates/lvcs-api/src/host.rs.
/// `hello`: must be the first request on a connection to the per-user
/// daemon (decision 272). `root` names the project every later request on
/// the connection acts on: an absolute path, which the daemon
/// canonicalizes, of a root it hosts. A root it doesn't host is refused
/// `NO_PARTICIPANT` with `next` naming `lvcs project`; one whose opening
/// failed is refused with the reason it failed, and one still opening or
/// joining is refused `retry_after`. Without `root`, the connection is the
/// host's: it may call only `init`, `join`, `forget`, `project`, and
/// `status` (of every project), and `participant` must be absent.
///
/// With `root`, every later request acts as `participant` (a public key
/// in `ID_ENCODING`, an alias, or a prefix), whose key the project holds
/// under `KEYS_DIR`; without `participant`, as the project's default
/// participant (the owner after `init`, the joined participant after
/// `join`, or whom `lvcs project --as` named). Custody is per
/// OS user: a connection from any other user than the daemon's is refused
/// at accept, before `hello` (decision 272, revising 115 and 149). A key
/// with no live grant is accepted, and its signed requests are refused
/// `REJECTED` until a grant renders.
///
/// On a host peer stream (decision 300) the daemon's first line is the
/// push `challenge`; `hello` names `root` and, for a device, its `device`
/// proof, and never `participant`. A device with a live device grant acts
/// as itself within it; one without may only `auth.pair`; with no
/// `device`, the connection reads what a `read` grant to `anyone` covers,
/// and is refused `NO_PARTICIPANT` naming `lvcs auth login` when there is
/// none. A request past its scope is refused `REJECTED` `device_scope`.
/// A device's `hello` naming no root opens a host-level stream, which
/// reads only `project.summary`, `inbox`, and `resources`, over the
/// projects where the device holds a device grant.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct HelloParams {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub participant: Option<String>,
/// The project root the connection acts on; absent for the host's.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub root: Option<String>,
/// On a host peer stream with no `root`, the project's id in its
/// place, as a public link names it (decision 337): the stream acts on
/// the running project with that id. Ignored on the socket.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub project: Option<String>,
/// On a host peer stream, the device's proof of its key (decision
/// 300); absent for an anonymous reader. Never sent on the socket.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub device: Option<DeviceProof>,
}
/// `hello`'s result, on every connection (decision 325): the socket's and
/// a host peer stream's, host-level and a project's. `version` is the
/// daemon's release, as `lvcs --version` prints it; absent, the daemon
/// predates decision 325, which answered `hello` with `{}`. `api` is
/// [`crate::API_VERSION`] as the daemon speaks it, 0 when absent. A client
/// that needs a newer daemon names `version` and `lvcs update` instead of
/// failing to decode a later reply.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct HelloResult {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub version: Option<String>,
#[serde(default)]
pub api: u16,
}
/// How a hosted project's transport runs (design §2, decision 272): its
/// own swarm in the per-user daemon, listening on `listen` (every
/// interface, a port the system picks, TCP and QUIC, when empty),
/// dialing `bootstrap` at each opening, in seed mode when `seed`, and
/// with no mDNS when `no_mdns` (decision 307).
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct TransportParams {
/// Multiaddrs to listen on.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub listen: Vec<String>,
/// Multiaddrs to dial at each opening, with their `/p2p/<PeerId>`.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub bootstrap: Vec<String>,
/// Seed mode: pin every document, serve as a circuit relay, and act
/// as a bootstrap address, with no special authority.
#[serde(default, skip_serializing_if = "core::ops::Not::not")]
pub seed: bool,
/// No mDNS: peers find the project only through bootstrap addresses,
/// Kademlia, and relays (decision 307).
#[serde(default, skip_serializing_if = "core::ops::Not::not")]
pub no_mdns: bool,
}
/// `init` (decision 272): create a project at `root` (absolute) and host
/// it, its default participant the owner. Refused when `root` holds
/// `.lvcs/` already: `lvcs project` registers an initialized project.
/// Answered once the project is open.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct InitParams {
pub root: String,
#[serde(default)]
pub transport: TransportParams,
}
/// `init` result: what `lvcs init` prints.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct InitResult {
/// The owner's participant id.
pub owner: String,
/// The project id.
pub project: String,
/// The project's peer id in the daemon.
pub peer_id: String,
}
/// `join` (design §8, decisions 107, 215, and 272): create a participant
/// key and the device key at `root` (absolute, holding no `.lvcs/`), sync
/// project `project` from `transport.bootstrap`, and host it as the new
/// participant. Answered when the join ends. An incomplete join is
/// refused `retry_after` with what is unsettled; the project stays
/// registered and open, so its sync goes on.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct JoinParams {
pub root: String,
/// The project id (`ID_ENCODING`).
pub project: String,
#[serde(default)]
pub transport: TransportParams,
}
/// `join` result: what `lvcs join` prints.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct JoinResult {
/// The new participant's id.
pub participant: String,
/// The project id.
pub project: String,
/// The project's peer id in the daemon.
pub peer_id: String,
}
/// `forget` (decision 272): stop hosting the project at `root` and drop
/// it from the registry, leaving its files alone.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ForgetParams {
pub root: String,
}
/// `forget` result.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ForgetResult {
/// The canonical root forgotten.
pub root: String,
}
/// `project` (decision 272): register the initialized project at `root`
/// (absolute) when the daemon doesn't host it, or change how it is
/// hosted, then open it again. `participant` is the default participant,
/// which authors changes made on disk in the project's mirror (its id, an
/// alias, or a prefix; registering without one takes the owner);
/// `transport`, when given, replaces how its transport runs. A project
/// whose opening failed (for example on a format refusal, after `lvcs
/// migrate`) is opened again. Answered once it is open.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ProjectParams {
pub root: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub participant: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub transport: Option<TransportParams>,
}
/// `project` result.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ProjectResult {
/// The canonical root.
pub root: String,
/// The default participant.
pub participant: String,
/// The project id.
pub project: String,
/// The project's peer id in the daemon.
pub peer_id: String,
}
/// `key.new` (decision 272): create a participant key in the
/// connection's project.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct KeyNewParams {}
/// `key.new` result.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct KeyNewResult {
/// The new participant's id.
pub participant: String,
}
/// `status` on the host's connection (decision 272): the socket and every
/// project the daemon hosts.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct HostStatusResult {
/// The socket (or pipe) the daemon serves.
pub socket: String,
/// Every hosted project, by root.
#[serde(default)]
pub projects: Vec<HostedProject>,
/// The host peer the app dials (decision 300), absent while it is off
/// (until `lvcs auth login` turns it on) or when it couldn't start.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub host_peer: Option<HostPeerStatus>,
/// Whether this daemon applies a signed release itself (decision 293):
/// its own setting, under its own `LVCS_NO_AUTO_UPDATE`, not the
/// caller's.
#[serde(default)]
pub auto_update: bool,
}
/// `host.enable` and `host.disable` (decision 300): turn the host peer on
/// or off, and keep it so across restarts. Answered with the host's
/// `status`. `lvcs auth login` enables it first.
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct HostToggleParams {}
/// The per-user daemon's host peer (decision 300): one libp2p identity
/// that reaches every hosted project over `/lvcs/rpc/1`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct HostPeerStatus {
/// Its peer id.
pub peer_id: String,
/// Its WebRTC-direct addresses, each ending in `/certhash/<hash>/p2p/<peer id>`.
pub addrs: Vec<String>,
/// The tracker it announces to and a browser signals it through
/// (decisions 301 and 327); none when `LVCS_TRACKER` is `off`.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub tracker: Option<String>,
/// The STUN servers a signaled session asks (decision 327).
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub stun: Vec<String>,
}
/// One project the per-user daemon hosts.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct HostedProject {
/// Its canonical root.
pub root: String,
/// Where its lifecycle stands: `opening`, `joining`, `open`,
/// `failed`, or `closing`.
pub stage: String,
/// The default participant.
pub participant: String,
/// Why its opening failed, when `stage` is `failed`.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
/// Its public link (decision 337), while a `read` grant to anyone
/// holds and the host peer is on: `APP_URL`, then `#` and its
/// [`crate::auth::ReadLink`].
#[serde(default, skip_serializing_if = "Option::is_none")]
pub link: Option<String>,
}