1use memstead_base::ops::coverage::{AxisCoverage, CoverageDisposition, SurfaceCoverage};
11
12const READS_DATA: &str = "returns data, not a verdict; an empty result is an empty \
16 result, never an all-clear";
17const MUTATION: &str = "mutation surface: reports what it did, never an all-clear \
18 over unexamined state";
19#[cfg(feature = "mem-repo")]
20const TRANSPORT: &str = "transport operation: reports the transfer's own outcome";
21const ACCOUNT_OP: &str = "registry or account operation: reports the operation's own \
22 outcome";
23
24const STATUS_SCOPE: &str = "outside the status rollup, whose verdict answers for \
26 declared projection bindings only";
27const ANCHORS_ONLY: &str = "the standalone anchor statement answers for anchors \
28 alone; it examines no other axis and says so";
29const VERIFY_SCOPE: &str = "the binding-scoped fidelity report answers for one \
30 binding's projection and its anchors; other axes belong to health";
31#[cfg(feature = "mem-repo")]
32const DUMP_SCOPE: &str = "the dump is a configuration and roster snapshot; graph \
33 axes belong to health";
34
35pub const HEALTH: SurfaceCoverage = SurfaceCoverage {
40 surface: "health",
41 disposition: CoverageDisposition::Verdict(memstead_base::ops::coverage::HEALTH_COVERAGE),
45};
46
47pub const STATUS: SurfaceCoverage = SurfaceCoverage {
48 surface: "status",
49 disposition: CoverageDisposition::Verdict(AxisCoverage {
50 examined: &["projection"],
51 advisory: &[],
52 not_examined: &[
53 ("orphans", STATUS_SCOPE),
54 ("stubs", STATUS_SCOPE),
55 ("most_connected", STATUS_SCOPE),
56 ("missing_fields", STATUS_SCOPE),
57 ("stale", STATUS_SCOPE),
58 ("dangling_links", STATUS_SCOPE),
59 ("tags", STATUS_SCOPE),
60 ("missing_required_outgoing", STATUS_SCOPE),
61 ("constraints", STATUS_SCOPE),
62 ("signals", STATUS_SCOPE),
63 ("labelling", STATUS_SCOPE),
64 ("conformance", STATUS_SCOPE),
65 ("integrity", STATUS_SCOPE),
66 ("config", STATUS_SCOPE),
67 ("anchors", STATUS_SCOPE),
68 ("friction", STATUS_SCOPE),
69 ("open_questions", STATUS_SCOPE),
70 ("vital_signs", STATUS_SCOPE),
71 ("stale_derivations", STATUS_SCOPE),
72 ("checks", STATUS_SCOPE),
73 ("ledger", STATUS_SCOPE),
74 ("mounts", STATUS_SCOPE),
75 ],
76 }),
77};
78
79const OVERVIEW: SurfaceCoverage = SurfaceCoverage {
80 surface: "overview",
81 disposition: CoverageDisposition::Verdict(memstead_base::ops::coverage::OVERVIEW_COVERAGE),
85};
86
87pub const VERIFY_ANCHORS: SurfaceCoverage = SurfaceCoverage {
88 surface: "verify-anchors",
89 disposition: CoverageDisposition::Verdict(AxisCoverage {
90 examined: &["anchors"],
91 advisory: &[],
92 not_examined: &[
93 ("orphans", ANCHORS_ONLY),
94 ("stubs", ANCHORS_ONLY),
95 ("most_connected", ANCHORS_ONLY),
96 ("missing_fields", ANCHORS_ONLY),
97 ("stale", ANCHORS_ONLY),
98 ("dangling_links", ANCHORS_ONLY),
99 ("tags", ANCHORS_ONLY),
100 ("missing_required_outgoing", ANCHORS_ONLY),
101 ("constraints", ANCHORS_ONLY),
102 ("signals", ANCHORS_ONLY),
103 ("labelling", ANCHORS_ONLY),
104 ("conformance", ANCHORS_ONLY),
105 ("integrity", ANCHORS_ONLY),
106 ("config", ANCHORS_ONLY),
107 ("friction", ANCHORS_ONLY),
108 ("open_questions", ANCHORS_ONLY),
109 ("vital_signs", ANCHORS_ONLY),
110 ("stale_derivations", ANCHORS_ONLY),
111 ("checks", ANCHORS_ONLY),
112 ("ledger", ANCHORS_ONLY),
113 ("projection", ANCHORS_ONLY),
114 ("mounts", ANCHORS_ONLY),
115 ],
116 }),
117};
118
119pub const PROJECTION_VERIFY: SurfaceCoverage = SurfaceCoverage {
120 surface: "projection verify",
121 disposition: CoverageDisposition::Verdict(AxisCoverage {
122 examined: &["projection", "anchors"],
123 advisory: &[],
124 not_examined: &[
125 ("orphans", VERIFY_SCOPE),
126 ("stubs", VERIFY_SCOPE),
127 ("most_connected", VERIFY_SCOPE),
128 ("missing_fields", VERIFY_SCOPE),
129 ("stale", VERIFY_SCOPE),
130 ("dangling_links", VERIFY_SCOPE),
131 ("tags", VERIFY_SCOPE),
132 ("missing_required_outgoing", VERIFY_SCOPE),
133 ("constraints", VERIFY_SCOPE),
134 ("signals", VERIFY_SCOPE),
135 ("labelling", VERIFY_SCOPE),
136 ("conformance", VERIFY_SCOPE),
137 ("integrity", VERIFY_SCOPE),
138 ("config", VERIFY_SCOPE),
139 ("friction", VERIFY_SCOPE),
140 ("open_questions", VERIFY_SCOPE),
141 ("vital_signs", VERIFY_SCOPE),
142 ("stale_derivations", VERIFY_SCOPE),
143 ("checks", VERIFY_SCOPE),
144 ("ledger", VERIFY_SCOPE),
145 ("mounts", VERIFY_SCOPE),
146 ],
147 }),
148};
149
150#[cfg(feature = "mem-repo")]
151pub const WORKSPACE_DUMP: SurfaceCoverage = SurfaceCoverage {
152 surface: "workspace dump",
153 disposition: CoverageDisposition::Verdict(AxisCoverage {
154 examined: &["mounts", "config"],
155 advisory: &[],
156 not_examined: &[
157 ("orphans", DUMP_SCOPE),
158 ("stubs", DUMP_SCOPE),
159 ("most_connected", DUMP_SCOPE),
160 ("missing_fields", DUMP_SCOPE),
161 ("stale", DUMP_SCOPE),
162 ("dangling_links", DUMP_SCOPE),
163 ("tags", DUMP_SCOPE),
164 ("missing_required_outgoing", DUMP_SCOPE),
165 ("constraints", DUMP_SCOPE),
166 ("signals", DUMP_SCOPE),
167 ("labelling", DUMP_SCOPE),
168 ("conformance", DUMP_SCOPE),
169 ("integrity", DUMP_SCOPE),
170 ("anchors", DUMP_SCOPE),
171 ("friction", DUMP_SCOPE),
172 ("open_questions", DUMP_SCOPE),
173 ("vital_signs", DUMP_SCOPE),
174 ("stale_derivations", DUMP_SCOPE),
175 ("checks", DUMP_SCOPE),
176 ("ledger", DUMP_SCOPE),
177 (
178 "projection",
179 "binding fidelity is answered by status and projection verify",
180 ),
181 ],
182 }),
183};
184
185fn no_verdict(surface: &'static str, reason: &'static str) -> SurfaceCoverage {
186 SurfaceCoverage {
187 surface,
188 disposition: CoverageDisposition::NoVerdict(reason),
189 }
190}
191
192pub fn surface_registry() -> Vec<SurfaceCoverage> {
197 #[cfg_attr(not(feature = "mem-repo"), allow(unused_mut))]
198 let mut rows = vec![
199 STATUS,
200 HEALTH,
201 OVERVIEW,
202 VERIFY_ANCHORS,
203 PROJECTION_VERIFY,
204 no_verdict("entity", READS_DATA),
206 no_verdict("relations", READS_DATA),
207 no_verdict("search", READS_DATA),
208 no_verdict("list", READS_DATA),
209 no_verdict("context", READS_DATA),
210 no_verdict("type", READS_DATA),
211 no_verdict("due", READS_DATA),
212 no_verdict("gates", READS_DATA),
213 no_verdict("export", READS_DATA),
214 no_verdict("changes", READS_DATA),
215 no_verdict("anchors", READS_DATA),
216 no_verdict("conflicts list", READS_DATA),
217 no_verdict("review-mark list", READS_DATA),
218 no_verdict("review-mark diff", READS_DATA),
219 no_verdict("projection brief", READS_DATA),
220 no_verdict("projection check-path", READS_DATA),
221 no_verdict(
226 "check",
227 "records the caller's verdict about the caller's own work into the \
228 append-only ledger; the engine derives no verdict of its own",
229 ),
230 no_verdict(
233 "schema validate",
234 "validates the caller-named schema package; the verdict is total over \
235 exactly that input and claims no workspace axis",
236 ),
237 no_verdict("schema new", MUTATION),
238 no_verdict("schema install", MUTATION),
239 no_verdict(
240 "schema migrate",
241 "previews or applies rewrites of the caller-named schema package; reports \
242 the rewrites, never an all-clear over any workspace axis",
243 ),
244 no_verdict("create", MUTATION),
246 no_verdict("update", MUTATION),
247 no_verdict("relate", MUTATION),
248 no_verdict("delete", MUTATION),
249 no_verdict("rename", MUTATION),
250 no_verdict("retype", MUTATION),
251 no_verdict("conflicts resolve", MUTATION),
252 no_verdict("review-mark set", MUTATION),
253 no_verdict("review-mark clear", MUTATION),
254 no_verdict("reload", MUTATION),
255 no_verdict("init", MUTATION),
256 no_verdict("quickstart", MUTATION),
257 no_verdict("projection init", MUTATION),
258 no_verdict("projection migrate", MUTATION),
259 no_verdict("projection enable", MUTATION),
260 no_verdict("projection edit", MUTATION),
261 no_verdict("projection advance", MUTATION),
262 no_verdict("projection exclude", MUTATION),
263 no_verdict("publish", ACCOUNT_OP),
265 no_verdict("unpublish", ACCOUNT_OP),
266 no_verdict("login", ACCOUNT_OP),
267 no_verdict("logout", ACCOUNT_OP),
268 no_verdict("domain keygen", ACCOUNT_OP),
269 no_verdict("domain manifest", ACCOUNT_OP),
270 no_verdict("admin takedown", ACCOUNT_OP),
271 no_verdict("admin denylist", ACCOUNT_OP),
272 ];
273 #[cfg(feature = "mem-repo")]
274 rows.extend([
275 WORKSPACE_DUMP,
276 no_verdict("install", MUTATION),
277 no_verdict("uninstall", MUTATION),
278 no_verdict("batch-update", MUTATION),
279 no_verdict("batch-create", MUTATION),
280 no_verdict("batch-relate", MUTATION),
281 no_verdict("recover", MUTATION),
282 no_verdict("fetch", TRANSPORT),
283 no_verdict("pull", TRANSPORT),
284 no_verdict("push", TRANSPORT),
285 no_verdict("branch-reset", TRANSPORT),
286 no_verdict("mem init", MUTATION),
287 no_verdict("mem unregister", MUTATION),
288 no_verdict("mem delete", MUTATION),
289 no_verdict("mem rename", MUTATION),
290 no_verdict("mem set-version", MUTATION),
291 no_verdict("mem set-schema", MUTATION),
292 no_verdict("mem set-description", MUTATION),
293 no_verdict("mem set-title", MUTATION),
294 no_verdict("mem set-subject", MUTATION),
295 no_verdict("mem set-sync-state", MUTATION),
296 no_verdict("mem set-internal", MUTATION),
297 no_verdict("mem list", READS_DATA),
298 no_verdict("mem-repo init", MUTATION),
299 no_verdict("mem-repo remote-add", MUTATION),
300 no_verdict("workspace show", READS_DATA),
301 no_verdict("workspace allow-create", MUTATION),
302 no_verdict("workspace revoke-create", MUTATION),
303 no_verdict("workspace allow-delete", MUTATION),
304 no_verdict("workspace revoke-delete", MUTATION),
305 no_verdict("workspace grant-cross-link", MUTATION),
306 no_verdict("workspace revoke-cross-link", MUTATION),
307 no_verdict("workspace set-mutations", MUTATION),
308 ]);
309 rows
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315 use clap::CommandFactory;
316 use memstead_base::ops::coverage::{validate_coverage, verdict_axes};
317
318 fn discovered_surfaces() -> Vec<String> {
323 fn walk(cmd: &clap::Command, prefix: &str, out: &mut Vec<String>) {
324 let mut leaves = 0;
325 for sub in cmd.get_subcommands() {
326 if sub.get_name() == "help" {
327 continue;
328 }
329 leaves += 1;
330 let path = if prefix.is_empty() {
331 sub.get_name().to_string()
332 } else {
333 format!("{prefix} {}", sub.get_name())
334 };
335 walk(sub, &path, out);
336 }
337 if leaves == 0 && !prefix.is_empty() {
338 out.push(prefix.to_string());
339 }
340 }
341 let mut out = Vec::new();
342 let cmd = crate::cli::Cli::command();
343 walk(&cmd, "", &mut out);
344 out
345 }
346
347 #[test]
351 fn every_cli_surface_declares_its_coverage() {
352 let discovered = discovered_surfaces();
353 let discovered_refs: Vec<&str> = discovered.iter().map(|s| s.as_str()).collect();
354 let vocab = verdict_axes();
355 let registry = surface_registry();
356 let findings = validate_coverage(&vocab, ®istry, &discovered_refs);
357 assert!(
358 findings.is_empty(),
359 "{} coverage finding(s):\n{}",
360 findings.len(),
361 findings.join("\n")
362 );
363 }
364}