From 7342c738a8b329d560afda1d55cc0980fba40e0d Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 18 Jun 2026 11:54:59 +0800 Subject: [PATCH] =?UTF-8?q?feat(ai):=20=E6=96=B0=E5=A2=9E=20P2-004=20?= =?UTF-8?q?=E4=BE=9B=E6=87=89=E9=8F=88=E6=BC=82=E7=A7=BB=E7=9B=A3=E6=8E=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/api/src/api/v1/agents.py | 32 ++ .../dependency_supply_chain_drift_monitor.py | 159 ++++++ ...t_dependency_supply_chain_drift_monitor.py | 235 +++++++++ ...pendency_supply_chain_drift_monitor_api.py | 41 ++ apps/web/messages/en.json | 38 ++ apps/web/messages/zh-TW.json | 38 ++ .../tabs/automation-inventory-tab.tsx | 167 +++++- apps/web/src/lib/api-client.ts | 90 ++++ docs/LOGBOOK.md | 35 ++ ...AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md | 69 ++- ...supply_chain_drift_monitor_2026-06-18.json | 495 ++++++++++++++++++ ..._supply_chain_drift_monitor_v1.schema.json | 346 ++++++++++++ ...-04-15-MASTER-ai-autonomous-flywheel-v2.md | 15 + 13 files changed, 1754 insertions(+), 6 deletions(-) create mode 100644 apps/api/src/services/dependency_supply_chain_drift_monitor.py create mode 100644 apps/api/tests/test_dependency_supply_chain_drift_monitor.py create mode 100644 apps/api/tests/test_dependency_supply_chain_drift_monitor_api.py create mode 100644 docs/evaluations/dependency_supply_chain_drift_monitor_2026-06-18.json create mode 100644 docs/schemas/dependency_supply_chain_drift_monitor_v1.schema.json diff --git a/apps/api/src/api/v1/agents.py b/apps/api/src/api/v1/agents.py index 278e4036f..541d6a179 100644 --- a/apps/api/src/api/v1/agents.py +++ b/apps/api/src/api/v1/agents.py @@ -280,6 +280,9 @@ from src.services.dependency_drift_check_plan import ( from src.services.dependency_risk_policy import ( load_latest_dependency_risk_policy, ) +from src.services.dependency_supply_chain_drift_monitor import ( + load_latest_dependency_supply_chain_drift_monitor, +) from src.services.dependency_upgrade_approval_package_template import ( load_latest_dependency_upgrade_approval_package_template, ) @@ -3197,6 +3200,35 @@ async def get_dependency_drift_check_plan() -> dict[str, Any]: ) from exc +@router.get( + "/dependency-supply-chain-drift-monitor", + response_model=dict[str, Any], + summary="取得 P2-004 依賴 / 供應鏈漂移監控", + description=( + "讀取最新已提交的 P2-004 依賴 / 供應鏈漂移監控;" + "此端點只回傳 repo-only committed snapshot,不啟用排程、不寫 workflow、" + "不呼叫外部 CVE / license / registry / Agent market 來源、不安裝或升級套件、" + "不寫 lockfile、不執行 npm audit、不執行 docker build、不 pull image、不推 registry、" + "不建立 PR、不送 Telegram、不讀 secret、不做 host probe、不做 production write。" + ), +) +async def get_dependency_supply_chain_drift_monitor() -> dict[str, Any]: + """Return the latest read-only P2-004 dependency / supply-chain drift monitor.""" + try: + return await asyncio.to_thread(load_latest_dependency_supply_chain_drift_monitor) + except FileNotFoundError as exc: + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=str(exc), + ) from exc + except (json.JSONDecodeError, ValueError) as exc: + logger.error("dependency_supply_chain_drift_monitor_invalid", error=str(exc)) + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="P2-004 依賴 / 供應鏈漂移監控快照無效", + ) from exc + + @router.get( "/dependency-upgrade-approval-package-template", response_model=dict[str, Any], diff --git a/apps/api/src/services/dependency_supply_chain_drift_monitor.py b/apps/api/src/services/dependency_supply_chain_drift_monitor.py new file mode 100644 index 000000000..5695a2c2f --- /dev/null +++ b/apps/api/src/services/dependency_supply_chain_drift_monitor.py @@ -0,0 +1,159 @@ +""" +P2-004 dependency / supply-chain drift monitor snapshot. + +Loads the latest committed, read-only monitor that aggregates existing +package, JavaScript, Docker, dependency-policy, and version freshness +snapshots. This monitor does not query external CVE/license/registry sources, +install packages, write lockfiles, build images, create PRs, send Telegram +messages, read secrets, or mutate production. +""" + +from __future__ import annotations + +import json +from pathlib import Path +from typing import Any + +from src.services.snapshot_paths import default_evaluations_dir + +_DEFAULT_EVALUATIONS_DIR = default_evaluations_dir(Path(__file__)) +_SNAPSHOT_PATTERN = "dependency_supply_chain_drift_monitor_*.json" +_SCHEMA_VERSION = "dependency_supply_chain_drift_monitor_v1" + +_ACTION_REQUIRED_STATUSES = {"action_required", "blocked_until_approval"} +_BLOCKED_BOUNDARY_FLAGS = { + "schedule_activation_allowed", + "workflow_write_allowed", + "external_cve_lookup_allowed", + "external_license_lookup_allowed", + "registry_lookup_allowed", + "agent_market_external_lookup_allowed", + "package_installation_allowed", + "package_upgrade_allowed", + "lockfile_write_allowed", + "docker_build_allowed", + "image_pull_allowed", + "image_rebuild_allowed", + "registry_push_allowed", + "pr_creation_allowed", + "telegram_send_allowed", + "production_write_allowed", + "paid_external_service_allowed", + "secret_read_allowed", + "host_probe_allowed", + "npm_audit_allowed", +} + + +def load_latest_dependency_supply_chain_drift_monitor( + evaluations_dir: Path | None = None, +) -> dict[str, Any]: + """Load the newest committed P2-004 dependency / supply-chain drift monitor.""" + directory = evaluations_dir or _DEFAULT_EVALUATIONS_DIR + candidates = sorted(directory.glob(_SNAPSHOT_PATTERN)) + if not candidates: + raise FileNotFoundError( + f"no dependency supply-chain drift monitor snapshots found in {directory}" + ) + + latest = candidates[-1] + with latest.open(encoding="utf-8") as handle: + payload = json.load(handle) + + if not isinstance(payload, dict): + raise ValueError(f"{latest}: expected JSON object") + _require_schema(payload, _SCHEMA_VERSION, str(latest)) + _require_read_only_boundaries(payload, str(latest)) + _require_rollup_consistency(payload, str(latest)) + _require_candidate_evidence(payload, str(latest)) + _require_owner_action_links(payload, str(latest)) + return payload + + +def _require_schema(payload: dict[str, Any], expected: str, label: str) -> None: + actual = payload.get("schema_version") + if actual != expected: + raise ValueError(f"{label}: expected schema_version={expected}, got {actual!r}") + + +def _require_read_only_boundaries(payload: dict[str, Any], label: str) -> None: + program_status = payload.get("program_status") or {} + if program_status.get("read_only_mode") is not True: + raise ValueError(f"{label}: program_status.read_only_mode must be true") + + boundaries = payload.get("monitor_boundaries") or {} + if boundaries.get("read_only_repo_monitor_allowed") is not True: + raise ValueError(f"{label}: read_only_repo_monitor_allowed must be true") + + allowed = sorted(flag for flag in _BLOCKED_BOUNDARY_FLAGS if boundaries.get(flag) is not False) + if allowed: + raise ValueError(f"{label}: monitor boundaries must remain false: {allowed}") + + +def _require_rollup_consistency(payload: dict[str, Any], label: str) -> None: + source_snapshots = payload.get("source_snapshot_readbacks") or [] + monitor_checks = payload.get("monitor_checks") or [] + drift_candidates = payload.get("drift_candidates") or [] + owner_actions = payload.get("owner_actions") or [] + rollups = payload.get("rollups") or {} + boundaries = payload.get("monitor_boundaries") or {} + + if rollups.get("source_snapshot_count") != len(source_snapshots): + raise ValueError(f"{label}: rollups.source_snapshot_count must match source snapshots") + if rollups.get("monitor_check_count") != len(monitor_checks): + raise ValueError(f"{label}: rollups.monitor_check_count must match monitor checks") + if rollups.get("drift_candidate_count") != len(drift_candidates): + raise ValueError(f"{label}: rollups.drift_candidate_count must match drift candidates") + if rollups.get("owner_action_count") != len(owner_actions): + raise ValueError(f"{label}: rollups.owner_action_count must match owner actions") + + stale_count = sum( + 1 for snapshot in source_snapshots if snapshot.get("freshness_status") == "stale_action_required" + ) + if rollups.get("stale_source_snapshot_count") != stale_count: + raise ValueError(f"{label}: rollups.stale_source_snapshot_count must match stale snapshots") + + action_required_ids = { + candidate.get("candidate_id") + for candidate in drift_candidates + if candidate.get("status") in _ACTION_REQUIRED_STATUSES + } + if set(rollups.get("action_required_drift_candidate_ids") or []) != action_required_ids: + raise ValueError( + f"{label}: rollups.action_required_drift_candidate_ids must match candidates" + ) + if rollups.get("action_required_candidate_count") != len(action_required_ids): + raise ValueError(f"{label}: rollups.action_required_candidate_count must match candidates") + + by_domain: dict[str, int] = {} + for candidate in drift_candidates: + domain = candidate.get("domain") + by_domain[domain] = by_domain.get(domain, 0) + 1 + if rollups.get("by_domain") != by_domain: + raise ValueError(f"{label}: rollups.by_domain must match drift candidates") + + blocked_count = sum(1 for flag in _BLOCKED_BOUNDARY_FLAGS if boundaries.get(flag) is False) + if rollups.get("blocked_operation_count") != blocked_count: + raise ValueError(f"{label}: rollups.blocked_operation_count must match false boundaries") + + +def _require_candidate_evidence(payload: dict[str, Any], label: str) -> None: + drift_candidates = payload.get("drift_candidates") or [] + for candidate in drift_candidates: + candidate_id = candidate.get("candidate_id") or "" + evidence_refs = candidate.get("evidence_refs") or [] + if not evidence_refs: + raise ValueError(f"{label}: drift candidate {candidate_id} must include evidence_refs") + if not candidate.get("next_owner_action"): + raise ValueError(f"{label}: drift candidate {candidate_id} must include next_owner_action") + + +def _require_owner_action_links(payload: dict[str, Any], label: str) -> None: + owner_action_ids = {action.get("action_id") for action in payload.get("owner_actions") or []} + for candidate in payload.get("drift_candidates") or []: + action_id = candidate.get("next_owner_action") + if action_id not in owner_action_ids: + raise ValueError( + f"{label}: drift candidate {candidate.get('candidate_id')} references " + f"unknown owner action {action_id!r}" + ) diff --git a/apps/api/tests/test_dependency_supply_chain_drift_monitor.py b/apps/api/tests/test_dependency_supply_chain_drift_monitor.py new file mode 100644 index 000000000..405bec6e9 --- /dev/null +++ b/apps/api/tests/test_dependency_supply_chain_drift_monitor.py @@ -0,0 +1,235 @@ +from __future__ import annotations + +import json + +import pytest + +from src.services.dependency_supply_chain_drift_monitor import ( + load_latest_dependency_supply_chain_drift_monitor, +) + + +def test_load_latest_dependency_supply_chain_drift_monitor_reads_newest_file(tmp_path): + older = _snapshot(generated_at="2026-06-17T00:00:00+08:00", completion=72) + newer = _snapshot(generated_at="2026-06-18T00:00:00+08:00", completion=100) + (tmp_path / "dependency_supply_chain_drift_monitor_2026-06-17.json").write_text( + json.dumps(older), + encoding="utf-8", + ) + (tmp_path / "dependency_supply_chain_drift_monitor_2026-06-18.json").write_text( + json.dumps(newer), + encoding="utf-8", + ) + + loaded = load_latest_dependency_supply_chain_drift_monitor(tmp_path) + + assert loaded["generated_at"] == "2026-06-18T00:00:00+08:00" + assert loaded["program_status"]["overall_completion_percent"] == 100 + assert loaded["program_status"]["current_task_id"] == "P2-004" + assert loaded["program_status"]["next_task_id"] == "P2-406B" + assert loaded["monitor_boundaries"]["external_cve_lookup_allowed"] is False + assert loaded["monitor_boundaries"]["telegram_send_allowed"] is False + + +def test_dependency_supply_chain_drift_monitor_requires_read_only_mode(tmp_path): + snapshot = _snapshot() + snapshot["program_status"]["read_only_mode"] = False + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="read_only_mode"): + load_latest_dependency_supply_chain_drift_monitor(tmp_path) + + +def test_dependency_supply_chain_drift_monitor_blocks_external_lookup(tmp_path): + snapshot = _snapshot() + snapshot["monitor_boundaries"]["external_cve_lookup_allowed"] = True + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="monitor boundaries"): + load_latest_dependency_supply_chain_drift_monitor(tmp_path) + + +def test_dependency_supply_chain_drift_monitor_requires_rollup_consistency(tmp_path): + snapshot = _snapshot() + snapshot["rollups"]["drift_candidate_count"] = 99 + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="drift_candidate_count"): + load_latest_dependency_supply_chain_drift_monitor(tmp_path) + + +def test_dependency_supply_chain_drift_monitor_requires_candidate_evidence(tmp_path): + snapshot = _snapshot() + snapshot["drift_candidates"][0]["evidence_refs"] = [] + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="evidence_refs"): + load_latest_dependency_supply_chain_drift_monitor(tmp_path) + + +def test_dependency_supply_chain_drift_monitor_requires_owner_action_link(tmp_path): + snapshot = _snapshot() + snapshot["drift_candidates"][0]["next_owner_action"] = "missing_action" + _write_snapshot(tmp_path, snapshot) + + with pytest.raises(ValueError, match="unknown owner action"): + load_latest_dependency_supply_chain_drift_monitor(tmp_path) + + +def test_dependency_supply_chain_drift_monitor_fails_when_missing(tmp_path): + with pytest.raises(FileNotFoundError): + load_latest_dependency_supply_chain_drift_monitor(tmp_path) + + +def _write_snapshot(tmp_path, payload: dict) -> None: + (tmp_path / "dependency_supply_chain_drift_monitor_2026-06-18.json").write_text( + json.dumps(payload), + encoding="utf-8", + ) + + +def _snapshot( + *, + generated_at: str = "2026-06-18T00:00:00+08:00", + completion: int = 100, +) -> dict: + return { + "schema_version": "dependency_supply_chain_drift_monitor_v1", + "generated_at": generated_at, + "program_status": { + "overall_completion_percent": completion, + "current_priority": "P2", + "current_task_id": "P2-004", + "next_task_id": "P2-406B", + "read_only_mode": True, + "runtime_authority": "repo_only", + "status_note": "只讀監控。", + }, + "source_refs": ["docs/evaluations/package_supply_chain_inventory_2026-06-04.json"], + "rollups": { + "source_snapshot_count": 2, + "stale_source_snapshot_count": 1, + "monitor_check_count": 1, + "drift_candidate_count": 2, + "action_required_candidate_count": 2, + "owner_action_count": 2, + "blocked_operation_count": 20, + "by_domain": {"python": 1, "docker": 1}, + "action_required_drift_candidate_ids": [ + "python_manifest_drift", + "docker_digest_gap", + ], + }, + "source_snapshot_readbacks": [ + _source_snapshot("package_supply_chain_inventory", "stale_action_required"), + _source_snapshot("ai_agent_version_freshness_snapshot", "recent_action_required"), + ], + "monitor_checks": [ + { + "check_id": "python_manifest_authority_drift_readback", + "domain": "python", + "owner_agent": "hermes", + "status": "action_required", + "current_signal": "manifest drift", + "evidence_refs": ["apps/api/pyproject.toml"], + "blocked_now": ["package upgrade"], + "next_action": "prepare_python_manifest_authority_packet", + } + ], + "drift_candidates": [ + _candidate( + "python_manifest_drift", + "python", + "prepare_python_manifest_authority_packet", + ), + _candidate("docker_digest_gap", "docker", "prepare_docker_digest_pin_packet"), + ], + "owner_actions": [ + _owner_action("prepare_python_manifest_authority_packet", "openclaw"), + _owner_action("prepare_docker_digest_pin_packet", "openclaw"), + ], + "telegram_policy": { + "status": "draft_only_no_send", + "direct_send_allowed": False, + "gateway_queue_write_allowed": False, + "success_quiet": True, + "failure_digest_after_approval": True, + "draft_only_outputs": ["digest draft"], + }, + "agent_roles": [ + { + "agent_id": "openclaw", + "role": "仲裁", + "autonomy_level": "L2", + "approval_gate": "human_review_required", + "outputs": ["risk packet"], + } + ], + "next_actions": [ + { + "task_id": "P2-406B", + "priority": "P2", + "summary": "下一步", + "gate": "no send", + } + ], + "monitor_boundaries": { + "read_only_repo_monitor_allowed": True, + "schedule_activation_allowed": False, + "workflow_write_allowed": False, + "external_cve_lookup_allowed": False, + "external_license_lookup_allowed": False, + "registry_lookup_allowed": False, + "agent_market_external_lookup_allowed": False, + "package_installation_allowed": False, + "package_upgrade_allowed": False, + "lockfile_write_allowed": False, + "docker_build_allowed": False, + "image_pull_allowed": False, + "image_rebuild_allowed": False, + "registry_push_allowed": False, + "pr_creation_allowed": False, + "telegram_send_allowed": False, + "production_write_allowed": False, + "paid_external_service_allowed": False, + "secret_read_allowed": False, + "host_probe_allowed": False, + "npm_audit_allowed": False, + }, + } + + +def _source_snapshot(snapshot_id: str, freshness_status: str) -> dict: + return { + "snapshot_id": snapshot_id, + "source_ref": f"docs/evaluations/{snapshot_id}.json", + "source_schema_version": f"{snapshot_id}_v1", + "generated_at": "2026-06-04T00:00:00+08:00", + "freshness_status": freshness_status, + "evidence_status": "ready", + "next_action": "review", + } + + +def _candidate(candidate_id: str, domain: str, owner_action: str) -> dict: + return { + "candidate_id": candidate_id, + "domain": domain, + "severity": "high", + "status": "action_required", + "owner_agent": "openclaw", + "summary": candidate_id, + "evidence_refs": ["docs/evaluations/source.json"], + "next_owner_action": owner_action, + } + + +def _owner_action(action_id: str, owner_agent: str) -> dict: + return { + "action_id": action_id, + "priority": "P2", + "owner_agent": owner_agent, + "approval_gate": "human_review_required", + "summary": action_id, + "blocked_until": "Owner Gate", + } diff --git a/apps/api/tests/test_dependency_supply_chain_drift_monitor_api.py b/apps/api/tests/test_dependency_supply_chain_drift_monitor_api.py new file mode 100644 index 000000000..a2cf1a653 --- /dev/null +++ b/apps/api/tests/test_dependency_supply_chain_drift_monitor_api.py @@ -0,0 +1,41 @@ +from __future__ import annotations + +from fastapi import FastAPI +from fastapi.testclient import TestClient + +from src.api.v1.agents import router + + +def test_dependency_supply_chain_drift_monitor_endpoint_returns_committed_snapshot(): + app = FastAPI() + app.include_router(router, prefix="/api/v1") + client = TestClient(app) + + response = client.get("/api/v1/agents/dependency-supply-chain-drift-monitor") + + assert response.status_code == 200 + data = response.json() + assert data["schema_version"] == "dependency_supply_chain_drift_monitor_v1" + assert data["program_status"]["current_task_id"] == "P2-004" + assert data["program_status"]["next_task_id"] == "P2-406B" + assert data["program_status"]["read_only_mode"] is True + assert data["rollups"]["source_snapshot_count"] == len(data["source_snapshot_readbacks"]) == 6 + assert data["rollups"]["monitor_check_count"] == len(data["monitor_checks"]) == 7 + assert data["rollups"]["drift_candidate_count"] == len(data["drift_candidates"]) == 9 + assert data["rollups"]["action_required_candidate_count"] == 9 + assert data["rollups"]["blocked_operation_count"] == 20 + assert data["monitor_boundaries"]["external_cve_lookup_allowed"] is False + assert data["monitor_boundaries"]["package_upgrade_allowed"] is False + assert data["monitor_boundaries"]["lockfile_write_allowed"] is False + assert data["monitor_boundaries"]["docker_build_allowed"] is False + assert data["monitor_boundaries"]["telegram_send_allowed"] is False + assert data["monitor_boundaries"]["production_write_allowed"] is False + assert data["telegram_policy"]["direct_send_allowed"] is False + assert any( + candidate["candidate_id"] == "api_python_manifest_drift" + for candidate in data["drift_candidates"] + ) + assert any( + candidate["candidate_id"] == "agent_market_source_lookup_not_approved" + for candidate in data["drift_candidates"] + ) diff --git a/apps/web/messages/en.json b/apps/web/messages/en.json index 0e4c7eb04..da01f6880 100644 --- a/apps/web/messages/en.json +++ b/apps/web/messages/en.json @@ -3587,6 +3587,44 @@ } } }, + "supplyChainDrift": { + "title": "P2-004 依賴 / 供應鏈漂移監控", + "subtitle": "{current} → {next};漂移候選 {candidates};阻擋操作 {blocked}。只讀、不外查、不升級。", + "source": "{generated} · {current} → {next}", + "badges": { + "readOnly": "repo-only 監控", + "telegram": "Telegram 實發 {value}", + "production": "Production write {value}" + }, + "metrics": { + "sources": "來源快照", + "stale": "過期來源", + "checks": "監控檢查", + "candidates": "漂移候選", + "ownerActions": "Owner actions", + "blocked": "禁止操作" + }, + "sections": { + "candidates": "優先漂移候選", + "boundaries": "仍被關閉的操作" + }, + "labels": { + "owner": "Owner", + "status": "狀態", + "evidence": "證據", + "nextAction": "下一步", + "summary": "摘要", + "boundaryClosed": "保持 false", + "boundaryOpen": "異常開啟" + }, + "boundaries": { + "external": "外部 CVE / registry", + "upgrade": "套件升級 / lockfile", + "docker": "Docker build / image", + "telegram": "Telegram / Gateway", + "production": "Production write" + } + }, "overview": { "title": "決策指揮摘要", "mode": "只讀決策支援", diff --git a/apps/web/messages/zh-TW.json b/apps/web/messages/zh-TW.json index 0e4c7eb04..da01f6880 100644 --- a/apps/web/messages/zh-TW.json +++ b/apps/web/messages/zh-TW.json @@ -3587,6 +3587,44 @@ } } }, + "supplyChainDrift": { + "title": "P2-004 依賴 / 供應鏈漂移監控", + "subtitle": "{current} → {next};漂移候選 {candidates};阻擋操作 {blocked}。只讀、不外查、不升級。", + "source": "{generated} · {current} → {next}", + "badges": { + "readOnly": "repo-only 監控", + "telegram": "Telegram 實發 {value}", + "production": "Production write {value}" + }, + "metrics": { + "sources": "來源快照", + "stale": "過期來源", + "checks": "監控檢查", + "candidates": "漂移候選", + "ownerActions": "Owner actions", + "blocked": "禁止操作" + }, + "sections": { + "candidates": "優先漂移候選", + "boundaries": "仍被關閉的操作" + }, + "labels": { + "owner": "Owner", + "status": "狀態", + "evidence": "證據", + "nextAction": "下一步", + "summary": "摘要", + "boundaryClosed": "保持 false", + "boundaryOpen": "異常開啟" + }, + "boundaries": { + "external": "外部 CVE / registry", + "upgrade": "套件升級 / lockfile", + "docker": "Docker build / image", + "telegram": "Telegram / Gateway", + "production": "Production write" + } + }, "overview": { "title": "決策指揮摘要", "mode": "只讀決策支援", diff --git a/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx b/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx index 03fe2306c..ec00e78b8 100644 --- a/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx +++ b/apps/web/src/app/[locale]/governance/tabs/automation-inventory-tab.tsx @@ -115,6 +115,7 @@ import { type BackupDrReadinessMatrixSnapshot, type BackupDrTargetInventorySnapshot, type BackupNotificationPolicySnapshot, + type DependencySupplyChainDriftMonitorSnapshot, type GiteaWorkflowRunnerHealthSnapshot, type ObservabilityContractMatrixSnapshot, type OffsiteEscrowReadinessStatusSnapshot, @@ -630,6 +631,7 @@ export function AutomationInventoryTab() { const [reportTruthActionabilityReview, setReportTruthActionabilityReview] = useState(null) const [ownerDryRunPackage, setOwnerDryRunPackage] = useState(null) const [hostStatefulInventory, setHostStatefulInventory] = useState(null) + const [dependencySupplyChainDriftMonitor, setDependencySupplyChainDriftMonitor] = useState(null) const [serviceHealthGapMatrix, setServiceHealthGapMatrix] = useState(null) const [serviceHealthNotificationPolicy, setServiceHealthNotificationPolicy] = useState(null) const [loading, setLoading] = useState(true) @@ -713,6 +715,7 @@ export function AutomationInventoryTab() { apiClient.getAiAgentReportTruthActionabilityReview(), apiClient.getAiAgentOwnerApprovedFixtureDryRun(), apiClient.getAiAgentHostStatefulVersionInventory(), + apiClient.getDependencySupplyChainDriftMonitor(), apiClient.getServiceHealthGapMatrix(), apiClient.getServiceHealthFailureNotificationPolicy(), ] as const @@ -795,6 +798,7 @@ export function AutomationInventoryTab() { reportTruthActionabilityReviewResult, ownerDryRunPackageResult, hostStatefulInventoryResult, + dependencySupplyChainDriftMonitorResult, serviceHealthGapMatrixResult, serviceHealthNotificationPolicyResult, ] = results @@ -874,6 +878,7 @@ export function AutomationInventoryTab() { setReportTruthActionabilityReview(reportTruthActionabilityReviewResult.status === 'fulfilled' ? reportTruthActionabilityReviewResult.value : null) setOwnerDryRunPackage(ownerDryRunPackageResult.status === 'fulfilled' ? ownerDryRunPackageResult.value : null) setHostStatefulInventory(hostStatefulInventoryResult.status === 'fulfilled' ? hostStatefulInventoryResult.value : null) + setDependencySupplyChainDriftMonitor(dependencySupplyChainDriftMonitorResult.status === 'fulfilled' ? dependencySupplyChainDriftMonitorResult.value : null) setServiceHealthGapMatrix(serviceHealthGapMatrixResult.status === 'fulfilled' ? serviceHealthGapMatrixResult.value : null) setServiceHealthNotificationPolicy(serviceHealthNotificationPolicyResult.status === 'fulfilled' ? serviceHealthNotificationPolicyResult.value : null) setError([ @@ -951,6 +956,7 @@ export function AutomationInventoryTab() { reportTruthActionabilityReviewResult, ownerDryRunPackageResult, hostStatefulInventoryResult, + dependencySupplyChainDriftMonitorResult, serviceHealthGapMatrixResult, serviceHealthNotificationPolicyResult, ].some(result => result.status === 'rejected')) @@ -2190,6 +2196,23 @@ export function AutomationInventoryTab() { })) }, [visibleServiceHealthTargets]) + const visibleSupplyChainDriftCandidates = useMemo(() => { + if (!dependencySupplyChainDriftMonitor) return [] + const severityPriority = { critical: 0, high: 1, medium: 2, low: 3 } as Record + const statusPriority = { blocked_until_approval: 0, action_required: 1 } as Record + return [...dependencySupplyChainDriftMonitor.drift_candidates] + .sort((a, b) => { + const leftStatus = statusPriority[a.status] ?? 2 + const rightStatus = statusPriority[b.status] ?? 2 + if (leftStatus !== rightStatus) return leftStatus - rightStatus + const leftSeverity = severityPriority[a.severity] ?? 4 + const rightSeverity = severityPriority[b.severity] ?? 4 + if (leftSeverity !== rightSeverity) return leftSeverity - rightSeverity + return a.candidate_id.localeCompare(b.candidate_id) + }) + .slice(0, 5) + }, [dependencySupplyChainDriftMonitor]) + if (loading) { return (
@@ -2203,7 +2226,7 @@ export function AutomationInventoryTab() { ) } - if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !warRoom || !professionalTaskExpansion || !proactiveOperations || !interactionLearningProof || !liveReadModelGate || !redisDryRunGate || !learningWritebackPackage || !telegramReceiptPackage || !ownerApprovedLearningDryRun || !runtimeWriteGateReview || !postWriteVerifierPackage || !runtimeVerifierEvidenceReview || !reportAutomationReview || !reportStatusBoard || !reportRuntimeReadiness || !reportRuntimeDryRun || !reportRuntimeFixtureReadback || !runtimeWorkerShadowGate || !operationPermissionModel || !candidateOperationDryRunEvidence || !taskResultAuditTrail || !matchedPlaybookLearningGap || !criticReviewerResultCapture || !ownerApprovedResultCaptureDryRun || !ownerApprovedResultCaptureReadback || !runtimeReadbackApprovalPackage || !runtimeReadbackImplementationReview || !reportLiveDeliveryApprovalPackage || !runtimeReadbackFixtureApproval || !runtimeReadbackPromotionGate || !ownerApprovedFixturePromotionGate || !canonicalRuntimeReadbackOwnerAcceptance || !failureReceiptNoSendReplay || !reviewerQueueNoWriteReadback || !resultCaptureNoWriteReadback || !resultCapturePromotionApprovalGate || !ownerApprovedResultCapturePromotionDryRun || !resultCaptureWriteGateReview || !resultCaptureWriterImplementationReview || !resultCaptureWriterDryRunFixture || !resultCaptureWriterDryRunReadback || !resultCaptureOwnerPromotionReview || !resultCaptureOwnerApprovedExecutionRehearsal || !resultCaptureOwnerAcceptanceMaintenanceGate || !resultCaptureOwnerAcceptanceReadbackPreflightHold || !resultCaptureOwnerApprovedPreflightReleasePackage || !resultCaptureOwnerApprovedReleaseReadinessReadback || !resultCaptureOwnerReleaseApprovalGate || !resultCapturePostReleaseVerifierRollbackGate || !resultCaptureFinalReleaseCandidateReadback || !resultCaptureReleaseAuthorizationHold || !resultCaptureReleaseAuthorizationReadbackGate || !resultCaptureReleaseVerifierPreflightGate || !resultCaptureReleaseVerifierOwnerReviewPacket || !resultCaptureReleaseDecisionHold || !resultCaptureReleaseDecisionReadback || !resultCaptureReleaseDecisionNextHandoff || !resultCaptureReleaseDecisionInputPrep || !resultCaptureReleaseDecisionOwnerResponsePreflight || !resultCaptureReleaseDecisionOwnerResponseReadback || !resultCaptureReleaseDecisionOwnerResponseAcceptanceGate || !reportTruthActionabilityReview || !ownerDryRunPackage || !hostStatefulInventory || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) { + if (error || !snapshot || !backlog || !backupTargets || !backupReadiness || !backupPolicy || !offsiteEscrow || !giteaHealth || !observabilityMatrix || !providerRouteMatrix || !deploymentLayout || !warRoom || !professionalTaskExpansion || !proactiveOperations || !interactionLearningProof || !liveReadModelGate || !redisDryRunGate || !learningWritebackPackage || !telegramReceiptPackage || !ownerApprovedLearningDryRun || !runtimeWriteGateReview || !postWriteVerifierPackage || !runtimeVerifierEvidenceReview || !reportAutomationReview || !reportStatusBoard || !reportRuntimeReadiness || !reportRuntimeDryRun || !reportRuntimeFixtureReadback || !runtimeWorkerShadowGate || !operationPermissionModel || !candidateOperationDryRunEvidence || !taskResultAuditTrail || !matchedPlaybookLearningGap || !criticReviewerResultCapture || !ownerApprovedResultCaptureDryRun || !ownerApprovedResultCaptureReadback || !runtimeReadbackApprovalPackage || !runtimeReadbackImplementationReview || !reportLiveDeliveryApprovalPackage || !runtimeReadbackFixtureApproval || !runtimeReadbackPromotionGate || !ownerApprovedFixturePromotionGate || !canonicalRuntimeReadbackOwnerAcceptance || !failureReceiptNoSendReplay || !reviewerQueueNoWriteReadback || !resultCaptureNoWriteReadback || !resultCapturePromotionApprovalGate || !ownerApprovedResultCapturePromotionDryRun || !resultCaptureWriteGateReview || !resultCaptureWriterImplementationReview || !resultCaptureWriterDryRunFixture || !resultCaptureWriterDryRunReadback || !resultCaptureOwnerPromotionReview || !resultCaptureOwnerApprovedExecutionRehearsal || !resultCaptureOwnerAcceptanceMaintenanceGate || !resultCaptureOwnerAcceptanceReadbackPreflightHold || !resultCaptureOwnerApprovedPreflightReleasePackage || !resultCaptureOwnerApprovedReleaseReadinessReadback || !resultCaptureOwnerReleaseApprovalGate || !resultCapturePostReleaseVerifierRollbackGate || !resultCaptureFinalReleaseCandidateReadback || !resultCaptureReleaseAuthorizationHold || !resultCaptureReleaseAuthorizationReadbackGate || !resultCaptureReleaseVerifierPreflightGate || !resultCaptureReleaseVerifierOwnerReviewPacket || !resultCaptureReleaseDecisionHold || !resultCaptureReleaseDecisionReadback || !resultCaptureReleaseDecisionNextHandoff || !resultCaptureReleaseDecisionInputPrep || !resultCaptureReleaseDecisionOwnerResponsePreflight || !resultCaptureReleaseDecisionOwnerResponseReadback || !resultCaptureReleaseDecisionOwnerResponseAcceptanceGate || !reportTruthActionabilityReview || !ownerDryRunPackage || !hostStatefulInventory || !dependencySupplyChainDriftMonitor || !serviceHealthGapMatrix || !serviceHealthNotificationPolicy) { return (
@@ -2363,6 +2386,36 @@ export function AutomationInventoryTab() { const reportStatusWaitingApproval = reportStatusBoard.rollups.workload_waiting_approval_total const reportStatusLiveDelivery = reportStatusBoard.rollups.live_delivery_count const reportStatusLiveOptimization = reportStatusBoard.rollups.live_auto_optimization_count + const supplyChainDriftBoundaryRows = [ + { + key: 'external', + label: t('supplyChainDrift.boundaries.external'), + active: dependencySupplyChainDriftMonitor.monitor_boundaries.external_cve_lookup_allowed + || dependencySupplyChainDriftMonitor.monitor_boundaries.registry_lookup_allowed, + }, + { + key: 'upgrade', + label: t('supplyChainDrift.boundaries.upgrade'), + active: dependencySupplyChainDriftMonitor.monitor_boundaries.package_upgrade_allowed + || dependencySupplyChainDriftMonitor.monitor_boundaries.lockfile_write_allowed, + }, + { + key: 'docker', + label: t('supplyChainDrift.boundaries.docker'), + active: dependencySupplyChainDriftMonitor.monitor_boundaries.docker_build_allowed + || dependencySupplyChainDriftMonitor.monitor_boundaries.image_pull_allowed, + }, + { + key: 'telegram', + label: t('supplyChainDrift.boundaries.telegram'), + active: dependencySupplyChainDriftMonitor.monitor_boundaries.telegram_send_allowed, + }, + { + key: 'production', + label: t('supplyChainDrift.boundaries.production'), + active: dependencySupplyChainDriftMonitor.monitor_boundaries.production_write_allowed, + }, + ] const reportRuntimeOverall = reportRuntimeReadiness.program_status.overall_completion_percent const reportRuntimeLanes = reportRuntimeReadiness.rollups.runtime_lane_count const reportRuntimeReady = reportRuntimeReadiness.rollups.ready_contract_count @@ -4347,6 +4400,118 @@ export function AutomationInventoryTab() {
+ +
+
+
+
+ +
+
+ + {t('supplyChainDrift.title')} + + + {t('supplyChainDrift.subtitle', { + current: dependencySupplyChainDriftMonitor.program_status.current_task_id, + next: dependencySupplyChainDriftMonitor.program_status.next_task_id, + candidates: dependencySupplyChainDriftMonitor.rollups.drift_candidate_count, + blocked: dependencySupplyChainDriftMonitor.rollups.blocked_operation_count, + })} + +
+
+
+ + + +
+
+ +
+ {[ + { label: t('supplyChainDrift.metrics.sources'), value: dependencySupplyChainDriftMonitor.rollups.source_snapshot_count, tone: 'ok' as const, icon: }, + { label: t('supplyChainDrift.metrics.stale'), value: dependencySupplyChainDriftMonitor.rollups.stale_source_snapshot_count, tone: 'warn' as const, icon: }, + { label: t('supplyChainDrift.metrics.checks'), value: dependencySupplyChainDriftMonitor.rollups.monitor_check_count, tone: 'ok' as const, icon: }, + { label: t('supplyChainDrift.metrics.candidates'), value: dependencySupplyChainDriftMonitor.rollups.drift_candidate_count, tone: 'warn' as const, icon: }, + { label: t('supplyChainDrift.metrics.ownerActions'), value: dependencySupplyChainDriftMonitor.rollups.owner_action_count, tone: 'warn' as const, icon: }, + { label: t('supplyChainDrift.metrics.blocked'), value: dependencySupplyChainDriftMonitor.rollups.blocked_operation_count, tone: 'ok' as const, icon: }, + ].map(metric => { + const color = toneColor(metric.tone) + return ( +
+
+
{metric.icon}
+ {metric.label} +
+
+ {metric.value} +
+
+ ) + })} +
+ +
+
+ {t('supplyChainDrift.sections.candidates')} + {visibleSupplyChainDriftCandidates.map(candidate => ( +
+
+ + {candidate.candidate_id} + + +
+
+ {redactPublicText(candidate.summary)} +
+
+ + + + +
+
+ ))} +
+ +
+ {t('supplyChainDrift.sections.boundaries')} +
+ {supplyChainDriftBoundaryRows.map(row => ( + + ))} +
+
+ +
+
+
+
+
+
diff --git a/apps/web/src/lib/api-client.ts b/apps/web/src/lib/api-client.ts index ec13de681..e98c92892 100644 --- a/apps/web/src/lib/api-client.ts +++ b/apps/web/src/lib/api-client.ts @@ -649,6 +649,11 @@ export const apiClient = { return handleResponse(res) }, + async getDependencySupplyChainDriftMonitor() { + const res = await fetch(`${API_BASE_URL}/agents/dependency-supply-chain-drift-monitor`) + return handleResponse(res) + }, + async getRuntimeSurfaceInventory() { const res = await fetch(`${API_BASE_URL}/agents/runtime-surface-inventory`) return handleResponse(res) @@ -11008,6 +11013,91 @@ export interface AiAgentHostStatefulVersionInventorySnapshot { }> } +export interface DependencySupplyChainDriftMonitorSnapshot { + schema_version: 'dependency_supply_chain_drift_monitor_v1' + generated_at: string + program_status: { + overall_completion_percent: number + current_priority: 'P0' | 'P1' | 'P2' | 'P3' + current_task_id: string + next_task_id: string + read_only_mode: true + runtime_authority: string + status_note: string + } + source_refs: string[] + rollups: { + source_snapshot_count: number + stale_source_snapshot_count: number + monitor_check_count: number + drift_candidate_count: number + action_required_candidate_count: number + owner_action_count: number + blocked_operation_count: number + by_domain: Record + action_required_drift_candidate_ids: string[] + } + source_snapshot_readbacks: Array<{ + snapshot_id: string + source_ref: string + source_schema_version: string + generated_at: string + freshness_status: string + evidence_status: string + next_action: string + }> + monitor_checks: Array<{ + check_id: string + domain: string + owner_agent: 'openclaw' | 'hermes' | 'nemotron' + status: string + current_signal: string + evidence_refs: string[] + blocked_now: string[] + next_action: string + }> + drift_candidates: Array<{ + candidate_id: string + domain: string + severity: 'low' | 'medium' | 'high' | 'critical' + status: string + owner_agent: 'openclaw' | 'hermes' | 'nemotron' + summary: string + evidence_refs: string[] + next_owner_action: string + }> + owner_actions: Array<{ + action_id: string + priority: 'P0' | 'P1' | 'P2' | 'P3' + owner_agent: 'openclaw' | 'hermes' | 'nemotron' + approval_gate: string + summary: string + blocked_until: string + }> + telegram_policy: { + status: string + direct_send_allowed: false + gateway_queue_write_allowed: false + success_quiet: boolean + failure_digest_after_approval: boolean + draft_only_outputs: string[] + } + agent_roles: Array<{ + agent_id: 'openclaw' | 'hermes' | 'nemotron' + role: string + autonomy_level: string + approval_gate: string + outputs: string[] + }> + next_actions: Array<{ + task_id: string + priority: 'P0' | 'P1' | 'P2' | 'P3' + summary: string + gate: string + }> + monitor_boundaries: Record +} + export interface RuntimeSurfaceInventorySnapshot { schema_version: 'runtime_surface_inventory_v1' generated_at: string diff --git a/docs/LOGBOOK.md b/docs/LOGBOOK.md index 4a094e9d0..d24b73da9 100644 --- a/docs/LOGBOOK.md +++ b/docs/LOGBOOK.md @@ -45,6 +45,41 @@ **邊界**:本輪只更新 SOP / workplan / LOGBOOK。未 SSH、未重啟主機、未改 Docker / systemd / Nginx / firewall / K8s / ArgoCD、未 workflow dispatch、未讀或保存 secret、未 active scan、未送 Telegram、未改 runtime gate。下一次真正重啟前仍必須重新跑 same-day live preflight,再決定 Plan A 或 Plan B 入口。 +## 2026-06-18|P2-004 依賴 / 供應鏈漂移監控讀回完成 + +**背景**:統帥要求每次推進都不能忘記 AI Agent 自主化目標與方向;本輪延續立即順序的 `P2-004`,把既有 Python / JavaScript / Docker / dependency policy / version freshness 基線收斂成可由 API 讀回、可測試、可擋越權旗標的依賴與供應鏈漂移監控,而不是直接啟用外部掃描或自動升級。 + +**完成內容**: +- 新增 `docs/schemas/dependency_supply_chain_drift_monitor_v1.schema.json`。 +- 新增 `docs/evaluations/dependency_supply_chain_drift_monitor_2026-06-18.json`,current `P2-004`、next `P2-406B`、overall `100%`。 +- 新增 `apps/api/src/services/dependency_supply_chain_drift_monitor.py` 與 `GET /api/v1/agents/dependency-supply-chain-drift-monitor`。 +- 新增 service / API tests,強制檢查 read-only、外部 CVE lookup、Telegram send、package upgrade、lockfile write、Docker build、production write 等邊界。 +- `/zh-TW/governance?tab=automation-inventory` 新增 P2-004 依賴 / 供應鏈漂移監控卡片,顯示 source snapshot、stale source、monitor check、drift candidate、owner action、blocked operation、優先漂移候選與仍為 false 的邊界。 +- Drift candidates `9`:Python manifest drift、Python lockfile absence、Web caret range、shared-types publish boundary、Docker digest gap、kubectl checksum gap、build-time network fetch、external source approval gap、Agent market source approval gap。 +- Agent 分工固定:OpenClaw 仲裁風險與 Owner Gate,Hermes 彙整 repo-only 證據與報表 / digest 草稿,NemoTron 做離線長任務比對與 Agent market / replay scorecard 草案,不取代 OpenClaw、不接 production route。 + +**完成度同步**: +- P2-004 依賴 / 供應鏈漂移監控讀回:`100%`。 +- 工具 / 服務 / 套件 AI 自動化:`92% -> 94%`。 +- AgentOps 治理與可觀測基礎:仍約 `68%`;本輪只補 supply-chain monitor,不代表 runtime learning / Telegram 實發已完成。 +- OpenClaw / Hermes / NemoTron 佈建布局:仍約 `45%`;本輪只新增供應鏈分工讀回。 +- Agent 主動溝通 / 學習 runtime:仍約 `35%`。 +- 日週月報可視化:`100%`;Telegram live send 仍 `0%`。 + +**驗證**: +- `python3 -m json.tool docs/schemas/dependency_supply_chain_drift_monitor_v1.schema.json` 通過。 +- `python3 -m json.tool docs/evaluations/dependency_supply_chain_drift_monitor_2026-06-18.json` 通過。 +- `python3 -m py_compile src/services/dependency_supply_chain_drift_monitor.py` 通過。 +- `DATABASE_URL=postgresql://test:test@localhost:5432/test pytest tests/test_dependency_supply_chain_drift_monitor.py tests/test_dependency_supply_chain_drift_monitor_api.py`:`8 passed`。 +- `python3 -m json.tool apps/web/messages/zh-TW.json` 與 `apps/web/messages/en.json` 通過。 +- i18n leaf parity:`zh=12402`、`en=12402`、`onlyZh=0`、`onlyEn=0`。 +- `python3 scripts/security/source-control-owner-response-guard.py --root .`:`SOURCE_CONTROL_OWNER_RESPONSE_GUARD_OK`。 +- `python3 scripts/security/security-mirror-progress-guard.py --root .`:`SECURITY_MIRROR_PROGRESS_GUARD_OK`。 +- `git diff --check` 通過。 +- `pnpm --filter @awoooi/web typecheck` 本地未能執行:乾淨 worktree 缺 `node_modules`,`tsc: command not found`;本輪依 P2-004 邊界未執行 `pnpm install`、未寫 lockfile、未安裝套件。 + +**邊界**:本輪未啟用排程、未寫 workflow、未查外部 CVE / license / registry / Agent market、未安裝或升級套件、未寫 lockfile、未執行 `npm audit`、未執行 `docker build`、未 pull image、未 push registry、未建立 PR、未送 Telegram、未寫 Gateway queue、未呼叫 Bot API、未讀 secret、未 host probe、未 production write、未替換 OpenClaw。下一步仍是 `P2-406B`,把日週月報 / Telegram receipt owner review 與此漂移監控串接成只讀審核視圖。 + ## 2026-06-18|AI Agent 自動化完整工作清單與主流差距重盤點 **背景**:統帥要求把前面所有關於 OpenClaw、Hermes、NemoTron、12-Agent War Room、Telegram Bot、日報 / 週報 / 月報、MCP、RAG、版本更新、工具 / 套件 / 服務 / 主機 / 專案 / 網站前後台自動化的工作項目全部細化列出,並繼續推進。由於 `docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md` 禁止另開孤島 MD,本輪把完整清單收斂到既有 `docs/ai/AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md`,並同步 MASTER §8。 diff --git a/docs/ai/AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md b/docs/ai/AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md index 875265ff1..aa681848a 100644 --- a/docs/ai/AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md +++ b/docs/ai/AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md @@ -148,7 +148,7 @@ |---|---:|---|---| | Agent 市場治理 | 72% | 進行中 | `agent_market_governance_snapshot_v1`、API、UI 分頁、每週觀察流程 | | Nemotron 實際整合應用 | 30% | 完整回放前仍被關卡擋下 | `blocked_needs_evidence`,下一關是 `refresh_source_evidence_then_5_record_smoke_only` | -| 工具 / 服務 / 套件 AI 自動化 | 92% | P0 已完成;P1 服務 / runtime / 監控 / provider / service health / 備份 / DR / 套件與供應鏈只讀基線已完成;P1-007 失敗限定通知合約與前端 redaction 合約已完成;下一主線是 P2-004 依賴 / 供應鏈漂移監控 | 狀態分類、盤點 schema、權限矩陣、靜態盤點種子、只讀 API、UI 骨架、驗證、自動化待辦 schema / 快照 / API / 分組 UI、Backup / DR 目標盤點、準備度矩陣、備份通知政策、Backup / DR 證據 UI、復原演練批准包模板、異地 / escrow 準備度狀態、任務批准邊界、確定性進度彙總、Python 套件 / 供應鏈只讀基線、JS pnpm/npm 只讀基線、Docker build surface 只讀基線、CVE / license / drift 嚴重度政策、定期依賴漂移與外部資料來源檢查設計、依賴升級批准包模板、runtime_surface_inventory_v1 schema / snapshot / API / UI、gitea_workflow_runner_health_v1 schema / snapshot / API / UI、observability_contract_matrix_v1 schema / snapshot / API / UI、ai_provider_route_matrix_v1 schema / snapshot / API / UI、service_health_gap_matrix_v1 schema / snapshot / API / UI、service health evidence cards UI、service_health_failure_notification_policy_v1 schema / snapshot / API / UI 已完成 | +| 工具 / 服務 / 套件 AI 自動化 | 94% | P2-004 依賴 / 供應鏈漂移監控讀回已完成;下一主線是 P2-406B,把 drift monitor 與日週月報 / Telegram receipt owner review 串接成只讀審核視圖 | 新增 `dependency_supply_chain_drift_monitor_v1` schema / snapshot / API / tests / governance UI 卡片;9 個 drift candidate、7 個 monitor check、9 個 owner action、20 個 blocked operation;仍不得外部 CVE / license / registry / Agent market lookup、不得升級、不得寫 lockfile、不得 Docker build、不得 Telegram 實發 | | OpenClaw / Hermes / NemoTron 佈建布局 | 45% | P1-401 / P1-402 已完成;仍是只讀 layout 與治理頁顯示,不是 runtime deploy | `ai_agent_deployment_layout_v1` schema、`ai_agent_deployment_layout_2026-06-11.json`、`GET /api/v1/agents/agent-deployment-layout`、治理頁自動化盤點 UI、`AI_AGENT_DEPLOYMENT_LAYOUT_2026-06-11.md` | | OpenClaw / Hermes / NemoTron 主動溝通、學習與成長證據 | 100% | P2-401A 到 P2-144 已完成只讀證據面、runtime / report / result-capture gates、no-write readback、promotion review、writer implementation review、writer dry-run fixture、writer dry-run readback、owner promotion execution gate、owner-approved execution rehearsal、owner acceptance / maintenance window gate、owner acceptance readback / preflight hold、owner-approved preflight release package、owner-approved release readiness readback、owner release approval gate、post-release verifier / rollback gate、final release candidate readback、release authorization hold / readback gate、release verifier preflight / owner review packet、release decision hold / readback、release decision next handoff、release decision input prep、12-Agent War Room、owner response 預檢與 owner response 回讀;P2-141 基線與 S4.9 owner release packet 補強皆已正式驗證,P2-142 12-Agent War Room 已完成 production readback 與 desktop / mobile smoke,P2-143 owner response 預檢已完成 production readback 與 in-app browser smoke,P2-144 owner response 回讀已完成 production API readback 與 desktop / mobile smoke。runtime worker、DB migration、production Redis consumer group、canonical runtime readback、live query、runtime score、result capture write、Telegram 實發、delivery receipt E2E、live report delivery、reviewer queue write、Gateway queue write、AI analysis runtime、中低風險 auto worker、KM / LOGBOOK / audit DB / timeline / PlayBook trust 寫入、SDK / 付費服務仍未開 gate | `ai_agent_result_capture_release_decision_owner_response_readback_v1`、`GET /api/v1/agents/agent-result-capture-release-decision-owner-response-readback`、`docs/evaluations/ai_agent_result_capture_release_decision_owner_response_readback_2026-06-14.json`、feature commit `8795f100`、deploy marker `ac938037`、Gitea code-review `2965` / CD `2964` success、5 個回覆讀回 lane、18 個 owner 必填欄位、6 個 readback validation check、6 個 rejection guard、5 個 operator action、等待外部回覆 `5`、未收件 lane `5`、正式寫入 / 發送 `0`;P2-142 feature commit `5de4b3f3`、deploy marker `1a2c9e36`、Gitea CD run `4232` success、production API readback、desktop / mobile in-app browser smoke;P2-143 feature commit `755b0a8d`、deploy marker `667d6329`、Gitea code-review `2961` / CD `2960` success、production API readback、desktop / mobile in-app browser smoke;MASTER §3.2.1b / §3.2.1d / §3.4.3 | | AI Agent 主動營運委派與版本生命週期 | 100% | P2-402A / P2-402B / P2-402C / P2-402D / P2-402E / P2-402F / P2-402G 已完成;已建立 repo-only 版本新鮮度快照、工具採用批准包、Telegram action-required digest policy、Gitea PR 草案 lane、host / K3s / stateful 版本只讀盤點、API 與 governance UI。定期排程、外部版本查詢、工具安裝、CI 變更、套件升級、主機更新、container pull、實際 PR creation、auto merge、Telegram 實發、SSH、kubectl、重啟仍未開 gate | `ai_agent_proactive_operations_contract_v1`、`ai_agent_version_freshness_snapshot_v1`、`ai_agent_tool_adoption_approval_package_v1`、`ai_agent_telegram_action_required_digest_policy_v1`、`ai_agent_gitea_pr_draft_lane_v1`、`ai_agent_host_stateful_version_inventory_v1`、`GET /api/v1/agents/agent-proactive-operations-contract`、`GET /api/v1/agents/agent-version-freshness-snapshot`、`GET /api/v1/agents/agent-tool-adoption-approval-package`、`GET /api/v1/agents/agent-telegram-action-required-digest-policy`、`GET /api/v1/agents/agent-gitea-pr-draft-lane`、`GET /api/v1/agents/agent-host-stateful-version-inventory`、`/zh-TW/governance?tab=automation-inventory`、MASTER §3.2.1c | @@ -1038,6 +1038,65 @@ API: - Dependency upgrade approval package template service + API tests `9 passed`。 - `py_compile` 通過。 +### P2-004 依賴 / 供應鏈漂移監控讀回摘要 + +正式 JSON Schema: + +- `docs/schemas/dependency_supply_chain_drift_monitor_v1.schema.json` + +正式 JSON Snapshot: + +- `docs/evaluations/dependency_supply_chain_drift_monitor_2026-06-18.json` + +API: + +- `GET /api/v1/agents/dependency-supply-chain-drift-monitor` + +Governance UI: + +- `/zh-TW/governance?tab=automation-inventory` +- 新增 P2-004 依賴 / 供應鏈漂移監控卡片,顯示 6 個來源快照、5 個過期來源、7 個監控檢查、9 個漂移候選、9 個 owner action、20 個仍為 false 的禁止操作、優先漂移候選與邊界矩陣。 + +監控內容: + +- Source snapshot readback:`6` +- Stale source snapshot:`5` +- Monitor checks:`7` +- Drift candidates:`9` +- Action-required / blocked candidates:`9` +- Owner actions:`9` +- Blocked operations:`20` + +核心裁決: + +- P2-004 是 repo-only 依賴 / 供應鏈漂移監控讀回,不是外部 CVE / registry scan,也不是自動升級器。 +- OpenClaw 負責 high / critical supply-chain drift、費用與 HITL gate 仲裁。 +- Hermes 負責 manifest / lockfile / Dockerfile / committed snapshot 證據與報表 / Telegram digest 草稿。 +- NemoTron 負責離線長任務比對、Agent market freshness 草案與 replay scorecard 草案;不得替換 OpenClaw 或接 production route。 +- 下一步是把 P2-004 drift monitor 接到 P2-406B / P2-407 的日週月報、Telegram receipt owner review 與 AI 報表分析 no-write runtime。 + +實作邊界: + +- 不啟用排程。 +- 不寫 workflow。 +- 不查外部 CVE / advisory / license / registry / Agent market。 +- 不安裝或升級套件。 +- 不寫 lockfile。 +- 不執行 `npm audit`。 +- 不執行 `docker build`、不 pull image、不 rebuild image、不 push registry。 +- 不建立 PR。 +- 不送 Telegram、不寫 Gateway queue、不呼叫 Bot API。 +- 不讀 secret、不 host probe、不 production write。 +- 不替換 OpenClaw。 + +驗證: + +- P2-004 JSON schema / snapshot parse 通過。 +- Dependency supply-chain drift monitor service + API tests `8 passed`。 +- `py_compile` 通過。 +- zh-TW / en i18n JSON parse 與 key parity 通過。 +- Web typecheck 因乾淨 worktree 缺 `node_modules` 而未執行;本輪依邊界未安裝套件或寫 lockfile。 + ### P1-103 備份通知政策摘要 正式 JSON Schema: @@ -1307,7 +1366,7 @@ UI: | P2-001 | 待辦 | 0 | OpenClaw | K8s requests / limits 建議引擎 | 只讀建議快照 | 不 apply | | P2-002 | 待辦 | 0 | Hermes | CronJob 排程碰撞分析 | 排程優化報告 | 不改排程 | | P2-003 | 待辦 | 0 | Hermes | Prometheus 告警噪音調整提案 | 告警規則建議 | 人工批准 | -| P2-004 | 待辦 | 0 | OpenClaw | AI Router / provider 成本與 fallback 優化 | 模型路由建議 | 費用批准 | +| P2-004 | 完成 | 100 | OpenClaw + Hermes + NemoTron | 依賴 / 供應鏈漂移監控讀回 | `dependency_supply_chain_drift_monitor_v1` / snapshot / 只讀 API / tests;9 個 drift candidate、7 個 monitor check、9 個 owner action、20 個 blocked operation | 只讀;不得外部 CVE / license / registry / Agent market lookup、不得升級、不得寫 lockfile、不得 Docker build、不得 Telegram 實發、不得 production write | | P2-005 | 待辦 | 0 | Nemotron | 針對回放 fixture 做離線模型 / 提示詞 比較 | 模型評分報告 | 未批准不得外部呼叫 | | P2-006 | 待辦 | 0 | Hermes | 前端 bundle / route 健康建議 | Web 優化報告 | 不做無關 redesign | @@ -1589,9 +1648,9 @@ UI: ## 13. 立即執行順序 -1. P2-004:依賴 / 供應鏈漂移監控,保持只讀觀察與批准包邊界。 -2. P2-406B:AI Agent Telegram receipt readback owner review / production verification,保持只讀;目標是確認日報 / 週報 / 月報與 Telegram canary receipt readback gate 在正式 API / governance UI 可讀,且 Telegram send、Gateway queue、Bot API、receipt production write 全部仍為 `0`。只有收到合格 owner review、receipt readback owner、mute / rollback plan、停止條件與可驗證證據後,才可進一步評估 canary live delivery;未批准前仍不得實發。 -3. P2-407:AI 報表自動分析 no-write runtime;讓 OpenClaw / Hermes / NemoTron 讀取日報 / 週報 / 月報後產生建議與風險分級,但只寫 committed snapshot / 草稿,不寫 production。 +1. P2-004:依賴 / 供應鏈漂移監控讀回已完成;維持只讀觀察與批准包邊界,下一輪不得把完成誤讀成可外部掃描或自動升級。 +2. P2-406B:AI Agent Telegram receipt readback owner review / production verification,保持只讀;目標是確認日報 / 週報 / 月報、P2-004 drift monitor 與 Telegram canary receipt readback gate 在正式 API / governance UI 可讀,且 Telegram send、Gateway queue、Bot API、receipt production write 全部仍為 `0`。只有收到合格 owner review、receipt readback owner、mute / rollback plan、停止條件與可驗證證據後,才可進一步評估 canary live delivery;未批准前仍不得實發。 +3. P2-407:AI 報表自動分析 no-write runtime;讓 OpenClaw / Hermes / NemoTron 讀取日報 / 週報 / 月報與 P2-004 drift monitor 後產生建議與風險分級,但只寫 committed snapshot / 草稿,不寫 production。 4. P2-408:中 / 低風險自動處理白名單與高風險審核分流;低風險先 dry-run,中風險需 verifier,高風險進 Owner Review Queue。 5. P2-411:Agent Event Bus / handoff protocol / RAG memory 的 no-write 基線;先讓 Agent 的觀察、判斷、交接、拒收與學習提案可追蹤。 6. P2-412:市場主流 AI Agent 定期評估;刷新 NVIDIA Nemotron、NeMo Agent Toolkit、OpenAI Agents、LangGraph、A2A、MCP、OpenTelemetry GenAI 等 primary sources,形成 scorecard 與候選整合提案。 diff --git a/docs/evaluations/dependency_supply_chain_drift_monitor_2026-06-18.json b/docs/evaluations/dependency_supply_chain_drift_monitor_2026-06-18.json new file mode 100644 index 000000000..aa232fa6a --- /dev/null +++ b/docs/evaluations/dependency_supply_chain_drift_monitor_2026-06-18.json @@ -0,0 +1,495 @@ +{ + "schema_version": "dependency_supply_chain_drift_monitor_v1", + "generated_at": "2026-06-18T23:05:00+08:00", + "program_status": { + "overall_completion_percent": 100, + "current_priority": "P2", + "current_task_id": "P2-004", + "next_task_id": "P2-406B", + "read_only_mode": true, + "runtime_authority": "repo_only_dependency_supply_chain_drift_monitor_no_external_lookup_or_write", + "status_note": "P2-004 將既有 Python / JavaScript / Docker / policy / version freshness 快照收斂為依賴與供應鏈漂移監控讀回;本輪只產生 committed snapshot 與 API,不啟用外部查詢、排程、升級、Telegram 實發或 production write。" + }, + "source_refs": [ + "docs/evaluations/package_supply_chain_inventory_2026-06-04.json", + "docs/evaluations/javascript_package_inventory_2026-06-04.json", + "docs/evaluations/docker_build_surface_inventory_2026-06-04.json", + "docs/evaluations/dependency_risk_policy_2026-06-04.json", + "docs/evaluations/dependency_drift_check_plan_2026-06-04.json", + "docs/evaluations/dependency_upgrade_approval_package_template_2026-06-04.json", + "docs/evaluations/ai_agent_version_freshness_snapshot_2026-06-11.json" + ], + "rollups": { + "source_snapshot_count": 6, + "stale_source_snapshot_count": 5, + "monitor_check_count": 7, + "drift_candidate_count": 9, + "action_required_candidate_count": 9, + "owner_action_count": 9, + "blocked_operation_count": 20, + "by_domain": { + "python": 2, + "javascript": 2, + "docker": 3, + "external_source": 1, + "agent_market": 1 + }, + "action_required_drift_candidate_ids": [ + "api_python_manifest_drift", + "python_lockfile_absence", + "apps_web_caret_range_exposure", + "shared_types_publish_boundary", + "docker_base_images_not_digest_pinned", + "api_kubectl_binary_without_checksum_policy", + "docker_build_time_network_fetches_present", + "external_cve_license_registry_lookup_not_approved", + "agent_market_source_lookup_not_approved" + ] + }, + "source_snapshot_readbacks": [ + { + "snapshot_id": "package_supply_chain_inventory", + "source_ref": "docs/evaluations/package_supply_chain_inventory_2026-06-04.json", + "source_schema_version": "package_supply_chain_inventory_v1", + "generated_at": "2026-06-04T21:06:22+08:00", + "freshness_status": "stale_action_required", + "evidence_status": "action_required_surfaces_present", + "next_action": "以 repo-only 方式刷新 Python / JS / Docker source rollup;未批准前不得查 registry 或升級。" + }, + { + "snapshot_id": "javascript_package_inventory", + "source_ref": "docs/evaluations/javascript_package_inventory_2026-06-04.json", + "source_schema_version": "javascript_package_inventory_v1", + "generated_at": "2026-06-04T20:22:00+08:00", + "freshness_status": "stale_action_required", + "evidence_status": "caret_range_and_workspace_boundary_present", + "next_action": "建立高影響前端套件與 workspace publish boundary 批准包;不得寫 pnpm-lock.yaml。" + }, + { + "snapshot_id": "docker_build_surface_inventory", + "source_ref": "docs/evaluations/docker_build_surface_inventory_2026-06-04.json", + "source_schema_version": "docker_build_surface_inventory_v1", + "generated_at": "2026-06-04T19:23:03+08:00", + "freshness_status": "stale_action_required", + "evidence_status": "digest_and_network_fetch_risk_present", + "next_action": "產生 digest pin、checksum、network source policy 批准包;未批准前不得 build 或 pull image。" + }, + { + "snapshot_id": "dependency_risk_policy", + "source_ref": "docs/evaluations/dependency_risk_policy_2026-06-04.json", + "source_schema_version": "dependency_risk_policy_v1", + "generated_at": "2026-06-04T21:40:00+08:00", + "freshness_status": "stale_action_required", + "evidence_status": "policy_defined_no_external_lookup", + "next_action": "將 policy hit 轉成 owner packet 與 Telegram failure-only digest draft;不得自動套用。" + }, + { + "snapshot_id": "dependency_drift_check_plan", + "source_ref": "docs/evaluations/dependency_drift_check_plan_2026-06-04.json", + "source_schema_version": "dependency_drift_check_plan_v1", + "generated_at": "2026-06-04T22:15:00+08:00", + "freshness_status": "stale_action_required", + "evidence_status": "cadence_design_only", + "next_action": "下一輪若要定期化,必須先送 schedule / workflow / external source approval packet。" + }, + { + "snapshot_id": "ai_agent_version_freshness_snapshot", + "source_ref": "docs/evaluations/ai_agent_version_freshness_snapshot_2026-06-11.json", + "source_schema_version": "ai_agent_version_freshness_snapshot_v1", + "generated_at": "2026-06-11T22:45:00+08:00", + "freshness_status": "recent_action_required", + "evidence_status": "repo_only_version_sources_present", + "next_action": "把 stale source refs 與 P2-004 drift candidates 串接到 P2-406B / P2-407 報表讀回,不啟用外部 lookup。" + } + ], + "monitor_checks": [ + { + "check_id": "python_manifest_authority_drift_readback", + "domain": "python", + "owner_agent": "hermes", + "status": "action_required", + "current_signal": "apps/api/pyproject.toml 與 apps/api/requirements.txt 權威性仍未決;Dockerfile 目前以 pyproject + uv 為建置來源。", + "evidence_refs": [ + "apps/api/pyproject.toml", + "apps/api/requirements.txt", + "apps/api/Dockerfile", + "docs/evaluations/package_supply_chain_inventory_2026-06-04.json" + ], + "blocked_now": ["delete requirements", "install packages", "write lockfile"], + "next_action": "prepare_python_manifest_authority_packet" + }, + { + "check_id": "python_lockfile_absence_readback", + "domain": "python", + "owner_agent": "openclaw", + "status": "action_required", + "current_signal": "Python manifests 仍無 uv.lock / poetry.lock / constraints authority;重現性策略需要人工批准。", + "evidence_refs": [ + "apps/api/pyproject.toml", + "packages/lewooogo-data/pyproject.toml", + "docs/evaluations/package_supply_chain_inventory_2026-06-04.json" + ], + "blocked_now": ["generate lockfile", "pin all versions", "upgrade dependencies"], + "next_action": "prepare_python_lockfile_strategy_packet" + }, + { + "check_id": "javascript_importer_lockfile_readback", + "domain": "javascript", + "owner_agent": "hermes", + "status": "read_only_passed", + "current_signal": "P1-202 已確認 pnpm importers 與 lockfile specifier 沒有 mismatch;仍需處理 high-impact caret range。", + "evidence_refs": [ + "package.json", + "apps/web/package.json", + "pnpm-lock.yaml", + "docs/evaluations/javascript_package_inventory_2026-06-04.json" + ], + "blocked_now": ["pnpm install", "npm audit", "lockfile write"], + "next_action": "prepare_js_high_impact_upgrade_packet" + }, + { + "check_id": "javascript_caret_range_exposure_readback", + "domain": "javascript", + "owner_agent": "openclaw", + "status": "action_required", + "current_signal": "apps/web 仍有大量 caret range;Next / React / Sentry / Playwright 等高影響套件需要升級批准包與 rollback plan。", + "evidence_refs": [ + "apps/web/package.json", + "pnpm-lock.yaml", + "docs/evaluations/javascript_package_inventory_2026-06-04.json" + ], + "blocked_now": ["package upgrade", "lockfile write", "external registry lookup"], + "next_action": "prepare_js_high_impact_upgrade_packet" + }, + { + "check_id": "docker_digest_pin_readback", + "domain": "docker", + "owner_agent": "openclaw", + "status": "action_required", + "current_signal": "API / Web Dockerfile 的 base image 與 uv image tag-pinned 但未 digest-pinned。", + "evidence_refs": [ + "apps/api/Dockerfile", + "apps/web/Dockerfile", + "docs/evaluations/docker_build_surface_inventory_2026-06-04.json" + ], + "blocked_now": ["docker build", "image pull", "registry push"], + "next_action": "prepare_docker_digest_pin_packet" + }, + { + "check_id": "docker_binary_checksum_readback", + "domain": "docker", + "owner_agent": "hermes", + "status": "action_required", + "current_signal": "API image build 下載 kubectl v1.29.0,但 checksum / signature policy 尚未固定。", + "evidence_refs": [ + "apps/api/Dockerfile", + "docs/evaluations/docker_build_surface_inventory_2026-06-04.json" + ], + "blocked_now": ["curl new binary", "edit Dockerfile", "image rebuild"], + "next_action": "prepare_kubectl_checksum_packet" + }, + { + "check_id": "external_source_activation_readback", + "domain": "external_source", + "owner_agent": "nemotron", + "status": "blocked_until_approval", + "current_signal": "OSV / Trivy / Syft / Grype / PyPI / npm / Docker Hub / Agent market primary sources 都仍是候選來源,尚未批准外部查詢。", + "evidence_refs": [ + "docs/evaluations/dependency_drift_check_plan_2026-06-04.json", + "docs/evaluations/ai_agent_version_freshness_snapshot_2026-06-11.json" + ], + "blocked_now": ["external CVE lookup", "license lookup", "registry lookup", "agent market lookup"], + "next_action": "prepare_external_source_activation_packet" + } + ], + "drift_candidates": [ + { + "candidate_id": "api_python_manifest_drift", + "domain": "python", + "severity": "high", + "status": "action_required", + "owner_agent": "openclaw", + "summary": "API pyproject 與 legacy requirements 仍未決定權威來源;若直接刪除或改寫會影響 build / local tool / rollback 判斷。", + "evidence_refs": [ + "apps/api/pyproject.toml", + "apps/api/requirements.txt", + "docs/evaluations/package_supply_chain_inventory_2026-06-04.json" + ], + "next_owner_action": "prepare_python_manifest_authority_packet" + }, + { + "candidate_id": "python_lockfile_absence", + "domain": "python", + "severity": "medium", + "status": "action_required", + "owner_agent": "openclaw", + "summary": "Python 依賴仍缺 committed lockfile 或 constraints authority;可先產策略批准包,不得直接生成。", + "evidence_refs": [ + "apps/api/pyproject.toml", + "packages/lewooogo-data/pyproject.toml", + "docs/evaluations/dependency_risk_policy_2026-06-04.json" + ], + "next_owner_action": "prepare_python_lockfile_strategy_packet" + }, + { + "candidate_id": "apps_web_caret_range_exposure", + "domain": "javascript", + "severity": "medium", + "status": "action_required", + "owner_agent": "openclaw", + "summary": "apps/web 仍有 high-impact caret ranges;下一步是升級批准包與 smoke matrix,不是直接 pnpm install。", + "evidence_refs": [ + "apps/web/package.json", + "pnpm-lock.yaml", + "docs/evaluations/javascript_package_inventory_2026-06-04.json" + ], + "next_owner_action": "prepare_js_high_impact_upgrade_packet" + }, + { + "candidate_id": "shared_types_publish_boundary", + "domain": "javascript", + "severity": "medium", + "status": "action_required", + "owner_agent": "hermes", + "summary": "shared-types 類 workspace 需要 publish / version / compatibility boundary;應以批准包定義,不由 Agent 自行 publish。", + "evidence_refs": [ + "packages/shared-types/package.json", + "pnpm-lock.yaml", + "docs/evaluations/javascript_package_inventory_2026-06-04.json" + ], + "next_owner_action": "prepare_shared_types_publish_boundary_packet" + }, + { + "candidate_id": "docker_base_images_not_digest_pinned", + "domain": "docker", + "severity": "high", + "status": "action_required", + "owner_agent": "openclaw", + "summary": "python:3.11-slim、node:20-alpine、ghcr.io/astral-sh/uv:0.6.9 尚未 digest-pinned;需要 digest、rollback 與 rebuild approval。", + "evidence_refs": [ + "apps/api/Dockerfile", + "apps/web/Dockerfile", + "docs/evaluations/docker_build_surface_inventory_2026-06-04.json" + ], + "next_owner_action": "prepare_docker_digest_pin_packet" + }, + { + "candidate_id": "api_kubectl_binary_without_checksum_policy", + "domain": "docker", + "severity": "high", + "status": "action_required", + "owner_agent": "hermes", + "summary": "API image build 以 curl 下載 kubectl v1.29.0,但 checksum / signature policy 尚未固定。", + "evidence_refs": [ + "apps/api/Dockerfile", + "docs/evaluations/docker_build_surface_inventory_2026-06-04.json" + ], + "next_owner_action": "prepare_kubectl_checksum_packet" + }, + { + "candidate_id": "docker_build_time_network_fetches_present", + "domain": "docker", + "severity": "medium", + "status": "action_required", + "owner_agent": "hermes", + "summary": "API build 的 apt-get / curl 與 Web build 的 corepack / pnpm install 都是 build-time network fetch,需要 source policy 與 failure-only digest。", + "evidence_refs": [ + "apps/api/Dockerfile", + "apps/web/Dockerfile", + "pnpm-lock.yaml", + "docs/evaluations/docker_build_surface_inventory_2026-06-04.json" + ], + "next_owner_action": "prepare_build_time_network_source_packet" + }, + { + "candidate_id": "external_cve_license_registry_lookup_not_approved", + "domain": "external_source", + "severity": "medium", + "status": "blocked_until_approval", + "owner_agent": "openclaw", + "summary": "CVE、license、PyPI、npm、Docker manifest freshness 尚未批准外部查詢;只能保留 repo-only drift 與批准包。", + "evidence_refs": [ + "docs/evaluations/dependency_drift_check_plan_2026-06-04.json", + "docs/evaluations/dependency_risk_policy_2026-06-04.json" + ], + "next_owner_action": "prepare_external_source_activation_packet" + }, + { + "candidate_id": "agent_market_source_lookup_not_approved", + "domain": "agent_market", + "severity": "medium", + "status": "blocked_until_approval", + "owner_agent": "nemotron", + "summary": "主流 AI Agent / SDK / model watch 已納入工作清單,但定期外部 primary-source lookup 尚未批准;NemoTron 只能先做 committed snapshot freshness 與離線 scorecard 草案。", + "evidence_refs": [ + "docs/ai/AI_AGENT_AUTOMATION_WORKLIST_2026-06-04.md", + "docs/evaluations/ai_agent_version_freshness_snapshot_2026-06-11.json" + ], + "next_owner_action": "prepare_agent_market_source_activation_packet" + } + ], + "owner_actions": [ + { + "action_id": "prepare_python_manifest_authority_packet", + "priority": "P2", + "owner_agent": "openclaw", + "approval_gate": "human_review_required_before_manifest_delete_or_generation", + "summary": "判定 pyproject / requirements 權威來源、生成或退場策略、rollback 與測試矩陣。", + "blocked_until": "Owner Gate + build/test/smoke plan + rollback owner" + }, + { + "action_id": "prepare_python_lockfile_strategy_packet", + "priority": "P2", + "owner_agent": "openclaw", + "approval_gate": "dependency_lock_strategy_approval_required", + "summary": "提出 uv.lock / constraints / no-lock exception 三路策略與風險評分。", + "blocked_until": "Owner Gate + package install approval + rollback plan" + }, + { + "action_id": "prepare_js_high_impact_upgrade_packet", + "priority": "P2", + "owner_agent": "openclaw", + "approval_gate": "frontend_high_impact_upgrade_approval_required", + "summary": "針對 Next / React / Sentry / Playwright 等 high-impact 套件產生升級、測試、rollback 與 cache busting 批准包。", + "blocked_until": "Owner Gate + pnpm lockfile write approval + browser smoke matrix" + }, + { + "action_id": "prepare_shared_types_publish_boundary_packet", + "priority": "P2", + "owner_agent": "hermes", + "approval_gate": "workspace_publish_boundary_approval_required", + "summary": "定義 shared-types version / publish / consumer compatibility 邊界與不自動 publish 規則。", + "blocked_until": "Owner Gate + package publish policy" + }, + { + "action_id": "prepare_docker_digest_pin_packet", + "priority": "P2", + "owner_agent": "openclaw", + "approval_gate": "image_digest_pin_and_rebuild_approval_required", + "summary": "產生 base image digest pin、重建、rollout、rollback 與 registry promotion 批准包。", + "blocked_until": "Owner Gate + docker build/pull/push approval + rollout window" + }, + { + "action_id": "prepare_kubectl_checksum_packet", + "priority": "P2", + "owner_agent": "hermes", + "approval_gate": "binary_checksum_policy_approval_required", + "summary": "定義 kubectl binary checksum / signature policy 與替換方案。", + "blocked_until": "Owner Gate + Dockerfile write approval" + }, + { + "action_id": "prepare_build_time_network_source_packet", + "priority": "P2", + "owner_agent": "hermes", + "approval_gate": "build_network_source_policy_approval_required", + "summary": "把 apt / curl / corepack / pnpm install 來源與失敗限定 digest policy 固定成批准包。", + "blocked_until": "Owner Gate + workflow and registry policy approval" + }, + { + "action_id": "prepare_external_source_activation_packet", + "priority": "P2", + "owner_agent": "openclaw", + "approval_gate": "external_cve_license_registry_lookup_approval_required", + "summary": "列出 OSV / Trivy / Syft / Grype / PyPI / npm / Docker registry lookup 的費用、頻率、rate limit、資料保存與 redaction 條件。", + "blocked_until": "Owner Gate + cost/rate-limit/privacy approval" + }, + { + "action_id": "prepare_agent_market_source_activation_packet", + "priority": "P2", + "owner_agent": "nemotron", + "approval_gate": "agent_market_primary_source_lookup_approval_required", + "summary": "把主流 AI Agent / SDK / model watch 來源、scorecard、回放基準與 OpenClaw 替代決策門檻固定成批准包。", + "blocked_until": "Owner Gate + primary-source refresh approval + replay policy" + } + ], + "telegram_policy": { + "status": "draft_only_no_send", + "direct_send_allowed": false, + "gateway_queue_write_allowed": false, + "success_quiet": true, + "failure_digest_after_approval": true, + "draft_only_outputs": [ + "P2-004 dependency drift action-required digest draft", + "Owner packet summary", + "failure-only schema mismatch digest draft" + ] + }, + "agent_roles": [ + { + "agent_id": "openclaw", + "role": "仲裁 high / critical supply-chain drift、升級風險、費用與 HITL gate;不得自行批准套件或 provider 變更。", + "autonomy_level": "L2_approval_package_only", + "approval_gate": "human_review_required_before_any_update_or_external_lookup", + "outputs": [ + "risk arbitration", + "owner action packet", + "blocked operation verification" + ] + }, + { + "agent_id": "hermes", + "role": "彙整 repo-only manifest / lockfile / Dockerfile / committed snapshot 證據,產出報表與 KM 草稿。", + "autonomy_level": "L1_report_only", + "approval_gate": "read_only_repo_monitor_allowed", + "outputs": [ + "drift evidence packet", + "report section", + "Telegram digest draft" + ] + }, + { + "agent_id": "nemotron", + "role": "進行離線長任務比對、版本 / Agent market freshness 草案與 replay scorecard 草案;不得替換 OpenClaw 或接 production route。", + "autonomy_level": "L1_offline_comparison_only", + "approval_gate": "offline_scorecard_only_until_market_lookup_and_replay_gate", + "outputs": [ + "agent market source activation packet", + "offline comparison note", + "replay candidate list" + ] + } + ], + "next_actions": [ + { + "task_id": "P2-406B", + "priority": "P2", + "summary": "把 P2-004 drift monitor 與日週月報 / Telegram receipt readback owner review 串接成同一個只讀審核視圖。", + "gate": "Telegram send、Gateway queue write、Bot API、receipt production write 仍必須全部為 0。" + }, + { + "task_id": "P2-407", + "priority": "P2", + "summary": "讓 OpenClaw / Hermes / NemoTron 讀取日週月報與 P2-004 drift monitor 後產生 no-write 分析與建議。", + "gate": "只寫 committed snapshot / 草稿;不得 production optimization write。" + }, + { + "task_id": "P2-412", + "priority": "P2", + "summary": "準備外部主流 Agent / SDK / model watch 的 primary-source refresh 批准包。", + "gate": "未批准前不得外部查詢、不得宣稱 latest、不得替換 OpenClaw。" + } + ], + "monitor_boundaries": { + "read_only_repo_monitor_allowed": true, + "schedule_activation_allowed": false, + "workflow_write_allowed": false, + "external_cve_lookup_allowed": false, + "external_license_lookup_allowed": false, + "registry_lookup_allowed": false, + "agent_market_external_lookup_allowed": false, + "package_installation_allowed": false, + "package_upgrade_allowed": false, + "lockfile_write_allowed": false, + "docker_build_allowed": false, + "image_pull_allowed": false, + "image_rebuild_allowed": false, + "registry_push_allowed": false, + "pr_creation_allowed": false, + "telegram_send_allowed": false, + "production_write_allowed": false, + "paid_external_service_allowed": false, + "secret_read_allowed": false, + "host_probe_allowed": false, + "npm_audit_allowed": false + } +} diff --git a/docs/schemas/dependency_supply_chain_drift_monitor_v1.schema.json b/docs/schemas/dependency_supply_chain_drift_monitor_v1.schema.json new file mode 100644 index 000000000..0e72e988e --- /dev/null +++ b/docs/schemas/dependency_supply_chain_drift_monitor_v1.schema.json @@ -0,0 +1,346 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:awoooi:dependency-supply-chain-drift-monitor-v1", + "title": "AWOOOI dependency supply-chain drift monitor v1", + "description": "P2-004 依賴 / 供應鏈漂移監控讀回。此 schema 僅授權 repo-only committed snapshot 讀回,不授權排程啟用、workflow 寫入、外部 CVE / license / registry / Agent market 查詢、套件安裝或升級、lockfile 寫入、Docker build / image pull / registry push、PR 建立、Telegram 實發、secret 讀取、host probe 或 production write。", + "type": "object", + "required": [ + "schema_version", + "generated_at", + "program_status", + "source_refs", + "rollups", + "source_snapshot_readbacks", + "monitor_checks", + "drift_candidates", + "owner_actions", + "telegram_policy", + "agent_roles", + "next_actions", + "monitor_boundaries" + ], + "properties": { + "schema_version": { + "type": "string", + "const": "dependency_supply_chain_drift_monitor_v1" + }, + "generated_at": { + "type": "string", + "minLength": 1 + }, + "program_status": { + "type": "object", + "required": [ + "overall_completion_percent", + "current_priority", + "current_task_id", + "next_task_id", + "read_only_mode", + "runtime_authority", + "status_note" + ], + "properties": { + "overall_completion_percent": { + "type": "integer", + "minimum": 0, + "maximum": 100 + }, + "current_priority": { + "type": "string", + "enum": ["P0", "P1", "P2", "P3"] + }, + "current_task_id": { + "type": "string", + "const": "P2-004" + }, + "next_task_id": { + "type": "string", + "minLength": 1 + }, + "read_only_mode": { + "type": "boolean", + "const": true + }, + "runtime_authority": { + "type": "string", + "minLength": 1 + }, + "status_note": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false + }, + "source_refs": { + "type": "array", + "minItems": 1, + "items": { + "type": "string", + "minLength": 1 + } + }, + "rollups": { + "type": "object", + "required": [ + "source_snapshot_count", + "stale_source_snapshot_count", + "monitor_check_count", + "drift_candidate_count", + "action_required_candidate_count", + "owner_action_count", + "blocked_operation_count", + "by_domain", + "action_required_drift_candidate_ids" + ], + "properties": { + "source_snapshot_count": { "type": "integer", "minimum": 0 }, + "stale_source_snapshot_count": { "type": "integer", "minimum": 0 }, + "monitor_check_count": { "type": "integer", "minimum": 0 }, + "drift_candidate_count": { "type": "integer", "minimum": 0 }, + "action_required_candidate_count": { "type": "integer", "minimum": 0 }, + "owner_action_count": { "type": "integer", "minimum": 0 }, + "blocked_operation_count": { "type": "integer", "minimum": 0 }, + "by_domain": { + "type": "object", + "additionalProperties": { + "type": "integer", + "minimum": 0 + } + }, + "action_required_drift_candidate_ids": { + "type": "array", + "items": { + "type": "string", + "minLength": 1 + } + } + }, + "additionalProperties": false + }, + "source_snapshot_readbacks": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "snapshot_id", + "source_ref", + "source_schema_version", + "generated_at", + "freshness_status", + "evidence_status", + "next_action" + ], + "properties": { + "snapshot_id": { "type": "string", "minLength": 1 }, + "source_ref": { "type": "string", "minLength": 1 }, + "source_schema_version": { "type": "string", "minLength": 1 }, + "generated_at": { "type": "string", "minLength": 1 }, + "freshness_status": { "type": "string", "minLength": 1 }, + "evidence_status": { "type": "string", "minLength": 1 }, + "next_action": { "type": "string", "minLength": 1 } + }, + "additionalProperties": false + } + }, + "monitor_checks": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "check_id", + "domain", + "owner_agent", + "status", + "current_signal", + "evidence_refs", + "blocked_now", + "next_action" + ], + "properties": { + "check_id": { "type": "string", "minLength": 1 }, + "domain": { "type": "string", "minLength": 1 }, + "owner_agent": { "type": "string", "enum": ["openclaw", "hermes", "nemotron"] }, + "status": { "type": "string", "minLength": 1 }, + "current_signal": { "type": "string", "minLength": 1 }, + "evidence_refs": { + "type": "array", + "minItems": 1, + "items": { "type": "string", "minLength": 1 } + }, + "blocked_now": { + "type": "array", + "items": { "type": "string", "minLength": 1 } + }, + "next_action": { "type": "string", "minLength": 1 } + }, + "additionalProperties": false + } + }, + "drift_candidates": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "candidate_id", + "domain", + "severity", + "status", + "owner_agent", + "summary", + "evidence_refs", + "next_owner_action" + ], + "properties": { + "candidate_id": { "type": "string", "minLength": 1 }, + "domain": { "type": "string", "minLength": 1 }, + "severity": { "type": "string", "enum": ["low", "medium", "high", "critical"] }, + "status": { "type": "string", "minLength": 1 }, + "owner_agent": { "type": "string", "enum": ["openclaw", "hermes", "nemotron"] }, + "summary": { "type": "string", "minLength": 1 }, + "evidence_refs": { + "type": "array", + "minItems": 1, + "items": { "type": "string", "minLength": 1 } + }, + "next_owner_action": { "type": "string", "minLength": 1 } + }, + "additionalProperties": false + } + }, + "owner_actions": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": [ + "action_id", + "priority", + "owner_agent", + "approval_gate", + "summary", + "blocked_until" + ], + "properties": { + "action_id": { "type": "string", "minLength": 1 }, + "priority": { "type": "string", "enum": ["P0", "P1", "P2", "P3"] }, + "owner_agent": { "type": "string", "enum": ["openclaw", "hermes", "nemotron"] }, + "approval_gate": { "type": "string", "minLength": 1 }, + "summary": { "type": "string", "minLength": 1 }, + "blocked_until": { "type": "string", "minLength": 1 } + }, + "additionalProperties": false + } + }, + "telegram_policy": { + "type": "object", + "required": [ + "status", + "direct_send_allowed", + "gateway_queue_write_allowed", + "success_quiet", + "failure_digest_after_approval", + "draft_only_outputs" + ], + "properties": { + "status": { "type": "string", "minLength": 1 }, + "direct_send_allowed": { "type": "boolean", "const": false }, + "gateway_queue_write_allowed": { "type": "boolean", "const": false }, + "success_quiet": { "type": "boolean" }, + "failure_digest_after_approval": { "type": "boolean" }, + "draft_only_outputs": { + "type": "array", + "items": { "type": "string", "minLength": 1 } + } + }, + "additionalProperties": false + }, + "agent_roles": { + "type": "array", + "minItems": 1, + "items": { + "type": "object", + "required": ["agent_id", "role", "autonomy_level", "approval_gate", "outputs"], + "properties": { + "agent_id": { "type": "string", "enum": ["openclaw", "hermes", "nemotron"] }, + "role": { "type": "string", "minLength": 1 }, + "autonomy_level": { "type": "string", "minLength": 1 }, + "approval_gate": { "type": "string", "minLength": 1 }, + "outputs": { + "type": "array", + "items": { "type": "string", "minLength": 1 } + } + }, + "additionalProperties": false + } + }, + "next_actions": { + "type": "array", + "items": { + "type": "object", + "required": ["task_id", "priority", "summary", "gate"], + "properties": { + "task_id": { "type": "string", "minLength": 1 }, + "priority": { "type": "string", "enum": ["P0", "P1", "P2", "P3"] }, + "summary": { "type": "string", "minLength": 1 }, + "gate": { "type": "string", "minLength": 1 } + }, + "additionalProperties": false + } + }, + "monitor_boundaries": { + "type": "object", + "required": [ + "read_only_repo_monitor_allowed", + "schedule_activation_allowed", + "workflow_write_allowed", + "external_cve_lookup_allowed", + "external_license_lookup_allowed", + "registry_lookup_allowed", + "agent_market_external_lookup_allowed", + "package_installation_allowed", + "package_upgrade_allowed", + "lockfile_write_allowed", + "docker_build_allowed", + "image_pull_allowed", + "image_rebuild_allowed", + "registry_push_allowed", + "pr_creation_allowed", + "telegram_send_allowed", + "production_write_allowed", + "paid_external_service_allowed", + "secret_read_allowed", + "host_probe_allowed", + "npm_audit_allowed" + ], + "properties": { + "read_only_repo_monitor_allowed": { "type": "boolean", "const": true }, + "schedule_activation_allowed": { "type": "boolean", "const": false }, + "workflow_write_allowed": { "type": "boolean", "const": false }, + "external_cve_lookup_allowed": { "type": "boolean", "const": false }, + "external_license_lookup_allowed": { "type": "boolean", "const": false }, + "registry_lookup_allowed": { "type": "boolean", "const": false }, + "agent_market_external_lookup_allowed": { "type": "boolean", "const": false }, + "package_installation_allowed": { "type": "boolean", "const": false }, + "package_upgrade_allowed": { "type": "boolean", "const": false }, + "lockfile_write_allowed": { "type": "boolean", "const": false }, + "docker_build_allowed": { "type": "boolean", "const": false }, + "image_pull_allowed": { "type": "boolean", "const": false }, + "image_rebuild_allowed": { "type": "boolean", "const": false }, + "registry_push_allowed": { "type": "boolean", "const": false }, + "pr_creation_allowed": { "type": "boolean", "const": false }, + "telegram_send_allowed": { "type": "boolean", "const": false }, + "production_write_allowed": { "type": "boolean", "const": false }, + "paid_external_service_allowed": { "type": "boolean", "const": false }, + "secret_read_allowed": { "type": "boolean", "const": false }, + "host_probe_allowed": { "type": "boolean", "const": false }, + "npm_audit_allowed": { "type": "boolean", "const": false } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md b/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md index 0686249a0..807b4a188 100644 --- a/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md +++ b/docs/superpowers/specs/2026-04-15-MASTER-ai-autonomous-flywheel-v2.md @@ -5045,3 +5045,18 @@ Trigger commit `f5cd37b7` 與 deploy marker `0ba92357` 已把 governance UI 的 - 同步立即執行順序:`P2-406B -> P2-407 -> P2-408 -> P2-411 -> P2-412 -> P2-413 -> P3-001/P3-004`,舊的 P2-405E 立即順序已改為歷史段落,不再作為下一步。 **裁決:** 本輪是工作清單、優先順序、主流基準與 gate 邊界同步,不是 Telegram send、Gateway queue write、Bot API call、delivery receipt production write、AI analysis runtime、auto optimization worker、host write、kubectl action、secret read、paid API call、production write 或 OpenClaw 替換;所有 live execution 仍需 Owner Gate、receipt、verifier、rollback 與 audit evidence。 + +### 2026-06-18 23:05 (台北) — §8 / P2-004 — 新增依賴 / 供應鏈漂移監控讀回 — 把套件與供應鏈風險收斂成 Agent 可共同判讀的只讀控制面 + +**觸發**:統帥提醒每次推進都不能忘記目標與方向;目前主線不是增加描述文字,而是把 AI Agents 的自動化工作推向可觀測、可審核、可測試、未批准不能越權的控制面。P1 已完成 Python、JavaScript、Docker、CVE / license / drift policy、定期檢查設計與升級批准包模板,但缺少 P2 層的統一漂移監控讀回。 + +**已推進:** +- 新增 `docs/schemas/dependency_supply_chain_drift_monitor_v1.schema.json` 與 `docs/evaluations/dependency_supply_chain_drift_monitor_2026-06-18.json`。 +- 新增 `apps/api/src/services/dependency_supply_chain_drift_monitor.py` 與 `GET /api/v1/agents/dependency-supply-chain-drift-monitor`。 +- `/zh-TW/governance?tab=automation-inventory` 新增 P2-004 依賴 / 供應鏈漂移監控卡片,讓 source snapshot、drift candidate、owner action、blocked operation 與 false boundary 可在治理頁直接讀回。 +- 監控讀回固定 6 個 source snapshot、7 個 monitor check、9 個 drift candidate、9 個 owner action、20 個 blocked operation。 +- Drift candidates 包含 Python manifest drift、Python lockfile absence、Web caret range、shared-types publish boundary、Docker digest gap、kubectl checksum gap、build-time network fetch、external source approval gap、Agent market source approval gap。 +- OpenClaw / Hermes / NemoTron 分工固定:OpenClaw 仲裁 supply-chain 風險與 Owner Gate;Hermes 產 repo-only 證據、報表與 Telegram digest 草稿;NemoTron 做離線長任務比對、Agent market freshness 草案與 replay scorecard 草案,不替換 OpenClaw、不接 production route。 +- 驗證:P2-004 JSON schema / snapshot parse 通過;`python3 -m py_compile src/services/dependency_supply_chain_drift_monitor.py` 通過;目標 service / API tests `8 passed`;zh-TW / en i18n JSON parse 與 key parity 通過;source-control / security mirror guards 與 `git diff --check` 通過。Web typecheck 因乾淨 worktree 缺 `node_modules` 未執行,本輪未安裝套件或寫 lockfile。 + +**裁決:** P2-004 是依賴 / 供應鏈漂移監控讀回,不是外部 CVE / license / registry / Agent market 查詢,不是套件升級、lockfile 寫入、Docker build、PR 建立、Telegram 實發、secret 讀取、host probe 或 production write。下一步維持 `P2-406B`:把日週月報、Telegram receipt owner review 與 P2-004 drift monitor 串接成只讀審核視圖;所有 live send / queue write / Bot API / receipt write 仍為 `0`。