solti_model/lib.rs
1//! # solti-model
2//!
3//! Domain model for the solti task execution system.
4//!
5//! This crate defines the core resource types.
6//!
7//! ## Architecture
8//!
9//! ```text
10//! ┌──────────────────────────────────────────────────────────┐
11//! │ Task │
12//! │ │
13//! │ ObjectMeta TaskSpec TaskStatus │
14//! │ ├─ id: TaskId ├─ slot: Slot ├─ phase │
15//! │ ├─ resource_version ├─ kind: TaskKind ├─ attempt │
16//! │ ├─ created_at ├─ timeout ├─ exit_code │
17//! │ └─ updated_at ├─ restart └─ error │
18//! │ ├─ backoff │
19//! │ ├─ admission │
20//! │ ├─ runner_selector │
21//! │ └─ labels │
22//! └──────────────────────────────────────────────────────────┘
23//! ```
24//!
25//! ## Resource model
26//!
27//! | Section | Type | Responsibility |
28//! |-----------------|------------------|-----------------------------------------------------------------|
29//! | **metadata** | [`ObjectMeta`] | Identity, versioning, timestamps |
30//! | **status** | [`TaskStatus`] | Observed state: phase, attempt count, exit code, last error |
31//! | **spec** | [`TaskSpec`] | Desired state (private fields; build via [`TaskSpec::builder`]) |
32//!
33//! Slot and labels live in `spec` as the single source of truth.
34//! [`Task`] provides convenience accessors ([`Task::slot`], [`Task::labels`]) that delegate to `spec`.
35//!
36//! ## Versioning
37//!
38//! [`ObjectMeta::resource_version`] is a monotonic counter bumped on every change
39//! (spec or status) for optimistic concurrency.
40//!
41//! ## Task lifecycle
42//!
43//! ```text
44//! Pending ──► Running ──► Succeeded
45//! │
46//! ├──► Failed ──► (restart) ──► Running
47//! ├──► Timeout
48//! ├──► Canceled
49//! └──► Exhausted (max retries reached)
50//! ```
51//!
52//! Terminal phases: `Succeeded`, `Failed`, `Timeout`, `Canceled`, `Exhausted`.
53//! See [`TaskPhase::is_terminal`].
54//!
55//! ## Task kinds
56//!
57//! [`TaskKind`] defines what a task actually runs:
58//!
59//! | Variant | Description |
60//! |----------------|------------------------------------------------------|
61//! | `Subprocess` | External process (`command`, `args`, `env`, `cwd`) |
62//! | `Wasm` | WebAssembly module |
63//! | `Container` | OCI container image |
64//! | `Embedded` | Code-defined task (in-process `TaskRef`) |
65//!
66//! `Subprocess` tasks go through `solti_runner::RunnerRouter`; `Embedded` tasks are submitted directly via `SupervisorApi::submit_with_task`.
67//!
68//! ## Policies
69//!
70//! | Policy | Controls |
71//! |----------------------|----------------------------------------------------------|
72//! | [`RestartPolicy`] | When to restart: `Never`, `OnFailure`, `Always` |
73//! | [`BackoffPolicy`] | Delay between retries: initial, max, factor, jitter |
74//! | [`JitterPolicy`] | Jitter strategy: `None`, `Full`, `Equal`, `Decorrelated` |
75//! | [`AdmissionPolicy`] | Duplicate handling: `DropIfRunning`, `Replace`, `Queue` |
76//!
77//! ## Construction
78//!
79//! [`TaskSpec`] fields are private; construct via [`TaskSpec::builder`]:
80//!
81//! ```text
82//! let spec = TaskSpec::builder("my-slot", kind, 5_000u64)
83//! .restart(RestartPolicy::OnFailure)
84//! .build()?;
85//! ```
86//!
87//! See [`TaskSpecBuilder`] for the full API.
88//!
89//! ## Also
90//!
91//! - `solti-runner` consumes [`TaskSpec`] and [`TaskKind`] to build executable tasks.
92//! - `solti-core` manages [`Task`] lifecycle and state transitions.
93//! - `solti-api` serializes/deserializes model types over gRPC and HTTP.
94//!
95//! ## Domain types
96//!
97//! | Type | Description |
98//! |--------------------|-----------------------------------------------------------|
99//! | [`Slot`] | Logical execution lane (newtype over `Arc<str>`) |
100//! | [`TaskId`] | Unique task identifier (newtype over `Arc<str>`) |
101//! | [`Timeout`] | Per-attempt timeout in milliseconds |
102//! | [`Labels`] | Key-value metadata for routing and filtering |
103//! | [`TaskEnv`] | Ordered environment variables for task execution |
104//! | [`Flag`] | Boolean toggle with `enabled()`/`disabled()` constructors |
105//! | [`TaskQuery`] | Builder for filtered, paginated task listing |
106//! | [`TaskPage`] | Paginated query result |
107//! | [`TaskSpecBuilder`]| Validated builder for [`TaskSpec`] |
108//!
109//! ## Example
110//!
111//! ```rust
112//! use solti_model::{
113//! BackoffPolicy, JitterPolicy,
114//! RestartPolicy, SubprocessMode, SubprocessSpec, Task, TaskKind, TaskPhase, TaskSpec,
115//! };
116//!
117//! // 1) Build a task spec via the builder
118//! let spec = TaskSpec::builder(
119//! "my-worker",
120//! TaskKind::Subprocess(SubprocessSpec {
121//! mode: SubprocessMode::Command {
122//! command: "echo".into(),
123//! args: vec!["hello".into()],
124//! },
125//! env: Default::default(),
126//! cwd: None,
127//! fail_on_non_zero: Default::default(),
128//! }),
129//! 5_000u64,
130//! )
131//! .restart(RestartPolicy::OnFailure)
132//! .backoff(BackoffPolicy {
133//! jitter: JitterPolicy::Equal,
134//! first_ms: 1_000,
135//! max_ms: 30_000,
136//! factor: 2.0,
137//! })
138//! .build()
139//! .expect("spec should be valid");
140//!
141//! // 2) Validate at submit boundary (checks business rules like no Embedded)
142//! spec.validate().expect("spec should pass submit validation");
143//!
144//! // 3) Create a task resource (normally done by the supervisor)
145//! let task = Task::new("task-001".into(), spec);
146//! assert_eq!(task.slot(), "my-worker");
147//! assert_eq!(*task.phase(), TaskPhase::Pending);
148//! assert_eq!(task.metadata().resource_version, 1);
149//! ```
150
151mod domain;
152pub use domain::{
153 AGENT_ID_MAX_LEN, AdmissionPolicy, AgentId, BackoffPolicy, ContainerSpec, DEFAULT_LIMIT, Flag,
154 JitterPolicy, KeyValue, Labels, LabelsIter, MAX_LIMIT, MAX_SCRIPT_BODY_BYTES, OutputChunk,
155 OutputEvent, RestartPolicy, RunnerEnv, RunnerSelector, Runtime, SLOT_MAX_LEN, SelectorOperator,
156 SelectorRequirement, Slot, StreamKind, SubprocessMode, SubprocessSpec, TASK_ID_MAX_LEN,
157 TaskEnv, TaskId, TaskKind, TaskPage, TaskPhase, TaskQuery, Timeout, WasmSpec, merge_env,
158};
159
160mod resource;
161pub use resource::{ObjectMeta, Task, TaskRun, TaskSpec, TaskSpecBuilder, TaskStatus};
162
163mod error;
164pub use error::{ModelError, ModelResult};