use schemars::JsonSchema;
use semver::Version;
use serde::{Deserialize, Serialize};
macro_rules! string_id {
($name:ident, $description:literal) => {
#[doc = $description]
#[derive(
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
)]
#[serde(transparent)]
pub struct $name(pub String);
impl $name {
#[must_use]
pub fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
};
}
string_id!(RepoId, "Stable identifier for a registered repository.");
string_id!(
CheckoutId,
"Stable identifier for one repository checkout or linked worktree."
);
string_id!(
WorkspaceId,
"Stable identifier for a `Code System Graph` workspace."
);
string_id!(NodeId, "Stable identifier for a federated graph node.");
string_id!(EdgeId, "Stable identifier for a federated graph edge.");
string_id!(EvidenceId, "Stable identifier for an evidence record.");
string_id!(
CommunityId,
"Stable identifier for a detected graph community."
);
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
)]
#[serde(rename_all = "snake_case")]
pub enum NativePathEncoding {
UnixBytes,
WindowsWide,
Utf8,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema)]
pub struct NativePath {
pub encoding: NativePathEncoding,
pub bytes: Vec<u8>,
pub display: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct RepositoryRecord {
pub id: RepoId,
pub checkout_id: CheckoutId,
pub alias: String,
pub canonical_path: NativePath,
pub git_common_dir: Option<NativePath>,
pub normalized_remote: Option<String>,
pub head_commit: Option<String>,
pub is_linked_worktree: bool,
pub working_tree_dirty: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct WorkspaceRecord {
pub id: WorkspaceId,
pub name: String,
pub manifest_hash: String,
pub config_path: Option<NativePath>,
pub repositories: Vec<RepositoryRecord>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum RepoFreshnessState {
Fresh,
WorkingTreeChanged,
CommitsBehind,
ConfigChanged,
ExtractorChanged,
CodegraphPending,
Partial,
Corrupt,
Unknown,
Unavailable,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct RepoFreshness {
pub repo_id: RepoId,
pub checkout_id: CheckoutId,
pub head_commit: Option<String>,
pub manifest_hash: String,
pub state: RepoFreshnessState,
pub reason: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ArtifactFingerprint {
pub repo_id: RepoId,
pub checkout_id: CheckoutId,
pub path: NativePath,
pub extractor: String,
pub content_hash: String,
pub size_bytes: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ArtifactChangeKind {
Added,
Modified,
Deleted,
Unchanged,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ArtifactChange {
pub repo_id: RepoId,
pub checkout_id: CheckoutId,
pub path: NativePath,
pub extractor: String,
pub kind: ArtifactChangeKind,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExtractorRunStatus {
Success,
SkippedUnchanged,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct ExtractorRun {
pub id: String,
pub snapshot_id: String,
pub repo_id: RepoId,
pub checkout_id: CheckoutId,
pub extractor: String,
pub extractor_version: String,
pub status: ExtractorRunStatus,
pub discovered_files: u64,
pub parsed_files: u64,
pub skipped_files: u64,
pub elapsed_ms: u64,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct StoredExtractorBatch {
pub source: ArtifactFingerprint,
pub extractor_version: String,
pub budget_fingerprint: String,
pub source_was_lossy: bool,
pub output_count: u64,
pub payload: Vec<u8>,
}
#[must_use]
pub fn stable_id(namespace: &str, canonical_key: &str) -> String {
stable_id_bytes(namespace, canonical_key.as_bytes())
}
#[must_use]
pub fn stable_id_bytes(namespace: &str, canonical_key: &[u8]) -> String {
let mut hasher = blake3::Hasher::new();
hasher.update(namespace.as_bytes());
hasher.update(&[0]);
hasher.update(canonical_key);
format!("{namespace}:{}", hasher.finalize().to_hex())
}
#[must_use]
pub fn contains_unsafe_metadata_characters(value: &str) -> bool {
value.chars().any(|character| {
character.is_control()
|| matches!(
character,
'\u{061c}'
| '\u{200e}'
| '\u{200f}'
| '\u{202a}'..='\u{202e}'
| '\u{2066}'..='\u{2069}'
| '\u{feff}'
)
})
}
pub fn validate_safe_path_display(value: &str) -> Result<(), UnsafePathDisplayError> {
if contains_unsafe_metadata_characters(value) {
Err(UnsafePathDisplayError)
} else {
Ok(())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UnsafePathDisplayError;
impl std::fmt::Display for UnsafePathDisplayError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("path display contains unsafe control or bidirectional characters")
}
}
impl std::error::Error for UnsafePathDisplayError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum NodeKind {
Repository,
Service,
Package,
Artifact,
SymbolRef,
TestCase,
HttpOperation,
GraphqlOperation,
RpcMethod,
EventChannel,
EventSchema,
Database,
DatabaseTable,
DatabaseColumn,
ConfigKey,
Deployment,
Document,
Adr,
Owner,
ChangeSet,
PullRequest,
Community,
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
)]
#[serde(rename_all = "snake_case")]
pub enum EdgeKind {
Contains,
Provides,
Consumes,
CallsRemote,
Publishes,
Subscribes,
DeliversTo,
DependsOnPackage,
DependsOnRepository,
ReadsTable,
WritesTable,
Deploys,
Configures,
Documents,
OwnedBy,
ImplementedBy,
Validates,
ChangedIn,
Affects,
Precedes,
Reverts,
CompatibleWith,
IncompatibleWith,
MemberOf,
ManualLink,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum Provenance {
Declared,
Extracted,
Inferred,
Manual,
Runtime,
CodeGraph,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum EpistemicStatus {
Confirmed,
Inferred,
Ambiguous,
Stale,
Incomplete,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct Evidence {
pub id: EvidenceId,
pub repo_id: Option<RepoId>,
pub file_path: Option<String>,
pub start_line: Option<u32>,
pub end_line: Option<u32>,
pub extractor: String,
pub extractor_version: String,
pub provenance: Provenance,
pub confidence: f32,
pub observed_at_commit: Option<String>,
pub content_hash: Option<String>,
pub note: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct Node {
pub id: NodeId,
pub kind: NodeKind,
pub repo_id: Option<RepoId>,
pub stable_key: String,
pub label: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct Edge {
pub id: EdgeId,
pub source: NodeId,
pub target: NodeId,
pub kind: EdgeKind,
pub confidence: f32,
pub status: EpistemicStatus,
pub evidence: Vec<EvidenceId>,
}
#[derive(
Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, JsonSchema,
)]
#[serde(rename_all = "snake_case")]
pub enum LinkStatus {
Confirmed,
Suppressed,
Ambiguous,
Rejected,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct RejectedAlternative {
pub candidate: NodeId,
pub score: f32,
pub reasons: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct EvidenceRef {
pub id: EvidenceId,
pub provenance: Provenance,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub struct LinkDecision {
pub source: NodeId,
pub target: NodeId,
pub relation: EdgeKind,
pub matcher: String,
#[schemars(with = "String")]
pub matcher_version: Version,
pub score: f32,
pub confidence: f32,
pub reasons: Vec<String>,
pub rejected_alternatives: Vec<RejectedAlternative>,
pub evidence: Vec<EvidenceRef>,
pub status: LinkStatus,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum OverallFreshness {
Fresh,
Stale,
Partial,
Unknown,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct FreshnessSummary {
pub overall: OverallFreshness,
pub stale_repositories: Vec<RepoId>,
pub reasons: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ToolStatus {
Ok,
Degraded,
Error,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct TraceSegment {
pub source: Node,
pub edge: Edge,
pub target: Node,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct TraceReport {
pub segments: Vec<TraceSegment>,
pub truncated: bool,
pub coverage_gaps: Vec<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum CommunityAlgorithm {
ConnectedComponents,
WeightedClustering,
Louvain,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case", tag = "kind", content = "value")]
pub enum CommunityScope {
Federated,
Repository(RepoId),
Service(String),
Workspace,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct CommunityEdgeWeight {
pub kind: EdgeKind,
pub weight: f64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct CommunityConfig {
pub algorithm: CommunityAlgorithm,
pub scope: CommunityScope,
pub seed: u64,
pub resolution: f64,
pub minimum_confidence: f32,
pub edge_weights: Vec<CommunityEdgeWeight>,
pub max_iterations: u32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct CommunityMetrics {
pub size: usize,
pub density: f64,
pub cohesion: f64,
pub coupling: f64,
pub cross_community_edges: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct CommunityLabelEvidence {
pub node_id: NodeId,
pub term: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct Community {
pub id: CommunityId,
pub label: String,
pub members: Vec<NodeId>,
pub central_nodes: Vec<NodeId>,
pub repositories: Vec<RepoId>,
pub services: Vec<NodeId>,
pub inbound_contracts: Vec<NodeId>,
pub outbound_contracts: Vec<NodeId>,
pub metrics: CommunityMetrics,
pub label_evidence: Vec<CommunityLabelEvidence>,
pub limitations: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct CommunitySnapshot {
pub snapshot_id: String,
pub engine_version: String,
pub config: CommunityConfig,
pub communities: Vec<Community>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum CommunityChangeKind {
Created,
Removed,
Split,
Merged,
MateriallyChanged,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct CommunityChange {
pub kind: CommunityChangeKind,
pub before: Vec<CommunityId>,
pub after: Vec<CommunityId>,
pub overlap: f64,
pub explanation: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct CommunityDelta {
pub before_snapshot_id: String,
pub after_snapshot_id: String,
pub changes: Vec<CommunityChange>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub struct ToolEnvelope<T> {
pub schema_version: u32,
pub status: ToolStatus,
pub data: Option<T>,
pub freshness: FreshnessSummary,
pub warnings: Vec<String>,
}
#[cfg(test)]
mod tests {
use super::{contains_unsafe_metadata_characters, stable_id};
#[test]
fn stable_id_should_repeat_for_identical_input() {
let first = stable_id("repo", "ssh://example.test/team/api");
let second = stable_id("repo", "ssh://example.test/team/api");
assert_eq!(first, second);
}
#[test]
fn stable_id_should_separate_namespaces() {
let repository = stable_id("repo", "shared-key");
let service = stable_id("service", "shared-key");
assert_ne!(repository, service);
}
#[test]
fn unsafe_metadata_should_detect_terminal_and_bidi_controls() {
assert!(contains_unsafe_metadata_characters(
"trusted\u{202e}txt.exe"
));
assert!(contains_unsafe_metadata_characters("line\nbreak"));
assert!(!contains_unsafe_metadata_characters(
"servicio-áccounts_日本"
));
}
}