Skip to main content

sim_lib_agent/atelier/
contract_native.rs

1//! Contract-native Atelier backend descriptors.
2
3use serde_json::{Value, json};
4use sim_kernel::Symbol;
5
6use super::{
7    AgentMission, AtelierAction, GuardCapability, GuardDecision, guard_action,
8    self_hosting::cassette_content_hash,
9};
10
11/// Stable JSON schema for contract-native Atelier cache evidence.
12pub const CONTRACT_NATIVE_SCHEMA: &str = "sim.atelier.contract-native.v1";
13
14/// Backend used by the Atelier shell.
15#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
16pub enum AtelierBackend {
17    /// Current source-Radar authoring view.
18    #[default]
19    SourceRadar,
20    /// Source-free FORGE contract authoring view.
21    ContractNative,
22}
23
24impl AtelierBackend {
25    /// Parses a backend label accepted by `simctl atelier --backend`.
26    pub fn parse(value: &str) -> Option<Self> {
27        match value {
28            "source-radar" => Some(Self::SourceRadar),
29            "contract-native" => Some(Self::ContractNative),
30            _ => None,
31        }
32    }
33
34    /// Returns the stable backend label.
35    pub fn as_str(self) -> &'static str {
36        match self {
37            Self::SourceRadar => "source-radar",
38            Self::ContractNative => "contract-native",
39        }
40    }
41}
42
43/// Contract deck totals cached for the contract-native Atelier backend.
44#[derive(Clone, Debug, PartialEq, Eq)]
45pub struct ContractNativeDeckSummary {
46    /// Number of cards selected for the task-scoped deck.
47    pub cards: u64,
48    /// Number of cards that carry complete callable contract data.
49    pub complete_cards: u64,
50    /// Number of partial cards preserved with diagnostics.
51    pub partial_cards: u64,
52    /// Non-fatal deck diagnostics.
53    pub diagnostics: Vec<String>,
54}
55
56/// Projection reductions and token accounting cached for the backend.
57#[derive(Clone, Debug, PartialEq, Eq)]
58pub struct ContractNativeProjectionSummary {
59    /// Prompt token budget used for projection.
60    pub token_budget: u64,
61    /// Estimated prompt tokens in the selected projection.
62    pub tokens: u64,
63    /// Cards retained in any representation.
64    pub included: u64,
65    /// Cards reduced to summary-only form.
66    pub summary_only: u64,
67    /// Cards dropped because they did not fit the budget.
68    pub dropped: u64,
69    /// Reduction diagnostics emitted during projection.
70    pub diagnostics: Vec<String>,
71}
72
73/// Grammar metadata cached with the contract-native backend.
74#[derive(Clone, Debug, PartialEq, Eq)]
75pub struct ContractNativeGrammarSummary {
76    /// Grammar dialect used by the output contract.
77    pub dialect: String,
78    /// Target codec for terminal model output.
79    pub target_codec: String,
80    /// Return Shape expression used to derive the grammar.
81    pub return_shape: String,
82    /// Whether strict grammar output is required.
83    pub strict: bool,
84}
85
86/// One route attempt cached from the deterministic authoring loop.
87#[derive(Clone, Debug, PartialEq, Eq)]
88pub struct ContractNativeRouteAttempt {
89    /// Route target id.
90    pub target: String,
91    /// Attempt status such as `failed` or `accepted`.
92    pub status: String,
93    /// Optional failure or skip reason.
94    pub reason: Option<String>,
95}
96
97/// One guarded action denied for contract-native authoring.
98#[derive(Clone, Debug, PartialEq, Eq)]
99pub struct ContractNativeGuardDenial {
100    /// Stable denial id used by cache consumers.
101    pub id: String,
102    /// Human-readable action label.
103    pub action: String,
104    /// Refusal reason from the Atelier guard.
105    pub reason: String,
106    /// Capability that would normally guard the action family.
107    pub required_capability: String,
108}
109
110/// Deterministic cache report for the contract-native Atelier backend.
111#[derive(Clone, Debug, PartialEq, Eq)]
112pub struct ContractNativeAtelierReport {
113    /// Backend label.
114    pub backend: AtelierBackend,
115    /// Contract deck totals.
116    pub deck: ContractNativeDeckSummary,
117    /// Projection reductions and token totals.
118    pub projection: ContractNativeProjectionSummary,
119    /// Grammar metadata.
120    pub grammar: ContractNativeGrammarSummary,
121    /// Route attempts.
122    pub route_attempts: Vec<ContractNativeRouteAttempt>,
123    /// Non-fatal backend diagnostics.
124    pub diagnostics: Vec<String>,
125    /// Stable cassette hash for the deterministic evidence events.
126    pub cassette_hash: String,
127    /// Guard denials retained by the backend.
128    pub guard_denials: Vec<ContractNativeGuardDenial>,
129}
130
131impl ContractNativeAtelierReport {
132    /// Encodes the report as the public Atelier cache JSON shape.
133    pub fn to_json(&self) -> Value {
134        json!({
135            "schema": CONTRACT_NATIVE_SCHEMA,
136            "backend": self.backend.as_str(),
137            "contract_deck": {
138                "cards": self.deck.cards,
139                "complete_cards": self.deck.complete_cards,
140                "partial_cards": self.deck.partial_cards,
141                "diagnostics": self.deck.diagnostics,
142            },
143            "projection": {
144                "token_budget": self.projection.token_budget,
145                "tokens": self.projection.tokens,
146                "included": self.projection.included,
147                "summary_only": self.projection.summary_only,
148                "dropped": self.projection.dropped,
149                "diagnostics": self.projection.diagnostics,
150            },
151            "grammar": {
152                "dialect": self.grammar.dialect,
153                "target_codec": self.grammar.target_codec,
154                "return_shape": self.grammar.return_shape,
155                "strict": self.grammar.strict,
156            },
157            "route_attempts": self.route_attempts.iter().map(|attempt| {
158                json!({
159                    "target": attempt.target,
160                    "status": attempt.status,
161                    "reason": attempt.reason,
162                })
163            }).collect::<Vec<_>>(),
164            "diagnostics": self.diagnostics,
165            "cassette_hash": self.cassette_hash,
166            "guard_denials": self.guard_denials.iter().map(|denial| {
167                json!({
168                    "id": denial.id,
169                    "action": denial.action,
170                    "reason": denial.reason,
171                    "required_capability": denial.required_capability,
172                })
173            }).collect::<Vec<_>>(),
174        })
175    }
176}
177
178/// Builds deterministic cache evidence for contract-native Atelier mode.
179pub fn deterministic_contract_native_report() -> ContractNativeAtelierReport {
180    let mission = AgentMission::new(
181        Symbol::qualified("agent/mission", "contract-native-atelier"),
182        "sim-agent-net",
183    )
184    .with_capability(GuardCapability::EditRepo("sim-agent-net".to_owned()));
185    let events = [
186        "contract-native:deck:task-scoped",
187        "contract-native:projection:tokens=114",
188        "contract-native:grammar:shapegrammar",
189        "contract-native:route:cheap-downshift:failed",
190        "contract-native:route:escalation-downshift:accepted",
191        "contract-native:guard:denials-retained",
192    ];
193    ContractNativeAtelierReport {
194        backend: AtelierBackend::ContractNative,
195        deck: ContractNativeDeckSummary {
196            cards: 4,
197            complete_cards: 3,
198            partial_cards: 1,
199            diagnostics: vec![
200                "missing authored example preserved with Shape-synthesized fallback".to_owned(),
201            ],
202        },
203        projection: ContractNativeProjectionSummary {
204            token_budget: 160,
205            tokens: 114,
206            included: 3,
207            summary_only: 1,
208            dropped: 1,
209            diagnostics: vec![
210                "contract projection reduced table/entries to summary only under token budget"
211                    .to_owned(),
212                "contract projection dropped unrelated export under token budget".to_owned(),
213            ],
214        },
215        grammar: ContractNativeGrammarSummary {
216            dialect: "shapegrammar".to_owned(),
217            target_codec: "codec:lisp".to_owned(),
218            return_shape: "(list-rest () Any)".to_owned(),
219            strict: true,
220        },
221        route_attempts: vec![
222            ContractNativeRouteAttempt {
223                target: "cheap-downshift".to_owned(),
224                status: "failed".to_owned(),
225                reason: Some("terminal output failed codec or Shape check".to_owned()),
226            },
227            ContractNativeRouteAttempt {
228                target: "escalation-downshift".to_owned(),
229                status: "accepted".to_owned(),
230                reason: None,
231            },
232        ],
233        diagnostics: vec![
234            "contract-native cache evidence is deterministic and source-free".to_owned(),
235            "sim-web-shell serves this cache without issuing model requests".to_owned(),
236        ],
237        cassette_hash: cassette_content_hash(&events),
238        guard_denials: contract_native_guard_denials(&mission),
239    }
240}
241
242/// Evaluates the guard denials contract-native mode must retain by default.
243pub fn contract_native_guard_denials(mission: &AgentMission) -> Vec<ContractNativeGuardDenial> {
244    [
245        (
246            "meta-workspace-edit",
247            AtelierAction::edit_file(
248                mission.leased_repo(),
249                ".meta-workspace/packages/sim-lib-agent/src/atelier.rs",
250            ),
251            "EditRepo(sim-agent-net)",
252        ),
253        (
254            "cross-repo-write",
255            AtelierAction::edit_file("sim-web", "crates/sim-web-shell/src/atelier.rs"),
256            "EditRepo(sim-web)",
257        ),
258        (
259            "github-outward-action",
260            AtelierAction::AddGithubRemote {
261                remote: "https://github.com/sim-nest/sim-private.git".to_owned(),
262            },
263            "PlanPin",
264        ),
265    ]
266    .into_iter()
267    .filter_map(|(id, action, required_capability)| {
268        let decision = guard_action(mission, action);
269        let GuardDecision::Refused(refusal) = decision else {
270            return None;
271        };
272        Some(ContractNativeGuardDenial {
273            id: id.to_owned(),
274            action: refusal.action().label(),
275            reason: refusal.reason().to_owned(),
276            required_capability: required_capability.to_owned(),
277        })
278    })
279    .collect()
280}