1use crate::canonical::compute_id;
2use crate::store::Store;
3use crate::tick::{Check, Ground, Liveness, Tick};
4use crate::verify::verify;
5use std::path::Path;
6use std::process::ExitCode;
7
8pub fn init(repo: &Path) -> ExitCode {
9 let store = Store::at(repo);
10 match store.init() {
11 Ok(true) => {
12 println!("created .evolving/ (content-addressed chain + results cache)");
13 ExitCode::SUCCESS
14 }
15 Ok(false) => {
16 println!(".evolving/ already exists (no-op)");
17 ExitCode::SUCCESS
18 }
19 Err(e) => {
20 eprintln!("error: could not create .evolving/: {e}");
21 ExitCode::FAILURE
22 }
23 }
24}
25pub fn show(repo: &Path, id: &str) -> ExitCode {
26 let store = Store::at(repo);
27 let path = store.ticks_dir().join(id);
28 if !path.is_file() {
29 eprintln!("error: no tick with id {id}");
30 return ExitCode::FAILURE;
31 }
32 match std::fs::read_to_string(&path) {
33 Ok(text) => {
34 println!("{text}");
36 ExitCode::SUCCESS
37 }
38 Err(e) => {
39 eprintln!("error: reading {id}: {e}");
40 ExitCode::FAILURE
41 }
42 }
43}
44pub fn decide(repo: &Path, decision: &str, args: &[String]) -> ExitCode {
45 match crate::capture::run(repo, decision, args) {
46 Ok(t) => {
47 println!("recorded {} ({} ground(s))", t.id, t.grounds.len());
48 ExitCode::SUCCESS
49 }
50 Err(e) => {
51 eprintln!("error: {e}");
52 ExitCode::FAILURE
53 }
54 }
55}
56
57pub fn guard(repo: &Path, a: crate::guard::GuardArgs) -> ExitCode {
58 match crate::guard::run(repo, a) {
59 Ok(t) => {
60 println!("bound; wrote child {}", t.id);
61 ExitCode::SUCCESS
62 }
63 Err(e) => {
64 eprintln!("error: {e}");
65 ExitCode::FAILURE
66 }
67 }
68}
69
70pub fn verify_cmd(repo: &Path, self_test: bool) -> ExitCode {
71 if self_test {
72 return self_test_golden();
73 }
74 let store = Store::at(repo);
75 match verify(&store) {
76 Ok(v) if v.is_empty() => {
77 println!("✓ chain intact: every id == hash(payload), lineage forward-only");
78 println!("✓ every tick validates against the closed schema (R1) and check shape (R2)");
79 ExitCode::SUCCESS
80 }
81 Ok(v) => {
82 for line in &v {
83 println!("✗ {line}");
84 }
85 eprintln!("{} violation(s)", v.len());
86 ExitCode::FAILURE
87 }
88 Err(e) => {
89 eprintln!("error: reading store: {e}");
90 ExitCode::FAILURE
91 }
92 }
93}
94
95fn self_test_golden() -> ExitCode {
97 let genesis = Tick {
98 id: String::new(),
99 parent_id: "".into(),
100 observe: "evaluating retrieval backend".into(),
101 decision: "freeze the retrieval schema for v2".into(),
102 grounds: vec![
103 Ground {
104 claim: "team still wants a frozen schema".into(),
105 supports: "chosen".into(),
106 check: Some(Check::Person {
107 reference: "Q3 infra review".into(),
108 }),
109 },
110 Ground {
111 claim: "pgvector would lock our schema".into(),
112 supports: "rejected:pgvector".into(),
113 check: None,
114 },
115 ],
116 status: "live".into(),
117 held_since: "".into(),
118 blame: "Wang Yu".into(),
119 };
120 let case1 = Tick {
121 id: String::new(),
122 parent_id: "7b21f0a4c8de".into(),
123 observe: "multi-pod restore-safety counter — chat-room R2289→R2290".into(),
124 decision: "restore-safety counter DB-backed; reject Redis".into(),
125 grounds: vec![
126 Ground {
127 claim: "Argus introduces no Redis; multi-pod coord via existing DB".into(),
128 supports: "chosen".into(),
129 check: Some(Check::Test {
130 reference: "pytest tests/test_redis_absent.py".into(),
131 verified_at_sha: "d308afac1b2c3d4e5f60718293a4b5c6d7e8f901".into(),
132 counter_test:
133 "pytest tests/test_redis_absent.py::test_redis_injection_flips_red".into(),
134 liveness: Liveness {
135 platforms: vec!["linux-ci".into()],
136 triggered_by: vec!["pyproject.toml".into()],
137 surfaces: vec!["pyproject-deps".into()],
138 },
139 }),
140 },
141 Ground {
142 claim: "team still wants 0-Redis posture".into(),
143 supports: "chosen".into(),
144 check: Some(Check::Person {
145 reference: "Q3 infra review".into(),
146 }),
147 },
148 Ground {
149 claim: "Redis would add a new infra dependency".into(),
150 supports: "rejected:Redis".into(),
151 check: None,
152 },
153 ],
154 status: "live".into(),
155 held_since: "".into(),
156 blame: "Wang Yu".into(),
157 };
158 let mut ok = true;
159 for (name, t, want) in [
160 ("genesis", &genesis, "e2b337f53a1f"),
161 ("case1", &case1, "638c47b0c9dd"),
162 ] {
163 let got = compute_id(t);
164 let pass = got == want;
165 ok &= pass;
166 println!(
167 "{} {name}: {got} (want {want})",
168 if pass { "✓" } else { "✗" }
169 );
170 }
171 if ok {
172 ExitCode::SUCCESS
173 } else {
174 ExitCode::FAILURE
175 }
176}