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    /// Spec §77: false = inspect without display; skip the ledger write
72    /// so future deltas still surface these ids. Default true (shown).
73    #[serde(default = "record_visible_on")]
74    pub record_visibility: bool,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
78// trace:exempt reason=internal-detail
79pub struct StartupRequest {
80    #[serde(default)]
81    pub budget: Option<usize>,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
85// trace:exempt reason=internal-detail
86pub struct SurfaceRequest {
87    #[serde(default)]
88    pub task: Option<String>,
89    #[serde(default)]
90    pub budget: Option<usize>,
91    #[serde(default)]
92    pub explain: bool,
93    /// Optional per-stage toggles for the surface ablation matrix
94    /// (`build_surface_staged`): omitted or all-true = `build_surface`.
95    #[serde(default)]
96    pub stages: Option<SurfaceStages>,
97}
98
99/// Per-stage toggles for `surface.build` (all default true).
100#[derive(Debug, Clone, Default, Serialize, Deserialize, schemars::JsonSchema)]
101// trace:exempt reason=internal-detail
102pub struct SurfaceStages {
103    #[serde(default = "stage_on")]
104    pub lexical: bool,
105    #[serde(default = "stage_on")]
106    pub global_ppr: bool,
107    #[serde(default = "stage_on")]
108    pub task_ppr: bool,
109    #[serde(default = "stage_on")]
110    pub mmr: bool,
111    #[serde(default = "stage_on")]
112    pub quotas: bool,
113    #[serde(default = "stage_on")]
114    pub optimizer: bool,
115}
116
117// trace:exempt reason=internal-detail
118fn stage_on() -> bool { true }
119
120#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
121// trace:exempt reason=internal-detail
122pub struct DetailRequest {
123    pub id: String,
124    #[serde(default)]
125    pub unbounded: bool,
126}
127
128#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
129// trace:exempt reason=internal-detail
130pub struct ImpactRequest {
131    #[serde(default)]
132    pub files: Vec<String>,
133    #[serde(default)]
134    pub symbols: Vec<String>,
135    #[serde(default)]
136    pub diff: Option<String>,
137    #[serde(default)]
138    pub unbounded: bool,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
142// trace:exempt reason=internal-detail
143pub struct StructuralRequest {
144    #[serde(default)]
145    pub files: Vec<String>,
146    #[serde(default)]
147    pub task: Option<String>,
148    #[serde(default)]
149    pub budget: Option<usize>,
150}
151
152#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
153// trace:exempt reason=internal-detail
154pub struct QueryRequest {
155    pub query: String,
156    #[serde(default)]
157    pub limit: usize,
158}
159
160/// Serializable multi-step graph traversal (§16): start entities resolved
161/// by kind + name substring, then each step walks `out` / `in` / `both`
162/// edges (optional predicate filter, optional kind filter on the landing
163/// entity). Pure JSON — the fluent builder lives in SDKs, this AST is
164/// the wire contract every transport shares.
165#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
166// trace:exempt reason=internal-detail
167pub struct TraverseStep {
168    /// Edge direction from the current frontier: `out`, `in`, or `both`.
169    pub dir: String,
170    /// Optional predicate filter (e.g. `calls`, `writes`).
171    #[serde(default)]
172    pub predicate: Option<String>,
173    /// Optional kind filter on the landing entity (e.g. `symbol`, `state`).
174    #[serde(default)]
175    pub where_kind: Option<String>,
176    /// Max edges followed per frontier entity per step (default 50).
177    #[serde(default)]
178    pub limit: usize,
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
182// trace:exempt reason=internal-detail
183pub struct TraverseRequest {
184    /// Entity kind of the start set (e.g. `symbol`).
185    #[serde(default)]
186    pub kind: Option<String>,
187    /// Name substring matched (LIKE) against the start kind.
188    #[serde(default)]
189    pub name: Option<String>,
190    /// Explicit start entity ids (unioned with kind/name matches).
191    #[serde(default)]
192    pub from_ids: Vec<String>,
193    /// Ordered traversal steps applied to the frontier.
194    #[serde(default)]
195    pub steps: Vec<TraverseStep>,
196    /// Max entities returned (default 100).
197    #[serde(default)]
198    pub limit: usize,
199    /// Filter edges/entities through TrustedGraphView (default true).
200    /// False exposes the raw graph explicitly — never silently.
201    #[serde(default = "trusted_on")]
202    pub trusted_only: bool,
203}
204
205// trace:exempt reason=internal-detail
206fn trusted_on() -> bool { true }
207
208// trace:exempt reason=internal-detail
209fn record_visible_on() -> bool { true }
210
211#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
212// trace:exempt reason=internal-detail
213pub struct DiffRequest {
214    pub from: i64,
215    pub to: i64,
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
219// trace:exempt reason=internal-detail
220pub struct SnapshotSaveRequest {
221    pub task: String,
222    #[serde(default)]
223    pub budget: Option<usize>,
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
227// trace:exempt reason=internal-detail
228pub struct ExportRequest {
229    pub format: String,
230}
231
232#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
233// trace:exempt reason=internal-detail
234pub struct IndexPathsRequest {
235    pub paths: Vec<String>,
236    #[serde(default)]
237    pub quiet: bool,
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
241// trace:exempt reason=internal-detail
242pub struct RankRequest {
243    #[serde(default)]
244    pub goal: Option<String>,
245    #[serde(default)]
246    pub profile: Option<String>,
247    #[serde(default)]
248    pub limit: usize,
249    #[serde(default)]
250    pub explain: bool,
251    #[serde(default)]
252    pub include_features: bool,
253    #[serde(default)]
254    pub include_intermediate: bool,
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
258// trace:exempt reason=internal-detail
259pub struct RankItem {
260    pub id: String,
261    pub rank: f64,
262    pub position: usize,
263    pub features: RankFeatures,
264    pub specificity: f64,
265    pub reasons: Vec<String>,
266    #[serde(default)]
267    pub plugin_features: std::collections::BTreeMap<String, f64>,
268}
269
270#[derive(Debug, Clone, Default, Serialize, Deserialize, schemars::JsonSchema)]
271// trace:exempt reason=internal-detail
272pub struct RankFeatures {
273    pub task_ppr: f64,
274    pub global_ppr: f64,
275    pub lexical: f64,
276    pub semantic: f64,
277    pub confidence: f64,
278    pub criticality: f64,
279    pub change_risk: f64,
280    pub novelty: f64,
281}
282
283#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
284// trace:exempt reason=internal-detail
285pub struct RankResult {
286    pub items: Vec<RankItem>,
287    #[serde(default)]
288    pub omitted_ids: Vec<String>,
289    #[serde(default)]
290    pub warnings: Vec<String>,
291}
292
293#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
294// trace:exempt reason=internal-detail
295pub struct SelectionRequest {
296    pub ranked: Vec<RankedEntry>,
297    pub budget: usize,
298    #[serde(default)]
299    pub lambda: Option<f64>,
300    #[serde(default)]
301    pub quotas: Option<Vec<QuotaEntry>>,
302}
303
304#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
305// trace:exempt reason=internal-detail
306pub struct RankedEntry {
307    pub id: String,
308    pub value: f64,
309    #[serde(default)]
310    pub token_cost: usize,
311    #[serde(default)]
312    pub kind: String,
313    #[serde(default)]
314    pub group: Option<String>,
315}
316
317#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
318// trace:exempt reason=internal-detail
319pub struct QuotaEntry {
320    pub kind: String,
321    pub fraction: f64,
322}
323
324#[cfg(test)]
325mod tests {
326    #[test]
327    // trace:exempt reason=unit-test
328    fn request_schemas_generate() {
329        // Every portable request type emits a JSON schema: the §6 contract.
330        let schemas = [
331            schemars::schema_for!(crate::TaskContextRequest),
332            schemars::schema_for!(crate::StartupRequest),
333            schemars::schema_for!(crate::SurfaceRequest),
334            schemars::schema_for!(crate::DetailRequest),
335            schemars::schema_for!(crate::ImpactRequest),
336            schemars::schema_for!(crate::StructuralRequest),
337            schemars::schema_for!(crate::QueryRequest),
338            schemars::schema_for!(crate::DiffRequest),
339            schemars::schema_for!(crate::SnapshotSaveRequest),
340            schemars::schema_for!(crate::ExportRequest),
341            schemars::schema_for!(crate::IndexPathsRequest),
342            schemars::schema_for!(crate::RankRequest),
343            schemars::schema_for!(crate::SelectionRequest),
344            schemars::schema_for!(crate::ContextPack),
345            schemars::schema_for!(crate::SystemIr),
346            schemars::schema_for!(crate::SystemAtlas),
347            schemars::schema_for!(crate::SystemSurfaceMap),
348            schemars::schema_for!(crate::SurfaceRenderResult),
349        ];
350        for s in &schemas {
351            let v = serde_json::to_value(s).unwrap();
352            assert!(v.get("$schema").is_some(), "{v}");
353        }
354    }
355}