CC: Cover Codex retention in CTO validator
This commit is contained in:
@@ -111,6 +111,14 @@ REQUIRED_FILES = [
|
||||
".sot/03-PROTOCOLS/CTO-CASE-SPARK-ENDPOINT-CONFIG-PRD.md",
|
||||
".sot/03-PROTOCOLS/CTO-CASE-SPARK-ENDPOINT-CONFIG-ISSUES.md",
|
||||
".sot/03-PROTOCOLS/CTO-CASE-AGENT-PROTOCOL-BLOCKER.md",
|
||||
".sot/03-PROTOCOLS/CTO-CODEX-RETENTION-DRY-RUN-PACKET.md",
|
||||
".sot/03-PROTOCOLS/CTO-CODEX-RETENTION-POLICY-PACKET.md",
|
||||
".sot/03-PROTOCOLS/CTO-CODEX-RETENTION-ARCHIVE-EXECUTOR-PACKET.md",
|
||||
"tools/report_codex_retention_pressure.py",
|
||||
"tools/plan_codex_retention_policy.py",
|
||||
"tools/archive_codex_inactive_threads.py",
|
||||
"tools/probe_codex_native_retention.py",
|
||||
"tools/codex_ephemeral_exec.py",
|
||||
]
|
||||
|
||||
REQUIRED_BRIEF_PHRASES = [
|
||||
@@ -1656,6 +1664,105 @@ REQUIRED_CORE_ROUTE_ADMISSION_GUARD_CLOSEOUT_PHRASES = [
|
||||
"candidate-only until the guard passes",
|
||||
]
|
||||
|
||||
REQUIRED_CODEX_RETENTION_ISSUE_IDS = [
|
||||
"CTO-WORK-093",
|
||||
"CTO-WORK-094",
|
||||
"CTO-WORK-095",
|
||||
"CTO-WORK-096",
|
||||
]
|
||||
|
||||
REQUIRED_CODEX_RETENTION_DRY_RUN_PHRASES = [
|
||||
"Local planning SOT only. Not a Core Protocol. Not active Core authority.",
|
||||
"Codex retention pressure is measurable without reading raw transcripts or mutating `~/.codex`.",
|
||||
"metadata-only JSON",
|
||||
"It does not read transcript bodies, update SQLite, delete files, vacuum databases, archive threads, mutate Core, start Runtime, read secrets, change Codex config, or claim product readiness.",
|
||||
"Prevention default: use `codex exec --ephemeral` for disposable non-interactive worker runs.",
|
||||
"Blocked without explicit operator approval:",
|
||||
"direct `threads.archived` updates",
|
||||
"session JSONL deletion",
|
||||
"logs table deletion",
|
||||
"SQLite vacuum or checkpoint",
|
||||
"raw transcript import into Core",
|
||||
"broad cleanup of `~/.codex`",
|
||||
]
|
||||
|
||||
REQUIRED_CODEX_RETENTION_POLICY_PHRASES = [
|
||||
"Local planning SOT only. Not a Core Protocol. Not active Core authority.",
|
||||
"Codex retention cleanup now has a backup-first, approval-gated policy plan.",
|
||||
"`python3 tools/plan_codex_retention_policy.py` emits metadata-only JSON.",
|
||||
"`python3 tools/probe_codex_native_retention.py` checks installed Codex CLI help, feature flags, and local version cache.",
|
||||
"native cleanup/archive/retention command advertised by installed CLI: false",
|
||||
"prevention flag advertised: `codex exec --ephemeral`",
|
||||
"Phase 1: backup `state_5.sqlite`, `logs_2.sqlite`, WAL, and SHM files.",
|
||||
"Phase 2: archive-only candidate threads by DB flag only after explicit approval.",
|
||||
"Phase 3: delete archived session JSONL only after separate destructive approval.",
|
||||
"`python3 tools/codex_ephemeral_exec.py` builds disposable worker commands as `codex exec --ephemeral`.",
|
||||
"The helper is prevention only. It does not archive threads, delete JSONL, truncate logs, checkpoint, vacuum, read transcript bodies, or mutate Core.",
|
||||
"Blocked without explicit operator approval:",
|
||||
"updating `threads.archived`",
|
||||
"deleting session JSONL",
|
||||
"SQLite checkpoint or vacuum",
|
||||
"raw transcript read/import",
|
||||
]
|
||||
|
||||
REQUIRED_CODEX_RETENTION_ARCHIVE_PHRASES = [
|
||||
"Local planning SOT only. Not a Core Protocol. Not active Core authority.",
|
||||
"Codex retention cleanup now has a guarded archive-only executor.",
|
||||
"Default mode is dry-run.",
|
||||
"Mutation requires an exact approval token.",
|
||||
"It does not delete session JSONL, truncate logs, checkpoint, vacuum, read transcript bodies, or import transcripts into Core.",
|
||||
"python3 tools/archive_codex_inactive_threads.py --check",
|
||||
"python3 tools/probe_codex_native_retention.py --check",
|
||||
"I approve CTO-WORK-095 archive-only Codex threads older than 7 days.",
|
||||
"candidate selection reads only `id`, `rollout_path`, `updated_at`, `archived`, and file size",
|
||||
"mutation is limited to `threads.archived=1` and `archived_at`",
|
||||
"Still blocked without separate approval:",
|
||||
"delete archived session JSONL",
|
||||
"run SQLite checkpoint or vacuum",
|
||||
"read raw transcript bodies",
|
||||
]
|
||||
|
||||
REQUIRED_CODEX_RETENTION_TOOL_PHRASES = {
|
||||
"tools/report_codex_retention_pressure.py": [
|
||||
"metadata_only",
|
||||
"raw_transcript_bodies_read",
|
||||
"mutation_performed",
|
||||
"blocked_without_approval",
|
||||
],
|
||||
"tools/plan_codex_retention_policy.py": [
|
||||
"metadata_only",
|
||||
"raw_transcript_bodies_read",
|
||||
"raw_thread_text_fields_read",
|
||||
"mutation_performed",
|
||||
"approval_boundaries",
|
||||
"false_effects",
|
||||
],
|
||||
"tools/archive_codex_inactive_threads.py": [
|
||||
"approval_token_invalid",
|
||||
"backup_codex_state",
|
||||
"raw_transcript_bodies_read",
|
||||
"session_jsonl_deleted",
|
||||
"sqlite_checkpoint_or_vacuum",
|
||||
"false_effects",
|
||||
],
|
||||
"tools/probe_codex_native_retention.py": [
|
||||
"metadata_only",
|
||||
"mutation_performed",
|
||||
"native_retention_cleanup_command_advertised",
|
||||
"ephemeral_prevention_advertised",
|
||||
"false_effects",
|
||||
],
|
||||
"tools/codex_ephemeral_exec.py": [
|
||||
"codex",
|
||||
"exec",
|
||||
"--ephemeral",
|
||||
"--print-command",
|
||||
"dangerously-bypass-approvals-and-sandbox",
|
||||
"session_persistence_requested",
|
||||
"false_effects",
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def workboard_status(text: str, issue_id: str) -> str | None:
|
||||
pattern = rf"- id: {re.escape(issue_id)}\n(?: .+\n)*? status: ([^\n]+)"
|
||||
@@ -2758,6 +2865,45 @@ def main() -> int:
|
||||
if phrase not in text:
|
||||
errors.append(f"missing_provider_decision_record_phrase:{phrase}")
|
||||
|
||||
codex_retention_dry_run = ROOT / ".sot/03-PROTOCOLS/CTO-CODEX-RETENTION-DRY-RUN-PACKET.md"
|
||||
if codex_retention_dry_run.is_file():
|
||||
text = codex_retention_dry_run.read_text(encoding="utf-8")
|
||||
for phrase in REQUIRED_CODEX_RETENTION_DRY_RUN_PHRASES:
|
||||
checked.append(f"codex_retention_dry_run_phrase:{phrase}")
|
||||
if phrase not in text:
|
||||
errors.append(f"missing_codex_retention_dry_run_phrase:{phrase}")
|
||||
if "source: CTO-WORK-093" not in text:
|
||||
errors.append("codex_retention_dry_run_missing_source")
|
||||
|
||||
codex_retention_policy = ROOT / ".sot/03-PROTOCOLS/CTO-CODEX-RETENTION-POLICY-PACKET.md"
|
||||
if codex_retention_policy.is_file():
|
||||
text = codex_retention_policy.read_text(encoding="utf-8")
|
||||
for phrase in REQUIRED_CODEX_RETENTION_POLICY_PHRASES:
|
||||
checked.append(f"codex_retention_policy_phrase:{phrase}")
|
||||
if phrase not in text:
|
||||
errors.append(f"missing_codex_retention_policy_phrase:{phrase}")
|
||||
if "source: CTO-WORK-094" not in text:
|
||||
errors.append("codex_retention_policy_missing_source")
|
||||
|
||||
codex_retention_archive = ROOT / ".sot/03-PROTOCOLS/CTO-CODEX-RETENTION-ARCHIVE-EXECUTOR-PACKET.md"
|
||||
if codex_retention_archive.is_file():
|
||||
text = codex_retention_archive.read_text(encoding="utf-8")
|
||||
for phrase in REQUIRED_CODEX_RETENTION_ARCHIVE_PHRASES:
|
||||
checked.append(f"codex_retention_archive_phrase:{phrase}")
|
||||
if phrase not in text:
|
||||
errors.append(f"missing_codex_retention_archive_phrase:{phrase}")
|
||||
if "source: CTO-WORK-095" not in text:
|
||||
errors.append("codex_retention_archive_missing_source")
|
||||
|
||||
for rel, phrases in REQUIRED_CODEX_RETENTION_TOOL_PHRASES.items():
|
||||
path = ROOT / rel
|
||||
if path.is_file():
|
||||
text = path.read_text(encoding="utf-8")
|
||||
for phrase in phrases:
|
||||
checked.append(f"codex_retention_tool_phrase:{rel}:{phrase}")
|
||||
if phrase not in text:
|
||||
errors.append(f"missing_codex_retention_tool_phrase:{rel}:{phrase}")
|
||||
|
||||
board = ROOT / "WORKBOARD.yaml"
|
||||
if board.is_file():
|
||||
text = board.read_text(encoding="utf-8")
|
||||
@@ -2825,6 +2971,10 @@ def main() -> int:
|
||||
checked.append(f"workboard_id:{issue_id}")
|
||||
if issue_id not in text:
|
||||
errors.append(f"missing_workboard_id:{issue_id}")
|
||||
for issue_id in REQUIRED_CODEX_RETENTION_ISSUE_IDS:
|
||||
checked.append(f"workboard_id:{issue_id}")
|
||||
if issue_id not in text:
|
||||
errors.append(f"missing_workboard_id:{issue_id}")
|
||||
expected_statuses = {
|
||||
"CTO-WORK-001": "validated",
|
||||
"CTO-WORK-002": "validated",
|
||||
@@ -2915,6 +3065,10 @@ def main() -> int:
|
||||
"CTO-WORK-090": "validated",
|
||||
"CTO-WORK-091": "validated",
|
||||
"CTO-WORK-092": "validated",
|
||||
"CTO-WORK-093": "validated",
|
||||
"CTO-WORK-094": "validated",
|
||||
"CTO-WORK-095": "validated",
|
||||
"CTO-WORK-096": "validated",
|
||||
}
|
||||
for issue_id, expected in expected_statuses.items():
|
||||
checked.append(f"workboard_status:{issue_id}:{expected}")
|
||||
@@ -3009,6 +3163,12 @@ def main() -> int:
|
||||
errors.append("workboard_missing_core_route_admission_guard_source")
|
||||
if "CTO-CORE-ROUTE-ADMISSION-GUARD-CLOSEOUT.md" not in text:
|
||||
errors.append("workboard_missing_core_route_admission_guard_closeout_source")
|
||||
if "CTO-CODEX-RETENTION-DRY-RUN-PACKET.md" not in text:
|
||||
errors.append("workboard_missing_codex_retention_dry_run_source")
|
||||
if "CTO-CODEX-RETENTION-POLICY-PACKET.md" not in text:
|
||||
errors.append("workboard_missing_codex_retention_policy_source")
|
||||
if "CTO-CODEX-RETENTION-ARCHIVE-EXECUTOR-PACKET.md" not in text:
|
||||
errors.append("workboard_missing_codex_retention_archive_source")
|
||||
|
||||
payload = {
|
||||
"ok": not errors,
|
||||
|
||||
Reference in New Issue
Block a user