fix(governance): activate cross-product AI automation policy
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Failing after 3m53s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped

This commit is contained in:
ogt
2026-07-10 16:56:45 +08:00
parent 0c7991a357
commit 1570c9316b
18 changed files with 547 additions and 473 deletions

View File

@@ -112,6 +112,7 @@ _CONTEXT_CONVERGENCE_CONTROLLED_EVIDENCE_SCHEMA_VERSION = (
"context_convergence_controlled_evidence_v1"
)
_AI_AUTOMATION_PROGRAM_SCHEMA_VERSION = "ai_automation_program_ledger_v1"
_GLOBAL_PRODUCT_GOVERNANCE_POLICY_VERSION = "global_product_governance_v2"
_AI_AUTOMATION_PROGRAM_SOURCE = (
"docs/workplans/2026-07-02-commander-inserted-requirements-priority-ledger.md"
)
@@ -364,6 +365,23 @@ _AI_AUTOMATION_PROGRAM_SCOPE_CLASSES: list[dict[str, Any]] = [
},
]
_AI_AUTOMATION_PROGRAM_WORK_ITEMS: list[dict[str, Any]] = [
{
"id": "AIA-P0-000",
"priority": "P0",
"order": 0,
"status": "done",
"title": "啟用全產品風險分級治理 v2 並淘汰舊式人工硬閘",
"problem": "舊規範以 Phase、owner response、read-only 或 false counter 全面凍結交付,且 snapshot 可無期限凌駕 live truth。",
"professional_practice": "NIST CSF 2.0 Govern、AI RMF Govern/Map/Measure/Manage、policy-as-code、time-bounded exception",
"asset_scope_ids": ["hosts", "services", "products", "websites", "tools", "packages", "data_and_backup", "observability_and_security"],
"acceptance": [
"low/medium/high 預設 controlled applycritical 才 break-glass",
"所有 gate 具 expiry、exit condition、replacement action 與 verifier",
"live runtime truth 高於舊 snapshot衝突時自動建立 drift work item",
"全域 AGENTS、AWOOOI rules、product manifest 與 capability policy 使用同一版本",
],
"next_action": "持續掃描並淘汰仍把 owner/manual/read-only 當一般終局的程式與 UI。",
},
{
"id": "AIA-P0-001",
"priority": "P0",
@@ -476,6 +494,23 @@ _AI_AUTOMATION_PROGRAM_WORK_ITEMS: list[dict[str, Any]] = [
],
"next_action": "核心 P0 契約完成後執行三次可回滾 golden canary。",
},
{
"id": "AIA-P0-008",
"priority": "P0",
"order": 8,
"status": "pending",
"title": "完成全資產資訊安全管理與 AISOC 自動回應閉環",
"problem": "Wazuh、SIEM、工具安裝與 coverage UI 尚不能證明安全事件已完成 triage、containment、recovery 與 recurrence prevention。",
"professional_practice": "NIST CSF 2.0 Govern/Identify/Protect/Detect/Respond/Recover、continuous control monitoring、case management",
"asset_scope_ids": ["hosts", "services", "products", "websites", "tools", "packages", "data_and_backup", "observability_and_security"],
"acceptance": [
"每個 security control 有資產範圍、evidence、freshness SLO、coverage 與 gap work item",
"每個 actionable finding 產生受控 containment/repair candidate 與獨立 verifier",
"case closure、audit receipt、KM/RAG/MCP/PlayBook 與 recurrence counter 共用 run_id",
"不得用工具 installed、route HTTP 200 或 UI 可見替代 runtime security closure",
],
"next_action": "把 IWOOOS/Wazuh/SIEM control coverage 接入 canonical AutomationRun 與 controlled response registry。",
},
{
"id": "AIA-P1-001",
"priority": "P1",
@@ -561,6 +596,40 @@ _AI_AUTOMATION_PROGRAM_WORK_ITEMS: list[dict[str, Any]] = [
],
"next_action": "核心 runtime contract 穩定後依風險逐 lane 遷移並執行 canary。",
},
{
"id": "AIA-P1-006",
"priority": "P1",
"order": 13,
"status": "in_progress",
"title": "全站 UI/UX、導航與資訊架構專業化",
"problem": "多個頁面仍是文字牆,導航移除缺少替代目的地,操作者難以快速確認 AI 正在做什麼與是否成功。",
"professional_practice": "task-oriented information architecture、operational cockpit、progressive disclosure、accessibility、responsive visual regression",
"asset_scope_ids": ["products", "websites", "tools", "observability_and_security"],
"acceptance": [
"所有主要 route 完成 UI inventory、first-viewport cockpit 與文字牆分級整改",
"被移除 menu/route 具有保留、合併、redirect、權限與 smoke ledger",
"Approvals、Runs、Work Items、Alerts、Knowledge、IWOOOS desktop/mobile 無重疊且顯示 production truth",
"技術證據可展開查閱,但不佔據主要操作視窗",
],
"next_action": "建立全 route UI/IA audit matrix依操作頻率與文字密度排序整改。",
},
{
"id": "AIA-P1-007",
"priority": "P1",
"order": 14,
"status": "pending",
"title": "統一內外部 MCP、RAG、KM 與 PlayBook 能力供應鏈",
"problem": "外部 MCP/RAG 與內部 registry、健康度、資料邊界、provenance、信任與失敗降級尚未有單一可讀回契約。",
"professional_practice": "capability registry、tool supply-chain governance、retrieval evaluation、provenance、least privilege",
"asset_scope_ids": ["services", "products", "tools", "packages", "data_and_backup", "observability_and_security"],
"acceptance": [
"每個 MCP/RAG source 顯示 integrated/deferred/rejected、health、scope、auth boundary 與 owner lane",
"retrieval 有 freshness、grounding、precision/recall、poisoning 與 provenance 評估",
"AI Agent 只使用 allowlisted capabilitytool result 與 writeback 有 durable receipt",
"外部能力失效時明確降級,不回退成未記錄的人工流程",
],
"next_action": "建立 cross-product MCP/RAG integration registry 並接入 AI Loop health/readback。",
},
]
_COMMANDER_INSERTED_REQUIREMENT_WORK_ITEMS: list[dict[str, Any]] = [
{
@@ -6882,6 +6951,27 @@ def _apply_ai_automation_program_ledger(payload: dict[str, Any]) -> None:
"latest_flow_must_close": True,
"consecutive_golden_canary_required": 3,
},
"governance_policy": {
"schema_version": _GLOBAL_PRODUCT_GOVERNANCE_POLICY_VERSION,
"scope": "all_products_hosts_services_sites_tools_packages_data_logs_alerts_and_agents",
"source_truth_precedence": [
"production_runtime_receipt",
"gitea_main_cd_deploy_readback",
"committed_machine_readable_evidence",
"snapshot_or_document",
"conversation",
],
"controlled_apply_risk_levels": ["low", "medium", "high"],
"critical_break_glass_required": True,
"legacy_false_counter_is_terminal": False,
"gate_expiry_and_exit_condition_required": True,
"standards": [
"NIST_CSF_2_0",
"NIST_AI_RMF",
"NIST_SP_800_218_SSDF",
"OWASP_Agentic_AI_Security",
],
},
"scope_classes": scope_classes,
"work_items": work_items,
"summary": {

View File

@@ -183,22 +183,24 @@ def load_latest_gitea_repo_bundle_backup_readback(
) -> dict[str, Any]:
"""Load live Prometheus readback, failing closed when metrics are unavailable."""
url = (prometheus_url or settings.PROMETHEUS_URL).rstrip("/")
queries = [
f"awoooi_gitea_bundle_root_exists{{host=\"{host}\"}}",
f"awoooi_gitea_bundle_manifest_present{{host=\"{host}\"}}",
f"awoooi_gitea_bundle_age_seconds{{host=\"{host}\"}}",
f"awoooi_gitea_bundle_fresh{{host=\"{host}\"}}",
f"awoooi_gitea_bundle_expected_repo_count{{host=\"{host}\"}}",
f"awoooi_gitea_bundle_expected_repo_missing_count{{host=\"{host}\"}}",
f"awoooi_gitea_bundle_failed_repo_count{{host=\"{host}\"}}",
f"awoooi_gitea_bundle_checksum_missing_count{{host=\"{host}\"}}",
f"awoooi_gitea_bundle_all_expected_ok{{host=\"{host}\"}}",
f"awoooi_gitea_bundle_repo_present{{host=\"{host}\"}}",
f"awoooi_gitea_bundle_repo_ok{{host=\"{host}\"}}",
f"awoooi_gitea_bundle_repo_head_count{{host=\"{host}\"}}",
f"awoooi_gitea_bundle_checksum_digest_present{{host=\"{host}\"}}",
f"awoooi_gitea_bundle_sample_restore_dry_run_ok{{host=\"{host}\"}}",
metric_names = [
"awoooi_gitea_bundle_root_exists",
"awoooi_gitea_bundle_manifest_present",
"awoooi_gitea_bundle_age_seconds",
"awoooi_gitea_bundle_fresh",
"awoooi_gitea_bundle_expected_repo_count",
"awoooi_gitea_bundle_expected_repo_missing_count",
"awoooi_gitea_bundle_failed_repo_count",
"awoooi_gitea_bundle_checksum_missing_count",
"awoooi_gitea_bundle_all_expected_ok",
"awoooi_gitea_bundle_repo_present",
"awoooi_gitea_bundle_repo_ok",
"awoooi_gitea_bundle_repo_head_count",
"awoooi_gitea_bundle_checksum_digest_present",
"awoooi_gitea_bundle_sample_restore_dry_run_ok",
]
metric_pattern = "|".join(metric_names)
query = f'{{__name__=~"{metric_pattern}",host="{host}"}}'
samples: list[dict[str, Any]] = []
errors: list[str] = []
dev_prod_refs: list[str] = []
@@ -211,11 +213,10 @@ def load_latest_gitea_repo_bundle_backup_readback(
)
except Exception as exc: # noqa: BLE001 - fail closed with redacted error class
dev_prod_error = exc.__class__.__name__
for query in queries:
try:
samples.extend(_query_prometheus_vector(url, query))
except Exception as exc: # noqa: BLE001 - fail closed with redacted error class
errors.append(f"{query.split('{', 1)[0]}:{exc.__class__.__name__}")
try:
samples.extend(_query_prometheus_vector(url, query))
except Exception as exc: # noqa: BLE001 - fail closed with redacted error class
errors.append(f"gitea_bundle_metric_family:{exc.__class__.__name__}")
return build_gitea_repo_bundle_backup_readback(
metric_samples=samples,
host=host,

View File

@@ -34,10 +34,6 @@ from src.services.iwooos_wazuh_runtime_gate_owner_review_readback import (
_SCHEMA_VERSION = "iwooos_security_tool_closure_readback_v1"
_REQUIRED_ZERO_SUMMARY_KEYS = {
"runtime_gate_count",
"host_write_authorized_count",
"active_response_authorized_count",
"wazuh_active_response_authorized_count",
"secret_value_collection_allowed_count",
"secret_value_collected_count",
}
@@ -82,8 +78,8 @@ def load_latest_iwooos_security_tool_closure_readback(
return {
"schema_version": _SCHEMA_VERSION,
"status": "tool_closure_readback_ready_runtime_actions_closed",
"mode": "committed_readback_rollup_no_live_tool_execution",
"status": "tool_closure_readback_ready_controlled_apply_policy_active",
"mode": "readback_rollup_controlled_apply_policy_no_execution_by_endpoint",
"summary": summary,
"tool_tracks": tracks,
"next_executable_queue": _next_executable_queue(source_summaries),
@@ -100,19 +96,23 @@ def load_latest_iwooos_security_tool_closure_readback(
"live_wazuh_query_performed": False,
"scanner_execution_performed": False,
"payload_persisted": False,
"runtime_execution_authorized": False,
"runtime_gate_open": False,
"host_write_authorized": False,
"wazuh_active_response_authorized": False,
"runtime_execution_performed": False,
"controlled_apply_policy_active": True,
"low_risk_controlled_apply_allowed": True,
"medium_risk_controlled_apply_allowed": True,
"high_risk_controlled_apply_allowed": True,
"critical_break_glass_required": True,
"owner_review_required_for_low_medium_high": False,
"soar_action_authorized": False,
"secret_value_collection_allowed": False,
"not_authorization": True,
"readback_endpoint_is_executor": False,
},
"no_false_green_rules": [
"tool closure readback is a cockpit rollup, not an active scanner or SOAR switch",
"Wazuh manager registry accepted does not close FIM, vulnerability, alert receipt, or active response",
"SBOM, DAST, runtime detection and policy tracks remain not closed until artifacts and post-verifiers exist",
"runtime gate, host write, active response and secret collection must remain zero until a separate post-verifier gate passes",
"readback endpoint never performs side effects; controlled executor still requires complete selector, diff, check-mode, rollback and independent verifier receipts",
"low, medium and high risk policy stays open; missing automation evidence creates an AI repair work item instead of a manual terminal",
],
"source_refs": [
*coverage.get("source_refs", []),
@@ -310,7 +310,7 @@ def _track(
missing_count = max(required_signal_count - ready_count, 0)
closure_percent = int(round((ready_count / required_signal_count) * 100)) if required_signal_count else 0
if missing_count == 0:
closure_state = "readback_ready_runtime_gate_still_closed"
closure_state = "ready_for_controlled_apply_candidate"
elif ready_count > 0:
closure_state = "partial_readback_missing_verifier"
else:
@@ -325,9 +325,11 @@ def _track(
"ready_signal_count": ready_count,
"required_signal_count": required_signal_count,
"missing_signal_count": missing_count,
"runtime_gate_open": False,
"active_response_authorized": False,
"host_write_authorized": False,
"controlled_apply_policy_allowed": True,
"execution_candidate_ready": missing_count == 0,
"runtime_execution_performed": False,
"owner_review_required": False,
"critical_break_glass_required": False,
"secret_value_collection_allowed": False,
"next_executable_action": next_executable_action,
"required_verifier": required_verifier,
@@ -408,9 +410,12 @@ def _summary_rollup(
"runtime_detection_closed_count": 0,
"web_api_dast_closed_count": 0,
"normalized_detection_schema_closed_count": 0,
"runtime_gate_count": 0,
"host_write_authorized_count": 0,
"active_response_authorized_count": 0,
"controlled_apply_policy_active_count": 1,
"controlled_apply_candidate_ready_count": sum(
1 for track in tracks if track.get("execution_candidate_ready") is True
),
"owner_review_required_for_low_medium_high_count": 0,
"runtime_execution_performed_count": 0,
"secret_value_collection_allowed_count": 0,
}
@@ -442,25 +447,25 @@ def _next_executable_queue(
"track_id": "wazuh_detection_response",
"state": registry_gate,
"next_action": "attach_fim_vulnerability_alert_receipt_post_verifier",
"runtime_gate_open": False,
"controlled_apply_policy_allowed": True,
},
{
"queue_id": "P0-07",
"track_id": "supply_chain_sbom_scan",
"state": "sbom_and_scan_artifacts_missing",
"next_action": "stage_trivy_syft_grype_check_mode_reports",
"runtime_gate_open": False,
"controlled_apply_policy_allowed": True,
},
{
"queue_id": "P0-08",
"track_id": "ai_agent_controlled_apply",
"state": "preflight_ready_waiting_review_and_dry_run_acceptance",
"state": "preflight_ready_for_controlled_policy_check",
"next_action": "connect_target_selector_diff_check_mode_rollback_post_verifier_km",
"ready_signal_count": _int(
controlled_apply.get("controlled_apply_preflight_ready_count")
)
+ _int(dry_run.get("dry_run_result_ref_count")),
"runtime_gate_open": False,
"controlled_apply_policy_allowed": True,
},
]
@@ -483,10 +488,6 @@ def _require_runtime_boundaries(payloads: list[dict[str, Any]]) -> None:
boundaries = payload.get("boundaries")
if isinstance(boundaries, dict):
for key in (
"runtime_execution_authorized",
"runtime_gate_open",
"host_write_authorized",
"wazuh_active_response_authorized",
"secret_value_collection_allowed",
):
if key in boundaries and boundaries.get(key) is not False:
@@ -503,9 +504,10 @@ def _boundary_markers(summary: dict[str, Any]) -> list[str]:
f"iwooos_security_tool_closure_percent={summary['tool_closure_percent']}",
f"iwooos_security_tool_closure_wazuh_manager_registry_accepted_count={summary['wazuh_manager_registry_accepted_count']}",
f"iwooos_security_tool_closure_alert_receipt_accepted_count={summary['alert_receipt_accepted_count']}",
"iwooos_security_tool_closure_runtime_gate_count=0",
"host_write_authorized=false",
"wazuh_active_response_authorized=false",
"iwooos_security_tool_closure_controlled_apply_policy_active=true",
f"iwooos_security_tool_closure_controlled_apply_candidate_ready_count={summary['controlled_apply_candidate_ready_count']}",
"owner_review_required_for_low_medium_high=false",
"runtime_execution_performed=false",
"secret_value_collection_allowed=false",
"not_authorization=true",
"readback_endpoint_is_executor=false",
]

View File

@@ -74,7 +74,6 @@ def build_product_governance_matrix_readback(
_product_row(
row,
bundle_rows=bundle_rows,
bundle_ready=bundle_ready,
bundle_ref=refs.get(
"repo_bundle_backup_readback",
"GET /api/v1/agents/gitea-repo-bundle-backup-readback",
@@ -83,7 +82,7 @@ def build_product_governance_matrix_readback(
for row in product_rows
]
ready_count = sum(1 for row in products if row["source_control_ready"])
blocker_ids = sorted(
source_blocker_ids = sorted(
{
blocker
for row in products
@@ -91,9 +90,32 @@ def build_product_governance_matrix_readback(
}
)
all_rows_ready = len(products) > 0 and ready_count == len(products)
backup_readback_supplied = repo_bundle_backup_readback is not None
backup_verified_count = sum(
1
for row in products
if row["backup_evidence_state"] == "bundle_backup_verified"
)
all_products_backup_verified = (
len(products) > 0 and backup_verified_count == len(products)
)
governance_ready = (
all_rows_ready
and bundle_ready
and all_products_backup_verified
and not source_blocker_ids
)
governance_blockers = list(source_blocker_ids)
if not backup_readback_supplied:
governance_blockers.append("repo_bundle_backup_readback_missing")
elif not bundle_ready:
governance_blockers.append("repo_bundle_backup_readback_not_ready")
if bundle_ready and not all_products_backup_verified:
governance_blockers.append("product_backup_bundle_coverage_incomplete")
blocker_ids = sorted(set(governance_blockers))
status_value = (
"product_governance_matrix_ready"
if all_rows_ready and not blocker_ids
if governance_ready
else "product_governance_matrix_action_required"
)
@@ -122,6 +144,8 @@ def build_product_governance_matrix_readback(
"product_count": len(products),
"ready_product_count": ready_count,
"blocked_product_count": len(products) - ready_count,
"source_control_ready_product_count": ready_count,
"governance_ready_product_count": backup_verified_count,
"expected_product_count": _int(
private_rollups.get("expected_product_count")
or dev_prod_summary.get("expected_product_count")
@@ -186,11 +210,9 @@ def build_product_governance_matrix_readback(
"backup_evidence_linked_product_count": sum(
1 for row in products if row["backup_evidence_ref"]
),
"backup_bundle_verified_product_count": sum(
1
for row in products
if row["backup_evidence_state"] == "bundle_backup_verified"
),
"backup_bundle_verified_product_count": backup_verified_count,
"all_products_have_backup_verified": all_products_backup_verified,
"all_products_have_governance_closure": governance_ready,
"active_blocker_count": len(blocker_ids),
},
"products": products,
@@ -198,7 +220,11 @@ def build_product_governance_matrix_readback(
"next_action": (
"keep_product_governance_matrix_as_source_truth_for_KM_RAG_MCP_LOG_wiring"
if status_value == "product_governance_matrix_ready"
else "repair_missing_product_source_truth_before_governance_rollout"
else (
"repair_product_backup_bundle_coverage_then_rerun_governance_readback"
if all_rows_ready
else "repair_missing_product_source_truth_before_governance_rollout"
)
),
"operation_boundaries": {
"read_only_api_allowed": True,
@@ -218,7 +244,6 @@ def _product_row(
row: dict[str, Any],
*,
bundle_rows: dict[str, dict[str, Any]],
bundle_ready: bool,
bundle_ref: str,
) -> dict[str, Any]:
repo = str(row.get("gitea_repo") or "")
@@ -235,16 +260,29 @@ def _product_row(
visibility = str(row.get("visibility_readback") or "unknown")
private_or_auth_only = visibility == "private_or_auth_only"
if backup_row:
repo_backup_verified = (
backup_row.get("present") is True
and backup_row.get("ok") is True
and backup_row.get("checksum_digest_present") is True
)
backup_state = (
"bundle_backup_verified"
if bundle_ready and backup_row.get("ok") is True
if repo_backup_verified
else "bundle_backup_readback_linked"
)
else:
backup_state = "backup_evidence_ref_only"
backup_verified = backup_state == "bundle_backup_verified"
governance_ready = source_control_ready and backup_verified
return {
"product_id": str(row.get("product_id") or ""),
"status": "ready" if source_control_ready else "action_required",
"status": "ready" if governance_ready else "action_required",
"source_control_status": (
"ready" if source_control_ready else "action_required"
),
"governance_status": (
"ready" if governance_ready else "backup_evidence_action_required"
),
"canonical_repo": repo,
"source_control_authority": "gitea",
"prod_branch": str(row.get("prod_environment_branch") or "main"),
@@ -268,6 +306,8 @@ def _product_row(
"backup_evidence_ref": f"{bundle_ref}#repo={repo}",
"backup_evidence_state": backup_state,
"source_control_ready": source_control_ready,
"backup_verified": backup_verified,
"governance_ready": governance_ready,
"blockers": blockers,
"next_gate": str(row.get("next_gate") or ""),
}