Skip to main content

onetaskgraph_plugin_api/
work.rs

1//! The work items every source is normalised into.
2
3use chrono::{DateTime, Utc};
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7use crate::NativeId;
8
9/// One unit of work as a source reports it.
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
11pub struct Task {
12    /// The source's own opaque identifier.
13    pub id: NativeId,
14    /// The one-line summary a user recognises the task by.
15    pub title: String,
16    /// The long-form body, when the source has one.
17    pub content: Option<String>,
18    /// The source's status, normalised and preserved.
19    pub status: Status,
20    /// Inline rather than by id: a source returning a task already knows them.
21    pub labels: Vec<Label>,
22    /// `None` is a first-class case — an orphan task — not an edge case.
23    pub project: Option<NativeId>,
24    /// Where a human can open this task.
25    // llmlint: ignore[invalid_states_unrepresentable] SECOND PERMITTED REASON — this restates at a new site the justification already recorded at `Capabilities.max_page_size` (capability.rs) and `PageRequest.limit` (query.rs), and in AGENTS.md's "The plugin contract": this crate's field types ARE the approved contract, six undispatched nodes compile against `Option<String>` here, and only the contract's owner may narrow one. No code change is available that clears this without editing that frozen surface.
26    // llmlint: ignore[boundary_inputs_validated] parsing this into a URL type would narrow the same frozen surface, and would add a URL dependency to the crate AGENTS.md says to keep still ("Keep the api crate still" — every change here re-tests every plugin). A plugin that returns a string this interface cannot represent is what `SourceError::Malformed` is for. Contract owner's call; recorded in AGENTS.md, "The plugin contract".
27    pub url: Option<String>,
28    /// When the source says the task was created.
29    pub created_at: Option<DateTime<Utc>>,
30    /// When the source says the task last changed.
31    pub updated_at: Option<DateTime<Utc>>,
32}
33
34/// A grouping of tasks, shaped like a [`Task`] without a parent of its own.
35#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
36pub struct Project {
37    /// The source's own opaque identifier.
38    pub id: NativeId,
39    /// The one-line summary a user recognises the project by.
40    pub title: String,
41    /// The long-form body, when the source has one.
42    pub content: Option<String>,
43    /// The source's status, normalised and preserved.
44    pub status: Status,
45    /// Inline rather than by id, for the same reason as on [`Task`].
46    pub labels: Vec<Label>,
47    /// Where a human can open this project.
48    // llmlint: ignore[invalid_states_unrepresentable] SECOND PERMITTED REASON — this restates at a new site the justification already recorded at `Capabilities.max_page_size` (capability.rs) and `PageRequest.limit` (query.rs), and in AGENTS.md's "The plugin contract": this crate's field types ARE the approved contract, six undispatched nodes compile against `Option<String>` here, and only the contract's owner may narrow one. No code change is available that clears this without editing that frozen surface.
49    // llmlint: ignore[boundary_inputs_validated] parsing this into a URL type would narrow the same frozen surface, and would add a URL dependency to the crate AGENTS.md says to keep still ("Keep the api crate still" — every change here re-tests every plugin). A plugin that returns a string this interface cannot represent is what `SourceError::Malformed` is for. Contract owner's call; recorded in AGENTS.md, "The plugin contract".
50    pub url: Option<String>,
51    /// When the source says the project was created.
52    pub created_at: Option<DateTime<Utc>>,
53    /// When the source says the project last changed.
54    pub updated_at: Option<DateTime<Utc>>,
55}
56
57/// A tag a source attaches to work.
58#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
59pub struct Label {
60    /// The source's own opaque identifier.
61    pub id: NativeId,
62    /// What a user filtering across sources actually types.
63    pub name: String,
64    /// The source's own colour for the label, when it has one.
65    pub color: Option<String>,
66}
67
68/// A source's status, kept in both normalised and original form.
69///
70/// `category` is what every filter compares against; `name` is the source's own
71/// wording, preserved so display never flattens "In Review" into "In Progress".
72#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
73pub struct Status {
74    /// The normalised value filters compare against.
75    pub category: StatusCategory,
76    /// The source's own label for this status.
77    pub name: String,
78}
79
80/// The normalised status vocabulary shared across every source.
81#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
82#[serde(rename_all = "kebab-case")]
83pub enum StatusCategory {
84    /// Known about, not yet queued.
85    Backlog,
86    /// Queued, not yet started.
87    Todo,
88    /// Being worked on.
89    InProgress,
90    /// Finished.
91    Done,
92    /// Abandoned.
93    Cancelled,
94    /// The source reported a status this vocabulary cannot place.
95    Unknown,
96}
97
98/// A dependency between two items **of the same source**.
99///
100/// Cross-source edges are deliberately absent: relating an id in one system to an
101/// id in another needs state, and the engine is forbidden to hold any.
102#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
103pub struct DependencyEdge {
104    /// The item the edge starts at.
105    pub from: NativeId,
106    /// The item the edge points at.
107    pub to: NativeId,
108    /// What the edge means.
109    pub kind: DependencyKind,
110}
111
112/// What a [`DependencyEdge`] means.
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
114#[serde(rename_all = "kebab-case")]
115pub enum DependencyKind {
116    /// `from` must finish before `to` can.
117    Blocks,
118    /// `from` and `to` are linked without an ordering.
119    Related,
120}
121
122/// Which way a dependency query walks the graph.
123#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
124#[serde(rename_all = "kebab-case")]
125pub enum Direction {
126    /// What this item depends on — the forward edges every source can report.
127    DependsOn,
128    /// What depends on this item — emulated by the engine for a
129    /// [`ForwardOnly`](crate::DependencySupport::ForwardOnly) source.
130    DependedOnBy,
131}