Files
ai-exchange/scripts/run_until_blocked.py
T
2026-05-11 23:37:47 +08:00

3470 lines
165 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional
import json
import os
import re
import subprocess
import sys
import time
ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from scripts.run_trading_delivery_rehearsal import run_trading_delivery_rehearsal
from scripts import run_analysis_decision_monitor
from src.services.research_council import CouncilGovernanceTickService
SESSION_PATH = ROOT / "SESSION.md"
TASKS_PATH = ROOT / "TASKS.md"
RUNNER_STATUS_PATH = ROOT / ".claude" / "automation_status.json"
ANALYSIS_DECISION_MONITOR_HISTORY_PATH = ROOT / ".claude" / "analysis_decision_monitor_history.jsonl"
ANALYSIS_DECISION_MONITOR_TREND_PATH = ROOT / ".claude" / "analysis_decision_monitor_trend.json"
ANALYSIS_DECISION_MONITOR_QUALITY_GATE_PATH = ROOT / ".claude" / "analysis_decision_monitor_quality_gate.json"
ANALYSIS_DECISION_MONITOR_MATURITY_ROLLUP_PATH = ROOT / ".claude" / "analysis_decision_monitor_maturity_rollup.json"
ANALYSIS_DECISION_MONITOR_REMOTE_DRIFT_CHECK_PATH = ROOT / ".claude" / "analysis_decision_monitor_remote_drift_check.json"
ANALYSIS_DECISION_SUPPORT_QUALITY_REVIEW_PATH = ROOT / ".claude" / "analysis_decision_support_quality_review.json"
ANALYSIS_DECISION_MONITOR_P7_SELECTOR_PATH = ROOT / ".claude" / "analysis_decision_monitor_p7_slice_selector.json"
ANALYSIS_DECISION_MONITOR_STABLE_SAMPLE_COUNT = 3
RUNNER_RESUME_WATCH_PATHS = [
ROOT / "src",
ROOT / "scripts",
ROOT / ".env",
ROOT / "SESSION.md",
ROOT / "TASKS.md",
]
STAGE_SEQUENCE = ["P0-2", "P0-3", "P0-4", "P0-5", "P1-1", "P1-2", "P1-3"]
STAGE_TITLES = {
"P0-1": "实时市场真值层",
"P0-2": "真实账户与订单只读同步",
"P0-3": "盘中持续调度与运行监督",
"P0-4": "决策包与主动提醒",
"P0-5": "真实交易映射与盘后闭环",
"P1-1": "辅助可信度与模型白名单",
"P1-2": "操作体验与运营工具",
"P1-3": "live adapter 前准备",
}
STAGE_GOALS = {
"P0-2": [
"建立 `BrokerReadModelService`",
"读取真实账户余额 / 持仓 / 挂单 / 成交",
"扩展 `RiskContext`",
"建立 reconciliation 读模型",
"前端展示真实 `open position / pending order`",
"focused smoke: `/trading/broker-state` 与 `/trading/market-state` 返回 broker state",
],
"P0-3": [
"建立 `TradingRuntimeScheduler`",
"建立 `TradingRuntimeSupervisor`",
"提供 runtime health API",
"前端展示 runtime status",
"focused smoke: runtime 状态 API / workbench runtime status 可见",
],
"P0-4": [
"建立 `DecisionPacketService`",
"输出统一决策包字段",
"接入主动提醒通道",
"workbench 展示决策卡",
],
"P0-5": [
"建立 execution 与真实 order/fill 映射",
"真实持仓变化触发 journal entry",
"增强真实交易归因与周报闭环",
],
"P1-1": [
"建立 symbol / session / setup 表现统计",
"增加辅助可信度评分与白名单",
],
"P1-2": [
"补齐 workbench 快捷筛选、session 视图、alert history、operator notes",
"支持一键生成盘后 review packet",
],
"P1-3": [
"校准真实 order schema",
"补全 bracket / OCO reconciliation 语义",
"建立 live readiness checklist 与 operator confirmation protocol",
],
}
DEFAULT_VALIDATION_COMMANDS = ["npm run lint", "npm run build", "npm run visual:snapshots"]
AUTOMATION_EXECUTION_MARKERS = {
"automation_flow_p0_p6_review_closed",
"automation_execution_layer_p0_governance_gate_closed",
"Automation Flow Ready For Execution-Layer Development",
"Automation Execution-Layer Development",
}
ANALYSIS_DECISION_MONITOR_MARKERS = {
"analysis_decision_monitor_runner_deployed",
"analysis_decision_monitor_runner_automation_wired",
"run_analysis_decision_monitor_periodically_without_fill_gate",
"Restricted Decision-Support Soft Rollout",
}
ANALYSIS_DECISION_MONITOR_STAGE = "analysis_decision_monitor:periodic"
ANALYSIS_DECISION_MONITOR_P7_NEXT_SLICE = "automation_execution_layer_p7_monitor_maturity_rollup"
ANALYSIS_DECISION_MONITOR_P7_REMOTE_DRIFT_SLICE = "automation_execution_layer_p7_remote_drift_check"
ANALYSIS_DECISION_MONITOR_P7_QUALITY_REVIEW_SLICE = "automation_execution_layer_p7_alert_decision_support_quality_review"
ANALYSIS_DECISION_MONITOR_P7_SELECTOR_SLICE = "automation_execution_layer_p7_bounded_automation_slice_selector"
ANALYSIS_DECISION_MONITOR_REMOTE_DRIFT_REPAIR_SLICE = "repair_analysis_decision_monitor_remote_drift"
ANALYSIS_DECISION_SUPPORT_QUALITY_IMPROVE_SLICE = "improve_analysis_decision_support_quality"
ANALYSIS_DECISION_MONITOR_WITH_MATURITY_SLICE = "continue_periodic_analysis_decision_monitoring_with_maturity_rollup"
AUTOMATION_EXECUTION_GATE_STAGE = "automation_execution_layer:P6_governance_tick"
AUTOMATION_EXECUTION_GATE_CLOSED_STATE = "automation_execution_layer_p0_governance_gate_closed"
AUTOMATION_EXECUTION_NEXT_SLICE = "automation_execution_layer_p1_anchor_slice_selector"
AUTOMATION_REQUIRED_DOC_PATHS = (
ROOT / "docs" / "AI_ICT_全自动开发执行流程_v1.md",
ROOT / "docs" / "AI_ICT_TradingAgents_P0-P6合理性审查与优化结论_v1.md",
)
AUTOMATION_EXECUTION_SLICE_SEQUENCE = [
("automation_execution_layer_p1_anchor_slice_selector", "Anchor-driven smallest-slice selector"),
("automation_execution_layer_p2_validation_quality_router", "Focused validation and quality-gate router"),
("automation_execution_layer_p3_deploy_smoke_router", "Deploy and smoke routing policy"),
("automation_execution_layer_p4_state_writeback_renderer", "State writeback renderer"),
]
AUTOMATION_EXECUTION_CLOSED_STATUS_SEQUENCE = [
("automation_execution_layer_p4_state_writeback_renderer", "automation_execution_layer_p4_state_writeback_renderer_closed", "Automation Execution-Layer P4 State Writeback Renderer"),
("automation_execution_layer_p3_deploy_smoke_router", "automation_execution_layer_p3_deploy_smoke_router_closed", "Automation Execution-Layer P3 Deploy Smoke Router"),
("automation_execution_layer_p2_validation_quality_router", "automation_execution_layer_p2_validation_quality_router_closed", "Automation Execution-Layer P2 Validation Quality Router"),
("automation_execution_layer_p1_anchor_slice_selector", "automation_execution_layer_p1_anchor_slice_selector_closed", "Automation Execution-Layer P1 Anchor Slice Selector"),
("automation_execution_layer_p0_governance_gate", AUTOMATION_EXECUTION_GATE_CLOSED_STATE, "Automation Execution-Layer P0 Governance Gate"),
]
PROJECT_PYTHON = r"D:\ai-exchange\.venv\Scripts\python.exe"
DEPLOY_SMOKE_FILE_PREFIXES = (
"migrations/",
"src/api/",
"src/services/runtime/",
"src/services/market_state/",
"src/services/readiness/",
"src/services/decision/",
"src/services/operator/",
"src/services/trust/",
"src/web/",
)
BLOCKED_MARKERS = [
"缺凭据",
"权限不足",
"不可逆",
"网络/供应商限制",
"外部阻塞",
]
def run_until_blocked(env=None, runner=None, sleeper=None) -> dict:
env = dict(os.environ if env is None else env)
runner = runner or subprocess.run
output_json = _get_bool(env, "RUN_UNTIL_BLOCKED_OUTPUT_JSON", False)
max_rounds = max(_get_int(env, "RUN_UNTIL_BLOCKED_MAX_ROUNDS", 8), 1)
max_idle_rounds = max(_get_int(env, "RUN_UNTIL_BLOCKED_MAX_IDLE_ROUNDS", 1), 0)
session_text = SESSION_PATH.read_text(encoding="utf-8")
tasks_text = TASKS_PATH.read_text(encoding="utf-8")
if should_use_analysis_decision_monitor_p7_selector(session_text=session_text, tasks_text=tasks_text):
return run_analysis_decision_monitor_p7_selector(env=env, output_json=output_json)
if should_use_analysis_decision_support_quality_review(session_text=session_text, tasks_text=tasks_text):
return run_analysis_decision_support_quality_review(env=env, output_json=output_json)
if should_use_analysis_decision_monitor_remote_drift_check(session_text=session_text, tasks_text=tasks_text):
return run_analysis_decision_monitor_remote_drift_check(env=env, output_json=output_json)
if should_use_analysis_decision_monitor_maturity_rollup(session_text=session_text, tasks_text=tasks_text):
return run_analysis_decision_monitor_maturity_rollup(env=env, output_json=output_json)
if should_use_analysis_decision_monitor(session_text=session_text, tasks_text=tasks_text):
return run_analysis_decision_monitor_gate(env=env, output_json=output_json)
if should_use_automation_execution_layer(session_text=session_text, tasks_text=tasks_text):
return run_automation_execution_layer_gate(env=env, output_json=output_json)
completed_stages = parse_done_stages(tasks_text)
current_stage = detect_current_stage(session_text, completed_stages)
idle_rounds = 0
rounds = []
blocked_reason = None
started_ts = datetime.now(timezone.utc).isoformat()
write_runner_status(
{
"state": "running",
"started_ts": started_ts,
"heartbeat_ts": started_ts,
"current_stage": current_stage,
"completed_stages": completed_stages,
"blocked_reason": None,
"rounds_completed": 0,
}
)
try:
while current_stage is not None and len(rounds) < max_rounds:
write_runner_status(
{
"state": "running",
"started_ts": started_ts,
"heartbeat_ts": datetime.now(timezone.utc).isoformat(),
"current_stage": current_stage,
"completed_stages": completed_stages,
"blocked_reason": blocked_reason,
"rounds_completed": len(rounds),
}
)
round_env = dict(env)
round_env["RUN_UNTIL_BLOCKED_STAGE"] = current_stage
round_env["RUN_UNTIL_BLOCKED_STAGE_TITLE"] = STAGE_TITLES[current_stage]
summary = run_trading_delivery_rehearsal(env=round_env, runner=runner, sleeper=sleeper)
round_result = {
"round": len(rounds) + 1,
"stage": current_stage,
"stage_title": STAGE_TITLES[current_stage],
"continuation_decision": summary.get("continuation_decision", "continue"),
"mode": summary.get("mode"),
"passed": summary.get("passed", False),
"blockers": list(summary.get("blockers") or []),
"promotion_decision": summary.get("promotion_decision"),
}
rounds.append(round_result)
if summary.get("continuation_decision") == "advance":
completed_stages.append(current_stage)
next_stage = get_next_stage(current_stage, completed_stages)
write_anchor_files(
completed_stages=completed_stages,
current_stage=next_stage,
last_result=summary,
)
current_stage = next_stage
idle_rounds = 0
continue
blocked_reason = first_blocked_reason(summary)
if blocked_reason is not None or summary.get("continuation_decision") == "blocked":
write_anchor_files(
completed_stages=completed_stages,
current_stage=current_stage,
last_result=summary,
blocked_reason=blocked_reason or ", ".join(summary.get("blockers") or []) or summary.get("mode") or "blocked",
)
break
idle_rounds += 1
write_anchor_files(
completed_stages=completed_stages,
current_stage=current_stage,
last_result=summary,
blocked_reason=None,
)
if idle_rounds > max_idle_rounds:
blocked_reason = f"idle_round_limit_exceeded:{summary.get('mode') or 'unknown'}"
break
final_stage = current_stage
summary = {
"started_ts": started_ts,
"finished_ts": datetime.now(timezone.utc).isoformat(),
"rounds": rounds,
"completed_stages": completed_stages,
"current_stage": final_stage,
"current_stage_title": STAGE_TITLES.get(final_stage),
"blocked_reason": blocked_reason,
"status": "completed" if final_stage is None and blocked_reason is None else "blocked" if blocked_reason else "stopped",
}
write_runner_status(
{
"state": summary["status"],
"started_ts": started_ts,
"heartbeat_ts": datetime.now(timezone.utc).isoformat(),
"current_stage": final_stage,
"completed_stages": completed_stages,
"blocked_reason": blocked_reason,
"rounds_completed": len(rounds),
}
)
except Exception:
write_runner_status(
{
"state": "failed",
"started_ts": started_ts,
"heartbeat_ts": datetime.now(timezone.utc).isoformat(),
"current_stage": current_stage,
"completed_stages": completed_stages,
"blocked_reason": blocked_reason or "runner_exception",
"rounds_completed": len(rounds),
}
)
raise
if output_json:
print(json.dumps(summary, ensure_ascii=False, sort_keys=True))
else:
print(f"Run-until-blocked status: {summary['status']}")
print(f"Completed stages: {', '.join(completed_stages) if completed_stages else 'none'}")
if final_stage:
print(f"Current stage: {final_stage} {STAGE_TITLES[final_stage]}")
if blocked_reason:
print(f"Blocked reason: {blocked_reason}")
return summary
def should_use_automation_execution_layer(*, session_text: str, tasks_text: str) -> bool:
anchor_text = f"{session_text}\n{tasks_text}"
return any(marker in anchor_text for marker in AUTOMATION_EXECUTION_MARKERS)
def should_use_analysis_decision_monitor(*, session_text: str, tasks_text: str) -> bool:
anchor_text = f"{session_text}\n{tasks_text}"
return any(marker in anchor_text for marker in ANALYSIS_DECISION_MONITOR_MARKERS)
def should_use_analysis_decision_monitor_maturity_rollup(*, session_text: str, tasks_text: str) -> bool:
anchor_text = f"{session_text}\n{tasks_text}"
return _anchor_has_active_slice(anchor_text=anchor_text, slice_name=ANALYSIS_DECISION_MONITOR_P7_NEXT_SLICE)
def should_use_analysis_decision_monitor_remote_drift_check(*, session_text: str, tasks_text: str) -> bool:
anchor_text = f"{session_text}\n{tasks_text}"
return (
_anchor_has_active_slice(
anchor_text=anchor_text,
slice_name=ANALYSIS_DECISION_MONITOR_P7_REMOTE_DRIFT_SLICE,
)
or _anchor_has_active_slice(
anchor_text=anchor_text,
slice_name=ANALYSIS_DECISION_MONITOR_REMOTE_DRIFT_REPAIR_SLICE,
)
)
def should_use_analysis_decision_support_quality_review(*, session_text: str, tasks_text: str) -> bool:
anchor_text = f"{session_text}\n{tasks_text}"
return _anchor_has_active_slice(
anchor_text=anchor_text,
slice_name=ANALYSIS_DECISION_MONITOR_P7_QUALITY_REVIEW_SLICE,
)
def should_use_analysis_decision_monitor_p7_selector(*, session_text: str, tasks_text: str) -> bool:
anchor_text = f"{session_text}\n{tasks_text}"
return _anchor_has_active_slice(
anchor_text=anchor_text,
slice_name=ANALYSIS_DECISION_MONITOR_P7_SELECTOR_SLICE,
)
def _anchor_has_active_slice(*, anchor_text: str, slice_name: str) -> bool:
active_markers = [
f"### {slice_name}",
f"- [ ] `{slice_name}`",
f"- [ ] {slice_name}",
f"- repair_next_action: {slice_name}",
f"repair_next_action: {slice_name}",
f"- next_slice: {slice_name}",
f"next_slice: {slice_name}",
]
return any(marker in anchor_text for marker in active_markers)
def run_analysis_decision_support_quality_review(*, env: dict, output_json: bool) -> dict:
started_ts = datetime.now(timezone.utc).isoformat()
write_runner_status(
{
"state": "running",
"mode": "analysis_decision_support_quality_review",
"started_ts": started_ts,
"heartbeat_ts": started_ts,
"current_stage": ANALYSIS_DECISION_MONITOR_P7_QUALITY_REVIEW_SLICE,
"blocked_reason": None,
"rounds_completed": 0,
}
)
base_url = str(env.get("ANALYSIS_DECISION_MONITOR_BASE_URL") or run_analysis_decision_monitor.DEFAULT_BASE_URL)
instrument_id = int(env.get("ANALYSIS_DECISION_MONITOR_INSTRUMENT_ID", "1"))
timeframe = str(env.get("ANALYSIS_DECISION_MONITOR_TIMEFRAME") or "1m")
timeout_seconds = float(
env.get(
"ANALYSIS_DECISION_MONITOR_TIMEOUT_SECONDS",
str(run_analysis_decision_monitor.DEFAULT_TIMEOUT_SECONDS),
)
)
try:
monitor_payload = run_analysis_decision_monitor.run_monitor(
base_url=base_url,
instrument_id=instrument_id,
timeframe=timeframe,
timeout_seconds=timeout_seconds,
run_p6_tick=True,
)
except Exception as exc:
monitor_payload = {
"passed": False,
"generated_ts": datetime.now(timezone.utc).isoformat(),
"base_url": base_url.rstrip("/"),
"instrument_id": instrument_id,
"timeframe": timeframe,
"phase": "analysis_decision_only",
"blockers": ["analysis_decision_support_quality_review_exception"],
"warnings": [],
"error_type": exc.__class__.__name__,
"error": str(exc),
"observed": {},
"checks": [],
}
quality_review = build_analysis_decision_support_quality_review(monitor_payload=monitor_payload)
ANALYSIS_DECISION_SUPPORT_QUALITY_REVIEW_PATH.parent.mkdir(parents=True, exist_ok=True)
ANALYSIS_DECISION_SUPPORT_QUALITY_REVIEW_PATH.write_text(
json.dumps(quality_review, ensure_ascii=False, indent=2, sort_keys=True, default=str),
encoding="utf-8",
)
summary = {
"started_ts": started_ts,
"finished_ts": datetime.now(timezone.utc).isoformat(),
"status": quality_review.get("status"),
"mode": "analysis_decision_support_quality_review",
"current_stage": ANALYSIS_DECISION_MONITOR_P7_QUALITY_REVIEW_SLICE,
"blocked_reason": quality_review.get("blocked_reason"),
"next_slice": quality_review.get("next_slice"),
"quality_review_path": _relative_path_label(ANALYSIS_DECISION_SUPPORT_QUALITY_REVIEW_PATH),
"quality_review": quality_review,
"no_new_phase_created": True,
"guards_unchanged": list(quality_review.get("guards_unchanged") or []),
"state_writeback_plan": {
"files_to_write": [
".claude/automation_status.json",
"SESSION.md",
"TASKS.md",
_relative_path_label(ANALYSIS_DECISION_SUPPORT_QUALITY_REVIEW_PATH),
],
"state": quality_review.get("state"),
"current_phase": "Restricted Decision-Support Soft Rollout",
"next_slice": quality_review.get("next_slice"),
"status": quality_review.get("status"),
"blocked_reason": quality_review.get("blocked_reason"),
"no_new_phase_created": True,
"guards_unchanged": list(quality_review.get("guards_unchanged") or []),
"docs_update_required": False,
"docs_update_reason": "analysis_decision_support_quality_review_state_writeback",
},
}
write_analysis_decision_support_quality_review_anchor_files(summary=summary)
write_runner_status(
{
"state": summary["status"],
"mode": "analysis_decision_support_quality_review",
"started_ts": started_ts,
"heartbeat_ts": datetime.now(timezone.utc).isoformat(),
"current_stage": summary.get("current_stage"),
"blocked_reason": summary.get("blocked_reason"),
"next_slice": summary.get("next_slice"),
"quality_review_path": summary.get("quality_review_path"),
"rounds_completed": 1,
"no_new_phase_created": True,
}
)
if output_json:
print(json.dumps(summary, ensure_ascii=False, sort_keys=True, default=str))
else:
print(f"P7 output quality review status: {summary['status']}")
print(f"Quality status: {quality_review.get('quality_status')}")
print(f"Next slice: {summary.get('next_slice')}")
if summary.get("blocked_reason"):
print(f"Blocked reason: {summary.get('blocked_reason')}")
return summary
def run_analysis_decision_monitor_p7_selector(*, env: dict, output_json: bool) -> dict:
started_ts = datetime.now(timezone.utc).isoformat()
write_runner_status(
{
"state": "running",
"mode": "analysis_decision_monitor_p7_selector",
"started_ts": started_ts,
"heartbeat_ts": started_ts,
"current_stage": ANALYSIS_DECISION_MONITOR_P7_SELECTOR_SLICE,
"blocked_reason": None,
"rounds_completed": 0,
}
)
trend = load_or_build_analysis_decision_monitor_trend()
quality_gate = load_or_build_analysis_decision_monitor_quality_gate(trend=trend)
maturity_rollup = load_analysis_decision_monitor_maturity_rollup()
remote_drift_check = load_analysis_decision_monitor_remote_drift_check()
quality_review = load_analysis_decision_support_quality_review()
selector = build_analysis_decision_monitor_p7_slice_selector(
trend=trend,
quality_gate=quality_gate,
maturity_rollup=maturity_rollup,
remote_drift_check=remote_drift_check,
quality_review=quality_review,
)
ANALYSIS_DECISION_MONITOR_P7_SELECTOR_PATH.parent.mkdir(parents=True, exist_ok=True)
ANALYSIS_DECISION_MONITOR_P7_SELECTOR_PATH.write_text(
json.dumps(selector, ensure_ascii=False, indent=2, sort_keys=True, default=str),
encoding="utf-8",
)
summary = {
"started_ts": started_ts,
"finished_ts": datetime.now(timezone.utc).isoformat(),
"status": selector.get("status"),
"mode": "analysis_decision_monitor_p7_selector",
"current_stage": ANALYSIS_DECISION_MONITOR_P7_SELECTOR_SLICE,
"blocked_reason": selector.get("blocked_reason"),
"next_slice": selector.get("next_slice"),
"selector_path": _relative_path_label(ANALYSIS_DECISION_MONITOR_P7_SELECTOR_PATH),
"selector": selector,
"no_new_phase_created": True,
"guards_unchanged": list(selector.get("guards_unchanged") or []),
"state_writeback_plan": {
"files_to_write": [
".claude/automation_status.json",
"SESSION.md",
"TASKS.md",
_relative_path_label(ANALYSIS_DECISION_MONITOR_P7_SELECTOR_PATH),
],
"state": selector.get("state"),
"current_phase": "Restricted Decision-Support Soft Rollout",
"next_slice": selector.get("next_slice"),
"status": selector.get("status"),
"blocked_reason": selector.get("blocked_reason"),
"no_new_phase_created": True,
"guards_unchanged": list(selector.get("guards_unchanged") or []),
"docs_update_required": False,
"docs_update_reason": "analysis_decision_monitor_p7_selector_state_writeback",
},
}
write_analysis_decision_monitor_p7_selector_anchor_files(summary=summary)
write_runner_status(
{
"state": summary["status"],
"mode": "analysis_decision_monitor_p7_selector",
"started_ts": started_ts,
"heartbeat_ts": datetime.now(timezone.utc).isoformat(),
"current_stage": summary.get("current_stage"),
"blocked_reason": summary.get("blocked_reason"),
"next_slice": summary.get("next_slice"),
"selector_path": summary.get("selector_path"),
"rounds_completed": 1,
"no_new_phase_created": True,
}
)
if output_json:
print(json.dumps(summary, ensure_ascii=False, sort_keys=True, default=str))
else:
print(f"P7 bounded selector status: {summary['status']}")
print(f"Selected next slice: {summary.get('next_slice')}")
if summary.get("blocked_reason"):
print(f"Blocked reason: {summary.get('blocked_reason')}")
return summary
def run_analysis_decision_monitor_remote_drift_check(*, env: dict, output_json: bool) -> dict:
started_ts = datetime.now(timezone.utc).isoformat()
write_runner_status(
{
"state": "running",
"mode": "analysis_decision_monitor_remote_drift_check",
"started_ts": started_ts,
"heartbeat_ts": started_ts,
"current_stage": ANALYSIS_DECISION_MONITOR_P7_REMOTE_DRIFT_SLICE,
"blocked_reason": None,
"rounds_completed": 0,
}
)
local_rollup = load_analysis_decision_monitor_maturity_rollup()
try:
remote_state = fetch_remote_analysis_decision_monitor_state(env=env)
except Exception as exc:
remote_state = {
"status": "unavailable",
"error_type": exc.__class__.__name__,
"error": str(exc),
}
drift_check = build_analysis_decision_monitor_remote_drift_check(
local_rollup=local_rollup,
remote_state=remote_state,
)
ANALYSIS_DECISION_MONITOR_REMOTE_DRIFT_CHECK_PATH.parent.mkdir(parents=True, exist_ok=True)
ANALYSIS_DECISION_MONITOR_REMOTE_DRIFT_CHECK_PATH.write_text(
json.dumps(drift_check, ensure_ascii=False, indent=2, sort_keys=True, default=str),
encoding="utf-8",
)
summary = {
"started_ts": started_ts,
"finished_ts": datetime.now(timezone.utc).isoformat(),
"status": drift_check.get("status"),
"mode": "analysis_decision_monitor_remote_drift_check",
"current_stage": ANALYSIS_DECISION_MONITOR_P7_REMOTE_DRIFT_SLICE,
"blocked_reason": drift_check.get("blocked_reason"),
"next_slice": drift_check.get("next_slice"),
"drift_check_path": _relative_path_label(ANALYSIS_DECISION_MONITOR_REMOTE_DRIFT_CHECK_PATH),
"drift_check": drift_check,
"no_new_phase_created": True,
"guards_unchanged": list(drift_check.get("guards_unchanged") or []),
"state_writeback_plan": {
"files_to_write": [
".claude/automation_status.json",
"SESSION.md",
"TASKS.md",
_relative_path_label(ANALYSIS_DECISION_MONITOR_REMOTE_DRIFT_CHECK_PATH),
],
"state": drift_check.get("state"),
"current_phase": "Restricted Decision-Support Soft Rollout",
"next_slice": drift_check.get("next_slice"),
"status": drift_check.get("status"),
"blocked_reason": drift_check.get("blocked_reason"),
"no_new_phase_created": True,
"guards_unchanged": list(drift_check.get("guards_unchanged") or []),
"docs_update_required": False,
"docs_update_reason": "analysis_decision_monitor_remote_drift_check_state_writeback",
},
}
write_analysis_decision_monitor_remote_drift_anchor_files(summary=summary)
write_runner_status(
{
"state": summary["status"],
"mode": "analysis_decision_monitor_remote_drift_check",
"started_ts": started_ts,
"heartbeat_ts": datetime.now(timezone.utc).isoformat(),
"current_stage": summary.get("current_stage"),
"blocked_reason": summary.get("blocked_reason"),
"next_slice": summary.get("next_slice"),
"drift_check_path": summary.get("drift_check_path"),
"rounds_completed": 1,
"no_new_phase_created": True,
}
)
if output_json:
print(json.dumps(summary, ensure_ascii=False, sort_keys=True, default=str))
else:
print(f"P7 remote drift check status: {summary['status']}")
print(f"Drift status: {drift_check.get('drift_status')}")
print(f"Next slice: {summary.get('next_slice')}")
if summary.get("blocked_reason"):
print(f"Blocked reason: {summary.get('blocked_reason')}")
return summary
def run_analysis_decision_monitor_maturity_rollup(*, env: dict, output_json: bool) -> dict:
started_ts = datetime.now(timezone.utc).isoformat()
write_runner_status(
{
"state": "running",
"mode": "analysis_decision_monitor_maturity_rollup",
"started_ts": started_ts,
"heartbeat_ts": started_ts,
"current_stage": ANALYSIS_DECISION_MONITOR_P7_NEXT_SLICE,
"blocked_reason": None,
"rounds_completed": 0,
}
)
trend = load_or_build_analysis_decision_monitor_trend()
quality_gate = load_or_build_analysis_decision_monitor_quality_gate(trend=trend)
rollup = build_analysis_decision_monitor_maturity_rollup(
trend=trend,
quality_gate=quality_gate,
)
ANALYSIS_DECISION_MONITOR_MATURITY_ROLLUP_PATH.parent.mkdir(parents=True, exist_ok=True)
ANALYSIS_DECISION_MONITOR_MATURITY_ROLLUP_PATH.write_text(
json.dumps(rollup, ensure_ascii=False, indent=2, sort_keys=True, default=str),
encoding="utf-8",
)
summary = {
"started_ts": started_ts,
"finished_ts": datetime.now(timezone.utc).isoformat(),
"status": rollup.get("status"),
"mode": "analysis_decision_monitor_maturity_rollup",
"current_stage": ANALYSIS_DECISION_MONITOR_P7_NEXT_SLICE,
"blocked_reason": rollup.get("blocked_reason"),
"next_slice": rollup.get("next_slice"),
"rollup_path": _relative_path_label(ANALYSIS_DECISION_MONITOR_MATURITY_ROLLUP_PATH),
"rollup": rollup,
"no_new_phase_created": True,
"guards_unchanged": list(rollup.get("guards_unchanged") or []),
"state_writeback_plan": {
"files_to_write": [
".claude/automation_status.json",
"SESSION.md",
"TASKS.md",
_relative_path_label(ANALYSIS_DECISION_MONITOR_MATURITY_ROLLUP_PATH),
],
"state": rollup.get("state"),
"current_phase": "Restricted Decision-Support Soft Rollout",
"next_slice": rollup.get("next_slice"),
"status": rollup.get("status"),
"blocked_reason": rollup.get("blocked_reason"),
"no_new_phase_created": True,
"guards_unchanged": list(rollup.get("guards_unchanged") or []),
"docs_update_required": False,
"docs_update_reason": "analysis_decision_monitor_maturity_rollup_state_writeback",
},
}
write_analysis_decision_monitor_maturity_rollup_anchor_files(summary=summary)
write_runner_status(
{
"state": summary["status"],
"mode": "analysis_decision_monitor_maturity_rollup",
"started_ts": started_ts,
"heartbeat_ts": datetime.now(timezone.utc).isoformat(),
"current_stage": summary.get("current_stage"),
"blocked_reason": summary.get("blocked_reason"),
"next_slice": summary.get("next_slice"),
"rollup_path": summary.get("rollup_path"),
"rounds_completed": 1,
"no_new_phase_created": True,
}
)
if output_json:
print(json.dumps(summary, ensure_ascii=False, sort_keys=True, default=str))
else:
print(f"P7 monitor maturity rollup status: {summary['status']}")
print(f"Maturity status: {rollup.get('maturity_status')}")
print(f"Next slice: {summary.get('next_slice')}")
if summary.get("blocked_reason"):
print(f"Blocked reason: {summary.get('blocked_reason')}")
return summary
def run_analysis_decision_monitor_gate(*, env: dict, output_json: bool) -> dict:
started_ts = datetime.now(timezone.utc).isoformat()
write_runner_status(
{
"state": "running",
"mode": "analysis_decision_monitor",
"started_ts": started_ts,
"heartbeat_ts": started_ts,
"current_stage": ANALYSIS_DECISION_MONITOR_STAGE,
"blocked_reason": None,
"rounds_completed": 0,
}
)
base_url = str(env.get("ANALYSIS_DECISION_MONITOR_BASE_URL") or run_analysis_decision_monitor.DEFAULT_BASE_URL)
instrument_id = int(env.get("ANALYSIS_DECISION_MONITOR_INSTRUMENT_ID", "1"))
timeframe = str(env.get("ANALYSIS_DECISION_MONITOR_TIMEFRAME") or "1m")
timeout_seconds = float(
env.get(
"ANALYSIS_DECISION_MONITOR_TIMEOUT_SECONDS",
str(run_analysis_decision_monitor.DEFAULT_TIMEOUT_SECONDS),
)
)
run_p6_tick = _get_bool(env, "ANALYSIS_DECISION_MONITOR_RUN_P6_TICK", True)
try:
monitor_payload = run_analysis_decision_monitor.run_monitor(
base_url=base_url,
instrument_id=instrument_id,
timeframe=timeframe,
timeout_seconds=timeout_seconds,
run_p6_tick=run_p6_tick,
)
except Exception as exc:
monitor_payload = {
"passed": False,
"generated_ts": datetime.now(timezone.utc).isoformat(),
"base_url": base_url.rstrip("/"),
"instrument_id": instrument_id,
"timeframe": timeframe,
"phase": "analysis_decision_only",
"blockers": ["analysis_decision_monitor_exception"],
"warnings": [],
"error_type": exc.__class__.__name__,
"error": str(exc),
"observed": {},
}
summary = build_analysis_decision_monitor_summary(
started_ts=started_ts,
monitor_payload=monitor_payload,
)
summary["next_slice"] = resolve_analysis_decision_monitor_next_slice(summary=summary)
summary["monitor_history"] = append_analysis_decision_monitor_history(summary=summary)
summary["state_writeback_plan"] = build_analysis_decision_monitor_writeback_plan(summary=summary)
write_analysis_decision_monitor_anchor_files(summary=summary)
write_runner_status(
{
"state": summary["status"],
"mode": "analysis_decision_monitor",
"started_ts": started_ts,
"heartbeat_ts": datetime.now(timezone.utc).isoformat(),
"current_stage": summary.get("current_stage"),
"blocked_reason": summary.get("blocked_reason"),
"rounds_completed": len(summary.get("rounds") or []),
"monitor_passed": bool(monitor_payload.get("passed")),
"monitor_generated_ts": monitor_payload.get("generated_ts"),
"monitor_blockers": list(monitor_payload.get("blockers") or []),
"monitor_warnings": list(monitor_payload.get("warnings") or []),
"next_slice": summary.get("next_slice"),
"no_new_phase_created": True,
}
)
if output_json:
print(json.dumps(summary, ensure_ascii=False, sort_keys=True, default=str))
else:
print(f"Analysis/decision monitor status: {summary['status']}")
print(f"Monitor passed: {monitor_payload.get('passed')}")
print(f"Blockers: {', '.join(monitor_payload.get('blockers') or []) or 'none'}")
print(f"Warnings: {', '.join(monitor_payload.get('warnings') or []) or 'none'}")
print(f"Next slice: {summary.get('next_slice')}")
return summary
def build_analysis_decision_monitor_summary(*, started_ts: str, monitor_payload: dict) -> dict:
blockers = list(monitor_payload.get("blockers") or [])
warnings = list(monitor_payload.get("warnings") or [])
passed = bool(monitor_payload.get("passed")) and not blockers
blocked_reason = None if passed else f"analysis_decision_monitor_blockers:{','.join(blockers) or 'monitor_failed'}"
next_slice = "continue_periodic_analysis_decision_monitoring" if passed else "repair_analysis_decision_monitor_blockers"
status = "completed" if passed else "blocked"
summary = {
"started_ts": started_ts,
"finished_ts": datetime.now(timezone.utc).isoformat(),
"status": status,
"mode": "analysis_decision_monitor",
"active_gate": "analysis_decision_only_restricted_decision_support_monitor",
"phase": monitor_payload.get("phase") or "analysis_decision_only",
"monitor_passed": passed,
"monitor_generated_ts": monitor_payload.get("generated_ts"),
"monitor_blockers": blockers,
"monitor_warnings": warnings,
"observed": dict(monitor_payload.get("observed") or {}),
"rounds": [
{
"round": 1,
"stage": ANALYSIS_DECISION_MONITOR_STAGE,
"passed": passed,
"blockers": blockers,
"warnings": warnings,
}
],
"current_stage": ANALYSIS_DECISION_MONITOR_STAGE,
"blocked_reason": blocked_reason,
"next_slice": next_slice,
"no_new_phase_created": True,
"guards_unchanged": [
"execution_allowed",
"real_live_ready",
"risk_governor",
"trust_bucket",
"operator_confirmation_protocol",
],
"monitor_payload": monitor_payload,
}
summary["state_writeback_plan"] = build_analysis_decision_monitor_writeback_plan(summary=summary)
return summary
def resolve_analysis_decision_monitor_next_slice(*, summary: dict) -> str:
if not summary.get("monitor_passed"):
return "repair_analysis_decision_monitor_blockers"
gate = preview_analysis_decision_monitor_quality_gate(summary=summary)
if gate.get("passed"):
return ANALYSIS_DECISION_MONITOR_P7_NEXT_SLICE
return "continue_periodic_analysis_decision_monitoring"
def build_analysis_decision_monitor_writeback_plan(*, summary: dict) -> dict:
files_to_write = [
".claude/automation_status.json",
"SESSION.md",
"TASKS.md",
]
history_path = (summary.get("monitor_history") or {}).get("path")
if history_path:
files_to_write.append(history_path)
trend_path = (summary.get("monitor_history") or {}).get("trend_path")
if trend_path:
files_to_write.append(trend_path)
quality_gate_path = (summary.get("monitor_history") or {}).get("quality_gate_path")
if quality_gate_path:
files_to_write.append(quality_gate_path)
return {
"files_to_write": files_to_write,
"state": "analysis_decision_monitor_runner_automation_wired",
"current_phase": "Restricted Decision-Support Soft Rollout",
"next_slice": summary.get("next_slice"),
"status": summary.get("status"),
"blocked_reason": summary.get("blocked_reason"),
"no_new_phase_created": bool(summary.get("no_new_phase_created")),
"guards_unchanged": list(summary.get("guards_unchanged") or []),
"docs_update_required": False,
"docs_update_reason": "analysis_decision_monitor_state_writeback",
}
def append_analysis_decision_monitor_history(*, summary: dict) -> dict:
record = build_analysis_decision_monitor_history_record(summary=summary)
ANALYSIS_DECISION_MONITOR_HISTORY_PATH.parent.mkdir(parents=True, exist_ok=True)
with ANALYSIS_DECISION_MONITOR_HISTORY_PATH.open("a", encoding="utf-8", newline="\n") as handle:
handle.write(json.dumps(record, ensure_ascii=False, sort_keys=True, default=str))
handle.write("\n")
records = read_analysis_decision_monitor_history_records(ANALYSIS_DECISION_MONITOR_HISTORY_PATH)
trend = build_analysis_decision_monitor_trend(records=records)
quality_gate = build_analysis_decision_monitor_quality_gate(trend=trend)
ANALYSIS_DECISION_MONITOR_TREND_PATH.parent.mkdir(parents=True, exist_ok=True)
ANALYSIS_DECISION_MONITOR_TREND_PATH.write_text(
json.dumps(trend, ensure_ascii=False, indent=2, sort_keys=True, default=str),
encoding="utf-8",
)
ANALYSIS_DECISION_MONITOR_QUALITY_GATE_PATH.parent.mkdir(parents=True, exist_ok=True)
ANALYSIS_DECISION_MONITOR_QUALITY_GATE_PATH.write_text(
json.dumps(quality_gate, ensure_ascii=False, indent=2, sort_keys=True, default=str),
encoding="utf-8",
)
return {
"path": _relative_path_label(ANALYSIS_DECISION_MONITOR_HISTORY_PATH),
"trend_path": _relative_path_label(ANALYSIS_DECISION_MONITOR_TREND_PATH),
"quality_gate_path": _relative_path_label(ANALYSIS_DECISION_MONITOR_QUALITY_GATE_PATH),
"latest_recorded_ts": record["recorded_ts"],
"record_count": len(records),
"trend": trend,
"quality_gate": quality_gate,
}
def build_analysis_decision_monitor_history_record(*, summary: dict) -> dict:
return {
"recorded_ts": datetime.now(timezone.utc).isoformat(),
"monitor_generated_ts": summary.get("monitor_generated_ts"),
"status": summary.get("status"),
"monitor_passed": bool(summary.get("monitor_passed")),
"blockers": list(summary.get("monitor_blockers") or []),
"warnings": list(summary.get("monitor_warnings") or []),
"observed": dict(summary.get("observed") or {}),
"next_slice": summary.get("next_slice"),
"no_new_phase_created": bool(summary.get("no_new_phase_created")),
"guards_unchanged": list(summary.get("guards_unchanged") or []),
}
def preview_analysis_decision_monitor_quality_gate(*, summary: dict) -> dict:
records = read_analysis_decision_monitor_history_records(ANALYSIS_DECISION_MONITOR_HISTORY_PATH)
records.append(build_analysis_decision_monitor_history_record(summary=summary))
trend = build_analysis_decision_monitor_trend(records=records)
return build_analysis_decision_monitor_quality_gate(trend=trend)
def load_or_build_analysis_decision_monitor_trend() -> dict:
if ANALYSIS_DECISION_MONITOR_TREND_PATH.exists():
return json.loads(ANALYSIS_DECISION_MONITOR_TREND_PATH.read_text(encoding="utf-8"))
records = read_analysis_decision_monitor_history_records(ANALYSIS_DECISION_MONITOR_HISTORY_PATH)
return build_analysis_decision_monitor_trend(records=records)
def load_or_build_analysis_decision_monitor_quality_gate(*, trend: dict) -> dict:
if ANALYSIS_DECISION_MONITOR_QUALITY_GATE_PATH.exists():
return json.loads(ANALYSIS_DECISION_MONITOR_QUALITY_GATE_PATH.read_text(encoding="utf-8"))
return build_analysis_decision_monitor_quality_gate(trend=trend)
def load_analysis_decision_monitor_maturity_rollup() -> dict:
if ANALYSIS_DECISION_MONITOR_MATURITY_ROLLUP_PATH.exists():
return json.loads(ANALYSIS_DECISION_MONITOR_MATURITY_ROLLUP_PATH.read_text(encoding="utf-8"))
trend = load_or_build_analysis_decision_monitor_trend()
quality_gate = load_or_build_analysis_decision_monitor_quality_gate(trend=trend)
return build_analysis_decision_monitor_maturity_rollup(trend=trend, quality_gate=quality_gate)
def build_analysis_decision_monitor_maturity_rollup(*, trend: dict, quality_gate: dict) -> dict:
observed = dict(quality_gate.get("latest_observed") or trend.get("latest_observed") or {})
gate_passed = bool(quality_gate.get("passed"))
failure_reasons = list(quality_gate.get("failure_reasons") or [])
blocked_reason = None if gate_passed else f"analysis_decision_monitor_maturity_not_ready:{','.join(failure_reasons) or 'quality_gate_not_passed'}"
next_slice = (
ANALYSIS_DECISION_MONITOR_P7_REMOTE_DRIFT_SLICE
if gate_passed
else quality_gate.get("next_slice") or trend.get("next_slice") or "continue_periodic_analysis_decision_monitoring"
)
guards_unchanged = list(
quality_gate.get("guards_unchanged")
or trend.get("guards_unchanged")
or [
"execution_allowed",
"real_live_ready",
"risk_governor",
"trust_bucket",
"operator_confirmation_protocol",
]
)
return {
"generated_ts": datetime.now(timezone.utc).isoformat(),
"state": (
"automation_execution_layer_p7_monitor_maturity_rollup_closed"
if gate_passed
else "automation_execution_layer_p7_monitor_maturity_rollup_blocked"
),
"status": "completed" if gate_passed else "blocked",
"maturity_status": "mature" if gate_passed else "attention_required",
"current_phase": "Restricted Decision-Support Soft Rollout",
"current_slice": ANALYSIS_DECISION_MONITOR_P7_NEXT_SLICE,
"next_slice": next_slice,
"blocked_reason": blocked_reason,
"local": {
"record_count": quality_gate.get("record_count") or trend.get("record_count"),
"consecutive_pass_count": quality_gate.get("consecutive_pass_count") or trend.get("consecutive_pass_count"),
"stable_sample_threshold": trend.get("stable_sample_threshold"),
"stable_monitoring_window": bool(quality_gate.get("stable_monitoring_window")),
"quality_gate_status": quality_gate.get("status"),
"quality_gate_passed": gate_passed,
"quality_gate_failure_reasons": failure_reasons,
"latest_monitor_generated_ts": quality_gate.get("latest_monitor_generated_ts") or trend.get("latest_monitor_generated_ts"),
"latest_blockers": list(trend.get("latest_blockers") or []),
"latest_warnings": list(trend.get("latest_warnings") or []),
},
"latest_observed": observed,
"stable_window_conclusion": (
"stable_monitoring_window_passed"
if gate_passed
else "stable_monitoring_window_not_ready"
),
"current_allowed_lane": "restricted_decision_support_observe_only",
"prohibited_lanes": [
"real_live_auto_trading",
"execution_allowed_enablement",
"real_live_ready_enablement",
"risk_governor_override",
"trust_bucket_change",
"operator_confirmation_bypass",
],
"guards_unchanged": guards_unchanged,
"remote_drift_check_status": "pending",
"no_research_council_phase_created": True,
"notes": [
"automation_execution_layer_p7_not_research_council_p7",
"analysis_decision_only_phase",
"okx_fills_zero_not_current_phase_blocker",
"real_trade_sample_deferred",
],
}
def fetch_remote_analysis_decision_monitor_state(*, env: dict) -> dict:
password = str(env.get("AI_ICT_DEPLOY_PASSWORD") or "")
if not password:
raise RuntimeError("missing_remote_password: set AI_ICT_DEPLOY_PASSWORD")
from scripts.deploy_with_password import (
DEFAULT_HOST,
DEFAULT_REMOTE_PATH,
DEFAULT_USER,
_sh_quote,
connect_ssh,
run_remote,
)
host = str(env.get("AI_ICT_DEPLOY_HOST") or DEFAULT_HOST)
user = str(env.get("AI_ICT_DEPLOY_USER") or DEFAULT_USER)
port = int(env.get("AI_ICT_DEPLOY_PORT") or "22")
remote_path = str(env.get("AI_ICT_DEPLOY_REMOTE_PATH") or DEFAULT_REMOTE_PATH)
client = connect_ssh(host=host, user=user, password=password, port=port)
try:
container_quality = json.loads(
run_remote(
client,
"docker exec -i ai-ict sh -lc 'cat .claude/analysis_decision_monitor_quality_gate.json'",
timeout=180,
)
)
container_trend = json.loads(
run_remote(
client,
"docker exec -i ai-ict sh -lc 'cat .claude/analysis_decision_monitor_trend.json'",
timeout=180,
)
)
host_quality = json.loads(
run_remote(
client,
"sh -lc "
+ _sh_quote(f"cat {remote_path}/.claude/analysis_decision_monitor_quality_gate.json"),
timeout=180,
)
)
finally:
client.close()
return {
"status": "available",
"host": host,
"container_quality_gate": container_quality,
"container_trend": container_trend,
"host_quality_gate": host_quality,
}
def build_analysis_decision_monitor_remote_drift_check(*, local_rollup: dict, remote_state: dict) -> dict:
local = dict(local_rollup.get("local") or {})
local_observed = dict(local_rollup.get("latest_observed") or {})
container_quality = dict(remote_state.get("container_quality_gate") or {})
host_quality = dict(remote_state.get("host_quality_gate") or {})
container_trend = dict(remote_state.get("container_trend") or {})
remote_observed = dict(container_quality.get("latest_observed") or container_trend.get("latest_observed") or {})
failure_reasons = []
if local_rollup.get("maturity_status") != "mature":
failure_reasons.append("local_maturity_not_mature")
if local.get("quality_gate_status") != "passed":
failure_reasons.append("local_quality_gate_not_passed")
if remote_state.get("status") != "available":
failure_reasons.append("remote_state_unavailable")
if container_quality.get("status") != "passed":
failure_reasons.append("remote_container_quality_gate_not_passed")
if host_quality.get("status") != "passed":
failure_reasons.append("remote_host_quality_gate_not_passed")
if not bool(container_quality.get("stable_monitoring_window")):
failure_reasons.append("remote_stable_window_not_passed")
remote_consecutive = container_quality.get("consecutive_pass_count") or container_trend.get("consecutive_pass_count")
remote_threshold = container_trend.get("stable_sample_threshold") or ANALYSIS_DECISION_MONITOR_STABLE_SAMPLE_COUNT
if int(remote_consecutive or 0) < int(remote_threshold or ANALYSIS_DECISION_MONITOR_STABLE_SAMPLE_COUNT):
failure_reasons.append("remote_consecutive_passes_below_threshold")
compare_fields = [
"broker_status",
"runtime_health",
"market_gap",
"decision_action",
"decision_operator_mode",
"real_trade_sample_gate_status",
"p6_governance_tick_status",
]
observed_diffs = {}
for field in compare_fields:
if local_observed.get(field) != remote_observed.get(field):
observed_diffs[field] = {
"local": local_observed.get(field),
"remote": remote_observed.get(field),
}
if observed_diffs:
failure_reasons.append("local_remote_observed_fields_drifted")
local_guards = set(local_rollup.get("guards_unchanged") or [])
remote_guards = set(container_quality.get("guards_unchanged") or [])
if local_guards and remote_guards and local_guards != remote_guards:
failure_reasons.append("guard_set_drifted")
passed = not failure_reasons
return {
"generated_ts": datetime.now(timezone.utc).isoformat(),
"state": (
"automation_execution_layer_p7_remote_drift_check_closed"
if passed
else "automation_execution_layer_p7_remote_drift_check_blocked"
),
"status": "completed" if passed else "blocked",
"drift_status": "clear" if passed else "drift_detected",
"current_phase": "Restricted Decision-Support Soft Rollout",
"current_slice": ANALYSIS_DECISION_MONITOR_P7_REMOTE_DRIFT_SLICE,
"next_slice": (
ANALYSIS_DECISION_MONITOR_P7_QUALITY_REVIEW_SLICE
if passed
else ANALYSIS_DECISION_MONITOR_REMOTE_DRIFT_REPAIR_SLICE
),
"blocked_reason": (
None
if passed
else f"analysis_decision_monitor_remote_drift:{','.join(failure_reasons)}"
),
"failure_reasons": failure_reasons,
"local": {
"maturity_status": local_rollup.get("maturity_status"),
"quality_gate_status": local.get("quality_gate_status"),
"record_count": local.get("record_count"),
"consecutive_pass_count": local.get("consecutive_pass_count"),
"stable_monitoring_window": local.get("stable_monitoring_window"),
},
"remote": {
"status": remote_state.get("status"),
"host": remote_state.get("host"),
"container_quality_gate_status": container_quality.get("status"),
"container_record_count": container_quality.get("record_count"),
"container_consecutive_pass_count": container_quality.get("consecutive_pass_count"),
"container_stable_monitoring_window": container_quality.get("stable_monitoring_window"),
"host_quality_gate_status": host_quality.get("status"),
"host_record_count": host_quality.get("record_count"),
"host_consecutive_pass_count": host_quality.get("consecutive_pass_count"),
"host_stable_monitoring_window": host_quality.get("stable_monitoring_window"),
"error_type": remote_state.get("error_type"),
"error": remote_state.get("error"),
},
"observed_diffs": observed_diffs,
"latest_observed": {
"local": local_observed,
"remote": remote_observed,
},
"guards_unchanged": list(local_rollup.get("guards_unchanged") or container_quality.get("guards_unchanged") or []),
"no_research_council_phase_created": True,
"notes": [
"automation_execution_layer_p7_not_research_council_p7",
"remote_drift_check_only_reads_monitor_state",
"okx_fills_zero_not_current_phase_blocker",
],
}
def load_analysis_decision_monitor_remote_drift_check() -> dict:
if ANALYSIS_DECISION_MONITOR_REMOTE_DRIFT_CHECK_PATH.exists():
return json.loads(ANALYSIS_DECISION_MONITOR_REMOTE_DRIFT_CHECK_PATH.read_text(encoding="utf-8"))
local_rollup = load_analysis_decision_monitor_maturity_rollup()
remote_state = {
"status": "unavailable",
"error_type": "artifact_missing",
"error": "analysis_decision_monitor_remote_drift_check_missing",
"container_quality_gate": {},
"container_trend": {},
"host_quality_gate": {},
}
return build_analysis_decision_monitor_remote_drift_check(local_rollup=local_rollup, remote_state=remote_state)
def load_analysis_decision_support_quality_review() -> dict:
if ANALYSIS_DECISION_SUPPORT_QUALITY_REVIEW_PATH.exists():
return json.loads(ANALYSIS_DECISION_SUPPORT_QUALITY_REVIEW_PATH.read_text(encoding="utf-8"))
return {
"generated_ts": datetime.now(timezone.utc).isoformat(),
"state": "automation_execution_layer_p7_alert_decision_support_quality_review_blocked",
"status": "blocked",
"quality_status": "needs_attention",
"current_phase": "Restricted Decision-Support Soft Rollout",
"current_slice": ANALYSIS_DECISION_MONITOR_P7_QUALITY_REVIEW_SLICE,
"next_slice": ANALYSIS_DECISION_SUPPORT_QUALITY_IMPROVE_SLICE,
"blocked_reason": "analysis_decision_support_quality:quality_review_artifact_missing",
"failure_reasons": ["quality_review_artifact_missing"],
"required_check_codes": [
"decision_restricted_support",
"decision_observe_only",
"decision_execution_not_allowed",
"review_packet_present",
"review_trust_score_present",
"review_research_summary_present",
"real_trade_sample_gate_deferred",
],
"missing_required_checks": [
"decision_restricted_support",
"decision_observe_only",
"decision_execution_not_allowed",
"review_packet_present",
"review_trust_score_present",
"review_research_summary_present",
"real_trade_sample_gate_deferred",
],
"failed_required_checks": [],
"monitor_blockers": [],
"monitor_warnings": [],
"latest_observed": {},
"operator_mode": None,
"decision_action": None,
"trust_classification": None,
"guards_unchanged": [
"execution_allowed",
"real_live_ready",
"risk_governor",
"trust_bucket",
"operator_confirmation_protocol",
],
"no_research_council_phase_created": True,
"notes": [
"automation_execution_layer_p7_not_research_council_p7",
"quality_review_artifact_missing",
],
}
def build_analysis_decision_support_quality_review(*, monitor_payload: dict) -> dict:
checks = {str(check.get("code")): dict(check) for check in monitor_payload.get("checks") or []}
observed = dict(monitor_payload.get("observed") or {})
required_check_codes = [
"decision_restricted_support",
"decision_observe_only",
"decision_execution_not_allowed",
"review_packet_present",
"review_trust_score_present",
"review_research_summary_present",
"real_trade_sample_gate_deferred",
]
missing_required_checks = [code for code in required_check_codes if code not in checks]
failed_required_checks = [
code
for code in required_check_codes
if code in checks and not bool(checks[code].get("passed"))
]
monitor_blockers = list(monitor_payload.get("blockers") or [])
monitor_warnings = list(monitor_payload.get("warnings") or [])
failure_reasons = []
if not bool(monitor_payload.get("passed")):
failure_reasons.append("monitor_not_passed")
if monitor_blockers:
failure_reasons.append("monitor_has_blockers")
if monitor_warnings:
failure_reasons.append("monitor_has_warnings")
if missing_required_checks:
failure_reasons.append("required_quality_checks_missing")
if failed_required_checks:
failure_reasons.append("required_quality_checks_failed")
if observed.get("decision_action") != "restricted_decision_support":
failure_reasons.append("decision_action_not_restricted_support")
if observed.get("decision_operator_mode") != "observe_only":
failure_reasons.append("operator_mode_not_observe_only")
if observed.get("real_trade_sample_gate_status") != "deferred_analysis_decision_only":
failure_reasons.append("real_trade_sample_gate_not_deferred")
quality_passed = not failure_reasons
next_slice = ANALYSIS_DECISION_MONITOR_P7_SELECTOR_SLICE
return {
"generated_ts": datetime.now(timezone.utc).isoformat(),
"state": (
"automation_execution_layer_p7_alert_decision_support_quality_review_closed"
if quality_passed
else "automation_execution_layer_p7_alert_decision_support_quality_review_blocked"
),
"status": "completed" if quality_passed else "blocked",
"quality_status": "healthy" if quality_passed else "needs_attention",
"current_phase": "Restricted Decision-Support Soft Rollout",
"current_slice": ANALYSIS_DECISION_MONITOR_P7_QUALITY_REVIEW_SLICE,
"next_slice": next_slice,
"blocked_reason": (
None
if quality_passed
else f"analysis_decision_support_quality:{','.join(failure_reasons)}"
),
"failure_reasons": failure_reasons,
"required_check_codes": required_check_codes,
"missing_required_checks": missing_required_checks,
"failed_required_checks": failed_required_checks,
"monitor_blockers": monitor_blockers,
"monitor_warnings": monitor_warnings,
"latest_observed": observed,
"operator_mode": observed.get("decision_operator_mode"),
"decision_action": observed.get("decision_action"),
"trust_classification": observed.get("decision_trust_classification"),
"guards_unchanged": [
"execution_allowed",
"real_live_ready",
"risk_governor",
"trust_bucket",
"operator_confirmation_protocol",
],
"no_research_council_phase_created": True,
"notes": [
"automation_execution_layer_p7_not_research_council_p7",
"quality_review_only_reads_decision_support_outputs",
"okx_fills_zero_not_current_phase_blocker",
],
}
def build_analysis_decision_monitor_p7_slice_selector(
*,
trend: dict,
quality_gate: dict,
maturity_rollup: dict,
remote_drift_check: dict,
quality_review: dict,
) -> dict:
trend_record_count = int(trend.get("record_count") or 0)
trend_pass_count = int(trend.get("consecutive_pass_count") or 0)
stable_sample_threshold = int(trend.get("stable_sample_threshold") or ANALYSIS_DECISION_MONITOR_STABLE_SAMPLE_COUNT)
stable_window = bool(quality_gate.get("stable_monitoring_window")) and bool(trend.get("stable_monitoring_window"))
monitor_blockers = list(quality_gate.get("failure_reasons") or trend.get("latest_blockers") or [])
remote_drift_status = str(remote_drift_check.get("drift_status") or "unknown")
remote_failure_reasons = list(remote_drift_check.get("failure_reasons") or [])
quality_status = str(quality_review.get("quality_status") or "unknown")
quality_failure_reasons = list(quality_review.get("failure_reasons") or [])
maturity_status = str(maturity_rollup.get("maturity_status") or "unknown")
selection_reasons = []
if monitor_blockers:
next_slice = "repair_analysis_decision_monitor_blockers"
selection_reasons.append("monitor_blockers_present")
elif remote_drift_status != "clear" or remote_failure_reasons:
next_slice = ANALYSIS_DECISION_MONITOR_REMOTE_DRIFT_REPAIR_SLICE
selection_reasons.append("remote_drift_detected")
elif quality_status != "healthy" or quality_failure_reasons:
next_slice = ANALYSIS_DECISION_SUPPORT_QUALITY_IMPROVE_SLICE
selection_reasons.append("decision_support_quality_needs_attention")
elif maturity_status != "mature" or not stable_window or trend_pass_count < stable_sample_threshold:
next_slice = "continue_periodic_analysis_decision_monitoring"
selection_reasons.append("monitor_not_stable")
else:
next_slice = ANALYSIS_DECISION_MONITOR_WITH_MATURITY_SLICE
selection_reasons.append("all_monitoring_artifacts_healthy")
return {
"generated_ts": datetime.now(timezone.utc).isoformat(),
"state": "automation_execution_layer_p7_bounded_automation_slice_selector_closed",
"status": "completed",
"current_phase": "Restricted Decision-Support Soft Rollout",
"current_slice": ANALYSIS_DECISION_MONITOR_P7_SELECTOR_SLICE,
"next_slice": next_slice,
"selected_next_slice": next_slice,
"selection_reason": selection_reasons[0] if selection_reasons else "unspecified",
"selection_reasons": selection_reasons,
"selection_priority": (
"monitor_blocker"
if next_slice == "repair_analysis_decision_monitor_blockers"
else "remote_drift"
if next_slice == ANALYSIS_DECISION_MONITOR_REMOTE_DRIFT_REPAIR_SLICE
else "output_quality"
if next_slice == ANALYSIS_DECISION_SUPPORT_QUALITY_IMPROVE_SLICE
else "monitor_not_stable"
if next_slice == "continue_periodic_analysis_decision_monitoring"
else "healthy_rollforward"
),
"trend": {
"record_count": trend_record_count,
"consecutive_pass_count": trend_pass_count,
"stable_sample_threshold": stable_sample_threshold,
"stable_monitoring_window": bool(trend.get("stable_monitoring_window")),
"latest_monitor_passed": bool(trend.get("latest_monitor_passed")),
},
"quality_gate": {
"status": quality_gate.get("status"),
"passed": bool(quality_gate.get("passed")),
"failure_reasons": list(quality_gate.get("failure_reasons") or []),
"stable_monitoring_window": bool(quality_gate.get("stable_monitoring_window")),
},
"maturity_rollup": {
"state": maturity_rollup.get("state"),
"maturity_status": maturity_status,
"blocked_reason": maturity_rollup.get("blocked_reason"),
"remote_drift_check_status": maturity_rollup.get("remote_drift_check_status"),
},
"remote_drift_check": {
"state": remote_drift_check.get("state"),
"drift_status": remote_drift_status,
"failure_reasons": remote_failure_reasons,
"blocked_reason": remote_drift_check.get("blocked_reason"),
},
"quality_review": {
"state": quality_review.get("state"),
"quality_status": quality_status,
"failure_reasons": quality_failure_reasons,
"blocked_reason": quality_review.get("blocked_reason"),
},
"selection_summary": {
"trend_record_count": trend_record_count,
"consecutive_pass_count": trend_pass_count,
"stable_monitoring_window": stable_window,
"maturity_status": maturity_status,
"remote_drift_status": remote_drift_status,
"quality_status": quality_status,
},
"guards_unchanged": list(
quality_gate.get("guards_unchanged")
or trend.get("guards_unchanged")
or maturity_rollup.get("guards_unchanged")
or remote_drift_check.get("guards_unchanged")
or quality_review.get("guards_unchanged")
or [
"execution_allowed",
"real_live_ready",
"risk_governor",
"trust_bucket",
"operator_confirmation_protocol",
]
),
"no_research_council_phase_created": True,
"notes": [
"automation_execution_layer_p7_not_research_council_p7",
"bounded_selector_only_chooses_one_next_slice",
"okx_fills_zero_not_current_phase_blocker",
],
}
def read_analysis_decision_monitor_history_records(path: Path) -> list[dict]:
if not path.exists():
return []
records = []
for line in path.read_text(encoding="utf-8").splitlines():
if not line.strip():
continue
records.append(json.loads(line))
return records
def build_analysis_decision_monitor_trend(*, records: list[dict]) -> dict:
latest = dict(records[-1]) if records else {}
consecutive_pass_count = 0
for record in reversed(records):
if not bool(record.get("monitor_passed")):
break
if list(record.get("blockers") or []):
break
consecutive_pass_count += 1
blocker_counts: dict[str, int] = {}
warning_counts: dict[str, int] = {}
for record in records:
for blocker in record.get("blockers") or []:
blocker_counts[str(blocker)] = blocker_counts.get(str(blocker), 0) + 1
for warning in record.get("warnings") or []:
warning_counts[str(warning)] = warning_counts.get(str(warning), 0) + 1
stable_monitoring_window = (
len(records) >= ANALYSIS_DECISION_MONITOR_STABLE_SAMPLE_COUNT
and consecutive_pass_count >= ANALYSIS_DECISION_MONITOR_STABLE_SAMPLE_COUNT
)
return {
"generated_ts": datetime.now(timezone.utc).isoformat(),
"record_count": len(records),
"stable_sample_threshold": ANALYSIS_DECISION_MONITOR_STABLE_SAMPLE_COUNT,
"stable_monitoring_window": stable_monitoring_window,
"consecutive_pass_count": consecutive_pass_count,
"latest_monitor_generated_ts": latest.get("monitor_generated_ts"),
"latest_recorded_ts": latest.get("recorded_ts"),
"latest_status": latest.get("status"),
"latest_monitor_passed": bool(latest.get("monitor_passed")) if latest else False,
"latest_blockers": list(latest.get("blockers") or []),
"latest_warnings": list(latest.get("warnings") or []),
"latest_observed": dict(latest.get("observed") or {}),
"blocker_counts": blocker_counts,
"warning_counts": warning_counts,
"next_slice": latest.get("next_slice"),
"guards_unchanged": list(latest.get("guards_unchanged") or []),
}
def build_analysis_decision_monitor_quality_gate(*, trend: dict) -> dict:
observed = dict(trend.get("latest_observed") or {})
failure_reasons = []
if not bool(trend.get("latest_monitor_passed")):
failure_reasons.append("latest_monitor_not_passed")
if not bool(trend.get("stable_monitoring_window")):
failure_reasons.append("stable_monitoring_window_not_reached")
if list(trend.get("latest_blockers") or []):
failure_reasons.append("latest_monitor_has_blockers")
if list(trend.get("latest_warnings") or []):
failure_reasons.append("latest_monitor_has_warnings")
if observed.get("decision_action") != "restricted_decision_support":
failure_reasons.append("decision_action_not_restricted_support")
if observed.get("decision_operator_mode") != "observe_only":
failure_reasons.append("operator_mode_not_observe_only")
if observed.get("real_trade_sample_gate_required") is not False:
failure_reasons.append("real_trade_sample_gate_not_deferred")
if observed.get("real_trade_sample_gate_status") != "deferred_analysis_decision_only":
failure_reasons.append("real_trade_sample_gate_status_not_deferred")
if observed.get("runtime_health") != "ok":
failure_reasons.append("runtime_health_not_ok")
if observed.get("market_gap") != "market_data_gap_clear":
failure_reasons.append("market_gap_not_clear")
if observed.get("p6_governance_tick_status") != "no_op_healthy":
failure_reasons.append("p6_governance_tick_not_noop")
gate_passed = not failure_reasons
return {
"generated_ts": datetime.now(timezone.utc).isoformat(),
"gate": "analysis_decision_monitor_quality_gate",
"status": "passed" if gate_passed else "attention_required",
"passed": gate_passed,
"failure_reasons": failure_reasons,
"record_count": trend.get("record_count"),
"consecutive_pass_count": trend.get("consecutive_pass_count"),
"stable_monitoring_window": bool(trend.get("stable_monitoring_window")),
"latest_monitor_generated_ts": trend.get("latest_monitor_generated_ts"),
"latest_observed": observed,
"next_slice": trend.get("next_slice"),
"guards_unchanged": list(trend.get("guards_unchanged") or []),
"notes": [
"analysis_decision_only_phase",
"execution_gate_unchanged",
"real_live_ready_unchanged",
"real_trade_sample_deferred",
],
}
def run_automation_execution_layer_gate(*, env: dict, output_json: bool) -> dict:
started_ts = datetime.now(timezone.utc).isoformat()
write_runner_status(
{
"state": "running",
"mode": "automation_execution_layer",
"started_ts": started_ts,
"heartbeat_ts": started_ts,
"current_stage": AUTOMATION_EXECUTION_GATE_STAGE,
"blocked_reason": None,
"rounds_completed": 0,
}
)
try:
anchor_state = read_automation_anchor_state()
tick_payload = run_research_council_governance_tick_from_env(env)
except Exception:
write_runner_status(
{
"state": "failed",
"mode": "automation_execution_layer",
"started_ts": started_ts,
"heartbeat_ts": datetime.now(timezone.utc).isoformat(),
"current_stage": AUTOMATION_EXECUTION_GATE_STAGE,
"blocked_reason": "p6_governance_tick_exception",
"rounds_completed": 0,
}
)
raise
tick_status = str(tick_payload.get("status") or "unknown")
changed_files = collect_declared_changed_files(env)
summary = build_automation_execution_summary(
started_ts=started_ts,
tick_payload=tick_payload,
anchor_state=anchor_state,
changed_files=changed_files,
)
write_automation_execution_anchor_files(tick_payload=tick_payload, summary=summary)
write_runner_status(
{
"state": summary["status"],
"mode": "automation_execution_layer",
"started_ts": started_ts,
"heartbeat_ts": datetime.now(timezone.utc).isoformat(),
"current_stage": summary.get("current_stage"),
"blocked_reason": summary.get("blocked_reason"),
"rounds_completed": len(summary.get("rounds") or []),
"tick_status": tick_status,
"effective_health_state": tick_payload.get("effective_health_state"),
"safe_to_auto_continue": tick_payload.get("safe_to_auto_continue"),
"next_slice": summary.get("next_slice"),
"no_new_phase_created": True,
}
)
if output_json:
print(json.dumps(summary, ensure_ascii=False, sort_keys=True, default=str))
else:
print(f"Automation execution-layer gate status: {summary['status']}")
print(f"P6 governance tick: {tick_status}")
print(f"Effective health: {tick_payload.get('effective_health_state')}")
print(f"Next slice: {summary.get('next_slice')}")
if summary.get("blocked_reason"):
print(f"Blocked reason: {summary['blocked_reason']}")
return summary
def run_research_council_governance_tick_from_env(env: dict) -> dict:
return CouncilGovernanceTickService().run_tick(
instrument_id=int(env.get("RESEARCH_COUNCIL_INSTRUMENT_ID", "1")),
limit=int(env.get("RESEARCH_COUNCIL_REVIEW_LIMIT", "5")),
stale_after_hours=float(env.get("RESEARCH_COUNCIL_STALE_AFTER_HOURS", "24")),
auto_run_stale_cycle=_get_bool(env, "RESEARCH_COUNCIL_GOVERNANCE_AUTO_RUN_STALE", False),
force_cycle=_get_bool(env, "RESEARCH_COUNCIL_GOVERNANCE_FORCE_CYCLE", False),
timeframe=env.get("RESEARCH_COUNCIL_TIMEFRAME", "1m"),
model_code=env.get("RESEARCH_COUNCIL_MODEL_CODE", "OSOK_DAILY_RANGE_SWEEP_MSS_FVG_RETRACE"),
replay_file=str(env.get("RESEARCH_COUNCIL_REPLAY_FILE") or "").strip() or None,
eval_limit=int(env.get("RESEARCH_COUNCIL_EVAL_LIMIT", "20")),
lesson_limit=int(env.get("RESEARCH_COUNCIL_LESSON_LIMIT", "100")),
cycle_key=str(env.get("RESEARCH_COUNCIL_OPTIMIZATION_CYCLE_KEY") or "").strip() or None,
)
def build_automation_execution_summary(*, started_ts: str, tick_payload: dict, anchor_state: dict, changed_files: Optional[list[str]] = None) -> dict:
tick_status = str(tick_payload.get("status") or "unknown")
selection = select_automation_execution_slice(anchor_state=anchor_state, tick_payload=tick_payload)
validation_plan = build_validation_plan(selected_slice=selection, changed_files=changed_files or [])
blocked_reason = None
status = "completed"
current_stage = None
next_slice = selection.get("slice") or AUTOMATION_EXECUTION_NEXT_SLICE
if selection.get("status") == "blocked":
status = "blocked"
current_stage = selection.get("slice")
blocked_reason = selection.get("blocked_reason") or "automation_slice_selection_blocked"
elif tick_status == "cycle_required":
status = "blocked"
current_stage = "automation_execution_layer:P6_cycle_required"
blocked_reason = "optimization_cycle_requires_explicit_authorization"
next_slice = "authorized_research_council_optimization_cycle"
elif tick_status == "bounded_slice_required":
status = "stopped"
current_stage = "automation_execution_layer:P6_bounded_slice_required"
next_slice = "smallest_research_council_bounded_slice"
elif tick_status not in {"no_op_healthy", "cycle_run"}:
status = "blocked"
current_stage = AUTOMATION_EXECUTION_GATE_STAGE
blocked_reason = f"unexpected_p6_governance_tick_status:{tick_status}"
next_slice = "inspect_p6_governance_tick_payload"
summary = {
"started_ts": started_ts,
"finished_ts": datetime.now(timezone.utc).isoformat(),
"status": status,
"mode": "automation_execution_layer",
"active_gate": "research_council_p6_governance_tick",
"tick_status": tick_status,
"effective_health_state": tick_payload.get("effective_health_state"),
"safe_to_auto_continue": bool(tick_payload.get("safe_to_auto_continue")),
"requires_cycle_run": bool(tick_payload.get("requires_cycle_run")),
"requires_bounded_slice": bool(tick_payload.get("requires_bounded_slice")),
"recommended_actions": list(tick_payload.get("recommended_actions") or []),
"boundary_notes": list(tick_payload.get("boundary_notes") or []),
"anchor_state": anchor_state,
"selected_slice": selection,
"validation_plan": validation_plan,
"rounds": [
{
"round": 1,
"stage": AUTOMATION_EXECUTION_GATE_STAGE,
"tick_status": tick_status,
"effective_health_state": tick_payload.get("effective_health_state"),
"recommended_actions": list(tick_payload.get("recommended_actions") or []),
}
],
"current_stage": current_stage,
"blocked_reason": blocked_reason,
"next_slice": next_slice,
"no_new_phase_created": True,
"guards_unchanged": [
"execution_allowed",
"real_live_ready",
"risk_governor",
"trust_bucket",
"operator_confirmation_protocol",
],
}
summary["state_writeback_plan"] = build_state_writeback_plan(summary=summary)
return summary
def build_state_writeback_plan(*, summary: dict) -> dict:
anchor_status = derive_automation_execution_anchor_status(summary.get("anchor_state") or {})
return {
"files_to_write": [
".claude/automation_status.json",
"SESSION.md",
"TASKS.md",
],
"state": anchor_status["state"],
"current_phase": anchor_status["current_phase"],
"next_slice": summary.get("next_slice"),
"status": summary.get("status"),
"blocked_reason": summary.get("blocked_reason"),
"no_new_phase_created": bool(summary.get("no_new_phase_created")),
"guards_unchanged": list(summary.get("guards_unchanged") or []),
"docs_update_required": False,
"docs_update_reason": "execution_layer_bootstrap_state_only",
}
def collect_declared_changed_files(env: dict) -> list[str]:
raw_value = (
env.get("RUN_UNTIL_BLOCKED_CHANGED_FILES")
or env.get("RUN_UNTIL_BLOCKED_TOUCHED_FILES")
or ""
)
if not str(raw_value).strip():
return []
files = [
item.strip().replace("\\", "/")
for item in re.split(r"[\n;,]+", str(raw_value))
if item.strip()
]
return sorted(dict.fromkeys(files))
def build_validation_plan(*, selected_slice: dict, changed_files: list[str]) -> dict:
normalized_files = sorted(dict.fromkeys(path.replace("\\", "/") for path in changed_files if path))
focused_commands = []
quality_gates = []
notes = []
selected_code = str(selected_slice.get("slice") or "")
if selected_code.startswith("automation_execution_layer_"):
focused_commands.append(f"{PROJECT_PYTHON} -m unittest tests.unit.test_run_until_blocked")
quality_gates.append(f"{PROJECT_PYTHON} scripts/run_script_static_checks.py")
if touches_any(normalized_files, ("scripts/",)):
focused_commands.append(f"{PROJECT_PYTHON} -m unittest tests.unit.test_run_until_blocked")
quality_gates.append(f"{PROJECT_PYTHON} scripts/run_script_static_checks.py")
if touches_any(normalized_files, ("docs/", "SESSION.md", "TASKS.md")):
focused_commands.append(f"{PROJECT_PYTHON} -m unittest tests.unit.test_documentation_consistency")
if touches_any(normalized_files, ("src/services/research_council/", "src/api/research_council.py")):
focused_commands.append(f"{PROJECT_PYTHON} -m unittest discover -s tests/unit -p \"test_research_council*.py\"")
if touches_any(normalized_files, ("migrations/",)):
quality_gates.append(f"{PROJECT_PYTHON} scripts/run_migration_static_checks.py")
deploy_required = touches_any(normalized_files, DEPLOY_SMOKE_FILE_PREFIXES)
if deploy_required:
notes.append("deploy_smoke_required_by_declared_runtime_api_db_or_workbench_change")
elif normalized_files:
notes.append("deploy_smoke_not_required_for_declared_files")
else:
notes.append("no_declared_changed_files; using selected-slice default validation only")
if selected_slice.get("requires_code") and "npm run lint" not in quality_gates:
quality_gates.append("npm run lint")
return {
"declared_changed_files": normalized_files,
"focused_commands": unique_list(focused_commands),
"quality_gates": unique_list(quality_gates),
"deploy_required": deploy_required,
"deploy_reason": "declared_runtime_api_db_or_workbench_change" if deploy_required else None,
"deploy_smoke_plan": build_deploy_smoke_plan(changed_files=normalized_files, deploy_required=deploy_required),
"notes": notes,
}
def build_deploy_smoke_plan(*, changed_files: list[str], deploy_required: bool) -> dict:
normalized_files = sorted(dict.fromkeys(path.replace("\\", "/") for path in changed_files if path))
if not deploy_required:
return {
"deploy_required": False,
"reason": "local_only_slice_or_no_declared_runtime_api_db_workbench_change",
"deploy_steps": [],
"smoke_targets": [],
}
smoke_targets = ["/health"]
deploy_steps = [
"deploy_current_head_to_remote",
"run_remote_migrations_if_needed",
"wait_for_remote_container_healthy",
]
if touches_any(normalized_files, ("migrations/",)):
smoke_targets.append("remote_migration_state")
if touches_any(normalized_files, ("src/api/research_council.py", "src/services/research_council/")):
smoke_targets.extend(
[
"POST /trading/research-council/governance-tick",
"GET /trading/research-council/optimization-review",
]
)
if touches_any(normalized_files, ("src/api/trading_runtime.py", "src/services/runtime/")):
smoke_targets.extend(["GET /trading/runtime-status", "POST /trading/runtime/run-once"])
if touches_any(normalized_files, ("src/services/readiness/",)):
smoke_targets.extend(["GET /trading/launch-readiness", "GET /trading/live-readiness"])
if touches_any(normalized_files, ("src/services/decision/",)):
smoke_targets.append("GET /trading/decision-packet")
if touches_any(normalized_files, ("src/services/operator/",)):
smoke_targets.extend(["GET /trading/review-packet", "GET /trading/session-view"])
if touches_any(normalized_files, ("src/services/trust/",)):
smoke_targets.append("GET /trading/trust-score")
if touches_any(normalized_files, ("src/web/",)):
smoke_targets.append("browser smoke: trading workbench")
return {
"deploy_required": True,
"reason": "declared_runtime_api_db_or_workbench_change",
"deploy_steps": deploy_steps,
"smoke_targets": unique_list(smoke_targets),
}
def touches_any(paths: list[str], prefixes: tuple[str, ...]) -> bool:
for path in paths:
for prefix in prefixes:
if path == prefix.rstrip("/") or path.startswith(prefix):
return True
return False
def unique_list(items: list[str]) -> list[str]:
return list(dict.fromkeys(items))
def get_automation_required_anchor_paths() -> tuple[Path, ...]:
return (*AUTOMATION_REQUIRED_DOC_PATHS, SESSION_PATH, TASKS_PATH)
def read_automation_anchor_state() -> dict:
anchors = []
missing_paths = []
combined_text_parts = []
for path in get_automation_required_anchor_paths():
path_label = _relative_path_label(path)
if not path.exists():
missing_paths.append(path_label)
anchors.append({"path": path_label, "present": False})
continue
text = path.read_text(encoding="utf-8")
combined_text_parts.append(text)
anchors.append(
{
"path": path_label,
"present": True,
"bytes": path.stat().st_size,
}
)
session_text = SESSION_PATH.read_text(encoding="utf-8") if SESSION_PATH.exists() else ""
tasks_text = TASKS_PATH.read_text(encoding="utf-8") if TASKS_PATH.exists() else ""
return {
"anchors": anchors,
"missing_required_anchors": missing_paths,
"state": extract_anchor_field(session_text, "state") or extract_checked_anchor_field(tasks_text, "state"),
"current_phase": extract_anchor_field(session_text, "current_phase") or extract_checked_anchor_field(tasks_text, "current_phase"),
"repair_next_action": extract_anchor_field(session_text, "repair_next_action"),
"completed_execution_slices": parse_completed_automation_execution_slices(tasks_text),
"in_progress_execution_slices": parse_in_progress_automation_execution_slices(tasks_text),
"has_p0_p6_freeze": "P0-P6" in "\n".join(combined_text_parts)
and "no_op_healthy" in "\n".join(combined_text_parts),
}
def select_automation_execution_slice(*, anchor_state: dict, tick_payload: dict) -> dict:
missing = list(anchor_state.get("missing_required_anchors") or [])
if missing:
return {
"slice": "repair_missing_automation_anchors",
"title": "Repair missing automation anchors",
"status": "blocked",
"blocked_reason": f"missing_required_anchors:{','.join(missing)}",
"requires_code": False,
}
tick_status = str(tick_payload.get("status") or "unknown")
if tick_status == "cycle_required":
return {
"slice": "authorized_research_council_optimization_cycle",
"title": "Authorized Research Council optimization cycle",
"status": "blocked",
"blocked_reason": "optimization_cycle_requires_explicit_authorization",
"requires_code": False,
}
if tick_status == "bounded_slice_required":
return {
"slice": smallest_bounded_slice_name(tick_payload),
"title": "Smallest Research Council bounded slice",
"status": "ready",
"blocked_reason": None,
"requires_code": True,
}
if tick_status not in {"no_op_healthy", "cycle_run"}:
return {
"slice": "inspect_p6_governance_tick_payload",
"title": "Inspect unexpected P6 governance tick payload",
"status": "blocked",
"blocked_reason": f"unexpected_p6_governance_tick_status:{tick_status}",
"requires_code": False,
}
completed = set(anchor_state.get("completed_execution_slices") or [])
in_progress = list(anchor_state.get("in_progress_execution_slices") or [])
for code, title in AUTOMATION_EXECUTION_SLICE_SEQUENCE:
if code in completed:
continue
return {
"slice": code,
"title": title,
"status": "ready",
"blocked_reason": None,
"requires_code": True,
"reason": "first_unfinished_execution_layer_slice",
"already_in_progress": code in in_progress,
}
return {
"slice": "automation_execution_layer_steady_state_tick",
"title": "Automation execution-layer steady-state tick",
"status": "ready",
"blocked_reason": None,
"requires_code": False,
"reason": "all_execution_layer_bootstrap_slices_completed",
}
def smallest_bounded_slice_name(tick_payload: dict) -> str:
actions = {str(action) for action in tick_payload.get("recommended_actions") or []}
if "add_replay_fixture_with_confirmed_swings" in actions:
return "bounded_research_council_confirmed_swing_replay_fixture"
if actions:
first_action = sorted(actions)[0].replace("-", "_").replace(" ", "_").lower()
return f"bounded_research_council_{first_action}"
return "smallest_research_council_bounded_slice"
def extract_anchor_field(text: str, key: str) -> Optional[str]:
match = re.search(rf"^- {re.escape(key)}:\s*(.+)$", text, flags=re.MULTILINE)
return match.group(1).strip() if match else None
def extract_checked_anchor_field(text: str, key: str) -> Optional[str]:
match = re.search(rf"^- \[x\] {re.escape(key)}:\s*(.+)$", text, flags=re.MULTILINE)
return match.group(1).strip() if match else None
def parse_completed_automation_execution_slices(tasks_text: str) -> list[str]:
slices = re.findall(r"- \[x\] `([^`]*automation_execution_layer[^`]*)`", tasks_text)
if "Automation Execution-Layer P0" in tasks_text or AUTOMATION_EXECUTION_GATE_CLOSED_STATE in tasks_text:
slices.insert(0, "automation_execution_layer_p0_governance_gate")
return list(dict.fromkeys(slices))
def parse_in_progress_automation_execution_slices(tasks_text: str) -> list[str]:
return re.findall(r"- \[ \] `([^`]*automation_execution_layer[^`]*)`", tasks_text)
def _relative_path_label(path: Path) -> str:
try:
return str(path.relative_to(ROOT)).replace("\\", "/")
except ValueError:
return str(path)
def write_automation_execution_anchor_files(*, tick_payload: dict, summary: dict) -> None:
SESSION_PATH.write_text(render_automation_execution_session(tick_payload=tick_payload, summary=summary), encoding="utf-8")
TASKS_PATH.write_text(render_automation_execution_tasks(tick_payload=tick_payload, summary=summary), encoding="utf-8")
def render_automation_execution_session(*, tick_payload: dict, summary: dict) -> str:
updated_ts = datetime.now(timezone.utc).strftime("%Y-%m-%d UTC")
anchor_status = derive_automation_execution_anchor_status(summary.get("anchor_state") or {})
goal_text = render_automation_execution_goal_text(summary)
return "\n".join(
[
"# ai-exchange Session",
"",
"## Current Goal",
"",
goal_text,
"",
"## Current Branch",
"",
"`main`",
"",
"## Last Updated",
"",
updated_ts,
"",
"## Automation Status",
"",
f"- state: {anchor_status['state']}",
f"- current_phase: {anchor_status['current_phase']}",
f"- updated_ts: {updated_ts}",
f"- latest_p6_tick_status: {summary.get('tick_status')}",
f"- latest_p6_effective_health_state: {summary.get('effective_health_state')}",
f"- latest_safe_to_auto_continue: {str(summary.get('safe_to_auto_continue')).lower()}",
f"- repair_next_action: continue with {summary.get('next_slice')}",
"",
"## Completed In This Run",
"",
"- Read the required automation anchors.",
"- Ran the P6 Research Council governance tick.",
"- Routed the current `automation_flow_p0_p6_review_closed` state through `scripts/run_until_blocked.py`.",
"- Preserved P0-P6 freeze semantics and did not create P7.",
"",
"## Current Working State",
"",
"- P0-P6 Research Council automation phases remain complete and frozen.",
"- `scripts/run_until_blocked.py` now treats P6 governance tick as the default execution-layer health gate when the anchors are in automation execution mode.",
"- A `no_op_healthy` tick records health and stops Research Council phase expansion.",
"- A `cycle_required` tick requires explicit optimization-cycle authorization before running the cycle.",
"- A `bounded_slice_required` tick opens only the smallest bounded implementation slice, then returns to P6.",
"- This slice did not change `execution_allowed`, `real_live_ready`, risk governor, trust bucket, or operator confirmation protocol.",
"",
"## Current Risks Or Open Questions",
"",
"- `gstack` / `GSTACK_OK` is not available as a local command or root sentinel in this checkout; this slice used repository-native focused validation.",
"- `decision_support_ready` remains incomplete.",
"- `real_trade_sample_verified` is deferred until a future real-trading validation phase.",
"- `real_live_ready` remains intentionally `false` because the OKX order adapter is still placeholder-only.",
"- Large backlog drain remains sensitive to remote PostgreSQL connection timeouts.",
"",
"## Exact Next Step",
"",
"Proceed directly to:",
"",
f"### {summary.get('next_slice')}",
"",
"Build the next automation execution-layer slice around anchor reading, smallest-slice selection, focused validation, quality-gate selection, optional deploy/smoke routing, and state writeback.",
"",
"## Validation Commands Used Recently",
"",
"```bash",
r"D:\ai-exchange\.venv\Scripts\python.exe -m unittest tests.unit.test_run_until_blocked tests.unit.test_research_council_governance_tick_service tests.unit.test_runtime_script_json tests.unit.test_documentation_consistency",
r"D:\ai-exchange\.venv\Scripts\python.exe scripts\run_script_static_checks.py",
"npm run lint",
"```",
"",
]
)
def render_automation_execution_goal_text(summary: dict) -> str:
if summary.get("next_slice") == "automation_execution_layer_steady_state_tick":
return "Automation execution-layer bootstrap is complete after the P0-P6 Research Council phase freeze. The default loop is now a steady-state P6 governance tick: healthy no-op ticks stop without code changes, while cycle or bounded-slice statuses route to the smallest allowed action."
return "Automation execution-layer development has started after the P0-P6 Research Council phase freeze. The default Research Council entrypoint is P6 governance tick, and a healthy no-op tick must stop Research Council expansion instead of creating P7."
def render_automation_execution_tasks(*, tick_payload: dict, summary: dict) -> str:
next_slice = summary.get("next_slice")
anchor_status = derive_automation_execution_anchor_status(summary.get("anchor_state") or {})
completed_slice_lines = render_completed_execution_slice_lines(summary.get("anchor_state") or {})
blocked_line = (
f"- [ ] {summary.get('blocked_reason')}"
if summary.get("blocked_reason")
else "- [ ] `decision_support_ready`"
)
return "\n".join(
[
"# ai-exchange Tasks",
"",
"## Execution Mode",
"",
"- [x] Autonomous mode enabled",
"- [x] No per-stage confirmation required",
"- [x] Each stage must close the loop through implementation, validation, deploy/smoke when possible, and state writeback",
"- [x] P0-P6 Research Council phase expansion is frozen",
"- [x] P6 `no_op_healthy` is a stop condition and must not create P7",
"",
"## In Progress",
"",
f"- [ ] `{next_slice}`",
"",
"## Next Up",
"",
"- [ ] Implement anchor-driven smallest-slice selection",
"- [ ] Add validation and quality-gate routing for the selected slice",
"- [ ] Add deploy/smoke routing only when runtime/API/workbench changes require it",
"- [ ] Keep execution, live readiness, risk, trust, and operator confirmation gates unchanged",
"",
"## Automation Status",
"",
f"- [x] state: {anchor_status['state']}",
f"- [x] current_phase: {anchor_status['current_phase']}",
f"- [x] latest_p6_tick_status: {summary.get('tick_status')}",
f"- [x] latest_p6_effective_health_state: {summary.get('effective_health_state')}",
f"- [x] no_new_phase_created: {str(summary.get('no_new_phase_created')).lower()}",
"",
"## Newly Done",
"",
"- [x] Read required automation anchors",
"- [x] Ran and classified P6 governance tick",
"- [x] Wired P6 governance tick into `scripts/run_until_blocked.py` for automation execution-layer mode",
"- [x] Added focused coverage for no-op healthy execution-layer routing",
"- [x] Preserved legacy trading-stage run-until-blocked behavior",
"",
"## Blocked",
"",
blocked_line,
"- [ ] Large backlog drain remains sensitive to remote PostgreSQL connection timeouts",
"",
"## Deferred Until Real-Trading Phase",
"",
"- [ ] `real_trade_sample_verified` is not required in the current analysis/decision-only phase",
"- [ ] Real fill reconciliation should be verified only when the project intentionally enters a real-trading validation phase",
"",
"## Major Done Milestones",
"",
"- [x] `P0-1` Real-time market truth layer",
"- [x] `P0-2` Real Account And Order Read Sync",
"- [x] `P0-3` Runtime Scheduling And Supervision",
"- [x] `P0-4` Decision Packet And Proactive Alerting",
"- [x] `P0-5` Real Trade Mapping And Post-Trade Loop",
"- [x] `P1-1` Trust Scoring And Model Whitelists",
"- [x] `P1-2` Operator Experience And Workflow Tools",
"- [x] `P1-3` Live Adapter Readiness Prep",
"- [x] `Research Council P0` local gate",
"- [x] `Research Council P1` local gate",
"- [x] `Research Council P2` remote deploy and smoke gate",
"- [x] `Research Council P3` remote deploy and smoke gate",
"- [x] `Research Council P4` remote deploy and smoke gate",
"- [x] `Research Council P5` remote deploy, remote cycle, and review smoke gate",
"- [x] `Research Council P6` remote deploy and governance tick smoke gate",
"- [x] `P0-P6` reasonableness review and phase freeze gate",
*completed_slice_lines,
"",
"## Backlog",
"",
"- [ ] Long-term target: complete `Real-time Assisted Trading V1` without enabling default live auto-trading",
"",
]
)
def write_analysis_decision_monitor_anchor_files(*, summary: dict) -> None:
SESSION_PATH.write_text(render_analysis_decision_monitor_session(summary=summary), encoding="utf-8")
TASKS_PATH.write_text(render_analysis_decision_monitor_tasks(summary=summary), encoding="utf-8")
def write_analysis_decision_monitor_maturity_rollup_anchor_files(*, summary: dict) -> None:
SESSION_PATH.write_text(render_analysis_decision_monitor_maturity_rollup_session(summary=summary), encoding="utf-8")
TASKS_PATH.write_text(render_analysis_decision_monitor_maturity_rollup_tasks(summary=summary), encoding="utf-8")
def write_analysis_decision_monitor_remote_drift_anchor_files(*, summary: dict) -> None:
SESSION_PATH.write_text(render_analysis_decision_monitor_remote_drift_session(summary=summary), encoding="utf-8")
TASKS_PATH.write_text(render_analysis_decision_monitor_remote_drift_tasks(summary=summary), encoding="utf-8")
def write_analysis_decision_support_quality_review_anchor_files(*, summary: dict) -> None:
SESSION_PATH.write_text(render_analysis_decision_support_quality_review_session(summary=summary), encoding="utf-8")
TASKS_PATH.write_text(render_analysis_decision_support_quality_review_tasks(summary=summary), encoding="utf-8")
def write_analysis_decision_monitor_p7_selector_anchor_files(*, summary: dict) -> None:
SESSION_PATH.write_text(render_analysis_decision_monitor_p7_selector_session(summary=summary), encoding="utf-8")
TASKS_PATH.write_text(render_analysis_decision_monitor_p7_selector_tasks(summary=summary), encoding="utf-8")
def render_analysis_decision_monitor_p7_selector_session(*, summary: dict) -> str:
updated_ts = datetime.now(timezone.utc).strftime("%Y-%m-%d UTC")
selector = dict(summary.get("selector") or {})
next_slice = str(selector.get("next_slice") or summary.get("next_slice") or "")
return "\n".join(
[
"# ai-exchange Session",
"",
"## Current Goal",
"",
"Automation execution-layer P7 bounded slice selector is active. The system is still analysis/decision-only, and this stage only chooses the single next bounded slice after the matured monitor review.",
"",
"## Current Branch",
"",
"`main`",
"",
"## Last Updated",
"",
updated_ts,
"",
"## Automation Status",
"",
f"- state: {selector.get('state')}",
"- current_phase: Restricted Decision-Support Soft Rollout",
f"- updated_ts: {updated_ts}",
"- latest_p6_tick_status: no_op_healthy",
f"- latest_p7_selector_choice: {selector.get('selected_next_slice')}",
f"- latest_p7_selector_reason: {selector.get('selection_reason')}",
f"- latest_p7_selector_path: {summary.get('selector_path')}",
f"- repair_next_action: {next_slice}",
"",
"## Completed In This Run",
"",
"- Read the required automation anchors.",
"- Loaded the monitor maturity, drift, and quality review artifacts.",
"- Selected exactly one bounded next slice for the analysis/decision automation loop.",
"- Generated `.claude/analysis_decision_monitor_p7_slice_selector.json`.",
"- Preserved P0-P6 Research Council freeze semantics and did not create Research Council P7.",
"- Did not change `execution_allowed`, `real_live_ready`, risk governor, trust bucket, or operator confirmation protocol.",
"",
"## Current Working State",
"",
f"- P7-4 selector choice: {selector.get('selected_next_slice')}.",
f"- Selector reason: {selector.get('selection_reason')}.",
f"- Selector priority: {selector.get('selection_priority')}.",
f"- Monitor stable window: {selector.get('selection_summary', {}).get('stable_monitoring_window')}.",
f"- Monitor maturity status: {selector.get('maturity_rollup', {}).get('maturity_status')}.",
f"- Remote drift status: {selector.get('remote_drift_check', {}).get('drift_status')}.",
f"- Output quality status: {selector.get('quality_review', {}).get('quality_status')}.",
"",
"## P7 Blockers",
"",
"- none",
"",
"## Current Risks Or Open Questions",
"",
"- `gstack` / `GSTACK_OK` is not available as a local command or root sentinel in this checkout; this slice used repository-native focused validation.",
"- `decision_support_ready` remains a strict unrestricted lane and must not be force-enabled.",
"- The recovered OKX path depends on the local v2rayN/xray process staying up on `192.168.3.73:10808`.",
"- `real_trade_sample_verified` is deferred and not required while the system is analysis/decision-only.",
"- `real_live_ready` remains intentionally `false` because the OKX order adapter is still placeholder-only.",
"",
"## Exact Next Step",
"",
"Proceed directly to:",
"",
f"### {next_slice}",
"",
"Run the bounded slice chosen by the selector, then continue the mature periodic monitoring loop.",
"",
"## Validation Commands Used Recently",
"",
"```bash",
r"D:\ai-exchange\.venv\Scripts\python.exe -m unittest tests.unit.test_run_until_blocked tests.unit.test_analysis_decision_monitor tests.unit.test_runtime_script_json",
r"D:\ai-exchange\.venv\Scripts\python.exe scripts\run_script_static_checks.py",
r"D:\ai-exchange\.venv\Scripts\python.exe scripts\run_until_blocked.py",
"```",
"",
]
)
def render_analysis_decision_monitor_p7_selector_tasks(*, summary: dict) -> str:
selector = dict(summary.get("selector") or {})
next_slice = str(selector.get("next_slice") or summary.get("next_slice") or "")
return "\n".join(
[
"# ai-exchange Tasks",
"",
"## Execution Mode",
"",
"- [x] Autonomous mode enabled",
"- [x] No per-stage confirmation required",
"- [x] Each stage must close the loop through implementation, validation, deploy/smoke when possible, and state writeback",
"- [x] P0-P6 Research Council phase expansion is frozen",
"- [x] P6 `no_op_healthy` must not create Research Council P7",
"- [x] Stable monitor quality may enter automation execution-layer P7",
"",
"## In Progress",
"",
f"- [ ] `{next_slice}`",
"",
"## Next Up",
"",
"- [ ] Continue bounded analysis-decision automation",
"- [ ] Keep local v2rayN/xray running on `192.168.3.73:10808`",
"- [ ] Treat OKX fills remaining `0` as acceptable in the current analysis/decision-only phase",
"- [ ] Keep execution, live readiness, risk, trust, and operator confirmation gates unchanged",
"",
"## Automation Status",
"",
f"- [x] state: {selector.get('state')}",
"- [x] current_phase: Restricted Decision-Support Soft Rollout",
"- [x] latest_p6_tick_status: no_op_healthy",
"- [x] no_new_research_council_phase_created: true",
f"- [x] latest_p7_selector_choice: {selector.get('selected_next_slice')}",
f"- [x] latest_p7_selector_reason: {selector.get('selection_reason')}",
f"- [x] latest_p7_selector_path: {summary.get('selector_path')}",
"",
"## Newly Done",
"",
"- [x] Completed P7-4 bounded automation slice selector",
"- [x] Chose a single next bounded slice from monitor maturity inputs",
"- [x] Wrote bounded selector artifact",
"- [x] Kept OKX fills out of the current-phase blocker set",
"- [x] Preserved execution, live readiness, risk, trust, and operator confirmation gates",
"",
"## Blocked",
"",
"- [x] none",
"",
"## Standing Guardrails",
"",
"- [x] `decision_support_ready` remains strict unrestricted lane and must not be force-enabled",
"- [x] `real_live_path_ready` remains intentionally disabled until the live adapter is real-live ready",
"- [x] Continue periodic analysis-decision monitoring with maturity rollup",
"",
"## Deferred Until Real-Trading Phase",
"",
"- [ ] `real_trade_sample_verified` is not required in the current analysis/decision-only phase",
"- [ ] Real fill reconciliation should be verified only when the project intentionally enters a real-trading validation phase",
"",
"## Operational Dependencies",
"",
"- [ ] Keep `D:\\v2rayN-windows-64\\v2rayN.exe` / `xray.exe` running on the local Windows host",
"- [ ] Keep remote `.env` proxy path compatible with `MARKET_TRUTH_PROXY_URL=http://192.168.3.73:10808`",
"",
"## Major Done Milestones",
"",
"- [x] `Research Council P6` remote deploy and governance tick smoke gate",
"- [x] `P0-P6` reasonableness review and phase freeze gate",
"- [x] `analysis_decision_monitor_runner` Current-phase single-command monitor",
"- [x] `automation_execution_layer_p7_monitor_maturity_rollup` P7-1 maturity rollup",
"- [x] `automation_execution_layer_p7_remote_drift_check` P7-2 remote drift check",
"- [x] `automation_execution_layer_p7_alert_decision_support_quality_review` P7-3 quality review",
"- [x] `automation_execution_layer_p7_bounded_automation_slice_selector` P7-4 bounded automation slice selector",
"",
"## Backlog",
"",
"- [ ] Long-term target: complete `Real-time Assisted Trading V1` without enabling default live auto-trading",
"",
]
)
def render_analysis_decision_support_quality_review_session(*, summary: dict) -> str:
updated_ts = datetime.now(timezone.utc).strftime("%Y-%m-%d UTC")
review = dict(summary.get("quality_review") or {})
observed = dict(review.get("latest_observed") or {})
next_slice = str(review.get("next_slice") or summary.get("next_slice") or "")
failure_reasons = list(review.get("failure_reasons") or [])
blocker_line = f"- {review.get('blocked_reason')}" if review.get("blocked_reason") else "- none"
failure_lines = [f"- {reason}" for reason in failure_reasons] or ["- none"]
return "\n".join(
[
"# ai-exchange Session",
"",
"## Current Goal",
"",
"Automation execution-layer P7 alert and decision-support quality review is active after local and remote monitor maturity converged. The system remains analysis/decision-only and real fill verification is deferred because this phase does not do live trading.",
"",
"## Current Branch",
"",
"`main`",
"",
"## Last Updated",
"",
updated_ts,
"",
"## Automation Status",
"",
f"- state: {review.get('state')}",
"- current_phase: Restricted Decision-Support Soft Rollout",
f"- updated_ts: {updated_ts}",
"- latest_p6_tick_status: no_op_healthy",
f"- latest_p7_quality_status: {review.get('quality_status')}",
f"- latest_p7_quality_review_path: {summary.get('quality_review_path')}",
f"- latest_decision_action: {review.get('decision_action')}",
f"- latest_operator_mode: {review.get('operator_mode')}",
f"- latest_trust_classification: {review.get('trust_classification')}",
f"- repair_next_action: {next_slice}",
"",
"## Completed In This Run",
"",
"- Read the required automation anchors.",
"- Reviewed restricted decision-support output quality from the current monitor payload.",
"- Verified the output is observe-only and does not enable execution.",
"- Generated `.claude/analysis_decision_support_quality_review.json`.",
"- Preserved P0-P6 Research Council freeze semantics and did not create Research Council P7.",
"- Did not wait on OKX fills because real fill verification is deferred to a future real-trading validation phase.",
"- Did not change `execution_allowed`, `real_live_ready`, risk governor, trust bucket, or operator confirmation protocol.",
"",
"## Current Working State",
"",
f"- P7-3 quality status: {review.get('quality_status')}.",
f"- Decision action: {review.get('decision_action')}.",
f"- Operator mode: {review.get('operator_mode')}.",
f"- Trust classification: {review.get('trust_classification')}.",
f"- Broker fills observed: {observed.get('broker_fills')}.",
f"- Real trade sample gate: {observed.get('real_trade_sample_gate_status')}.",
"",
"## P7 Blockers",
"",
blocker_line,
"",
"## P7 Quality Failure Reasons",
"",
*failure_lines,
"",
"## Current Risks Or Open Questions",
"",
"- `gstack` / `GSTACK_OK` is not available as a local command or root sentinel in this checkout; this slice used repository-native focused validation.",
"- `decision_support_ready` remains a strict unrestricted lane and must not be force-enabled.",
"- The recovered OKX path depends on the local v2rayN/xray process staying up on `192.168.3.73:10808`.",
"- `real_trade_sample_verified` is deferred and not required while the system is analysis/decision-only.",
"- `real_live_ready` remains intentionally `false` because the OKX order adapter is still placeholder-only.",
"",
"## Exact Next Step",
"",
"Proceed directly to:",
"",
f"### {next_slice}",
"",
"Run the bounded P7-4 selector next. It will choose the single bounded slice that follows this quality review.",
"",
"## Validation Commands Used Recently",
"",
"```bash",
r"D:\ai-exchange\.venv\Scripts\python.exe -m unittest tests.unit.test_run_until_blocked tests.unit.test_analysis_decision_monitor tests.unit.test_runtime_script_json",
r"D:\ai-exchange\.venv\Scripts\python.exe scripts\run_script_static_checks.py",
r"D:\ai-exchange\.venv\Scripts\python.exe scripts\run_until_blocked.py",
"```",
"",
]
)
def render_analysis_decision_support_quality_review_tasks(*, summary: dict) -> str:
review = dict(summary.get("quality_review") or {})
next_slice = str(review.get("next_slice") or summary.get("next_slice") or "")
failure_reasons = list(review.get("failure_reasons") or [])
blocker_lines = (
[f"- [ ] {review.get('blocked_reason')}"]
if review.get("blocked_reason")
else ["- [x] none"]
)
standing_guardrail_lines = [
"- [x] `decision_support_ready` remains strict unrestricted lane and must not be force-enabled",
"- [x] `real_live_path_ready` remains intentionally disabled until the live adapter is real-live ready",
"- [x] Continue periodic analysis-decision monitoring with maturity rollup",
]
failure_lines = [f"- [ ] {reason}" for reason in failure_reasons] or ["- [x] none"]
return "\n".join(
[
"# ai-exchange Tasks",
"",
"## Execution Mode",
"",
"- [x] Autonomous mode enabled",
"- [x] No per-stage confirmation required",
"- [x] Each stage must close the loop through implementation, validation, deploy/smoke when possible, and state writeback",
"- [x] P0-P6 Research Council phase expansion is frozen",
"- [x] P6 `no_op_healthy` must not create Research Council P7",
"- [x] Stable monitor quality may enter automation execution-layer P7",
"",
"## In Progress",
"",
f"- [ ] `{next_slice}`",
"",
"## Next Up",
"",
"- [ ] Run P7-4 bounded automation slice selector",
"- [ ] Keep local v2rayN/xray running on `192.168.3.73:10808`",
"- [ ] Treat OKX fills remaining `0` as acceptable in the current analysis/decision-only phase",
"- [ ] Keep execution, live readiness, risk, trust, and operator confirmation gates unchanged",
"",
"## Automation Status",
"",
f"- [x] state: {review.get('state')}",
"- [x] current_phase: Restricted Decision-Support Soft Rollout",
"- [x] latest_p6_tick_status: no_op_healthy",
"- [x] no_new_research_council_phase_created: true",
f"- [x] latest_p7_quality_status: {review.get('quality_status')}",
f"- [x] latest_p7_quality_review_path: {summary.get('quality_review_path')}",
f"- [x] latest_decision_action: {review.get('decision_action')}",
f"- [x] latest_operator_mode: {review.get('operator_mode')}",
"",
"## Newly Done",
"",
"- [x] Completed P7-1 monitor maturity rollup",
"- [x] Completed P7-2 remote drift check",
"- [x] Reviewed restricted decision-support output quality",
"- [x] Generated analysis decision support quality review",
"- [x] Closed P7-3 without adding a new Research Council phase",
"- [x] Kept OKX fills out of the current-phase blocker set",
"- [x] Preserved execution, live readiness, risk, trust, and operator confirmation gates",
"",
"## Blocked",
"",
*blocker_lines,
"",
"## Standing Guardrails",
"",
*standing_guardrail_lines,
"",
"## Quality Failure Reasons",
"",
*failure_lines,
"",
"## Deferred Until Real-Trading Phase",
"",
"- [ ] `real_trade_sample_verified` is not required in the current analysis/decision-only phase",
"- [ ] Real fill reconciliation should be verified only when the project intentionally enters a real-trading validation phase",
"",
"## Operational Dependencies",
"",
"- [ ] Keep `D:\\v2rayN-windows-64\\v2rayN.exe` / `xray.exe` running on the local Windows host",
"- [ ] Keep remote `.env` proxy path compatible with `MARKET_TRUTH_PROXY_URL=http://192.168.3.73:10808`",
"",
"## Major Done Milestones",
"",
"- [x] `Research Council P6` remote deploy and governance tick smoke gate",
"- [x] `P0-P6` reasonableness review and phase freeze gate",
"- [x] `analysis_decision_monitor_runner` Current-phase single-command monitor",
"- [x] `automation_execution_layer_p7_monitor_maturity_rollup` P7-1 maturity rollup",
"- [x] `automation_execution_layer_p7_remote_drift_check` P7-2 remote drift check",
"- [x] `automation_execution_layer_p7_alert_decision_support_quality_review` P7-3 quality review",
"",
"## Backlog",
"",
"- [ ] Long-term target: complete `Real-time Assisted Trading V1` without enabling default live auto-trading",
"",
]
)
def render_analysis_decision_monitor_remote_drift_session(*, summary: dict) -> str:
updated_ts = datetime.now(timezone.utc).strftime("%Y-%m-%d UTC")
drift = dict(summary.get("drift_check") or {})
local = dict(drift.get("local") or {})
remote = dict(drift.get("remote") or {})
next_slice = str(drift.get("next_slice") or summary.get("next_slice") or "")
blocker_line = f"- {drift.get('blocked_reason')}" if drift.get("blocked_reason") else "- none"
return "\n".join(
[
"# ai-exchange Session",
"",
"## Current Goal",
"",
"Automation execution-layer P7 remote drift check is active after the monitor maturity rollup. The system remains analysis/decision-only and real fill verification is deferred because this phase does not do live trading.",
"",
"## Current Branch",
"",
"`main`",
"",
"## Last Updated",
"",
updated_ts,
"",
"## Automation Status",
"",
f"- state: {drift.get('state')}",
"- current_phase: Restricted Decision-Support Soft Rollout",
f"- updated_ts: {updated_ts}",
"- latest_p6_tick_status: no_op_healthy",
f"- latest_p7_remote_drift_status: {drift.get('drift_status')}",
f"- latest_p7_remote_drift_check_path: {summary.get('drift_check_path')}",
f"- latest_local_quality_gate_status: {local.get('quality_gate_status')}",
f"- latest_local_consecutive_passes: {local.get('consecutive_pass_count')}",
f"- latest_remote_container_quality_gate_status: {remote.get('container_quality_gate_status')}",
f"- latest_remote_container_consecutive_passes: {remote.get('container_consecutive_pass_count')}",
f"- repair_next_action: {next_slice}",
"",
"## Completed In This Run",
"",
"- Read the required automation anchors.",
"- Compared local monitor maturity rollup with remote container and host persisted quality gates.",
"- Generated `.claude/analysis_decision_monitor_remote_drift_check.json`.",
"- Preserved P0-P6 Research Council freeze semantics and did not create Research Council P7.",
"- Did not wait on OKX fills because real fill verification is deferred to a future real-trading validation phase.",
"- Did not change `execution_allowed`, `real_live_ready`, risk governor, trust bucket, or operator confirmation protocol.",
"",
"## Current Working State",
"",
f"- P7-2 remote drift status: {drift.get('drift_status')}.",
f"- Local maturity status: {local.get('maturity_status')}.",
f"- Local monitor records: {local.get('record_count')}.",
f"- Local monitor consecutive passes: {local.get('consecutive_pass_count')}.",
f"- Remote host: {remote.get('host')}.",
f"- Remote container quality gate: {remote.get('container_quality_gate_status')}.",
f"- Remote container records: {remote.get('container_record_count')}.",
f"- Remote container consecutive passes: {remote.get('container_consecutive_pass_count')}.",
f"- Remote host persisted quality gate: {remote.get('host_quality_gate_status')}.",
"",
"## P7 Blockers",
"",
blocker_line,
"",
"## Current Risks Or Open Questions",
"",
"- `gstack` / `GSTACK_OK` is not available as a local command or root sentinel in this checkout; this slice used repository-native focused validation.",
"- `decision_support_ready` remains a strict unrestricted lane and must not be force-enabled.",
"- The recovered OKX path depends on the local v2rayN/xray process staying up on `192.168.3.73:10808`.",
"- `real_trade_sample_verified` is deferred and not required while the system is analysis/decision-only.",
"- `real_live_ready` remains intentionally `false` because the OKX order adapter is still placeholder-only.",
"- P7-3 must review alert and decision-support output quality before returning to long-running monitoring.",
"",
"## Exact Next Step",
"",
"Proceed directly to:",
"",
f"### {next_slice}",
"",
"Review restricted decision-support and alert output quality. If quality is insufficient, open `improve_analysis_decision_support_quality`; if healthy, return to long-running monitoring with maturity rollup.",
"",
"## Validation Commands Used Recently",
"",
"```bash",
r"D:\ai-exchange\.venv\Scripts\python.exe -m unittest tests.unit.test_run_until_blocked tests.unit.test_analysis_decision_monitor tests.unit.test_runtime_script_json",
r"D:\ai-exchange\.venv\Scripts\python.exe scripts\run_script_static_checks.py",
r"D:\ai-exchange\.venv\Scripts\python.exe scripts\run_until_blocked.py",
"```",
"",
]
)
def render_analysis_decision_monitor_remote_drift_tasks(*, summary: dict) -> str:
drift = dict(summary.get("drift_check") or {})
local = dict(drift.get("local") or {})
remote = dict(drift.get("remote") or {})
next_slice = str(drift.get("next_slice") or summary.get("next_slice") or "")
blocker_lines = (
[f"- [ ] {drift.get('blocked_reason')}"]
if drift.get("blocked_reason")
else [
"- [ ] `decision_support_ready` remains strict unrestricted lane and must not be force-enabled",
"- [ ] `real_live_path_ready` remains intentionally disabled until the live adapter is real-live ready",
"- [ ] P7-3 alert and decision-support quality review is still pending",
]
)
return "\n".join(
[
"# ai-exchange Tasks",
"",
"## Execution Mode",
"",
"- [x] Autonomous mode enabled",
"- [x] No per-stage confirmation required",
"- [x] Each stage must close the loop through implementation, validation, deploy/smoke when possible, and state writeback",
"- [x] P0-P6 Research Council phase expansion is frozen",
"- [x] P6 `no_op_healthy` must not create Research Council P7",
"- [x] Stable monitor quality may enter automation execution-layer P7",
"",
"## In Progress",
"",
f"- [ ] `{next_slice}`",
"",
"## Next Up",
"",
"- [ ] Run P7-3 alert and decision-support quality review",
"- [ ] Keep local v2rayN/xray running on `192.168.3.73:10808`",
"- [ ] Monitor restricted decision-support output and alert quality",
"- [ ] Treat OKX fills remaining `0` as acceptable in the current analysis/decision-only phase",
"- [ ] Keep execution, live readiness, risk, trust, and operator confirmation gates unchanged",
"",
"## Automation Status",
"",
f"- [x] state: {drift.get('state')}",
"- [x] current_phase: Restricted Decision-Support Soft Rollout",
"- [x] latest_p6_tick_status: no_op_healthy",
"- [x] no_new_research_council_phase_created: true",
f"- [x] latest_p7_remote_drift_status: {drift.get('drift_status')}",
f"- [x] latest_p7_remote_drift_check_path: {summary.get('drift_check_path')}",
f"- [x] latest_local_quality_gate_status: {local.get('quality_gate_status')}",
f"- [x] latest_remote_container_quality_gate_status: {remote.get('container_quality_gate_status')}",
"",
"## Newly Done",
"",
"- [x] Completed P7-1 monitor maturity rollup",
"- [x] Compared local and remote monitor quality gate state",
"- [x] Generated analysis decision monitor remote drift check",
"- [x] Closed P7-2 without adding a new Research Council phase",
"- [x] Kept OKX fills out of the current-phase blocker set",
"- [x] Preserved execution, live readiness, risk, trust, and operator confirmation gates",
"",
"## Blocked",
"",
*blocker_lines,
"",
"## Deferred Until Real-Trading Phase",
"",
"- [ ] `real_trade_sample_verified` is not required in the current analysis/decision-only phase",
"- [ ] Real fill reconciliation should be verified only when the project intentionally enters a real-trading validation phase",
"",
"## Operational Dependencies",
"",
"- [ ] Keep `D:\\v2rayN-windows-64\\v2rayN.exe` / `xray.exe` running on the local Windows host",
"- [ ] Keep remote `.env` proxy path compatible with `MARKET_TRUTH_PROXY_URL=http://192.168.3.73:10808`",
"",
"## Major Done Milestones",
"",
"- [x] `Research Council P6` remote deploy and governance tick smoke gate",
"- [x] `P0-P6` reasonableness review and phase freeze gate",
"- [x] `analysis_decision_monitor_runner` Current-phase single-command monitor",
"- [x] `automation_execution_layer_p7_monitor_maturity_rollup` P7-1 maturity rollup",
"- [x] `automation_execution_layer_p7_remote_drift_check` P7-2 remote drift check",
"",
"## Backlog",
"",
"- [ ] Long-term target: complete `Real-time Assisted Trading V1` without enabling default live auto-trading",
"",
]
)
def render_analysis_decision_monitor_maturity_rollup_session(*, summary: dict) -> str:
updated_ts = datetime.now(timezone.utc).strftime("%Y-%m-%d UTC")
rollup = dict(summary.get("rollup") or {})
local = dict(rollup.get("local") or {})
observed = dict(rollup.get("latest_observed") or {})
next_slice = str(rollup.get("next_slice") or summary.get("next_slice") or "")
blocker_line = f"- {rollup.get('blocked_reason')}" if rollup.get("blocked_reason") else "- none"
return "\n".join(
[
"# ai-exchange Session",
"",
"## Current Goal",
"",
"Automation execution-layer P7 monitor maturity rollup is active after the P0-P6 Research Council phase freeze. The system remains analysis/decision-only and real fill verification is deferred because this phase does not do live trading.",
"",
"## Current Branch",
"",
"`main`",
"",
"## Last Updated",
"",
updated_ts,
"",
"## Automation Status",
"",
f"- state: {rollup.get('state')}",
"- current_phase: Restricted Decision-Support Soft Rollout",
f"- updated_ts: {updated_ts}",
"- latest_p6_tick_status: no_op_healthy",
"- latest_safe_to_auto_continue: true",
f"- latest_p7_maturity_status: {rollup.get('maturity_status')}",
f"- latest_p7_rollup_path: {summary.get('rollup_path')}",
f"- latest_analysis_decision_monitor_history_records: {local.get('record_count')}",
f"- latest_analysis_decision_monitor_consecutive_passes: {local.get('consecutive_pass_count')}",
f"- latest_analysis_decision_monitor_stable_window: {str(local.get('stable_monitoring_window')).lower()}",
f"- latest_analysis_decision_monitor_quality_gate_status: {local.get('quality_gate_status')}",
f"- latest_real_trade_sample_gate_status: {observed.get('real_trade_sample_gate_status')}",
f"- repair_next_action: {next_slice}",
"",
"## Completed In This Run",
"",
"- Read the required automation anchors.",
"- Consumed analysis decision monitor history, trend, and quality gate without adding a new monitor sample.",
"- Generated `.claude/analysis_decision_monitor_maturity_rollup.json`.",
"- Closed P7-1 monitor maturity rollup when the stable quality window was mature.",
"- Preserved P0-P6 Research Council freeze semantics and did not create Research Council P7.",
"- Did not wait on OKX fills because real fill verification is deferred to a future real-trading validation phase.",
"- Did not change `execution_allowed`, `real_live_ready`, risk governor, trust bucket, or operator confirmation protocol.",
"",
"## Current Working State",
"",
"- P7-1 monitor maturity rollup is closed.",
"- Local stable window conclusion: " + str(rollup.get("stable_window_conclusion")),
f"- Local monitor records: {local.get('record_count')}.",
f"- Local monitor consecutive passes: {local.get('consecutive_pass_count')} / {local.get('stable_sample_threshold')}.",
f"- Local quality gate: {local.get('quality_gate_status')}.",
f"- Current allowed lane: {rollup.get('current_allowed_lane')}.",
"- Remote drift check status: pending.",
f"- Current broker status: {observed.get('broker_status')}.",
f"- Current broker fills: {observed.get('broker_fills')}.",
f"- Current runtime health: {observed.get('runtime_health')}.",
f"- Current market gap: {observed.get('market_gap')}.",
f"- Current decision action: {observed.get('decision_action')}.",
f"- Current operator mode: {observed.get('decision_operator_mode')}.",
f"- Current trust classification: {observed.get('decision_trust_classification')}.",
"",
"## P7 Blockers",
"",
blocker_line,
"",
"## Current Risks Or Open Questions",
"",
"- `gstack` / `GSTACK_OK` is not available as a local command or root sentinel in this checkout; this slice used repository-native focused validation.",
"- `decision_support_ready` remains a strict unrestricted lane and must not be force-enabled.",
"- The recovered OKX path depends on the local v2rayN/xray process staying up on `192.168.3.73:10808`.",
"- `real_trade_sample_verified` is deferred and not required while the system is analysis/decision-only.",
"- `real_live_ready` remains intentionally `false` because the OKX order adapter is still placeholder-only.",
"- P7-2 must compare local and remote monitor maturity before returning to long-running monitoring.",
"",
"## Exact Next Step",
"",
"Proceed directly to:",
"",
f"### {next_slice}",
"",
"Compare local and remote monitor maturity state. If both sides are passed and stable, continue to P7-3 alert and decision-support quality review; if remote state drifts, route to `repair_analysis_decision_monitor_remote_drift`.",
"",
"## Validation Commands Used Recently",
"",
"```bash",
r"D:\ai-exchange\.venv\Scripts\python.exe -m unittest tests.unit.test_run_until_blocked tests.unit.test_analysis_decision_monitor tests.unit.test_runtime_script_json",
r"D:\ai-exchange\.venv\Scripts\python.exe scripts\run_script_static_checks.py",
r"D:\ai-exchange\.venv\Scripts\python.exe scripts\run_until_blocked.py",
"```",
"",
]
)
def render_analysis_decision_monitor_maturity_rollup_tasks(*, summary: dict) -> str:
rollup = dict(summary.get("rollup") or {})
local = dict(rollup.get("local") or {})
next_slice = str(rollup.get("next_slice") or summary.get("next_slice") or "")
blocker_lines = (
[f"- [ ] {rollup.get('blocked_reason')}"]
if rollup.get("blocked_reason")
else [
"- [ ] `decision_support_ready` remains strict unrestricted lane and must not be force-enabled",
"- [ ] `real_live_path_ready` remains intentionally disabled until the live adapter is real-live ready",
"- [ ] P7-2 remote drift check is still pending",
]
)
return "\n".join(
[
"# ai-exchange Tasks",
"",
"## Execution Mode",
"",
"- [x] Autonomous mode enabled",
"- [x] No per-stage confirmation required",
"- [x] Each stage must close the loop through implementation, validation, deploy/smoke when possible, and state writeback",
"- [x] P0-P6 Research Council phase expansion is frozen",
"- [x] P6 `no_op_healthy` must not create Research Council P7",
"- [x] Stable monitor quality may enter automation execution-layer P7",
"",
"## In Progress",
"",
f"- [ ] `{next_slice}`",
"",
"## Next Up",
"",
"- [ ] Run P7-2 local/remote monitor maturity drift check",
"- [ ] Keep local v2rayN/xray running on `192.168.3.73:10808`",
"- [ ] Monitor restricted decision-support output and alert quality",
"- [ ] Treat OKX fills remaining `0` as acceptable in the current analysis/decision-only phase",
"- [ ] Keep execution, live readiness, risk, trust, and operator confirmation gates unchanged",
"",
"## Automation Status",
"",
f"- [x] state: {rollup.get('state')}",
"- [x] current_phase: Restricted Decision-Support Soft Rollout",
"- [x] latest_p6_tick_status: no_op_healthy",
"- [x] no_new_research_council_phase_created: true",
f"- [x] latest_p7_maturity_status: {rollup.get('maturity_status')}",
f"- [x] latest_p7_rollup_path: {summary.get('rollup_path')}",
f"- [x] latest_analysis_decision_monitor_history_records: {local.get('record_count')}",
f"- [x] latest_analysis_decision_monitor_consecutive_passes: {local.get('consecutive_pass_count')}",
f"- [x] latest_analysis_decision_monitor_stable_window: {str(local.get('stable_monitoring_window')).lower()}",
f"- [x] latest_analysis_decision_monitor_quality_gate_status: {local.get('quality_gate_status')}",
"",
"## Newly Done",
"",
"- [x] Generated analysis decision monitor maturity rollup",
"- [x] Promoted stable monitor evidence into a P7 operator-consumable state artifact",
"- [x] Closed P7-1 without adding a new Research Council phase",
"- [x] Kept OKX fills out of the current-phase blocker set",
"- [x] Preserved execution, live readiness, risk, trust, and operator confirmation gates",
"",
"## Blocked",
"",
*blocker_lines,
"",
"## Deferred Until Real-Trading Phase",
"",
"- [ ] `real_trade_sample_verified` is not required in the current analysis/decision-only phase",
"- [ ] Real fill reconciliation should be verified only when the project intentionally enters a real-trading validation phase",
"",
"## Operational Dependencies",
"",
"- [ ] Keep `D:\\v2rayN-windows-64\\v2rayN.exe` / `xray.exe` running on the local Windows host",
"- [ ] Keep remote `.env` proxy path compatible with `MARKET_TRUTH_PROXY_URL=http://192.168.3.73:10808`",
"",
"## Major Done Milestones",
"",
"- [x] `P0-1` Real-time market truth layer",
"- [x] `P0-2` Real Account And Order Read Sync",
"- [x] `P0-3` Runtime Scheduling And Supervision",
"- [x] `P0-4` Decision Packet And Proactive Alerting",
"- [x] `P0-5` Real Trade Mapping And Post-Trade Loop",
"- [x] `P1-1` Trust Scoring And Model Whitelists",
"- [x] `P1-2` Operator Experience And Workflow Tools",
"- [x] `P1-3` Live Adapter Readiness Prep",
"- [x] `Research Council P0` local gate",
"- [x] `Research Council P1` local gate",
"- [x] `Research Council P2` remote deploy and smoke gate",
"- [x] `Research Council P3` remote deploy and smoke gate",
"- [x] `Research Council P4` remote deploy and smoke gate",
"- [x] `Research Council P5` remote deploy, remote cycle, and review smoke gate",
"- [x] `Research Council P6` remote deploy and governance tick smoke gate",
"- [x] `P0-P6` reasonableness review and phase freeze gate",
"- [x] `analysis_decision_monitor_runner` Current-phase single-command monitor",
"- [x] `automation_execution_layer_p7_monitor_maturity_rollup` P7-1 maturity rollup",
"",
"## Backlog",
"",
"- [ ] Long-term target: complete `Real-time Assisted Trading V1` without enabling default live auto-trading",
"",
]
)
def render_analysis_decision_monitor_session(*, summary: dict) -> str:
updated_ts = datetime.now(timezone.utc).strftime("%Y-%m-%d UTC")
observed = dict(summary.get("observed") or {})
history = dict(summary.get("monitor_history") or {})
trend = dict(history.get("trend") or {})
quality_gate = dict(history.get("quality_gate") or {})
next_slice = str(summary.get("next_slice") or "")
p7_ready = next_slice == ANALYSIS_DECISION_MONITOR_P7_NEXT_SLICE
status_text = "passed" if summary.get("monitor_passed") else "blocked"
blocker_lines = [f"- {item}" for item in summary.get("monitor_blockers") or []] or ["- none"]
warning_lines = [f"- {item}" for item in summary.get("monitor_warnings") or []] or ["- none"]
stable_next_step_text = (
"Enter P7 monitor maturity rollup. Convert the stable monitoring window into an operator-facing "
"maturity summary, local/remote drift check, alert-quality review, and the next bounded automation slice. "
"This is an automation execution-layer P7, not a new Research Council phase."
if p7_ready
else "Run the analysis/decision monitor again on the next periodic tick. If it passes, continue observation and state recording. If it reports blockers, repair the smallest blocker slice first. Do not wait on OKX fills in this phase."
)
stable_done_line = (
"- Stable monitor quality gate passed, so automation opened execution-layer P7 without creating a new Research Council phase."
if p7_ready
else "- Preserved P0-P6 freeze semantics and did not create P7."
)
stable_working_line = (
"- A passing stable-window monitor now routes to automation execution-layer P7 instead of looping forever on periodic monitoring."
if p7_ready
else "- A passing monitor closes the current loop and schedules the next periodic monitoring tick."
)
return "\n".join(
[
"# ai-exchange Session",
"",
"## Current Goal",
"",
"Automation execution-layer bootstrap is complete after the P0-P6 Research Council phase freeze. The active path is analysis/decision-only restricted decision-support monitoring; real fill verification is deferred because this phase does not do live trading.",
"",
"## Current Branch",
"",
"`main`",
"",
"## Last Updated",
"",
updated_ts,
"",
"## Automation Status",
"",
"- state: analysis_decision_monitor_runner_automation_wired",
"- current_phase: Restricted Decision-Support Soft Rollout",
f"- updated_ts: {updated_ts}",
"- latest_p6_tick_status: no_op_healthy",
"- latest_p6_effective_health_state: healthy",
"- latest_safe_to_auto_continue: true",
f"- latest_analysis_decision_monitor_status: {status_text}",
f"- latest_analysis_decision_monitor_ts: {summary.get('monitor_generated_ts')}",
f"- latest_analysis_decision_monitor_history_path: {history.get('path')}",
f"- latest_analysis_decision_monitor_history_records: {history.get('record_count')}",
f"- latest_analysis_decision_monitor_trend_path: {history.get('trend_path')}",
f"- latest_analysis_decision_monitor_consecutive_passes: {trend.get('consecutive_pass_count')}",
f"- latest_analysis_decision_monitor_stable_window: {str(trend.get('stable_monitoring_window')).lower()}",
f"- latest_analysis_decision_monitor_quality_gate_path: {history.get('quality_gate_path')}",
f"- latest_analysis_decision_monitor_quality_gate_status: {quality_gate.get('status')}",
f"- latest_real_trade_sample_gate_required: {str(observed.get('real_trade_sample_gate_required')).lower()}",
f"- latest_real_trade_sample_gate_status: {observed.get('real_trade_sample_gate_status')}",
f"- repair_next_action: {summary.get('next_slice')}",
"",
"## Completed In This Run",
"",
"- Read the required automation anchors.",
"- Routed `run_until_blocked.py` into the analysis/decision-only monitor when that phase is active.",
"- Ran the single-command analysis decision monitor for launch readiness, broker state, runtime health, decision packet, review packet, and P6 no-new-phase governance.",
"- Wrote machine-consumable automation status and refreshed `SESSION.md` / `TASKS.md`.",
"- Appended the monitor sample to `.claude/analysis_decision_monitor_history.jsonl` for periodic evidence tracking.",
"- Refreshed `.claude/analysis_decision_monitor_trend.json` so periodic monitoring has a stable-window summary.",
"- Refreshed `.claude/analysis_decision_monitor_quality_gate.json` so automation can consume the current-phase gate directly.",
stable_done_line,
"- Did not wait on OKX fills because real fill verification is deferred to a future real-trading validation phase.",
"- Did not change `execution_allowed`, `real_live_ready`, risk governor, trust bucket, or operator confirmation protocol.",
"",
"## Current Working State",
"",
"- P0-P6 Research Council automation phases remain complete and frozen.",
"- `scripts/run_until_blocked.py` now treats the analysis/decision monitor as the active execution-layer stage after monitor deployment.",
stable_working_line,
"- A failing monitor routes to the smallest repair slice: `repair_analysis_decision_monitor_blockers`.",
f"- Monitor history records: {history.get('record_count')} at `{history.get('path')}`.",
f"- Monitor consecutive passes: {trend.get('consecutive_pass_count')} / {trend.get('stable_sample_threshold')}.",
f"- Monitor stable window: {str(trend.get('stable_monitoring_window')).lower()}.",
f"- Monitor quality gate: {quality_gate.get('status')}.",
f"- Current monitor result: {status_text}.",
f"- Current broker status: {observed.get('broker_status')}.",
f"- Current broker fills: {observed.get('broker_fills')}.",
f"- Current runtime health: {observed.get('runtime_health')}.",
f"- Current market stream: {observed.get('market_stream')}.",
f"- Current market freshness: {observed.get('market_freshness')}.",
f"- Current market gap: {observed.get('market_gap')}.",
f"- Current decision action: {observed.get('decision_action')}.",
f"- Current operator mode: {observed.get('decision_operator_mode')}.",
f"- Current trust classification: {observed.get('decision_trust_classification')}.",
"",
"## Monitor Blockers",
"",
*blocker_lines,
"",
"## Monitor Warnings",
"",
*warning_lines,
"",
"## Current Risks Or Open Questions",
"",
"- `gstack` / `GSTACK_OK` is not available as a local command or root sentinel in this checkout; this slice used repository-native focused validation.",
"- `decision_support_ready` remains a strict unrestricted lane and must not be force-enabled.",
"- The recovered OKX path depends on the local v2rayN/xray process staying up on `192.168.3.73:10808`.",
"- `real_trade_sample_verified` is deferred and not required while the system is analysis/decision-only.",
"- `real_live_ready` remains intentionally `false` because the OKX order adapter is still placeholder-only.",
"- Large backlog drain remains sensitive to remote PostgreSQL connection timeouts.",
"",
"## Exact Next Step",
"",
"Proceed directly to:",
"",
f"### {next_slice}",
"",
stable_next_step_text,
"",
"## Validation Commands Used Recently",
"",
"```bash",
r"D:\ai-exchange\.venv\Scripts\python.exe -m unittest tests.unit.test_run_until_blocked tests.unit.test_analysis_decision_monitor tests.unit.test_runtime_script_json",
r"D:\ai-exchange\.venv\Scripts\python.exe scripts\run_script_static_checks.py",
"npm run lint",
r"D:\ai-exchange\.venv\Scripts\python.exe scripts\deploy_with_password.py",
r"D:\ai-exchange\.venv\Scripts\python.exe scripts\run_analysis_decision_monitor.py",
"```",
"",
]
)
def render_analysis_decision_monitor_tasks(*, summary: dict) -> str:
status_text = "passed" if summary.get("monitor_passed") else "blocked"
history = dict(summary.get("monitor_history") or {})
trend = dict(history.get("trend") or {})
quality_gate = dict(history.get("quality_gate") or {})
next_slice = str(summary.get("next_slice") or "")
p7_ready = next_slice == ANALYSIS_DECISION_MONITOR_P7_NEXT_SLICE
p7_next_up = (
"- [ ] Execute P7 monitor maturity rollup before returning to periodic monitoring"
if p7_ready
else "- [ ] Keep P6 governance tick as no-new-phase health gate during monitoring"
)
p7_done_line = (
"- [x] Opened automation execution-layer P7 after stable monitor quality gate"
if p7_ready
else "- [x] Preserved P0-P6 freeze semantics and did not create P7"
)
blocker_lines = (
[f"- [ ] {summary.get('blocked_reason')}"]
if summary.get("blocked_reason")
else [
"- [ ] `decision_support_ready` remains strict unrestricted lane and must not be force-enabled",
"- [ ] `real_live_path_ready` remains intentionally disabled until the live adapter is real-live ready",
"- [ ] Large backlog drain remains sensitive to remote PostgreSQL connection timeouts",
]
)
return "\n".join(
[
"# ai-exchange Tasks",
"",
"## Execution Mode",
"",
"- [x] Autonomous mode enabled",
"- [x] No per-stage confirmation required",
"- [x] Each stage must close the loop through implementation, validation, deploy/smoke when possible, and state writeback",
"- [x] P0-P6 Research Council phase expansion is frozen",
"- [x] P6 `no_op_healthy` must not create Research Council P7",
"- [x] Stable monitor quality may enter automation execution-layer P7",
"",
"## In Progress",
"",
f"- [ ] `{next_slice}`",
"",
"## Next Up",
"",
"- [ ] Prefer `scripts/run_analysis_decision_monitor.py` for current-phase smoke",
"- [ ] Keep local v2rayN/xray running on `192.168.3.73:10808`",
"- [ ] Monitor restricted decision-support output and alert quality",
"- [ ] Treat OKX fills remaining `0` as acceptable in the current analysis/decision-only phase",
"- [ ] Keep execution, live readiness, risk, trust, and operator confirmation gates unchanged",
p7_next_up,
"",
"## Automation Status",
"",
"- [x] state: analysis_decision_monitor_runner_automation_wired",
"- [x] current_phase: Restricted Decision-Support Soft Rollout",
"- [x] latest_p6_tick_status: no_op_healthy",
"- [x] latest_p6_effective_health_state: healthy",
"- [x] no_new_phase_created: true",
f"- [x] latest_analysis_decision_monitor_status: {status_text}",
f"- [x] latest_analysis_decision_monitor_ts: {summary.get('monitor_generated_ts')}",
f"- [x] latest_analysis_decision_monitor_history_records: {history.get('record_count')}",
f"- [x] latest_analysis_decision_monitor_consecutive_passes: {trend.get('consecutive_pass_count')}",
f"- [x] latest_analysis_decision_monitor_stable_window: {str(trend.get('stable_monitoring_window')).lower()}",
f"- [x] latest_analysis_decision_monitor_quality_gate_status: {quality_gate.get('status')}",
"",
"## Newly Done",
"",
"- [x] Wired `run_until_blocked.py` to run the analysis/decision monitor for the current phase",
"- [x] Added machine-consumable monitor summary and runner status writeback",
"- [x] Appended periodic analysis decision monitor history",
"- [x] Refreshed periodic monitor trend summary",
"- [x] Refreshed analysis decision monitor quality gate",
"- [x] Refreshed `SESSION.md` and `TASKS.md` from monitor result",
"- [x] Kept OKX fills out of the current-phase blocker set",
p7_done_line,
"- [x] Preserved execution, live readiness, risk, trust, and operator confirmation gates",
"",
"## Blocked",
"",
*blocker_lines,
"",
"## Deferred Until Real-Trading Phase",
"",
"- [ ] `real_trade_sample_verified` is not required in the current analysis/decision-only phase",
"- [ ] Real fill reconciliation should be verified only when the project intentionally enters a real-trading validation phase",
"",
"## Operational Dependencies",
"",
"- [ ] Keep `D:\\v2rayN-windows-64\\v2rayN.exe` / `xray.exe` running on the local Windows host",
"- [ ] Keep remote `.env` proxy path compatible with `MARKET_TRUTH_PROXY_URL=http://192.168.3.73:10808`",
"",
"## Major Done Milestones",
"",
"- [x] `P0-1` Real-time market truth layer",
"- [x] `P0-2` Real Account And Order Read Sync",
"- [x] `P0-3` Runtime Scheduling And Supervision",
"- [x] `P0-4` Decision Packet And Proactive Alerting",
"- [x] `P0-5` Real Trade Mapping And Post-Trade Loop",
"- [x] `P1-1` Trust Scoring And Model Whitelists",
"- [x] `P1-2` Operator Experience And Workflow Tools",
"- [x] `P1-3` Live Adapter Readiness Prep",
"- [x] `Research Council P0` local gate",
"- [x] `Research Council P1` local gate",
"- [x] `Research Council P2` remote deploy and smoke gate",
"- [x] `Research Council P3` remote deploy and smoke gate",
"- [x] `Research Council P4` remote deploy and smoke gate",
"- [x] `Research Council P5` remote deploy, remote cycle, and review smoke gate",
"- [x] `Research Council P6` remote deploy and governance tick smoke gate",
"- [x] `P0-P6` reasonableness review and phase freeze gate",
"- [x] `Automation Execution-Layer P0` governance gate routing",
"- [x] `automation_execution_layer_p1_anchor_slice_selector` Anchor-driven smallest-slice selector",
"- [x] `automation_execution_layer_p2_validation_quality_router` Focused validation and quality-gate router",
"- [x] `automation_execution_layer_p3_deploy_smoke_router` Deploy and smoke routing policy",
"- [x] `automation_execution_layer_p4_state_writeback_renderer` State writeback renderer",
"- [x] `launch_readiness_operator_soft_rollout_lane` Runtime readiness progress lane",
"- [x] `analysis_decision_monitor_runner` Current-phase single-command monitor",
"",
"## Backlog",
"",
"- [ ] Long-term target: complete `Real-time Assisted Trading V1` without enabling default live auto-trading",
"",
]
)
def derive_automation_execution_anchor_status(anchor_state: dict) -> dict:
completed = set(anchor_state.get("completed_execution_slices") or [])
for code, state, phase in AUTOMATION_EXECUTION_CLOSED_STATUS_SEQUENCE:
if code in completed:
return {"state": state, "current_phase": phase}
return {
"state": AUTOMATION_EXECUTION_GATE_CLOSED_STATE,
"current_phase": "Automation Execution-Layer P0 Governance Gate",
}
def render_completed_execution_slice_lines(anchor_state: dict) -> list[str]:
completed = set(anchor_state.get("completed_execution_slices") or [])
lines = []
for code, _state, phase in reversed(AUTOMATION_EXECUTION_CLOSED_STATUS_SEQUENCE):
if code in completed:
lines.append(f"- [x] `{phase}`")
return lines or ["- [x] `Automation Execution-Layer P0` governance gate routing"]
def run_supervised_until_stopped(env=None, runner=None, sleeper=None) -> dict:
env = dict(os.environ if env is None else env)
sleeper = sleeper or time.sleep
loop_sleep_seconds = max(_get_int(env, "RUN_UNTIL_BLOCKED_LOOP_SLEEP_SECONDS", 30), 1)
max_supervisor_cycles = max(_get_int(env, "RUN_UNTIL_BLOCKED_SUPERVISOR_CYCLES", 0), 0)
cycles = 0
last_summary = None
resume_fingerprint = compute_resume_fingerprint()
while True:
last_summary = run_until_blocked(env=env, runner=runner, sleeper=sleeper)
cycles += 1
if last_summary["status"] == "completed":
return last_summary
supervisor_state = classify_supervisor_state(last_summary)
write_runner_status(
{
"state": supervisor_state,
"started_ts": last_summary["started_ts"],
"heartbeat_ts": datetime.now(timezone.utc).isoformat(),
"current_stage": last_summary.get("current_stage"),
"completed_stages": last_summary.get("completed_stages") or [],
"blocked_reason": last_summary.get("blocked_reason"),
"rounds_completed": len(last_summary.get("rounds") or []),
"sleep_seconds": loop_sleep_seconds,
"supervisor_cycles": cycles,
"resume_fingerprint": resume_fingerprint,
}
)
if max_supervisor_cycles and cycles >= max_supervisor_cycles:
return last_summary
if supervisor_state in {"paused", "needs_fix"}:
if wait_for_resume_condition(
previous_fingerprint=resume_fingerprint,
sleeper=sleeper,
loop_sleep_seconds=loop_sleep_seconds,
):
resume_fingerprint = compute_resume_fingerprint()
continue
return last_summary
sleeper(loop_sleep_seconds)
def classify_supervisor_state(summary: dict) -> str:
blocked_reason = str(summary.get("blocked_reason") or "")
if summary.get("status") == "completed":
return "completed"
if blocked_reason.startswith("autonomous_mode:pipeline_failed"):
return "needs_fix"
if blocked_reason.startswith("autonomous_mode:offline_checks_failed"):
return "needs_fix"
if blocked_reason.startswith("autonomous_mode:migration_failed"):
return "needs_fix"
if blocked_reason.startswith("autonomous_mode:backfill_failed"):
return "paused"
if blocked_reason.startswith("autonomous_mode:waiting_for_database"):
return "sleeping"
if blocked_reason:
return "paused"
return "sleeping"
def wait_for_resume_condition(*, previous_fingerprint: str, sleeper, loop_sleep_seconds: int) -> bool:
current_fingerprint = compute_resume_fingerprint()
if current_fingerprint != previous_fingerprint:
return True
sleeper(loop_sleep_seconds)
return compute_resume_fingerprint() != previous_fingerprint
def compute_resume_fingerprint() -> str:
entries = []
for path in RUNNER_RESUME_WATCH_PATHS:
if not path.exists():
entries.append(f"{path}:missing")
continue
if path.is_file():
stat = path.stat()
entries.append(f"{path}:{stat.st_mtime_ns}:{stat.st_size}")
continue
for child in sorted(path.rglob("*")):
if not child.is_file():
continue
stat = child.stat()
entries.append(f"{child}:{stat.st_mtime_ns}:{stat.st_size}")
return "|".join(entries)
def write_runner_status(payload: dict) -> None:
RUNNER_STATUS_PATH.parent.mkdir(parents=True, exist_ok=True)
RUNNER_STATUS_PATH.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
def detect_current_stage(session_text: str, completed_stages: list[str]) -> Optional[str]:
exact_next_step = re.search(r"###\s+`(P\d-\d)`", session_text)
if exact_next_step and exact_next_step.group(1) in STAGE_SEQUENCE:
return exact_next_step.group(1)
for stage in STAGE_SEQUENCE:
if stage not in completed_stages:
return stage
return None
def get_next_stage(current_stage: str, completed_stages: list[str]) -> Optional[str]:
for stage in STAGE_SEQUENCE:
if stage not in completed_stages:
return stage
return None
def parse_done_stages(tasks_text: str) -> list[str]:
return re.findall(r"- \[x\] `(P\d-\d)`", tasks_text)
def first_blocked_reason(summary: dict) -> Optional[str]:
blockers = [str(item) for item in summary.get("blockers") or []]
for blocker in blockers:
if any(marker in blocker for marker in BLOCKED_MARKERS):
return blocker
if summary.get("continuation_decision") == "blocked":
return blockers[0] if blockers else summary.get("mode")
return None
def write_anchor_files(*, completed_stages: list[str], current_stage: Optional[str], last_result: dict, blocked_reason: Optional[str] = None) -> None:
SESSION_PATH.write_text(render_session(completed_stages, current_stage, last_result, blocked_reason), encoding="utf-8")
TASKS_PATH.write_text(render_tasks(completed_stages, current_stage, blocked_reason), encoding="utf-8")
def render_session(completed_stages: list[str], current_stage: Optional[str], last_result: dict, blocked_reason: Optional[str]) -> str:
completed_lines = [f"- 已完成 `{stage}`:{STAGE_TITLES[stage]}" for stage in completed_stages] or ["- 本轮尚无新增完成阶段"]
next_step_block = render_next_step_block(current_stage)
risk_lines = [
"- 当前市场真值链路依赖本机 `192.168.3.73:10808` 持续可达;如本机关机或代理退出,远端将退化",
"- 真实账户只读同步下一阶段可能需要额外凭据或账号权限;若现有环境缺失,将构成唯一允许的用户介入点",
]
if blocked_reason:
risk_lines.append(f"- 当前自动化已阻塞:{blocked_reason}")
return "\n".join(
[
"# ai-exchange Session",
"",
"## Current Goal",
"",
"从现在开始按全自动模式持续推进交易模块主线,不再等待阶段确认,直到完成 `docs/AI_ICT_实盘辅助决策系统开发方案_v2.md` 中剩余开发目标。",
"",
"## Current Branch",
"",
"`main`",
"",
"## Last Updated",
"",
f"{datetime.now(timezone.utc).strftime('%Y-%m-%d UTC')}",
"",
"## Completed In Last Session",
"",
*completed_lines,
"",
"## Current Working State",
"",
"- 当前主线执行协议已固化到:",
" - `docs/AI_ICT_全自动开发执行流程_v1.md`",
"- 当前阶段队列来源于:",
" - `docs/AI_ICT_实盘辅助决策系统开发方案_v2.md`",
"- 默认不再等待用户中途确认",
"- 每阶段必须完成:",
" - 实现",
" - focused tests",
" - `npm run lint`",
" - `npm run build`",
" - `npm run visual:snapshots`",
" - Docker 部署",
" - 远端 smoke",
" - 状态回写",
"",
"## Exact Next Step",
"",
next_step_block,
"",
"## Validation Commands",
"",
"```bash",
*DEFAULT_VALIDATION_COMMANDS,
"```",
"",
"## Risks Or Open Questions",
"",
*risk_lines,
"",
]
)
def render_next_step_block(current_stage: Optional[str]) -> str:
if current_stage is None:
return "所有阶段已完成,无下一阶段。"
goal_lines = STAGE_GOALS.get(current_stage, [])
rendered_goals = "\n".join(f"- {line}" for line in goal_lines)
return f"直接进入:\n\n### `{current_stage}` {STAGE_TITLES[current_stage]}\n\n目标:\n\n{rendered_goals}"
def render_tasks(completed_stages: list[str], current_stage: Optional[str], blocked_reason: Optional[str]) -> str:
in_progress_lines = [f"- [ ] `{current_stage}` {STAGE_TITLES[current_stage]}"] if current_stage else ["- [x] 所有阶段已完成"]
next_up_lines = [
f"- [ ] `{stage}` {STAGE_TITLES[stage]}"
for stage in STAGE_SEQUENCE
if stage != current_stage and stage not in completed_stages
] or ["- [x] 当前无后续阶段"]
blocked_lines = [f"- [ ] {blocked_reason}"] if blocked_reason else ["- [ ] 当前无阻塞;如缺凭据或外部权限,再转入此区"]
done_lines = [f"- [x] `{stage}` {STAGE_TITLES[stage]}" for stage in completed_stages] or ["- [ ] 当前尚无完成阶段"]
return "\n".join(
[
"# ai-exchange Tasks",
"",
"## Execution Mode",
"",
"- [x] 全自动模式已启用",
"- [x] 默认不等待阶段确认",
"- [x] 每阶段必须完成实现、验证、部署、smoke、状态回写",
"",
"## In Progress",
"",
*in_progress_lines,
"",
"## Next Up",
"",
*next_up_lines,
"",
"## Backlog",
"",
"- [ ] 长期目标:在不默认开启真实自动下单的前提下,完成 `Real-time Assisted Trading V1`",
"",
"## Blocked",
"",
*blocked_lines,
"",
"## Done",
"",
*done_lines,
"",
]
)
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 _get_int(env, key: str, default: int) -> int:
value = env.get(key)
if value is None or value == "":
return default
return int(value)
def main() -> int:
env = dict(os.environ)
summary = run_supervised_until_stopped(env=env) if _get_bool(env, "RUN_UNTIL_BLOCKED_SUPERVISOR", False) else run_until_blocked(env=env)
return 0 if summary["status"] == "completed" else 2 if summary["status"] == "blocked" else 1
if __name__ == "__main__":
raise SystemExit(main())