use std::collections::{BTreeMap, BTreeSet};
use std::fs::OpenOptions;
use std::io::Write as _;
use std::path::{Path, PathBuf};
use std::time::{SystemTime, UNIX_EPOCH};
use code_system_graph_core::{
ChangeImpactReport, ContractAction, ContractReport, ContractRequest, ImpactReport, ImpactRequest, LocalContextResult, ManualLinkConfig, PullRequestInspection, SearchReport, inspect_contracts, public_schema_catalog
};
use code_system_graph_model::{
Community, CommunityId, Evidence, FreshnessSummary, Node, NodeId, NodeKind, OverallFreshness, RepoFreshness, RepoFreshnessState, RepositoryRecord, ToolEnvelope, ToolStatus, TraceReport
};
use code_system_graph_store_sqlite::{SqliteStore, StoreError};
use schemars::{JsonSchema, schema_for};
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use crate::{
ChangesInput, CommunityInput, CommunityReport, ExploreInput, ManifestMutationSummary, PullRequestInput, ScanSummary, SearchInput, TraceInput
};
pub(super) const ADMIN_TOOL_NAMES: [&str; 5] = [
"scan",
"update_workspace",
"write_manual_link",
"clean_cache",
"recompute_communities",
];
pub(super) const JSON_MIME_TYPE: &str = "application/json";
const MAX_PAGE_SIZE: usize = 100;
const MAX_RESOURCE_BYTES: usize = 256 * 1024;
const MAX_SCHEMA_RESOURCE_BYTES: usize = 2 * 1024 * 1024;
#[derive(Debug, Clone, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub(super) struct WorkspaceInput {
pub workspace: String,
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub(super) struct WorkspaceUpdateInput {
pub workspace: String,
#[serde(flatten)]
operation: WorkspaceUpdateOperation,
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
#[serde(tag = "action", rename_all = "snake_case", deny_unknown_fields)]
enum WorkspaceUpdateOperation {
AddRepository {
alias: String,
repository_path: String,
},
RemoveRepository {
alias: String,
},
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub(super) struct ManualLinkWriteInput {
pub workspace: String,
#[serde(flatten)]
operation: ManualLinkWriteOperation,
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
#[serde(tag = "action", rename_all = "snake_case", deny_unknown_fields)]
enum ManualLinkWriteOperation {
Add {
from: String,
to: String,
relation: code_system_graph_model::EdgeKind,
#[serde(default)]
contract: Option<String>,
reason: String,
},
Suppress {
from: String,
to: String,
relation: code_system_graph_model::EdgeKind,
#[serde(default)]
contract: Option<String>,
reason: String,
},
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub(super) struct CacheCleanInput {
pub workspace: String,
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub(super) struct ContractsInput {
pub workspace: String,
#[serde(flatten)]
operation: ContractsOperation,
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
#[serde(tag = "action", rename_all = "snake_case", deny_unknown_fields)]
enum ContractsOperation {
List {
#[serde(default = "default_page_size")]
#[schemars(range(min = 1, max = 100))]
limit: usize,
},
Show {
contract: NodeId,
},
ValidateAll {
#[serde(default = "default_page_size")]
#[schemars(range(min = 1, max = 100))]
limit: usize,
},
ValidateOne {
contract: NodeId,
},
Diff {
contract: NodeId,
related_contract: NodeId,
},
ExplainLink {
contract: NodeId,
related_contract: NodeId,
},
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
pub(super) struct CommunitiesInput {
pub workspace: String,
#[serde(flatten)]
operation: CommunitiesOperation,
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
#[serde(tag = "action", rename_all = "snake_case", deny_unknown_fields)]
enum CommunitiesOperation {
List {
#[serde(default)]
offset: usize,
#[serde(default = "default_page_size")]
#[schemars(range(min = 1, max = 100))]
limit: usize,
},
Show {
community_id: CommunityId,
},
Compare {
snapshot_id: String,
#[serde(default)]
offset: usize,
#[serde(default = "default_page_size")]
#[schemars(range(min = 1, max = 100))]
limit: usize,
},
}
impl WorkspaceUpdateInput {
pub(super) fn workspace(&self) -> &str {
&self.workspace
}
pub(super) fn alias(&self) -> &str {
match &self.operation {
WorkspaceUpdateOperation::AddRepository { alias, .. }
| WorkspaceUpdateOperation::RemoveRepository { alias } => alias,
}
}
pub(super) fn repository_path(&self) -> Option<&str> {
match &self.operation {
WorkspaceUpdateOperation::AddRepository {
repository_path, ..
} => Some(repository_path),
WorkspaceUpdateOperation::RemoveRepository { .. } => None,
}
}
}
impl ManualLinkWriteInput {
pub(super) fn workspace(&self) -> &str {
&self.workspace
}
pub(super) fn declaration(&self) -> ManualLinkConfig {
let (from, to, relation, contract, reason, suppress) = match &self.operation {
ManualLinkWriteOperation::Add {
from,
to,
relation,
contract,
reason,
..
} => (from, to, relation, contract, reason, false),
ManualLinkWriteOperation::Suppress {
from,
to,
relation,
contract,
reason,
..
} => (from, to, relation, contract, reason, true),
};
ManualLinkConfig {
from: from.clone(),
to: to.clone(),
relation: *relation,
contract: contract.clone(),
reason: reason.clone(),
suppress,
}
}
}
impl ContractsInput {
pub(super) fn workspace(&self) -> &str {
&self.workspace
}
fn request(&self) -> ContractRequest {
match &self.operation {
ContractsOperation::List { limit } => ContractRequest {
action: ContractAction::List,
limit: *limit,
..ContractRequest::default()
},
ContractsOperation::Show { contract } => ContractRequest {
action: ContractAction::Show,
contract: Some(contract.clone()),
..ContractRequest::default()
},
ContractsOperation::ValidateAll { limit } => ContractRequest {
action: ContractAction::Validate,
limit: *limit,
..ContractRequest::default()
},
ContractsOperation::ValidateOne { contract } => ContractRequest {
action: ContractAction::Validate,
contract: Some(contract.clone()),
..ContractRequest::default()
},
ContractsOperation::Diff {
contract,
related_contract,
} => ContractRequest {
action: ContractAction::Diff,
contract: Some(contract.clone()),
related_contract: Some(related_contract.clone()),
..ContractRequest::default()
},
ContractsOperation::ExplainLink {
contract,
related_contract,
} => ContractRequest {
action: ContractAction::ExplainLink,
contract: Some(contract.clone()),
related_contract: Some(related_contract.clone()),
..ContractRequest::default()
},
}
}
}
impl CommunitiesInput {
pub(super) fn workspace(&self) -> &str {
&self.workspace
}
pub(super) fn application_input(&self) -> CommunityInput {
match &self.operation {
CommunitiesOperation::List { offset, limit } => CommunityInput {
community_id: None,
compare_snapshot_id: None,
offset: *offset,
limit: *limit,
},
CommunitiesOperation::Show { community_id } => CommunityInput {
community_id: Some(community_id.clone()),
compare_snapshot_id: None,
offset: 0,
limit: 1,
},
CommunitiesOperation::Compare {
snapshot_id,
offset,
limit,
} => CommunityInput {
community_id: None,
compare_snapshot_id: Some(snapshot_id.clone()),
offset: *offset,
limit: *limit,
},
}
}
}
#[derive(Debug, Clone, Deserialize, JsonSchema)]
#[serde(deny_unknown_fields)]
pub(super) struct SourceContextInput {
pub workspace: String,
pub node_id: NodeId,
#[serde(default = "default_evidence_limit")]
#[schemars(range(min = 1, max = 100))]
pub evidence_limit: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
pub(super) struct SnapshotMetrics {
pub snapshot_id: String,
pub node_count: usize,
pub edge_count: usize,
pub evidence_count: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
pub(super) struct GraphStatusReport {
pub workspace: String,
pub schema_version: i64,
pub integrity_ok: bool,
pub snapshot: SnapshotMetrics,
pub repositories: Vec<RepoFreshness>,
}
#[derive(Debug, Clone, PartialEq, Serialize, JsonSchema)]
pub(super) struct SourceContextReport {
pub workspace: String,
pub entity: Node,
pub related_entities: Vec<Node>,
pub evidence: Vec<Evidence>,
pub total_evidence: usize,
pub truncated: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
pub(super) struct AdminAudit<T> {
pub schema_version: u32,
pub operation: String,
pub workspace: String,
pub mutated: bool,
pub audit_persisted: bool,
pub result: T,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
pub(super) struct ManifestAdminReport {
pub mutation: ManifestMutationSummary,
pub scan: ScanSummary,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, JsonSchema)]
pub(super) struct CacheCleanReport {
pub removed_entries: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
struct RepositoryView {
id: String,
checkout_id: String,
alias: String,
normalized_remote: Option<String>,
head_commit: Option<String>,
linked_worktree: bool,
working_tree_dirty: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum ResourceErrorKind {
Invalid,
Missing,
Internal,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(super) struct ResourceError {
pub kind: ResourceErrorKind,
pub message: String,
}
pub(super) fn status_envelope(
database_path: &Path,
configured_workspace: &str,
requested_workspace: &str,
) -> ToolEnvelope<GraphStatusReport> {
if let Err(message) = validate_workspace(configured_workspace, requested_workspace) {
return error_envelope("Workspace policy rejected the request.", message);
}
match load_status(database_path, configured_workspace) {
Ok((report, freshness)) => {
let status = if report.integrity_ok && freshness.overall == OverallFreshness::Fresh {
ToolStatus::Ok
} else {
ToolStatus::Degraded
};
ToolEnvelope {
schema_version: 1,
status,
data: Some(report),
freshness,
warnings: Vec::new(),
}
}
Err(error) => error_envelope("Workspace status could not be loaded.", error),
}
}
pub(super) fn contracts_envelope(
database_path: &Path,
configured_workspace: &str,
input: &ContractsInput,
) -> ToolEnvelope<ContractReport> {
if let Err(message) = validate_workspace(configured_workspace, input.workspace()) {
return error_envelope("Workspace policy rejected the request.", message);
}
let result = (|| {
let store =
SqliteStore::open_read_only(database_path).map_err(|error| error.to_string())?;
let (nodes, edges) = store
.load_current_graph(configured_workspace)
.map_err(|error| error.to_string())?;
let evidence = store
.load_current_evidence(configured_workspace)
.map_err(|error| error.to_string())?;
let freshness = freshness_summary(
&store
.load_current_freshness(configured_workspace)
.map_err(|error| error.to_string())?,
);
let report = inspect_contracts(&nodes, &edges, &evidence, &[], &input.request())
.map_err(|error| error.to_string())?;
Ok::<_, String>((report, freshness))
})();
match result {
Ok((report, freshness)) => {
let status = if !report.complete || freshness.overall != OverallFreshness::Fresh {
ToolStatus::Degraded
} else {
ToolStatus::Ok
};
ToolEnvelope {
schema_version: 1,
status,
data: Some(report),
freshness,
warnings: Vec::new(),
}
}
Err(error) => error_envelope("Contracts could not be loaded.", error),
}
}
pub(super) fn source_context_envelope(
database_path: &Path,
configured_workspace: &str,
input: &SourceContextInput,
) -> ToolEnvelope<SourceContextReport> {
if let Err(message) = validate_workspace(configured_workspace, &input.workspace) {
return error_envelope("Workspace policy rejected the request.", message);
}
if !(1..=MAX_PAGE_SIZE).contains(&input.evidence_limit) {
return error_envelope(
"Source-context bounds are invalid.",
"evidence_limit must be between 1 and 100",
);
}
let result = (|| {
let store =
SqliteStore::open_read_only(database_path).map_err(|error| error.to_string())?;
let (nodes, edges) = store
.load_current_graph(configured_workspace)
.map_err(|error| error.to_string())?;
let entity = nodes
.iter()
.find(|node| node.id == input.node_id)
.cloned()
.ok_or_else(|| format!("node `{}` was not found", input.node_id.as_str()))?;
let related_ids = edges
.iter()
.filter_map(|edge| {
if edge.source == input.node_id {
Some(edge.target.clone())
} else if edge.target == input.node_id {
Some(edge.source.clone())
} else {
None
}
})
.collect::<BTreeSet<_>>();
let related_entities = nodes
.into_iter()
.filter(|node| related_ids.contains(&node.id))
.take(MAX_PAGE_SIZE)
.collect::<Vec<_>>();
let evidence_ids = edges
.iter()
.filter(|edge| edge.source == input.node_id || edge.target == input.node_id)
.flat_map(|edge| edge.evidence.iter().cloned())
.collect::<BTreeSet<_>>();
let selected = store
.load_current_evidence(configured_workspace)
.map_err(|error| error.to_string())?
.into_iter()
.filter(|item| evidence_ids.contains(&item.id))
.collect::<Vec<_>>();
let total_evidence = selected.len();
let evidence = selected
.into_iter()
.take(input.evidence_limit)
.collect::<Vec<_>>();
let freshness = freshness_summary(
&store
.load_current_freshness(configured_workspace)
.map_err(|error| error.to_string())?,
);
Ok::<_, String>((
SourceContextReport {
workspace: configured_workspace.to_owned(),
entity,
related_entities,
evidence,
total_evidence,
truncated: total_evidence > input.evidence_limit,
},
freshness,
))
})();
match result {
Ok((report, freshness)) => {
let status = if report.truncated || freshness.overall != OverallFreshness::Fresh {
ToolStatus::Degraded
} else {
ToolStatus::Ok
};
ToolEnvelope {
schema_version: 1,
status,
data: Some(report),
freshness,
warnings: Vec::new(),
}
}
Err(error) => error_envelope("Persisted source context could not be loaded.", error),
}
}
pub(super) fn admin_audit_envelope(
database_path: &Path,
workspace: &str,
operation: &str,
summary: ScanSummary,
) -> ToolEnvelope<AdminAudit<ScanSummary>> {
let snapshot_id = summary.snapshot_id.clone();
admin_mutation_envelope(database_path, workspace, operation, &snapshot_id, summary)
}
pub(super) fn admin_mutation_envelope<T>(
database_path: &Path,
workspace: &str,
operation: &str,
snapshot_id: &str,
result: T,
) -> ToolEnvelope<AdminAudit<T>> {
let audit = persist_admin_audit(database_path, workspace, operation, snapshot_id);
let audit_persisted = audit.is_ok();
let warnings = audit.err().into_iter().collect();
let freshness = SqliteStore::open_read_only(database_path)
.and_then(|store| store.load_current_freshness(workspace))
.map_or_else(
|error| FreshnessSummary {
overall: OverallFreshness::Unknown,
stale_repositories: Vec::new(),
reasons: vec![error.to_string()],
},
|items| freshness_summary(&items),
);
ToolEnvelope {
schema_version: 1,
status: if freshness.overall == OverallFreshness::Fresh && audit_persisted {
ToolStatus::Ok
} else {
ToolStatus::Degraded
},
data: Some(AdminAudit {
schema_version: 1,
operation: operation.to_owned(),
workspace: workspace.to_owned(),
mutated: true,
audit_persisted,
result,
}),
freshness,
warnings,
}
}
fn persist_admin_audit(
database_path: &Path,
workspace: &str,
operation: &str,
snapshot_id: &str,
) -> Result<(), String> {
let parent = database_path
.parent()
.ok_or_else(|| "database path has no parent for the administrative audit log".to_owned())?;
std::fs::create_dir_all(parent)
.map_err(|error| format!("failed to create audit directory: {error}"))?;
let path = parent.join("admin-audit.jsonl");
let mut options = OpenOptions::new();
options.create(true).append(true);
#[cfg(unix)]
{
use std::os::unix::fs::OpenOptionsExt as _;
options.mode(0o600);
}
let mut file = options
.open(&path)
.map_err(|error| format!("failed to open administrative audit log: {error}"))?;
let timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_err(|error| format!("failed to timestamp administrative audit entry: {error}"))?
.as_secs();
let entry = json!({
"schema_version": 1,
"timestamp_unix": timestamp,
"workspace": workspace,
"operation": operation,
"snapshot_id": snapshot_id
});
serde_json::to_writer(&mut file, &entry)
.map_err(|error| format!("failed to serialize administrative audit entry: {error}"))?;
file.write_all(b"\n")
.and_then(|()| file.sync_data())
.map_err(|error| format!("failed to persist administrative audit entry: {error}"))
}
pub(super) fn configured_manifest_path(
database_path: &Path,
workspace: &str,
) -> Result<PathBuf, String> {
let store = SqliteStore::open_read_only(database_path).map_err(|error| error.to_string())?;
let registry = store
.load_workspace_registry(workspace)
.map_err(|error| error.to_string())?;
let path = registry
.config_path
.ok_or_else(|| format!("workspace `{workspace}` has no registered manifest path"))?;
Ok(native_path(&path))
}
pub(super) fn resource_uris(workspace: &str) -> Vec<(String, String, String)> {
let prefix = format!("code-system-graph://workspace/{workspace}");
vec![
(
"code-system-graph://workspaces".to_owned(),
"workspaces".to_owned(),
"Configured workspace catalog.".to_owned(),
),
(
format!("{prefix}/overview"),
"workspace-overview".to_owned(),
"Current immutable snapshot counts.".to_owned(),
),
(
format!("{prefix}/status"),
"workspace-status".to_owned(),
"Persisted workspace integrity and freshness.".to_owned(),
),
(
format!("{prefix}/repositories"),
"workspace-repositories".to_owned(),
"Credential-free repository registration metadata.".to_owned(),
),
(
format!("{prefix}/services"),
"workspace-services".to_owned(),
"Bounded service entity catalog.".to_owned(),
),
(
format!("{prefix}/contracts"),
"workspace-contracts".to_owned(),
"Bounded public contract entity catalog.".to_owned(),
),
(
format!("{prefix}/communities"),
"workspace-communities".to_owned(),
"Bounded deterministic community catalog.".to_owned(),
),
(
format!("{prefix}/schema"),
"schema-catalog".to_owned(),
"Versioned JSON Schema catalog for MCP tool inputs and results.".to_owned(),
),
(
format!("{prefix}/coverage"),
"workspace-coverage".to_owned(),
"Bounded extractor and freshness coverage metadata.".to_owned(),
),
(
"code-system-graph://evidence/{id}".to_owned(),
"evidence-metadata".to_owned(),
"Template URI for one evidence metadata record; replace {id} with its stable ID."
.to_owned(),
),
]
}
pub(super) fn read_resource(
database_path: &Path,
workspace: &str,
uri: &str,
) -> Result<String, ResourceError> {
let value = if uri == "code-system-graph://workspaces" {
json!({
"schema_version": 1,
"workspaces": [{"name": workspace, "configured": true}]
})
} else if let Some(path) = uri.strip_prefix("code-system-graph://workspace/") {
let (requested, resource) = path.split_once('/').ok_or_else(|| invalid_resource(uri))?;
validate_workspace(workspace, requested).map_err(|message| ResourceError {
kind: ResourceErrorKind::Invalid,
message,
})?;
read_workspace_resource(database_path, workspace, resource)?
} else if let Some(evidence_id) = uri.strip_prefix("code-system-graph://evidence/") {
read_evidence_resource(database_path, workspace, evidence_id)?
} else {
return Err(invalid_resource(uri));
};
let text = serde_json::to_string_pretty(&value).map_err(|error| ResourceError {
kind: ResourceErrorKind::Internal,
message: format!("resource serialization failed: {error}"),
})?;
let maximum = if uri.ends_with("/schema") {
MAX_SCHEMA_RESOURCE_BYTES
} else {
MAX_RESOURCE_BYTES
};
if text.len() > maximum {
return Err(ResourceError {
kind: ResourceErrorKind::Internal,
message: format!("resource exceeds the {maximum}-byte response bound"),
});
}
Ok(text)
}
pub(super) fn schema_catalog() -> Value {
let mut schemas = BTreeMap::new();
insert_schema::<TraceInput>(&mut schemas, "trace.input");
insert_schema::<ToolEnvelope<TraceReport>>(&mut schemas, "trace.result");
insert_schema::<SearchInput>(&mut schemas, "query.input");
insert_schema::<ToolEnvelope<SearchReport>>(&mut schemas, "query.result");
insert_schema::<CommunitiesInput>(&mut schemas, "communities.input");
insert_schema::<ToolEnvelope<CommunityReport>>(&mut schemas, "communities.result");
insert_schema::<ExploreInput>(&mut schemas, "explore.input");
insert_schema::<ToolEnvelope<LocalContextResult>>(&mut schemas, "explore.result");
insert_schema::<ImpactRequest>(&mut schemas, "impact.input");
insert_schema::<ToolEnvelope<ImpactReport>>(&mut schemas, "impact.result");
insert_schema::<ChangesInput>(&mut schemas, "analyze_changes.input");
insert_schema::<ToolEnvelope<ChangeImpactReport>>(&mut schemas, "analyze_changes.result");
insert_schema::<PullRequestInput>(&mut schemas, "analyze_pull_request.input");
insert_schema::<ToolEnvelope<PullRequestInspection>>(
&mut schemas,
"analyze_pull_request.result",
);
insert_schema::<WorkspaceInput>(&mut schemas, "status.input");
insert_schema::<ToolEnvelope<GraphStatusReport>>(&mut schemas, "status.result");
insert_schema::<ContractsInput>(&mut schemas, "contracts.input");
insert_schema::<ToolEnvelope<ContractReport>>(&mut schemas, "contracts.result");
insert_schema::<SourceContextInput>(&mut schemas, "source_context.input");
insert_schema::<ToolEnvelope<SourceContextReport>>(&mut schemas, "source_context.result");
insert_schema::<WorkspaceInput>(&mut schemas, "scan.input");
insert_schema::<ToolEnvelope<AdminAudit<ScanSummary>>>(&mut schemas, "scan.result");
insert_schema::<WorkspaceUpdateInput>(&mut schemas, "update_workspace.input");
insert_schema::<ToolEnvelope<AdminAudit<ManifestAdminReport>>>(
&mut schemas,
"update_workspace.result",
);
insert_schema::<ManualLinkWriteInput>(&mut schemas, "write_manual_link.input");
insert_schema::<ToolEnvelope<AdminAudit<ManifestAdminReport>>>(
&mut schemas,
"write_manual_link.result",
);
insert_schema::<CacheCleanInput>(&mut schemas, "clean_cache.input");
insert_schema::<ToolEnvelope<AdminAudit<CacheCleanReport>>>(&mut schemas, "clean_cache.result");
insert_schema::<WorkspaceInput>(&mut schemas, "recompute_communities.input");
insert_schema::<ToolEnvelope<AdminAudit<ScanSummary>>>(
&mut schemas,
"recompute_communities.result",
);
let application_interfaces = public_schema_catalog().map_or_else(
|error| {
json!({
"schema_version": 1,
"error": error.to_string()
})
},
|catalog| {
serde_json::to_value(catalog).unwrap_or_else(|error| {
json!({
"schema_version": 1,
"error": error.to_string()
})
})
},
);
json!({
"schema_version": 1,
"media_type": "application/schema+json",
"schemas": schemas,
"application_interfaces": application_interfaces
})
}
fn read_workspace_resource(
database_path: &Path,
workspace: &str,
resource: &str,
) -> Result<Value, ResourceError> {
if resource == "schema" {
return Ok(schema_catalog());
}
let store = SqliteStore::open_read_only(database_path).map_err(internal_store)?;
match resource {
"overview" => {
let summary = store
.current_snapshot_summary(workspace)
.map_err(internal_store)?;
Ok(json!({
"schema_version": 1,
"workspace": workspace,
"snapshot": {
"id": summary.snapshot_id,
"nodes": summary.node_count,
"edges": summary.edge_count,
"evidence": summary.evidence_count
}
}))
}
"status" => {
let (status, freshness) =
load_status(database_path, workspace).map_err(internal_message)?;
Ok(json!({
"schema_version": 1,
"status": status,
"freshness": freshness
}))
}
"repositories" => {
let registry = store
.load_workspace_registry(workspace)
.map_err(internal_store)?;
let repositories = registry
.repositories
.iter()
.take(MAX_PAGE_SIZE)
.map(repository_view)
.collect::<Vec<_>>();
Ok(json!({
"schema_version": 1,
"workspace": workspace,
"total": registry.repositories.len(),
"truncated": registry.repositories.len() > MAX_PAGE_SIZE,
"repositories": repositories
}))
}
"services" => node_resource(&store, workspace, |kind| kind == NodeKind::Service),
"contracts" => node_resource(&store, workspace, is_contract),
"communities" => {
let snapshot = store
.load_current_community_snapshot(workspace)
.map_err(internal_store)?;
let total = snapshot.communities.len();
let communities = snapshot
.communities
.into_iter()
.take(MAX_PAGE_SIZE)
.collect::<Vec<Community>>();
Ok(json!({
"schema_version": 1,
"workspace": workspace,
"snapshot_id": snapshot.snapshot_id,
"engine_version": snapshot.engine_version,
"config": snapshot.config,
"total": total,
"truncated": total > MAX_PAGE_SIZE,
"communities": communities
}))
}
"coverage" => {
let runs = store
.load_current_extractor_runs(workspace)
.map_err(internal_store)?;
let freshness = store
.load_current_freshness(workspace)
.map_err(internal_store)?;
let run_total = runs.len();
Ok(json!({
"schema_version": 1,
"workspace": workspace,
"run_total": run_total,
"truncated": run_total > MAX_PAGE_SIZE,
"runs": runs.into_iter().take(MAX_PAGE_SIZE).collect::<Vec<_>>(),
"freshness": freshness_summary(&freshness)
}))
}
_ => Err(ResourceError {
kind: ResourceErrorKind::Missing,
message: format!("workspace resource `{resource}` was not found"),
}),
}
}
fn read_evidence_resource(
database_path: &Path,
workspace: &str,
evidence_id: &str,
) -> Result<Value, ResourceError> {
if evidence_id.is_empty() || evidence_id == "{id}" {
return Err(ResourceError {
kind: ResourceErrorKind::Invalid,
message: "a concrete evidence identifier is required".to_owned(),
});
}
let store = SqliteStore::open_read_only(database_path).map_err(internal_store)?;
let evidence = store
.load_current_evidence(workspace)
.map_err(|error| match error {
StoreError::CurrentSnapshotMissing(_) => ResourceError {
kind: ResourceErrorKind::Missing,
message: format!("workspace `{workspace}` has no evidence snapshot"),
},
other => internal_store(other),
})?
.into_iter()
.find(|item| item.id.as_str() == evidence_id)
.ok_or_else(|| ResourceError {
kind: ResourceErrorKind::Missing,
message: format!("evidence `{evidence_id}` was not found in workspace `{workspace}`"),
})?;
Ok(json!({
"schema_version": 1,
"workspace": workspace,
"evidence": evidence
}))
}
fn node_resource(
store: &SqliteStore,
workspace: &str,
predicate: impl Fn(NodeKind) -> bool,
) -> Result<Value, ResourceError> {
let (nodes, _) = store
.load_current_graph(workspace)
.map_err(internal_store)?;
let selected = nodes
.into_iter()
.filter(|node| predicate(node.kind))
.collect::<Vec<_>>();
let total = selected.len();
Ok(json!({
"schema_version": 1,
"workspace": workspace,
"total": total,
"truncated": total > MAX_PAGE_SIZE,
"entities": selected.into_iter().take(MAX_PAGE_SIZE).collect::<Vec<_>>()
}))
}
fn load_status(
database_path: &Path,
workspace: &str,
) -> Result<(GraphStatusReport, FreshnessSummary), String> {
let store = SqliteStore::open_read_only(database_path).map_err(|error| error.to_string())?;
if !store
.workspace_exists(workspace)
.map_err(|error| error.to_string())?
{
return Err(format!("workspace `{workspace}` is not registered"));
}
let repositories = store
.load_current_freshness(workspace)
.map_err(|error| error.to_string())?;
let freshness = freshness_summary(&repositories);
let snapshot = store
.current_snapshot_summary(workspace)
.map_err(|error| error.to_string())?;
Ok((
GraphStatusReport {
workspace: workspace.to_owned(),
schema_version: store.schema_version().map_err(|error| error.to_string())?,
integrity_ok: store.integrity_check().map_err(|error| error.to_string())?,
snapshot: SnapshotMetrics {
snapshot_id: snapshot.snapshot_id,
node_count: snapshot.node_count,
edge_count: snapshot.edge_count,
evidence_count: snapshot.evidence_count,
},
repositories,
},
freshness,
))
}
fn freshness_summary(repositories: &[RepoFreshness]) -> FreshnessSummary {
let overall = if repositories.is_empty() {
OverallFreshness::Unknown
} else if repositories
.iter()
.all(|item| item.state == RepoFreshnessState::Fresh)
{
OverallFreshness::Fresh
} else if repositories.iter().any(|item| {
matches!(
item.state,
RepoFreshnessState::Unavailable
| RepoFreshnessState::Unknown
| RepoFreshnessState::Corrupt
)
}) {
OverallFreshness::Partial
} else {
OverallFreshness::Stale
};
let stale_repositories = repositories
.iter()
.filter(|item| item.state != RepoFreshnessState::Fresh)
.map(|item| item.repo_id.clone())
.collect();
let reasons = repositories
.iter()
.filter_map(|item| item.reason.clone())
.collect();
FreshnessSummary {
overall,
stale_repositories,
reasons,
}
}
fn validate_workspace(configured: &str, requested: &str) -> Result<(), String> {
if requested == configured {
Ok(())
} else {
Err(format!(
"workspace `{requested}` is outside this server's configured `{configured}` policy"
))
}
}
fn is_contract(kind: NodeKind) -> bool {
matches!(
kind,
NodeKind::HttpOperation
| NodeKind::GraphqlOperation
| NodeKind::RpcMethod
| NodeKind::EventChannel
| NodeKind::EventSchema
| NodeKind::DatabaseTable
| NodeKind::DatabaseColumn
| NodeKind::ConfigKey
)
}
fn repository_view(repository: &RepositoryRecord) -> RepositoryView {
RepositoryView {
id: repository.id.as_str().to_owned(),
checkout_id: repository.checkout_id.as_str().to_owned(),
alias: repository.alias.clone(),
normalized_remote: repository.normalized_remote.clone(),
head_commit: repository.head_commit.clone(),
linked_worktree: repository.is_linked_worktree,
working_tree_dirty: repository.working_tree_dirty,
}
}
fn insert_schema<T: JsonSchema>(catalog: &mut BTreeMap<String, Value>, name: &str) {
catalog.insert(name.to_owned(), json!(schema_for!(T)));
}
fn error_envelope<T>(reason: &str, error: impl std::fmt::Display) -> ToolEnvelope<T> {
ToolEnvelope {
schema_version: 1,
status: ToolStatus::Error,
data: None,
freshness: FreshnessSummary {
overall: OverallFreshness::Unknown,
stale_repositories: Vec::new(),
reasons: vec![reason.to_owned()],
},
warnings: vec![error.to_string()],
}
}
fn invalid_resource(uri: &str) -> ResourceError {
ResourceError {
kind: ResourceErrorKind::Missing,
message: format!("resource `{uri}` was not found"),
}
}
fn internal_store(error: impl std::fmt::Display) -> ResourceError {
internal_message(error.to_string())
}
fn internal_message(message: String) -> ResourceError {
ResourceError {
kind: ResourceErrorKind::Internal,
message,
}
}
fn default_page_size() -> usize {
50
}
fn default_evidence_limit() -> usize {
20
}
#[cfg(unix)]
fn native_path(path: &code_system_graph_model::NativePath) -> PathBuf {
use std::ffi::OsString;
use std::os::unix::ffi::OsStringExt;
match path.encoding {
code_system_graph_model::NativePathEncoding::UnixBytes
| code_system_graph_model::NativePathEncoding::Utf8 => {
PathBuf::from(OsString::from_vec(path.bytes.clone()))
}
code_system_graph_model::NativePathEncoding::WindowsWide => PathBuf::from(&path.display),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn administrative_audit_should_be_append_only_and_source_free()
-> Result<(), Box<dyn std::error::Error>> {
let temporary = tempfile::tempdir()?;
let database = temporary.path().join("graph.db");
SqliteStore::open(&database)?;
persist_admin_audit(&database, "workspace", "scan", "snapshot:one")?;
persist_admin_audit(&database, "workspace", "scan", "snapshot:two")?;
let source = std::fs::read_to_string(temporary.path().join("admin-audit.jsonl"))?;
let lines = source.lines().collect::<Vec<_>>();
assert_eq!(lines.len(), 2);
assert!(lines[0].contains("snapshot:one"));
assert!(lines[1].contains("snapshot:two"));
assert!(!source.contains("source_body"));
Ok(())
}
}
#[cfg(windows)]
fn native_path(path: &code_system_graph_model::NativePath) -> PathBuf {
use std::ffi::OsString;
use std::os::windows::ffi::OsStringExt;
match path.encoding {
code_system_graph_model::NativePathEncoding::WindowsWide => {
let wide = path
.bytes
.chunks_exact(2)
.map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
.collect::<Vec<_>>();
PathBuf::from(OsString::from_wide(&wide))
}
code_system_graph_model::NativePathEncoding::UnixBytes
| code_system_graph_model::NativePathEncoding::Utf8 => PathBuf::from(&path.display),
}
}
#[cfg(not(any(unix, windows)))]
fn native_path(path: &code_system_graph_model::NativePath) -> PathBuf {
PathBuf::from(&path.display)
}