selfware/evolve/
actions.rs1use anyhow::Result;
7use chrono::Utc;
8
9#[derive(Debug, Clone)]
10pub enum Action {
11 Extend { component: String },
12 Connect { from: String, to: String },
13 BlockEvolution { component: String },
14 Notify { component: String },
15}
16
17pub struct ActionResult {
18 pub branch: Option<String>,
20 pub message: String,
21}
22
23pub struct ActionEngine {
24 }
26
27impl Default for ActionEngine {
28 fn default() -> Self {
29 Self::new()
30 }
31}
32
33impl ActionEngine {
34 pub fn new() -> Self {
35 Self {}
36 }
37
38 pub fn branch_name(action: &Action) -> String {
39 let ts = Utc::now().format("%Y%m%d-%H%M%S");
40 match action {
41 Action::Extend { component } => {
42 format!("evolve/extend-{}-{}", branch_segment(component), ts)
43 }
44 Action::Connect { from, to } => format!(
45 "evolve/connect-{}-{}-{}",
46 branch_segment(from),
47 branch_segment(to),
48 ts
49 ),
50 Action::BlockEvolution { component } => {
51 format!("evolve/block-{}-{}", branch_segment(component), ts)
52 }
53 Action::Notify { component } => {
54 format!("evolve/notify-{}-{}", branch_segment(component), ts)
55 }
56 }
57 }
58
59 pub fn propose(&self, action: &Action) -> Result<ActionResult> {
64 match action {
65 Action::Extend { component } => Ok(ActionResult {
66 branch: Some(Self::branch_name(action)),
67 message: format!("Proposed an extension branch for {component}"),
68 }),
69 _ => Ok(ActionResult {
70 branch: None,
71 message: "No mutation was performed; this action has no executor".to_string(),
72 }),
73 }
74 }
75}
76
77fn branch_segment(value: &str) -> String {
78 let mut result = String::new();
79 let mut previous_separator = false;
80 for character in value.chars() {
81 let allowed = character.is_ascii_alphanumeric() || matches!(character, '.' | '_' | '-');
82 if allowed {
83 result.push(character.to_ascii_lowercase());
84 previous_separator = false;
85 } else if !previous_separator && !result.is_empty() {
86 result.push('-');
87 previous_separator = true;
88 }
89 }
90 let trimmed = result.trim_matches(['.', '-']).to_string();
91 if trimmed.is_empty() {
92 "component".to_string()
93 } else {
94 trimmed
95 }
96}