1use serde::{Deserialize, Serialize};
7use std::collections::{BTreeMap, BTreeSet};
8
9pub const SCHEMA_VERSION: &str = "0.1.0";
10
11pub mod handles;
12pub mod identity;
13pub mod languages;
14pub mod lex;
15pub mod resolution;
16pub mod retrieval;
17
18pub use handles::{fnv1a64_hex, ContentHandle, HandleError, HandleKind};
19pub use languages::{
20 extracted_language_ids, language_by_id, language_registry, support_matrix_markdown,
21 LanguageCapability, LanguageTier, LANGUAGE_REGISTRY,
22};
23pub use lex::{
24 bm25_rank, bm25_scores, bm25_scores_with_stats, classify_query, extract_query_mentions,
25 is_exact_anchor, mention_matches_doc, path_matches_locus, ranking_arm_ids, relevance_hits,
26 relevance_hits_with_stats, route_query, subtokens, Bm25CorpusStats, LexDoc, LexField, QueryLocus,
27 QueryMention, QueryShape,
28 RankingArm, RelevanceHit, RetrievalPlan, BM25_B, BM25_K1, QUERY_MENTION_MAX_RAW, WEIGHT_BODY,
29 WEIGHT_DOC, WEIGHT_NAME, WEIGHT_PATH,
30};
31pub use resolution::{
32 choose_representation, AnalysisQuality, CallQuality, FileQuality, RecvKind,
33 RepresentationChoice, RepresentationKind, ResolutionClass,
34};
35pub use retrieval::{mean_reciprocal_rank, recall_at_k};
36
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, schemars::JsonSchema)]
45#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
46pub enum Provenance {
48 Extracted,
50 Resolved,
52 Observed,
54 Declared,
56 Inferred,
58 Stale,
60}
61
62impl Provenance {
64pub fn as_str(&self) -> &'static str {
66 match self {
67 Provenance::Extracted => "EXTRACTED",
68 Provenance::Resolved => "RESOLVED",
69 Provenance::Observed => "OBSERVED",
70 Provenance::Declared => "DECLARED",
71 Provenance::Inferred => "INFERRED",
72 Provenance::Stale => "STALE",
73 }
74 }
75
76 pub fn default_confidence(&self) -> f64 {
79 match self {
80 Provenance::Extracted => 1.0,
81 Provenance::Resolved => 0.98,
82 Provenance::Observed => 1.0,
83 Provenance::Declared => 1.0,
84 Provenance::Inferred => 0.7,
85 Provenance::Stale => 0.0,
86 }
87 }
88
89 pub fn is_trusted(&self) -> bool {
92 !matches!(self, Provenance::Stale)
93 }
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
103#[serde(rename_all = "lowercase")]
104pub enum Severity {
107 Info,
108 Low,
109 Medium,
110 High,
111 Critical,
112}
113
114impl Severity {
116pub fn rank(&self) -> u8 {
118 match self {
119 Severity::Info => 0,
120 Severity::Low => 1,
121 Severity::Medium => 2,
122 Severity::High => 3,
123 Severity::Critical => 4,
124 }
125 }
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
132#[serde(rename_all = "lowercase")]
133pub enum FlowKind {
136 Architecture,
137 Workflow,
138 Sequence,
139 Dataflow,
140 Lifecycle,
141}
142
143impl FlowKind {
145pub fn as_str(&self) -> &'static str {
147 match self {
148 FlowKind::Architecture => "architecture",
149 FlowKind::Workflow => "workflow",
150 FlowKind::Sequence => "sequence",
151 FlowKind::Dataflow => "dataflow",
152 FlowKind::Lifecycle => "lifecycle",
153 }
154 }
155}
156
157pub fn flow_kind_str(k: &FlowKind) -> &'static str {
159 k.as_str()
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
164#[serde(rename_all = "lowercase")]
165pub enum EvidenceType {
167 Source,
168 Config,
169 Runtime,
170 Test,
171 Intent,
172 History,
173}
174
175#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, schemars::JsonSchema)]
186#[serde(rename_all = "snake_case")]
187pub enum Archetype {
190 ServiceApplication,
192 Cli,
194 LibrarySdk,
196 WebFramework,
198 CompilerLanguageTool,
200 PluginFramework,
202 InfrastructureProject,
204 MonorepoPlatform,
206 Unknown,
208}
209
210impl Archetype {
212pub fn as_str(&self) -> &'static str {
214 match self {
215 Archetype::ServiceApplication => "service_application",
216 Archetype::Cli => "cli",
217 Archetype::LibrarySdk => "library_sdk",
218 Archetype::WebFramework => "web_framework",
219 Archetype::CompilerLanguageTool => "compiler_language_tool",
220 Archetype::PluginFramework => "plugin_framework",
221 Archetype::InfrastructureProject => "infrastructure_project",
222 Archetype::MonorepoPlatform => "monorepo_platform",
223 Archetype::Unknown => "unknown",
224 }
225 }
226
227pub fn label(&self) -> &'static str {
229 match self {
230 Archetype::ServiceApplication => "service application",
231 Archetype::Cli => "cli",
232 Archetype::LibrarySdk => "library/sdk",
233 Archetype::WebFramework => "web framework",
234 Archetype::CompilerLanguageTool => "compiler/language tool",
235 Archetype::PluginFramework => "plugin framework",
236 Archetype::InfrastructureProject => "infrastructure project",
237 Archetype::MonorepoPlatform => "monorepo platform",
238 Archetype::Unknown => "unknown",
239 }
240 }
241
242 pub const PRECEDENCE: [Archetype; 9] = [
245 Archetype::MonorepoPlatform,
246 Archetype::InfrastructureProject,
247 Archetype::WebFramework,
248 Archetype::ServiceApplication,
249 Archetype::Cli,
250 Archetype::LibrarySdk,
251 Archetype::CompilerLanguageTool,
252 Archetype::PluginFramework,
253 Archetype::Unknown,
254 ];
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
262pub struct Repository {
264 pub id: String,
265 pub name: String,
266 #[serde(skip_serializing_if = "Option::is_none")]
267 pub url: Option<String>,
268}
269
270#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
271pub struct Snapshot {
273 pub revision: String,
274 #[serde(skip_serializing_if = "Option::is_none")]
275 pub branch: Option<String>,
276 pub indexed_at: String,
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
282pub struct Entity {
285 pub id: String,
286 pub kind: String,
287 pub name: String,
288 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
289 pub attributes: BTreeMap<String, serde_json::Value>,
290 #[serde(default, skip_serializing_if = "Vec::is_empty")]
291 pub evidence: Vec<String>,
292}
293
294impl Entity {
296pub fn new(id: impl Into<String>, kind: impl Into<String>, name: impl Into<String>) -> Self {
298 Entity {
299 id: id.into(),
300 kind: kind.into(),
301 name: name.into(),
302 attributes: BTreeMap::new(),
303 evidence: Vec::new(),
304 }
305 }
306
307pub fn attr(&mut self, key: &str, value: impl Into<serde_json::Value>) -> &mut Self {
309 self.attributes.insert(key.to_string(), value.into());
310 self
311 }
312}
313
314#[derive(Debug, Clone, Serialize, Deserialize)]
325pub struct Occurrence {
328 pub id: String,
330 pub concept: String,
332 pub path: String,
334 pub owner: String,
336 pub line: u32,
339}
340
341#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
344pub struct Relationship {
347 pub id: String,
348 pub subject: String,
349 pub predicate: String,
350 pub object: String,
351 pub provenance: Provenance,
352 pub confidence: f64,
353 #[serde(default, skip_serializing_if = "Vec::is_empty")]
354 pub evidence: Vec<String>,
355 #[serde(default, skip_serializing_if = "String::is_empty")]
356 pub verified_at: String,
357}
358
359impl Relationship {
361pub fn new(
363 id: impl Into<String>,
364 subject: impl Into<String>,
365 predicate: impl Into<String>,
366 object: impl Into<String>,
367 provenance: Provenance,
368 ) -> Self {
369 Relationship {
370 id: id.into(),
371 subject: subject.into(),
372 predicate: predicate.into(),
373 object: object.into(),
374 provenance,
375 confidence: provenance.default_confidence(),
376 evidence: Vec::new(),
377 verified_at: String::new(),
378 }
379 }
380
381pub fn with_confidence(mut self, c: f64) -> Self {
383 self.confidence = c;
384 self
385 }
386
387pub fn with_evidence(mut self, evidence: Vec<String>) -> Self {
389 self.evidence = evidence;
390 self
391 }
392}
393
394#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
395pub struct FlowStep {
397 pub id: String,
398 pub order: u32,
399 pub actor: String,
400 pub operation: String,
401 #[serde(default, skip_serializing_if = "Option::is_none")]
402 pub condition: Option<String>,
403 #[serde(default, skip_serializing_if = "Option::is_none")]
404 pub r#async: Option<bool>,
405 #[serde(default, skip_serializing_if = "Option::is_none")]
406 pub timeout_ms: Option<u64>,
407 #[serde(default, skip_serializing_if = "Option::is_none")]
408 pub retry_policy: Option<String>,
409 #[serde(default, skip_serializing_if = "Option::is_none")]
410 pub failure_outcome: Option<String>,
411 #[serde(default)]
412 pub provenance: Option<Provenance>,
413 #[serde(default, skip_serializing_if = "Vec::is_empty")]
414 pub evidence: Vec<String>,
415}
416
417#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
418pub struct Flow {
420 pub id: String,
421 pub kind: FlowKind,
422 pub name: String,
423 #[serde(default, skip_serializing_if = "Option::is_none")]
424 pub trigger: Option<String>,
425 pub steps: Vec<FlowStep>,
426 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
427 pub attributes: BTreeMap<String, serde_json::Value>,
428}
429
430#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
431pub struct Invariant {
433 pub id: String,
434 pub statement: String,
435 pub severity: Severity,
436 #[serde(default, skip_serializing_if = "Vec::is_empty")]
437 pub scope: Vec<String>,
438 #[serde(default, skip_serializing_if = "Vec::is_empty")]
439 pub enforced_by: Vec<String>,
440 #[serde(default)]
441 pub provenance: Option<Provenance>,
442 #[serde(default, skip_serializing_if = "Vec::is_empty")]
443 pub evidence: Vec<String>,
444}
445
446#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
449pub struct Evidence {
452 pub id: String,
453 #[serde(rename = "type")]
454 pub r#type: EvidenceType,
455 #[serde(default, skip_serializing_if = "Option::is_none")]
456 pub path: Option<String>,
457 #[serde(default, skip_serializing_if = "Option::is_none")]
458 pub symbol: Option<String>,
459 #[serde(default, skip_serializing_if = "Option::is_none")]
460 pub start_line: Option<u32>,
461 #[serde(default, skip_serializing_if = "Option::is_none")]
462 pub end_line: Option<u32>,
463 #[serde(default, skip_serializing_if = "Option::is_none")]
464 pub revision: Option<String>,
465 #[serde(default, skip_serializing_if = "Option::is_none")]
466 pub content_hash: Option<String>,
467 #[serde(default, skip_serializing_if = "Option::is_none")]
468 pub extractor: Option<String>,
469 #[serde(default, skip_serializing_if = "Option::is_none")]
470 pub extractor_version: Option<String>,
471}
472
473impl Evidence {
475pub fn source(id: impl Into<String>, path: impl Into<String>) -> Self {
477 Evidence {
478 id: id.into(),
479 r#type: EvidenceType::Source,
480 path: Some(path.into()),
481 symbol: None,
482 start_line: None,
483 end_line: None,
484 revision: None,
485 content_hash: None,
486 extractor: None,
487 extractor_version: None,
488 }
489 }
490}
491
492#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
498#[serde(rename_all = "lowercase")]
499pub enum FlowEdgeKind {
501 Next,
503 Branch,
506 Error,
508 Retry,
510 Fallback,
513 Async,
515 Publish,
517 Consume,
519 Join,
521 Return,
524 Timeout,
527 Compensation,
530 Read,
532 Write,
534 Transform,
537 Validate,
540 Authorize,
543 Cache,
546 Invalidate,
549}
550
551#[derive(Debug, Clone, Serialize, Deserialize)]
555pub struct FlowNode {
557 pub id: u32,
559 pub actor: String,
561 pub operation: String,
563 #[serde(default, skip_serializing_if = "Vec::is_empty")]
564 pub evidence: Vec<String>,
565}
566
567#[derive(Debug, Clone, Serialize, Deserialize)]
568pub struct FlowEdge {
570 pub from: u32,
571 pub to: u32,
572 pub kind: FlowEdgeKind,
573 #[serde(default, skip_serializing_if = "Option::is_none")]
574 pub condition: Option<String>,
575 #[serde(default)]
576 pub provenance: Option<Provenance>,
577 #[serde(default)]
578 pub confidence: f64,
579 #[serde(default, skip_serializing_if = "Vec::is_empty")]
580 pub evidence: Vec<String>,
581}
582
583#[derive(Debug, Clone, Serialize, Deserialize)]
587pub struct FlowGraph {
589 pub id: String,
590 pub kind: FlowKind,
591 pub name: String,
592 #[serde(default, skip_serializing_if = "Option::is_none")]
593 pub trigger: Option<String>,
594 #[serde(default)]
595 pub nodes: Vec<FlowNode>,
596 #[serde(default)]
597 pub edges: Vec<FlowEdge>,
598 #[serde(default)]
600 pub entrypoints: Vec<u32>,
601 #[serde(default)]
603 pub exits: Vec<u32>,
604 #[serde(default)]
605 pub provenance_summary: BTreeMap<String, usize>,
606}
607
608#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
617pub struct AtlasComponent {
619 pub name: String,
620 pub purpose: String,
621 #[serde(default)]
626 pub implementation: Vec<String>,
627 #[serde(default)]
630 pub implementation_paths: Vec<String>,
631 #[serde(default)]
633 pub symbols: Vec<String>,
634 #[serde(default)]
635 pub consumes: Vec<String>,
636 #[serde(default)]
637 pub produces: Vec<String>,
638 #[serde(default)]
639 pub upstream: Vec<String>,
640 #[serde(default)]
641 pub downstream: Vec<String>,
642 #[serde(default)]
643 pub failure_behavior: Vec<String>,
644 #[serde(default)]
645 pub owns: Vec<AtlasOwnershipClaim>,
646 #[serde(default)]
649 pub layer: String,
650 #[serde(default)]
653 pub parent: Option<String>,
654 #[serde(default)]
658 pub role: String,
659}
660
661#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
665pub struct AtlasHierarchyNode {
667 pub id: String,
669 pub name: String,
670 pub kind: String,
672 #[serde(default)]
673 pub members: Vec<String>,
674}
675
676#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
679pub struct AtlasOwnershipClaim {
681 pub target: String,
682 pub provenance: String,
683}
684
685#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
688pub struct AtlasFlow {
690 pub name: String,
691 pub kind: FlowKind,
692 #[serde(default, skip_serializing_if = "Option::is_none")]
693 pub trigger: Option<String>,
694 #[serde(default)]
695 pub steps: Vec<String>,
696}
697
698#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
699pub struct AtlasEntrypoint {
701 pub name: String,
702 pub kind: String,
703 pub trigger: String,
704 #[serde(default)]
705 pub symbol: String,
706}
707
708#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
709pub struct AtlasInvariant {
711 pub statement: String,
712 pub severity: Severity,
713}
714
715#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default, schemars::JsonSchema)]
726#[serde(rename_all = "snake_case")]
727pub enum ContractSubclass {
730 CallContract,
733 PublicApi,
735 #[default]
737 Http,
738 Rpc,
740 Cli,
742 Event,
744 Message,
746 Schema,
748 Configuration,
750 Plugin,
752 Extension,
754 Serialization,
756}
757
758impl ContractSubclass {
760 pub fn as_str(&self) -> &'static str {
766 match self {
767 ContractSubclass::CallContract => "call",
768 ContractSubclass::PublicApi => "public-api",
769 ContractSubclass::Http => "http",
770 ContractSubclass::Rpc => "rpc",
771 ContractSubclass::Cli => "cli",
772 ContractSubclass::Event => "event",
773 ContractSubclass::Message => "message",
774 ContractSubclass::Schema => "schema",
775 ContractSubclass::Configuration => "config",
776 ContractSubclass::Plugin => "plugin",
777 ContractSubclass::Extension => "extension",
778 ContractSubclass::Serialization => "serialization",
779 }
780 }
781
782 pub fn from_kind_str(kind: &str) -> Option<ContractSubclass> {
789 Some(match kind {
790 "http" | "route" => ContractSubclass::Http,
791 "cli" => ContractSubclass::Cli,
792 "event" | "topic" => ContractSubclass::Event,
793 "config" | "configuration" | "next-config" => ContractSubclass::Configuration,
794 "factory" | "export" | "public-api" | "public_api" => ContractSubclass::PublicApi,
795 "builder" => ContractSubclass::Configuration,
796 "serialization" | "serialize" | "deserialize" => ContractSubclass::Serialization,
797 "extension" => ContractSubclass::Extension,
798 "plugin" => ContractSubclass::Plugin,
799 "rpc" => ContractSubclass::Rpc,
800 "message" | "queue" => ContractSubclass::Message,
801 "schema" => ContractSubclass::Schema,
802 "call" | "task" | "bean" | "rule" | "middleware" | "callback" => {
803 ContractSubclass::CallContract
804 }
805 _ => return None,
806 })
807 }
808}
809
810#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
818pub struct Contract {
821 pub id: String,
822 pub kind: String,
824 #[serde(default)]
831 pub subclass: ContractSubclass,
832 #[serde(default)]
834 pub producer: String,
835 #[serde(default)]
837 pub consumers: Vec<String>,
838 #[serde(default)]
840 pub operations: Vec<String>,
841 #[serde(default)]
842 pub evidence: Vec<String>,
843}
844
845impl Contract {
847pub fn new(
849 id: impl Into<String>,
850 kind: impl Into<String>,
851 producer: impl Into<String>,
852 ) -> Self {
853 Contract {
854 id: id.into(),
855 kind: kind.into(),
856 subclass: ContractSubclass::default(),
857 producer: producer.into(),
858 consumers: Vec::new(),
859 operations: Vec::new(),
860 evidence: Vec::new(),
861 }
862 }
863
864 pub fn with_subclass(mut self, subclass: ContractSubclass) -> Self {
868 self.subclass = subclass;
869 self
870 }
871}
872
873#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
878#[serde(rename_all = "snake_case")]
879pub enum InvocationSurfaceKind {
882 Process,
884 Http,
886 Cli,
888 PublicApi,
890 Event,
892 Queue,
894 Schedule,
896 Plugin,
898 FrameworkCallback,
900 Lifecycle,
902}
903
904impl InvocationSurfaceKind {
906pub fn as_str(&self) -> &'static str {
908 match self {
909 InvocationSurfaceKind::Process => "process",
910 InvocationSurfaceKind::Http => "http",
911 InvocationSurfaceKind::Cli => "cli",
912 InvocationSurfaceKind::PublicApi => "public_api",
913 InvocationSurfaceKind::Event => "event",
914 InvocationSurfaceKind::Queue => "queue",
915 InvocationSurfaceKind::Schedule => "schedule",
916 InvocationSurfaceKind::Plugin => "plugin",
917 InvocationSurfaceKind::FrameworkCallback => "framework_callback",
918 InvocationSurfaceKind::Lifecycle => "lifecycle",
919 }
920 }
921}
922
923#[derive(Debug, Clone, Serialize, Deserialize)]
925pub struct InvocationSurface {
927 pub symbol: String,
928 pub kind: InvocationSurfaceKind,
929 pub trigger: String,
930}
931
932#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
936pub struct SystemAtlas {
938 pub repository: String,
939 pub revision: String,
940 pub indexed_at: String,
941 pub freshness: String,
942 pub purpose: String,
943 #[serde(default)]
944 pub components: Vec<AtlasComponent>,
945 #[serde(default)]
946 pub entrypoints: Vec<AtlasEntrypoint>,
947 #[serde(default)]
948 pub contracts: Vec<Contract>,
949 #[serde(default)]
952 pub coverage: BTreeMap<String, String>,
953 #[serde(default)]
954 pub flows: Vec<AtlasFlow>,
955 #[serde(default)]
956 pub invariants: Vec<AtlasInvariant>,
957 #[serde(default)]
958 pub deployment_units: Vec<String>,
959 #[serde(default)]
960 pub external_systems: Vec<String>,
961 #[serde(default)]
962 pub trust_boundaries: Vec<String>,
963 #[serde(default)]
964 pub async_boundaries: Vec<String>,
965 #[serde(default)]
966 pub implementation_map: BTreeMap<String, Vec<String>>,
967 #[serde(default)]
970 pub data_stores: Vec<String>,
971 #[serde(default)]
973 pub archetype: Option<Archetype>,
974 #[serde(default)]
978 pub state_authority: BTreeMap<String, Vec<String>>,
979 #[serde(default)]
982 pub hierarchy: Vec<AtlasHierarchyNode>,
983 #[serde(default)]
984 pub evidence_summary: BTreeMap<String, usize>,
985 #[serde(default)]
986 pub warnings: Vec<String>,
987 #[serde(default)]
991 pub public_api: BTreeMap<String, Vec<String>>,
992 #[serde(default)]
996 pub framework_semantics: BTreeMap<String, Vec<String>>,
997 #[serde(default)]
1001 pub pipeline: Vec<String>,
1002 #[serde(default)]
1005 pub landmarks: Vec<String>,
1006}
1007
1008#[derive(Debug, Clone, Serialize, Deserialize, schemars::JsonSchema)]
1015pub struct SystemIr {
1018 pub schema_version: String,
1019 pub repository: Repository,
1020 pub snapshot: Snapshot,
1021 pub entities: Vec<Entity>,
1022 pub relationships: Vec<Relationship>,
1023 pub flows: Vec<Flow>,
1024 pub invariants: Vec<Invariant>,
1025 #[serde(default)]
1026 pub evidence: Vec<Evidence>,
1027 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1031 pub extensions: BTreeMap<String, Vec<String>>,
1032}
1033
1034impl SystemIr {
1036pub fn empty(repository: Repository, snapshot: Snapshot) -> Self {
1038 SystemIr {
1039 schema_version: SCHEMA_VERSION.to_string(),
1040 repository,
1041 snapshot,
1042 entities: Vec::new(),
1043 relationships: Vec::new(),
1044 flows: Vec::new(),
1045 invariants: Vec::new(),
1046 evidence: Vec::new(),
1047 extensions: BTreeMap::new(),
1048 }
1049 }
1050}
1051
1052pub fn sanitize_key(input: &str) -> String {
1059 let mut out = String::with_capacity(input.len());
1060 let mut prev_dash = false;
1061 for c in input.chars() {
1062 let ok = c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.' || c == '/';
1063 if ok {
1064 if c == '/' {
1066 out.push('/');
1067 prev_dash = false;
1068 } else if c == '-' || c == '_' {
1069 if !prev_dash {
1070 out.push('-');
1071 }
1072 prev_dash = true;
1073 } else {
1074 out.push(c.to_ascii_lowercase());
1075 prev_dash = false;
1076 }
1077 } else {
1078 if !prev_dash {
1079 out.push('-');
1080 }
1081 prev_dash = true;
1082 }
1083 }
1084 while out.ends_with('-') {
1085 out.pop();
1086 }
1087 if out.is_empty() {
1088 out.push_str("unnamed");
1089 }
1090 out
1091}
1092
1093pub fn entity_id(repo: &str, kind: &str, key: &str) -> String {
1096 format!("repo://{}/{}/{}", sanitize_key(repo), kind, sanitize_key(key))
1097}
1098
1099pub fn occurrence_id(repo: &str, concept: &str, path: &str, owner: &str, line: u32) -> String {
1107 format!(
1108 "repo://{}/occurrence/{}@{}@{}@{}",
1109 sanitize_key(repo),
1110 encode_component(concept),
1111 encode_component(path),
1112 encode_component(owner),
1113 line
1114 )
1115}
1116
1117pub fn encode_component(input: &str) -> String {
1122 let mut out = String::with_capacity(input.len());
1123 for b in input.bytes() {
1124 let keep = b.is_ascii_alphanumeric() || matches!(b, b'/' | b'.' | b'_' | b'-');
1125 if keep {
1126 out.push(b as char);
1127 } else {
1128 out.push('%');
1129 out.push_str(&format!("{b:02X}"));
1130 }
1131 }
1132 out
1133}
1134
1135pub fn decode_component(input: &str) -> String {
1139 let bytes = input.as_bytes();
1140 let mut out = Vec::with_capacity(bytes.len());
1141 let mut i = 0;
1142 while i < bytes.len() {
1143 if bytes[i] == b'%' && i + 2 < bytes.len() {
1144 let hi = (bytes[i + 1] as char).to_digit(16);
1145 let lo = (bytes[i + 2] as char).to_digit(16);
1146 if let (Some(hi), Some(lo)) = (hi, lo) {
1147 out.push((hi * 16 + lo) as u8);
1148 i += 3;
1149 continue;
1150 }
1151 }
1152 out.push(bytes[i]);
1153 i += 1;
1154 }
1155 String::from_utf8_lossy(&out).to_string()
1156}
1157
1158pub fn symbol_id(repo: &str, file: &str, name: &str) -> String {
1161 format!(
1162 "repo://{}/symbol/{}/{}",
1163 sanitize_key(repo),
1164 encode_component(file),
1165 encode_component(name)
1166 )
1167}
1168
1169pub fn evidence_id(n: u64) -> String {
1173 format!("evidence:{n}")
1174}
1175
1176pub fn relationship_id(n: u64) -> String {
1179 format!("rel:{n}")
1180}
1181
1182pub mod kinds {
1187 pub const FILE: &str = "file";
1188 pub const SYMBOL: &str = "symbol";
1189 pub const PACKAGE: &str = "package";
1190 pub const MODULE: &str = "module";
1191 pub const SYSTEM: &str = "system";
1192 pub const SUBSYSTEM: &str = "subsystem";
1193 pub const SERVICE: &str = "service";
1194 pub const COMPONENT: &str = "component";
1195 pub const DEPLOYMENT_UNIT: &str = "deployment_unit";
1196 pub const ROUTE: &str = "route";
1197 pub const ENDPOINT: &str = "endpoint";
1198 pub const EVENT: &str = "event";
1199 pub const TOPIC: &str = "topic";
1200 pub const QUEUE: &str = "queue";
1201 pub const DATA_ENTITY: &str = "data";
1202 pub const DATA_STORE: &str = "store";
1203 pub const TABLE: &str = "table";
1204 pub const COLLECTION: &str = "collection";
1205 pub const CACHE: &str = "cache";
1206 pub const EXTERNAL_SYSTEM: &str = "external_system";
1207 pub const EXTERNAL_API: &str = "external_api";
1208 pub const CONFIGURATION: &str = "configuration";
1209 pub const FEATURE_FLAG: &str = "feature_flag";
1210 pub const SECRET_REFERENCE: &str = "secret_reference";
1211 pub const CONTRACT: &str = "contract";
1212 pub const INVARIANT: &str = "invariant";
1213 pub const TEST: &str = "test";
1214 pub const TEST_SUITE: &str = "test_suite";
1215 pub const FLOW: &str = "flow";
1216 pub const WORKFLOW: &str = "workflow";
1217 pub const STATE: &str = "state";
1218 pub const EXPORT: &str = "export";
1221 pub const ANNOTATION: &str = "annotation";
1222 pub const FIELD: &str = "field";
1223 pub const REGISTRY: &str = "registry";
1224 pub const MIDDLEWARE: &str = "middleware";
1225 pub const DI_BINDING: &str = "di_binding";
1226 pub const TRANSITION: &str = "transition";
1227 pub const RESOURCE: &str = "resource";
1228 pub const TRUST_BOUNDARY: &str = "trust_boundary";
1229 pub const SECURITY_CONTROL: &str = "security_control";
1230 pub const RUNTIME_OBSERVATION: &str = "runtime_observation";
1231 pub const SCHEMA: &str = "schema";
1233 pub const REACTIVE: &str = "reactive";
1234 pub const OCCURRENCE: &str = "occurrence";
1237
1238 pub const ALL: &[&str] = &[
1241 FILE, SYMBOL, PACKAGE, MODULE, SYSTEM, SUBSYSTEM, SERVICE, COMPONENT, DEPLOYMENT_UNIT,
1242 ROUTE, ENDPOINT, EVENT, TOPIC, QUEUE, DATA_ENTITY, DATA_STORE, TABLE, COLLECTION, CACHE,
1243 EXTERNAL_SYSTEM, EXTERNAL_API, CONFIGURATION, FEATURE_FLAG, SECRET_REFERENCE, CONTRACT,
1244 INVARIANT, TEST, TEST_SUITE, FLOW, WORKFLOW, STATE, EXPORT, ANNOTATION, FIELD, REGISTRY,
1245 MIDDLEWARE, DI_BINDING, TRANSITION, RESOURCE, TRUST_BOUNDARY, SECURITY_CONTROL,
1246 RUNTIME_OBSERVATION, SCHEMA, REACTIVE, OCCURRENCE,
1247 ];
1248}
1249
1250pub mod predicates {
1251 pub const CONTAINS: &str = "contains";
1252 pub const IMPLEMENTS: &str = "implements";
1253 pub const INHERITS: &str = "inherits";
1254 pub const IMPORTS: &str = "imports";
1255 pub const CALLS: &str = "calls";
1256 pub const READS: &str = "reads";
1257 pub const WRITES: &str = "writes";
1258 pub const QUERIES: &str = "queries";
1259 pub const OWNS: &str = "owns";
1260 pub const PUBLISHES: &str = "publishes";
1261 pub const CONSUMES: &str = "consumes";
1262 pub const SUBSCRIBES: &str = "subscribes";
1263 pub const PRODUCES: &str = "produces";
1264 pub const TRANSFORMS: &str = "transforms";
1265 pub const VALIDATES: &str = "validates";
1266 pub const DEFINES: &str = "defines";
1267 pub const COMPOSES: &str = "composes";
1268 pub const ROUTES_TO: &str = "routes_to";
1269 pub const HANDLES: &str = "handles";
1270 pub const INVOKES: &str = "invokes";
1271 pub const DEPENDS_ON: &str = "depends_on";
1272 pub const DEPLOYED_WITH: &str = "deployed_with";
1273 pub const DEPLOYED_IN: &str = "deployed_in";
1274 pub const CONFIGURED_BY: &str = "configured_by";
1275 pub const PROTECTED_BY: &str = "protected_by";
1276 pub const CROSSES_BOUNDARY: &str = "crosses_boundary";
1277 pub const ENFORCES: &str = "enforces";
1278 pub const TESTED_BY: &str = "tested_by";
1279 pub const PARTICIPATES_IN: &str = "participates_in";
1280 pub const PRECEDES: &str = "precedes";
1281 pub const FOLLOWS: &str = "follows";
1282 pub const BRANCHES_TO: &str = "branches_to";
1283 pub const RETRIES: &str = "retries";
1284 pub const FALLS_BACK_TO: &str = "falls_back_to";
1285 pub const OBSERVED_AS: &str = "observed_as";
1286 pub const DECLARED_AS: &str = "declared_as";
1287 pub const IMPLEMENTED_BY: &str = "implemented_by";
1288 pub const EXPORTS: &str = "exports";
1290 pub const ANNOTATES: &str = "annotates";
1291 pub const REGISTERS: &str = "registers";
1292 pub const INJECTS: &str = "injects";
1293 pub const HANDLES_CALLBACK: &str = "handles_callback";
1294 pub const DECORATES: &str = "decorates";
1295 pub const OCCURS: &str = "occurs";
1298
1299 pub const ALL: &[&str] = &[
1303 CONTAINS, IMPLEMENTS, INHERITS, IMPORTS, CALLS, READS, WRITES, QUERIES, OWNS, PUBLISHES,
1304 CONSUMES, SUBSCRIBES, PRODUCES, TRANSFORMS, VALIDATES, DEFINES, COMPOSES, ROUTES_TO,
1305 HANDLES, INVOKES, DEPENDS_ON, DEPLOYED_WITH, DEPLOYED_IN, CONFIGURED_BY, PROTECTED_BY,
1306 CROSSES_BOUNDARY, ENFORCES, TESTED_BY, PARTICIPATES_IN, PRECEDES, FOLLOWS, BRANCHES_TO,
1307 RETRIES, FALLS_BACK_TO, OBSERVED_AS, DECLARED_AS, IMPLEMENTED_BY, EXPORTS, ANNOTATES,
1308 REGISTERS, INJECTS, HANDLES_CALLBACK, DECORATES, OCCURS,
1309 ];
1310}
1311
1312pub fn ontology_registries() -> (&'static [&'static str], &'static [&'static str]) {
1316 (kinds::ALL, predicates::ALL)
1317}
1318
1319pub fn estimate_tokens(text: &str) -> usize {
1327 let chars = text.chars().count();
1328 chars.div_ceil(4)
1329}
1330
1331pub fn truncate_to_budget(text: &str, budget: usize) -> String {
1335 if estimate_tokens(text) <= budget {
1336 return text.to_string();
1337 }
1338 let max_chars = budget.saturating_mul(4);
1339 let mut end = 0;
1340 let mut chars = 0;
1341 for (i, c) in text.char_indices() {
1342 chars += 1;
1343 if chars > max_chars {
1344 break;
1345 }
1346 end = i + c.len_utf8();
1347 }
1348 let min_chars = max_chars / 2;
1351 let mut cut = end;
1352 if let Some(nl) = text[..end].rfind('\n') {
1353 let prefix_chars = text[..nl].chars().count();
1354 if prefix_chars >= min_chars {
1355 cut = nl;
1356 }
1357 }
1358 let mut out = text[..cut].to_string();
1359 out.push_str("\n… (truncated by token budget)");
1360 out
1361}
1362
1363pub fn now_rfc3339() -> String {
1369 chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true)
1370}
1371
1372#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
1380pub struct SourceRange {
1383 pub path: String,
1384 pub start_line: u32,
1385 pub end_line: u32,
1386}
1387
1388impl SourceRange {
1390pub fn new(path: impl Into<String>, start_line: u32, end_line: u32) -> Self {
1392 SourceRange {
1393 path: path.into(),
1394 start_line,
1395 end_line,
1396 }
1397 }
1398}
1399
1400#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, schemars::JsonSchema)]
1402#[serde(rename_all = "snake_case")]
1403pub enum Visibility {
1406 Public,
1407 Protected,
1408 Private,
1409 Package,
1410}
1411
1412impl Visibility {
1414pub fn as_str(&self) -> &'static str {
1416 match self {
1417 Visibility::Public => "public",
1418 Visibility::Protected => "protected",
1419 Visibility::Private => "private",
1420 Visibility::Package => "package",
1421 }
1422 }
1423}
1424
1425#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, schemars::JsonSchema)]
1427#[serde(rename_all = "snake_case")]
1428pub enum SurfaceKind {
1431 Function,
1432 Method,
1433 Constructor,
1434 Class,
1435 Interface,
1436 Trait,
1437 Type,
1438 Enum,
1439 Const,
1440 Module,
1441 Record,
1442}
1443
1444impl SurfaceKind {
1446pub fn as_str(&self) -> &'static str {
1448 match self {
1449 SurfaceKind::Function => "function",
1450 SurfaceKind::Method => "method",
1451 SurfaceKind::Constructor => "constructor",
1452 SurfaceKind::Class => "class",
1453 SurfaceKind::Interface => "interface",
1454 SurfaceKind::Trait => "trait",
1455 SurfaceKind::Type => "type",
1456 SurfaceKind::Enum => "enum",
1457 SurfaceKind::Const => "const",
1458 SurfaceKind::Module => "module",
1459 SurfaceKind::Record => "record",
1460 }
1461 }
1462}
1463
1464#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
1466pub struct SemanticParameter {
1469 pub name: String,
1470 #[serde(default, skip_serializing_if = "Option::is_none")]
1471 pub ty: Option<String>,
1472 #[serde(default)]
1474 pub receiver: bool,
1475 #[serde(default, skip_serializing_if = "Option::is_none")]
1476 pub default: Option<String>,
1477 #[serde(default)]
1478 pub variadic: bool,
1479}
1480
1481#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, schemars::JsonSchema)]
1485pub struct SemanticSignature {
1488 pub name: String,
1489 #[serde(default, skip_serializing_if = "Option::is_none")]
1490 pub owner: Option<String>,
1491 #[serde(default)]
1492 pub visibility: Option<Visibility>,
1493 #[serde(default)]
1494 pub async_: bool,
1495 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1496 pub generic_parameters: Vec<String>,
1497 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1498 pub parameters: Vec<SemanticParameter>,
1499 #[serde(default, skip_serializing_if = "Option::is_none")]
1500 pub returns: Option<String>,
1501 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1503 pub constraints: Vec<String>,
1504}
1505
1506#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
1509pub struct SurfaceRank {
1512 pub task_ppr: f64,
1513 pub global_ppr: f64,
1514 pub lexical: f64,
1515 pub semantic: f64,
1516 pub confidence: f64,
1517 pub criticality: f64,
1518 pub change_risk: f64,
1519 pub novelty: f64,
1520 pub total: f64,
1521 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1522 pub reasons: Vec<String>,
1523}
1524
1525impl Default for SurfaceRank {
1527fn default() -> Self {
1530 SurfaceRank {
1531 task_ppr: 0.0,
1532 global_ppr: 0.0,
1533 lexical: 0.0,
1534 semantic: 0.0,
1535 confidence: 0.0,
1536 criticality: 0.0,
1537 change_risk: 0.0,
1538 novelty: 0.0,
1539 total: 0.0,
1540 reasons: Vec::new(),
1541 }
1542 }
1543}
1544
1545#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
1549pub struct SurfaceEntry {
1552 pub id: String,
1553 pub symbol_id: String,
1554 pub qualified_name: String,
1555 pub kind: SurfaceKind,
1556 pub path: String,
1557 pub range: SourceRange,
1558 pub source_signature: String,
1560 pub canonical_signature: String,
1562 pub semantic_signature: SemanticSignature,
1563 pub visibility: Visibility,
1564 #[serde(default)]
1565 pub exported: bool,
1566 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1567 pub modifiers: Vec<String>,
1568 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1569 pub annotations: Vec<String>,
1570 #[serde(default, skip_serializing_if = "Option::is_none")]
1571 pub component: Option<String>,
1572 #[serde(default, skip_serializing_if = "Option::is_none")]
1573 pub subsystem: Option<String>,
1574 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1575 pub flows: Vec<String>,
1576 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1577 pub contracts: Vec<String>,
1578 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1579 pub state_authorities: Vec<String>,
1580 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1581 pub invocation_surfaces: Vec<String>,
1582 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1583 pub callers: Vec<String>,
1584 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1585 pub callees: Vec<String>,
1586 #[serde(default)]
1589 pub caller_count: usize,
1590 #[serde(default)]
1591 pub callee_count: usize,
1592 #[serde(default, skip_serializing_if = "Option::is_none")]
1596 pub importance: Option<ImportanceProfile>,
1597 pub provenance: Provenance,
1598 #[serde(default)]
1599 pub confidence: f32,
1600 #[serde(default)]
1601 pub rank: SurfaceRank,
1602}
1603
1604#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
1612pub struct ImportanceProfile {
1614 pub overall: f64,
1615 pub caller_count: usize,
1616 pub callee_count: usize,
1617 pub global_ppr: f64,
1618 pub task_ppr: f64,
1619 pub entrypoint: bool,
1620 pub exported: bool,
1621 pub flow_count: usize,
1622 pub contract_count: usize,
1623 pub state_read_count: usize,
1624 pub state_write_count: usize,
1625 pub dependent_count: usize,
1626 pub change_risk: f64,
1627 pub badges: Vec<String>,
1628}
1629
1630#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
1631pub struct SurfaceOmission {
1634 pub count: usize,
1635 pub kind: String,
1636 pub reason: String,
1637}
1638
1639#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
1642pub struct SystemSurfaceMap {
1645 pub repository: String,
1646 pub revision: String,
1647 pub epoch: String,
1648 pub entries: Vec<SurfaceEntry>,
1649 #[serde(default)]
1650 pub token_count: usize,
1651 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1652 pub omitted: Vec<SurfaceOmission>,
1653}
1654
1655#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
1661pub struct SurfaceRenderResult {
1664 pub text: String,
1666 pub rendered_ids: Vec<String>,
1668 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1672 pub rendered_entries: Vec<SurfaceEntry>,
1673 pub omitted_ids: Vec<String>,
1675 pub omissions: Vec<SurfaceOmission>,
1677 pub token_count: usize,
1679 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1684 pub critical_drops: Vec<String>,
1685}
1686
1687#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1693pub struct RankNode {
1696 pub id: String,
1698 pub kind: String,
1700 pub name: String,
1702}
1703
1704#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1708pub struct ReferenceEdge {
1711 pub source_symbol: String,
1712 pub target_symbol: String,
1713 pub kind: ReferenceKind,
1714 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1715 pub locations: Vec<SourceRange>,
1716 pub provenance: Provenance,
1717 #[serde(default)]
1718 pub confidence: f32,
1719}
1720
1721#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
1722#[serde(rename_all = "snake_case")]
1723pub enum ReferenceKind {
1726 Read,
1727 Write,
1728 #[default]
1729 Call,
1730 TypeUse,
1731 Instantiate,
1732 Implement,
1733 Extend,
1734 Decorate,
1735 Register,
1736 Import,
1737 Export,
1738 Macro,
1739 FieldAccess,
1740 Construct,
1741}
1742
1743impl ReferenceKind {
1745pub fn as_str(&self) -> &'static str {
1747 match self {
1748 ReferenceKind::Read => "read",
1749 ReferenceKind::Write => "write",
1750 ReferenceKind::Call => "call",
1751 ReferenceKind::TypeUse => "type_use",
1752 ReferenceKind::Instantiate => "instantiate",
1753 ReferenceKind::Implement => "implement",
1754 ReferenceKind::Extend => "extend",
1755 ReferenceKind::Decorate => "decorate",
1756 ReferenceKind::Register => "register",
1757 ReferenceKind::Import => "import",
1758 ReferenceKind::Export => "export",
1759 ReferenceKind::Macro => "macro",
1760 ReferenceKind::FieldAccess => "field_access",
1761 ReferenceKind::Construct => "construct",
1762 }
1763 }
1764}
1765
1766#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1768pub struct ContextItem {
1771 pub id: String,
1772 pub value: f64,
1773 pub token_cost: usize,
1774 #[serde(default)]
1775 pub required: bool,
1776 #[serde(default, skip_serializing_if = "Option::is_none")]
1777 pub group: Option<String>,
1778}
1779
1780#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1782pub struct ContextBudget {
1784 pub total: usize,
1785 pub atlas: usize,
1786 pub surface: usize,
1787 pub task_delta: usize,
1788 pub structural_source: usize,
1789}
1790
1791impl Default for ContextBudget {
1793fn default() -> Self {
1796 ContextBudget {
1797 total: 20_000,
1798 atlas: 13_000,
1799 surface: 7_000,
1800 task_delta: 3_000,
1801 structural_source: 6_000,
1802 }
1803 }
1804}
1805
1806const MASSIVE_SURFACE_CAP: usize = 10_000;
1809
1810impl ContextBudget {
1812 pub fn adaptive(
1838 total: usize,
1839 entity_count: usize,
1840 component_count: usize,
1841 flow_count: usize,
1842 surface_candidates: usize,
1843 ) -> ContextBudget {
1844 let (surface_pct, boost, cap): (f64, f64, Option<usize>) = if entity_count > 20_000 {
1845 (30.0, 0.0, Some(MASSIVE_SURFACE_CAP))
1846 } else if entity_count > 5_000 || component_count > 30 {
1847 (35.0, 5.0, None)
1848 } else if flow_count > 40 {
1849 (35.0, 5.0, None)
1853 } else if entity_count < 200 {
1854 (45.0, 5.0, None)
1855 } else {
1856 (40.0, 5.0, None)
1857 };
1858 let boost_pp = (surface_candidates / 2_000).min(boost as usize) as f64;
1859 let surface_pct = (surface_pct + boost_pp).min(50.0);
1860 let atlas_pct = 100.0 - surface_pct;
1861 let mut surface = ((total as f64) * surface_pct / 100.0).round() as usize;
1862 if let Some(c) = cap {
1863 surface = surface.min(c);
1864 }
1865 let atlas = ((total as f64) * atlas_pct / 100.0).round() as usize;
1866 let def = ContextBudget::default();
1867 ContextBudget {
1868 total,
1869 atlas,
1870 surface,
1871 task_delta: def.task_delta,
1872 structural_source: def.structural_source,
1873 }
1874 }
1875}
1876
1877#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1880pub struct ContextLedger {
1883 pub model_epoch: String,
1884 #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
1885 pub visible_entities: BTreeSet<String>,
1886 #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
1887 pub visible_symbols: BTreeSet<String>,
1888 #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
1889 pub visible_files: BTreeSet<String>,
1890 #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
1891 pub visible_components: BTreeSet<String>,
1892 #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
1893 pub visible_flows: BTreeSet<String>,
1894 #[serde(default, skip_serializing_if = "Option::is_none")]
1895 pub last_task: Option<String>,
1896}
1897
1898#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1901pub struct StructuralSourceUnit {
1904 pub path: String,
1905 pub source: String,
1907 pub representation: String,
1908 pub revision: String,
1909 pub content: String,
1910 #[serde(default, skip_serializing_if = "String::is_empty")]
1913 pub handle: String,
1914 #[serde(default, skip_serializing_if = "String::is_empty")]
1917 pub representation_reason: String,
1918}
1919
1920#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1923pub struct ContextArtifact {
1926 pub kind: String,
1927 pub epoch: String,
1928 pub renderer_version: String,
1929 pub trust_policy: String,
1930 pub budget: ContextBudget,
1931 pub sha256: String,
1935 #[serde(default, skip_serializing_if = "String::is_empty")]
1939 pub content_hash: String,
1940 pub text: String,
1941}
1942
1943#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1945pub struct TaskSeed {
1948 pub kind: String,
1949 pub id: String,
1950 pub weight: f64,
1951}
1952
1953#[cfg(test)]
1954mod tests {
1955 use super::*;
1956 #[test]
1957 fn estimate_tokens_is_chars_ceiling() {
1959 assert_eq!(estimate_tokens(""), 0);
1962 assert_eq!(estimate_tokens("a"), 1);
1963 assert_eq!(estimate_tokens("abcd"), 1);
1964 assert_eq!(estimate_tokens("abcde"), 2);
1965 assert_eq!(estimate_tokens("fn main() { println!(\"hi\"); }"), 8);
1966 assert_eq!(estimate_tokens("日本語"), 1);
1968 assert_eq!(estimate_tokens("日本語テスト"), 2);
1969 let path = "crates/scc-context/src/surface/structural_source_selection_policy.rs";
1971 assert_eq!(estimate_tokens(path), path.chars().count().div_ceil(4));
1972 }
1973
1974 #[test]
1975fn component_encode_decode_roundtrip() {
1978 for name in [
1979 "Normalizer",
1980 "GET /api/transcripts/:id handler",
1981 "OrderStateMachine.advance",
1982 "test_normalization_preserves_raw",
1983 "src/app.ts",
1984 "a b%c",
1985 ] {
1986 assert_eq!(decode_component(&encode_component(name)), name, "{name}");
1987 }
1988 }
1989
1990 #[test]
1991fn component_ids_are_collision_free() {
1994 assert_ne!(
1995 encode_component("foo_bar"),
1996 encode_component("foo-bar"),
1997 "underscore and dash must not collide"
1998 );
1999 }
2000
2001#[test]
2004fn occurrence_ids_are_collision_free_per_site() {
2007 assert_ne!(
2009 occurrence_id("r", "expr", "a.ts", "A", 1),
2010 occurrence_id("r", "expr", "b.ts", "A", 1)
2011 );
2012 assert_ne!(
2013 occurrence_id("r", "expr", "a.ts", "A", 1),
2014 occurrence_id("r", "expr", "a.ts", "B", 1)
2015 );
2016 assert_ne!(
2017 occurrence_id("r", "expr", "a.ts", "A", 1),
2018 occurrence_id("r", "expr", "a.ts", "A", 2)
2019 );
2020 assert_ne!(
2021 occurrence_id("r", "expr1", "a.ts", "A", 1),
2022 occurrence_id("r", "expr2", "a.ts", "A", 1)
2023 );
2024 assert_ne!(
2026 occurrence_id("r", "expr", "a_b.ts", "A", 1),
2027 occurrence_id("r", "expr", "a-b.ts", "A", 1)
2028 );
2029 assert_ne!(
2030 occurrence_id("r", "expr", "a.ts", "Foo", 1),
2031 occurrence_id("r", "expr", "a.ts", "foo", 1)
2032 );
2033 assert_eq!(
2035 occurrence_id("r", "expr", "a.ts", "A", 1),
2036 occurrence_id("r", "expr", "a.ts", "A", 1)
2037 );
2038 }
2039
2040#[test]
2043fn occurrence_roundtrips_through_serde() {
2046 let o = Occurrence {
2047 id: occurrence_id("r", "z.object({ x: z.string() })", "src/a.ts", "make", 7),
2048 concept: entity_id("r", kinds::SCHEMA, "z.object({ x: z.string() })"),
2049 path: "src/a.ts".into(),
2050 owner: "make".into(),
2051 line: 7,
2052 };
2053 let json = serde_json::to_string(&o).unwrap();
2054 let back: Occurrence = serde_json::from_str(&json).unwrap();
2055 assert_eq!(back.id, o.id);
2056 assert_eq!(back.concept, o.concept);
2057 assert_eq!(back.line, 7);
2058 }
2059
2060#[test]
2063fn adaptive_budget_scales_split_by_repo_complexity() {
2066 let tiny = ContextBudget::adaptive(20_000, 50, 2, 1, 10);
2069 let tiny_ratio = tiny.surface as f64 / tiny.total as f64;
2070 assert!(
2071 (tiny_ratio - 0.45).abs() <= 0.01,
2072 "tiny: surface share {tiny_ratio}"
2073 );
2074 assert_eq!(tiny.total, 20_000);
2075 assert!(tiny.atlas + tiny.surface <= 20_000);
2076
2077 let normal = ContextBudget::adaptive(20_000, 1_000, 5, 4, 10);
2079 let normal_ratio = normal.surface as f64 / normal.total as f64;
2080 assert!(
2081 (normal_ratio - 0.40).abs() <= 0.01,
2082 "normal: surface share {normal_ratio}"
2083 );
2084
2085 let large = ContextBudget::adaptive(20_000, 1_000, 40, 4, 10);
2087 let large_ratio = large.surface as f64 / large.total as f64;
2088 assert!(
2089 (large_ratio - 0.35).abs() <= 0.01,
2090 "large: surface share {large_ratio}"
2091 );
2092
2093 let massive = ContextBudget::adaptive(20_000, 25_000, 60, 30, 10);
2095 assert!(massive.atlas > massive.surface);
2096 let massive_ratio = massive.surface as f64 / massive.total as f64;
2097 assert!(
2098 (massive_ratio - 0.30).abs() <= 0.01,
2099 "massive: surface share {massive_ratio}"
2100 );
2101 let massive_huge = ContextBudget::adaptive(100_000, 25_000, 60, 30, 10);
2103 assert_eq!(
2104 massive_huge.surface, MASSIVE_SURFACE_CAP,
2105 "massive surface absolutely capped"
2106 );
2107
2108 assert_ne!(
2110 tiny.atlas as f64 / tiny.surface as f64,
2111 large.atlas as f64 / large.surface as f64,
2112 "tiny vs large splits must differ"
2113 );
2114 }
2115
2116 #[test]
2117fn adaptive_budget_candidate_pool_boosts_surface_share() {
2120 let boosted = ContextBudget::adaptive(20_000, 1_000, 5, 4, 10_000);
2122 let boosted_ratio = boosted.surface as f64 / boosted.total as f64;
2123 assert!(
2124 (boosted_ratio - 0.45).abs() <= 0.01,
2125 "boosted: surface share {boosted_ratio}"
2126 );
2127 let huge = ContextBudget::adaptive(20_000, 100, 2, 1, 100_000);
2129 assert!(huge.surface <= huge.total / 2, "surface capped at 50%");
2130 }
2131
2132 #[test]
2133fn adaptive_budget_is_deterministic_and_total_preserving() {
2136 let a = ContextBudget::adaptive(15_000, 3_000, 8, 6, 500);
2137 let b = ContextBudget::adaptive(15_000, 3_000, 8, 6, 500);
2138 assert_eq!(a, b);
2139 assert_eq!(a.total, 15_000);
2140 assert!(a.atlas + a.surface <= 15_000);
2141 }
2142
2143 #[test]
2144fn contract_kind_renders_as_operation_lines() {
2147 let mut c = Contract::new(
2148 "repo://repo/contract/http/get--api-x",
2149 "http",
2150 "repo://repo/symbol/main.py/handler",
2151 );
2152 c.operations.push("GET /api/x".into());
2153 c.consumers.push("repo://repo/symbol/main.py/handler".into());
2154 let lines: Vec<String> = c
2155 .operations
2156 .iter()
2157 .map(|op| format!("{}: {}", c.kind, op))
2158 .collect();
2159 assert_eq!(lines, vec!["http: GET /api/x"]);
2160 }
2161
2162 #[test]
2163fn contract_subclass_ontology_maps_and_renders() {
2166 assert_eq!(ContractSubclass::Http.as_str(), "http");
2168 assert_eq!(ContractSubclass::Cli.as_str(), "cli");
2169 assert_eq!(ContractSubclass::Event.as_str(), "event");
2170 assert_eq!(ContractSubclass::Configuration.as_str(), "config");
2171 assert_eq!(ContractSubclass::PublicApi.as_str(), "public-api");
2172 assert_eq!(ContractSubclass::Extension.as_str(), "extension");
2173 assert_eq!(ContractSubclass::Serialization.as_str(), "serialization");
2174 assert_eq!(ContractSubclass::CallContract.as_str(), "call");
2175 assert_eq!(ContractSubclass::Rpc.as_str(), "rpc");
2176 assert_eq!(ContractSubclass::Message.as_str(), "message");
2177 assert_eq!(ContractSubclass::Schema.as_str(), "schema");
2178 assert_eq!(ContractSubclass::Plugin.as_str(), "plugin");
2179
2180 assert_eq!(ContractSubclass::from_kind_str("http"), Some(ContractSubclass::Http));
2183 assert_eq!(ContractSubclass::from_kind_str("route"), Some(ContractSubclass::Http));
2184 assert_eq!(ContractSubclass::from_kind_str("cli"), Some(ContractSubclass::Cli));
2185 assert_eq!(ContractSubclass::from_kind_str("event"), Some(ContractSubclass::Event));
2186 assert_eq!(
2187 ContractSubclass::from_kind_str("config"),
2188 Some(ContractSubclass::Configuration)
2189 );
2190 assert_eq!(
2191 ContractSubclass::from_kind_str("next-config"),
2192 Some(ContractSubclass::Configuration)
2193 );
2194 assert_eq!(
2196 ContractSubclass::from_kind_str("builder"),
2197 Some(ContractSubclass::Configuration)
2198 );
2199 assert_eq!(
2200 ContractSubclass::from_kind_str("factory"),
2201 Some(ContractSubclass::PublicApi)
2202 );
2203 assert_eq!(
2204 ContractSubclass::from_kind_str("serialization"),
2205 Some(ContractSubclass::Serialization)
2206 );
2207 assert_eq!(
2208 ContractSubclass::from_kind_str("extension"),
2209 Some(ContractSubclass::Extension)
2210 );
2211 assert_eq!(ContractSubclass::from_kind_str("plugin"), Some(ContractSubclass::Plugin));
2212 assert_eq!(ContractSubclass::from_kind_str("rpc"), Some(ContractSubclass::Rpc));
2213 assert_eq!(ContractSubclass::from_kind_str("message"), Some(ContractSubclass::Message));
2214 assert_eq!(ContractSubclass::from_kind_str("schema"), Some(ContractSubclass::Schema));
2215 assert_eq!(ContractSubclass::from_kind_str("task"), Some(ContractSubclass::CallContract));
2216 assert_eq!(ContractSubclass::from_kind_str("include_router"), None);
2218 assert_eq!(ContractSubclass::from_kind_str("add_middleware"), None);
2219
2220 let mut c = Contract::new(
2223 "repo://repo/contract/http/get--api-x",
2224 "http",
2225 "repo://repo/symbol/main.py/handler",
2226 );
2227 assert_eq!(c.subclass, ContractSubclass::Http);
2228 c.subclass = ContractSubclass::Serialization;
2229 let json = serde_json::to_string(&c).unwrap();
2230 let back: Contract = serde_json::from_str(&json).unwrap();
2231 assert_eq!(back.subclass, ContractSubclass::Serialization);
2232 let legacy = serde_json::json!({
2233 "id": "repo://repo/contract/x",
2234 "kind": "http",
2235 "producer": "p",
2236 "operations": ["GET /x"],
2237 "evidence": [],
2238 "consumers": [],
2239 });
2240 let c2: Contract = serde_json::from_value(legacy).unwrap();
2241 assert_eq!(c2.subclass, ContractSubclass::Http);
2242 }
2243
2244 #[test]
2245fn invocation_surface_kinds_stringify() {
2248 assert_eq!(InvocationSurfaceKind::PublicApi.as_str(), "public_api");
2249 assert_eq!(InvocationSurfaceKind::Queue.as_str(), "queue");
2250 assert_eq!(InvocationSurfaceKind::Lifecycle.as_str(), "lifecycle");
2251 assert_eq!(InvocationSurfaceKind::FrameworkCallback.as_str(), "framework_callback");
2252 let json = serde_json::to_string(&InvocationSurfaceKind::PublicApi).unwrap();
2254 assert_eq!(json, "\"public_api\"");
2255 let back: InvocationSurfaceKind = serde_json::from_str(&json).unwrap();
2256 assert_eq!(back, InvocationSurfaceKind::PublicApi);
2257 }
2258
2259 #[test]
2260fn surface_render_result_roundtrips_through_serde() {
2263 let r = SurfaceRenderResult {
2264 text: "SCC SYSTEM SURFACE MAP\n\n function serve\n".into(),
2265 rendered_ids: vec!["repo://r/symbol/api.py/serve".into()],
2266 rendered_entries: vec![],
2267 omitted_ids: vec!["repo://r/symbol/api.py/internal".into()],
2268 omissions: vec![SurfaceOmission {
2269 count: 1,
2270 kind: "function".into(),
2271 reason: "token budget".into(),
2272 }],
2273 token_count: 7,
2274 critical_drops: vec![],
2275 };
2276 let json = serde_json::to_string(&r).unwrap();
2277 let back: SurfaceRenderResult = serde_json::from_str(&json).unwrap();
2278 assert_eq!(back, r);
2279 assert_eq!(back.rendered_ids[0], "repo://r/symbol/api.py/serve");
2280 assert_eq!(back.omissions[0].count, 1);
2281 }
2282
2283 #[test]
2284fn rank_node_carries_kind_and_name() {
2287 let n = RankNode {
2288 id: "repo://r/contract/c1".into(),
2289 kind: kinds::CONTRACT.into(),
2290 name: "c1".into(),
2291 };
2292 let json = serde_json::to_string(&n).unwrap();
2293 let back: RankNode = serde_json::from_str(&json).unwrap();
2294 assert_eq!(back.kind, "contract");
2295 assert_eq!(back.name, "c1");
2296 }
2297
2298 #[test]
2299fn context_artifact_content_hash_roundtrips_and_defaults() {
2302 let a = ContextArtifact {
2304 kind: "startup".into(),
2305 epoch: "e1".into(),
2306 renderer_version: "0.1.0".into(),
2307 trust_policy: "floor=0.85".into(),
2308 budget: ContextBudget::default(),
2309 sha256: "abc".into(),
2310 content_hash: "def".into(),
2311 text: "body".into(),
2312 };
2313 let json = serde_json::to_string(&a).unwrap();
2314 let back: ContextArtifact = serde_json::from_str(&json).unwrap();
2315 assert_eq!(back.content_hash, "def");
2316
2317 let legacy = serde_json::json!({
2319 "kind": "startup",
2320 "epoch": "e1",
2321 "renderer_version": "0.1.0",
2322 "trust_policy": "floor=0.85",
2323 "budget": ContextBudget::default(),
2324 "sha256": "abc",
2325 "text": "body",
2326 });
2327 let c: ContextArtifact = serde_json::from_value(legacy).unwrap();
2328 assert_eq!(c.content_hash, "");
2329 }
2330
2331 #[test]
2332 fn predicate_and_kind_registries_are_complete() {
2334 let (kind_ids, predicate_ids) = ontology_registries();
2337 for required in [
2338 predicates::DEFINES,
2339 predicates::COMPOSES,
2340 predicates::EXPORTS,
2341 predicates::ANNOTATES,
2342 predicates::REGISTERS,
2343 predicates::INJECTS,
2344 predicates::HANDLES_CALLBACK,
2345 predicates::DECORATES,
2346 predicates::OCCURS,
2347 ] {
2348 assert!(
2349 predicate_ids.contains(&required),
2350 "predicates::ALL missing {required}"
2351 );
2352 }
2353 for required in [kinds::FIELD, kinds::SCHEMA, kinds::TRUST_BOUNDARY, kinds::OCCURRENCE]
2354 {
2355 assert!(kind_ids.contains(&required), "kinds::ALL missing {required}");
2356 }
2357 let mut seen = std::collections::BTreeSet::new();
2358 for p in predicate_ids {
2359 assert!(seen.insert(*p), "duplicate predicate in ALL: {p}");
2360 }
2361 }
2362}