gestura_core_foundation/
outcomes.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use std::fmt;
4
5/// Durable outcome labels that corrective learning can attach to turns and tasks.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
7#[serde(rename_all = "snake_case")]
8pub enum OutcomeSignalKind {
9    /// A same-turn retry materially improved the answer.
10    RetryImproved,
11    /// A same-turn retry did not materially improve the answer.
12    RetryDidNotImprove,
13    /// Execution finished and is waiting for review approval.
14    ExecutionAwaitingReview,
15    /// Execution finished and is waiting for explicit test validation.
16    ExecutionAwaitingTestValidation,
17    /// The task reached a completed state.
18    TaskCompleted,
19    /// The task failed.
20    TaskFailed,
21    /// The task became blocked.
22    TaskBlocked,
23    /// The task was cancelled.
24    TaskCancelled,
25    /// The task was approved before execution.
26    PreExecutionApproved,
27    /// The task was rejected before execution.
28    PreExecutionRejected,
29    /// The task needs revision before execution.
30    PreExecutionNeedsRevision,
31    /// Review approval was granted.
32    ReviewApproved,
33    /// Review approval was rejected.
34    ReviewRejected,
35    /// Review requested revision.
36    ReviewNeedsRevision,
37    /// Test validation was approved.
38    TestValidationApproved,
39    /// Test validation was rejected.
40    TestValidationRejected,
41    /// Test validation requested revision.
42    TestValidationNeedsRevision,
43}
44
45impl OutcomeSignalKind {
46    /// Stable machine-readable identifier suitable for persistence and tags.
47    #[must_use]
48    pub const fn as_str(self) -> &'static str {
49        match self {
50            Self::RetryImproved => "retry_improved",
51            Self::RetryDidNotImprove => "retry_did_not_improve",
52            Self::ExecutionAwaitingReview => "execution_awaiting_review",
53            Self::ExecutionAwaitingTestValidation => "execution_awaiting_test_validation",
54            Self::TaskCompleted => "task_completed",
55            Self::TaskFailed => "task_failed",
56            Self::TaskBlocked => "task_blocked",
57            Self::TaskCancelled => "task_cancelled",
58            Self::PreExecutionApproved => "pre_execution_approved",
59            Self::PreExecutionRejected => "pre_execution_rejected",
60            Self::PreExecutionNeedsRevision => "pre_execution_needs_revision",
61            Self::ReviewApproved => "review_approved",
62            Self::ReviewRejected => "review_rejected",
63            Self::ReviewNeedsRevision => "review_needs_revision",
64            Self::TestValidationApproved => "test_validation_approved",
65            Self::TestValidationRejected => "test_validation_rejected",
66            Self::TestValidationNeedsRevision => "test_validation_needs_revision",
67        }
68    }
69
70    /// Human-readable label suitable for UI summaries and markdown persistence.
71    #[must_use]
72    pub const fn label(self) -> &'static str {
73        match self {
74            Self::RetryImproved => "Retry improved",
75            Self::RetryDidNotImprove => "Retry did not improve",
76            Self::ExecutionAwaitingReview => "Execution awaiting review",
77            Self::ExecutionAwaitingTestValidation => "Execution awaiting test validation",
78            Self::TaskCompleted => "Task completed",
79            Self::TaskFailed => "Task failed",
80            Self::TaskBlocked => "Task blocked",
81            Self::TaskCancelled => "Task cancelled",
82            Self::PreExecutionApproved => "Pre-execution approved",
83            Self::PreExecutionRejected => "Pre-execution rejected",
84            Self::PreExecutionNeedsRevision => "Pre-execution needs revision",
85            Self::ReviewApproved => "Review approved",
86            Self::ReviewRejected => "Review rejected",
87            Self::ReviewNeedsRevision => "Review needs revision",
88            Self::TestValidationApproved => "Test validation approved",
89            Self::TestValidationRejected => "Test validation rejected",
90            Self::TestValidationNeedsRevision => "Test validation needs revision",
91        }
92    }
93
94    /// Confidence delta used when outcome-linked learning ranks a reflection.
95    #[must_use]
96    pub const fn confidence_delta(self) -> f32 {
97        match self {
98            Self::RetryImproved => 0.03,
99            Self::RetryDidNotImprove => -0.10,
100            Self::ExecutionAwaitingReview | Self::ExecutionAwaitingTestValidation => 0.02,
101            Self::TaskCompleted => 0.12,
102            Self::TaskFailed | Self::TaskBlocked => -0.12,
103            Self::TaskCancelled => -0.08,
104            Self::PreExecutionApproved | Self::ReviewApproved | Self::TestValidationApproved => {
105                0.08
106            }
107            Self::PreExecutionNeedsRevision
108            | Self::ReviewNeedsRevision
109            | Self::TestValidationNeedsRevision => -0.12,
110            Self::PreExecutionRejected | Self::ReviewRejected | Self::TestValidationRejected => {
111                -0.16
112            }
113        }
114    }
115}
116
117impl fmt::Display for OutcomeSignalKind {
118    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119        f.write_str(self.label())
120    }
121}
122
123/// Durable outcome observation attached to a reflection, task, or memory record.
124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
125pub struct OutcomeSignal {
126    /// Outcome label.
127    pub kind: OutcomeSignalKind,
128    /// When this outcome was observed.
129    pub observed_at: DateTime<Utc>,
130    /// Optional explanatory detail.
131    #[serde(default, skip_serializing_if = "Option::is_none")]
132    pub summary: Option<String>,
133}
134
135impl OutcomeSignal {
136    /// Build a new outcome signal with the current timestamp.
137    #[must_use]
138    pub fn new(kind: OutcomeSignalKind) -> Self {
139        Self {
140            kind,
141            observed_at: Utc::now(),
142            summary: None,
143        }
144    }
145
146    /// Attach explanatory detail to the signal.
147    #[must_use]
148    pub fn with_summary(mut self, summary: impl Into<String>) -> Self {
149        self.summary = Some(summary.into());
150        self
151    }
152
153    /// Stable label used for durable metadata and retrieval tags.
154    #[must_use]
155    pub const fn durable_label(&self) -> &'static str {
156        self.kind.as_str()
157    }
158}