workspace-index
Source: crates/lvcs-doc/src/kinds/workspace/mod.rs.
/// A workspace's id (design §8, decisions 57 and 112): the `HASH` of the
/// canonical CBOR array (name, creator key, base frontier id, creation
/// time), the name as text, the key and frontier id as byte strings, and
/// the creation time as the forking request's `Origin.time_ms`; its one
/// implementation is `WorkspaceId::of`. Written in `ID_ENCODING`. `main`
/// has none.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct WorkspaceId(#[serde(with = "crate::cbor::fixed")] pub [u8; 32]);
/// The workspace a workspace was forked from: its base view. Workspaces
/// stack (`main` → `auth-refactor` → `agent-scratch`), so a base may be
/// another workspace's view.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum WorkspaceBase {
/// The shared live `main` workspace: the `file` documents themselves.
Main,
Workspace(WorkspaceId),
}
/// A workspace index as its document holds it (design §8): every lookup it
/// doesn't answer falls through to the base view.
///
/// Layout: the genesis operations put `/overrides`, `/whiteouts`, and
/// `/local` as maps, in that order, so every participant writes into the
/// same three objects. [`IndexInit`] puts `/name` and `/base` (`main`, or
/// the base workspace id in `ID_ENCODING`) as strings, `/subtree` as a
/// string when there is one, `/created_ms` as an integer, and
/// `/base_frontier` as a 32-byte byte string: with the init's signer, the
/// inputs of the workspace id ([`WorkspaceId::of`]). An override is the key
/// `/overrides/<id>` and a workspace-local file `/local/<id>`, the override
/// id in `ID_ENCODING`, each holding a byte string, the canonical CBOR
/// encoding of the override's [`OverrideClaim`] (an `Absent` claim under
/// `/local`, any other under `/overrides`): the key is that claim's id in
/// this workspace (`OverrideClaim::id`), so concurrent claims never write
/// the same key, and the key commits to the claim's base and `path`, which
/// every peer reads from the value and checks against it (decision 112).
/// The index reads only the claim's `base` and `path`; the override
/// document's own claim is what a view composes by. A whiteout is the key
/// `/whiteouts/<path>` holding a byte string, the canonical CBOR encoding
/// of its [`WhiteoutEntry`] (decision 86). Removing an entry deletes its
/// key, so a concurrent re-add survives.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WorkspaceIndex {
pub name: String,
pub base: WorkspaceBase,
/// The path prefix the workspace was forked with (`lvcs fork --subtree`):
/// its view holds only paths under it. `None`: the whole project.
pub subtree: Option<String>,
/// Range and whole-document overrides, by base address: the document
/// whose content each claims (see `OverrideClaim.base`).
pub overrides: BTreeMap<String, BTreeSet<OverrideId>>,
/// Paths deleted in this workspace though the base view has them, each
/// with what its whiteout recorded.
pub whiteouts: BTreeMap<String, WhiteoutEntry>,
/// Files created only in this workspace, by path: each an override with
/// an absent base. More than one id at a path means concurrent
/// creations; the view uses the smallest.
pub local: BTreeMap<String, BTreeSet<OverrideId>>,
}
/// What a whiteout records (decision 86): the heads of the document that
/// held the path in the base view when it was whited out, which merge-back
/// deletes the file at only while the base is still at them, and the
/// participant who placed it.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct WhiteoutEntry {
#[serde(with = "crate::cbor::heads")]
pub heads: Vec<ChangeHash>,
pub by: ParticipantKey,
}
/// Write a new index's name, base, subtree, creation time, and base
/// frontier (design §8, decision 112): the first event a new index document
/// gets, sent once by the forking daemon, committed by `origin.author` at
/// `origin.time_ms / 1000` seconds on the live document (an index's writes
/// have no base: each writes keys of its own). `/created_ms` is
/// `origin.time_ms`, its one source, and `base_frontier` the base's visible
/// frontier id when the workspace was forked, so the index holds every
/// input of its workspace's id but the creator, who signs the init. A peer
/// accepts the init only in the index of the workspace
/// `WorkspaceId::of(name, signer, base_frontier, created_ms)` names, in a
/// change that writes nothing else (decision 112). An index that already
/// has a name ignores it.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IndexInit {
pub origin: Origin,
pub name: String,
pub base: WorkspaceBase,
pub subtree: Option<String>,
#[serde(with = "crate::cbor::fixed")]
pub base_frontier: [u8; 32],
}
/// Change one entry of an index (design §8), committed by `origin.author` at
/// `origin.time_ms / 1000` seconds on the live document. An edit that
/// changes nothing (an entry added that is already there with that claim, a
/// whiteout with the same heads and participant included, or one removed
/// that isn't there) makes no commit and no render.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IndexChange {
pub origin: Origin,
pub edit: IndexEdit,
}
/// One index entry change (layout on [`WorkspaceIndex`]).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum IndexEdit {
/// Record the range or whole-document override `id` of `claim.base`:
/// `id` is `claim.id` of this workspace, and the scope isn't `Absent`.
AddOverride {
claim: OverrideClaim,
id: OverrideId,
},
/// Retire an override from the view.
RemoveOverride { id: OverrideId },
/// Delete `path` in this workspace: `heads` are those of the document
/// that holds it in the base view, and `by` the participant deleting
/// it, as its [`WhiteoutEntry`] records them.
Whiteout {
path: String,
#[serde(with = "crate::cbor::heads")]
heads: Vec<ChangeHash>,
by: ParticipantKey,
},
/// Undo a whiteout of `path`.
ClearWhiteout { path: String },
/// Record the file `id` created only in this workspace at `claim.path`:
/// `id` is `claim.id` of this workspace, and the scope is `Absent`.
AddLocal {
claim: OverrideClaim,
id: OverrideId,
},
/// Drop a workspace-local file.
RemoveLocal { id: OverrideId },
}
/// An index document's state at `heads`, emitted after every applied change
/// and on `Render`, for the workspace actor. `index` is `None` while the
/// document holds only its genesis change, before its [`IndexInit`] arrived
/// or synced, or when `/base` names no workspace; an entry whose id or
/// value is malformed is left out.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IndexRendered {
#[serde(with = "crate::cbor::heads")]
pub heads: Vec<ChangeHash>,
pub index: Option<WorkspaceIndex>,
}