1use sim_cookbook::fnv1a64_hex;
4
5#[derive(Clone, Debug, PartialEq, Eq)]
7pub struct SelfHostingScenario {
8 pub id: &'static str,
10 pub title: &'static str,
12 pub runner_mode: &'static str,
14 pub live_model: bool,
16 pub network: bool,
18 pub evidence: &'static [&'static str],
20 pub roles: &'static [&'static str],
22 pub cassette_events: &'static [&'static str],
24 pub cassette_hash: &'static str,
26}
27
28pub fn self_hosting_scenarios() -> Vec<SelfHostingScenario> {
30 vec![
31 SelfHostingScenario {
32 id: "atelier-radar-standard-crate",
33 title: "Retrieval Radar standard crate",
34 runner_mode: "fake",
35 live_model: false,
36 network: false,
37 evidence: &["radar", "ranked-hints", "confidence"],
38 roles: &["cartographer"],
39 cassette_events: &[
40 "fake-runner:load-index:sim-kernel",
41 "radar-query:standard-crate-operations",
42 "rank:hints:3",
43 "explain:confidence:0.91",
44 ],
45 cassette_hash: "fnv1a64:7f9f5b6c744aa528",
46 },
47 SelfHostingScenario {
48 id: "atelier-runtime-operation",
49 title: "Runtime operation edit loop",
50 runner_mode: "fake",
51 live_model: false,
52 network: false,
53 evidence: &[
54 "runtime-operation",
55 "rustdoc",
56 "codec-prism",
57 "generated-docs",
58 "validation",
59 "pin-plan",
60 ],
61 roles: &["editor", "validator", "docs-agent", "pin-agent"],
62 cassette_events: &[
63 "fake-runner:open-runtime-op-descriptor",
64 "editor:add-operation:sample-op",
65 "codec-prism:lisp-json-roundtrip",
66 "validate:meta-check",
67 "pin-plan:sim-runtime",
68 ],
69 cassette_hash: "fnv1a64:8a3282f3c7ef34b2",
70 },
71 SelfHostingScenario {
72 id: "atelier-codec-roundtrip",
73 title: "Codec round-trip fixture",
74 runner_mode: "fake",
75 live_model: false,
76 network: false,
77 evidence: &["codec-prism", "roundtrip", "semantic-id", "simdoc"],
78 roles: &["editor", "validator"],
79 cassette_events: &[
80 "fake-runner:load-codec-fixture",
81 "codec-prism:lisp-json-algol-roundtrip",
82 "assert:semantic-id-stable",
83 "docs:simdoc-check",
84 ],
85 cassette_hash: "fnv1a64:32be093d6f32315a",
86 },
87 SelfHostingScenario {
88 id: "atelier-guideline-firewall",
89 title: "Guideline Firewall bad fixture",
90 runner_mode: "fake",
91 live_model: false,
92 network: false,
93 evidence: &["guideline-firewall", "rule-evidence", "refused-capability"],
94 roles: &["guard", "reviewer"],
95 cassette_events: &[
96 "fake-runner:load-bad-fixture",
97 "guard:present-tense-public-docs",
98 "refuse:EditRepo(sim-web)",
99 "evidence:past-tense-wording",
100 ],
101 cassette_hash: "fnv1a64:768ad342812bdd9a",
102 },
103 SelfHostingScenario {
104 id: "atelier-change-capsule",
105 title: "Multi-agent Change Capsule",
106 runner_mode: "cassette",
107 live_model: false,
108 network: false,
109 evidence: &[
110 "change-capsule",
111 "validation",
112 "docs",
113 "pin-plan",
114 "human-gate",
115 "replay-hash",
116 ],
117 roles: &[
118 "cartographer",
119 "editor",
120 "guard",
121 "validator",
122 "docs-agent",
123 "pin-agent",
124 "reviewer",
125 "human-gate",
126 ],
127 cassette_events: &[
128 "cassette-runner:cartographer-scope",
129 "fake-runner:editor-patch",
130 "fake-runner:guard-approve",
131 "process:validator-pass",
132 "process:docs-agent-pass",
133 "fake-runner:pin-agent-plan",
134 "fake-runner:reviewer-summary",
135 "human-gate:approved",
136 "replay:hash-match",
137 ],
138 cassette_hash: "fnv1a64:5ec7c4222478f8f1",
139 },
140 SelfHostingScenario {
141 id: "atelier-contract-deck-assembly",
142 title: "Contract deck assembly",
143 runner_mode: "fake",
144 live_model: false,
145 network: false,
146 evidence: &[
147 "contract-deck",
148 "contract-card-shape",
149 "shape-synthesized-example",
150 "partial-card-diagnostics",
151 ],
152 roles: &["cartographer", "contract-author"],
153 cassette_events: &[
154 "contract-deck:assemble:registry-generation-1",
155 "contract-card:shape:data-roundtrip",
156 "contract-card:example:shape-synthesized",
157 "contract-gap:partial-card-preserved",
158 ],
159 cassette_hash: "fnv1a64:f016c320c6277c50",
160 },
161 SelfHostingScenario {
162 id: "atelier-shape-query-cache",
163 title: "Cached ShapeQuery retrieval",
164 runner_mode: "fake",
165 live_model: false,
166 network: false,
167 evidence: &[
168 "shape-query",
169 "contract-deck-cache",
170 "cache-hit",
171 "shape-specificity",
172 ],
173 roles: &["cartographer"],
174 cassette_events: &[
175 "shape-query:table-set-entries",
176 "contract-deck-cache:miss:generation-1",
177 "contract-deck-cache:hit:generation-1",
178 "rank:shape-specificity:2",
179 ],
180 cassette_hash: "fnv1a64:ffca094bf5b5fb1e",
181 },
182 SelfHostingScenario {
183 id: "atelier-contract-native-success",
184 title: "Contract-native authoring success",
185 runner_mode: "fake",
186 live_model: false,
187 network: false,
188 evidence: &[
189 "contract-native",
190 "shapegrammar",
191 "shape-check",
192 "diminished-realize",
193 "checked-form",
194 "cassette",
195 ],
196 roles: &["contract-author", "validator"],
197 cassette_events: &[
198 "contract-native:author:strict-grammar",
199 "contract-native:decode:codec-lisp",
200 "contract-native:shape-check:accepted",
201 "contract-native:realize:diminished",
202 "contract-native:cassette:accepted",
203 ],
204 cassette_hash: "fnv1a64:099488ea5fbe5b6a",
205 },
206 SelfHostingScenario {
207 id: "atelier-cheap-first-escalation",
208 title: "Cheap-first authoring escalation",
209 runner_mode: "fake",
210 live_model: false,
211 network: false,
212 evidence: &[
213 "cheap-first",
214 "route-attempts",
215 "escalation",
216 "route-diagnostic",
217 "checked-form",
218 ],
219 roles: &["contract-author", "validator"],
220 cassette_events: &[
221 "contract-native:route:cheap-downshift:malformed",
222 "contract-native:route:cheap-downshift:diagnostic",
223 "contract-native:route:escalation-downshift:accepted",
224 "contract-native:route:attempts=2",
225 ],
226 cassette_hash: "fnv1a64:509ae4bba20e5cfd",
227 },
228 ]
229}
230
231pub fn validate_self_hosting_scenarios(scenarios: &[SelfHostingScenario]) -> Vec<String> {
233 let mut failures = Vec::new();
234 for scenario in scenarios {
235 if scenario.runner_mode != "fake" && scenario.runner_mode != "cassette" {
236 failures.push(format!("{} uses unsupported runner", scenario.id));
237 }
238 if scenario.live_model {
239 failures.push(format!("{} uses a live model", scenario.id));
240 }
241 if scenario.network {
242 failures.push(format!("{} uses network access", scenario.id));
243 }
244 if scenario.evidence.is_empty() {
245 failures.push(format!("{} has no evidence", scenario.id));
246 }
247 let actual_hash = cassette_content_hash(scenario.cassette_events);
248 if actual_hash != scenario.cassette_hash {
249 failures.push(format!(
250 "{} cassette hash mismatch: expected {}, got {}",
251 scenario.id, scenario.cassette_hash, actual_hash
252 ));
253 }
254 }
255 failures
256}
257
258pub fn cassette_content_hash(events: &[&str]) -> String {
260 let mut bytes = Vec::new();
261 for event in events {
262 bytes.extend_from_slice(event.as_bytes());
263 }
264 format!("fnv1a64:{}", fnv1a64_hex(&bytes))
265}