1use std::collections::BTreeMap;
26use std::path::Path;
27
28use serde::{Deserialize, Serialize};
29
30use crate::configfile::{resolve, ResolveOptions, Resolved};
31use crate::presets;
32use crate::runtime::{RuntimeBackend, RuntimeCapabilities};
33use crate::tools::ToolRegistry;
34
35pub const LEDGER_JSON: &str = include_str!("parity/ledger.json");
37
38pub const ORCHESTRATION_JSON: &str = include_str!("parity/orchestration.json");
40
41pub const PARITY_PRESETS: &[(&str, &str)] = &[("cc", "cc-parity"), ("cx", "cx-parity")];
43
44pub const ORCHESTRATION_PRESETS: &[&str] = &["hermes", "openclaw"];
48
49pub const ORCHESTRATOR_PRESET: &str = "orchestrator";
63
64const HERMES_HELP_FIXTURE: &str = include_str!("parity/fixtures/hermes-help.txt");
66
67const OPENCLAW_HELP_FIXTURE: &str = include_str!("parity/fixtures/openclaw-help.txt");
69
70const STORE_SEARCH_ROOTS: &[&str] = &["crates/interchange/src", "crates/harness/src"];
72
73const STORE_OPEN_CALLS: &[&str] = &["SELECT", "open(", "open_with_flags(", ".join("];
77
78pub fn preset_names() -> Vec<&'static str> {
80 PARITY_PRESETS
81 .iter()
82 .map(|(_, preset)| *preset)
83 .chain(ORCHESTRATION_PRESETS.iter().copied())
84 .chain(std::iter::once(ORCHESTRATOR_PRESET))
85 .collect()
86}
87
88pub fn help_fixture(harness: &str) -> Option<&'static str> {
90 match harness {
91 "hermes" => Some(HERMES_HELP_FIXTURE),
92 "openclaw" => Some(OPENCLAW_HELP_FIXTURE),
93 _ => None,
94 }
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
99#[serde(rename_all = "snake_case")]
100pub enum Has {
101 Yes,
103 Variant,
105 Extension,
107 No,
109}
110
111impl Has {
112 pub fn present(self) -> bool {
114 !matches!(self, Has::No)
115 }
116}
117
118#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
120#[serde(rename_all = "snake_case")]
121pub enum Status {
122 Implemented,
124 Partial,
126 Absent,
128 Irreducible,
130 NotApplicable,
132 Unaudited,
134}
135
136impl Status {
137 pub fn is_gap(self) -> bool {
139 matches!(
140 self,
141 Status::Partial | Status::Absent | Status::Irreducible | Status::Unaudited
142 )
143 }
144
145 pub fn requires_evidence(self) -> bool {
147 matches!(self, Status::Implemented | Status::Partial)
148 }
149
150 fn label(self) -> &'static str {
151 match self {
152 Status::Implemented => "implemented",
153 Status::Partial => "partial",
154 Status::Absent => "absent",
155 Status::Irreducible => "irreducible",
156 Status::NotApplicable => "not_applicable",
157 Status::Unaudited => "unaudited",
158 }
159 }
160}
161
162#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
164#[serde(rename_all = "snake_case")]
165pub enum Cost {
166 Trivial,
168 Architectural,
170}
171
172#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
175#[serde(tag = "kind", rename_all = "snake_case")]
176pub enum Evidence {
177 Tool {
179 name: String,
181 },
182 Module {
184 name: String,
186 },
187 Config {
189 key: String,
191 },
192 Runtime {
194 capability: String,
196 },
197 Code {
201 path: String,
203 #[serde(default, skip_serializing_if = "Option::is_none")]
205 symbol: Option<String>,
206 },
207 CliVerb {
214 harness: String,
216 verb: String,
218 },
219 Store {
225 harness: String,
227 path: String,
229 },
230}
231
232#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
234pub struct Row {
235 pub id: String,
237 pub domain: u8,
239 pub domain_name: String,
241 pub capability: String,
243 pub semantics: String,
245 pub cc: Has,
247 pub cx: Has,
249 pub cc_detail: String,
251 pub cx_detail: String,
253 pub catalog_supercode_today: String,
256 pub provenance: String,
258 pub status: Status,
260 #[serde(default)]
262 pub evidence: Vec<Evidence>,
263 #[serde(default)]
265 pub note: String,
266 #[serde(default, skip_serializing_if = "Option::is_none")]
268 pub cost: Option<Cost>,
269}
270
271impl Row {
272 pub fn has(&self, column: &str) -> bool {
274 match column {
275 "cc" => self.cc.present(),
276 "cx" => self.cx.present(),
277 _ => false,
278 }
279 }
280
281 pub fn applicable_presets(&self) -> Vec<&'static str> {
283 PARITY_PRESETS
284 .iter()
285 .filter(|(column, _)| self.has(column))
286 .map(|(_, preset)| *preset)
287 .collect()
288 }
289}
290
291pub fn ledger() -> Vec<Row> {
293 serde_json::from_str(LEDGER_JSON).expect("embedded parity ledger is valid JSON")
294}
295
296pub const ORCHESTRATION_DOMAIN: u8 = 11;
300
301#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
308pub struct OrchestrationRow {
309 pub id: String,
311 pub concept: String,
313 pub verbs: String,
315 pub capability: String,
317 pub semantics: String,
319 pub hermes: Has,
321 pub openclaw: Has,
323 pub orchestrator: Has,
327 pub hermes_detail: String,
329 pub openclaw_detail: String,
331 pub orchestrator_detail: String,
333 #[serde(default)]
335 pub hermes_evidence: Vec<Evidence>,
336 #[serde(default)]
338 pub openclaw_evidence: Vec<Evidence>,
339 pub orchestrator_status: Status,
343 #[serde(default)]
346 pub orchestrator_evidence: Vec<Evidence>,
347 pub orchestrator_note: String,
350 #[serde(default, skip_serializing_if = "Option::is_none")]
352 pub orchestrator_cost: Option<Cost>,
353 pub provenance: String,
355 pub status: Status,
357 #[serde(default)]
359 pub evidence: Vec<Evidence>,
360 #[serde(default)]
362 pub note: String,
363 #[serde(default, skip_serializing_if = "Option::is_none")]
365 pub cost: Option<Cost>,
366}
367
368impl OrchestrationRow {
369 pub fn has(&self, harness: &str) -> bool {
371 self.column(harness)
372 .is_some_and(|(has, _, _)| has.present())
373 }
374
375 pub fn column(&self, harness: &str) -> Option<(Has, &str, &[Evidence])> {
377 match harness {
378 "hermes" => Some((
379 self.hermes,
380 self.hermes_detail.as_str(),
381 self.hermes_evidence.as_slice(),
382 )),
383 "openclaw" => Some((
384 self.openclaw,
385 self.openclaw_detail.as_str(),
386 self.openclaw_evidence.as_slice(),
387 )),
388 _ => None,
389 }
390 }
391}
392
393pub fn orchestration_ledger() -> Vec<OrchestrationRow> {
395 serde_json::from_str(ORCHESTRATION_JSON).expect("embedded orchestration ledger is valid JSON")
396}
397
398#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
400pub struct PresetSummary {
401 pub preset: String,
403 pub harness_column: String,
405 pub rows: usize,
407 pub counts: BTreeMap<String, usize>,
409 pub gaps: usize,
411}
412
413#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
415pub struct GapRow {
416 pub id: String,
418 pub domain: u8,
420 pub capability: String,
422 pub status: Status,
424 #[serde(default, skip_serializing_if = "Option::is_none")]
426 pub cost: Option<Cost>,
427 pub note: String,
429}
430
431#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
433pub struct PresetReport {
434 pub summary: PresetSummary,
436 pub gaps: Vec<GapRow>,
438}
439
440pub fn report(preset: &str) -> Option<PresetReport> {
443 if let Some((column, _)) = PARITY_PRESETS.iter().find(|(_, p)| *p == preset) {
444 return Some(catalog_report(preset, column));
445 }
446 if preset == ORCHESTRATOR_PRESET {
447 return Some(orchestrator_report());
448 }
449 if ORCHESTRATION_PRESETS.contains(&preset) {
450 return Some(orchestration_report(preset));
451 }
452 None
453}
454
455fn catalog_report(preset: &str, column: &str) -> PresetReport {
456 let rows: Vec<Row> = ledger().into_iter().filter(|r| r.has(column)).collect();
457 let mut counts: BTreeMap<String, usize> = BTreeMap::new();
458 let mut gaps = Vec::new();
459 for row in &rows {
460 *counts.entry(row.status.label().to_string()).or_default() += 1;
461 if row.status.is_gap() {
462 gaps.push(GapRow {
463 id: row.id.clone(),
464 domain: row.domain,
465 capability: row.capability.clone(),
466 status: row.status,
467 cost: row.cost,
468 note: row.note.clone(),
469 });
470 }
471 }
472 PresetReport {
473 summary: PresetSummary {
474 preset: preset.to_string(),
475 harness_column: column.to_string(),
476 rows: rows.len(),
477 counts,
478 gaps: gaps.len(),
479 },
480 gaps,
481 }
482}
483
484fn orchestration_report(harness: &str) -> PresetReport {
485 let rows: Vec<OrchestrationRow> = orchestration_ledger()
486 .into_iter()
487 .filter(|r| r.has(harness))
488 .collect();
489 let mut counts: BTreeMap<String, usize> = BTreeMap::new();
490 let mut gaps = Vec::new();
491 for row in &rows {
492 *counts.entry(row.status.label().to_string()).or_default() += 1;
493 if row.status.is_gap() {
494 gaps.push(GapRow {
495 id: row.id.clone(),
496 domain: ORCHESTRATION_DOMAIN,
497 capability: row.capability.clone(),
498 status: row.status,
499 cost: row.cost,
500 note: row.note.clone(),
501 });
502 }
503 }
504 PresetReport {
505 summary: PresetSummary {
506 preset: harness.to_string(),
507 harness_column: harness.to_string(),
508 rows: rows.len(),
509 counts,
510 gaps: gaps.len(),
511 },
512 gaps,
513 }
514}
515
516fn orchestrator_report() -> PresetReport {
522 let rows = orchestration_ledger();
523 let mut counts: BTreeMap<String, usize> = BTreeMap::new();
524 let mut gaps = Vec::new();
525 for row in &rows {
526 *counts
527 .entry(row.orchestrator_status.label().to_string())
528 .or_default() += 1;
529 if row.orchestrator_status.is_gap() {
530 gaps.push(GapRow {
531 id: row.id.clone(),
532 domain: ORCHESTRATION_DOMAIN,
533 capability: row.capability.clone(),
534 status: row.orchestrator_status,
535 cost: row.orchestrator_cost,
536 note: row.orchestrator_note.clone(),
537 });
538 }
539 }
540 PresetReport {
541 summary: PresetSummary {
542 preset: ORCHESTRATOR_PRESET.to_string(),
543 harness_column: ORCHESTRATOR_PRESET.to_string(),
544 rows: rows.len(),
545 counts,
546 gaps: gaps.len(),
547 },
548 gaps,
549 }
550}
551
552pub fn render(report: &PresetReport) -> String {
554 let s = &report.summary;
555 let mut out = format!("{}: {} rows · {} gaps", s.preset, s.rows, s.gaps);
556 for status in [
557 Status::Implemented,
558 Status::Partial,
559 Status::Absent,
560 Status::Irreducible,
561 Status::NotApplicable,
565 Status::Unaudited,
566 ] {
567 if let Some(n) = s.counts.get(status.label()) {
568 out.push_str(&format!(" · {n} {}", status.label()));
569 }
570 }
571 out.push('\n');
572 let mut domain = 0u8;
573 for gap in &report.gaps {
574 if gap.domain != domain {
575 domain = gap.domain;
576 out.push_str(&format!("\nDomain {domain}\n"));
577 }
578 let cost = match gap.cost {
579 Some(Cost::Trivial) => " [trivial]",
580 Some(Cost::Architectural) => " [architectural]",
581 None => "",
582 };
583 out.push_str(&format!(
584 " {:<12}{cost} {} ({})",
585 gap.status.label(),
586 gap.capability,
587 gap.id
588 ));
589 if !gap.note.is_empty() {
590 out.push_str(&format!(" — {}", gap.note));
591 }
592 out.push('\n');
593 }
594 out
595}
596
597pub fn resolve_preset(preset: &str) -> Result<Resolved, String> {
599 let toml = presets::lookup(preset).ok_or_else(|| format!("unknown preset `{preset}`"))?;
600 resolve(toml, None, &ResolveOptions { strict: true }).map_err(|e| e.to_string())
601}
602
603fn runtime_flag(capabilities: &RuntimeCapabilities, flag: &str) -> Option<bool> {
604 Some(match flag {
605 "start_session" => capabilities.start_session,
606 "resume_session" => capabilities.resume_session,
607 "attach_existing_process" => capabilities.attach_existing_process,
608 "send_input" => capabilities.send_input,
609 "stream_events" => capabilities.stream_events,
610 "interrupt" => capabilities.interrupt,
611 "steer" => capabilities.steer,
612 "respond_to_requests" => capabilities.respond_to_requests,
613 _ => return None,
614 })
615}
616
617fn backend_capabilities(column: &str) -> RuntimeCapabilities {
618 match column {
619 "cc" => crate::runtime::ClaudeCodeRuntimeBackend::default().capabilities(),
620 "cx" => crate::runtime::CodexRuntimeBackend::default().capabilities(),
621 other => panic!("no runtime backend for column `{other}`"),
622 }
623}
624
625fn check_cli_verb(harness: &str, verb: &str) -> Result<(), String> {
633 let fixture = help_fixture(harness)
634 .ok_or_else(|| format!("no committed help fixture for harness `{harness}`"))?;
635 let mut parts: Vec<&str> = verb.split_whitespace().collect();
636 let leaf = parts.pop().ok_or_else(|| "empty cli verb".to_string())?;
637 let header = if parts.is_empty() {
638 format!("$ {harness} --help")
639 } else {
640 format!("$ {harness} {} --help", parts.join(" "))
641 };
642 let mut in_section = false;
643 let mut saw_section = false;
644 for line in fixture.lines() {
645 if line.starts_with("$ ") {
646 in_section = line.trim() == header;
647 saw_section |= in_section;
648 continue;
649 }
650 if !in_section || line.starts_with('#') {
651 continue;
652 }
653 let indent = line.len() - line.trim_start().len();
656 if !(2..=6).contains(&indent) {
657 continue;
658 }
659 let Some(token) = line.split_whitespace().next() else {
660 continue;
661 };
662 if token.starts_with('-') {
663 continue;
664 }
665 if token.split('|').any(|alias| alias == leaf) {
666 return Ok(());
667 }
668 }
669 if !saw_section {
670 return Err(format!(
671 "`{header}` is not a section of the {harness} help fixture"
672 ));
673 }
674 Err(format!(
675 "`{harness} {verb}` is not advertised under `{header}`"
676 ))
677}
678
679fn push_rust_sources(dir: &Path, out: &mut Vec<std::path::PathBuf>) {
680 let Ok(entries) = std::fs::read_dir(dir) else {
681 return;
682 };
683 for entry in entries.flatten() {
684 let path = entry.path();
685 if path.is_dir() {
686 push_rust_sources(&path, out);
687 } else if path.extension().is_some_and(|ext| ext == "rs") {
688 out.push(path);
689 }
690 }
691}
692
693fn store_segment_is_opened(segment: &str, workspace_root: &Path) -> bool {
698 let mut files = Vec::new();
699 for root in STORE_SEARCH_ROOTS {
700 push_rust_sources(&workspace_root.join(root), &mut files);
701 }
702 for file in files {
703 let Ok(text) = std::fs::read_to_string(&file) else {
704 continue;
705 };
706 let lines: Vec<&str> = text.lines().collect();
707 for (index, line) in lines.iter().enumerate() {
708 if !line.contains(segment) || line.trim_start().starts_with("//") {
709 continue;
710 }
711 let start = index.saturating_sub(3);
712 let context = lines[start..=index].join("\n");
713 if STORE_OPEN_CALLS.iter().any(|call| context.contains(call)) {
714 return true;
715 }
716 }
717 }
718 false
719}
720
721fn check_store(harness: &str, path: &str, workspace_root: &Path) -> Result<(), String> {
724 if !ORCHESTRATION_PRESETS.contains(&harness) && harness != ORCHESTRATOR_PRESET {
725 return Err(format!(
726 "`{harness}` is not an orchestration harness or the orchestrator"
727 ));
728 }
729 let mut checked = 0usize;
730 for segment in path.split('/') {
731 if segment.is_empty() || (segment.starts_with('<') && segment.ends_with('>')) {
732 continue;
733 }
734 if segment.len() < 3 {
735 return Err(format!(
736 "store segment `{segment}` is too short to identify a store"
737 ));
738 }
739 if !store_segment_is_opened(segment, workspace_root) {
740 return Err(format!(
741 "no loader under {} opens `{segment}` (from {harness} store `{path}`)",
742 STORE_SEARCH_ROOTS.join(", ")
743 ));
744 }
745 checked += 1;
746 }
747 if checked == 0 {
748 return Err(format!("store path `{path}` names no concrete segment"));
749 }
750 Ok(())
751}
752
753fn toml_has_key(doc: &toml::Value, key: &str) -> bool {
754 let mut cur = doc;
755 for part in key.split('.') {
756 match cur.get(part) {
757 Some(next) => cur = next,
758 None => return false,
759 }
760 }
761 true
762}
763
764pub fn check_evidence(
767 row: &Row,
768 evidence: &Evidence,
769 workspace_root: &std::path::Path,
770) -> Result<(), String> {
771 let applicable: Vec<(&str, &str)> = PARITY_PRESETS
772 .iter()
773 .filter(|(column, _)| row.has(column))
774 .map(|(c, p)| (*c, *p))
775 .collect();
776 if applicable.is_empty() {
777 return Err("row has no applicable preset (neither cc nor cx has it)".into());
778 }
779 match evidence {
780 Evidence::Tool { name } => {
781 for (_, preset) in &applicable {
782 let resolved = resolve_preset(preset)?;
783 let registry = ToolRegistry::from_config(&resolved.config);
784 if registry.get(name).is_none() {
785 return Err(format!("tool `{name}` is not registered under `{preset}`"));
786 }
787 }
788 Ok(())
789 }
790 Evidence::Module { name } => {
791 for (_, preset) in &applicable {
792 let resolved = resolve_preset(preset)?;
793 match resolved.modules.get(name) {
794 Some(true) => {}
795 Some(false) => {
796 return Err(format!("module `{name}` is disabled under `{preset}`"))
797 }
798 None => return Err(format!("module `{name}` is not a known module")),
799 }
800 }
801 Ok(())
802 }
803 Evidence::Config { key } => {
804 for (_, preset) in &applicable {
805 let text =
806 presets::lookup(preset).ok_or_else(|| format!("unknown preset `{preset}`"))?;
807 let doc: toml::Value = toml::from_str(text).map_err(|e| e.to_string())?;
808 if !toml_has_key(&doc, key) {
809 return Err(format!("`{preset}` does not set `{key}`"));
810 }
811 }
812 Ok(())
813 }
814 Evidence::Runtime { capability } => {
815 for (column, _) in &applicable {
816 let caps = backend_capabilities(column);
817 match runtime_flag(&caps, capability) {
818 Some(true) => {}
819 Some(false) => {
820 return Err(format!(
821 "runtime capability `{capability}` is false for `{column}`"
822 ))
823 }
824 None => return Err(format!("`{capability}` is not a runtime capability flag")),
825 }
826 }
827 Ok(())
828 }
829 Evidence::Code { .. } | Evidence::CliVerb { .. } | Evidence::Store { .. } => {
830 check_source_evidence(evidence, workspace_root)
831 }
832 }
833}
834
835pub fn check_source_evidence(
839 evidence: &Evidence,
840 workspace_root: &std::path::Path,
841) -> Result<(), String> {
842 match evidence {
843 Evidence::Code { path, symbol } => {
844 let full = workspace_root.join(path);
845 let text =
846 std::fs::read_to_string(&full).map_err(|e| format!("cannot read `{path}`: {e}"))?;
847 if let Some(symbol) = symbol {
848 if !text.contains(symbol.as_str()) {
849 return Err(format!("`{path}` does not contain `{symbol}`"));
850 }
851 }
852 Ok(())
853 }
854 Evidence::CliVerb { harness, verb } => check_cli_verb(harness, verb),
855 Evidence::Store { harness, path } => check_store(harness, path, workspace_root),
856 other => Err(format!(
857 "{other:?} needs a preset context; use `check_evidence`"
858 )),
859 }
860}
861
862pub fn check_orchestration_evidence(
870 lane: OrchestrationLane<'_>,
871 evidence: &Evidence,
872 workspace_root: &std::path::Path,
873) -> Result<(), String> {
874 match (lane, evidence) {
875 (OrchestrationLane::Column(harness), Evidence::CliVerb { harness: cited, .. }) => {
876 if cited != harness {
877 return Err(format!(
878 "the {harness} column cites a `{cited}` verb ({evidence:?})"
879 ));
880 }
881 check_source_evidence(evidence, workspace_root)
882 }
883 (OrchestrationLane::Column(harness), other) => Err(format!(
884 "the {harness} column may only cite `cli_verb`, not {other:?}"
885 )),
886 (OrchestrationLane::Supercode, Evidence::Store { .. } | Evidence::Code { .. }) => {
887 check_source_evidence(evidence, workspace_root)
888 }
889 (OrchestrationLane::Supercode, other) => Err(format!(
890 "a supercode status may only cite `store` or `code`, not {other:?}"
891 )),
892 }
893}
894
895#[derive(Debug, Clone, Copy, PartialEq, Eq)]
897pub enum OrchestrationLane<'a> {
898 Column(&'a str),
900 Supercode,
902}
903
904#[cfg(test)]
905mod tests {
906 use super::*;
907 use std::collections::HashSet;
908 use std::path::PathBuf;
909
910 fn workspace_root() -> PathBuf {
911 PathBuf::from(env!("CARGO_MANIFEST_DIR"))
912 .join("../..")
913 .canonicalize()
914 .unwrap()
915 }
916
917 #[test]
918 fn ledger_parses_with_unique_ids_and_full_catalog() {
919 let rows = ledger();
920 assert_eq!(rows.len(), 263, "one row per catalog capability");
921 let ids: HashSet<&str> = rows.iter().map(|r| r.id.as_str()).collect();
922 assert_eq!(ids.len(), rows.len(), "row ids must be unique");
923 for row in &rows {
924 assert!(
925 (1..=11).contains(&row.domain),
926 "{}: domain out of range",
927 row.id
928 );
929 }
930 }
931
932 #[test]
933 fn not_applicable_rows_are_exactly_those_neither_harness_has() {
934 for row in ledger() {
935 let neither = !row.cc.present() && !row.cx.present();
936 assert_eq!(
937 row.status == Status::NotApplicable,
938 neither,
939 "{}: not_applicable must mean neither cc nor cx has it",
940 row.id
941 );
942 }
943 }
944
945 #[test]
948 fn no_row_remains_unaudited() {
949 let mut stale: Vec<String> = ledger()
950 .into_iter()
951 .filter(|r| r.status == Status::Unaudited)
952 .map(|r| r.id)
953 .collect();
954 stale.extend(
955 orchestration_ledger()
956 .into_iter()
957 .filter(|r| r.status == Status::Unaudited)
958 .map(|r| r.id),
959 );
960 assert!(stale.is_empty(), "unaudited rows: {stale:?}");
961 }
962
963 #[test]
964 fn both_parity_presets_resolve_strictly() {
965 for (_, preset) in PARITY_PRESETS {
966 resolve_preset(preset).unwrap_or_else(|e| panic!("{preset}: {e}"));
967 }
968 }
969
970 #[test]
973 fn every_audited_claim_is_backed_by_resolvable_evidence() {
974 let root = workspace_root();
975 let mut failures = Vec::new();
976 for row in ledger() {
977 if row.status.requires_evidence() && row.evidence.is_empty() {
978 failures.push(format!(
979 "{}: `{}` cites no evidence",
980 row.id,
981 row.status.label()
982 ));
983 }
984 if row.status == Status::Irreducible && row.note.is_empty() {
985 failures.push(format!("{}: irreducible without a note", row.id));
986 }
987 if row.status == Status::Absent && row.cost.is_none() {
988 failures.push(format!("{}: absent without a cost class", row.id));
989 }
990 for ev in &row.evidence {
991 if let Err(reason) = check_evidence(&row, ev, &root) {
992 failures.push(format!("{}: {reason}", row.id));
993 }
994 }
995 }
996 assert!(
997 failures.is_empty(),
998 "ledger evidence failures:\n{}",
999 failures.join("\n")
1000 );
1001 }
1002
1003 #[test]
1005 fn evidence_gate_rejects_unresolvable_citations() {
1006 let root = workspace_root();
1007 let mut row = ledger().into_iter().find(|r| r.cc.present()).unwrap();
1008 row.cx = Has::No;
1009 let bad = [
1010 Evidence::Tool {
1011 name: "no_such_tool".into(),
1012 },
1013 Evidence::Module {
1014 name: "no_such_module".into(),
1015 },
1016 Evidence::Module {
1017 name: "model_oauth".into(),
1018 }, Evidence::Config {
1020 key: "capabilities.no_such.key".into(),
1021 },
1022 Evidence::Runtime {
1023 capability: "attach_existing_process".into(),
1024 }, Evidence::Runtime {
1026 capability: "not_a_flag".into(),
1027 },
1028 Evidence::Code {
1029 path: "crates/harness/src/no_such_file.rs".into(),
1030 symbol: None,
1031 },
1032 Evidence::Code {
1033 path: "crates/harness/src/parity.rs".into(),
1034 symbol: Some(["ZZZ_NOT", "_PRESENT_ZZZ"].concat()),
1036 },
1037 ];
1038 for ev in bad {
1039 assert!(
1040 check_evidence(&row, &ev, &root).is_err(),
1041 "{ev:?} must be rejected"
1042 );
1043 }
1044 let good = [
1045 Evidence::Tool {
1046 name: "read_file".into(),
1047 },
1048 Evidence::Module {
1049 name: "subagents".into(),
1050 },
1051 Evidence::Config {
1052 key: "capabilities.subagents".into(),
1053 },
1054 Evidence::Runtime {
1055 capability: "steer".into(),
1056 },
1057 Evidence::Code {
1058 path: "crates/harness/src/parity.rs".into(),
1059 symbol: Some("pub fn check_evidence".into()),
1060 },
1061 ];
1062 for ev in good {
1063 check_evidence(&row, &ev, &root).unwrap_or_else(|e| panic!("{ev:?}: {e}"));
1064 }
1065 row.cc = Has::No;
1067 assert!(check_evidence(
1068 &row,
1069 &Evidence::Tool {
1070 name: "read_file".into()
1071 },
1072 &root
1073 )
1074 .is_err());
1075 }
1076
1077 #[test]
1078 fn report_counts_add_up() {
1079 for preset in preset_names() {
1080 let r = report(preset).unwrap();
1081 let total: usize = r.summary.counts.values().sum();
1082 assert_eq!(total, r.summary.rows);
1083 assert_eq!(r.gaps.len(), r.summary.gaps);
1084 assert!(!render(&r).is_empty());
1085 }
1086 assert!(report("pi-core").is_none());
1087 }
1088
1089 #[test]
1092 fn preset_names_cover_both_ledgers() {
1093 assert_eq!(
1094 preset_names(),
1095 vec![
1096 "cc-parity",
1097 "cx-parity",
1098 "hermes",
1099 "openclaw",
1100 "orchestrator"
1101 ]
1102 );
1103 for harness in ORCHESTRATION_PRESETS {
1104 let r = report(harness).unwrap();
1105 assert_eq!(&r.summary.preset, harness);
1106 assert_eq!(&r.summary.harness_column, harness);
1107 assert!(r.summary.rows > 0, "{harness}: empty denominator");
1108 let rendered = render(&r);
1109 assert!(
1110 rendered.starts_with(&format!("{harness}: {} rows · ", r.summary.rows)),
1111 "{harness}: unexpected headline: {rendered}"
1112 );
1113 assert!(rendered.contains("\nDomain 11\n"), "{harness}: {rendered}");
1114 }
1115 }
1116
1117 #[test]
1124 fn the_orchestrator_column_is_graded_on_every_row_with_resolvable_evidence() {
1125 let root = workspace_root();
1126 let rows = orchestration_ledger();
1127 let report = report(ORCHESTRATOR_PRESET).unwrap();
1128 assert_eq!(
1129 report.summary.rows,
1130 rows.len(),
1131 "the orchestrator is graded on every row, never a filtered subset"
1132 );
1133 let mut failures = Vec::new();
1134 for row in &rows {
1135 if row.orchestrator.present() && row.orchestrator_detail.is_empty() {
1136 failures.push(format!("{}: orchestrator column has no detail", row.id));
1137 }
1138 if !row.orchestrator.present() && row.orchestrator_status != Status::NotApplicable {
1139 failures.push(format!(
1140 "{}: the orchestrator lacks this row but is graded `{}`",
1141 row.id,
1142 row.orchestrator_status.label()
1143 ));
1144 }
1145 if !row.orchestrator.present() && !row.orchestrator_evidence.is_empty() {
1146 failures.push(format!("{}: a `no` column cites evidence", row.id));
1147 }
1148 if row.orchestrator_status.requires_evidence() && row.orchestrator_evidence.is_empty() {
1149 failures.push(format!(
1150 "{}: orchestrator `{}` cites no evidence",
1151 row.id,
1152 row.orchestrator_status.label()
1153 ));
1154 }
1155 if row.orchestrator_status == Status::Absent && row.orchestrator_cost.is_none() {
1156 failures.push(format!(
1157 "{}: orchestrator absent without a cost class",
1158 row.id
1159 ));
1160 }
1161 if row.orchestrator_note.is_empty() {
1162 failures.push(format!("{}: no orchestrator note", row.id));
1163 }
1164 for ev in &row.orchestrator_evidence {
1165 if let Err(reason) =
1166 check_orchestration_evidence(OrchestrationLane::Supercode, ev, &root)
1167 {
1168 failures.push(format!("{}: {reason}", row.id));
1169 }
1170 }
1171 }
1172 assert!(
1173 failures.is_empty(),
1174 "orchestrator column failures:\n{}",
1175 failures.join("\n")
1176 );
1177 let rendered = render(&report);
1179 assert!(
1180 rendered.starts_with(&format!(
1181 "orchestrator: {} rows · {} gaps",
1182 report.summary.rows, report.summary.gaps
1183 )),
1184 "{rendered}"
1185 );
1186 }
1187
1188 #[test]
1191 fn orchestrator_store_citations_resolve_like_every_other_supercode_status() {
1192 let root = workspace_root();
1193 check_orchestration_evidence(
1194 OrchestrationLane::Supercode,
1195 &Evidence::Store {
1196 harness: ORCHESTRATOR_PRESET.into(),
1197 path: "cron/jobs.json".into(),
1198 },
1199 &root,
1200 )
1201 .unwrap();
1202 assert!(check_orchestration_evidence(
1203 OrchestrationLane::Supercode,
1204 &Evidence::Store {
1205 harness: ORCHESTRATOR_PRESET.into(),
1206 path: "cron/no_such_store.json".into(),
1207 },
1208 &root,
1209 )
1210 .is_err());
1211 }
1212
1213 #[test]
1214 fn orchestration_ledger_parses_with_unique_ids_and_every_concept() {
1215 let rows = orchestration_ledger();
1216 let ids: HashSet<&str> = rows.iter().map(|r| r.id.as_str()).collect();
1217 assert_eq!(ids.len(), rows.len(), "row ids must be unique");
1218 let concepts: HashSet<&str> = rows.iter().map(|r| r.concept.as_str()).collect();
1219 let expected: HashSet<&str> = crate::support::ORCHESTRATION_CONCEPTS
1220 .iter()
1221 .copied()
1222 .collect();
1223 assert_eq!(
1224 concepts, expected,
1225 "every ORCHESTRATION_CONCEPTS entry needs at least one row, and no others"
1226 );
1227 for row in &rows {
1228 assert!(!row.verbs.is_empty(), "{}: no verb group", row.id);
1229 assert!(!row.semantics.is_empty(), "{}: no semantics", row.id);
1230 assert!(!row.provenance.is_empty(), "{}: no provenance", row.id);
1231 }
1232 }
1233
1234 #[test]
1235 fn orchestration_not_applicable_rows_are_exactly_those_neither_harness_has() {
1236 for row in orchestration_ledger() {
1237 let neither = !row.hermes.present() && !row.openclaw.present();
1238 assert_eq!(
1239 row.status == Status::NotApplicable,
1240 neither,
1241 "{}: not_applicable must mean neither hermes nor openclaw has it",
1242 row.id
1243 );
1244 }
1245 }
1246
1247 #[test]
1252 fn every_orchestration_claim_is_backed_by_resolvable_evidence() {
1253 let root = workspace_root();
1254 let mut failures = Vec::new();
1255 for row in orchestration_ledger() {
1256 for harness in ORCHESTRATION_PRESETS {
1257 let (has, detail, evidence) = row.column(harness).unwrap();
1258 if has.present() {
1259 if detail.is_empty() {
1260 failures.push(format!("{}: {harness} column has no detail", row.id));
1261 }
1262 if evidence.is_empty() {
1263 failures.push(format!("{}: {harness} column cites no verb", row.id));
1264 }
1265 } else if !evidence.is_empty() {
1266 failures.push(format!(
1267 "{}: {harness} lacks the row but cites {evidence:?}",
1268 row.id
1269 ));
1270 }
1271 for ev in evidence {
1272 if let Err(reason) =
1273 check_orchestration_evidence(OrchestrationLane::Column(harness), ev, &root)
1274 {
1275 failures.push(format!("{}: {reason}", row.id));
1276 }
1277 }
1278 }
1279 if row.status.requires_evidence() && row.evidence.is_empty() {
1280 failures.push(format!(
1281 "{}: `{}` cites no evidence",
1282 row.id,
1283 row.status.label()
1284 ));
1285 }
1286 if row.status == Status::Absent && row.cost.is_none() {
1287 failures.push(format!("{}: absent without a cost class", row.id));
1288 }
1289 if row.note.is_empty() {
1290 failures.push(format!("{}: no note", row.id));
1291 }
1292 for ev in &row.evidence {
1293 if let Err(reason) =
1294 check_orchestration_evidence(OrchestrationLane::Supercode, ev, &root)
1295 {
1296 failures.push(format!("{}: {reason}", row.id));
1297 }
1298 }
1299 }
1300 assert!(
1301 failures.is_empty(),
1302 "orchestration ledger evidence failures:\n{}",
1303 failures.join("\n")
1304 );
1305 }
1306
1307 #[test]
1311 fn orchestration_evidence_gate_rejects_unresolvable_citations() {
1312 let root = workspace_root();
1313 let bad = [
1314 Evidence::CliVerb {
1317 harness: "hermes".into(),
1318 verb: "cron teleport".into(),
1319 },
1320 Evidence::CliVerb {
1321 harness: "hermes".into(),
1322 verb: "approvals list".into(),
1323 },
1324 Evidence::CliVerb {
1325 harness: "openclaw".into(),
1326 verb: "sessions archive".into(),
1327 },
1328 Evidence::CliVerb {
1329 harness: "openclaw".into(),
1330 verb: "approvals resolve".into(),
1331 },
1332 Evidence::CliVerb {
1334 harness: "hermes".into(),
1335 verb: "kanban list".into(),
1336 },
1337 Evidence::CliVerb {
1339 harness: "claude-code".into(),
1340 verb: "cron list".into(),
1341 },
1342 Evidence::Store {
1347 harness: "openclaw".into(),
1348 path: "state/openclaw.sqlite/delivery_queue_entries".into(),
1349 },
1350 Evidence::Store {
1353 harness: "openclaw".into(),
1354 path: "cron_run_receipts".into(),
1355 },
1356 Evidence::Store {
1357 harness: "grok".into(),
1358 path: "state.db".into(),
1359 },
1360 Evidence::Store {
1361 harness: "hermes".into(),
1362 path: "<agentId>".into(),
1363 },
1364 ];
1365 for ev in &bad {
1366 let lane = match ev {
1367 Evidence::CliVerb { harness, .. } => OrchestrationLane::Column(harness),
1368 _ => OrchestrationLane::Supercode,
1369 };
1370 assert!(
1371 check_orchestration_evidence(lane, ev, &root).is_err(),
1372 "{ev:?} must be rejected"
1373 );
1374 }
1375 let good = [
1376 Evidence::CliVerb {
1377 harness: "hermes".into(),
1378 verb: "cron list".into(),
1379 },
1380 Evidence::CliVerb {
1381 harness: "openclaw".into(),
1382 verb: "agents bindings".into(),
1383 },
1384 Evidence::Store {
1385 harness: "hermes".into(),
1386 path: "state.db/sessions/session_key".into(),
1387 },
1388 Evidence::Store {
1391 harness: "hermes".into(),
1392 path: "profiles/<name>/cron/jobs.json".into(),
1393 },
1394 Evidence::Store {
1395 harness: "openclaw".into(),
1396 path: "cron/jobs.json".into(),
1397 },
1398 Evidence::Store {
1401 harness: "hermes".into(),
1402 path: "cron/executions.db".into(),
1403 },
1404 Evidence::Store {
1405 harness: "openclaw".into(),
1406 path: "state/openclaw.sqlite/cron_run_logs".into(),
1407 },
1408 Evidence::Store {
1411 harness: "hermes".into(),
1412 path: "state.db/delivery_obligations".into(),
1413 },
1414 ];
1415 for ev in &good {
1416 let lane = match ev {
1417 Evidence::CliVerb { harness, .. } => OrchestrationLane::Column(harness),
1418 _ => OrchestrationLane::Supercode,
1419 };
1420 check_orchestration_evidence(lane, ev, &root).unwrap_or_else(|e| panic!("{ev:?}: {e}"));
1421 }
1422 assert!(
1425 check_orchestration_evidence(OrchestrationLane::Supercode, &good[0], &root).is_err()
1426 );
1427 assert!(
1428 check_orchestration_evidence(OrchestrationLane::Column("hermes"), &good[2], &root)
1429 .is_err()
1430 );
1431 assert!(check_orchestration_evidence(
1432 OrchestrationLane::Column("openclaw"),
1433 &good[0],
1434 &root
1435 )
1436 .is_err());
1437 }
1438
1439 #[test]
1442 fn help_fixtures_record_the_pin_and_how_to_recapture() {
1443 for harness in ORCHESTRATION_PRESETS {
1444 let fixture = help_fixture(harness).expect("committed fixture");
1445 let mut lines = fixture.lines();
1446 let first = lines.next().unwrap_or_default();
1447 assert!(
1448 first.starts_with(&format!("# fixture: {harness} CLI help @ ")),
1449 "{harness}: first line must name the harness and the pinned version: {first}"
1450 );
1451 assert!(
1452 first.trim_end().len() > format!("# fixture: {harness} CLI help @ ").len(),
1453 "{harness}: no pinned version in `{first}`"
1454 );
1455 let recapture = fixture
1456 .lines()
1457 .find(|line| line.starts_with("# recapture:"))
1458 .unwrap_or_else(|| panic!("{harness}: no `# recapture:` header line"));
1459 assert!(
1460 recapture.contains("--help"),
1461 "{harness}: recapture line names no command: {recapture}"
1462 );
1463 assert!(
1464 fixture
1465 .lines()
1466 .any(|line| line.starts_with("# provenance:")),
1467 "{harness}: no `# provenance:` header line"
1468 );
1469 assert!(
1470 fixture
1471 .lines()
1472 .any(|line| line.starts_with(&format!("$ {harness} "))),
1473 "{harness}: fixture captures no `$ {harness} ... --help` section"
1474 );
1475 }
1476 assert!(help_fixture("claude-code").is_none());
1477 }
1478}