Skip to main content

fallow_output/
similar_code.rs

1//! Independent JSON contracts for opt-in semantic similar-code discovery.
2
3use std::fmt;
4
5use crate::root_envelopes::{RootEnvelopeMode, serialize_named_json_output};
6use fallow_types::envelope::{ElapsedMs, ToolVersion};
7use serde::{Deserialize, Serialize};
8
9/// Current raw similar-code envelope schema version.
10pub const SIMILAR_CODE_SCHEMA_VERSION: u32 = 1;
11/// Current similar-code inspect envelope schema version.
12pub const SIMILAR_CODE_INSPECT_SCHEMA_VERSION: u32 = 1;
13/// Current similar-code review envelope schema version.
14pub const SIMILAR_CODE_REVIEW_SCHEMA_VERSION: u32 = 1;
15/// Current local-provider status envelope schema version.
16pub const SIMILAR_CODE_STATUS_SCHEMA_VERSION: u32 = 1;
17/// Current vector-cache clear envelope schema version.
18pub const SIMILAR_CODE_CACHE_CLEAR_SCHEMA_VERSION: u32 = 1;
19
20/// Version singleton for raw similar-code output.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
22#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
23pub enum SimilarCodeSchemaVersion {
24    /// Initial independent contract.
25    #[serde(rename = "1")]
26    V1,
27}
28
29/// Version singleton for a similar-code inspect packet.
30#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
31#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
32pub enum SimilarCodeInspectSchemaVersion {
33    /// Initial independent contract.
34    #[serde(rename = "1")]
35    V1,
36}
37
38/// Version singleton for reviewed similar-code output.
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
40#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
41pub enum SimilarCodeReviewSchemaVersion {
42    /// Initial independent contract.
43    #[serde(rename = "1")]
44    V1,
45}
46
47/// Version singleton for local-provider status output.
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
49#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
50pub enum SimilarCodeStatusSchemaVersion {
51    /// Initial independent status contract.
52    #[serde(rename = "1")]
53    V1,
54}
55
56/// Version singleton for vector-cache clear output.
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
58#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
59pub enum SimilarCodeCacheClearSchemaVersion {
60    /// Initial independent cache-mutation contract.
61    #[serde(rename = "1")]
62    V1,
63}
64
65/// Machine-readable readiness of the exact local companion and pinned model.
66#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
67#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
68pub struct SimilarCodeStatusOutput {
69    /// Independent status schema version.
70    pub schema_version: SimilarCodeStatusSchemaVersion,
71    /// Fallow version producing the status envelope.
72    pub version: ToolVersion,
73    /// Companion protocol version.
74    pub protocol_version: u32,
75    /// Embedding calculation semantics implemented by the companion.
76    pub embedding_semantics_version: u32,
77    /// Exact companion package version.
78    pub companion_version: String,
79    /// Whether every pinned model artifact is ready and verified.
80    pub model_ready: bool,
81    /// Immutable model identifier.
82    pub model_id: String,
83    /// Immutable model revision.
84    pub model_revision: String,
85    /// Embedding width.
86    pub dimensions: u32,
87    /// Maximum tokenizer length.
88    pub max_tokens: u32,
89    /// Model license identifier.
90    pub license: String,
91    /// Local model cache directory.
92    pub cache_dir: String,
93    /// Expected download size for all pinned artifacts.
94    pub download_bytes: u64,
95    /// Whether source analysis stays local and offline.
96    pub analysis_offline: bool,
97    /// Whether all installed artifacts passed integrity validation.
98    pub integrity_verified: bool,
99    /// Actionable readiness problem when the model is unavailable.
100    #[serde(default, skip_serializing_if = "Option::is_none")]
101    pub problem: Option<String>,
102    /// Whether this setup invocation downloaded new bytes.
103    #[serde(default, skip_serializing_if = "Option::is_none")]
104    pub downloaded: Option<bool>,
105}
106
107/// Result of explicitly clearing the derived project-namespaced vector cache.
108#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
109#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
110pub struct SimilarCodeCacheClearOutput {
111    /// Independent cache-clear schema version.
112    pub schema_version: SimilarCodeCacheClearSchemaVersion,
113    /// Fallow version producing the cache-clear envelope.
114    pub version: ToolVersion,
115    /// Whether an existing vector cache was removed.
116    pub removed: bool,
117    /// Whether model artifacts were removed. Version 1 always emits false.
118    pub model_removed: bool,
119}
120
121/// Version singleton for the separate human or agent verdict document.
122#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
123#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
124pub enum SimilarCodeVerdictSchemaVersion {
125    /// Initial independent verdict contract.
126    #[serde(rename = "1")]
127    V1,
128}
129
130/// Immutable local provider provenance for one generation run.
131#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
132#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
133pub struct SimilarCodeProviderProvenance {
134    /// Provider family. Version 1 accepts only the official local companion.
135    pub provider: SimilarCodeProvider,
136    /// Exact companion package version.
137    pub companion_version: String,
138    /// Companion protocol version negotiated for this run.
139    pub protocol_version: u32,
140    /// Whether source content left the local machine. Version 1 requires false.
141    pub source_left_machine: bool,
142}
143
144/// Provider families admitted by the version 1 public contract.
145#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
146#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
147#[serde(rename_all = "kebab-case")]
148pub enum SimilarCodeProvider {
149    /// Exact-version official companion executed locally.
150    OfficialLocalCompanion,
151}
152
153/// Immutable model artifact provenance.
154#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
155#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
156pub struct SimilarCodeModelProvenance {
157    /// Stable model identifier.
158    pub model_id: String,
159    /// Immutable model revision.
160    pub revision: String,
161    /// SHA-256 digest of the exact model artifact bytes.
162    pub artifact_sha256: String,
163    /// SPDX license identifier or reviewed license label.
164    pub license: String,
165    /// Embedding vector dimensions.
166    pub dimensions: u32,
167}
168
169/// Parameters that materially affect generated embeddings and scores.
170#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
171#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
172pub struct SimilarCodeGenerationParameters {
173    /// Numeric representation used for model inference.
174    pub dtype: String,
175    /// Pooling strategy applied to model output.
176    pub pooling: String,
177    /// Whether vectors were normalized before comparison.
178    pub normalized: bool,
179    /// Maximum inference batch size used by the run.
180    pub batch_size: u32,
181    /// Maximum tokenizer length before deterministic truncation.
182    pub max_tokens: u32,
183    /// Digest over the complete effective generation parameter set.
184    pub parameter_sha256: String,
185}
186
187/// Effective endpoint scope used for corpus admission and pair retention.
188#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
189#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
190pub struct SimilarCodeScopeProvenance {
191    /// Whether file, changed-file, diff, or workspace scoping was active.
192    pub active: bool,
193    /// Sorted project-root-relative paths satisfying every active predicate.
194    pub paths: Vec<String>,
195}
196
197/// Complete provenance needed to reproduce candidate generation.
198#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
199#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
200pub struct SimilarCodeGeneration {
201    /// Version of extraction and normalization semantics used for both IDs.
202    pub extraction_semantics_version: u32,
203    /// Version of the calculation that produces model embeddings.
204    pub embedding_semantics_version: u32,
205    /// Local provider provenance.
206    pub provider: SimilarCodeProviderProvenance,
207    /// Immutable model provenance.
208    pub model: SimilarCodeModelProvenance,
209    /// Effective generation parameters.
210    pub parameters: SimilarCodeGenerationParameters,
211    /// Materialized endpoint scope needed to reproduce scoped discovery.
212    pub scope: SimilarCodeScopeProvenance,
213    /// Minimum cosine similarity admitted into the candidate set.
214    pub threshold: f64,
215    /// Minimum source line count admitted into function extraction.
216    pub min_lines: u64,
217}
218
219/// Exact named location of one candidate function.
220#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
221#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
222pub struct SimilarCodeLocation {
223    /// Project-root-relative, forward-slash path.
224    pub path: String,
225    /// Extracted function or method name.
226    pub name: String,
227    /// One-based inclusive start line.
228    pub start_line: u32,
229    /// One-based inclusive start column.
230    pub start_column: u32,
231    /// One-based inclusive end line.
232    pub end_line: u32,
233    /// One-based inclusive end column.
234    pub end_column: u32,
235    /// SHA-256 digest of the exact extracted function source.
236    pub source_sha256: String,
237}
238
239/// Stable, coarse interpretation of a candidate score.
240#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
241#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
242#[serde(rename_all = "kebab-case")]
243pub enum SimilarCodeSimilarityBand {
244    /// Candidate is close to the configured lower threshold.
245    Moderate,
246    /// Candidate has a strong semantic similarity score.
247    High,
248    /// Candidate has an exceptionally high semantic similarity score.
249    VeryHigh,
250}
251
252/// Verification state of a raw semantic candidate.
253#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
254#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
255#[serde(rename_all = "kebab-case")]
256pub enum SimilarCodeVerificationStatus {
257    /// Candidate generation is discovery only and has not verified behavior.
258    Unverified,
259}
260
261/// Explicit availability state for one optional enrichment source.
262#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
263#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
264#[serde(rename_all = "kebab-case")]
265pub enum SimilarCodeEnrichmentState {
266    /// Evidence is included in the inspect packet.
267    Available,
268    /// Evidence was requested but could not be obtained.
269    Unavailable,
270    /// Evidence was not requested for this run.
271    NotRequested,
272}
273
274/// Availability of every supported source-grounded enrichment.
275#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
276#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
277pub struct SimilarCodeEnrichmentAvailability {
278    /// Import or workspace relationship evidence.
279    pub graph_relationship: SimilarCodeEnrichmentState,
280    /// Entry-point reachability evidence.
281    pub entry_point_reachability: SimilarCodeEnrichmentState,
282    /// Direct caller evidence.
283    pub callers: SimilarCodeEnrichmentState,
284    /// Direct callee evidence.
285    pub callees: SimilarCodeEnrichmentState,
286    /// Ownership evidence.
287    pub ownership: SimilarCodeEnrichmentState,
288    /// Churn evidence.
289    pub churn: SimilarCodeEnrichmentState,
290    /// Test relationship evidence.
291    pub tests: SimilarCodeEnrichmentState,
292    /// Deterministic clone coverage evidence.
293    pub deterministic_clone_coverage: SimilarCodeEnrichmentState,
294    /// Runtime evidence.
295    pub runtime: SimilarCodeEnrichmentState,
296}
297
298/// Read-only follow-up exposed for a candidate.
299#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
300#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
301pub struct SimilarCodeAction {
302    /// Stable action identifier.
303    pub action: SimilarCodeActionType,
304    /// Human-readable description of the read-only operation.
305    pub description: String,
306    /// Explicit mutation guarantee. Version 1 requires this to be true.
307    pub read_only: bool,
308}
309
310/// Read-only actions supported by the candidate workflow.
311#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
312#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
313#[serde(rename_all = "kebab-case")]
314pub enum SimilarCodeActionType {
315    /// Build a bounded source-grounded inspect packet.
316    Inspect,
317    /// Join this candidate with an external verdict document.
318    Review,
319}
320
321/// One unverified semantic similar-code candidate.
322#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
323#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
324pub struct SimilarCodeCandidate {
325    /// Snapshot-stable opaque candidate identity.
326    pub candidate_id: String,
327    /// Content-stable key used for safe line-movement rebinding.
328    pub review_key: String,
329    /// First location in deterministic pair order.
330    pub left: SimilarCodeLocation,
331    /// Second location in deterministic pair order.
332    pub right: SimilarCodeLocation,
333    /// Cosine similarity reported by the pinned provider and model.
334    pub similarity: f64,
335    /// Stable score band for consumers that do not need the raw score.
336    pub similarity_band: SimilarCodeSimilarityBand,
337    /// Raw candidates are always explicitly unverified.
338    pub verification_status: SimilarCodeVerificationStatus,
339    /// Availability of optional deterministic and runtime context.
340    pub enrichment: SimilarCodeEnrichmentAvailability,
341    /// Read-only inspect and review affordances.
342    pub actions: Vec<SimilarCodeAction>,
343}
344
345/// Bounded phase names in the similar-code pipeline.
346#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
347#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
348#[serde(rename_all = "kebab-case")]
349pub enum SimilarCodePhase {
350    /// Discover eligible source files.
351    Discovery,
352    /// Extract and normalize supported functions.
353    Extraction,
354    /// Load or populate the local vector cache.
355    Cache,
356    /// Generate embeddings with the official local companion.
357    Embedding,
358    /// Validate provider output before using it.
359    Validation,
360    /// Compare vectors and select bounded candidates.
361    Comparison,
362    /// Build optional deterministic context.
363    Enrichment,
364}
365
366/// Completion state for one generation phase.
367#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
368#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
369#[serde(rename_all = "kebab-case")]
370pub enum SimilarCodePhaseStatus {
371    /// Phase completed its admitted scope.
372    Complete,
373    /// Phase returned bounded partial results.
374    Partial,
375    /// Phase was intentionally not run.
376    Skipped,
377    /// Phase reached its configured timeout.
378    TimedOut,
379}
380
381/// Accounting for one bounded generation phase.
382#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
383#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
384pub struct SimilarCodePhaseCompletion {
385    /// Pipeline phase.
386    pub phase: SimilarCodePhase,
387    /// Whether this phase completed, skipped, or returned partial data.
388    pub status: SimilarCodePhaseStatus,
389    /// Number of admitted inputs processed by this phase.
390    pub processed: u64,
391    /// Total admitted inputs known to this phase, when available.
392    pub total: Option<u64>,
393    /// Stable explanation when the phase did not complete.
394    pub reason: Option<String>,
395}
396
397/// Effective resource limits for a similar-code run.
398#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
399#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
400pub struct SimilarCodeLimits {
401    /// Maximum source files admitted.
402    pub max_files: u64,
403    /// Maximum extracted functions admitted.
404    pub max_functions: u64,
405    /// Maximum aggregate normalized source bytes admitted.
406    pub max_source_bytes: u64,
407    /// Maximum normalized bytes admitted for one function.
408    pub max_function_bytes: u64,
409    /// Maximum embedding batch size.
410    pub max_batch_size: u64,
411    /// Maximum vector bytes retained for comparison.
412    pub max_vector_bytes: u64,
413    /// Maximum pair comparisons performed.
414    pub max_comparisons: u64,
415    /// Maximum candidates returned.
416    pub max_candidates: u64,
417    /// Maximum returned neighbors per function.
418    pub max_neighbors_per_function: u64,
419    /// End-to-end timeout in milliseconds.
420    pub timeout_ms: u64,
421}
422
423/// Stable reasons why admitted work was skipped or truncated.
424#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Deserialize, Serialize)]
425#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
426#[serde(rename_all = "kebab-case")]
427pub enum SimilarCodeSkipReason {
428    /// Function was shorter than the configured minimum source-line count.
429    BelowMinimumLines,
430    /// Syntax form is outside the supported extraction contract.
431    UnsupportedFunction,
432    /// Generated source was excluded.
433    GeneratedSource,
434    /// One function exceeded the per-function byte limit.
435    FunctionTooLarge,
436    /// File or function admission limit was reached.
437    InputLimit,
438    /// Aggregate source byte limit was reached.
439    SourceBytesLimit,
440    /// Vector memory limit was reached.
441    VectorMemoryLimit,
442    /// Pair comparison limit was reached.
443    ComparisonLimit,
444    /// Candidate result limit was reached.
445    CandidateLimit,
446    /// Per-function neighbor limit was reached.
447    NeighborLimit,
448    /// Provider or overall timeout was reached.
449    Timeout,
450    /// Local provider failed after returning a usable subset of vectors.
451    ProviderFailure,
452    /// Provider tokenization truncated an otherwise admitted source fragment.
453    TokenTruncation,
454    /// Optional enrichment source was unavailable.
455    EnrichmentUnavailable,
456}
457
458/// Count of skipped work for a stable reason.
459#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
460#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
461pub struct SimilarCodeSkip {
462    /// Phase that skipped the work.
463    pub phase: SimilarCodePhase,
464    /// Stable skip reason.
465    pub reason: SimilarCodeSkipReason,
466    /// Number of inputs skipped for this phase and reason.
467    pub count: u64,
468}
469
470/// Vector cache outcome for a run.
471#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
472#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
473#[serde(rename_all = "kebab-case")]
474pub enum SimilarCodeCacheStatus {
475    /// Cache use was disabled.
476    Disabled,
477    /// Every requested vector was found in the cache.
478    Hit,
479    /// No requested vector was found in the cache.
480    Miss,
481    /// The run used both cached and newly generated vectors.
482    Mixed,
483}
484
485/// Privacy-safe cache accounting. Source fragments are never represented.
486#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
487#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
488pub struct SimilarCodeCacheSummary {
489    /// Aggregate cache outcome.
490    pub status: SimilarCodeCacheStatus,
491    /// Valid vector cache hits.
492    pub hits: u64,
493    /// Vector cache misses.
494    pub misses: u64,
495    /// Newly written vector cache entries.
496    pub writes: u64,
497    /// Corrupt or incompatible entries ignored safely.
498    pub invalid_entries: u64,
499}
500
501/// Overall trustworthiness of an emitted result set.
502#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
503#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
504#[serde(rename_all = "kebab-case")]
505pub enum SimilarCodeCompletionStatus {
506    /// Every admitted generation phase completed.
507    Complete,
508    /// One or more limits, skips, or timeouts made the result partial.
509    Partial,
510}
511
512/// Typed completion, limit, skip, and cache accounting.
513#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
514#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
515pub struct SimilarCodeCompletion {
516    /// Overall completion status. Only `complete` makes an empty set conclusive.
517    pub status: SimilarCodeCompletionStatus,
518    /// Per-phase completion in pipeline order.
519    pub phases: Vec<SimilarCodePhaseCompletion>,
520    /// Effective run limits.
521    pub limits: SimilarCodeLimits,
522    /// Aggregated skips in phase and reason order.
523    pub skips: Vec<SimilarCodeSkip>,
524    /// Privacy-safe vector cache accounting.
525    pub cache: SimilarCodeCacheSummary,
526    /// Aggregate model inference wall time reported by the local provider.
527    pub provider_inference_ms: u64,
528}
529
530/// Non-severity diagnostic domain for similar-code generation.
531#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
532#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
533#[serde(rename_all = "kebab-case")]
534pub enum SimilarCodeDiagnosticDomain {
535    /// Source discovery or workspace interpretation.
536    Workspace,
537    /// Function extraction or normalization.
538    Extraction,
539    /// Local provider execution or protocol validation.
540    Provider,
541    /// Local vector cache handling.
542    Cache,
543    /// Optional source-grounded enrichment.
544    Enrichment,
545    /// Verdict matching and review-key rebinding.
546    Review,
547}
548
549/// Actionable diagnostic without a severity or gate implication.
550#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
551#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
552pub struct SimilarCodeDiagnostic {
553    /// Stable diagnostic domain.
554    pub domain: SimilarCodeDiagnosticDomain,
555    /// Stable machine-readable code.
556    pub code: String,
557    /// Bounded human-readable explanation.
558    pub message: String,
559    /// Optional project-root-relative path.
560    pub path: Option<String>,
561}
562
563/// Raw `fallow similar-code --format json` output.
564#[derive(Debug, Clone, Serialize)]
565#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
566pub struct SimilarCodeOutput {
567    /// Independent envelope schema version.
568    pub schema_version: SimilarCodeSchemaVersion,
569    /// Fallow version that produced this output.
570    pub version: ToolVersion,
571    /// End-to-end elapsed milliseconds.
572    pub elapsed_ms: ElapsedMs,
573    /// Immutable provider, model, and parameter provenance.
574    pub generation: SimilarCodeGeneration,
575    /// Deterministically ordered unverified candidates.
576    pub candidates: Vec<SimilarCodeCandidate>,
577    /// Typed completion and boundedness accounting.
578    pub completion: SimilarCodeCompletion,
579    /// Non-severity diagnostics in deterministic order.
580    pub diagnostics: Vec<SimilarCodeDiagnostic>,
581}
582
583#[derive(Deserialize)]
584#[serde(deny_unknown_fields)]
585struct SimilarCodeOutputWire {
586    schema_version: SimilarCodeSchemaVersion,
587    version: String,
588    elapsed_ms: u64,
589    generation: SimilarCodeGeneration,
590    candidates: Vec<SimilarCodeCandidate>,
591    completion: SimilarCodeCompletion,
592    diagnostics: Vec<SimilarCodeDiagnostic>,
593}
594
595impl<'de> Deserialize<'de> for SimilarCodeOutput {
596    fn deserialize<Deserializer>(deserializer: Deserializer) -> Result<Self, Deserializer::Error>
597    where
598        Deserializer: serde::Deserializer<'de>,
599    {
600        let wire = SimilarCodeOutputWire::deserialize(deserializer)?;
601        Ok(Self {
602            schema_version: wire.schema_version,
603            version: ToolVersion(wire.version),
604            elapsed_ms: ElapsedMs(wire.elapsed_ms),
605            generation: wire.generation,
606            candidates: wire.candidates,
607            completion: wire.completion,
608            diagnostics: wire.diagnostics,
609        })
610    }
611}
612
613/// Bounded handoff for inspecting one immutable discovery candidate without
614/// rerunning global retrieval or ranking.
615#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
616#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
617#[serde(deny_unknown_fields)]
618pub struct SimilarCodeCandidateSnapshot {
619    /// Schema version of the discovery envelope that produced the candidate.
620    pub schema_version: SimilarCodeSchemaVersion,
621    /// Immutable provider, model, parameter, and scope provenance.
622    pub generation: SimilarCodeGeneration,
623    /// The exact unverified candidate selected from discovery.
624    pub candidate: SimilarCodeCandidate,
625    /// Original discovery completeness and limit accounting.
626    pub completion: SimilarCodeCompletion,
627    /// Original non-severity discovery diagnostics.
628    pub diagnostics: Vec<SimilarCodeDiagnostic>,
629}
630
631/// One named graph reference used in an inspect packet.
632#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
633#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
634pub struct SimilarCodeNamedReference {
635    /// Project-root-relative, forward-slash path.
636    pub path: String,
637    /// Referenced symbol name.
638    pub name: String,
639    /// One-based source line.
640    pub line: u32,
641}
642
643/// Conservative syntactic side-effect hint for an inspected function.
644#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
645#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
646#[serde(rename_all = "kebab-case")]
647pub enum SimilarCodeSideEffectHint {
648    /// No syntactic side-effect signal was found.
649    PureLooking,
650    /// The function contains a syntactic side-effect signal.
651    MayHaveSideEffects,
652    /// The available evidence is insufficient to classify the function.
653    Unknown,
654}
655
656/// Bounded evidence for one side of an inspect packet.
657#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
658#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
659pub struct SimilarCodeSideEvidence {
660    /// Bounded source window included only in inspect output, never raw output or cache.
661    pub source_window: Option<String>,
662    /// Declared parameter count when extraction supplied it.
663    pub parameter_count: Option<u32>,
664    /// Whether the inspected function is declared async.
665    pub is_async: Option<bool>,
666    /// Whether the inspected function is a generator.
667    pub is_generator: Option<bool>,
668    /// Whether the inspected function contains an await expression.
669    pub has_await: Option<bool>,
670    /// Whether the inspected function contains a throw expression.
671    pub has_throw: Option<bool>,
672    /// Conservative syntactic side-effect classification.
673    pub side_effect_hint: Option<SimilarCodeSideEffectHint>,
674    /// Whether the function is reachable from a configured entry point.
675    pub entry_point_reachable: Option<bool>,
676    /// Bounded, deterministically ordered direct callers.
677    pub callers: Vec<SimilarCodeNamedReference>,
678    /// Bounded, deterministically ordered direct callees.
679    pub callees: Vec<SimilarCodeNamedReference>,
680    /// Bounded, deterministically ordered ownership labels.
681    pub owners: Vec<String>,
682    /// Recent commit count in the configured churn window.
683    pub churn_commits: Option<u64>,
684    /// Bounded, root-relative related test paths.
685    pub tests: Vec<String>,
686    /// Fraction covered by deterministic clone groups, from zero through one.
687    pub deterministic_clone_coverage: Option<f64>,
688    /// Runtime observation count when compatible runtime evidence is present.
689    pub runtime_observations: Option<u64>,
690}
691
692/// Bounded source-grounded packet for one immutable candidate.
693#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
694#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
695pub struct SimilarCodeInspectPacket {
696    /// Candidate identity this packet describes.
697    pub candidate_id: String,
698    /// Content-stable review key this packet describes.
699    pub review_key: String,
700    /// Availability of every optional evidence source.
701    pub availability: SimilarCodeEnrichmentAvailability,
702    /// Graph relationship label, when relationship evidence is available.
703    pub graph_relationship: Option<String>,
704    /// Evidence for the candidate's first location.
705    pub left: SimilarCodeSideEvidence,
706    /// Evidence for the candidate's second location.
707    pub right: SimilarCodeSideEvidence,
708}
709
710/// `fallow similar-code inspect --format json` output.
711#[derive(Debug, Clone, Serialize)]
712#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
713pub struct SimilarCodeInspectOutput {
714    /// Independent inspect envelope schema version.
715    pub schema_version: SimilarCodeInspectSchemaVersion,
716    /// Fallow version that produced this output.
717    pub version: ToolVersion,
718    /// End-to-end elapsed milliseconds.
719    pub elapsed_ms: ElapsedMs,
720    /// Generation provenance copied from the candidate document.
721    pub generation: SimilarCodeGeneration,
722    /// Immutable raw candidate being inspected.
723    pub candidate: SimilarCodeCandidate,
724    /// Bounded source-grounded inspect packet.
725    pub packet: SimilarCodeInspectPacket,
726    /// Typed completion and boundedness accounting.
727    pub completion: SimilarCodeCompletion,
728    /// Non-severity diagnostics in deterministic order.
729    pub diagnostics: Vec<SimilarCodeDiagnostic>,
730}
731
732#[derive(Deserialize)]
733#[serde(deny_unknown_fields)]
734struct SimilarCodeInspectOutputWire {
735    schema_version: SimilarCodeInspectSchemaVersion,
736    version: String,
737    elapsed_ms: u64,
738    generation: SimilarCodeGeneration,
739    candidate: SimilarCodeCandidate,
740    packet: SimilarCodeInspectPacket,
741    completion: SimilarCodeCompletion,
742    diagnostics: Vec<SimilarCodeDiagnostic>,
743}
744
745impl<'de> Deserialize<'de> for SimilarCodeInspectOutput {
746    fn deserialize<Deserializer>(deserializer: Deserializer) -> Result<Self, Deserializer::Error>
747    where
748        Deserializer: serde::Deserializer<'de>,
749    {
750        let wire = SimilarCodeInspectOutputWire::deserialize(deserializer)?;
751        Ok(Self {
752            schema_version: wire.schema_version,
753            version: ToolVersion(wire.version),
754            elapsed_ms: ElapsedMs(wire.elapsed_ms),
755            generation: wire.generation,
756            candidate: wire.candidate,
757            packet: wire.packet,
758            completion: wire.completion,
759            diagnostics: wire.diagnostics,
760        })
761    }
762}
763
764/// Separate verdict input for one immutable candidate.
765#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
766#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
767#[serde(deny_unknown_fields)]
768pub struct SimilarCodeVerdict {
769    /// Snapshot identity from the raw candidate.
770    pub candidate_id: String,
771    /// Content-stable identity from the raw candidate.
772    pub review_key: String,
773    /// Whether the pair is useful enough to review. Null means undecided.
774    pub candidate_worthy: Option<bool>,
775    /// Whether the two functions behave equivalently. Null means undecided.
776    pub behaviorally_equivalent: Option<bool>,
777    /// Whether consolidation is safe. Null means undecided.
778    pub refactor_safe: Option<bool>,
779    /// Domain interpretation independent of the three judgments.
780    pub outcome: SimilarCodeDomainOutcome,
781    /// Bounded explanation grounded in the inspected sources.
782    pub rationale: String,
783}
784
785impl SimilarCodeVerdict {
786    /// Validate the implication chain without collapsing the three judgments.
787    ///
788    /// # Errors
789    ///
790    /// Returns an error when a positive stronger judgment contradicts a
791    /// negative or unknown prerequisite.
792    pub fn validate(&self) -> Result<(), SimilarCodeVerdictValidationError> {
793        if self.refactor_safe == Some(true) && self.behaviorally_equivalent != Some(true) {
794            return Err(SimilarCodeVerdictValidationError::RefactorSafetyRequiresEquivalence);
795        }
796        if self.behaviorally_equivalent == Some(true) && self.candidate_worthy != Some(true) {
797            return Err(SimilarCodeVerdictValidationError::EquivalenceRequiresCandidate);
798        }
799        Ok(())
800    }
801}
802
803/// Validation failures for the independent verdict judgments.
804#[derive(Debug, Clone, Copy, PartialEq, Eq)]
805pub enum SimilarCodeVerdictValidationError {
806    /// Refactor safety cannot be positive without behavioral equivalence.
807    RefactorSafetyRequiresEquivalence,
808    /// Behavioral equivalence cannot be positive for a rejected candidate.
809    EquivalenceRequiresCandidate,
810}
811
812impl fmt::Display for SimilarCodeVerdictValidationError {
813    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
814        match self {
815            Self::RefactorSafetyRequiresEquivalence => {
816                formatter.write_str("refactor_safe=true requires behaviorally_equivalent=true")
817            }
818            Self::EquivalenceRequiresCandidate => {
819                formatter.write_str("behaviorally_equivalent=true requires candidate_worthy=true")
820            }
821        }
822    }
823}
824
825impl std::error::Error for SimilarCodeVerdictValidationError {}
826
827/// Domain outcome assigned by review, never by candidate generation.
828#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
829#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
830#[serde(rename_all = "kebab-case")]
831pub enum SimilarCodeDomainOutcome {
832    /// Functions implement the same responsibility.
833    SameResponsibility,
834    /// Functions are related but intentionally serve distinct responsibilities.
835    RelatedButDistinct,
836    /// Duplication is understood and intentional.
837    IntentionalDuplication,
838    /// Pair is not meaningfully related.
839    Unrelated,
840    /// Available evidence does not support a verdict.
841    NeedsHumanReview,
842}
843
844/// Versioned external verdict document consumed by review.
845#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
846#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
847#[serde(deny_unknown_fields)]
848pub struct SimilarCodeVerdictInput {
849    /// Independent verdict document schema version.
850    pub schema_version: SimilarCodeVerdictSchemaVersion,
851    /// Verdicts in deterministic candidate order.
852    pub verdicts: Vec<SimilarCodeVerdict>,
853}
854
855/// How review matched an external verdict to the current candidate.
856#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize)]
857#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
858#[serde(rename_all = "kebab-case")]
859pub enum SimilarCodeVerdictMatch {
860    /// Verdict matched the exact snapshot candidate identity.
861    CandidateId,
862    /// Verdict was rebound unambiguously through both content digests.
863    ReviewKey,
864    /// No verdict was supplied for this candidate.
865    Unverified,
866    /// Review-key rebinding was ambiguous and therefore refused.
867    AmbiguousReviewKey,
868}
869
870/// One candidate joined with its separate verdict, if safely matched.
871#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
872#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
873pub struct SimilarCodeReviewedCandidate {
874    /// Immutable raw candidate, unchanged by review.
875    pub candidate: SimilarCodeCandidate,
876    /// Safely matched external verdict, absent when still unverified.
877    pub verdict: Option<SimilarCodeVerdict>,
878    /// Match or abstention path used by review.
879    pub verdict_match: SimilarCodeVerdictMatch,
880    /// Domain outcome. Unverified or ambiguous entries use `needs-human-review`.
881    pub outcome: SimilarCodeDomainOutcome,
882}
883
884/// Digests that make the review join reproducible without exposing source.
885#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
886#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
887pub struct SimilarCodeReviewProvenance {
888    /// SHA-256 digest of the exact candidate JSON input bytes.
889    pub candidates_sha256: String,
890    /// SHA-256 digest of the exact verdict JSON input bytes.
891    pub verdicts_sha256: String,
892}
893
894/// `fallow similar-code review --format json` output.
895#[derive(Debug, Clone, Serialize)]
896#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
897pub struct SimilarCodeReviewOutput {
898    /// Independent review envelope schema version.
899    pub schema_version: SimilarCodeReviewSchemaVersion,
900    /// Fallow version that produced this output.
901    pub version: ToolVersion,
902    /// End-to-end elapsed milliseconds.
903    pub elapsed_ms: ElapsedMs,
904    /// Immutable generation provenance copied from the candidate document.
905    pub generation: SimilarCodeGeneration,
906    /// Input document provenance for this deterministic join.
907    pub review: SimilarCodeReviewProvenance,
908    /// Raw candidates joined with verdicts in candidate order.
909    pub candidates: Vec<SimilarCodeReviewedCandidate>,
910    /// Typed completion and boundedness accounting.
911    pub completion: SimilarCodeCompletion,
912    /// Non-severity diagnostics in deterministic order.
913    pub diagnostics: Vec<SimilarCodeDiagnostic>,
914}
915
916#[derive(Deserialize)]
917#[serde(deny_unknown_fields)]
918struct SimilarCodeReviewOutputWire {
919    schema_version: SimilarCodeReviewSchemaVersion,
920    version: String,
921    elapsed_ms: u64,
922    generation: SimilarCodeGeneration,
923    review: SimilarCodeReviewProvenance,
924    candidates: Vec<SimilarCodeReviewedCandidate>,
925    completion: SimilarCodeCompletion,
926    diagnostics: Vec<SimilarCodeDiagnostic>,
927}
928
929impl<'de> Deserialize<'de> for SimilarCodeReviewOutput {
930    fn deserialize<Deserializer>(deserializer: Deserializer) -> Result<Self, Deserializer::Error>
931    where
932        Deserializer: serde::Deserializer<'de>,
933    {
934        let wire = SimilarCodeReviewOutputWire::deserialize(deserializer)?;
935        Ok(Self {
936            schema_version: wire.schema_version,
937            version: ToolVersion(wire.version),
938            elapsed_ms: ElapsedMs(wire.elapsed_ms),
939            generation: wire.generation,
940            review: wire.review,
941            candidates: wire.candidates,
942            completion: wire.completion,
943            diagnostics: wire.diagnostics,
944        })
945    }
946}
947
948/// Serialize raw similar-code output with its root discriminator.
949///
950/// # Errors
951///
952/// Returns a serde error when the envelope cannot be converted to JSON.
953pub fn serialize_similar_code_json_output(
954    output: SimilarCodeOutput,
955    mode: RootEnvelopeMode,
956) -> Result<serde_json::Value, serde_json::Error> {
957    serialize_named_json_output(output, "similar-code", mode)
958}
959
960/// Serialize a similar-code inspect packet with its root discriminator.
961///
962/// # Errors
963///
964/// Returns a serde error when the envelope cannot be converted to JSON.
965pub fn serialize_similar_code_inspect_json_output(
966    output: SimilarCodeInspectOutput,
967    mode: RootEnvelopeMode,
968) -> Result<serde_json::Value, serde_json::Error> {
969    serialize_named_json_output(output, "similar-code-inspect", mode)
970}
971
972/// Serialize reviewed similar-code output with its root discriminator.
973///
974/// # Errors
975///
976/// Returns a serde error when the envelope cannot be converted to JSON.
977pub fn serialize_similar_code_review_json_output(
978    output: SimilarCodeReviewOutput,
979    mode: RootEnvelopeMode,
980) -> Result<serde_json::Value, serde_json::Error> {
981    serialize_named_json_output(output, "similar-code-review", mode)
982}
983
984/// Serialize local-provider status with its root discriminator.
985///
986/// # Errors
987///
988/// Returns a serde error when the envelope cannot be converted to JSON.
989pub fn serialize_similar_code_status_json_output(
990    output: SimilarCodeStatusOutput,
991    mode: RootEnvelopeMode,
992) -> Result<serde_json::Value, serde_json::Error> {
993    serialize_named_json_output(output, "similar-code-status", mode)
994}
995
996/// Serialize a vector-cache clear result with its root discriminator.
997///
998/// # Errors
999///
1000/// Returns a serde error when the envelope cannot be converted to JSON.
1001pub fn serialize_similar_code_cache_clear_json_output(
1002    output: SimilarCodeCacheClearOutput,
1003    mode: RootEnvelopeMode,
1004) -> Result<serde_json::Value, serde_json::Error> {
1005    serialize_named_json_output(output, "similar-code-cache-clear", mode)
1006}
1007
1008#[cfg(test)]
1009mod tests {
1010    use super::*;
1011
1012    #[test]
1013    fn verdict_axes_remain_independent_but_enforce_implication() {
1014        let verdict = SimilarCodeVerdict {
1015            candidate_id: "sc_123".to_string(),
1016            review_key: "scr_456".to_string(),
1017            candidate_worthy: Some(true),
1018            behaviorally_equivalent: Some(false),
1019            refactor_safe: Some(true),
1020            outcome: SimilarCodeDomainOutcome::RelatedButDistinct,
1021            rationale: "Same domain, different behavior.".to_string(),
1022        };
1023
1024        assert_eq!(
1025            verdict.validate(),
1026            Err(SimilarCodeVerdictValidationError::RefactorSafetyRequiresEquivalence)
1027        );
1028    }
1029
1030    #[test]
1031    fn raw_serializer_adds_only_the_similar_code_kind() {
1032        let output = raw_output();
1033
1034        let value = serialize_similar_code_json_output(output, RootEnvelopeMode::Tagged)
1035            .expect("similar-code output should serialize");
1036
1037        assert_eq!(value["kind"], "similar-code");
1038        assert_eq!(value["schema_version"], "1");
1039        assert!(value.get("severity").is_none());
1040        assert!(value.get("gate").is_none());
1041        assert!(value.get("fixes").is_none());
1042    }
1043
1044    #[test]
1045    fn raw_output_round_trips_for_review_input() {
1046        let value = serde_json::to_value(raw_output()).expect("raw output should serialize");
1047        let decoded: SimilarCodeOutput =
1048            serde_json::from_value(value).expect("raw output should deserialize");
1049
1050        assert_eq!(decoded.schema_version, SimilarCodeSchemaVersion::V1);
1051        assert_eq!(decoded.version.0, "3.9.0");
1052        assert_eq!(
1053            decoded.completion.status,
1054            SimilarCodeCompletionStatus::Complete
1055        );
1056    }
1057
1058    #[test]
1059    fn verdict_input_rejects_unknown_fields() {
1060        let value = serde_json::json!({
1061            "schema_version": "1",
1062            "verdicts": [],
1063            "unexpected": true
1064        });
1065
1066        assert!(serde_json::from_value::<SimilarCodeVerdictInput>(value).is_err());
1067    }
1068
1069    #[test]
1070    fn review_preserves_null_judgments() {
1071        let verdict = SimilarCodeVerdict {
1072            candidate_id: "sc_123".to_string(),
1073            review_key: "scr_456".to_string(),
1074            candidate_worthy: None,
1075            behaviorally_equivalent: None,
1076            refactor_safe: None,
1077            outcome: SimilarCodeDomainOutcome::NeedsHumanReview,
1078            rationale: "Insufficient evidence.".to_string(),
1079        };
1080
1081        let value = serde_json::to_value(verdict).expect("verdict should serialize");
1082        assert!(value["candidate_worthy"].is_null());
1083        assert!(value["behaviorally_equivalent"].is_null());
1084        assert!(value["refactor_safe"].is_null());
1085    }
1086
1087    fn generation() -> SimilarCodeGeneration {
1088        SimilarCodeGeneration {
1089            extraction_semantics_version: 1,
1090            embedding_semantics_version: 1,
1091            provider: SimilarCodeProviderProvenance {
1092                provider: SimilarCodeProvider::OfficialLocalCompanion,
1093                companion_version: "3.9.0".to_string(),
1094                protocol_version: 2,
1095                source_left_machine: false,
1096            },
1097            model: SimilarCodeModelProvenance {
1098                model_id: "example/model".to_string(),
1099                revision: "immutable-revision".to_string(),
1100                artifact_sha256: "abc".to_string(),
1101                license: "Apache-2.0".to_string(),
1102                dimensions: 384,
1103            },
1104            parameters: SimilarCodeGenerationParameters {
1105                dtype: "fp32".to_string(),
1106                pooling: "mean".to_string(),
1107                normalized: true,
1108                batch_size: 8,
1109                max_tokens: 1024,
1110                parameter_sha256: "def".to_string(),
1111            },
1112            scope: SimilarCodeScopeProvenance {
1113                active: true,
1114                paths: vec!["src/a.ts".to_string()],
1115            },
1116            threshold: 0.8,
1117            min_lines: 3,
1118        }
1119    }
1120
1121    fn raw_output() -> SimilarCodeOutput {
1122        SimilarCodeOutput {
1123            schema_version: SimilarCodeSchemaVersion::V1,
1124            version: ToolVersion("3.9.0".to_string()),
1125            elapsed_ms: ElapsedMs(4),
1126            generation: generation(),
1127            candidates: Vec::new(),
1128            completion: completion(),
1129            diagnostics: Vec::new(),
1130        }
1131    }
1132
1133    fn completion() -> SimilarCodeCompletion {
1134        SimilarCodeCompletion {
1135            status: SimilarCodeCompletionStatus::Complete,
1136            phases: Vec::new(),
1137            limits: SimilarCodeLimits {
1138                max_files: 10,
1139                max_functions: 100,
1140                max_source_bytes: 1_000_000,
1141                max_function_bytes: 10_000,
1142                max_batch_size: 8,
1143                max_vector_bytes: 1_000_000,
1144                max_comparisons: 1_000,
1145                max_candidates: 10,
1146                max_neighbors_per_function: 3,
1147                timeout_ms: 30_000,
1148            },
1149            skips: Vec::new(),
1150            cache: SimilarCodeCacheSummary {
1151                status: SimilarCodeCacheStatus::Miss,
1152                hits: 0,
1153                misses: 0,
1154                writes: 0,
1155                invalid_entries: 0,
1156            },
1157            provider_inference_ms: 0,
1158        }
1159    }
1160}