Skip to main content

supercode_harness/
parity.rs

1//! Behavior-parity ledger: the product's own gap count, for the `cc-parity`
2//! and `cx-parity` presets and for the orchestration harnesses.
3//!
4//! Two embedded ledgers, one report shape:
5//!
6//! * `parity/ledger.json` — one row per capability in
7//!   `docs/composable-harness/CAPABILITY-CATALOG.md` (263 rows, ids derived
8//!   from the capability name), columns `cc` / `cx`.
9//! * `parity/orchestration.json` — one row per (orchestration concept × verb
10//!   group) from `docs/composable-harness/inventory/orchestration.md`,
11//!   columns `hermes` / `openclaw`.
12//!
13//! Each row records which harness has the capability and supercode's audited
14//! status, with EVIDENCE that the test suite resolves mechanically against
15//! the real preset resolver, module registry, tool registry, runtime
16//! backends, the committed harness `--help` fixtures and the source tree —
17//! an "implemented" claim that cites nothing checkable does not parse as
18//! implemented. The ledger is the denominator for the parity program:
19//! `supercode harness parity --preset cc-parity|cx-parity|hermes|openclaw`
20//! prints the headline count, and only zero is done.
21//!
22//! The embedded JSON is the single source; the catalog's "supercode today"
23//! column is a snapshot of the survey and is NOT consulted at runtime.
24
25use 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
35/// Raw ledger rows, embedded at build time.
36pub const LEDGER_JSON: &str = include_str!("parity/ledger.json");
37
38/// Raw orchestration ledger rows, embedded at build time.
39pub const ORCHESTRATION_JSON: &str = include_str!("parity/orchestration.json");
40
41/// Presets the ledger reports on, keyed by the catalog column they mirror.
42pub const PARITY_PRESETS: &[(&str, &str)] = &[("cc", "cc-parity"), ("cx", "cx-parity")];
43
44/// Orchestration harnesses the orchestration ledger reports on. The preset
45/// name IS the column name (there is no `*-parity` config preset behind
46/// these: supercode does not emulate a gateway, it reports on one).
47pub const ORCHESTRATION_PRESETS: &[&str] = &["hermes", "openclaw"];
48
49/// ORC-7: supercode's OWN orchestrator, graded on the same 17 rows through
50/// its own column.
51///
52/// It is not one of [`ORCHESTRATION_PRESETS`]: those are external harnesses
53/// whose columns cite a verb their PINNED CLI advertises, checked against a
54/// committed `--help` fixture. The orchestrator has no such CLI — it is a
55/// package in this workspace — so its column is graded like a supercode
56/// status, from `store` and `code` citations that resolve here.
57///
58/// Its denominator is the WHOLE ledger: every concept is one the
59/// orchestrator's own model either has or deliberately does not
60/// (`docs/ORCHESTRATOR-IR.md` §2), so a row it lacks is reported as
61/// `not_applicable` rather than dropped from the count.
62pub const ORCHESTRATOR_PRESET: &str = "orchestrator";
63
64/// `hermes --help` capture for the pinned version (see the fixture header).
65const HERMES_HELP_FIXTURE: &str = include_str!("parity/fixtures/hermes-help.txt");
66
67/// `openclaw --help` capture for the pinned version (see the fixture header).
68const OPENCLAW_HELP_FIXTURE: &str = include_str!("parity/fixtures/openclaw-help.txt");
69
70/// Workspace directories searched for a loader that opens a harness store.
71const STORE_SEARCH_ROOTS: &[&str] = &["crates/interchange/src", "crates/harness/src"];
72
73/// Call forms that count as "a loader opens this": a SQL read, a file open,
74/// or a path join. A bare mention (comment, doc-comment, error string) does
75/// not resolve a [`Evidence::Store`] citation.
76const STORE_OPEN_CALLS: &[&str] = &["SELECT", "open(", "open_with_flags(", ".join("];
77
78/// Every preset name `report` accepts, in product order.
79pub 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
88/// The committed `--help` fixture for a pinned orchestration harness CLI.
89pub 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/// Whether a harness has a capability (the catalog's per-harness column).
98#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
99#[serde(rename_all = "snake_case")]
100pub enum Has {
101    /// `✓` — has it as described.
102    Yes,
103    /// `✓*` — has it with a footnoted variant.
104    Variant,
105    /// `ext` — only through an extension, plugin, or example.
106    Extension,
107    /// `—` — lacks it.
108    No,
109}
110
111impl Has {
112    /// True for every column value except `—`.
113    pub fn present(self) -> bool {
114        !matches!(self, Has::No)
115    }
116}
117
118/// supercode's audited status for a row.
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
120#[serde(rename_all = "snake_case")]
121pub enum Status {
122    /// Behaves as the catalog row describes, under the applicable preset(s).
123    Implemented,
124    /// Some of the row's semantics exist; the note names what is missing.
125    Partial,
126    /// Nothing exists yet.
127    Absent,
128    /// Cannot be reproduced by design (the note cites the design-doc ledger).
129    Irreducible,
130    /// Neither Claude Code nor Codex has it; outside the parity program.
131    NotApplicable,
132    /// Seeded from the catalog and not yet audited against current code.
133    Unaudited,
134}
135
136impl Status {
137    /// Whether the row counts against the preset's gap headline.
138    pub fn is_gap(self) -> bool {
139        matches!(
140            self,
141            Status::Partial | Status::Absent | Status::Irreducible | Status::Unaudited
142        )
143    }
144
145    /// Whether the row must cite at least one checkable evidence item.
146    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/// Adoption cost class for an absent row (the catalog's §4 split).
163#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
164#[serde(rename_all = "snake_case")]
165pub enum Cost {
166    /// Config knob, small tool, or prompt-assembly change; no new subsystem.
167    Trivial,
168    /// New subsystem or cross-cutting contract.
169    Architectural,
170}
171
172/// A mechanically checkable citation. Every variant is resolved by
173/// [`check_evidence`] against the applicable preset(s) or the source tree.
174#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
175#[serde(tag = "kind", rename_all = "snake_case")]
176pub enum Evidence {
177    /// A tool registered under the applicable preset's resolved config.
178    Tool {
179        /// Registered tool name (e.g. `read_file`).
180        name: String,
181    },
182    /// A capability module enabled in the applicable preset(s).
183    Module {
184        /// Capability module name (a `MODULE_NAMES` entry).
185        name: String,
186    },
187    /// A dotted key that the applicable preset TOML sets explicitly.
188    Config {
189        /// Dotted TOML key path (e.g. `core.tools.edit_file.require_read_before_edit`).
190        key: String,
191    },
192    /// A live-runtime capability flag on the harness's own backend.
193    Runtime {
194        /// `RuntimeCapabilities` flag name (e.g. `steer`).
195        capability: String,
196    },
197    /// A source file (workspace-relative) that must exist and, when
198    /// `symbol` is given, contain that text. The weakest kind; for loop
199    /// mechanics that no config key or tool name expresses.
200    Code {
201        /// Workspace-relative source path.
202        path: String,
203        /// Text the file must contain.
204        #[serde(default, skip_serializing_if = "Option::is_none")]
205        symbol: Option<String>,
206    },
207    /// A subcommand the PINNED harness CLI advertises. Resolved against the
208    /// committed capture `parity/fixtures/<harness>-help.txt`: the verb's
209    /// leaf must appear as a subcommand entry inside the
210    /// `$ <harness> <parents> --help` section. Backs a harness column, never
211    /// a supercode status — that a harness has a verb says nothing about
212    /// whether supercode calls it.
213    CliVerb {
214        /// One of [`ORCHESTRATION_PRESETS`].
215        harness: String,
216        /// Space-separated verb path (`cron list`, `agents bindings`, `acp`).
217        verb: String,
218    },
219    /// A harness store (file path, table, or column) that a loader in THIS
220    /// workspace actually opens. Resolved by a symbol search over
221    /// [`STORE_SEARCH_ROOTS`]: every non-placeholder segment of `path` must
222    /// appear in a [`STORE_OPEN_CALLS`] form on a non-comment line.
223    /// `<...>` segments are placeholders and are skipped.
224    Store {
225        /// The store's owner, one of [`ORCHESTRATION_PRESETS`].
226        harness: String,
227        /// `/`-separated store path, e.g. `state.db/sessions/session_key`.
228        path: String,
229    },
230}
231
232/// One catalog row with its audited status.
233#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
234pub struct Row {
235    /// Stable slug derived from the capability name.
236    pub id: String,
237    /// Catalog domain number (1..=11).
238    pub domain: u8,
239    /// Catalog domain title.
240    pub domain_name: String,
241    /// Capability name as the catalog states it.
242    pub capability: String,
243    /// The catalog's one-line semantics.
244    pub semantics: String,
245    /// Claude Code column.
246    pub cc: Has,
247    /// Codex column.
248    pub cx: Has,
249    /// Claude Code cell text (variant footnotes live here).
250    pub cc_detail: String,
251    /// Codex cell text.
252    pub cx_detail: String,
253    /// The catalog's own "supercode today" cell at survey time (prior claim,
254    /// never consulted for status).
255    pub catalog_supercode_today: String,
256    /// Catalog provenance citations.
257    pub provenance: String,
258    /// Audited status.
259    pub status: Status,
260    /// Checkable citations backing `status`.
261    #[serde(default)]
262    pub evidence: Vec<Evidence>,
263    /// What is missing (partial), why (irreducible), or where (absent).
264    #[serde(default)]
265    pub note: String,
266    /// Adoption cost class; required when `status` is absent.
267    #[serde(default, skip_serializing_if = "Option::is_none")]
268    pub cost: Option<Cost>,
269}
270
271impl Row {
272    /// Whether the harness behind `column` (`"cc"` / `"cx"`) has this row.
273    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    /// Preset names this row must hold under.
282    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
291/// Parse the embedded ledger.
292pub fn ledger() -> Vec<Row> {
293    serde_json::from_str(LEDGER_JSON).expect("embedded parity ledger is valid JSON")
294}
295
296/// Catalog domain the orchestration rows belong to (Domain 11,
297/// "Orchestration & automation"). The orchestration ledger is the same
298/// domain seen through the two orchestration-level harnesses' own doors.
299pub const ORCHESTRATION_DOMAIN: u8 = 11;
300
301/// One (orchestration concept × verb group) row with its audited status.
302///
303/// Same shape as [`Row`] with the harness columns swapped: `hermes` /
304/// `openclaw` instead of `cc` / `cx`, and a per-column evidence list, since
305/// what a harness advertises is checkable independently of what supercode
306/// does with it.
307#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
308pub struct OrchestrationRow {
309    /// Stable slug (`orch-` + concept + verb group).
310    pub id: String,
311    /// One of `crate::support::ORCHESTRATION_CONCEPTS`.
312    pub concept: String,
313    /// The verb group this row covers (`list/get`, `create/update/...`).
314    pub verbs: String,
315    /// Row name as the inventory states it (`Scheduled job: list/get`).
316    pub capability: String,
317    /// The inventory's one-line semantics.
318    pub semantics: String,
319    /// Hermes column.
320    pub hermes: Has,
321    /// OpenClaw column.
322    pub openclaw: Has,
323    /// The orchestrator's column: whether ITS model has this concept
324    /// (ORC-7). `no` means the concept is deliberately outside
325    /// `docs/ORCHESTRATOR-IR.md` §2.
326    pub orchestrator: Has,
327    /// Hermes cell text (variant footnotes live here).
328    pub hermes_detail: String,
329    /// OpenClaw cell text.
330    pub openclaw_detail: String,
331    /// The orchestrator cell text.
332    pub orchestrator_detail: String,
333    /// Citations backing the Hermes column (`cli_verb` against the pin).
334    #[serde(default)]
335    pub hermes_evidence: Vec<Evidence>,
336    /// Citations backing the OpenClaw column.
337    #[serde(default)]
338    pub openclaw_evidence: Vec<Evidence>,
339    /// Audited status of supercode's support for the ORCHESTRATOR on this
340    /// row, which is a different question from [`OrchestrationRow::status`]
341    /// (that one grades the Hermes and OpenClaw readers).
342    pub orchestrator_status: Status,
343    /// Citations backing `orchestrator_status` (`store` / `code`, resolved in
344    /// this workspace exactly like a supercode status).
345    #[serde(default)]
346    pub orchestrator_evidence: Vec<Evidence>,
347    /// What is missing (partial), why (not_applicable), or where (absent) for
348    /// the orchestrator.
349    pub orchestrator_note: String,
350    /// Adoption cost class for an absent orchestrator row.
351    #[serde(default, skip_serializing_if = "Option::is_none")]
352    pub orchestrator_cost: Option<Cost>,
353    /// Inventory provenance.
354    pub provenance: String,
355    /// Audited status of SUPERCODE, not of the harnesses.
356    pub status: Status,
357    /// Checkable citations backing `status` (`store` / `code`).
358    #[serde(default)]
359    pub evidence: Vec<Evidence>,
360    /// What is missing (partial), why (irreducible), or where (absent).
361    #[serde(default)]
362    pub note: String,
363    /// Adoption cost class; required when `status` is absent.
364    #[serde(default, skip_serializing_if = "Option::is_none")]
365    pub cost: Option<Cost>,
366}
367
368impl OrchestrationRow {
369    /// Whether `harness` (`"hermes"` / `"openclaw"`) has this row.
370    pub fn has(&self, harness: &str) -> bool {
371        self.column(harness)
372            .is_some_and(|(has, _, _)| has.present())
373    }
374
375    /// The `(has, detail, evidence)` triple for one harness column.
376    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
393/// Parse the embedded orchestration ledger.
394pub fn orchestration_ledger() -> Vec<OrchestrationRow> {
395    serde_json::from_str(ORCHESTRATION_JSON).expect("embedded orchestration ledger is valid JSON")
396}
397
398/// Per-status counts plus the headline gap number for one preset.
399#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
400pub struct PresetSummary {
401    /// Preset name.
402    pub preset: String,
403    /// Catalog column the preset mirrors (`cc` / `cx`).
404    pub harness_column: String,
405    /// Rows the harness has (the denominator).
406    pub rows: usize,
407    /// Row count per status label.
408    pub counts: BTreeMap<String, usize>,
409    /// The headline: rows that are not implemented.
410    pub gaps: usize,
411}
412
413/// A gap row as printed: enough to act on without opening the ledger.
414#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
415pub struct GapRow {
416    /// Row id.
417    pub id: String,
418    /// Catalog domain.
419    pub domain: u8,
420    /// Capability name.
421    pub capability: String,
422    /// Audited status (never implemented here).
423    pub status: Status,
424    /// Adoption cost class when known.
425    #[serde(default, skip_serializing_if = "Option::is_none")]
426    pub cost: Option<Cost>,
427    /// Row note.
428    pub note: String,
429}
430
431/// The parity report for one preset.
432#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
433pub struct PresetReport {
434    /// Headline counts.
435    pub summary: PresetSummary,
436    /// Every gap row, in catalog order.
437    pub gaps: Vec<GapRow>,
438}
439
440/// Build the report for `preset`: a capability preset (`cc-parity` /
441/// `cx-parity`) or an orchestration harness (`hermes` / `openclaw`).
442pub 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
516/// ORC-7: the same 17 rows, graded through the orchestrator's own column.
517///
518/// Every row counts: a concept the orchestrator's model does not have is
519/// `not_applicable` (not a gap, but still in the denominator), so the
520/// headline can never be improved by dropping a row.
521fn 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
552/// Render the headline plus the gap list, grouped by domain.
553pub 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        // ORC-7: a preset whose denominator is the WHOLE ledger reports rows
562        // its subject deliberately lacks; leaving them out of the headline
563        // would make the counts fail to add up to the row count.
564        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
597/// Resolve a built-in preset through the real resolver (strict mode).
598pub 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
625/// Resolve a [`Evidence::CliVerb`] against the committed help fixture.
626///
627/// A verb is advertised when its LEAF appears as a subcommand entry inside
628/// the section headed `$ <harness> <parents> --help`. Subcommand entries are
629/// shallow-indented lines whose first token names the command; both help
630/// renderers' alias forms are accepted (`create (add)` from argparse,
631/// `add|create` from Commander).
632fn 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        // Subcommand entries sit 2–6 columns in; description continuations
654        // and usage wrapping sit far deeper, so they cannot false-positive.
655        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
693/// True when `segment` appears inside an opening call (SQL read, file open,
694/// path join) on a non-comment line under [`STORE_SEARCH_ROOTS`]. The call
695/// may be split across lines (multi-line SQL), so the three preceding lines
696/// count as the same call context.
697fn 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
721/// Resolve a [`Evidence::Store`]: every non-placeholder path segment must be
722/// opened by a loader in this workspace.
723fn 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
764/// Check one evidence item for one row. Returns `Err(reason)` when the
765/// citation does not resolve under every applicable preset / harness.
766pub 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
835/// Check one evidence item whose resolution does not depend on a preset:
836/// `code` against the source tree, `cli_verb` against the pinned help
837/// fixture, `store` against the workspace's loaders.
838pub 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
862/// Check one evidence item on an orchestration row.
863///
864/// The two evidence lanes are kept apart on purpose: a harness COLUMN may
865/// only cite a `cli_verb` of that same harness (what the pinned CLI
866/// advertises), and supercode's STATUS may only cite `store` / `code` (what
867/// this workspace actually does). A `cli_verb` can never make supercode look
868/// implemented, and a `code` citation can never make a harness look capable.
869pub 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/// Which side of an orchestration row an evidence item backs.
896#[derive(Debug, Clone, Copy, PartialEq, Eq)]
897pub enum OrchestrationLane<'a> {
898    /// One harness column: what the pinned harness CLI advertises.
899    Column(&'a str),
900    /// supercode's audited status: what this workspace does.
901    Supercode,
902}