from __future__ import annotations import dataclasses import glob import hashlib import json import subprocess from dataclasses import dataclass from pathlib import Path from typing import Any from chat.cross_pack_grounding import CROSS_PACK_CORPUS_ID, _all_cross_pack_chains from chat.teaching_grounding import TEACHING_CORPORA, _all_chains_index from core.capability.domains import ( DOMAIN_CAPABILITY_CORPORA, DOMAIN_CORPORA, DOMAIN_OPERATOR_CLAIMS, DOMAIN_PACKS, ) from core.capability.expert_demo import ( collect_domain_lanes, evaluate_expert_demo, materialise_lane_results, ) from core.capability.reviewers import ( ReviewerRegistry, ReviewerRegistryError, load_reviewer_registry, ) from core.capability.sources import LEDGER_SOURCES from core.config import DEFAULT_CONFIG from language_packs.domain_contract import validate_domain_contract_pack _REPO_ROOT = Path(__file__).resolve().parent.parent.parent _CHAINS_PER_OPERATOR_DOMAIN = 8 _TARGET_INTENT_SHAPES = ("cause", "verification", "comparison", "procedure", "correction") _EXPERT_DOMAIN_STATUSES = ( "blocked", "seeded", "grounded", "reasoning-capable", "audit-passed", "expert", ) # ADR-0120 reserves the ``expert`` tier; the predicate computation # is per-domain because each domain ratifies its own composer # (ADR-0131.4 → ADR-0120-math for ``mathematics_logic``). Adding a # new expert-tier domain means wiring its own composer entry here. _EXPERT_COMPOSERS: dict[str, str] = { "mathematics_logic": "core.capability.expert_promotion_math", } _DOMAIN_FOUNDATION_GAPS: dict[str, tuple[str, ...]] = { "hebrew_greek_textual_reasoning": ( "gap:grc_he_glosses_absent", "gap:grc_he_chains_absent", ), } _FLAG_CATALOG: dict[str, dict[str, str]] = { "realizer_grounded_authority": {"state": "flag_shipped_default_off", "adr": "ADR-0088"}, "stop_tokens": {"state": "flag_shipped_default_off", "adr": "ADR-0087"}, "composed_surface": {"state": "flag_shipped_default_off", "adr": "ADR-0062"}, "transitive_surface": {"state": "flag_shipped_default_off", "adr": "ADR-0083"}, "gloss_aware_cause": {"state": "flag_shipped_default_off", "adr": "ADR-0085"}, "thread_anaphora": {"state": "flag_shipped_default_off", "adr": "P3.2"}, "discourse_planner": {"state": "flag_shipped_default_on", "adr": "ADR-0089"}, "compound_intent_dispatch": {"state": "substrate_shipped_flag_missing", "adr": "ADR-0089-C2"}, "inference_trace": {"state": "substrate_missing", "adr": "ADR-0024"}, } @dataclass(frozen=True, slots=True) class CapabilityArtifactQuery: lane: str split: str version: str def _sha256_json(value: Any) -> str: body = json.dumps(value, sort_keys=True, separators=(",", ":")).encode("utf-8") return hashlib.sha256(body).hexdigest() def _commit_sha() -> str: try: completed = subprocess.run( ("git", "rev-parse", "HEAD"), cwd=_REPO_ROOT, capture_output=True, text=True, check=True, ) return completed.stdout.strip() except Exception: return "unknown" def _load_json(path: Path) -> dict[str, Any]: try: raw = json.loads(path.read_text(encoding="utf-8")) except Exception: return {} return raw if isinstance(raw, dict) else {} def _latest_eval_result(lane: str, version: str, split: str) -> dict[str, Any]: pattern = _REPO_ROOT / "evals" / lane / "results" / f"{version}_{split}_*.json" matches = sorted(glob.glob(str(pattern))) if not matches: exact = _REPO_ROOT / "evals" / lane / "results" / f"{version}_{split}.json" return _load_json(exact) return _load_json(Path(matches[-1])) def _manifest_for_pack(pack_id: str) -> dict[str, Any]: path = _REPO_ROOT / "language_packs" / "data" / pack_id / "manifest.json" return _load_json(path) def _pack_lemmas(pack_id: str) -> set[str]: root = _REPO_ROOT / "language_packs" / "data" / pack_id path = root / "lexicon.jsonl" lemmas: set[str] = set() if not path.exists(): return lemmas for line in path.read_text(encoding="utf-8").splitlines(): if not line.strip(): continue try: row = json.loads(line) except json.JSONDecodeError: continue lemma = row.get("lemma") if isinstance(row, dict) else None if isinstance(lemma, str): lemmas.add(lemma) return lemmas def _count_jsonl(path: Path) -> int: if not path.exists(): return 0 return sum(1 for line in path.read_text(encoding="utf-8").splitlines() if line.strip()) def _pack_metrics(pack_id: str) -> dict[str, Any]: root = _REPO_ROOT / "language_packs" / "data" / pack_id manifest = _manifest_for_pack(pack_id) lexicon_path = root / str(manifest.get("lexicon", "lexicon.jsonl")) glosses_path = root / "glosses.jsonl" lemma_count = _count_jsonl(lexicon_path) gloss_count = _count_jsonl(glosses_path) coverage = (gloss_count / lemma_count) if lemma_count else 0.0 return { "pack_id": pack_id, "manifest_present": bool(manifest), "checksum_present": bool(manifest.get("checksum")), "glosses_checksum_present": bool(manifest.get("glosses_checksum")), "lemma_count": lemma_count, "gloss_count": gloss_count, "gloss_coverage": round(coverage, 4), "mount_eligible": bool(manifest and manifest.get("checksum") and coverage >= 0.85), } def _gap_registry() -> dict[str, str]: path = _REPO_ROOT / LEDGER_SOURCES.gaps if not path.exists(): return {} out: dict[str, str] = {} for line in path.read_text(encoding="utf-8").splitlines(): stripped = line.strip() if not stripped.startswith("-"): continue status = "closed" if stripped.startswith("- [x]") else "open" marker = stripped.split("`", 2) if len(marker) >= 2 and marker[1].startswith("gap:"): out[marker[1]] = status return out def _operator_family_for_chain(intent: str, connective: str) -> str: intent_l = intent.strip().lower() conn_l = connective.strip().lower() if intent_l == "verification" or conn_l in {"supports", "verifies", "confirms"}: return "proof_chain" if intent_l == "cause" or conn_l in {"causes", "grounds", "reveals", "requires", "enables"}: return "causal" if "contradict" in conn_l or "opposes" in conn_l: return "contradiction" if "transitive" in conn_l or "entails" in conn_l: return "transitive" if "modal" in conn_l or "permits" in conn_l or "requires" in conn_l: return "modal" return "unclassified" def _domain_chain_records() -> list[dict[str, Any]]: out: list[dict[str, Any]] = [] for corpus_id, rel_path in sorted(DOMAIN_CAPABILITY_CORPORA.items()): path = _REPO_ROOT / rel_path if not path.exists(): continue for line_no, line in enumerate(path.read_text(encoding="utf-8").splitlines(), start=1): if not line.strip(): continue try: row = json.loads(line) except json.JSONDecodeError: continue if not isinstance(row, dict): continue if row.get("review_status") != "reviewed": continue intent = str(row.get("intent", "")).strip().lower() connective = str(row.get("connective", "")).strip() operator_family = str(row.get("operator_family") or "").strip() or _operator_family_for_chain(intent, connective) domain = str(row.get("domain") or "").strip() if domain not in DOMAIN_PACKS: continue subject = str(row.get("subject") or "").strip() obj = str(row.get("object") or "").strip() subject_pack = str(row.get("subject_pack_id") or "").strip() object_pack = str(row.get("object_pack_id") or "").strip() if subject_pack and subject not in _pack_lemmas(subject_pack): continue if object_pack and obj not in _pack_lemmas(object_pack): continue out.append( { "chain_id": str(row.get("chain_id") or f"{corpus_id}:{line_no}"), "corpus_id": corpus_id, "domain": domain, "intent": intent, "connective": connective, "operator_family": operator_family, "subject_pack_id": subject_pack, "object_pack_id": object_pack, } ) return out def _chain_inventory() -> dict[str, Any]: chains = list(_all_chains_index().values()) chains.extend(_all_cross_pack_chains()) domain_records = _domain_chain_records() corpus_pack: dict[str, str] = {spec.corpus_id: spec.pack_id for spec in TEACHING_CORPORA} by_intent: dict[str, int] = {} by_connective: dict[str, int] = {} by_operator: dict[str, int] = {} by_corpus: dict[str, int] = {} by_pack: dict[str, int] = {} by_domain_operator: dict[str, dict[str, int]] = {} by_domain_intent: dict[str, dict[str, int]] = {} corpus_domains: dict[str, list[str]] = {} for domain, corpora in DOMAIN_CORPORA.items(): for corpus_id in corpora: corpus_domains.setdefault(corpus_id, []).append(domain) for chain in chains: by_intent[chain.intent] = by_intent.get(chain.intent, 0) + 1 by_connective[chain.connective] = by_connective.get(chain.connective, 0) + 1 op = _operator_family_for_chain(chain.intent, chain.connective) by_operator[op] = by_operator.get(op, 0) + 1 by_corpus[chain.corpus_id] = by_corpus.get(chain.corpus_id, 0) + 1 if hasattr(chain, "subject_pack_id"): for pack_id in (chain.subject_pack_id, chain.object_pack_id): by_pack[pack_id] = by_pack.get(pack_id, 0) + 1 else: pack_id = corpus_pack.get(chain.corpus_id, "unknown") by_pack[pack_id] = by_pack.get(pack_id, 0) + 1 for domain in corpus_domains.get(chain.corpus_id, ()): by_domain_operator.setdefault(domain, {}) by_domain_operator[domain][op] = by_domain_operator[domain].get(op, 0) + 1 by_domain_intent.setdefault(domain, {}) by_domain_intent[domain][chain.intent] = by_domain_intent[domain].get(chain.intent, 0) + 1 for record in domain_records: intent = str(record["intent"]) connective = str(record["connective"]) op = str(record["operator_family"]) domain = str(record["domain"]) corpus_id = str(record["corpus_id"]) by_intent[intent] = by_intent.get(intent, 0) + 1 by_connective[connective] = by_connective.get(connective, 0) + 1 by_operator[op] = by_operator.get(op, 0) + 1 by_corpus[corpus_id] = by_corpus.get(corpus_id, 0) + 1 by_domain_operator.setdefault(domain, {}) by_domain_operator[domain][op] = by_domain_operator[domain].get(op, 0) + 1 by_domain_intent.setdefault(domain, {}) by_domain_intent[domain][intent] = by_domain_intent[domain].get(intent, 0) + 1 for pack_id in (record.get("subject_pack_id"), record.get("object_pack_id")): if pack_id: by_pack[str(pack_id)] = by_pack.get(str(pack_id), 0) + 1 by_domain = { domain: sum(by_corpus.get(corpus_id, 0) for corpus_id in DOMAIN_CORPORA.get(domain, ())) for domain in DOMAIN_PACKS } return { "chains": chains, "by_intent": dict(sorted(by_intent.items())), "by_connective": dict(sorted(by_connective.items())), "by_operator_family": dict(sorted(by_operator.items())), "by_corpus": dict(sorted(by_corpus.items())), "by_pack": dict(sorted(by_pack.items())), "by_domain": dict(sorted(by_domain.items())), "by_domain_operator_family": { domain: dict(sorted(counts.items())) for domain, counts in sorted(by_domain_operator.items()) }, "by_domain_intent_shape": { domain: dict(sorted(counts.items())) for domain, counts in sorted(by_domain_intent.items()) }, } def _pack_hash(pack_ids: tuple[str, ...]) -> str: material: list[tuple[str, str, str, str]] = [] for pack_id in sorted(pack_ids): m = _manifest_for_pack(pack_id) mastery_hash = str(m.get("mastery_report_sha256", "")) material.append( ( pack_id, str(m.get("checksum", "")), str(m.get("glosses_checksum", "")), mastery_hash, ) ) return hashlib.sha256(repr(tuple(material)).encode("utf-8")).hexdigest() def _config_hash() -> str: material = { "artifact_contract_version": 1, "runtime_config": dataclasses.asdict(DEFAULT_CONFIG), } return _sha256_json(material) def chain_report() -> dict[str, Any]: inventory = _chain_inventory() teaching_counts: dict[str, int] = dict(inventory["by_corpus"]) for spec in TEACHING_CORPORA: teaching_counts.setdefault(spec.corpus_id, 0) teaching_counts.setdefault(CROSS_PACK_CORPUS_ID, 0) active = sum(teaching_counts.values()) operator_count = 5 domain_count = len(DOMAIN_PACKS) required = _CHAINS_PER_OPERATOR_DOMAIN * operator_count * domain_count return { "active_chains": active, "by_corpus": teaching_counts, "by_intent_shape": inventory["by_intent"], "by_connective": inventory["by_connective"], "by_operator_family": inventory["by_operator_family"], "by_pack": inventory["by_pack"], "by_domain": inventory["by_domain"], "by_domain_operator_family": inventory["by_domain_operator_family"], "by_domain_intent_shape": inventory["by_domain_intent_shape"], "target_intent_shapes": list(_TARGET_INTENT_SHAPES), "missing_target_intent_shapes": [ shape for shape in _TARGET_INTENT_SHAPES if shape not in inventory["by_intent"] ], "formula": { "operators": operator_count, "domains": domain_count, "chains_per_operator_domain": _CHAINS_PER_OPERATOR_DOMAIN, "chains_required": required, "chains_present": active, "chains_remaining": max(0, required - active), }, } def flag_report() -> dict[str, Any]: grouped: dict[str, list[dict[str, str]]] = { "flag_shipped_default_off": [], "flag_shipped_default_on": [], "substrate_shipped_flag_missing": [], "substrate_missing": [], } for name, meta in sorted(_FLAG_CATALOG.items()): grouped[meta["state"]].append({"flag": name, "adr": meta["adr"]}) return grouped def ledger_report() -> dict[str, Any]: sources = LEDGER_SOURCES resolved = sources.resolve(_REPO_ROOT) missing_sources = [k for k, p in resolved.items() if not p.exists()] gaps = _gap_registry() chain_inventory = _chain_inventory() _resolved_registry = _load_registry_for_expert_demo() domain_rows: list[dict[str, Any]] = [] for domain, packs in DOMAIN_PACKS.items(): pack_metrics = [_pack_metrics(pack_id) for pack_id in packs] domain_contracts = [ validate_domain_contract_pack(pack_id).as_dict() for pack_id in packs ] domain_gaps: list[str] = [] if not packs: domain_gaps.append(f"gap:{domain}_pack_absent") domain_gaps.extend(_DOMAIN_FOUNDATION_GAPS.get(domain, ())) domain_gaps.extend( f"gap:{metric['pack_id']}_gloss_coverage_below_threshold" for metric in pack_metrics if metric["manifest_present"] and not metric["mount_eligible"] ) domain_gaps.extend(f"gap:ledger-source-missing:{src}" for src in missing_sources) seeded = bool(packs) and all(p["manifest_present"] and p["checksum_present"] for p in pack_metrics) grounded = seeded and all(p["mount_eligible"] for p in pack_metrics) claimed = DOMAIN_OPERATOR_CLAIMS.get(domain, ()) op_counts = { op: int(chain_inventory["by_domain_operator_family"].get(domain, {}).get(op, 0)) for op in claimed } op_required = {op: _CHAINS_PER_OPERATOR_DOMAIN for op in claimed} domain_gaps.extend( f"gap:{domain}_{op}_chains_below_threshold" for op in claimed if op_counts[op] < op_required[op] ) intent_shapes_present = len(chain_inventory["by_domain_intent_shape"].get(domain, {})) holdout_present = (_REPO_ROOT / "evals" / "cognition" / "holdouts" / "cases_plaintext.jsonl").exists() if intent_shapes_present < 3: domain_gaps.append(f"gap:{domain}_intent_shapes_below_threshold") if not holdout_present: domain_gaps.append(f"gap:{domain}_holdout_absent") domain_gaps = sorted(dict.fromkeys(domain_gaps)) open_gaps = [gap for gap in domain_gaps if gaps.get(gap, "open") != "closed"] reasoning_capable = ( grounded and bool(claimed) and all(op_counts[op] >= op_required[op] for op in claimed) and intent_shapes_present >= 3 and holdout_present ) manifests = [_manifest_for_pack(pack_id) for pack_id in packs] domain_lanes = collect_domain_lanes(manifests) def _fetch_lane_split(lane: str, split: str) -> dict[str, Any]: payload = _latest_eval_result(lane, "v1", split) metrics = dict(payload.get("metrics", {}) or {}) # Some lanes (notably fabrication_control) report # ``by_class`` at the top level of the result file rather # than nested under ``metrics``. Fold it in so shape # checkers see a single canonical metrics dict regardless # of lane-internal layout. if "by_class" not in metrics and "by_class" in payload: metrics["by_class"] = payload["by_class"] return metrics lane_results = materialise_lane_results( domain_lanes, fetch_split=_fetch_lane_split, ) verdict = evaluate_expert_demo( domain_id=domain, reasoning_capable=reasoning_capable, registry=_resolved_registry, domain_lanes=domain_lanes, lane_results=lane_results, ) audit_passed = verdict.passed audit_passed_reason = verdict.reason # ADR-0120 expert tier: per-domain composer (mathematics_logic # via ADR-0120-math). Only evaluated when audit-passed already # holds — expert is a strict super-tier of audit-passed. expert = False expert_reason = "audit-passed predicate must hold first" if audit_passed and domain in _EXPERT_COMPOSERS: try: from importlib import import_module module = import_module(_EXPERT_COMPOSERS[domain]) evaluator = getattr(module, "evaluate_math_expert_promotion", None) if evaluator is not None: expert_verdict = evaluator() expert = bool(getattr(expert_verdict, "promote_admitted", False)) expert_reason = ( "ADR-0120-math composer admitted" if expert else getattr(expert_verdict, "refusal_reason", "composer refused") ) else: expert_reason = ( f"composer module {_EXPERT_COMPOSERS[domain]!r} " f"missing evaluate_math_expert_promotion()" ) except Exception as exc: # noqa: BLE001 — fail closed expert_reason = ( f"composer raised {type(exc).__name__}: {exc}" ) elif audit_passed: expert_reason = ( f"no expert composer wired for domain {domain!r}" ) if expert: status = "expert" elif audit_passed: status = "audit-passed" elif reasoning_capable: status = "reasoning-capable" elif grounded: status = "grounded" elif seeded: status = "seeded" else: status = "blocked" if open_gaps and status == "blocked": blocked_lift = "gap closes and seeded predicate passes" elif open_gaps: blocked_lift = "open gaps explain missing next-status predicates" else: blocked_lift = "no open gap; next status requires failing predicate to pass" domain_rows.append( { "domain": domain, "status": status, "status_order": list(_EXPERT_DOMAIN_STATUSES), "packs": list(packs), "pack_metrics": pack_metrics, "domain_contracts": domain_contracts, "corpora": list(DOMAIN_CORPORA.get(domain, ())), "claimed_operators": list(DOMAIN_OPERATOR_CLAIMS.get(domain, ())), "operator_chain_coverage": { op: { "chains_present": op_counts[op], "chains_required": op_required[op], "ready": op_counts[op] >= op_required[op], } for op in claimed }, "intent_shapes_present": intent_shapes_present, "intent_shapes_required": 3, "holdout_present": holdout_present, "predicates": { "seeded": seeded, "grounded": grounded, "reasoning_capable": reasoning_capable, "audit_passed": audit_passed, "expert": expert, }, "audit_passed_reason": audit_passed_reason, "expert_reason": expert_reason, "known_gaps": domain_gaps, "open_gaps": open_gaps, "blocked_lift": blocked_lift, } ) eval_files = glob.glob(str(_REPO_ROOT / "evals" / "*" / "results" / "v*_*.json")) mastery_files = glob.glob(str(_REPO_ROOT / "packs" / "**" / "*.mastery_report.json"), recursive=True) identity_files = glob.glob(str(_REPO_ROOT / "evals" / "identity_divergence" / "results" / "*.json")) refusal_files = glob.glob(str(_REPO_ROOT / "evals" / "refusal_calibration" / "results" / "*.json")) audit_files = glob.glob(str(_REPO_ROOT / "evals" / "audit_tour" / "results" / "*.json")) return { "sources": dataclasses.asdict(sources), "missing_sources": missing_sources, "thresholds": { "gloss_coverage": 0.85, "surface_groundedness": 0.95, "term_capture": 0.85, "intent_accuracy": 0.95, "versor_closure_rate": 1.0, "replay_determinism": 0.99, }, "evidence_counts": { "eval_results": len(eval_files), "mastery_reports": len(mastery_files), "identity_div": len(identity_files), "refusal_cal": len(refusal_files), "audit_tour": len(audit_files), "pack_measurements_present": (_REPO_ROOT / LEDGER_SOURCES.pack_measurements).exists(), "reviewers_present": (_REPO_ROOT / LEDGER_SOURCES.reviewers).exists(), "reviewer_registry": _reviewer_registry_status(), "gaps_present": (_REPO_ROOT / LEDGER_SOURCES.gaps).exists(), }, "domains": domain_rows, "day1_expected_output": True, } def artifact_report(query: CapabilityArtifactQuery) -> dict[str, Any]: commit_sha = _commit_sha() cfg_hash = _config_hash() p_hash = _pack_hash(tuple(sorted({p for packs in DOMAIN_PACKS.values() for p in packs}))) artifact_material = { "artifact_contract_version": 1, "commit_sha": commit_sha, "pack_hash": p_hash, "eval_lane": query.lane, "version": query.version, "split": query.split, "config_hash": cfg_hash, } artifact_id = _sha256_json(artifact_material) exact = _REPO_ROOT / "evals" / query.lane / "results" / f"{query.version}_{query.split}.json" matches = sorted(glob.glob(str(_REPO_ROOT / "evals" / query.lane / "results" / f"{query.version}_{query.split}_*.json"))) result_path = Path(matches[-1]) if matches else exact if not result_path.exists(): return { "artifact_id": artifact_id, "exists": False, "would_run": artifact_material, "result_path": str(result_path), } return { "artifact_id": artifact_id, "exists": True, "result_path": str(result_path), "result_sha256": hashlib.sha256(result_path.read_bytes()).hexdigest(), } def evidence_plan_report() -> dict[str, Any]: commit_sha = _commit_sha() cfg_hash = _config_hash() pack_hash = _pack_hash(tuple(sorted({p for packs in DOMAIN_PACKS.values() for p in packs}))) lanes = ( ("pack_validation", "packs", "public"), ("eval_matrix", "cognition", "public"), ("eval_matrix", "cognition", "holdout"), ("replay_sweep", "cognition", "public"), ("vault_benchmark", "vault", "public"), ("curriculum_experiment", "capability", "dev"), ) jobs: list[dict[str, Any]] = [] for job_type, lane, split in lanes: material = { "artifact_contract_version": 1, "commit_sha": commit_sha, "pack_hash": pack_hash, "job_type": job_type, "eval_lane": lane, "version": "v1", "split": split, "config_hash": cfg_hash, } jobs.append( { "job_id": _sha256_json(material), "job_type": job_type, "lane": lane, "version": "v1", "split": split, "artifact_key": material, "execution": "local-or-worker", "promotion": "reviewed-mainline-only", "mutates_packs": False, } ) return { "artifact_contract_version": 1, "commit_sha": commit_sha, "pack_hash": pack_hash, "config_hash": cfg_hash, "scheduler": "local-first", "workers_promote_packs": False, "jobs": jobs, } def _load_registry_for_expert_demo() -> ReviewerRegistry: """Load reviewer registry, returning an empty one on any failure. Expert-demo gating is fail-closed: an unloadable registry yields a registry with zero reviewers and zero claims, which makes ``evaluate_expert_demo`` refuse every domain. A broken registry must never silently grant ``expert_demo=true``. """ path = _REPO_ROOT / LEDGER_SOURCES.reviewers if not path.exists(): return ReviewerRegistry(schema_version=1, reviewers=(), expert_demo_claims=()) try: return load_reviewer_registry(path) except ReviewerRegistryError: return ReviewerRegistry(schema_version=1, reviewers=(), expert_demo_claims=()) def _reviewer_registry_status() -> dict[str, Any]: """Report Reviewer Registry v1 validity for the capability ledger (ADR-0092). Returns a structured object containing the parsed schema version, reviewer count, and a list of registered reviewer ids. If the registry is missing or fails ADR-0092 schema validation, the object reports ``valid: False`` and names the error rather than raising, so the ledger surface remains generatable on a broken registry. """ path = _REPO_ROOT / LEDGER_SOURCES.reviewers if not path.exists(): return { "valid": False, "error": "reviewers.yaml not found", "schema_version": None, "reviewer_count": 0, "reviewer_ids": [], } try: registry = load_reviewer_registry(path) except ReviewerRegistryError as exc: return { "valid": False, "error": str(exc), "schema_version": None, "reviewer_count": 0, "reviewer_ids": [], } return { "valid": True, "error": None, "schema_version": registry.schema_version, "reviewer_count": len(registry.reviewers), "reviewer_ids": sorted(r.reviewer_id for r in registry.reviewers), }