Skip to main content

runx_contracts/
decision.rs

1//! Decision contracts: choices, justifications, and closure dispositions.
2use serde::{Deserialize, Serialize};
3
4use crate::schema::{IsoDateTime, NonEmptyString, RunxSchema};
5use crate::{Intent, Reference};
6
7#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
8#[serde(rename_all = "snake_case")]
9pub enum DecisionChoice {
10    Open,
11    Continue,
12    SpawnChild,
13    Escalate,
14    Defer,
15    Close,
16    Decline,
17    Monitor,
18}
19
20#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
21#[serde(deny_unknown_fields)]
22pub struct DecisionInputs {
23    #[serde(default)]
24    pub signal_refs: Vec<Reference>,
25    pub target_ref: Option<Reference>,
26    #[serde(default)]
27    pub opportunity_refs: Vec<Reference>,
28    pub selection_ref: Option<Reference>,
29}
30
31#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
32#[serde(deny_unknown_fields)]
33pub struct DecisionJustification {
34    pub summary: NonEmptyString,
35    #[serde(default)]
36    pub evidence_refs: Vec<Reference>,
37}
38
39#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
40#[serde(rename_all = "snake_case")]
41pub enum ClosureDisposition {
42    Closed,
43    Deferred,
44    Superseded,
45    Declined,
46    Blocked,
47    Failed,
48    Killed,
49    TimedOut,
50}
51
52impl ClosureDisposition {
53    /// Stable string label, identical to the serde `snake_case` representation.
54    /// Single source of truth for the runtime's disposition strings.
55    pub fn label(&self) -> &'static str {
56        match self {
57            ClosureDisposition::Closed => "closed",
58            ClosureDisposition::Deferred => "deferred",
59            ClosureDisposition::Superseded => "superseded",
60            ClosureDisposition::Declined => "declined",
61            ClosureDisposition::Blocked => "blocked",
62            ClosureDisposition::Failed => "failed",
63            ClosureDisposition::Killed => "killed",
64            ClosureDisposition::TimedOut => "timed_out",
65        }
66    }
67}
68
69#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
70#[serde(deny_unknown_fields)]
71pub struct Closure {
72    pub disposition: ClosureDisposition,
73    pub reason_code: NonEmptyString,
74    pub summary: NonEmptyString,
75    pub closed_at: IsoDateTime,
76}
77
78#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, RunxSchema)]
79#[serde(deny_unknown_fields)]
80#[runx_schema(id = "runx.decision.v1")]
81pub struct Decision {
82    pub decision_id: NonEmptyString,
83    pub choice: DecisionChoice,
84    pub inputs: DecisionInputs,
85    pub proposed_intent: Intent,
86    pub selected_act_id: Option<NonEmptyString>,
87    pub selected_harness_ref: Option<Reference>,
88    pub justification: DecisionJustification,
89    pub closure: Option<Closure>,
90    #[serde(default)]
91    pub artifact_refs: Vec<Reference>,
92}