From f6eae1da55f486c6c45a50f4c3ad6e0803be6c01 Mon Sep 17 00:00:00 2001 From: Codex Date: Tue, 12 May 2026 09:34:08 +0800 Subject: [PATCH] Close PXX automation execution gate --- SESSION.md | 23 ++- TASKS.md | 23 ++- docs/AI_ICT_盯盘告警开发任务清单_v1.md | 1 + docs/AI_ICT_盯盘告警技术设计稿_v1.md | 5 + ...ICT_自动化执行层_P15-PXX闭环任务清单_v1.md | 16 +- .../run_automation_execution_closure_gate.py | 159 ++++++++++++++++++ ...t_run_automation_execution_closure_gate.py | 78 +++++++++ 7 files changed, 285 insertions(+), 20 deletions(-) create mode 100644 scripts/run_automation_execution_closure_gate.py create mode 100644 tests/unit/test_run_automation_execution_closure_gate.py diff --git a/SESSION.md b/SESSION.md index 815d27a..fa49bbe 100644 --- a/SESSION.md +++ b/SESSION.md @@ -2,7 +2,7 @@ ## Current Goal -Automation execution-layer P24 is closed. Current `HEAD` was deployed to the remote host and P24 remote ops smoke passed across health, runtime status, review packet, alert quality, operator digest, runtime incidents, maintenance summary, visual-watch dry-run, and P23 unattended harness. The default next action is PXX `Automation Execution Closure Gate`. +Automation execution-layer PXX is closed. P15-P24 are all closed, current `HEAD` was deployed to the remote host, P24 remote ops smoke passed, and the PXX closure gate froze the known execution-layer scope. The default state is long-running analysis-decision monitoring with ops rollup; new product phases require explicit user direction. ## Current Branch @@ -14,8 +14,8 @@ Automation execution-layer P24 is closed. Current `HEAD` was deployed to the rem ## Automation Status -- state: automation_execution_layer_p24_remote_smoke_ops_closure_closed -- current_phase: P24 Remote Smoke And Ops Closure +- state: automation_execution_layer_pxx_known_scope_closed +- current_phase: Long-running Analysis Decision Monitor With Ops Rollup - latest_p7_selector_choice: continue_periodic_analysis_decision_monitoring_with_maturity_rollup - latest_p14_slice: webhook_transport_retry - latest_p15_pxx_tasklist_status: ready @@ -65,7 +65,10 @@ Automation execution-layer P24 is closed. Current `HEAD` was deployed to the rem - latest_remote_deploy_status: deployed_current_head - latest_remote_ops_smoke_status: passed - latest_remote_ops_smoke_base_url: http://192.168.3.90:8000 -- next_execution_layer_phase: PXX Automation Execution Closure Gate +- latest_pxx_slice: automation_execution_closure_gate +- latest_pxx_closure_gate_status: passed +- latest_pxx_new_phase_policy: require_explicit_user_product_decision +- next_execution_layer_phase: Long-running Analysis Decision Monitor With Ops Rollup - latest_visual_review_script_status: idle - latest_runtime_visual_watch_task_status: ok - latest_visual_capture_cleanup_task_status: ok @@ -155,6 +158,8 @@ Automation execution-layer P24 is closed. Current `HEAD` was deployed to the rem - Added `scripts/run_remote_ops_smoke.py` for P24 remote API validation. - Extended password deploy smoke coverage to include alert quality, operator digest, runtime incidents, maintenance summary, and the P23 harness. - Deployed current `HEAD` to `http://192.168.3.90:8000` and confirmed P24 remote ops smoke passed without warnings or blockers. +- Added `scripts/run_automation_execution_closure_gate.py` for the PXX finite-scope closure check. +- Marked P15-P24 and PXX closed in the state anchors and froze automatic P25/P26 phase creation. ## Current Working State @@ -188,6 +193,8 @@ Automation execution-layer P24 is closed. Current `HEAD` was deployed to the rem - P23 is analysis/decision-only: `external_channels_used=false` and `real_trade_required=false`. - P24 remote deployment is complete for current `HEAD`. - P24 remote smoke is implemented and passed: `/health`, `/trading/runtime-status`, `/trading/review-packet`, `/trading/alerts/quality-summary`, `/trading/alerts/operator-digest`, `/trading/runtime-incidents`, `/trading/maintenance-summary`, visual-watch cleanup dry-run, and `/trading/unattended-harness` all passed on `http://192.168.3.90:8000`. +- PXX closure gate is implemented and passed: P15-P24 and PXX are checked in `TASKS.md`, PXX state is recorded in `SESSION.md`, final validation evidence is present, and no new Research Council phase was created. +- PXX post-state is frozen: `Long-running Analysis Decision Monitor With Ops Rollup`; new product phases require explicit user direction. - Latest `scripts/run_visual_review.py` smoke found no pending reviews: attempted=0, reviewed=0, status=idle. - Latest capture smoke returned `status=ignored` when no watch run was supplied, which is the expected safe no-op. - Latest cleanup smoke deleted one expired temporary artifact through `scripts/run_visual_capture_cleanup.py`. @@ -197,7 +204,7 @@ Automation execution-layer P24 is closed. Current `HEAD` was deployed to the rem - Latest `scripts/run_trading_runtime.py` smoke returned blockers=none and includes a healthy `visual_watch` runtime task. - Latest local runtime smoke confirmed `runtime_status.tasks.alert_dispatch.details` now exposes retry and silent-window counters with blockers=none. -## P24 Blockers +## PXX Blockers - none @@ -222,6 +229,7 @@ Automation execution-layer P24 is closed. Current `HEAD` was deployed to the rem - P22 maintenance summary remains read-only and uses visual capture retention dry-run statistics only; it must not delete business data or alter execution permission, live readiness, trust bucket, risk governor, or operator confirmation. - P23 unattended harness remains a local/remote smoke verifier only and must not use external channels, require real trades, or alter execution permission, live readiness, trust bucket, risk governor, or operator confirmation. - P24 remote ops smoke remains verification only: its visual-watch step uses dry-run cleanup and it must not use external channels, require real trades, or alter execution permission, live readiness, trust bucket, risk governor, or operator confirmation. +- PXX closure gate is verification/state-freeze only and must not create a Research Council phase, alter execution permission, enable real-live readiness, change risk governor/trust bucket behavior, or weaken operator confirmation. - A final rerun of `scripts/run_alert_dispatcher.py` and `scripts/run_trading_runtime.py` hit local PostgreSQL connection timeout because nothing was listening on `127.0.0.1:5432`; the P14 code path is still covered by full unit tests and earlier successful smoke during implementation. - P15 dispatcher smoke succeeded with `suppressed_noise_policy`, `workbench_only`, and `policy_external_allowed` counters present. P15 runtime smoke reached `alert_dispatch` successfully, but the overall runtime returned external environment blockers for market stream disconnected and OKX read transport connection refused. - P16 direct browser smoke succeeded locally with `browser_capture_status=captured`. Script-level capture without a watch run still returns safe no-op `watch_run_missing`. @@ -246,11 +254,12 @@ Automation execution-layer P24 is closed. Current `HEAD` was deployed to the rem - P24 focused validation passed: remote ops smoke tests and deploy smoke coverage tests passed; script static checks passed across 72 scripts. - P24 deploy passed through `scripts/deploy_with_password.py` with 11 HTTP smoke endpoints. - P24 remote ops smoke passed on `http://192.168.3.90:8000` with 9 passed checks, warnings=[], blockers=[]. +- PXX final validation passed: 709 unit tests, 73 script static checks, `node --check src/web/app.js`, P24 remote ops smoke passed, and closure gate passed. - Real-live order submission remains intentionally disabled because the OKX order adapter is still not a real-live submitter. ## Exact Next Step -P24 is closed. Default next action is PXX `Automation Execution Closure Gate`: verify P15-P24 are all closed, rerun final quality gates, freeze known automation execution-layer scope, and write the final closure status. +PXX is closed. Default next action is long-running analysis-decision monitoring with ops rollup; do not create P25/P26 automatically. Any new real-trading closure, real external media channel, or product capability must be opened as an explicit new user-directed phase. ## Validation Commands Used Recently @@ -282,4 +291,6 @@ D:\ai-exchange\.venv\Scripts\python.exe -m unittest tests.unit.test_unattended_h D:\ai-exchange\.venv\Scripts\python.exe scripts\run_unattended_harness.py D:\ai-exchange\.venv\Scripts\python.exe -m unittest tests.unit.test_run_remote_ops_smoke tests.unit.test_deploy_with_password D:\ai-exchange\.venv\Scripts\python.exe scripts\run_remote_ops_smoke.py --base-url http://192.168.3.90:8000 --attempts 2 --timeout-seconds 20 +D:\ai-exchange\.venv\Scripts\python.exe -m unittest tests.unit.test_run_automation_execution_closure_gate +D:\ai-exchange\.venv\Scripts\python.exe scripts\run_automation_execution_closure_gate.py ``` diff --git a/TASKS.md b/TASKS.md index 92afe67..cac75f0 100644 --- a/TASKS.md +++ b/TASKS.md @@ -13,7 +13,7 @@ ## In Progress -- [x] P24 Remote Smoke And Ops Closure complete +- [x] PXX Automation Execution Closure Gate complete ## Next Up @@ -35,14 +35,14 @@ - [x] P22 local closure complete - [x] P23 local closure complete - [x] P24 remote smoke closure complete -- [ ] Start PXX Automation Execution Closure Gate -- [ ] Continue periodic analysis-decision monitoring with maturity rollup in parallel -- [ ] Treat browser screenshot capture and additional external media adapters as future enhancements +- [x] PXX closure gate complete +- [x] Continue periodic analysis-decision monitoring with maturity rollup in parallel +- [x] Treat browser screenshot capture and additional external media adapters as future enhancements ## Automation Status -- [x] state: automation_execution_layer_p24_remote_smoke_ops_closure_closed -- [x] current_phase: P24 Remote Smoke And Ops Closure +- [x] state: automation_execution_layer_pxx_known_scope_closed +- [x] current_phase: Long-running Analysis Decision Monitor With Ops Rollup - [x] latest_p7_selector_choice: continue_periodic_analysis_decision_monitoring_with_maturity_rollup - [x] latest_p14_slice: webhook_transport_retry - [x] latest_p15_pxx_tasklist_status: ready @@ -92,7 +92,10 @@ - [x] latest_remote_deploy_status: deployed_current_head - [x] latest_remote_ops_smoke_status: passed - [x] latest_remote_ops_smoke_base_url: http://192.168.3.90:8000 -- [x] next_execution_layer_phase: PXX Automation Execution Closure Gate +- [x] latest_pxx_slice: automation_execution_closure_gate +- [x] latest_pxx_closure_gate_status: passed +- [x] latest_pxx_new_phase_policy: require_explicit_user_product_decision +- [x] next_execution_layer_phase: Long-running Analysis Decision Monitor With Ops Rollup - [x] latest_visual_review_script_status: idle - [x] latest_runtime_visual_watch_task_status: ok - [x] latest_visual_capture_cleanup_task_status: ok @@ -207,6 +210,8 @@ - [x] Added `scripts/run_remote_ops_smoke.py` - [x] Extended deploy smoke to include P20-P23 execution-layer routes - [x] Added focused P24 remote ops smoke and deploy coverage tests +- [x] Added `scripts/run_automation_execution_closure_gate.py` +- [x] Added focused PXX closure gate tests - [x] Added runtime `visual_watch` task - [x] Added focused service, script, API, and runtime tests - [x] Smoked the visual review script with no pending reviews @@ -241,6 +246,7 @@ - [x] Deployed current `HEAD` to `http://192.168.3.90:8000` - [x] Completed P24 remote deploy smoke through `scripts/deploy_with_password.py` with 11 HTTP endpoints - [x] Completed P24 remote ops smoke with 9 passed checks, no warnings, and no blockers +- [x] Completed PXX closure gate with finite-scope freeze and no new Research Council phase creation ## Blocked @@ -296,6 +302,7 @@ - [x] `automation_execution_layer_p22_maintenance_retention_rollup` - [x] `automation_execution_layer_p23_end_to_end_unattended_harness` - [x] `automation_execution_layer_p24_remote_smoke_ops_closure` +- [x] `automation_execution_layer_pxx_known_scope_closed` ## P15-PXX Finite Queue @@ -309,7 +316,7 @@ - [x] `P22` Maintenance And Retention Rollup - [x] `P23` End-To-End Unattended Harness - [x] `P24` Remote Smoke And Ops Closure -- [ ] `PXX` Automation Execution Closure Gate +- [x] `PXX` Automation Execution Closure Gate ## Backlog diff --git a/docs/AI_ICT_盯盘告警开发任务清单_v1.md b/docs/AI_ICT_盯盘告警开发任务清单_v1.md index f28f11b..d4a0526 100644 --- a/docs/AI_ICT_盯盘告警开发任务清单_v1.md +++ b/docs/AI_ICT_盯盘告警开发任务清单_v1.md @@ -414,6 +414,7 @@ candidate -> decision alert -> dispatcher -> 外部消息 -> delivery record - 2026-05-12 P22 已完成 `maintenance / retention rollup` 本地闭环:maintenance summary 以只读方式聚合 visual capture retention dry-run、delivery/feedback、runtime watchdog 与 incident journal,输出 P23 harness 可复用的 checks/blockers/warnings/p23_assertions,不执行未纳入 retention policy 的业务数据删除。 - 2026-05-12 P23 已完成 `end-to-end unattended harness` 本地闭环:`UnattendedHarnessService`、脚本与 API 会在 analysis/decision-only 模式下一次性验证 P15-P22 关键路径,输出单一 JSON blockers/warnings/passed-checks 报告,不使用外部通道、不要求真实成交。 - 2026-05-12 P24 已完成 `remote smoke / ops closure` 闭环:当前 `HEAD` 已部署到远端,部署后 11 个 HTTP smoke endpoint 通过,`scripts/run_remote_ops_smoke.py` 对 P15-P23 关键远端能力返回 9 个 passed checks、无 warnings、无 blockers。 +- 2026-05-12 PXX 已完成 `automation execution closure gate` 闭环:P15-P24 已冻结,`SESSION.md` / `TASKS.md` / `docs` 已回写,后续默认进入长期 monitor + ops rollup,不再自动发明 P25/P26。 ### 验收 - 非重大错误不打断主循环 diff --git a/docs/AI_ICT_盯盘告警技术设计稿_v1.md b/docs/AI_ICT_盯盘告警技术设计稿_v1.md index c550786..76f80b8 100644 --- a/docs/AI_ICT_盯盘告警技术设计稿_v1.md +++ b/docs/AI_ICT_盯盘告警技术设计稿_v1.md @@ -134,6 +134,11 @@ 2026-05-12 P24 已落地:remote ops smoke 覆盖 health、runtime status、review packet、alert quality、operator digest、runtime incidents、maintenance summary、visual-watch cleanup dry-run 与 P23 unattended harness。该脚本只做远端可用性验证,visual-watch 步骤使用 dry-run,不触发外部通道、不要求真实成交,也不改变任何交易权限或 operator confirmation protocol。 +#### `scripts/run_automation_execution_closure_gate.py` +职责:验证 P15-P24 与 PXX finite-scope closure 是否已经完成,并冻结自动扩阶段的默认后续状态。 + +2026-05-12 PXX 已落地:closure gate 只读取 `SESSION.md`、`TASKS.md` 与 P15-PXX 任务清单,确认 P15-P24 已 closed、PXX 后默认状态已冻结、`new_phase_policy` 要求用户显式打开新阶段。该脚本只做状态校验,不改变任何执行权限、交易权限或 operator confirmation protocol。 + #### `src/services/alerts/delivery_policy_service.py` 职责: diff --git a/docs/AI_ICT_自动化执行层_P15-PXX闭环任务清单_v1.md b/docs/AI_ICT_自动化执行层_P15-PXX闭环任务清单_v1.md index 6fd7643..abcc037 100644 --- a/docs/AI_ICT_自动化执行层_P15-PXX闭环任务清单_v1.md +++ b/docs/AI_ICT_自动化执行层_P15-PXX闭环任务清单_v1.md @@ -323,6 +323,8 @@ - 2026-05-12 P24 已新增 `scripts/run_remote_ops_smoke.py`,覆盖 `/health`、`/trading/runtime-status`、`/trading/review-packet`、`/trading/alerts/quality-summary`、`/trading/alerts/operator-digest`、`/trading/runtime-incidents`、`/trading/maintenance-summary`、visual-watch cleanup dry-run 与 `/trading/unattended-harness`。 - 2026-05-12 P24 已扩展 `scripts/deploy_with_password.py` 的部署后 HTTP smoke,使远端版本漂移会在 P20-P23 新入口缺失时失败。 - 2026-05-12 P24 已将当前 `HEAD` 部署到 `http://192.168.3.90:8000`;部署脚本 11 个 HTTP smoke endpoint 通过,P24 remote ops smoke 返回 `status=passed`、passed_checks=9、warnings=[]、blockers=[]。 +- 2026-05-12 PXX 已新增 `scripts/run_automation_execution_closure_gate.py`,以 finite-scope gate 校验 P15-P24 已闭环、`SESSION.md` / `TASKS.md` 已回写、PXX 后默认状态已冻结。 +- 2026-05-12 PXX closure gate 已通过:P15-P24 均为 closed,`SESSION.md` / `TASKS.md` / 本文档均已回写,当前执行层不再自动发明 P25/P26。 --- @@ -357,15 +359,17 @@ PXX 之后,系统默认进入长期 monitor + ops rollup,不再自动发明 ## 16. 推荐连续执行入口 -默认下一步: +PXX 关闭后默认状态: ```text -P15 Alert Noise Policy Matrix +state: automation_execution_layer_pxx_known_scope_closed +current_phase: Long-running Analysis Decision Monitor With Ops Rollup +next_action: continue_periodic_analysis_decision_monitoring_with_maturity_rollup +new_phase_policy: require_explicit_user_product_decision ``` 执行方式: -1. 打开 P15 最小闭环。 -2. 完成代码、验证、状态回写、commit。 -3. 自动进入 P16。 -4. 重复直到 PXX closure gate 或真实 blocker。 +1. 保持长期 monitor + ops rollup。 +2. 不再自动发明 P25/P26。 +3. 若未来需要真实成交闭环、真实外部媒体发送、或新的产品能力,必须由用户明确打开新阶段。 diff --git a/scripts/run_automation_execution_closure_gate.py b/scripts/run_automation_execution_closure_gate.py new file mode 100644 index 0000000..d7d41e2 --- /dev/null +++ b/scripts/run_automation_execution_closure_gate.py @@ -0,0 +1,159 @@ +from __future__ import annotations + +from datetime import datetime, timezone +from pathlib import Path +from typing import Optional +import argparse +import json +import os + + +ROOT = Path(__file__).resolve().parents[1] +TASKS_PATH = Path("TASKS.md") +SESSION_PATH = Path("SESSION.md") +TASKLIST_PATH = Path("docs/AI_ICT_自动化执行层_P15-PXX闭环任务清单_v1.md") + +REQUIRED_QUEUE_ITEMS = ( + "`P15` Alert Noise Policy Matrix", + "`P16` Browser Screenshot Capture Adapter", + "`P17` Visual Trigger ROI Router", + "`P18` Visual Evidence Media Dispatch", + "`P19` Multi-Channel Alert Routing", + "`P20` Operator Digest And Review Queue", + "`P21` Runtime Incident Journal", + "`P22` Maintenance And Retention Rollup", + "`P23` End-To-End Unattended Harness", + "`P24` Remote Smoke And Ops Closure", + "`PXX` Automation Execution Closure Gate", +) + +REQUIRED_GUARDS = ( + "execution_allowed", + "real_live_ready", + "risk_governor", + "trust_bucket", + "operator_confirmation_protocol", +) + + +def run_closure_gate(root: Path = ROOT) -> dict: + tasks = _read_text(root / TASKS_PATH) + session = _read_text(root / SESSION_PATH) + tasklist = _read_text(root / TASKLIST_PATH) + checks = [ + _check_queue_closed(tasks), + _check_session_pxx_state(session), + _check_scope_freeze_policy(tasklist), + _check_validation_evidence(session), + _check_guardrails(session), + ] + blockers = [check["name"] for check in checks if check["status"] == "blocked"] + return { + "status": "blocked" if blockers else "passed", + "mode": "automation_execution_closure_gate", + "generated_ts": datetime.now(timezone.utc).isoformat(), + "checks": checks, + "passed_checks": [check["name"] for check in checks if check["status"] == "passed"], + "blockers": blockers, + "post_pxx_state": { + "state": "automation_execution_layer_pxx_known_scope_closed", + "current_phase": "Long-running Analysis Decision Monitor With Ops Rollup", + "next_action": "continue_periodic_analysis_decision_monitoring_with_maturity_rollup", + "new_phase_policy": "require_explicit_user_product_decision", + }, + } + + +def _check_queue_closed(tasks: str) -> dict: + missing = [] + for item in REQUIRED_QUEUE_ITEMS: + if f"- [x] {item}" not in tasks: + missing.append(item) + if missing: + return _blocked("p15_pxx_queue_closed", f"unchecked queue items: {missing}") + return _passed("p15_pxx_queue_closed", "P15-P24 and PXX are checked in TASKS.md") + + +def _check_session_pxx_state(session: str) -> dict: + required = ( + "state: automation_execution_layer_pxx_known_scope_closed", + "current_phase: Long-running Analysis Decision Monitor With Ops Rollup", + "next_execution_layer_phase: Long-running Analysis Decision Monitor With Ops Rollup", + ) + missing = [item for item in required if item not in session] + if missing: + return _blocked("session_pxx_state", f"SESSION.md missing {missing}") + return _passed("session_pxx_state", "SESSION.md records the PXX frozen state") + + +def _check_scope_freeze_policy(tasklist: str) -> dict: + required = ( + "new_phase_policy: require_explicit_user_product_decision", + "不再自动发明 P25/P26", + ) + missing = [item for item in required if item not in tasklist] + if missing: + return _blocked("scope_freeze_policy", f"tasklist missing {missing}") + return _passed("scope_freeze_policy", "PXX tasklist freezes automatic phase creation") + + +def _check_validation_evidence(session: str) -> dict: + required = ( + "PXX final validation passed", + "unit tests", + "script static checks", + "P24 remote ops smoke passed", + ) + missing = [item for item in required if item not in session] + if missing: + return _blocked("validation_evidence", f"SESSION.md missing {missing}") + return _passed("validation_evidence", "SESSION.md records final quality and remote smoke evidence") + + +def _check_guardrails(session: str) -> dict: + missing = [guard for guard in REQUIRED_GUARDS if guard not in session] + if missing: + return _blocked("guardrails_unchanged", f"SESSION.md missing guards {missing}") + return _passed("guardrails_unchanged", "guardrail names remain recorded as unchanged") + + +def _read_text(path: Path) -> str: + return path.read_text(encoding="utf-8") + + +def _passed(name: str, detail: str) -> dict: + return {"name": name, "status": "passed", "detail": detail} + + +def _blocked(name: str, detail: str) -> dict: + return {"name": name, "status": "blocked", "detail": detail} + + +def _get_bool(env, key: str, default: bool) -> bool: + value = env.get(key) + if value is None or value == "": + return default + return str(value).strip().lower() in {"1", "true", "yes", "on"} + + +def _parse_args(argv: Optional[list[str]]) -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Run the PXX automation execution closure gate.") + parser.add_argument("--root", default=str(ROOT)) + return parser.parse_args(argv) + + +def main(argv: Optional[list[str]] = None, env=None) -> int: + env = os.environ if env is None else env + args = _parse_args(argv) + payload = run_closure_gate(root=Path(args.root)) + if _get_bool(env, "CLOSURE_GATE_OUTPUT_JSON", False): + print(json.dumps(payload, ensure_ascii=False, sort_keys=True)) + else: + print(f"Closure gate status: {payload['status']}") + print(f"Passed checks: {', '.join(payload['passed_checks']) or 'none'}") + print(f"Blockers: {', '.join(payload['blockers']) or 'none'}") + return 0 if payload["status"] == "passed" else 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/unit/test_run_automation_execution_closure_gate.py b/tests/unit/test_run_automation_execution_closure_gate.py new file mode 100644 index 0000000..dd20c71 --- /dev/null +++ b/tests/unit/test_run_automation_execution_closure_gate.py @@ -0,0 +1,78 @@ +import tempfile +import unittest +from pathlib import Path + +from scripts.run_automation_execution_closure_gate import REQUIRED_QUEUE_ITEMS, run_closure_gate + + +class AutomationExecutionClosureGateTests(unittest.TestCase): + def test_passes_when_queue_state_and_evidence_are_closed(self) -> None: + with tempfile.TemporaryDirectory() as tmp_dir: + root = Path(tmp_dir) + _write_closure_files(root) + + payload = run_closure_gate(root=root) + + self.assertEqual(payload["status"], "passed") + self.assertEqual(payload["blockers"], []) + self.assertIn("p15_pxx_queue_closed", payload["passed_checks"]) + self.assertEqual(payload["post_pxx_state"]["new_phase_policy"], "require_explicit_user_product_decision") + + def test_blocks_when_a_queue_item_is_not_checked(self) -> None: + with tempfile.TemporaryDirectory() as tmp_dir: + root = Path(tmp_dir) + _write_closure_files(root, unchecked_item="`P24` Remote Smoke And Ops Closure") + + payload = run_closure_gate(root=root) + + self.assertEqual(payload["status"], "blocked") + self.assertIn("p15_pxx_queue_closed", payload["blockers"]) + + def test_blocks_when_validation_evidence_is_missing(self) -> None: + with tempfile.TemporaryDirectory() as tmp_dir: + root = Path(tmp_dir) + _write_closure_files(root, include_validation=False) + + payload = run_closure_gate(root=root) + + self.assertEqual(payload["status"], "blocked") + self.assertIn("validation_evidence", payload["blockers"]) + + +def _write_closure_files(root: Path, unchecked_item=None, include_validation: bool = True) -> None: + docs = root / "docs" + docs.mkdir(parents=True) + task_lines = ["## P15-PXX Finite Queue"] + for item in REQUIRED_QUEUE_ITEMS: + checkbox = "[ ]" if item == unchecked_item else "[x]" + task_lines.append(f"- {checkbox} {item}") + (root / "TASKS.md").write_text("\n".join(task_lines), encoding="utf-8") + + validation = "" + if include_validation: + validation = "PXX final validation passed: unit tests, script static checks. P24 remote ops smoke passed." + (root / "SESSION.md").write_text( + "\n".join( + [ + "state: automation_execution_layer_pxx_known_scope_closed", + "current_phase: Long-running Analysis Decision Monitor With Ops Rollup", + "next_execution_layer_phase: Long-running Analysis Decision Monitor With Ops Rollup", + "guards_unchanged: execution_allowed, real_live_ready, risk_governor, trust_bucket, operator_confirmation_protocol", + validation, + ] + ), + encoding="utf-8", + ) + (docs / "AI_ICT_自动化执行层_P15-PXX闭环任务清单_v1.md").write_text( + "\n".join( + [ + "new_phase_policy: require_explicit_user_product_decision", + "PXX 之后不再自动发明 P25/P26。", + ] + ), + encoding="utf-8", + ) + + +if __name__ == "__main__": + unittest.main()