Skip to main content

scc_api/
lib.rs

1//! SCC versioned request/response contracts.
2//!
3//! Thin by design: result types ARE the canonical engine types
4//! (re-exported, never re-modelled), and request types are the exact
5//! argument tuples every transport already passes to the builders.
6//! Serialization is the only difference between transports — never
7//! semantic content. (`JsonSchema` derives arrive with the schemars
8//! dependency; adding a derive never changes the JSON wire shape.)
9
10use serde::{Deserialize, Serialize};
11
12/// Operation API version negotiated per request.
13// trace:exempt reason=internal-detail
14pub type ApiVersion = u32;
15
16/// The current operation API version.
17// trace:exempt reason=internal-detail
18pub const API_VERSION: ApiVersion = 1;
19
20/// SCC plugin API compatibility version.
21// trace:exempt reason=internal-detail
22pub const PLUGIN_API_VERSION: ApiVersion = 1;
23
24// ---------------------------------------------------------------------------
25// Canonical result types (single model — re-exported, not redefined).
26// ---------------------------------------------------------------------------
27
28// trace:exempt reason=internal-detail
29pub use scc_context::ContextPack;
30// trace:exempt reason=internal-detail
31pub use scc_core::{SurfaceRenderResult, SystemAtlas, SystemIr, SystemSurfaceMap};
32
33// ---------------------------------------------------------------------------
34// Envelope: every operation response carries model identity.
35// ---------------------------------------------------------------------------
36
37#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
38// trace:exempt reason=internal-detail
39pub struct ModelIdentity {
40    pub epoch: String,
41    pub graph_revision: i64,
42    pub repository_revision: String,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
46// trace:exempt reason=internal-detail
47pub struct OperationResponse<T> {
48    pub operation: String,
49    pub api_version: ApiVersion,
50    pub scc_version: String,
51    pub model: ModelIdentity,
52    pub output: T,
53}
54
55// ---------------------------------------------------------------------------
56// Requests: one struct per operation family, mirroring the CLI args.
57// ---------------------------------------------------------------------------
58
59#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
60// trace:exempt reason=internal-detail
61pub struct TaskContextRequest {
62    pub goal: String,
63    #[serde(default)]
64    pub files: Vec<String>,
65    #[serde(default)]
66    pub symbols: Vec<String>,
67    #[serde(default)]
68    pub budget: Option<usize>,
69    #[serde(default)]
70    pub hook: bool,
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
74// trace:exempt reason=internal-detail
75pub struct StartupRequest {
76    #[serde(default)]
77    pub budget: Option<usize>,
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
81// trace:exempt reason=internal-detail
82pub struct SurfaceRequest {
83    #[serde(default)]
84    pub task: Option<String>,
85    #[serde(default)]
86    pub budget: Option<usize>,
87    #[serde(default)]
88    pub explain: bool,
89    /// Optional per-stage toggles for the surface ablation matrix
90    /// (`build_surface_staged`): omitted or all-true = `build_surface`.
91    #[serde(default)]
92    pub stages: Option<SurfaceStages>,
93}
94
95/// Per-stage toggles for `surface.build` (all default true).
96#[derive(Debug, Clone, Default, Serialize, Deserialize, schemars::JsonSchema)]
97// trace:exempt reason=internal-detail
98pub struct SurfaceStages {
99    #[serde(default = "stage_on")]
100    pub lexical: bool,
101    #[serde(default = "stage_on")]
102    pub global_ppr: bool,
103    #[serde(default = "stage_on")]
104    pub task_ppr: bool,
105    #[serde(default = "stage_on")]
106    pub mmr: bool,
107    #[serde(default = "stage_on")]
108    pub quotas: bool,
109    #[serde(default = "stage_on")]
110    pub optimizer: bool,
111}
112
113// trace:exempt reason=internal-detail
114fn stage_on() -> bool { true }
115
116#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
117// trace:exempt reason=internal-detail
118pub struct DetailRequest {
119    pub id: String,
120    #[serde(default)]
121    pub unbounded: bool,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
125// trace:exempt reason=internal-detail
126pub struct ImpactRequest {
127    #[serde(default)]
128    pub files: Vec<String>,
129    #[serde(default)]
130    pub symbols: Vec<String>,
131    #[serde(default)]
132    pub diff: Option<String>,
133    #[serde(default)]
134    pub unbounded: bool,
135}
136
137#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
138// trace:exempt reason=internal-detail
139pub struct StructuralRequest {
140    #[serde(default)]
141    pub files: Vec<String>,
142    #[serde(default)]
143    pub task: Option<String>,
144    #[serde(default)]
145    pub budget: Option<usize>,
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
149// trace:exempt reason=internal-detail
150pub struct QueryRequest {
151    pub query: String,
152    #[serde(default)]
153    pub limit: usize,
154}
155
156#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
157// trace:exempt reason=internal-detail
158pub struct DiffRequest {
159    pub from: i64,
160    pub to: i64,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
164// trace:exempt reason=internal-detail
165pub struct SnapshotSaveRequest {
166    pub task: String,
167    #[serde(default)]
168    pub budget: Option<usize>,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
172// trace:exempt reason=internal-detail
173pub struct ExportRequest {
174    pub format: String,
175}
176
177#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
178// trace:exempt reason=internal-detail
179pub struct IndexPathsRequest {
180    pub paths: Vec<String>,
181    #[serde(default)]
182    pub quiet: bool,
183}
184
185#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
186// trace:exempt reason=internal-detail
187pub struct RankRequest {
188    #[serde(default)]
189    pub goal: Option<String>,
190    #[serde(default)]
191    pub profile: Option<String>,
192    #[serde(default)]
193    pub limit: usize,
194    #[serde(default)]
195    pub explain: bool,
196    #[serde(default)]
197    pub include_features: bool,
198    #[serde(default)]
199    pub include_intermediate: bool,
200}
201
202#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
203// trace:exempt reason=internal-detail
204pub struct RankItem {
205    pub id: String,
206    pub rank: f64,
207    pub position: usize,
208    pub features: RankFeatures,
209    pub specificity: f64,
210    pub reasons: Vec<String>,
211    #[serde(default)]
212    pub plugin_features: std::collections::BTreeMap<String, f64>,
213}
214
215#[derive(Debug, Clone, Default, Serialize, Deserialize, schemars::JsonSchema)]
216// trace:exempt reason=internal-detail
217pub struct RankFeatures {
218    pub task_ppr: f64,
219    pub global_ppr: f64,
220    pub lexical: f64,
221    pub semantic: f64,
222    pub confidence: f64,
223    pub criticality: f64,
224    pub change_risk: f64,
225    pub novelty: f64,
226}
227
228#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
229// trace:exempt reason=internal-detail
230pub struct RankResult {
231    pub items: Vec<RankItem>,
232    #[serde(default)]
233    pub omitted_ids: Vec<String>,
234    #[serde(default)]
235    pub warnings: Vec<String>,
236}
237
238#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
239// trace:exempt reason=internal-detail
240pub struct SelectionRequest {
241    pub ranked: Vec<RankedEntry>,
242    pub budget: usize,
243    #[serde(default)]
244    pub lambda: Option<f64>,
245    #[serde(default)]
246    pub quotas: Option<Vec<QuotaEntry>>,
247}
248
249#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
250// trace:exempt reason=internal-detail
251pub struct RankedEntry {
252    pub id: String,
253    pub value: f64,
254    #[serde(default)]
255    pub token_cost: usize,
256    #[serde(default)]
257    pub kind: String,
258    #[serde(default)]
259    pub group: Option<String>,
260}
261
262#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
263// trace:exempt reason=internal-detail
264pub struct QuotaEntry {
265    pub kind: String,
266    pub fraction: f64,
267}
268
269#[cfg(test)]
270mod tests {
271    #[test]
272    // trace:exempt reason=unit-test
273    fn request_schemas_generate() {
274        // Every portable request type emits a JSON schema: the §6 contract.
275        let schemas = [
276            schemars::schema_for!(crate::TaskContextRequest),
277            schemars::schema_for!(crate::StartupRequest),
278            schemars::schema_for!(crate::SurfaceRequest),
279            schemars::schema_for!(crate::DetailRequest),
280            schemars::schema_for!(crate::ImpactRequest),
281            schemars::schema_for!(crate::StructuralRequest),
282            schemars::schema_for!(crate::QueryRequest),
283            schemars::schema_for!(crate::DiffRequest),
284            schemars::schema_for!(crate::SnapshotSaveRequest),
285            schemars::schema_for!(crate::ExportRequest),
286            schemars::schema_for!(crate::IndexPathsRequest),
287            schemars::schema_for!(crate::RankRequest),
288            schemars::schema_for!(crate::SelectionRequest),
289            schemars::schema_for!(crate::ContextPack),
290            schemars::schema_for!(crate::SystemIr),
291            schemars::schema_for!(crate::SystemAtlas),
292            schemars::schema_for!(crate::SystemSurfaceMap),
293            schemars::schema_for!(crate::SurfaceRenderResult),
294        ];
295        for s in &schemas {
296            let v = serde_json::to_value(s).unwrap();
297            assert!(v.get("$schema").is_some(), "{v}");
298        }
299    }
300}