Skip to main content

sim_lib_agent/atelier/
self_hosting.rs

1//! Deterministic self-hosting scenario fixtures for the Atelier.
2
3use sim_cookbook::fnv1a64_hex;
4
5/// One self-hosting scenario exercised by recipes and conformance tests.
6#[derive(Clone, Debug, PartialEq, Eq)]
7pub struct SelfHostingScenario {
8    /// Stable scenario id, matching the recipe directory.
9    pub id: &'static str,
10    /// Scenario title.
11    pub title: &'static str,
12    /// Runner mode used by the scenario.
13    pub runner_mode: &'static str,
14    /// Whether a live model is used.
15    pub live_model: bool,
16    /// Whether network access is used.
17    pub network: bool,
18    /// Required evidence families.
19    pub evidence: &'static [&'static str],
20    /// Roles participating in the scenario.
21    pub roles: &'static [&'static str],
22    /// Cassette event payloads used for the replay hash.
23    pub cassette_events: &'static [&'static str],
24    /// Expected replay hash for `cassette_events`.
25    pub cassette_hash: &'static str,
26}
27
28/// Returns the Atelier self-hosting scenarios used by recipe and cassette tests.
29pub 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
231/// Validate offline runner, evidence, and replay-hash invariants.
232pub 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
258/// Stable FNV-1a hash used by self-hosting cassette fixtures.
259pub 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}