cto/tools/validate_cto_child.py

165 lines
5.8 KiB
Python

#!/usr/bin/env python3
"""Validate the child-local Cortex OS CTO workspace."""
from __future__ import annotations
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
REQUIRED_FILES = [
"AGENTS.md",
"README.md",
"WORKBOARD.yaml",
"CONTEXT.md",
"sot/00-START/CTO-WORKSPACE-INTENT.md",
"sot/03-PROTOCOLS/CTO-CASE-BACKEND-BRIEF.md",
"sot/03-PROTOCOLS/CTO-CASE-CANDIDATE-BACKEND-PRD.md",
"sot/03-PROTOCOLS/CTO-CASE-CANDIDATE-BACKEND-ISSUES.md",
"sot/03-PROTOCOLS/CTO-HARNESS-EVIDENCE-INTERFACE-CONTRACT.md",
]
REQUIRED_BRIEF_PHRASES = [
"Cortex governs.",
"Hermes controls.",
"Case executes.",
"Harness proves.",
"Core promotes only through SOT route.",
"Case is the candidate CTO execution backend, not the CTO authority layer.",
"This brief is child-local planning.",
]
REQUIRED_PRD_PHRASES = [
"Local planning SOT only. Not a Core Protocol. Not active Core authority.",
"Case Candidate Backend",
"Harness Evidence Interface",
"Case may recommend; CTO Harness records; Hermes or operator approval is the only human approval signal.",
"Candidate Cortex Work Packet is an unpromoted term.",
"Staged Proof Gates",
"Source Admission Requirements",
"Failure-Mode Matrix",
"Fake remains the default validation lane.",
]
FORBIDDEN_PRD_PHRASES = [
"Case should be the default real-repo execution backend",
"Case is the default real-repo execution backend",
]
REQUIRED_ISSUE_IDS = [
"CTO-WORK-003",
"CTO-WORK-004",
"CTO-WORK-005",
"CTO-WORK-006",
"CTO-WORK-007",
"CTO-WORK-008",
]
REQUIRED_EVIDENCE_INTERFACE_PHRASES = [
"Local planning SOT only. Not a Core Protocol. Not active Core authority.",
"This contract does not authorize Runtime behavior",
"The Harness Evidence Interface is the only accepted proof surface for backend comparison.",
"report.json",
"events.normalized.jsonl",
"patch.diff",
"test.log",
"trace.jsonl",
"artifact_digests",
"SHA-256",
"run_started_at",
"run_finished_at",
"backend_exit_code",
"allowed_writes_passed",
"approval.requested",
"approval.granted",
"approval.denied",
"Case may recommend. Case must not approve itself.",
"No merge, push, deploy, close, or real-repo mutation is allowed without explicit task-contract permission.",
"fail closed",
]
def main() -> int:
checked: list[str] = []
errors: list[str] = []
for rel in REQUIRED_FILES:
path = ROOT / rel
checked.append(rel)
if not path.is_file():
errors.append(f"missing_required_file:{rel}")
brief = ROOT / "sot/03-PROTOCOLS/CTO-CASE-BACKEND-BRIEF.md"
if brief.is_file():
text = brief.read_text(encoding="utf-8")
for phrase in REQUIRED_BRIEF_PHRASES:
checked.append(f"brief_phrase:{phrase}")
if phrase not in text:
errors.append(f"missing_brief_phrase:{phrase}")
if "core_promotion_status: not-promoted" not in text:
errors.append("brief_missing_not_promoted_frontmatter")
prd = ROOT / "sot/03-PROTOCOLS/CTO-CASE-CANDIDATE-BACKEND-PRD.md"
if prd.is_file():
text = prd.read_text(encoding="utf-8")
for phrase in REQUIRED_PRD_PHRASES:
checked.append(f"prd_phrase:{phrase}")
if phrase not in text:
errors.append(f"missing_prd_phrase:{phrase}")
for phrase in FORBIDDEN_PRD_PHRASES:
checked.append(f"prd_forbidden_phrase:{phrase}")
if phrase in text:
errors.append(f"forbidden_prd_phrase:{phrase}")
if "core_promotion_status: not-promoted" not in text:
errors.append("prd_missing_not_promoted_frontmatter")
issues = ROOT / "sot/03-PROTOCOLS/CTO-CASE-CANDIDATE-BACKEND-ISSUES.md"
if issues.is_file():
text = issues.read_text(encoding="utf-8")
if "Local planning SOT only. Not a Core Protocol. Not active Core authority." not in text:
errors.append("issues_missing_local_planning_notice")
if "core_promotion_status: not-promoted" not in text:
errors.append("issues_missing_not_promoted_frontmatter")
for issue_id in REQUIRED_ISSUE_IDS:
checked.append(f"issue_id:{issue_id}")
if issue_id not in text:
errors.append(f"missing_issue_id:{issue_id}")
evidence_interface = ROOT / "sot/03-PROTOCOLS/CTO-HARNESS-EVIDENCE-INTERFACE-CONTRACT.md"
if evidence_interface.is_file():
text = evidence_interface.read_text(encoding="utf-8")
if "core_promotion_status: not-promoted" not in text:
errors.append("evidence_interface_missing_not_promoted_frontmatter")
for phrase in REQUIRED_EVIDENCE_INTERFACE_PHRASES:
checked.append(f"evidence_interface_phrase:{phrase}")
if phrase not in text:
errors.append(f"missing_evidence_interface_phrase:{phrase}")
board = ROOT / "WORKBOARD.yaml"
if board.is_file():
text = board.read_text(encoding="utf-8")
for issue_id in ["CTO-WORK-002", *REQUIRED_ISSUE_IDS]:
checked.append(f"workboard_id:{issue_id}")
if issue_id not in text:
errors.append(f"missing_workboard_id:{issue_id}")
if "CTO-HARNESS-EVIDENCE-INTERFACE-CONTRACT.md" not in text:
errors.append("workboard_missing_evidence_interface_contract_source")
if "CTO-WORK-004" in text and "status: validated" not in text:
errors.append("workboard_cto_work_004_not_validated")
payload = {
"ok": not errors,
"validator": "cto-child-v1",
"checked": checked,
"errors": errors,
"warnings": [],
}
print(json.dumps(payload, indent=2, sort_keys=True))
return 0 if not errors else 1
if __name__ == "__main__":
raise SystemExit(main())