1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
use std::collections::HashMap;
use myko_macros::myko_item;
use serde::{Deserialize, Serialize};
use ts_rs::TS;
use crate::PlaybookId;
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
pub enum RunStatus {
/// Admitted to the queue but not yet started: waiting for a lease on every
/// resource it targets. Promoted to `Running` by the admission step.
Queued,
#[default]
Running,
Completed,
Failed,
Cancelled,
}
myko::register_ts_export!(RunStatus);
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
pub struct HostStats {
pub ok: u32,
pub changed: u32,
pub failed: u32,
pub skipped: u32,
pub unreachable: u32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_failure: Option<HostFailure>,
}
myko::register_ts_export!(HostStats);
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
pub struct HostFailure {
pub task: String,
pub message: String,
pub seq: u64,
}
myko::register_ts_export!(HostFailure);
#[myko_item]
pub struct Run {
pub playbook_id: PlaybookId,
pub status: RunStatus,
pub started_at: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub finished_at: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub exit_code: Option<i32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub extra_vars: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub limit: Option<String>,
/// Inventory groups this run targets (derived from `limit` at submission).
/// The lease queue serializes runs whose resources overlap. `#[serde(default)]`
/// keeps older persisted runs (written before the queue existed) loadable.
#[serde(default)]
pub resources: Vec<String>,
#[serde(default)]
pub host_stats: HashMap<String, HostStats>,
/// Identity of the WS connection that launched (or recorded) this run —
/// actor attribution (a human via the UI, an agent via MCP/CLI). Stamped by the
/// RunPlaybook/RecordRun handlers from the command's request context; the
/// `#[myko_client_id]` attribute only covers direct client SET events, which
/// never happen for runs (lv-67c8). `#[serde(default)]` keeps runs persisted
/// before this field existed loadable.
///
/// NOTE: live only — the SQLite projection does not yet store this column, so
/// it resets to `None` on restart. Durable persistence (schema column +
/// insert/select) is a tracked follow-up.
#[myko_client_id]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub client_id: Option<String>,
/// The runner that holds this run for execution — set by `ClaimRun` (a CAS:
/// succeeds only while unclaimed), never by the runner watching alone. The
/// execution gate: a run is executed by exactly the runner that won the claim,
/// so a second runner cannot double-execute and a restarted runner does not
/// re-grab work it already spawned. `#[serde(default)]` keeps pre-claim runs
/// loadable; a terminal record never carries a claim.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub claimed_by: Option<String>,
}