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}
1028
1029impl SystemIr {
1031pub fn empty(repository: Repository, snapshot: Snapshot) -> Self {
1033 SystemIr {
1034 schema_version: SCHEMA_VERSION.to_string(),
1035 repository,
1036 snapshot,
1037 entities: Vec::new(),
1038 relationships: Vec::new(),
1039 flows: Vec::new(),
1040 invariants: Vec::new(),
1041 evidence: Vec::new(),
1042 }
1043 }
1044}
1045
1046pub fn sanitize_key(input: &str) -> String {
1053 let mut out = String::with_capacity(input.len());
1054 let mut prev_dash = false;
1055 for c in input.chars() {
1056 let ok = c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == '.' || c == '/';
1057 if ok {
1058 if c == '/' {
1060 out.push('/');
1061 prev_dash = false;
1062 } else if c == '-' || c == '_' {
1063 if !prev_dash {
1064 out.push('-');
1065 }
1066 prev_dash = true;
1067 } else {
1068 out.push(c.to_ascii_lowercase());
1069 prev_dash = false;
1070 }
1071 } else {
1072 if !prev_dash {
1073 out.push('-');
1074 }
1075 prev_dash = true;
1076 }
1077 }
1078 while out.ends_with('-') {
1079 out.pop();
1080 }
1081 if out.is_empty() {
1082 out.push_str("unnamed");
1083 }
1084 out
1085}
1086
1087pub fn entity_id(repo: &str, kind: &str, key: &str) -> String {
1090 format!("repo://{}/{}/{}", sanitize_key(repo), kind, sanitize_key(key))
1091}
1092
1093pub fn occurrence_id(repo: &str, concept: &str, path: &str, owner: &str, line: u32) -> String {
1101 format!(
1102 "repo://{}/occurrence/{}@{}@{}@{}",
1103 sanitize_key(repo),
1104 encode_component(concept),
1105 encode_component(path),
1106 encode_component(owner),
1107 line
1108 )
1109}
1110
1111pub fn encode_component(input: &str) -> String {
1116 let mut out = String::with_capacity(input.len());
1117 for b in input.bytes() {
1118 let keep = b.is_ascii_alphanumeric() || matches!(b, b'/' | b'.' | b'_' | b'-');
1119 if keep {
1120 out.push(b as char);
1121 } else {
1122 out.push('%');
1123 out.push_str(&format!("{b:02X}"));
1124 }
1125 }
1126 out
1127}
1128
1129pub fn decode_component(input: &str) -> String {
1133 let bytes = input.as_bytes();
1134 let mut out = Vec::with_capacity(bytes.len());
1135 let mut i = 0;
1136 while i < bytes.len() {
1137 if bytes[i] == b'%' && i + 2 < bytes.len() {
1138 let hi = (bytes[i + 1] as char).to_digit(16);
1139 let lo = (bytes[i + 2] as char).to_digit(16);
1140 if let (Some(hi), Some(lo)) = (hi, lo) {
1141 out.push((hi * 16 + lo) as u8);
1142 i += 3;
1143 continue;
1144 }
1145 }
1146 out.push(bytes[i]);
1147 i += 1;
1148 }
1149 String::from_utf8_lossy(&out).to_string()
1150}
1151
1152pub fn symbol_id(repo: &str, file: &str, name: &str) -> String {
1155 format!(
1156 "repo://{}/symbol/{}/{}",
1157 sanitize_key(repo),
1158 encode_component(file),
1159 encode_component(name)
1160 )
1161}
1162
1163pub fn evidence_id(n: u64) -> String {
1167 format!("evidence:{n}")
1168}
1169
1170pub fn relationship_id(n: u64) -> String {
1173 format!("rel:{n}")
1174}
1175
1176pub mod kinds {
1181 pub const FILE: &str = "file";
1182 pub const SYMBOL: &str = "symbol";
1183 pub const PACKAGE: &str = "package";
1184 pub const MODULE: &str = "module";
1185 pub const SYSTEM: &str = "system";
1186 pub const SUBSYSTEM: &str = "subsystem";
1187 pub const SERVICE: &str = "service";
1188 pub const COMPONENT: &str = "component";
1189 pub const DEPLOYMENT_UNIT: &str = "deployment_unit";
1190 pub const ROUTE: &str = "route";
1191 pub const ENDPOINT: &str = "endpoint";
1192 pub const EVENT: &str = "event";
1193 pub const TOPIC: &str = "topic";
1194 pub const QUEUE: &str = "queue";
1195 pub const DATA_ENTITY: &str = "data";
1196 pub const DATA_STORE: &str = "store";
1197 pub const TABLE: &str = "table";
1198 pub const COLLECTION: &str = "collection";
1199 pub const CACHE: &str = "cache";
1200 pub const EXTERNAL_SYSTEM: &str = "external_system";
1201 pub const EXTERNAL_API: &str = "external_api";
1202 pub const CONFIGURATION: &str = "configuration";
1203 pub const FEATURE_FLAG: &str = "feature_flag";
1204 pub const SECRET_REFERENCE: &str = "secret_reference";
1205 pub const CONTRACT: &str = "contract";
1206 pub const INVARIANT: &str = "invariant";
1207 pub const TEST: &str = "test";
1208 pub const TEST_SUITE: &str = "test_suite";
1209 pub const FLOW: &str = "flow";
1210 pub const WORKFLOW: &str = "workflow";
1211 pub const STATE: &str = "state";
1212 pub const EXPORT: &str = "export";
1215 pub const ANNOTATION: &str = "annotation";
1216 pub const FIELD: &str = "field";
1217 pub const REGISTRY: &str = "registry";
1218 pub const MIDDLEWARE: &str = "middleware";
1219 pub const DI_BINDING: &str = "di_binding";
1220 pub const TRANSITION: &str = "transition";
1221 pub const RESOURCE: &str = "resource";
1222 pub const TRUST_BOUNDARY: &str = "trust_boundary";
1223 pub const SECURITY_CONTROL: &str = "security_control";
1224 pub const RUNTIME_OBSERVATION: &str = "runtime_observation";
1225 pub const SCHEMA: &str = "schema";
1227 pub const REACTIVE: &str = "reactive";
1228 pub const OCCURRENCE: &str = "occurrence";
1231
1232 pub const ALL: &[&str] = &[
1235 FILE, SYMBOL, PACKAGE, MODULE, SYSTEM, SUBSYSTEM, SERVICE, COMPONENT, DEPLOYMENT_UNIT,
1236 ROUTE, ENDPOINT, EVENT, TOPIC, QUEUE, DATA_ENTITY, DATA_STORE, TABLE, COLLECTION, CACHE,
1237 EXTERNAL_SYSTEM, EXTERNAL_API, CONFIGURATION, FEATURE_FLAG, SECRET_REFERENCE, CONTRACT,
1238 INVARIANT, TEST, TEST_SUITE, FLOW, WORKFLOW, STATE, EXPORT, ANNOTATION, FIELD, REGISTRY,
1239 MIDDLEWARE, DI_BINDING, TRANSITION, RESOURCE, TRUST_BOUNDARY, SECURITY_CONTROL,
1240 RUNTIME_OBSERVATION, SCHEMA, REACTIVE, OCCURRENCE,
1241 ];
1242}
1243
1244pub mod predicates {
1245 pub const CONTAINS: &str = "contains";
1246 pub const IMPLEMENTS: &str = "implements";
1247 pub const INHERITS: &str = "inherits";
1248 pub const IMPORTS: &str = "imports";
1249 pub const CALLS: &str = "calls";
1250 pub const READS: &str = "reads";
1251 pub const WRITES: &str = "writes";
1252 pub const QUERIES: &str = "queries";
1253 pub const OWNS: &str = "owns";
1254 pub const PUBLISHES: &str = "publishes";
1255 pub const CONSUMES: &str = "consumes";
1256 pub const SUBSCRIBES: &str = "subscribes";
1257 pub const PRODUCES: &str = "produces";
1258 pub const TRANSFORMS: &str = "transforms";
1259 pub const VALIDATES: &str = "validates";
1260 pub const DEFINES: &str = "defines";
1261 pub const COMPOSES: &str = "composes";
1262 pub const ROUTES_TO: &str = "routes_to";
1263 pub const HANDLES: &str = "handles";
1264 pub const INVOKES: &str = "invokes";
1265 pub const DEPENDS_ON: &str = "depends_on";
1266 pub const DEPLOYED_WITH: &str = "deployed_with";
1267 pub const DEPLOYED_IN: &str = "deployed_in";
1268 pub const CONFIGURED_BY: &str = "configured_by";
1269 pub const PROTECTED_BY: &str = "protected_by";
1270 pub const CROSSES_BOUNDARY: &str = "crosses_boundary";
1271 pub const ENFORCES: &str = "enforces";
1272 pub const TESTED_BY: &str = "tested_by";
1273 pub const PARTICIPATES_IN: &str = "participates_in";
1274 pub const PRECEDES: &str = "precedes";
1275 pub const FOLLOWS: &str = "follows";
1276 pub const BRANCHES_TO: &str = "branches_to";
1277 pub const RETRIES: &str = "retries";
1278 pub const FALLS_BACK_TO: &str = "falls_back_to";
1279 pub const OBSERVED_AS: &str = "observed_as";
1280 pub const DECLARED_AS: &str = "declared_as";
1281 pub const IMPLEMENTED_BY: &str = "implemented_by";
1282 pub const EXPORTS: &str = "exports";
1284 pub const ANNOTATES: &str = "annotates";
1285 pub const REGISTERS: &str = "registers";
1286 pub const INJECTS: &str = "injects";
1287 pub const HANDLES_CALLBACK: &str = "handles_callback";
1288 pub const DECORATES: &str = "decorates";
1289 pub const OCCURS: &str = "occurs";
1292
1293 pub const ALL: &[&str] = &[
1297 CONTAINS, IMPLEMENTS, INHERITS, IMPORTS, CALLS, READS, WRITES, QUERIES, OWNS, PUBLISHES,
1298 CONSUMES, SUBSCRIBES, PRODUCES, TRANSFORMS, VALIDATES, DEFINES, COMPOSES, ROUTES_TO,
1299 HANDLES, INVOKES, DEPENDS_ON, DEPLOYED_WITH, DEPLOYED_IN, CONFIGURED_BY, PROTECTED_BY,
1300 CROSSES_BOUNDARY, ENFORCES, TESTED_BY, PARTICIPATES_IN, PRECEDES, FOLLOWS, BRANCHES_TO,
1301 RETRIES, FALLS_BACK_TO, OBSERVED_AS, DECLARED_AS, IMPLEMENTED_BY, EXPORTS, ANNOTATES,
1302 REGISTERS, INJECTS, HANDLES_CALLBACK, DECORATES, OCCURS,
1303 ];
1304}
1305
1306pub fn ontology_registries() -> (&'static [&'static str], &'static [&'static str]) {
1310 (kinds::ALL, predicates::ALL)
1311}
1312
1313pub fn estimate_tokens(text: &str) -> usize {
1321 let chars = text.chars().count();
1322 chars.div_ceil(4)
1323}
1324
1325pub fn truncate_to_budget(text: &str, budget: usize) -> String {
1329 if estimate_tokens(text) <= budget {
1330 return text.to_string();
1331 }
1332 let max_chars = budget.saturating_mul(4);
1333 let mut end = 0;
1334 let mut chars = 0;
1335 for (i, c) in text.char_indices() {
1336 chars += 1;
1337 if chars > max_chars {
1338 break;
1339 }
1340 end = i + c.len_utf8();
1341 }
1342 let min_chars = max_chars / 2;
1345 let mut cut = end;
1346 if let Some(nl) = text[..end].rfind('\n') {
1347 let prefix_chars = text[..nl].chars().count();
1348 if prefix_chars >= min_chars {
1349 cut = nl;
1350 }
1351 }
1352 let mut out = text[..cut].to_string();
1353 out.push_str("\n… (truncated by token budget)");
1354 out
1355}
1356
1357pub fn now_rfc3339() -> String {
1363 chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true)
1364}
1365
1366#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, schemars::JsonSchema)]
1374pub struct SourceRange {
1377 pub path: String,
1378 pub start_line: u32,
1379 pub end_line: u32,
1380}
1381
1382impl SourceRange {
1384pub fn new(path: impl Into<String>, start_line: u32, end_line: u32) -> Self {
1386 SourceRange {
1387 path: path.into(),
1388 start_line,
1389 end_line,
1390 }
1391 }
1392}
1393
1394#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, schemars::JsonSchema)]
1396#[serde(rename_all = "snake_case")]
1397pub enum Visibility {
1400 Public,
1401 Protected,
1402 Private,
1403 Package,
1404}
1405
1406impl Visibility {
1408pub fn as_str(&self) -> &'static str {
1410 match self {
1411 Visibility::Public => "public",
1412 Visibility::Protected => "protected",
1413 Visibility::Private => "private",
1414 Visibility::Package => "package",
1415 }
1416 }
1417}
1418
1419#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, schemars::JsonSchema)]
1421#[serde(rename_all = "snake_case")]
1422pub enum SurfaceKind {
1425 Function,
1426 Method,
1427 Constructor,
1428 Class,
1429 Interface,
1430 Trait,
1431 Type,
1432 Enum,
1433 Const,
1434 Module,
1435 Record,
1436}
1437
1438impl SurfaceKind {
1440pub fn as_str(&self) -> &'static str {
1442 match self {
1443 SurfaceKind::Function => "function",
1444 SurfaceKind::Method => "method",
1445 SurfaceKind::Constructor => "constructor",
1446 SurfaceKind::Class => "class",
1447 SurfaceKind::Interface => "interface",
1448 SurfaceKind::Trait => "trait",
1449 SurfaceKind::Type => "type",
1450 SurfaceKind::Enum => "enum",
1451 SurfaceKind::Const => "const",
1452 SurfaceKind::Module => "module",
1453 SurfaceKind::Record => "record",
1454 }
1455 }
1456}
1457
1458#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
1460pub struct SemanticParameter {
1463 pub name: String,
1464 #[serde(default, skip_serializing_if = "Option::is_none")]
1465 pub ty: Option<String>,
1466 #[serde(default)]
1468 pub receiver: bool,
1469 #[serde(default, skip_serializing_if = "Option::is_none")]
1470 pub default: Option<String>,
1471 #[serde(default)]
1472 pub variadic: bool,
1473}
1474
1475#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize, schemars::JsonSchema)]
1479pub struct SemanticSignature {
1482 pub name: String,
1483 #[serde(default, skip_serializing_if = "Option::is_none")]
1484 pub owner: Option<String>,
1485 #[serde(default)]
1486 pub visibility: Option<Visibility>,
1487 #[serde(default)]
1488 pub async_: bool,
1489 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1490 pub generic_parameters: Vec<String>,
1491 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1492 pub parameters: Vec<SemanticParameter>,
1493 #[serde(default, skip_serializing_if = "Option::is_none")]
1494 pub returns: Option<String>,
1495 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1497 pub constraints: Vec<String>,
1498}
1499
1500#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
1503pub struct SurfaceRank {
1506 pub task_ppr: f64,
1507 pub global_ppr: f64,
1508 pub lexical: f64,
1509 pub semantic: f64,
1510 pub confidence: f64,
1511 pub criticality: f64,
1512 pub change_risk: f64,
1513 pub novelty: f64,
1514 pub total: f64,
1515 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1516 pub reasons: Vec<String>,
1517}
1518
1519impl Default for SurfaceRank {
1521fn default() -> Self {
1524 SurfaceRank {
1525 task_ppr: 0.0,
1526 global_ppr: 0.0,
1527 lexical: 0.0,
1528 semantic: 0.0,
1529 confidence: 0.0,
1530 criticality: 0.0,
1531 change_risk: 0.0,
1532 novelty: 0.0,
1533 total: 0.0,
1534 reasons: Vec::new(),
1535 }
1536 }
1537}
1538
1539#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
1543pub struct SurfaceEntry {
1546 pub id: String,
1547 pub symbol_id: String,
1548 pub qualified_name: String,
1549 pub kind: SurfaceKind,
1550 pub path: String,
1551 pub range: SourceRange,
1552 pub source_signature: String,
1554 pub canonical_signature: String,
1556 pub semantic_signature: SemanticSignature,
1557 pub visibility: Visibility,
1558 #[serde(default)]
1559 pub exported: bool,
1560 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1561 pub modifiers: Vec<String>,
1562 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1563 pub annotations: Vec<String>,
1564 #[serde(default, skip_serializing_if = "Option::is_none")]
1565 pub component: Option<String>,
1566 #[serde(default, skip_serializing_if = "Option::is_none")]
1567 pub subsystem: Option<String>,
1568 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1569 pub flows: Vec<String>,
1570 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1571 pub contracts: Vec<String>,
1572 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1573 pub state_authorities: Vec<String>,
1574 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1575 pub invocation_surfaces: Vec<String>,
1576 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1577 pub callers: Vec<String>,
1578 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1579 pub callees: Vec<String>,
1580 #[serde(default)]
1583 pub caller_count: usize,
1584 #[serde(default)]
1585 pub callee_count: usize,
1586 #[serde(default, skip_serializing_if = "Option::is_none")]
1590 pub importance: Option<ImportanceProfile>,
1591 pub provenance: Provenance,
1592 #[serde(default)]
1593 pub confidence: f32,
1594 #[serde(default)]
1595 pub rank: SurfaceRank,
1596}
1597
1598#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
1606pub struct ImportanceProfile {
1608 pub overall: f64,
1609 pub caller_count: usize,
1610 pub callee_count: usize,
1611 pub global_ppr: f64,
1612 pub task_ppr: f64,
1613 pub entrypoint: bool,
1614 pub exported: bool,
1615 pub flow_count: usize,
1616 pub contract_count: usize,
1617 pub state_read_count: usize,
1618 pub state_write_count: usize,
1619 pub dependent_count: usize,
1620 pub change_risk: f64,
1621 pub badges: Vec<String>,
1622}
1623
1624#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
1625pub struct SurfaceOmission {
1628 pub count: usize,
1629 pub kind: String,
1630 pub reason: String,
1631}
1632
1633#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
1636pub struct SystemSurfaceMap {
1639 pub repository: String,
1640 pub revision: String,
1641 pub epoch: String,
1642 pub entries: Vec<SurfaceEntry>,
1643 #[serde(default)]
1644 pub token_count: usize,
1645 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1646 pub omitted: Vec<SurfaceOmission>,
1647}
1648
1649#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, schemars::JsonSchema)]
1655pub struct SurfaceRenderResult {
1658 pub text: String,
1660 pub rendered_ids: Vec<String>,
1662 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1666 pub rendered_entries: Vec<SurfaceEntry>,
1667 pub omitted_ids: Vec<String>,
1669 pub omissions: Vec<SurfaceOmission>,
1671 pub token_count: usize,
1673 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1678 pub critical_drops: Vec<String>,
1679}
1680
1681#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1687pub struct RankNode {
1690 pub id: String,
1692 pub kind: String,
1694 pub name: String,
1696}
1697
1698#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1702pub struct ReferenceEdge {
1705 pub source_symbol: String,
1706 pub target_symbol: String,
1707 pub kind: ReferenceKind,
1708 #[serde(default, skip_serializing_if = "Vec::is_empty")]
1709 pub locations: Vec<SourceRange>,
1710 pub provenance: Provenance,
1711 #[serde(default)]
1712 pub confidence: f32,
1713}
1714
1715#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize)]
1716#[serde(rename_all = "snake_case")]
1717pub enum ReferenceKind {
1720 Read,
1721 Write,
1722 #[default]
1723 Call,
1724 TypeUse,
1725 Instantiate,
1726 Implement,
1727 Extend,
1728 Decorate,
1729 Register,
1730 Import,
1731 Export,
1732 Macro,
1733 FieldAccess,
1734 Construct,
1735}
1736
1737impl ReferenceKind {
1739pub fn as_str(&self) -> &'static str {
1741 match self {
1742 ReferenceKind::Read => "read",
1743 ReferenceKind::Write => "write",
1744 ReferenceKind::Call => "call",
1745 ReferenceKind::TypeUse => "type_use",
1746 ReferenceKind::Instantiate => "instantiate",
1747 ReferenceKind::Implement => "implement",
1748 ReferenceKind::Extend => "extend",
1749 ReferenceKind::Decorate => "decorate",
1750 ReferenceKind::Register => "register",
1751 ReferenceKind::Import => "import",
1752 ReferenceKind::Export => "export",
1753 ReferenceKind::Macro => "macro",
1754 ReferenceKind::FieldAccess => "field_access",
1755 ReferenceKind::Construct => "construct",
1756 }
1757 }
1758}
1759
1760#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1762pub struct ContextItem {
1765 pub id: String,
1766 pub value: f64,
1767 pub token_cost: usize,
1768 #[serde(default)]
1769 pub required: bool,
1770 #[serde(default, skip_serializing_if = "Option::is_none")]
1771 pub group: Option<String>,
1772}
1773
1774#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1776pub struct ContextBudget {
1778 pub total: usize,
1779 pub atlas: usize,
1780 pub surface: usize,
1781 pub task_delta: usize,
1782 pub structural_source: usize,
1783}
1784
1785impl Default for ContextBudget {
1787fn default() -> Self {
1790 ContextBudget {
1791 total: 20_000,
1792 atlas: 13_000,
1793 surface: 7_000,
1794 task_delta: 3_000,
1795 structural_source: 6_000,
1796 }
1797 }
1798}
1799
1800const MASSIVE_SURFACE_CAP: usize = 10_000;
1803
1804impl ContextBudget {
1806 pub fn adaptive(
1832 total: usize,
1833 entity_count: usize,
1834 component_count: usize,
1835 flow_count: usize,
1836 surface_candidates: usize,
1837 ) -> ContextBudget {
1838 let (surface_pct, boost, cap): (f64, f64, Option<usize>) = if entity_count > 20_000 {
1839 (30.0, 0.0, Some(MASSIVE_SURFACE_CAP))
1840 } else if entity_count > 5_000 || component_count > 30 {
1841 (35.0, 5.0, None)
1842 } else if flow_count > 40 {
1843 (35.0, 5.0, None)
1847 } else if entity_count < 200 {
1848 (45.0, 5.0, None)
1849 } else {
1850 (40.0, 5.0, None)
1851 };
1852 let boost_pp = (surface_candidates / 2_000).min(boost as usize) as f64;
1853 let surface_pct = (surface_pct + boost_pp).min(50.0);
1854 let atlas_pct = 100.0 - surface_pct;
1855 let mut surface = ((total as f64) * surface_pct / 100.0).round() as usize;
1856 if let Some(c) = cap {
1857 surface = surface.min(c);
1858 }
1859 let atlas = ((total as f64) * atlas_pct / 100.0).round() as usize;
1860 let def = ContextBudget::default();
1861 ContextBudget {
1862 total,
1863 atlas,
1864 surface,
1865 task_delta: def.task_delta,
1866 structural_source: def.structural_source,
1867 }
1868 }
1869}
1870
1871#[derive(Debug, Clone, Default, Serialize, Deserialize)]
1874pub struct ContextLedger {
1877 pub model_epoch: String,
1878 #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
1879 pub visible_entities: BTreeSet<String>,
1880 #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
1881 pub visible_symbols: BTreeSet<String>,
1882 #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
1883 pub visible_files: BTreeSet<String>,
1884 #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
1885 pub visible_components: BTreeSet<String>,
1886 #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
1887 pub visible_flows: BTreeSet<String>,
1888 #[serde(default, skip_serializing_if = "Option::is_none")]
1889 pub last_task: Option<String>,
1890}
1891
1892#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1895pub struct StructuralSourceUnit {
1898 pub path: String,
1899 pub source: String,
1901 pub representation: String,
1902 pub revision: String,
1903 pub content: String,
1904 #[serde(default, skip_serializing_if = "String::is_empty")]
1907 pub handle: String,
1908 #[serde(default, skip_serializing_if = "String::is_empty")]
1911 pub representation_reason: String,
1912}
1913
1914#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1917pub struct ContextArtifact {
1920 pub kind: String,
1921 pub epoch: String,
1922 pub renderer_version: String,
1923 pub trust_policy: String,
1924 pub budget: ContextBudget,
1925 pub sha256: String,
1929 #[serde(default, skip_serializing_if = "String::is_empty")]
1933 pub content_hash: String,
1934 pub text: String,
1935}
1936
1937#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1939pub struct TaskSeed {
1942 pub kind: String,
1943 pub id: String,
1944 pub weight: f64,
1945}
1946
1947#[cfg(test)]
1948mod tests {
1949 use super::*;
1950 #[test]
1951 fn estimate_tokens_is_chars_ceiling() {
1953 assert_eq!(estimate_tokens(""), 0);
1956 assert_eq!(estimate_tokens("a"), 1);
1957 assert_eq!(estimate_tokens("abcd"), 1);
1958 assert_eq!(estimate_tokens("abcde"), 2);
1959 assert_eq!(estimate_tokens("fn main() { println!(\"hi\"); }"), 8);
1960 assert_eq!(estimate_tokens("日本語"), 1);
1962 assert_eq!(estimate_tokens("日本語テスト"), 2);
1963 let path = "crates/scc-context/src/surface/structural_source_selection_policy.rs";
1965 assert_eq!(estimate_tokens(path), path.chars().count().div_ceil(4));
1966 }
1967
1968 #[test]
1969fn component_encode_decode_roundtrip() {
1972 for name in [
1973 "Normalizer",
1974 "GET /api/transcripts/:id handler",
1975 "OrderStateMachine.advance",
1976 "test_normalization_preserves_raw",
1977 "src/app.ts",
1978 "a b%c",
1979 ] {
1980 assert_eq!(decode_component(&encode_component(name)), name, "{name}");
1981 }
1982 }
1983
1984 #[test]
1985fn component_ids_are_collision_free() {
1988 assert_ne!(
1989 encode_component("foo_bar"),
1990 encode_component("foo-bar"),
1991 "underscore and dash must not collide"
1992 );
1993 }
1994
1995#[test]
1998fn occurrence_ids_are_collision_free_per_site() {
2001 assert_ne!(
2003 occurrence_id("r", "expr", "a.ts", "A", 1),
2004 occurrence_id("r", "expr", "b.ts", "A", 1)
2005 );
2006 assert_ne!(
2007 occurrence_id("r", "expr", "a.ts", "A", 1),
2008 occurrence_id("r", "expr", "a.ts", "B", 1)
2009 );
2010 assert_ne!(
2011 occurrence_id("r", "expr", "a.ts", "A", 1),
2012 occurrence_id("r", "expr", "a.ts", "A", 2)
2013 );
2014 assert_ne!(
2015 occurrence_id("r", "expr1", "a.ts", "A", 1),
2016 occurrence_id("r", "expr2", "a.ts", "A", 1)
2017 );
2018 assert_ne!(
2020 occurrence_id("r", "expr", "a_b.ts", "A", 1),
2021 occurrence_id("r", "expr", "a-b.ts", "A", 1)
2022 );
2023 assert_ne!(
2024 occurrence_id("r", "expr", "a.ts", "Foo", 1),
2025 occurrence_id("r", "expr", "a.ts", "foo", 1)
2026 );
2027 assert_eq!(
2029 occurrence_id("r", "expr", "a.ts", "A", 1),
2030 occurrence_id("r", "expr", "a.ts", "A", 1)
2031 );
2032 }
2033
2034#[test]
2037fn occurrence_roundtrips_through_serde() {
2040 let o = Occurrence {
2041 id: occurrence_id("r", "z.object({ x: z.string() })", "src/a.ts", "make", 7),
2042 concept: entity_id("r", kinds::SCHEMA, "z.object({ x: z.string() })"),
2043 path: "src/a.ts".into(),
2044 owner: "make".into(),
2045 line: 7,
2046 };
2047 let json = serde_json::to_string(&o).unwrap();
2048 let back: Occurrence = serde_json::from_str(&json).unwrap();
2049 assert_eq!(back.id, o.id);
2050 assert_eq!(back.concept, o.concept);
2051 assert_eq!(back.line, 7);
2052 }
2053
2054#[test]
2057fn adaptive_budget_scales_split_by_repo_complexity() {
2060 let tiny = ContextBudget::adaptive(20_000, 50, 2, 1, 10);
2063 let tiny_ratio = tiny.surface as f64 / tiny.total as f64;
2064 assert!(
2065 (tiny_ratio - 0.45).abs() <= 0.01,
2066 "tiny: surface share {tiny_ratio}"
2067 );
2068 assert_eq!(tiny.total, 20_000);
2069 assert!(tiny.atlas + tiny.surface <= 20_000);
2070
2071 let normal = ContextBudget::adaptive(20_000, 1_000, 5, 4, 10);
2073 let normal_ratio = normal.surface as f64 / normal.total as f64;
2074 assert!(
2075 (normal_ratio - 0.40).abs() <= 0.01,
2076 "normal: surface share {normal_ratio}"
2077 );
2078
2079 let large = ContextBudget::adaptive(20_000, 1_000, 40, 4, 10);
2081 let large_ratio = large.surface as f64 / large.total as f64;
2082 assert!(
2083 (large_ratio - 0.35).abs() <= 0.01,
2084 "large: surface share {large_ratio}"
2085 );
2086
2087 let massive = ContextBudget::adaptive(20_000, 25_000, 60, 30, 10);
2089 assert!(massive.atlas > massive.surface);
2090 let massive_ratio = massive.surface as f64 / massive.total as f64;
2091 assert!(
2092 (massive_ratio - 0.30).abs() <= 0.01,
2093 "massive: surface share {massive_ratio}"
2094 );
2095 let massive_huge = ContextBudget::adaptive(100_000, 25_000, 60, 30, 10);
2097 assert_eq!(
2098 massive_huge.surface, MASSIVE_SURFACE_CAP,
2099 "massive surface absolutely capped"
2100 );
2101
2102 assert_ne!(
2104 tiny.atlas as f64 / tiny.surface as f64,
2105 large.atlas as f64 / large.surface as f64,
2106 "tiny vs large splits must differ"
2107 );
2108 }
2109
2110 #[test]
2111fn adaptive_budget_candidate_pool_boosts_surface_share() {
2114 let boosted = ContextBudget::adaptive(20_000, 1_000, 5, 4, 10_000);
2116 let boosted_ratio = boosted.surface as f64 / boosted.total as f64;
2117 assert!(
2118 (boosted_ratio - 0.45).abs() <= 0.01,
2119 "boosted: surface share {boosted_ratio}"
2120 );
2121 let huge = ContextBudget::adaptive(20_000, 100, 2, 1, 100_000);
2123 assert!(huge.surface <= huge.total / 2, "surface capped at 50%");
2124 }
2125
2126 #[test]
2127fn adaptive_budget_is_deterministic_and_total_preserving() {
2130 let a = ContextBudget::adaptive(15_000, 3_000, 8, 6, 500);
2131 let b = ContextBudget::adaptive(15_000, 3_000, 8, 6, 500);
2132 assert_eq!(a, b);
2133 assert_eq!(a.total, 15_000);
2134 assert!(a.atlas + a.surface <= 15_000);
2135 }
2136
2137 #[test]
2138fn contract_kind_renders_as_operation_lines() {
2141 let mut c = Contract::new(
2142 "repo://repo/contract/http/get--api-x",
2143 "http",
2144 "repo://repo/symbol/main.py/handler",
2145 );
2146 c.operations.push("GET /api/x".into());
2147 c.consumers.push("repo://repo/symbol/main.py/handler".into());
2148 let lines: Vec<String> = c
2149 .operations
2150 .iter()
2151 .map(|op| format!("{}: {}", c.kind, op))
2152 .collect();
2153 assert_eq!(lines, vec!["http: GET /api/x"]);
2154 }
2155
2156 #[test]
2157fn contract_subclass_ontology_maps_and_renders() {
2160 assert_eq!(ContractSubclass::Http.as_str(), "http");
2162 assert_eq!(ContractSubclass::Cli.as_str(), "cli");
2163 assert_eq!(ContractSubclass::Event.as_str(), "event");
2164 assert_eq!(ContractSubclass::Configuration.as_str(), "config");
2165 assert_eq!(ContractSubclass::PublicApi.as_str(), "public-api");
2166 assert_eq!(ContractSubclass::Extension.as_str(), "extension");
2167 assert_eq!(ContractSubclass::Serialization.as_str(), "serialization");
2168 assert_eq!(ContractSubclass::CallContract.as_str(), "call");
2169 assert_eq!(ContractSubclass::Rpc.as_str(), "rpc");
2170 assert_eq!(ContractSubclass::Message.as_str(), "message");
2171 assert_eq!(ContractSubclass::Schema.as_str(), "schema");
2172 assert_eq!(ContractSubclass::Plugin.as_str(), "plugin");
2173
2174 assert_eq!(ContractSubclass::from_kind_str("http"), Some(ContractSubclass::Http));
2177 assert_eq!(ContractSubclass::from_kind_str("route"), Some(ContractSubclass::Http));
2178 assert_eq!(ContractSubclass::from_kind_str("cli"), Some(ContractSubclass::Cli));
2179 assert_eq!(ContractSubclass::from_kind_str("event"), Some(ContractSubclass::Event));
2180 assert_eq!(
2181 ContractSubclass::from_kind_str("config"),
2182 Some(ContractSubclass::Configuration)
2183 );
2184 assert_eq!(
2185 ContractSubclass::from_kind_str("next-config"),
2186 Some(ContractSubclass::Configuration)
2187 );
2188 assert_eq!(
2190 ContractSubclass::from_kind_str("builder"),
2191 Some(ContractSubclass::Configuration)
2192 );
2193 assert_eq!(
2194 ContractSubclass::from_kind_str("factory"),
2195 Some(ContractSubclass::PublicApi)
2196 );
2197 assert_eq!(
2198 ContractSubclass::from_kind_str("serialization"),
2199 Some(ContractSubclass::Serialization)
2200 );
2201 assert_eq!(
2202 ContractSubclass::from_kind_str("extension"),
2203 Some(ContractSubclass::Extension)
2204 );
2205 assert_eq!(ContractSubclass::from_kind_str("plugin"), Some(ContractSubclass::Plugin));
2206 assert_eq!(ContractSubclass::from_kind_str("rpc"), Some(ContractSubclass::Rpc));
2207 assert_eq!(ContractSubclass::from_kind_str("message"), Some(ContractSubclass::Message));
2208 assert_eq!(ContractSubclass::from_kind_str("schema"), Some(ContractSubclass::Schema));
2209 assert_eq!(ContractSubclass::from_kind_str("task"), Some(ContractSubclass::CallContract));
2210 assert_eq!(ContractSubclass::from_kind_str("include_router"), None);
2212 assert_eq!(ContractSubclass::from_kind_str("add_middleware"), None);
2213
2214 let mut c = Contract::new(
2217 "repo://repo/contract/http/get--api-x",
2218 "http",
2219 "repo://repo/symbol/main.py/handler",
2220 );
2221 assert_eq!(c.subclass, ContractSubclass::Http);
2222 c.subclass = ContractSubclass::Serialization;
2223 let json = serde_json::to_string(&c).unwrap();
2224 let back: Contract = serde_json::from_str(&json).unwrap();
2225 assert_eq!(back.subclass, ContractSubclass::Serialization);
2226 let legacy = serde_json::json!({
2227 "id": "repo://repo/contract/x",
2228 "kind": "http",
2229 "producer": "p",
2230 "operations": ["GET /x"],
2231 "evidence": [],
2232 "consumers": [],
2233 });
2234 let c2: Contract = serde_json::from_value(legacy).unwrap();
2235 assert_eq!(c2.subclass, ContractSubclass::Http);
2236 }
2237
2238 #[test]
2239fn invocation_surface_kinds_stringify() {
2242 assert_eq!(InvocationSurfaceKind::PublicApi.as_str(), "public_api");
2243 assert_eq!(InvocationSurfaceKind::Queue.as_str(), "queue");
2244 assert_eq!(InvocationSurfaceKind::Lifecycle.as_str(), "lifecycle");
2245 assert_eq!(InvocationSurfaceKind::FrameworkCallback.as_str(), "framework_callback");
2246 let json = serde_json::to_string(&InvocationSurfaceKind::PublicApi).unwrap();
2248 assert_eq!(json, "\"public_api\"");
2249 let back: InvocationSurfaceKind = serde_json::from_str(&json).unwrap();
2250 assert_eq!(back, InvocationSurfaceKind::PublicApi);
2251 }
2252
2253 #[test]
2254fn surface_render_result_roundtrips_through_serde() {
2257 let r = SurfaceRenderResult {
2258 text: "SCC SYSTEM SURFACE MAP\n\n function serve\n".into(),
2259 rendered_ids: vec!["repo://r/symbol/api.py/serve".into()],
2260 rendered_entries: vec![],
2261 omitted_ids: vec!["repo://r/symbol/api.py/internal".into()],
2262 omissions: vec![SurfaceOmission {
2263 count: 1,
2264 kind: "function".into(),
2265 reason: "token budget".into(),
2266 }],
2267 token_count: 7,
2268 critical_drops: vec![],
2269 };
2270 let json = serde_json::to_string(&r).unwrap();
2271 let back: SurfaceRenderResult = serde_json::from_str(&json).unwrap();
2272 assert_eq!(back, r);
2273 assert_eq!(back.rendered_ids[0], "repo://r/symbol/api.py/serve");
2274 assert_eq!(back.omissions[0].count, 1);
2275 }
2276
2277 #[test]
2278fn rank_node_carries_kind_and_name() {
2281 let n = RankNode {
2282 id: "repo://r/contract/c1".into(),
2283 kind: kinds::CONTRACT.into(),
2284 name: "c1".into(),
2285 };
2286 let json = serde_json::to_string(&n).unwrap();
2287 let back: RankNode = serde_json::from_str(&json).unwrap();
2288 assert_eq!(back.kind, "contract");
2289 assert_eq!(back.name, "c1");
2290 }
2291
2292 #[test]
2293fn context_artifact_content_hash_roundtrips_and_defaults() {
2296 let a = ContextArtifact {
2298 kind: "startup".into(),
2299 epoch: "e1".into(),
2300 renderer_version: "0.1.0".into(),
2301 trust_policy: "floor=0.85".into(),
2302 budget: ContextBudget::default(),
2303 sha256: "abc".into(),
2304 content_hash: "def".into(),
2305 text: "body".into(),
2306 };
2307 let json = serde_json::to_string(&a).unwrap();
2308 let back: ContextArtifact = serde_json::from_str(&json).unwrap();
2309 assert_eq!(back.content_hash, "def");
2310
2311 let legacy = serde_json::json!({
2313 "kind": "startup",
2314 "epoch": "e1",
2315 "renderer_version": "0.1.0",
2316 "trust_policy": "floor=0.85",
2317 "budget": ContextBudget::default(),
2318 "sha256": "abc",
2319 "text": "body",
2320 });
2321 let c: ContextArtifact = serde_json::from_value(legacy).unwrap();
2322 assert_eq!(c.content_hash, "");
2323 }
2324
2325 #[test]
2326 fn predicate_and_kind_registries_are_complete() {
2328 let (kind_ids, predicate_ids) = ontology_registries();
2331 for required in [
2332 predicates::DEFINES,
2333 predicates::COMPOSES,
2334 predicates::EXPORTS,
2335 predicates::ANNOTATES,
2336 predicates::REGISTERS,
2337 predicates::INJECTS,
2338 predicates::HANDLES_CALLBACK,
2339 predicates::DECORATES,
2340 predicates::OCCURS,
2341 ] {
2342 assert!(
2343 predicate_ids.contains(&required),
2344 "predicates::ALL missing {required}"
2345 );
2346 }
2347 for required in [kinds::FIELD, kinds::SCHEMA, kinds::TRUST_BOUNDARY, kinds::OCCURRENCE]
2348 {
2349 assert!(kind_ids.contains(&required), "kinds::ALL missing {required}");
2350 }
2351 let mut seen = std::collections::BTreeSet::new();
2352 for p in predicate_ids {
2353 assert!(seen.insert(*p), "duplicate predicate in ALL: {p}");
2354 }
2355 }
2356}