api-issues
Source: crates/lvcs-api/src/issues.rs.
/// `issue.create`: create an issue with `title`, `body` (empty when
/// omitted), and `labels`, numbered by the connection's participant, with
/// its thread. Errors: `REJECTED` (no grant lets the participant write),
/// `SECRET_DETECTED`, `RATE_LIMITED`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct IssueCreateParams {
pub title: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub body: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub labels: Vec<String>,
}
/// `issue.create` result: the issue's name (`<alias>/<n>`), its document's
/// address, and its thread's address.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IssueCreateResult {
pub issue: String,
pub address: String,
pub thread: String,
}
/// Which issues `issue.list` lists, a JSON string: `open` (the default:
/// neither fixed nor closed by hand), `closed` (fixed or closed by hand),
/// or `all`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum IssueStateFilter {
#[default]
Open,
Closed,
All,
}
/// How `issue.list` orders issues, a JSON string: `newest` (the default),
/// `oldest`, or `severity` (highest first by `severity_rank`, then
/// newest). Ties go by address.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum IssueSort {
#[default]
Newest,
Oldest,
Severity,
}
/// A severity label's rank, highest first: `sev:critical` 0, `sev:high` 1,
/// `sev:medium` 2, `sev:low` 3; any other label is plain (`None`). An
/// issue's severity is the lowest rank among its labels, and an issue
/// with none ranks after `sev:low`.
#[must_use]
pub fn severity_rank(label: &str) -> Option<u8> {
match label {
"sev:critical" => Some(0),
"sev:high" => Some(1),
"sev:medium" => Some(2),
"sev:low" => Some(3),
_ => None,
}
}
/// `issue.list`: the issues this daemon holds, filtered by `state`, by an
/// `assignee` (`@me`, an alias, or a participant id), and by a `label`,
/// ordered by `sort`. Metadata only: a body is read with `issue.view`.
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema)]
pub struct IssueListParams {
#[serde(default)]
pub state: IssueStateFilter,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub assignee: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(default)]
pub sort: IssueSort,
}
/// An issue's state, a JSON string: `open`; `fixed`, when its done channel
/// holds all its linked work and its plan is done (derived, so a rollback
/// opens it again); or `closed`, by hand as won't fix or duplicate.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum IssueState {
Open,
Fixed,
Closed,
}
/// Why an issue was closed by hand, a JSON string.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum IssueCloseReason {
Wontfix,
Duplicate,
}
/// One issue as `issue.list` lists it: its name, address, title, state,
/// labels, assignees (aliases or ids), and its plan's checked and total
/// task counts.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IssueSummary {
pub issue: String,
pub address: String,
pub title: String,
pub state: IssueState,
#[serde(default)]
pub labels: Vec<String>,
#[serde(default)]
pub assignees: Vec<String>,
#[serde(default)]
pub tasks_done: usize,
#[serde(default)]
pub tasks: usize,
pub created_ms: i64,
/// The latest checkpoint's first `next` item, else its first `done`
/// one (decision 313).
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_checkpoint: Option<String>,
}
/// `issue.list` result.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IssueListResult {
#[serde(default)]
pub issues: Vec<IssueSummary>,
}
/// `issue.view`: one issue, named `<alias>/<n>` or by its address.
/// Reading it counts as presence on it (`ISSUE_PRESENCE_SECS`).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct IssueViewParams {
pub issue: String,
}
/// One plan task: its number (from 1, as `--check` takes it), id, text,
/// whether it is checked, its owner, and the code range its latest link
/// names (`--task <n> --link <file>:<lines>`, decision 313): `address` a
/// project path and `lines` as `IssueLink.lines` writes them.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IssueTask {
pub n: usize,
pub id: String,
pub text: String,
pub done: bool,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub owner: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub address: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lines: Option<String>,
}
/// One piece of linked work: a document, the lines a hand link names, who
/// linked it, and whether the issue's done channel holds it.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IssueLink {
pub address: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub lines: Option<String>,
pub by: String,
pub done: bool,
}
/// `issue.view` result: the issue's fields, its derived assignees, plan,
/// and linked work, its workspaces, its thread, its done channel (a channel
/// or `[channels]` combination name, or `changes` for every change
/// action's channel), and `fixed_at`, the frontier id it is fixed at.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IssueViewResult {
pub issue: String,
pub address: String,
pub title: String,
pub body: String,
pub creator: String,
pub created_ms: i64,
pub state: IssueState,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reason: Option<IssueCloseReason>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub fixed_at: Option<String>,
pub done_channel: String,
#[serde(default)]
pub labels: Vec<String>,
#[serde(default)]
pub assignees: Vec<String>,
#[serde(default)]
pub plan: Vec<IssueTask>,
#[serde(default)]
pub links: Vec<IssueLink>,
#[serde(default)]
pub workspaces: Vec<String>,
pub thread: String,
}
/// `issue.edit`: change an issue. Assignees take `@me`, an alias, or a
/// participant id. `add_assignee` `@me` is a claim: at once on an issue
/// (or, with `task`, a plan task) with no assignee, else an ask to the
/// assignees that waits out the wait window and is held when one of them
/// is present on the issue; adding or removing anyone else needs the
/// assign grant (`Write` on `<address>/assign`). `check` and `uncheck` and
/// `task` take task numbers from 1; `link` takes `<file>:<lines>`
/// (`src/a.rs:L3-L9`, or `src/a.rs` for the whole file), and with `task`
/// links that plan task's code range (decision 313). Errors:
/// `INVALID_PARAMS`, `REJECTED` (`out_of_scope` without the assign grant).
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema)]
pub struct IssueEditParams {
pub issue: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub body: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub add_label: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub remove_label: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub add_assignee: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub remove_assignee: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub task: Option<usize>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub add_task: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub check: Vec<usize>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub uncheck: Vec<usize>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub link: Vec<String>,
}
/// How a claim ended, a JSON string: `taken` (the participant holds it
/// now) or `held` (an assignee working on it kept it).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ClaimOutcome {
Taken,
Held,
}
/// `issue.edit` result: the issue, and for a claim how it ended, with the
/// holder and its reason when it was held, `recover` `wait` and `next` to
/// read the issue.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IssueEditResult {
pub issue: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub claim: Option<ClaimOutcome>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub held_by: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub recover: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub next: Option<Next>,
}
/// `issue.close`: close an issue by hand as `reason` (`wontfix` when
/// omitted), or with `reopen` open it again. Needs the issue's creator or
/// the assign grant. A fixed issue closes by itself: see `issue.view`.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct IssueCloseParams {
pub issue: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reason: Option<IssueCloseReason>,
#[serde(default)]
pub reopen: bool,
}
/// `issue.close` result: the issue and its state now.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IssueCloseResult {
pub issue: String,
pub state: IssueState,
}
/// `issue.revisions` (decision 313): the code revisions of the issue's
/// linked work, newest first, at most `limit` (20 when omitted): one row
/// per frontier this daemon knows (a channel's value, an attestation, an
/// outcome, or a run) that holds a different state of the issue's linked
/// files, its newest such frontier, with how far each channel got with it.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct IssueRevisionsParams {
pub issue: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limit: Option<usize>,
}
/// How far a channel got with a revision, a JSON string: its value holds
/// it (`holds`), a run of it is under way (`running`), its runs passed
/// (`passed`) or one failed (`failed`), or none ran (`not_run`).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum RevisionState {
Holds,
Running,
Passed,
Failed,
NotRun,
}
/// One channel's state for a revision; `check` names the failing action.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RevisionChannel {
pub channel: String,
pub state: RevisionState,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub check: Option<String>,
}
/// One revision: its frontier's id, when its newest linked change was
/// made and by whom (the participant's alias, as `issue.view` names
/// assignees), what changed for the issue (the
/// linked files whose state differs from the revision before, one line),
/// and each channel's state.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IssueRevision {
pub frontier: String,
pub at_ms: i64,
pub by: String,
pub summary: String,
#[serde(default)]
pub channels: Vec<RevisionChannel>,
}
/// `issue.revisions` result, with the issue's status at its done channel.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IssueRevisionsResult {
#[serde(default)]
pub revisions: Vec<IssueRevision>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub fixed_at: Option<String>,
pub done_channel: String,
}
/// `issue.checkpoint` (decision 313): record a structured checkpoint of
/// the work on an issue at main's visible frontier: what is done, what
/// comes next, what blocks it, and its open questions.
#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize, JsonSchema)]
pub struct IssueCheckpointParams {
pub issue: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub done: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub next: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub blockers: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub open_questions: Vec<String>,
}
/// `issue.checkpoint` result: the issue and the frontier recorded.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IssueCheckpointResult {
pub issue: String,
pub frontier: String,
}
/// `checkpoint.list` (decision 313): an issue's checkpoints, newest first,
/// at most `limit` (10 when omitted).
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct CheckpointListParams {
pub issue: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limit: Option<usize>,
}
/// One checkpoint: who wrote it (the participant's alias, as `issue.view`
/// names assignees), when, at which frontier, and its lists.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IssueCheckpoint {
pub by: String,
pub at_ms: i64,
pub frontier: String,
pub done: Vec<String>,
#[serde(default)]
pub next: Vec<String>,
#[serde(default)]
pub blockers: Vec<String>,
#[serde(default)]
pub open_questions: Vec<String>,
}
/// `checkpoint.list` result.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CheckpointListResult {
#[serde(default)]
pub checkpoints: Vec<IssueCheckpoint>,
}
/// `issue.develop`: fork a workspace bound to the issue (`name` by
/// default `issue-<alias>-<n>`), record it on the issue, and make the
/// issue the participant's current one, so its saves and edits on `main`
/// link to it; merging the workspace into `main` links the work done in
/// it (decision 334). A participant with no assignee on the issue also
/// takes it.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct IssueDevelopParams {
pub issue: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
}
/// `issue.develop` result: `ws.fork`'s for the forked workspace, its id
/// (`ID_ENCODING`) and its mirror directory.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct IssueDevelopResult {
pub workspace: String,
pub dir: String,
}