Skip to main content

relay_knowledge/application/knowledge/map/
contracts.rs

1//! Public request/response contracts and typed Knowledge Map mutation state.
2
3use serde::Serialize;
4
5use crate::{
6    api::{ApiMetadata, RequestContext},
7    clock::system_now_millis_or_zero,
8    domain::{
9        DirectoryLoadHint, DirectoryUpdateRule, KnowledgeMap, KnowledgeMapHistoryEntry,
10        KnowledgeMapRoute, KnowledgeMapSource, KnowledgeMapSourceKind, KnowledgeMapTopic,
11        RepositoryMapDirectory, RepositoryMapType,
12    },
13};
14
15use super::artifact::ARTIFACT_SCHEMA_VERSION;
16
17pub(super) struct MutableKnowledgeMap {
18    pub(super) map_type: RepositoryMapType,
19    pub(super) directories: Vec<RepositoryMapDirectory>,
20    pub(super) map: KnowledgeMap,
21    pub(super) omitted_through: u64,
22    pub(super) requires_publish: bool,
23    pub(super) legacy_glossary_uri_normalized: bool,
24}
25
26impl MutableKnowledgeMap {
27    pub(super) fn initial(map_type: RepositoryMapType, updated_at: String) -> Self {
28        Self {
29            map: match map_type {
30                RepositoryMapType::Knowledge => KnowledgeMap::initial(updated_at),
31                RepositoryMapType::Codespec => KnowledgeMap::empty(updated_at),
32            },
33            map_type,
34            directories: baseline_directories(map_type),
35            omitted_through: 0,
36            requires_publish: false,
37            legacy_glossary_uri_normalized: false,
38        }
39    }
40
41    pub(super) fn record_required_publication(
42        &mut self,
43        source_schema_version: u16,
44        updated_at: String,
45    ) -> String {
46        let glossary_only =
47            source_schema_version == ARTIFACT_SCHEMA_VERSION && self.legacy_glossary_uri_normalized;
48        let (action, history_summary, response_summary) = if glossary_only {
49            (
50                "source.migrate",
51                "Migrated the reserved business glossary source URI to the canonical artifact.",
52                "migrated Knowledge Map legacy glossary URI to the canonical artifact",
53            )
54        } else {
55            (
56                "history.compact",
57                "Migrated repository map to bounded recent-only history storage.",
58                "migrated repository map to schema v4 recent-only history",
59            )
60        };
61        self.map
62            .record_change(action, history_summary.to_owned(), updated_at);
63        response_summary.to_owned()
64    }
65}
66
67pub(super) fn baseline_directories(map_type: RepositoryMapType) -> Vec<RepositoryMapDirectory> {
68    map_type
69        .required_directories()
70        .iter()
71        .map(|directory| RepositoryMapDirectory {
72            directory: (*directory).to_owned(),
73            purpose: baseline_purpose(map_type, directory).to_owned(),
74            content_scope: vec![format!("{}/{directory}/**", map_type.as_str())],
75            key_files: vec![format!("{}/{directory}/README.md", map_type.as_str())],
76            load_hint: DirectoryLoadHint::OnDemand,
77            relations: Vec::new(),
78            update_rule: DirectoryUpdateRule::Reviewed,
79        })
80        .collect()
81}
82
83fn baseline_purpose(map_type: RepositoryMapType, directory: &str) -> &'static str {
84    match (map_type, directory) {
85        (RepositoryMapType::Codespec, "requirements") => {
86            "Product requirements and acceptance criteria."
87        }
88        (RepositoryMapType::Codespec, "design") => {
89            "Architecture and implementation design records."
90        }
91        (RepositoryMapType::Codespec, "api") => "Public interface and schema contracts.",
92        (RepositoryMapType::Codespec, "test") => "Verification strategy, fixtures, and evidence.",
93        (RepositoryMapType::Codespec, "decisions") => "Durable architecture and product decisions.",
94        (RepositoryMapType::Knowledge, "domain") => "Domain concepts, models, and business rules.",
95        (RepositoryMapType::Knowledge, "guides") => "Task-oriented repository knowledge guides.",
96        (RepositoryMapType::Knowledge, "ops") => "Operational procedures and diagnostics.",
97        (RepositoryMapType::Knowledge, "glossary") => {
98            "Business terminology, aliases, and technical mappings."
99        }
100        (RepositoryMapType::Knowledge, "best-practices") => {
101            "Reviewed engineering and knowledge-management practices."
102        }
103        _ => "Repository navigation knowledge.",
104    }
105}
106
107pub(super) fn metadata(context: &RequestContext) -> ApiMetadata {
108    ApiMetadata::graph_only(context, crate::domain::GraphVersion::ZERO)
109}
110
111pub(super) fn now_stamp() -> String {
112    let seconds = system_now_millis_or_zero() / 1_000;
113    format!("unix:{seconds}")
114}
115
116/// Request to register a source in the repository knowledge map.
117#[derive(Debug, Clone, PartialEq, Eq)]
118pub struct KnowledgeMapSourceAddRequest {
119    pub id: String,
120    pub topic: String,
121    pub kind: KnowledgeMapSourceKind,
122    pub uri: String,
123    pub source_scope: Option<String>,
124    pub description: Option<String>,
125}
126
127/// Response shared by map mutation commands.
128#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
129pub struct KnowledgeMapMutationResponse {
130    pub metadata: ApiMetadata,
131    pub path: String,
132    pub map_type: RepositoryMapType,
133    pub map_version: u64,
134    pub summary: String,
135}
136
137/// Response returned by read-only map commands.
138#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
139pub struct KnowledgeMapShowResponse {
140    pub metadata: ApiMetadata,
141    pub path: String,
142    pub map_type: RepositoryMapType,
143    pub map: KnowledgeMapView,
144}
145
146/// Bounded assembled view returned by `map show`.
147#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
148pub struct KnowledgeMapView {
149    pub artifact_schema_version: u16,
150    pub map_version: u64,
151    pub updated_at: String,
152    pub directories: Vec<RepositoryMapDirectory>,
153    pub topics: Vec<KnowledgeMapTopic>,
154    pub sources: Vec<KnowledgeMapSource>,
155    pub routes: Vec<KnowledgeMapRoute>,
156    pub history: KnowledgeMapHistoryWindow,
157}
158
159/// Recent history and the checkpoint for history intentionally omitted from a show response.
160#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
161pub struct KnowledgeMapHistoryWindow {
162    pub omitted_through: u64,
163    pub complete: bool,
164    pub recent: Vec<KnowledgeMapHistoryEntry>,
165}
166
167/// One explicitly bounded page from the retained recent repository-map history.
168#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
169pub struct KnowledgeMapHistoryResponse {
170    pub metadata: ApiMetadata,
171    pub path: String,
172    pub map_type: RepositoryMapType,
173    pub map_version: u64,
174    pub omitted_through: u64,
175    pub earliest_available_version: u64,
176    pub from_version: u64,
177    pub through_version: u64,
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub next_from_version: Option<u64>,
180    pub entries: Vec<KnowledgeMapHistoryEntry>,
181}
182
183/// Response returned by topic routing commands.
184#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
185pub struct KnowledgeMapRouteResponse {
186    pub metadata: ApiMetadata,
187    pub path: String,
188    pub map_type: RepositoryMapType,
189    pub topic: String,
190    pub route: Option<KnowledgeMapRoute>,
191    pub sources: Vec<KnowledgeMapSource>,
192}
193
194/// Response returned by validation commands.
195#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
196pub struct KnowledgeMapValidationResponse {
197    pub metadata: ApiMetadata,
198    pub path: String,
199    pub map_type: RepositoryMapType,
200    pub valid: bool,
201    pub diagnostics: Vec<String>,
202}
203
204/// Response that contains the AGENTS.md reference snippet.
205#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
206pub struct KnowledgeMapAgentSnippetResponse {
207    pub metadata: ApiMetadata,
208    pub snippet: String,
209}