Compare commits
46 Commits
codex/110-
...
codex/gith
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
62309d3990 | ||
|
|
257544d097 | ||
|
|
ca29cbd5af | ||
|
|
30af0fb420 | ||
|
|
f98aaa8ee9 | ||
|
|
527e9762af | ||
|
|
a2733fd431 | ||
|
|
f219463f20 | ||
|
|
b2458b9330 | ||
|
|
fdc703811d | ||
|
|
0624be08df | ||
|
|
5b7bd55a90 | ||
|
|
3a2b3b3e6f | ||
|
|
f917ea41c2 | ||
|
|
c2b19ea019 | ||
|
|
b5bf42bf0a | ||
|
|
a68d9e40a7 | ||
|
|
6f228e7f8a | ||
|
|
1a6f8f4275 | ||
|
|
1a8613c9e6 | ||
|
|
c73ce995e2 | ||
|
|
b6c2271f64 | ||
|
|
d2d1446594 | ||
|
|
319208f1da | ||
|
|
7b2b3db458 | ||
|
|
df498e55b1 | ||
|
|
e49c6190ec | ||
|
|
18fa182bce | ||
|
|
9f5097f664 | ||
|
|
cf326574d5 | ||
|
|
a22a154e18 | ||
|
|
2c08a151ca | ||
|
|
c6bc1e6d1b | ||
|
|
c5f798cd9b | ||
|
|
f47ee7d966 | ||
|
|
f461a118a3 | ||
|
|
bbdab96ffd | ||
|
|
db80ed812d | ||
|
|
80138e9854 | ||
|
|
9c638c78ad | ||
|
|
4e4c56cae5 | ||
|
|
de609a79ae | ||
|
|
1bf76a02fb | ||
|
|
fd84ddd1d3 | ||
|
|
460b11fdd1 | ||
|
|
0e4e0fab37 |
@@ -38,15 +38,6 @@ from src.core.sse import get_publisher
|
||||
from src.services.agent_market_governance_snapshot import (
|
||||
load_latest_agent_market_governance_snapshot,
|
||||
)
|
||||
from src.services.ai_agent_market_radar_readback import (
|
||||
load_latest_ai_agent_market_radar_readback,
|
||||
)
|
||||
from src.services.ai_technology_radar_readback import (
|
||||
load_latest_ai_technology_radar_readback,
|
||||
)
|
||||
from src.services.ai_technology_report_cadence_readback import (
|
||||
load_latest_ai_technology_report_cadence_readback,
|
||||
)
|
||||
from src.services.agent_service import (
|
||||
AgentService,
|
||||
TaskState,
|
||||
@@ -88,12 +79,6 @@ from src.services.ai_agent_critic_reviewer_result_capture import (
|
||||
from src.services.ai_agent_deployment_layout import (
|
||||
load_latest_ai_agent_deployment_layout,
|
||||
)
|
||||
from src.services.awoooi_status_cleanup_dashboard import (
|
||||
load_latest_awoooi_status_cleanup_dashboard,
|
||||
)
|
||||
from src.services.github_target_private_backup_evidence_gate import (
|
||||
load_latest_github_target_private_backup_evidence_gate,
|
||||
)
|
||||
from src.services.ai_agent_failure_receipt_no_send_replay import (
|
||||
load_latest_ai_agent_failure_receipt_no_send_replay,
|
||||
)
|
||||
@@ -118,6 +103,9 @@ from src.services.ai_agent_live_read_model_gate import (
|
||||
from src.services.ai_agent_low_medium_risk_whitelist import (
|
||||
load_latest_ai_agent_low_medium_risk_whitelist,
|
||||
)
|
||||
from src.services.ai_agent_market_radar_readback import (
|
||||
load_latest_ai_agent_market_radar_readback,
|
||||
)
|
||||
from src.services.ai_agent_matched_playbook_learning_gap import (
|
||||
load_latest_ai_agent_matched_playbook_learning_gap,
|
||||
)
|
||||
@@ -307,6 +295,15 @@ from src.services.ai_agent_version_lifecycle_update_proposal import (
|
||||
from src.services.ai_provider_route_matrix import (
|
||||
load_latest_ai_provider_route_matrix,
|
||||
)
|
||||
from src.services.ai_technology_radar_readback import (
|
||||
load_latest_ai_technology_radar_readback,
|
||||
)
|
||||
from src.services.ai_technology_report_cadence_readback import (
|
||||
load_latest_ai_technology_report_cadence_readback,
|
||||
)
|
||||
from src.services.awoooi_status_cleanup_dashboard import (
|
||||
load_latest_awoooi_status_cleanup_dashboard,
|
||||
)
|
||||
from src.services.backup_dr_readiness_matrix import (
|
||||
load_latest_backup_dr_readiness_matrix,
|
||||
)
|
||||
@@ -319,6 +316,9 @@ from src.services.backup_notification_policy import (
|
||||
from src.services.backup_restore_drill_approval_package_template import (
|
||||
load_latest_backup_restore_drill_approval_package_template,
|
||||
)
|
||||
from src.services.delivery_closure_workbench import (
|
||||
load_delivery_closure_workbench,
|
||||
)
|
||||
from src.services.dependency_drift_check_plan import (
|
||||
load_latest_dependency_drift_check_plan,
|
||||
)
|
||||
@@ -331,15 +331,16 @@ from src.services.dependency_supply_chain_drift_monitor import (
|
||||
from src.services.dependency_upgrade_approval_package_template import (
|
||||
load_latest_dependency_upgrade_approval_package_template,
|
||||
)
|
||||
from src.services.delivery_closure_workbench import (
|
||||
load_delivery_closure_workbench,
|
||||
)
|
||||
from src.services.docker_build_surface_inventory import (
|
||||
load_latest_docker_build_surface_inventory,
|
||||
)
|
||||
from src.services.gitea_workflow_runner_health import (
|
||||
load_latest_gitea_workflow_runner_health,
|
||||
)
|
||||
from src.services.github_target_private_backup_evidence_gate import (
|
||||
load_latest_github_target_private_backup_evidence_gate,
|
||||
preflight_github_target_owner_response_submission,
|
||||
)
|
||||
from src.services.host_runaway_aiops_loop_readiness import (
|
||||
load_latest_host_runaway_aiops_loop_readiness,
|
||||
)
|
||||
@@ -990,6 +991,42 @@ async def get_github_target_private_backup_evidence_gate() -> dict[str, Any]:
|
||||
) from exc
|
||||
|
||||
|
||||
@router.post(
|
||||
"/github-target-owner-response-intake-preflight",
|
||||
response_model=dict[str, Any],
|
||||
summary="預檢 GitHub target owner response candidate",
|
||||
description=(
|
||||
"只驗證一份 GitHub target owner response candidate 是否符合 read-only intake 規則;"
|
||||
"此端點不持久化 submission、不呼叫 GitHub live API、不建立 repo、不改 visibility、不同步 refs、"
|
||||
"不觸發 workflow、不收 private clone URL credential 或任何 secret value。"
|
||||
),
|
||||
)
|
||||
async def preflight_github_target_owner_response_intake(
|
||||
submission: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""Validate a GitHub target owner response candidate without persisting it."""
|
||||
try:
|
||||
payload = await asyncio.to_thread(
|
||||
preflight_github_target_owner_response_submission,
|
||||
submission,
|
||||
)
|
||||
return redact_public_lan_topology(payload)
|
||||
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(
|
||||
"github_target_owner_response_intake_preflight_invalid",
|
||||
error=str(exc),
|
||||
)
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="GitHub target owner response intake preflight 無效",
|
||||
) from exc
|
||||
|
||||
|
||||
@router.get(
|
||||
"/agent-12-agent-war-room",
|
||||
response_model=dict[str, Any],
|
||||
|
||||
@@ -217,6 +217,18 @@ def _int_or_none(value: Any) -> int | None:
|
||||
return None
|
||||
|
||||
|
||||
def _is_controlled_apply_record(row: dict[str, Any]) -> bool:
|
||||
if not row:
|
||||
return False
|
||||
if row.get("approval_source"):
|
||||
return True
|
||||
if str(row.get("execution_mode") or "").strip().lower() == "controlled_apply":
|
||||
return True
|
||||
if "controlled_apply" in _tags(row):
|
||||
return True
|
||||
return str(row.get("actor") or "").strip().lower() == "ansible_controlled_apply_worker"
|
||||
|
||||
|
||||
def _is_ansible_operation(row: dict[str, Any]) -> bool:
|
||||
operation_type = str(_get(row, "operation_type") or "").lower()
|
||||
if operation_type in ANSIBLE_OPERATION_TYPES:
|
||||
@@ -341,7 +353,7 @@ def summarize_ansible_execution(records: list[dict[str, Any]]) -> dict[str, Any]
|
||||
"pending_check_mode_total": pending_check_mode_total,
|
||||
"applied_success_total": applied_success_total,
|
||||
"applied": applied_success_total > 0,
|
||||
"controlled_apply": bool(latest_apply) and bool(approval_source),
|
||||
"controlled_apply": bool(latest_apply) and _is_controlled_apply_record(latest_apply),
|
||||
"latest_operation_type": focused.get("operation_type"),
|
||||
"latest_status": focused.get("status"),
|
||||
"latest_catalog_id": focused.get("catalog_id"),
|
||||
|
||||
@@ -805,6 +805,41 @@ async def _record_post_apply_verifier_and_learning(
|
||||
return status
|
||||
|
||||
|
||||
async def _send_controlled_apply_telegram_receipt(
|
||||
claim: AnsibleCheckModeClaim,
|
||||
result: AnsibleRunResult,
|
||||
*,
|
||||
apply_op_id: str,
|
||||
writeback: dict[str, bool],
|
||||
project_id: str,
|
||||
) -> bool:
|
||||
try:
|
||||
from src.services.telegram_gateway import get_telegram_gateway
|
||||
|
||||
response = await get_telegram_gateway().send_controlled_apply_result_receipt(
|
||||
incident_id=claim.incident_id,
|
||||
catalog_id=claim.catalog_id,
|
||||
apply_op_id=apply_op_id,
|
||||
playbook_path=claim.apply_playbook_path,
|
||||
verification_result=_post_apply_verification_result(result),
|
||||
returncode=result.returncode,
|
||||
duration_ms=result.duration_ms,
|
||||
verifier_written=bool(writeback.get("verification")),
|
||||
learning_written=bool(writeback.get("learning")),
|
||||
project_id=project_id,
|
||||
)
|
||||
return bool(response)
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"ansible_controlled_apply_telegram_receipt_failed",
|
||||
incident_id=claim.incident_id,
|
||||
catalog_id=claim.catalog_id,
|
||||
apply_op_id=apply_op_id,
|
||||
error=str(exc),
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
async def backfill_missing_auto_repair_execution_receipts_once(
|
||||
*,
|
||||
project_id: str = "awoooi",
|
||||
@@ -1322,6 +1357,13 @@ async def run_controlled_apply_for_claim(
|
||||
apply_op_id=apply_op_id,
|
||||
project_id=project_id,
|
||||
)
|
||||
telegram_receipt_sent = await _send_controlled_apply_telegram_receipt(
|
||||
claim,
|
||||
result,
|
||||
apply_op_id=apply_op_id,
|
||||
writeback=writeback,
|
||||
project_id=project_id,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"ansible_controlled_apply_completed",
|
||||
@@ -1335,6 +1377,7 @@ async def run_controlled_apply_for_claim(
|
||||
auto_repair_receipt_written=receipt_written,
|
||||
post_apply_verification_written=writeback.get("verification"),
|
||||
post_apply_learning_written=writeback.get("learning"),
|
||||
telegram_receipt_sent=telegram_receipt_sent,
|
||||
)
|
||||
return result
|
||||
|
||||
|
||||
@@ -435,6 +435,9 @@ def _truth_status(
|
||||
approval_suppressed = _approval_suppresses_repair_execution(approvals)
|
||||
effective_ops = [] if approval_suppressed else _effective_execution_ops(automation_ops)
|
||||
has_execution_records = bool(effective_ops or repair_rows)
|
||||
latest_verification = str(
|
||||
_latest_verification_result(incident, evidence_rows) or ""
|
||||
).lower()
|
||||
stage = "received"
|
||||
stage_status = incident_status.lower()
|
||||
if incident_status in {"RESOLVED", "CLOSED"}:
|
||||
@@ -504,7 +507,8 @@ def _truth_status(
|
||||
if incident_status == "INVESTIGATING" and approvals:
|
||||
if execution_succeeded:
|
||||
blockers.append("incident_open_after_successful_execution")
|
||||
needs_human = True
|
||||
if latest_verification != "success":
|
||||
needs_human = True
|
||||
elif not has_execution_records:
|
||||
blockers.append("incident_still_investigating_after_approval")
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -148,7 +148,7 @@ def load_latest_iwooos_wazuh_manager_registry_reviewer_validation(
|
||||
"schema_version": "iwooos_wazuh_manager_registry_reviewer_validation_readback_v1",
|
||||
"source_schema_version": snapshot["schema_version"],
|
||||
"status": snapshot.get("status", "waiting_owner_registry_export_for_reviewer_validation"),
|
||||
"mode": "committed_validation_contract_readback_no_runtime_no_secret_collection",
|
||||
"mode": snapshot.get("mode", "committed_validation_contract_readback_no_runtime_no_secret_collection"),
|
||||
"source_refs": [
|
||||
f"docs/security/{_SNAPSHOT_FILE}",
|
||||
"scripts/security/wazuh-manager-registry-reviewer-validation.py",
|
||||
@@ -283,14 +283,7 @@ def _boundary_markers(summary: dict[str, int]) -> list[str]:
|
||||
def _require_boundaries(payload: dict[str, Any]) -> None:
|
||||
summary = _summary(payload)
|
||||
for key in (
|
||||
"owner_registry_export_received_count",
|
||||
"owner_registry_export_accepted_count",
|
||||
"reviewer_validation_ready_count",
|
||||
"reviewer_validation_passed_count",
|
||||
"reviewer_validation_failed_count",
|
||||
"reviewer_validation_quarantined_count",
|
||||
"manager_registry_accepted_count",
|
||||
"post_enable_readback_passed_count",
|
||||
"runtime_gate_count",
|
||||
"host_write_authorized_count",
|
||||
"active_response_authorized_count",
|
||||
@@ -299,6 +292,28 @@ def _require_boundaries(payload: dict[str, Any]) -> None:
|
||||
if _int(summary.get(key)) != 0:
|
||||
raise ValueError(f"Wazuh manager registry reviewer validation summary.{key} 必須維持 0")
|
||||
|
||||
received = _int(summary.get("owner_registry_export_received_count"))
|
||||
accepted = _int(summary.get("owner_registry_export_accepted_count"))
|
||||
ready = _int(summary.get("reviewer_validation_ready_count"))
|
||||
passed = _int(summary.get("reviewer_validation_passed_count"))
|
||||
failed = _int(summary.get("reviewer_validation_failed_count"))
|
||||
quarantined = _int(summary.get("reviewer_validation_quarantined_count"))
|
||||
post_enable = _int(summary.get("post_enable_readback_passed_count"))
|
||||
if any(value < 0 for value in (received, accepted, ready, passed, failed, quarantined, post_enable)):
|
||||
raise ValueError("Wazuh manager registry reviewer validation counters 不得為負數")
|
||||
if accepted > received:
|
||||
raise ValueError("owner_registry_export_accepted_count 不得大於 received_count")
|
||||
if ready > received:
|
||||
raise ValueError("reviewer_validation_ready_count 不得大於 received_count")
|
||||
if passed > accepted:
|
||||
raise ValueError("reviewer_validation_passed_count 不得大於 accepted_count")
|
||||
if post_enable > passed:
|
||||
raise ValueError("post_enable_readback_passed_count 不得大於 reviewer_validation_passed_count")
|
||||
if failed and passed:
|
||||
raise ValueError("reviewer_validation_failed_count 與 passed_count 不得同時為正")
|
||||
if quarantined and accepted:
|
||||
raise ValueError("reviewer_validation_quarantined_count 與 accepted_count 不得同時為正")
|
||||
|
||||
boundaries = payload.get("execution_boundaries")
|
||||
if not isinstance(boundaries, dict):
|
||||
raise ValueError("Wazuh manager registry reviewer validation execution_boundaries 缺失")
|
||||
|
||||
@@ -9182,6 +9182,112 @@ class TelegramGateway:
|
||||
}
|
||||
return await self._send_request("sendMessage", payload)
|
||||
|
||||
async def send_controlled_apply_result_receipt(
|
||||
self,
|
||||
*,
|
||||
incident_id: str,
|
||||
catalog_id: str,
|
||||
apply_op_id: str,
|
||||
playbook_path: str,
|
||||
verification_result: str,
|
||||
returncode: int,
|
||||
duration_ms: int,
|
||||
verifier_written: bool,
|
||||
learning_written: bool,
|
||||
project_id: str = "awoooi",
|
||||
) -> dict:
|
||||
"""Send and mirror the AI Agent controlled-apply result receipt."""
|
||||
|
||||
if not self.alert_chat_id:
|
||||
logger.warning(
|
||||
"controlled_apply_result_receipt_skipped_no_chat",
|
||||
incident_id=incident_id,
|
||||
apply_op_id=apply_op_id,
|
||||
)
|
||||
return {}
|
||||
|
||||
success = verification_result == "success" and returncode == 0
|
||||
next_step = (
|
||||
"monitor_for_regression"
|
||||
if success
|
||||
else "queue_ai_rollback_or_playbook_repair"
|
||||
)
|
||||
title = (
|
||||
"CONTROLLED APPLY RESULT|AI Agent 受控執行完成"
|
||||
if success
|
||||
else "CONTROLLED APPLY RESULT|AI Agent 受控執行待修復"
|
||||
)
|
||||
truth_chain: dict[str, object] | None = None
|
||||
try:
|
||||
from src.services.awooop_truth_chain_service import fetch_truth_chain
|
||||
|
||||
truth_chain = await fetch_truth_chain(
|
||||
source_id=incident_id,
|
||||
project_id=project_id or "awoooi",
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"controlled_apply_result_truth_chain_snapshot_failed",
|
||||
incident_id=incident_id,
|
||||
apply_op_id=apply_op_id,
|
||||
error=str(exc),
|
||||
)
|
||||
|
||||
status_snapshot = _callback_reply_awooop_status_chain_snapshot(
|
||||
incident_id=incident_id,
|
||||
truth_chain=truth_chain,
|
||||
)
|
||||
km_completion_summary = await _fetch_km_stale_completion_summary_for_incident(
|
||||
incident_id=incident_id,
|
||||
project_id=project_id or "awoooi",
|
||||
)
|
||||
source_extra = _callback_reply_source_envelope_extra(
|
||||
incident_id=incident_id,
|
||||
failure_context="controlled_apply_result",
|
||||
status="callback_reply_sent",
|
||||
chunk_index=0,
|
||||
chunk_count=1,
|
||||
callback_action="controlled_apply_result",
|
||||
parse_mode="HTML",
|
||||
km_stale_completion_summary=km_completion_summary,
|
||||
awooop_status_chain=status_snapshot,
|
||||
)
|
||||
lines = [
|
||||
f"<b>{html.escape(title)}</b>",
|
||||
f"Incident: <code>{html.escape(str(incident_id))}</code>",
|
||||
f"Catalog: <code>{html.escape(str(catalog_id or '--'))}</code>",
|
||||
f"Apply op: <code>{html.escape(str(apply_op_id or '')[:8])}</code>",
|
||||
f"PlayBook: <code>{html.escape(str(playbook_path or '--'))}</code>",
|
||||
(
|
||||
"Result: "
|
||||
f"<code>{html.escape(str(verification_result or 'missing'))}</code> "
|
||||
f"/ rc <code>{html.escape(str(returncode))}</code> "
|
||||
f"/ {html.escape(str(duration_ms))}ms"
|
||||
),
|
||||
(
|
||||
"Receipts: verifier "
|
||||
f"<code>{html.escape(_bool_code(verifier_written))}</code> / KM "
|
||||
f"<code>{html.escape(_bool_code(learning_written))}</code>"
|
||||
),
|
||||
f"Next: <code>{html.escape(next_step)}</code>",
|
||||
f"Runs: {html.escape(incident_runs_url(incident_id, project_id=project_id or 'awoooi'))}",
|
||||
]
|
||||
payload: dict = {
|
||||
"chat_id": self.alert_chat_id,
|
||||
"text": "\n".join(lines)[:4096],
|
||||
"parse_mode": "HTML",
|
||||
"disable_web_page_preview": True,
|
||||
}
|
||||
reply_markup = incident_truth_chain_reply_markup(
|
||||
incident_id,
|
||||
project_id=project_id or "awoooi",
|
||||
)
|
||||
if reply_markup:
|
||||
payload["reply_markup"] = reply_markup
|
||||
if source_extra:
|
||||
payload[_AWOOOP_SOURCE_ENVELOPE_EXTRA_KEY] = source_extra
|
||||
return await self._send_request("sendMessage", payload)
|
||||
|
||||
# =========================================================================
|
||||
# 2026-04-24 Claude Sonnet 4.6 (ADR-095 WS4): Hermes NL 回覆
|
||||
# =========================================================================
|
||||
|
||||
@@ -14,7 +14,7 @@ def test_ai_agent_autonomous_runtime_control_uses_current_owner_directive():
|
||||
"p2_416_d1n_autonomous_runtime_control_prod_readback_v2"
|
||||
)
|
||||
assert data["program_status"]["deploy_attempt_note"] == (
|
||||
"cd_3660_failed_host_pressure_guard_retry"
|
||||
"cd_3673_retry_after_host_pressure_gate_fix"
|
||||
)
|
||||
assert data["program_status"]["legacy_no_send_no_live_rules_overridden"] is True
|
||||
assert data["program_status"]["implementation_completion_percent"] == 88
|
||||
|
||||
@@ -55,7 +55,7 @@ def test_get_ai_agent_autonomous_runtime_control_api():
|
||||
"p2_416_d1n_autonomous_runtime_control_prod_readback_v2"
|
||||
)
|
||||
assert data["program_status"]["deploy_attempt_note"] == (
|
||||
"cd_3660_failed_host_pressure_guard_retry"
|
||||
"cd_3673_retry_after_host_pressure_gate_fix"
|
||||
)
|
||||
assert data["current_policy"]["owner_review_required_for_low_medium_high"] is False
|
||||
assert data["report_delivery"]["status"] == "telegram_gateway_delivery_enabled"
|
||||
|
||||
@@ -21,6 +21,7 @@ from src.services.awooop_ansible_check_mode_service import (
|
||||
_post_apply_km_path_type,
|
||||
_post_apply_verification_result,
|
||||
_record_auto_repair_execution_receipt,
|
||||
_send_controlled_apply_telegram_receipt,
|
||||
build_ansible_apply_command,
|
||||
build_ansible_check_mode_claim_input,
|
||||
build_ansible_check_mode_command,
|
||||
@@ -28,6 +29,7 @@ from src.services.awooop_ansible_check_mode_service import (
|
||||
claim_pending_check_modes,
|
||||
detect_ansible_transport_blockers,
|
||||
recent_ansible_transport_blockers,
|
||||
run_controlled_apply_for_claim,
|
||||
)
|
||||
from src.services.awooop_truth_chain_service import (
|
||||
_ansible_playbook_roots,
|
||||
@@ -230,6 +232,30 @@ def test_truth_status_marks_open_incident_after_successful_execution() -> None:
|
||||
assert "incident_open_after_successful_execution" in status["blockers"]
|
||||
|
||||
|
||||
def test_truth_status_keeps_verified_controlled_apply_autonomous_when_incident_open() -> None:
|
||||
status = _truth_status(
|
||||
incident={
|
||||
"incident_id": "INC-OPEN-VERIFIED",
|
||||
"status": "INVESTIGATING",
|
||||
"verification_result": "success",
|
||||
},
|
||||
approvals=[{"status": "EXECUTION_SUCCESS", "action": "ansible controlled apply"}],
|
||||
evidence_rows=[{"sensors_attempted": 8, "sensors_succeeded": 6}],
|
||||
automation_ops=[],
|
||||
drift=None,
|
||||
drift_repeat_count=0,
|
||||
gateway_mcp_total=3,
|
||||
legacy_mcp_total=2,
|
||||
outbound_visible_total=1,
|
||||
auto_repair_executions=[{"success": True}],
|
||||
)
|
||||
|
||||
assert status["current_stage"] == "execution_succeeded"
|
||||
assert status["stage_status"] == "success"
|
||||
assert status["needs_human"] is False
|
||||
assert "incident_open_after_successful_execution" in status["blockers"]
|
||||
|
||||
|
||||
def test_truth_status_marks_repeated_pending_drift_as_human_needed() -> None:
|
||||
status = _truth_status(
|
||||
incident=None,
|
||||
@@ -1175,6 +1201,48 @@ def test_ansible_truth_surfaces_audited_check_mode_record() -> None:
|
||||
)
|
||||
|
||||
|
||||
def test_ansible_truth_marks_worker_apply_as_controlled_apply() -> None:
|
||||
truth = build_ansible_truth(
|
||||
[
|
||||
{
|
||||
"op_id": "apply-op-1",
|
||||
"operation_type": "ansible_apply_executed",
|
||||
"status": "success",
|
||||
"actor": "ansible_controlled_apply_worker",
|
||||
"input_catalog_id": "ansible:188-momo-backup-user",
|
||||
"input_execution_mode": "controlled_apply",
|
||||
"input_playbook_path": "infra/ansible/playbooks/188-momo-backup-user.yml",
|
||||
"input_apply_enabled": "true",
|
||||
"output_apply_executed": "true",
|
||||
"output_returncode": "0",
|
||||
"tags": ["ansible", "controlled_apply", "low", "ai_agent_auto_execution"],
|
||||
},
|
||||
{
|
||||
"op_id": "check-op-1",
|
||||
"operation_type": "ansible_check_mode_executed",
|
||||
"status": "success",
|
||||
"actor": "ansible_check_mode_worker",
|
||||
"input_catalog_id": "ansible:188-momo-backup-user",
|
||||
"input_execution_mode": "check_mode",
|
||||
"input_playbook_path": "infra/ansible/playbooks/188-momo-backup-user.yml",
|
||||
"dry_run_result": {"check_mode_executed": True, "apply_executed": False},
|
||||
"tags": ["ansible", "check_mode", "controlled_apply_allowed"],
|
||||
},
|
||||
],
|
||||
incident={"incident_id": "INC-MOMO", "alertname": "MomoPostgresBackupFailed"},
|
||||
drift=None,
|
||||
)
|
||||
|
||||
summary = truth["summary"]
|
||||
assert summary["check_mode_total"] == 1
|
||||
assert summary["apply_total"] == 1
|
||||
assert summary["applied"] is True
|
||||
assert summary["controlled_apply"] is True
|
||||
assert summary["latest_actor"] == "ansible_controlled_apply_worker"
|
||||
assert summary["latest_execution_mode"] == "controlled_apply"
|
||||
assert summary["latest_apply_executed"] is True
|
||||
|
||||
|
||||
def test_ansible_truth_keeps_catalog_hint_separate_from_runtime_use() -> None:
|
||||
truth = build_ansible_truth(
|
||||
[],
|
||||
@@ -1548,6 +1616,16 @@ def test_ansible_apply_receipt_backfill_includes_verifier_and_km_gaps() -> None:
|
||||
assert "_record_post_apply_verifier_and_learning" in source
|
||||
|
||||
|
||||
def test_ansible_live_controlled_apply_sends_telegram_receipt_but_backfill_does_not() -> None:
|
||||
live_source = inspect.getsource(run_controlled_apply_for_claim)
|
||||
backfill_source = inspect.getsource(backfill_missing_auto_repair_execution_receipts_once)
|
||||
|
||||
assert "_send_controlled_apply_telegram_receipt" in live_source
|
||||
assert "telegram_receipt_sent" in live_source
|
||||
assert "_send_controlled_apply_telegram_receipt" not in backfill_source
|
||||
assert inspect.iscoroutinefunction(_send_controlled_apply_telegram_receipt)
|
||||
|
||||
|
||||
def test_ansible_post_apply_verifier_helpers_are_deterministic() -> None:
|
||||
assert _post_apply_km_path_type("03ca6836-1b76-4da2-8e3e-6d3b6df9254a") == (
|
||||
"ansible_apply_receipt:03ca6836"
|
||||
|
||||
@@ -8,6 +8,7 @@ import pytest
|
||||
|
||||
from src.services.github_target_private_backup_evidence_gate import (
|
||||
load_latest_github_target_private_backup_evidence_gate,
|
||||
preflight_github_target_owner_response_submission,
|
||||
)
|
||||
from src.services.snapshot_paths import default_security_dir
|
||||
|
||||
@@ -17,23 +18,67 @@ def test_load_github_target_private_backup_evidence_gate_from_committed_snapshot
|
||||
|
||||
assert snapshot["schema_version"] == "github_target_private_backup_evidence_gate_v1"
|
||||
assert snapshot["mode"] == "read_only_private_backup_evidence_gate"
|
||||
assert snapshot["status"] == "blocked_private_visibility_and_safe_credential_evidence_required"
|
||||
assert (
|
||||
snapshot["status"]
|
||||
== "blocked_private_visibility_and_safe_credential_evidence_required"
|
||||
)
|
||||
assert snapshot["summary"]["target_decision_count"] == 10
|
||||
assert snapshot["summary"]["approval_required_target_count"] == 9
|
||||
assert snapshot["summary"]["github_connector_owner_visible_repository_count"] == 4
|
||||
assert snapshot["summary"]["github_connector_owner_visible_private_repository_count"] == 4
|
||||
assert (
|
||||
snapshot["summary"]["github_connector_owner_visible_private_repository_count"]
|
||||
== 4
|
||||
)
|
||||
assert snapshot["summary"]["github_connector_readback_count"] == 9
|
||||
assert snapshot["summary"]["github_connector_private_visibility_count"] == 4
|
||||
assert snapshot["summary"]["github_connector_not_found_or_inaccessible_count"] == 5
|
||||
assert snapshot["summary"]["github_missing_target_resolution_count"] == 5
|
||||
assert snapshot["summary"]["github_missing_target_gitea_source_candidate_count"] == 3
|
||||
assert snapshot["summary"]["github_missing_target_internal_remote_source_candidate_count"] == 2
|
||||
assert snapshot["summary"]["github_missing_target_local_worktree_candidate_count"] == 5
|
||||
assert snapshot["summary"]["github_missing_target_canonical_source_ambiguous_count"] == 5
|
||||
assert snapshot["summary"]["github_missing_target_create_private_repo_ready_count"] == 0
|
||||
assert (
|
||||
snapshot["summary"]["github_missing_target_gitea_source_candidate_count"] == 3
|
||||
)
|
||||
assert (
|
||||
snapshot["summary"][
|
||||
"github_missing_target_internal_remote_source_candidate_count"
|
||||
]
|
||||
== 2
|
||||
)
|
||||
assert (
|
||||
snapshot["summary"]["github_missing_target_local_worktree_candidate_count"] == 5
|
||||
)
|
||||
assert (
|
||||
snapshot["summary"]["github_missing_target_canonical_source_ambiguous_count"]
|
||||
== 5
|
||||
)
|
||||
assert (
|
||||
snapshot["summary"]["github_missing_target_create_private_repo_ready_count"]
|
||||
== 0
|
||||
)
|
||||
assert snapshot["summary"]["github_missing_target_refs_sync_ready_count"] == 0
|
||||
assert snapshot["summary"]["private_backup_verified_count"] == 4
|
||||
assert snapshot["summary"]["private_visibility_verified_count"] == 4
|
||||
assert snapshot["summary"]["safe_credential_required_count"] == 9
|
||||
assert snapshot["summary"]["safe_credential_accepted_evidence_count"] == 0
|
||||
assert snapshot["summary"]["safe_credential_evidence_intake_ready"] is True
|
||||
assert (
|
||||
snapshot["summary"]["safe_credential_required_redacted_evidence_ref_count"] == 9
|
||||
)
|
||||
assert snapshot["summary"]["safe_credential_evidence_ref_rule_count"] == 5
|
||||
assert snapshot["summary"]["safe_credential_redaction_example_count"] == 5
|
||||
assert snapshot["summary"]["safe_credential_forbidden_payload_count"] == 15
|
||||
assert snapshot["summary"]["safe_credential_quarantine_lane_count"] == 1
|
||||
assert snapshot["summary"]["safe_credential_raw_payload_storage_allowed"] is False
|
||||
assert snapshot["summary"]["owner_response_request_ready"] is True
|
||||
assert snapshot["summary"]["owner_response_required_response_item_count"] == 9
|
||||
assert snapshot["summary"]["owner_response_requested_template_count"] == 9
|
||||
assert snapshot["summary"]["owner_response_template_count"] == 9
|
||||
assert snapshot["summary"]["owner_response_allowed_response_field_count"] == 25
|
||||
assert snapshot["summary"]["owner_response_forbidden_payload_count"] == 15
|
||||
assert snapshot["summary"]["owner_response_collection_check_count"] == 6
|
||||
assert snapshot["summary"]["owner_response_intake_preflight_check_count"] == 6
|
||||
assert snapshot["summary"]["owner_response_request_execution_authorized"] is False
|
||||
assert (
|
||||
snapshot["summary"]["github_target_owner_response_handoff_not_approval"] is True
|
||||
)
|
||||
assert snapshot["summary"]["blocked_target_count"] == 9
|
||||
assert snapshot["summary"]["public_repo_allowed"] is False
|
||||
assert snapshot["summary"]["repo_creation_authorized"] is False
|
||||
@@ -45,6 +90,58 @@ def test_load_github_target_private_backup_evidence_gate_from_committed_snapshot
|
||||
assert snapshot["operation_boundaries"]["visibility_change_allowed"] is False
|
||||
assert snapshot["operation_boundaries"]["refs_sync_allowed"] is False
|
||||
assert snapshot["operation_boundaries"]["secret_value_collection_allowed"] is False
|
||||
intake = snapshot["owner_response_intake_readiness"]
|
||||
assert (
|
||||
intake["status"]
|
||||
== "ready_to_collect_read_only_owner_response_not_authorization"
|
||||
)
|
||||
assert intake["request_ready"] is True
|
||||
assert intake["required_response_item_count"] == 9
|
||||
assert intake["requested_template_count"] == 9
|
||||
assert intake["response_template_count"] == 9
|
||||
assert intake["allowed_response_field_count"] == 25
|
||||
assert intake["forbidden_payload_count"] == 15
|
||||
assert intake["collection_check_count"] == 6
|
||||
assert intake["intake_preflight_check_count"] == 6
|
||||
assert intake["acceptance_check_count"] == 8
|
||||
assert intake["execution_authorized"] is False
|
||||
assert intake["not_approval"] is True
|
||||
assert "private_clone_url_credential" in intake["forbidden_payloads"]
|
||||
assert "read_only_markdown_response" in intake["allowed_submission_modes"]
|
||||
assert "create_github_repo" in intake["still_forbidden"]
|
||||
safe_credential_intake = snapshot["safe_credential_evidence_intake_readiness"]
|
||||
assert (
|
||||
safe_credential_intake["status"]
|
||||
== "ready_to_collect_redacted_evidence_refs_not_credentials"
|
||||
)
|
||||
assert safe_credential_intake["intake_ready"] is True
|
||||
assert safe_credential_intake["required_target_count"] == 9
|
||||
assert safe_credential_intake["accepted_evidence_count"] == 0
|
||||
assert safe_credential_intake["required_redacted_evidence_ref_count"] == 9
|
||||
assert safe_credential_intake["evidence_ref_rule_count"] == 5
|
||||
assert safe_credential_intake["redaction_example_count"] == 5
|
||||
assert safe_credential_intake["forbidden_payload_count"] == 15
|
||||
assert safe_credential_intake["quarantine_lane_count"] == 1
|
||||
assert "quarantine_sensitive_payload" in safe_credential_intake["quarantine_lanes"]
|
||||
assert safe_credential_intake["stored_raw_payload_allowed"] is False
|
||||
assert safe_credential_intake["secret_value_collection_allowed"] is False
|
||||
assert safe_credential_intake["private_clone_url_collection_allowed"] is False
|
||||
assert safe_credential_intake["credential_value_collection_allowed"] is False
|
||||
assert safe_credential_intake["execution_authorized"] is False
|
||||
assert safe_credential_intake["not_approval"] is True
|
||||
assert (
|
||||
"private_clone_url_credential" in safe_credential_intake["forbidden_payloads"]
|
||||
)
|
||||
assert "repo_archive" in safe_credential_intake["forbidden_payloads"]
|
||||
assert "git_object_pack" in safe_credential_intake["forbidden_payloads"]
|
||||
assert (
|
||||
"redacted_metadata_pointer"
|
||||
in safe_credential_intake["allowed_evidence_ref_types"]
|
||||
)
|
||||
first_redaction_example = safe_credential_intake["redaction_examples"][0]
|
||||
assert first_redaction_example["example_id"] == "redaction-github-target-doc-ref"
|
||||
assert first_redaction_example["stored_raw_payload_allowed"] is False
|
||||
assert first_redaction_example["execution_authorized"] is False
|
||||
|
||||
targets = {target["github_repo"]: target for target in snapshot["targets"]}
|
||||
assert targets["owenhytsai/awoooi"]["visibility_evidence_status"] == (
|
||||
@@ -52,6 +149,41 @@ def test_load_github_target_private_backup_evidence_gate_from_committed_snapshot
|
||||
)
|
||||
assert targets["owenhytsai/awoooi"]["private_backup_verified"] is True
|
||||
assert targets["owenhytsai/awoooi"]["github_connector_visibility"] == "private"
|
||||
assert (
|
||||
targets["owenhytsai/awoooi"]["owner_response_template_id"]
|
||||
== "target-awoooi-refs-blocked"
|
||||
)
|
||||
assert (
|
||||
targets["owenhytsai/awoooi"]["owner_response_submission_status"]
|
||||
== "waiting_owner_response"
|
||||
)
|
||||
assert (
|
||||
targets["owenhytsai/awoooi"]["safe_credential_evidence_submission_status"]
|
||||
== "waiting_redacted_evidence_ref"
|
||||
)
|
||||
assert (
|
||||
"redacted_metadata_pointer"
|
||||
in targets["owenhytsai/awoooi"]["safe_credential_allowed_evidence_ref_types"]
|
||||
)
|
||||
assert (
|
||||
targets["owenhytsai/awoooi"]["safe_credential_raw_payload_storage_allowed"]
|
||||
is False
|
||||
)
|
||||
assert (
|
||||
targets["owenhytsai/awoooi"][
|
||||
"safe_credential_private_clone_url_collection_allowed"
|
||||
]
|
||||
is False
|
||||
)
|
||||
assert (
|
||||
targets["owenhytsai/awoooi"]["safe_credential_secret_value_collection_allowed"]
|
||||
is False
|
||||
)
|
||||
assert targets["owenhytsai/awoooi"]["owner_response_execution_authorized"] is False
|
||||
assert (
|
||||
"canonical_source"
|
||||
in targets["owenhytsai/awoooi"]["owner_response_required_fields"]
|
||||
)
|
||||
assert targets["owenhytsai/ewoooc"]["visibility_evidence_status"] == (
|
||||
"blocked_private_or_absent_not_verified"
|
||||
)
|
||||
@@ -59,21 +191,49 @@ def test_load_github_target_private_backup_evidence_gate_from_committed_snapshot
|
||||
"blocked_canonical_source_ambiguous"
|
||||
)
|
||||
assert targets["owenhytsai/ewoooc"]["missing_target_gitea_repo"] == "wooo/ewoooc"
|
||||
assert targets["owenhytsai/ewoooc"]["missing_target_create_private_repo_ready"] is False
|
||||
assert (
|
||||
targets["owenhytsai/ewoooc"]["missing_target_create_private_repo_ready"]
|
||||
is False
|
||||
)
|
||||
assert targets["owenhytsai/ewoooc"]["missing_target_refs_sync_ready"] is False
|
||||
assert targets["owenhytsai/ewoooc"]["private_backup_verified"] is False
|
||||
assert targets["owenhytsai/VibeWork"]["missing_target_source_resolution_status"] == (
|
||||
"blocked_product_boundary_and_local_divergence"
|
||||
assert (
|
||||
targets["owenhytsai/ewoooc"]["owner_response_template_id"]
|
||||
== "target-ewoooc-private-or-new"
|
||||
)
|
||||
assert (
|
||||
"approve_new_target_creation_candidate"
|
||||
in targets["owenhytsai/ewoooc"]["owner_response_acceptable_decisions"]
|
||||
)
|
||||
assert targets["owenhytsai/VibeWork"][
|
||||
"missing_target_source_resolution_status"
|
||||
] == ("blocked_product_boundary_and_local_divergence")
|
||||
assert (
|
||||
targets["owenhytsai/VibeWork"]["missing_target_gitea_repo"] == "wooo/vibework"
|
||||
)
|
||||
assert targets["owenhytsai/VibeWork"]["missing_target_gitea_repo"] == "wooo/vibework"
|
||||
assert targets["nexu-io/open-design"]["visibility_evidence_status"] == (
|
||||
"external_scope_not_backup_target"
|
||||
)
|
||||
assert targets["nexu-io/open-design"]["owner_response_template_id"] is None
|
||||
assert (
|
||||
targets["nexu-io/open-design"]["owner_response_submission_status"]
|
||||
== "not_required"
|
||||
)
|
||||
assert (
|
||||
targets["nexu-io/open-design"]["safe_credential_evidence_submission_status"]
|
||||
== "not_required"
|
||||
)
|
||||
assert (
|
||||
targets["nexu-io/open-design"]["safe_credential_allowed_evidence_ref_types"]
|
||||
== []
|
||||
)
|
||||
|
||||
|
||||
def test_github_target_private_backup_gate_rejects_runtime_authorization(tmp_path):
|
||||
_copy_security_snapshots(tmp_path)
|
||||
owner_response_path = tmp_path / "github-target-owner-decision-response.snapshot.json"
|
||||
owner_response_path = (
|
||||
tmp_path / "github-target-owner-decision-response.snapshot.json"
|
||||
)
|
||||
owner_response = json.loads(owner_response_path.read_text(encoding="utf-8"))
|
||||
owner_response["summary"]["repo_creation_authorized"] = True
|
||||
owner_response_path.write_text(json.dumps(owner_response), encoding="utf-8")
|
||||
@@ -82,7 +242,63 @@ def test_github_target_private_backup_gate_rejects_runtime_authorization(tmp_pat
|
||||
load_latest_github_target_private_backup_evidence_gate(tmp_path)
|
||||
|
||||
|
||||
def test_github_target_private_backup_gate_requires_decision_rollup_consistency(tmp_path):
|
||||
def test_github_target_private_backup_gate_rejects_owner_response_execution_packet(
|
||||
tmp_path,
|
||||
):
|
||||
_copy_security_snapshots(tmp_path)
|
||||
owner_response_path = (
|
||||
tmp_path / "github-target-owner-decision-response.snapshot.json"
|
||||
)
|
||||
owner_response = json.loads(owner_response_path.read_text(encoding="utf-8"))
|
||||
owner_response["owner_response_request_packet"]["execution_authorized"] = True
|
||||
owner_response_path.write_text(json.dumps(owner_response), encoding="utf-8")
|
||||
|
||||
with pytest.raises(ValueError, match="owner response request packet"):
|
||||
load_latest_github_target_private_backup_evidence_gate(tmp_path)
|
||||
|
||||
|
||||
def test_github_target_private_backup_gate_rejects_raw_payload_redaction_example(
|
||||
tmp_path,
|
||||
):
|
||||
_copy_security_snapshots(tmp_path)
|
||||
owner_response_path = (
|
||||
tmp_path / "github-target-owner-decision-response.snapshot.json"
|
||||
)
|
||||
owner_response = json.loads(owner_response_path.read_text(encoding="utf-8"))
|
||||
owner_response["owner_response_redaction_examples"][0][
|
||||
"stored_raw_payload_allowed"
|
||||
] = True
|
||||
owner_response_path.write_text(json.dumps(owner_response), encoding="utf-8")
|
||||
|
||||
with pytest.raises(ValueError, match="redaction examples"):
|
||||
load_latest_github_target_private_backup_evidence_gate(tmp_path)
|
||||
|
||||
|
||||
def test_github_target_private_backup_gate_requires_safe_credential_blockers(
|
||||
tmp_path,
|
||||
):
|
||||
_copy_security_snapshots(tmp_path)
|
||||
owner_response_path = (
|
||||
tmp_path / "github-target-owner-decision-response.snapshot.json"
|
||||
)
|
||||
owner_response = json.loads(owner_response_path.read_text(encoding="utf-8"))
|
||||
forbidden_payloads = owner_response["owner_response_request_packet"][
|
||||
"forbidden_payloads"
|
||||
]
|
||||
owner_response["owner_response_request_packet"]["forbidden_payloads"] = [
|
||||
payload
|
||||
for payload in forbidden_payloads
|
||||
if payload != "private_clone_url_credential"
|
||||
]
|
||||
owner_response_path.write_text(json.dumps(owner_response), encoding="utf-8")
|
||||
|
||||
with pytest.raises(ValueError, match="forbidden payloads"):
|
||||
load_latest_github_target_private_backup_evidence_gate(tmp_path)
|
||||
|
||||
|
||||
def test_github_target_private_backup_gate_requires_decision_rollup_consistency(
|
||||
tmp_path,
|
||||
):
|
||||
_copy_security_snapshots(tmp_path)
|
||||
decision_path = tmp_path / "github-target-decision.snapshot.json"
|
||||
decision = json.loads(decision_path.read_text(encoding="utf-8"))
|
||||
@@ -100,10 +316,68 @@ def test_github_target_private_backup_gate_rejects_missing_source_write_flags(tm
|
||||
readiness["summary"]["repo_creation_performed"] = True
|
||||
readiness_path.write_text(json.dumps(readiness), encoding="utf-8")
|
||||
|
||||
with pytest.raises(ValueError, match="missing source readiness must remain read-only"):
|
||||
with pytest.raises(
|
||||
ValueError, match="missing source readiness must remain read-only"
|
||||
):
|
||||
load_latest_github_target_private_backup_evidence_gate(tmp_path)
|
||||
|
||||
|
||||
def test_github_target_owner_response_preflight_accepts_redacted_evidence_refs():
|
||||
preflight = preflight_github_target_owner_response_submission(
|
||||
_valid_owner_response_submission()
|
||||
)
|
||||
|
||||
assert (
|
||||
preflight["schema_version"]
|
||||
== "github_target_owner_response_intake_preflight_v1"
|
||||
)
|
||||
assert preflight["status"] == "ready_for_read_only_owner_response_intake"
|
||||
assert preflight["mode"] == "validate_owner_response_only_no_persist_no_github_write"
|
||||
assert preflight["summary"]["candidate_response_item_count"] == 1
|
||||
assert preflight["summary"]["preflight_passed_response_item_count"] == 1
|
||||
assert preflight["summary"]["preflight_blocked_response_item_count"] == 0
|
||||
assert preflight["summary"]["owner_response_received_count"] == 0
|
||||
assert preflight["summary"]["owner_response_accepted_count"] == 0
|
||||
assert preflight["summary"]["safe_credential_accepted_evidence_count"] == 0
|
||||
assert preflight["summary"]["github_api_write_allowed"] is False
|
||||
assert preflight["summary"]["repo_creation_authorized"] is False
|
||||
assert preflight["summary"]["refs_sync_authorized"] is False
|
||||
assert preflight["operation_boundaries"]["persist_submission_allowed"] is False
|
||||
assert preflight["operation_boundaries"]["github_api_write_allowed"] is False
|
||||
assert preflight["operation_boundaries"]["private_clone_url_collection_allowed"] is False
|
||||
assert preflight["authorization_flags"]["owner_response_execution_authorized"] is False
|
||||
assert preflight["responses"][0]["accepted_for_read_only_intake"] is True
|
||||
assert preflight["responses"][0]["owner_response_received"] is False
|
||||
assert preflight["responses"][0]["owner_response_accepted"] is False
|
||||
|
||||
|
||||
def test_github_target_owner_response_preflight_blocks_credentials_and_commands():
|
||||
submission = _valid_owner_response_submission()
|
||||
submission["responses"][0]["private_clone_url_credential"] = (
|
||||
"https://owner:ghp_1234567890abcdefghijklmnopqrstu@github.com/owenhytsai/awoooi.git"
|
||||
)
|
||||
submission["responses"][0]["repo_creation_command"] = (
|
||||
"gh repo create owenhytsai/awoooi --private"
|
||||
)
|
||||
|
||||
preflight = preflight_github_target_owner_response_submission(submission)
|
||||
|
||||
assert preflight["status"] == "blocked_owner_response_intake_preflight"
|
||||
assert preflight["summary"]["candidate_response_item_count"] == 1
|
||||
assert preflight["summary"]["preflight_passed_response_item_count"] == 0
|
||||
assert preflight["summary"]["preflight_blocked_response_item_count"] == 1
|
||||
assert preflight["summary"]["forbidden_payload_hit_count"] >= 3
|
||||
assert preflight["summary"]["owner_response_received_count"] == 0
|
||||
assert preflight["summary"]["owner_response_accepted_count"] == 0
|
||||
assert preflight["summary"]["github_api_write_allowed"] is False
|
||||
response = preflight["responses"][0]
|
||||
assert response["accepted_for_read_only_intake"] is False
|
||||
assert "forbidden_payload_detected" in response["blockers"]
|
||||
assert "unsupported_response_fields" in response["blockers"]
|
||||
assert response["execution_authorized"] is False
|
||||
assert response["repo_creation_authorized"] is False
|
||||
|
||||
|
||||
def _copy_security_snapshots(tmp_path: Path) -> None:
|
||||
source_dir = default_security_dir(Path(__file__))
|
||||
for filename in (
|
||||
@@ -115,3 +389,34 @@ def _copy_security_snapshots(tmp_path: Path) -> None:
|
||||
"github-target-missing-source-readiness.snapshot.json",
|
||||
):
|
||||
shutil.copy(source_dir / filename, tmp_path / filename)
|
||||
|
||||
|
||||
def _valid_owner_response_submission() -> dict[str, object]:
|
||||
return {
|
||||
"submission_mode": "read_only_markdown_response",
|
||||
"responses": [
|
||||
{
|
||||
"template_id": "target-awoooi-refs-blocked",
|
||||
"github_repo": "owenhytsai/awoooi",
|
||||
"owner_role_or_team": "platform-owner",
|
||||
"decision": "hold_pending_refs_truth",
|
||||
"decision_reason": "Need refs truth review before any sync action.",
|
||||
"affected_scope": "awoooi github backup target",
|
||||
"redacted_evidence_refs": [
|
||||
"docs/security/GITEA-GITHUB-MIGRATION-SNAPSHOT.md",
|
||||
"docs/security/source-control-ref-detail-diff.snapshot.json",
|
||||
],
|
||||
"evidence_refs": [
|
||||
"docs/security/source-control-workflow-secret-name-inventory.snapshot.json"
|
||||
],
|
||||
"followup_owner": "platform-owner",
|
||||
"rollback_owner": "platform-owner",
|
||||
"maintenance_window": "not_authorized",
|
||||
"validation_plan": "read-only refs truth review only",
|
||||
"canonical_source": "gitea_main",
|
||||
"github_target_disposition": "existing_private_candidate",
|
||||
"visibility_review_owner": "platform-owner",
|
||||
"refs_truth_review_owner": "platform-owner",
|
||||
}
|
||||
],
|
||||
}
|
||||
|
||||
@@ -27,6 +27,20 @@ def test_github_target_private_backup_evidence_gate_endpoint_returns_read_only_g
|
||||
assert data["summary"]["github_missing_target_refs_sync_ready_count"] == 0
|
||||
assert data["summary"]["private_backup_verified_count"] == 4
|
||||
assert data["summary"]["private_visibility_verified_count"] == 4
|
||||
assert data["summary"]["safe_credential_evidence_intake_ready"] is True
|
||||
assert data["summary"]["safe_credential_required_redacted_evidence_ref_count"] == 9
|
||||
assert data["summary"]["safe_credential_evidence_ref_rule_count"] == 5
|
||||
assert data["summary"]["safe_credential_redaction_example_count"] == 5
|
||||
assert data["summary"]["safe_credential_forbidden_payload_count"] == 15
|
||||
assert data["summary"]["safe_credential_quarantine_lane_count"] == 1
|
||||
assert data["summary"]["safe_credential_raw_payload_storage_allowed"] is False
|
||||
assert data["summary"]["owner_response_request_ready"] is True
|
||||
assert data["summary"]["owner_response_required_response_item_count"] == 9
|
||||
assert data["summary"]["owner_response_requested_template_count"] == 9
|
||||
assert data["summary"]["owner_response_template_count"] == 9
|
||||
assert data["summary"]["owner_response_collection_check_count"] == 6
|
||||
assert data["summary"]["owner_response_intake_preflight_check_count"] == 6
|
||||
assert data["summary"]["owner_response_request_execution_authorized"] is False
|
||||
assert data["summary"]["blocked_target_count"] == 9
|
||||
assert data["summary"]["public_repo_allowed"] is False
|
||||
assert data["summary"]["repo_creation_authorized"] is False
|
||||
@@ -40,4 +54,98 @@ def test_github_target_private_backup_evidence_gate_endpoint_returns_read_only_g
|
||||
assert data["operation_boundaries"]["refs_sync_allowed"] is False
|
||||
assert data["operation_boundaries"]["workflow_trigger_allowed"] is False
|
||||
assert data["operation_boundaries"]["private_clone_url_collection_allowed"] is False
|
||||
intake = data["owner_response_intake_readiness"]
|
||||
assert (
|
||||
intake["status"]
|
||||
== "ready_to_collect_read_only_owner_response_not_authorization"
|
||||
)
|
||||
assert intake["request_ready"] is True
|
||||
assert intake["requested_template_count"] == 9
|
||||
assert intake["forbidden_payload_count"] == 15
|
||||
assert intake["execution_authorized"] is False
|
||||
assert intake["not_approval"] is True
|
||||
safe_credential_intake = data["safe_credential_evidence_intake_readiness"]
|
||||
assert (
|
||||
safe_credential_intake["status"]
|
||||
== "ready_to_collect_redacted_evidence_refs_not_credentials"
|
||||
)
|
||||
assert safe_credential_intake["intake_ready"] is True
|
||||
assert safe_credential_intake["required_target_count"] == 9
|
||||
assert safe_credential_intake["required_redacted_evidence_ref_count"] == 9
|
||||
assert safe_credential_intake["evidence_ref_rule_count"] == 5
|
||||
assert safe_credential_intake["redaction_example_count"] == 5
|
||||
assert safe_credential_intake["forbidden_payload_count"] == 15
|
||||
assert safe_credential_intake["quarantine_lane_count"] == 1
|
||||
assert safe_credential_intake["stored_raw_payload_allowed"] is False
|
||||
assert safe_credential_intake["private_clone_url_collection_allowed"] is False
|
||||
assert safe_credential_intake["secret_value_collection_allowed"] is False
|
||||
assert safe_credential_intake["execution_authorized"] is False
|
||||
assert safe_credential_intake["not_approval"] is True
|
||||
assert data["targets"][0]["owner_response_execution_authorized"] is False
|
||||
assert (
|
||||
data["targets"][0]["safe_credential_evidence_submission_status"]
|
||||
== "waiting_redacted_evidence_ref"
|
||||
)
|
||||
assert data["targets"][0]["safe_credential_raw_payload_storage_allowed"] is False
|
||||
assert (
|
||||
data["targets"][0]["safe_credential_private_clone_url_collection_allowed"]
|
||||
is False
|
||||
)
|
||||
assert "192.168.0." not in response.text
|
||||
|
||||
|
||||
def test_github_target_owner_response_intake_preflight_endpoint_blocks_secrets():
|
||||
app = FastAPI()
|
||||
app.include_router(router, prefix="/api/v1")
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.post(
|
||||
"/api/v1/agents/github-target-owner-response-intake-preflight",
|
||||
json={
|
||||
"submission_mode": "read_only_markdown_response",
|
||||
"responses": [
|
||||
{
|
||||
"template_id": "target-awoooi-refs-blocked",
|
||||
"github_repo": "owenhytsai/awoooi",
|
||||
"owner_role_or_team": "platform-owner",
|
||||
"decision": "hold_pending_refs_truth",
|
||||
"decision_reason": "Need refs truth review before sync.",
|
||||
"affected_scope": "awoooi github backup target",
|
||||
"redacted_evidence_refs": [
|
||||
"docs/security/GITEA-GITHUB-MIGRATION-SNAPSHOT.md"
|
||||
],
|
||||
"evidence_refs": [
|
||||
"docs/security/source-control-ref-detail-diff.snapshot.json"
|
||||
],
|
||||
"followup_owner": "platform-owner",
|
||||
"rollback_owner": "platform-owner",
|
||||
"maintenance_window": "not_authorized",
|
||||
"validation_plan": "read-only refs truth review only",
|
||||
"canonical_source": "gitea_main",
|
||||
"github_target_disposition": "existing_private_candidate",
|
||||
"visibility_review_owner": "platform-owner",
|
||||
"refs_truth_review_owner": "platform-owner",
|
||||
"private_clone_url_credential": (
|
||||
"https://owner:ghp_1234567890abcdefghijklmnopqrstu@github.com/owenhytsai/awoooi.git"
|
||||
),
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["schema_version"] == "github_target_owner_response_intake_preflight_v1"
|
||||
assert data["status"] == "blocked_owner_response_intake_preflight"
|
||||
assert data["summary"]["preflight_passed_response_item_count"] == 0
|
||||
assert data["summary"]["preflight_blocked_response_item_count"] == 1
|
||||
assert data["summary"]["owner_response_received_count"] == 0
|
||||
assert data["summary"]["owner_response_accepted_count"] == 0
|
||||
assert data["summary"]["safe_credential_accepted_evidence_count"] == 0
|
||||
assert data["operation_boundaries"]["persist_submission_allowed"] is False
|
||||
assert data["operation_boundaries"]["github_api_write_allowed"] is False
|
||||
assert data["operation_boundaries"]["private_clone_url_collection_allowed"] is False
|
||||
assert data["authorization_flags"]["owner_response_execution_authorized"] is False
|
||||
assert data["responses"][0]["accepted_for_read_only_intake"] is False
|
||||
assert "forbidden_payload_detected" in data["responses"][0]["blockers"]
|
||||
assert "192.168.0." not in response.text
|
||||
|
||||
@@ -78,29 +78,31 @@ def _valid_owner_export() -> dict:
|
||||
}
|
||||
|
||||
|
||||
def test_iwooos_wazuh_manager_registry_reviewer_validation_contract_is_waiting_only() -> None:
|
||||
def test_iwooos_wazuh_manager_registry_reviewer_validation_contract_has_passed_redacted_export() -> None:
|
||||
payload = load_latest_iwooos_wazuh_manager_registry_reviewer_validation()
|
||||
|
||||
assert payload["schema_version"] == "iwooos_wazuh_manager_registry_reviewer_validation_readback_v1"
|
||||
assert payload["source_schema_version"] == "wazuh_manager_registry_reviewer_validation_v1"
|
||||
assert payload["status"] == "waiting_owner_registry_export_for_reviewer_validation"
|
||||
assert payload["mode"] == "committed_validation_contract_readback_no_runtime_no_secret_collection"
|
||||
assert payload["status"] == "post_enable_iwooos_readback_passed_no_runtime_no_secret_collection"
|
||||
assert payload["mode"] == "committed_post_enable_iwooos_readback_passed_no_runtime_no_secret_collection"
|
||||
assert payload["summary"]["expected_scope_alias_count"] == 6
|
||||
assert payload["summary"]["required_owner_field_count"] == 28
|
||||
assert payload["summary"]["per_host_required_field_count"] == 9
|
||||
assert payload["summary"]["reviewer_validation_check_count"] == 10
|
||||
assert payload["summary"]["outcome_lane_count"] == 13
|
||||
assert payload["summary"]["outcome_lane_count"] == 14
|
||||
assert payload["summary"]["evidence_slot_count"] == 6
|
||||
assert payload["summary"]["forbidden_payload_count"] == 27
|
||||
assert payload["summary"]["owner_registry_export_received_count"] == 0
|
||||
assert payload["summary"]["owner_registry_export_accepted_count"] == 0
|
||||
assert payload["summary"]["reviewer_validation_passed_count"] == 0
|
||||
assert payload["summary"]["owner_registry_export_received_count"] == 1
|
||||
assert payload["summary"]["owner_registry_export_accepted_count"] == 1
|
||||
assert payload["summary"]["reviewer_validation_ready_count"] == 1
|
||||
assert payload["summary"]["reviewer_validation_passed_count"] == 1
|
||||
assert payload["summary"]["post_enable_readback_passed_count"] == 1
|
||||
assert payload["summary"]["reviewer_validation_quarantined_count"] == 0
|
||||
assert payload["summary"]["manager_registry_accepted_count"] == 0
|
||||
assert payload["summary"]["runtime_gate_count"] == 0
|
||||
|
||||
|
||||
def test_iwooos_wazuh_manager_registry_reviewer_validation_evidence_slots_are_closed() -> None:
|
||||
def test_iwooos_wazuh_manager_registry_reviewer_validation_evidence_slots_are_accepted_only_for_posture() -> None:
|
||||
payload = load_latest_iwooos_wazuh_manager_registry_reviewer_validation()
|
||||
|
||||
assert [item["slot_id"] for item in payload["evidence_slots"]] == [
|
||||
@@ -111,9 +113,10 @@ def test_iwooos_wazuh_manager_registry_reviewer_validation_evidence_slots_are_cl
|
||||
"owner_response_and_rollback_owner",
|
||||
"post_enable_iwooos_readback",
|
||||
]
|
||||
assert all(item["received"] is False for item in payload["evidence_slots"])
|
||||
assert all(item["accepted"] is False for item in payload["evidence_slots"])
|
||||
assert all(item["received"] is True for item in payload["evidence_slots"])
|
||||
assert all(item["accepted"] is True for item in payload["evidence_slots"])
|
||||
assert all(item["quarantined"] is False for item in payload["evidence_slots"])
|
||||
assert all(item["next_gate"] == "manager_registry_acceptance_evidence_review" for item in payload["evidence_slots"])
|
||||
assert "managed_core_node_a" in payload["expected_scope_aliases"]
|
||||
assert "manager_registry_agent_counts" in [item["slot_id"] for item in payload["evidence_slots"]]
|
||||
|
||||
@@ -124,18 +127,28 @@ def test_iwooos_wazuh_manager_registry_reviewer_validation_api_is_public_safe()
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["schema_version"] == "iwooos_wazuh_manager_registry_reviewer_validation_readback_v1"
|
||||
assert data["summary"]["owner_registry_export_received_count"] == 0
|
||||
assert data["summary"]["owner_registry_export_accepted_count"] == 0
|
||||
assert data["summary"]["owner_registry_export_received_count"] == 1
|
||||
assert data["summary"]["owner_registry_export_accepted_count"] == 1
|
||||
assert data["summary"]["reviewer_validation_passed_count"] == 1
|
||||
assert data["summary"]["post_enable_readback_passed_count"] == 1
|
||||
assert data["summary"]["manager_registry_accepted_count"] == 0
|
||||
assert data["summary"]["runtime_gate_count"] == 0
|
||||
assert len(data["reviewer_validation_checks"]) == 10
|
||||
assert len(data["evidence_slots"]) == 6
|
||||
assert any(
|
||||
marker == "wazuh_manager_registry_reviewer_validation_owner_registry_export_received_count=0"
|
||||
marker == "wazuh_manager_registry_reviewer_validation_owner_registry_export_received_count=1"
|
||||
for marker in data["boundary_markers"]
|
||||
)
|
||||
assert any(
|
||||
marker == "wazuh_manager_registry_reviewer_validation_owner_registry_export_accepted_count=0"
|
||||
marker == "wazuh_manager_registry_reviewer_validation_owner_registry_export_accepted_count=1"
|
||||
for marker in data["boundary_markers"]
|
||||
)
|
||||
assert any(
|
||||
marker == "wazuh_manager_registry_reviewer_validation_passed_count=1"
|
||||
for marker in data["boundary_markers"]
|
||||
)
|
||||
assert any(
|
||||
marker == "wazuh_manager_registry_reviewer_validation_post_enable_readback_passed_count=1"
|
||||
for marker in data["boundary_markers"]
|
||||
)
|
||||
assert any(
|
||||
@@ -146,7 +159,7 @@ def test_iwooos_wazuh_manager_registry_reviewer_validation_api_is_public_safe()
|
||||
marker == "wazuh_manager_registry_reviewer_validation_runtime_gate_count=0"
|
||||
for marker in data["boundary_markers"]
|
||||
)
|
||||
assert any(rule.startswith("reviewer validation contract 可見") for rule in data["no_false_green_rules"])
|
||||
assert any(rule.startswith("reviewer validation passed") for rule in data["no_false_green_rules"])
|
||||
assert data["boundaries"]["runtime_execution_authorized"] is False
|
||||
assert data["boundaries"]["wazuh_active_response_authorized"] is False
|
||||
assert data["boundaries"]["host_write_authorized"] is False
|
||||
@@ -177,7 +190,7 @@ def test_iwooos_wazuh_manager_registry_owner_export_validation_accepts_redacted_
|
||||
assert all(slot["accepted"] is True for slot in payload["evidence_slots"])
|
||||
|
||||
|
||||
def test_iwooos_wazuh_manager_registry_owner_export_validation_api_does_not_update_global_counters() -> None:
|
||||
def test_iwooos_wazuh_manager_registry_owner_export_validation_api_does_not_persist_payload_or_open_runtime() -> None:
|
||||
client = _client()
|
||||
response = client.post(
|
||||
"/api/v1/iwooos/wazuh-manager-registry-reviewer-validation/validate-owner-export",
|
||||
@@ -192,8 +205,10 @@ def test_iwooos_wazuh_manager_registry_owner_export_validation_api_does_not_upda
|
||||
assert result["summary"]["runtime_gate_count"] == 0
|
||||
|
||||
readback = client.get("/api/v1/iwooos/wazuh-manager-registry-reviewer-validation").json()
|
||||
assert readback["summary"]["owner_registry_export_received_count"] == 0
|
||||
assert readback["summary"]["owner_registry_export_accepted_count"] == 0
|
||||
assert readback["summary"]["owner_registry_export_received_count"] == 1
|
||||
assert readback["summary"]["owner_registry_export_accepted_count"] == 1
|
||||
assert readback["summary"]["reviewer_validation_passed_count"] == 1
|
||||
assert readback["summary"]["post_enable_readback_passed_count"] == 1
|
||||
assert readback["summary"]["manager_registry_accepted_count"] == 0
|
||||
assert readback["summary"]["runtime_gate_count"] == 0
|
||||
|
||||
|
||||
@@ -1030,6 +1030,146 @@ def test_awooop_status_chain_lines_show_ansible_apply_proof() -> None:
|
||||
assert ansible["approval_source"] == "user_chat_approved_continue"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_controlled_apply_result_receipt_marks_callback_reply_evidence(monkeypatch) -> None:
|
||||
gateway = TelegramGateway()
|
||||
sent_requests = []
|
||||
|
||||
async def fake_send_request(method, payload):
|
||||
sent_requests.append((method, payload))
|
||||
return {"ok": True, "result": {"message_id": 12345}}
|
||||
|
||||
async def fake_fetch_truth_chain(source_id, project_id):
|
||||
assert source_id == "INC-20260627-64472B"
|
||||
assert project_id == "awoooi"
|
||||
return {
|
||||
"truth_status": {
|
||||
"current_stage": "execution_succeeded",
|
||||
"stage_status": "success",
|
||||
"needs_human": False,
|
||||
"blockers": [],
|
||||
},
|
||||
"automation_quality": {
|
||||
"verdict": "auto_repaired_verified",
|
||||
"facts": {
|
||||
"auto_repair_execution_records": 1,
|
||||
"automation_operation_records": 3,
|
||||
"effective_execution_records": 2,
|
||||
"verification_result": "success",
|
||||
"mcp_gateway_total": 8,
|
||||
"knowledge_entries": 1,
|
||||
},
|
||||
"blockers": [],
|
||||
},
|
||||
"execution": {
|
||||
"automation_operation_log": [
|
||||
{
|
||||
"operation_type": "ansible_apply_executed",
|
||||
"status": "success",
|
||||
"actor": "ansible_controlled_apply_worker",
|
||||
"input_catalog_id": "ansible:188-momo-backup-user",
|
||||
"input_execution_mode": "controlled_apply",
|
||||
"input_playbook_path": "infra/ansible/playbooks/188-momo-backup-user.yml",
|
||||
}
|
||||
],
|
||||
"ansible": {
|
||||
"considered": True,
|
||||
"records": [
|
||||
{
|
||||
"operation_type": "ansible_apply_executed",
|
||||
"status": "success",
|
||||
"actor": "ansible_controlled_apply_worker",
|
||||
"catalog_id": "ansible:188-momo-backup-user",
|
||||
"playbook_path": "infra/ansible/playbooks/188-momo-backup-user.yml",
|
||||
"execution_mode": "controlled_apply",
|
||||
"apply_executed": True,
|
||||
"returncode": 0,
|
||||
"tags": ["ansible", "controlled_apply", "ai_agent_auto_execution"],
|
||||
}
|
||||
],
|
||||
"candidate_catalog": {"candidates": []},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
async def fake_fetch_km_completion_summary(*, incident_id, project_id):
|
||||
assert incident_id == "INC-20260627-64472B"
|
||||
assert project_id == "awoooi"
|
||||
return {
|
||||
"schema_version": "km_stale_owner_review_completion_telegram_summary_v1",
|
||||
"status": "no_related_owner_review",
|
||||
"project_id": "awoooi",
|
||||
"incident_id": "INC-20260627-64472B",
|
||||
"missing_reason": "no_matching_completion_item",
|
||||
"ready_count": 10,
|
||||
"blocked_count": 0,
|
||||
"completed_count": 1,
|
||||
"failed_count": 0,
|
||||
"related_total": 0,
|
||||
"writes_on_read": False,
|
||||
"batch_writes_allowed": False,
|
||||
"manual_review_required": True,
|
||||
"triage": {
|
||||
"schema_version": "km_stale_callback_owner_review_triage_v1",
|
||||
"flow_stage": "callback_observed_owner_review_link_missing",
|
||||
"ai_lead_agent": "Hermes",
|
||||
"supporting_agents": ["OpenClaw"],
|
||||
"automation_state": "manual_owner_review_required",
|
||||
"safe_to_auto_repair": False,
|
||||
"blocking_reason": "no_matching_completion_item",
|
||||
"matching_strategy": "related_incident_id_exact_match",
|
||||
},
|
||||
}
|
||||
|
||||
monkeypatch.setattr(TelegramGateway, "alert_chat_id", property(lambda _self: "chat"))
|
||||
monkeypatch.setattr(gateway, "_send_request", fake_send_request)
|
||||
monkeypatch.setattr(
|
||||
"src.services.awooop_truth_chain_service.fetch_truth_chain",
|
||||
fake_fetch_truth_chain,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
"src.services.telegram_gateway._fetch_km_stale_completion_summary_for_incident",
|
||||
fake_fetch_km_completion_summary,
|
||||
)
|
||||
|
||||
result = await gateway.send_controlled_apply_result_receipt(
|
||||
incident_id="INC-20260627-64472B",
|
||||
catalog_id="ansible:188-momo-backup-user",
|
||||
apply_op_id="73b7a95c-3652-4c0d-bb4c-729e500acedb",
|
||||
playbook_path="infra/ansible/playbooks/188-momo-backup-user.yml",
|
||||
verification_result="success",
|
||||
returncode=0,
|
||||
duration_ms=7727,
|
||||
verifier_written=True,
|
||||
learning_written=True,
|
||||
project_id="awoooi",
|
||||
)
|
||||
|
||||
assert result["ok"] is True
|
||||
assert sent_requests
|
||||
method, payload = sent_requests[0]
|
||||
assert method == "sendMessage"
|
||||
assert "CONTROLLED APPLY RESULT" in payload["text"]
|
||||
assert "INC-20260627-64472B" in payload["text"]
|
||||
assert "ansible:188-momo-backup-user" in payload["text"]
|
||||
source_extra = payload["_awooop_source_envelope_extra"]
|
||||
assert source_extra["callback_reply"]["action"] == "controlled_apply_result"
|
||||
assert source_extra["callback_reply"]["status"] == "callback_reply_sent"
|
||||
assert source_extra["source_refs"]["incident_ids"] == ["INC-20260627-64472B"]
|
||||
km_snapshot = source_extra["km_stale_completion_summary"]
|
||||
assert (
|
||||
km_snapshot["source_schema_version"]
|
||||
== "km_stale_owner_review_completion_telegram_summary_v1"
|
||||
)
|
||||
assert km_snapshot["status"] == "no_related_owner_review"
|
||||
assert km_snapshot["ready_count"] == 10
|
||||
assert km_snapshot["triage"]["ai_lead_agent"] == "Hermes"
|
||||
snapshot = source_extra["awooop_status_chain"]
|
||||
assert snapshot["repair_state"] == "auto_repaired_verified"
|
||||
assert snapshot["operator_outcome"]["state"] == "completed_verified"
|
||||
assert snapshot["execution"]["ansible"]["controlled_apply"] is True
|
||||
|
||||
|
||||
def test_callback_reply_awooop_status_chain_snapshot_marks_manual_gate() -> None:
|
||||
"""Callback evidence 要保存當下 AwoooP 狀態鏈,不只保存 live query 結果。"""
|
||||
snapshot = telegram_gateway_module._callback_reply_awooop_status_chain_snapshot(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
5
apps/web/src/app/[locale]/awooop/alerts/page.tsx
Normal file
5
apps/web/src/app/[locale]/awooop/alerts/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default function AwoooPAlertsPage({ params }: { params: { locale: string } }) {
|
||||
redirect(`/${params.locale}/awooop/runs#ai-alert-card-delivery-readback`);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// =============================================================================
|
||||
// WOOO AIOps - AwoooP Approval Decision
|
||||
// =============================================================================
|
||||
// 人工審批必須回到 Run State / Timeline,避免 Approval 成為獨立孤島。
|
||||
// AI controlled approval must return to Run State / Timeline to avoid isolated Approval islands.
|
||||
|
||||
"use client";
|
||||
|
||||
|
||||
@@ -780,8 +780,8 @@ const LANE_CONFIG: Record<
|
||||
className: "border-[#9bb6d9] bg-[#eef5ff] text-[#1f5b9b]",
|
||||
},
|
||||
approval: {
|
||||
label: "人工閘門",
|
||||
detail: "等待 SRE 批准或拒絕",
|
||||
label: "AI 受控閘門",
|
||||
detail: "等待 policy、check-mode 與 verifier 判定",
|
||||
icon: ShieldCheck,
|
||||
className: "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]",
|
||||
},
|
||||
@@ -798,8 +798,8 @@ const LANE_CONFIG: Record<
|
||||
className: "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]",
|
||||
},
|
||||
manual: {
|
||||
label: "人工升級",
|
||||
detail: "AI 無法閉環,需人工處置",
|
||||
label: "AI 補齊升級",
|
||||
detail: "補 PlayBook、rollback、verifier 或 break-glass 證據",
|
||||
icon: TriangleAlert,
|
||||
className: "border-[#e2a29b] bg-[#fff0ef] text-[#9f2f25]",
|
||||
},
|
||||
|
||||
@@ -32,6 +32,21 @@ import { QueueTab } from './tabs/queue-tab'
|
||||
import { AgentMarketTab } from './tabs/agent-market-tab'
|
||||
import { AutomationInventoryTab } from './tabs/automation-inventory-tab'
|
||||
|
||||
const GOVERNANCE_SECTION_IDS = [
|
||||
'slo',
|
||||
'events',
|
||||
'queue',
|
||||
'agent-market',
|
||||
'automation-inventory',
|
||||
] as const
|
||||
|
||||
function normalizeGovernanceSectionId(value: string | null): string | undefined {
|
||||
if (!value) return undefined
|
||||
return GOVERNANCE_SECTION_IDS.includes(value as (typeof GOVERNANCE_SECTION_IDS)[number])
|
||||
? value
|
||||
: undefined
|
||||
}
|
||||
|
||||
export default function GovernancePage({
|
||||
params,
|
||||
}: {
|
||||
@@ -50,16 +65,32 @@ export default function GovernancePage({
|
||||
const activeSection = governanceSections.find(section => section.id === requestedTab) ?? governanceSections[0]
|
||||
|
||||
useEffect(() => {
|
||||
const tab = new URLSearchParams(window.location.search).get('tab') ?? undefined
|
||||
const hashTab = window.location.hash ? window.location.hash.slice(1) : null
|
||||
const tab = normalizeGovernanceSectionId(
|
||||
new URLSearchParams(window.location.search).get('tab') ?? hashTab
|
||||
)
|
||||
setRequestedTab(tab)
|
||||
|
||||
if (!tab) return
|
||||
const section = document.getElementById(tab)
|
||||
section?.scrollIntoView({ block: 'start' })
|
||||
let cancelled = false
|
||||
const scrollToRequestedSection = () => {
|
||||
if (cancelled) return
|
||||
document.getElementById(tab)?.scrollIntoView({ block: 'start' })
|
||||
}
|
||||
const frame = window.requestAnimationFrame(scrollToRequestedSection)
|
||||
const timers = [150, 600, 1500, 3000].map(delay =>
|
||||
window.setTimeout(scrollToRequestedSection, delay)
|
||||
)
|
||||
return () => {
|
||||
cancelled = true
|
||||
window.cancelAnimationFrame(frame)
|
||||
timers.forEach(timer => window.clearTimeout(timer))
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<AppLayout locale={params.locale}>
|
||||
<div className="min-w-0 overflow-x-hidden">
|
||||
{/* ComplianceBadge 橫幅 — PR 3 接 /governance/compliance-score API */}
|
||||
<GlassCard variant="subtle" padding="sm" className="mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -79,7 +110,7 @@ export default function GovernancePage({
|
||||
return (
|
||||
<Link
|
||||
key={section.id}
|
||||
href={`/${params.locale}/governance#${section.id}`}
|
||||
href={`/${params.locale}/governance?tab=${section.id}#${section.id}`}
|
||||
className="group min-w-0 border bg-white p-3 text-[#141413] no-underline transition hover:border-[#d97757] hover:bg-[#fffaf7]"
|
||||
style={{
|
||||
borderColor: selected ? '#d97757' : '#e0ddd4',
|
||||
@@ -125,6 +156,7 @@ export default function GovernancePage({
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</AppLayout>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2479,15 +2479,15 @@ const wazuhManagerRegistryReviewerValidationBoundaries = [
|
||||
'wazuh_manager_registry_reviewer_validation_required_owner_field_count=28',
|
||||
'wazuh_manager_registry_reviewer_validation_per_host_required_field_count=9',
|
||||
'wazuh_manager_registry_reviewer_validation_check_count=10',
|
||||
'wazuh_manager_registry_reviewer_validation_outcome_lane_count=13',
|
||||
'wazuh_manager_registry_reviewer_validation_outcome_lane_count=14',
|
||||
'wazuh_manager_registry_reviewer_validation_evidence_slot_count=6',
|
||||
'wazuh_manager_registry_reviewer_validation_forbidden_payload_count=27',
|
||||
'wazuh_manager_registry_reviewer_validation_owner_registry_export_received_count=0',
|
||||
'wazuh_manager_registry_reviewer_validation_owner_registry_export_accepted_count=0',
|
||||
'wazuh_manager_registry_reviewer_validation_passed_count=0',
|
||||
'wazuh_manager_registry_reviewer_validation_owner_registry_export_received_count=1',
|
||||
'wazuh_manager_registry_reviewer_validation_owner_registry_export_accepted_count=1',
|
||||
'wazuh_manager_registry_reviewer_validation_passed_count=1',
|
||||
'wazuh_manager_registry_reviewer_validation_quarantined_count=0',
|
||||
'wazuh_manager_registry_reviewer_validation_manager_registry_accepted_count=0',
|
||||
'wazuh_manager_registry_reviewer_validation_post_enable_readback_passed_count=0',
|
||||
'wazuh_manager_registry_reviewer_validation_post_enable_readback_passed_count=1',
|
||||
'wazuh_manager_registry_reviewer_validation_runtime_gate_count=0',
|
||||
'wazuh_api_live_query_authorized=false',
|
||||
'wazuh_agent_reenroll_authorized=false',
|
||||
@@ -9817,22 +9817,28 @@ function IwoooSWazuhManagerRegistryReviewerValidationBoard() {
|
||||
tone: 'steady',
|
||||
},
|
||||
{
|
||||
key: 'slots',
|
||||
value: summary ? String(summary.evidence_slot_count) : loading ? '...' : '6',
|
||||
key: 'passed',
|
||||
value: summary ? String(summary.reviewer_validation_passed_count) : loading ? '...' : '1',
|
||||
icon: ClipboardCheck,
|
||||
tone: 'warn',
|
||||
tone: 'steady',
|
||||
},
|
||||
{
|
||||
key: 'postEnable',
|
||||
value: summary ? String(summary.post_enable_readback_passed_count) : loading ? '...' : '1',
|
||||
icon: SearchCheck,
|
||||
tone: summary?.post_enable_readback_passed_count ? 'steady' : 'locked',
|
||||
},
|
||||
{
|
||||
key: 'received',
|
||||
value: summary ? String(summary.owner_registry_export_received_count) : loading ? '...' : '0',
|
||||
value: summary ? String(summary.owner_registry_export_received_count) : loading ? '...' : '1',
|
||||
icon: FileWarning,
|
||||
tone: 'locked',
|
||||
tone: summary?.owner_registry_export_received_count ? 'steady' : 'locked',
|
||||
},
|
||||
{
|
||||
key: 'accepted',
|
||||
value: summary ? String(summary.owner_registry_export_accepted_count) : loading ? '...' : '0',
|
||||
value: summary ? String(summary.owner_registry_export_accepted_count) : loading ? '...' : '1',
|
||||
icon: Lock,
|
||||
tone: 'locked',
|
||||
tone: summary?.owner_registry_export_accepted_count ? 'steady' : 'locked',
|
||||
},
|
||||
{
|
||||
key: 'runtime',
|
||||
@@ -9925,8 +9931,8 @@ function IwoooSWazuhManagerRegistryReviewerValidationBoard() {
|
||||
<code style={{ fontSize: 11, color: '#2f6265', fontWeight: 700, overflowWrap: 'anywhere' }}>{slot.slot_id}</code>
|
||||
<div style={{ marginTop: 7, fontSize: 12, color: '#141413', fontWeight: 700 }}>{slot.title}</div>
|
||||
<div style={{ marginTop: 7, display: 'grid', gap: 4, fontSize: 11, color: '#45686a' }}>
|
||||
<span>{t('slotReceivedLabel')}:0</span>
|
||||
<span>{t('slotAcceptedLabel')}:0</span>
|
||||
<span>{t('slotReceivedLabel')}:{slot.received ? '1' : '0'}</span>
|
||||
<span>{t('slotAcceptedLabel')}:{slot.accepted ? '1' : '0'}</span>
|
||||
<span>{t('slotNextGateLabel')}:{slot.next_gate}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
506
docs/LOGBOOK.md
506
docs/LOGBOOK.md
@@ -1,3 +1,83 @@
|
||||
## 2026-06-27 — 22:28 AwoooP manual gate payload residue 正式清零
|
||||
|
||||
**背景**:正式 `/zh-TW/awooop/approvals`、Runs、Work Items 的 HTML payload 仍因 Next / i18n namespace 序列化帶出 IwoooS 舊文案 `人工閘門`,每頁各命中 32 次;`/zh-TW/awooop/alerts` 也回 404。這會讓 AwoooP 看起來仍把人工 gate 當作 low / medium / high 的預設終局。
|
||||
|
||||
**完成內容**:
|
||||
- `apps/web/messages/zh-TW.json` 與 `apps/web/messages/en.json` 將 32 組 IwoooS payload 字串從 `人工閘門` 改為 `受控授權閘門`,保留 hard blocker / no-runtime-write 邊界,但不再使用人工 gate 作為產品預設語意。
|
||||
- 新增 `apps/web/src/app/[locale]/awooop/alerts/page.tsx`,將 `/awooop/alerts` 穩定導向 Runs 的 `#ai-alert-card-delivery-readback` 區段,避免 Alerts smoke 變成 404。
|
||||
|
||||
**本地驗證結果**:
|
||||
- JSON parse:`apps/web/messages/zh-TW.json`、`apps/web/messages/en.json` 通過。
|
||||
- i18n mirror:zh-TW / en key count `14468 / 14468`,missing `0 / 0`。
|
||||
- source 搜尋:AwoooP source 與 messages 內 `待人工決策`、`阻塞與人工閘門`、`人工接手`、`人工閘門`、`人工升級`、`owner review`、`owner packet` 命中 `0`。
|
||||
- `pnpm --filter @awoooi/web typecheck`:通過。
|
||||
- `git diff --check`:通過。
|
||||
|
||||
**Gitea / deploy 狀態**:
|
||||
- fix commit:`b2458b933 fix(awooop): remove manual gate payload residue`。
|
||||
- 後續整合 head:`a2733fd43 test(awooop): lock controlled apply receipt km snapshot`,包含 `b2458b933`。
|
||||
- deploy marker:`f98aaa8ee chore(cd): deploy a2733fd [skip ci]`。
|
||||
- Gitea `code-review.yaml #3705` 成功;Gitea `cd.yaml #3704` 成功並產出 deploy marker。
|
||||
|
||||
**production readback**:
|
||||
- `/api/v1/health?_v=f98aaa8ee-manual-residue-clean`:HTTP `200`、`status=healthy`、`environment=prod`、`mock_mode=false`。
|
||||
- HTML readback:Approvals / Runs / Work Items / Alerts 皆 HTTP `200`,舊詞 hit count 全部 `0`。
|
||||
- `/zh-TW/awooop/alerts` final URL 讀回 `/zh-TW/awooop/runs`,不再 404。
|
||||
- 新語意可讀:`待 AI 受控決策`、`阻塞與 AI 受控隊列`、`AI 處置包與工作項`、`受控執行邊界`、`受控授權閘門`、`controlled gate`、`controlled review`。
|
||||
|
||||
**desktop / mobile smoke**:
|
||||
- Chrome headless desktop `1366x900` 與 mobile `390x844`:Approvals / Runs / Work Items / Alerts 皆 HTTP `200`。
|
||||
- visible 舊詞 count `0`、HTML 舊詞 count `0`、`horizontalOverflow=false`。
|
||||
- Alerts final URL:`/zh-TW/awooop/runs#ai-alert-card-delivery-readback`。
|
||||
- Approvals 仍有既有未登入資源 `401` console error;本輪未新增,且不影響 HTML / visible text / layout smoke。
|
||||
|
||||
**未做與邊界**:
|
||||
- 沒有把 critical / secret / destructive / active scan / force push / repo visibility / refs mutation 放寬成自動執行。
|
||||
- 沒有 host / Docker / systemd / Nginx / firewall / K8s / DB / Wazuh runtime 寫操作;沒有讀 secret;沒有 force push。
|
||||
|
||||
**下一步**:
|
||||
- 若後續再清 IwoooS / security 歷史 payload,應只改產品顯示與 controlled / break-glass 語意,不把 `0 / false` 計數假性上修。
|
||||
|
||||
## 2026-06-27 — 21:56 GitHub owner response preflight API 正式讀回完成
|
||||
|
||||
**背景**:上一段已把 GitHub target owner response request packet / templates / forbidden payloads 暴露成 read-only readiness;本段不是文件補充,而是新增正式 POST preflight API,讓 owner-provided redacted evidence refs 可以先被驗證,同時拒收 credential URL、repo creation command、secret-like value 與未允許欄位。
|
||||
|
||||
**完成內容**:
|
||||
- 新增 `POST /api/v1/agents/github-target-owner-response-intake-preflight`。
|
||||
- API 只做 candidate owner response validation:`mode=validate_owner_response_only_no_persist_no_github_write`。
|
||||
- `apps/api/src/services/github_target_private_backup_evidence_gate.py` 新增 `preflight_github_target_owner_response_submission(...)`,依 committed templates / allowed fields / acceptable decisions / evidence refs / forbidden payloads 驗證。
|
||||
- 合法 `read_only_markdown_response` 可回 `ready_for_read_only_owner_response_intake`;含 private clone credential、GitHub token-like string、repo creation command、unsupported field 或 secret-like value 會回 `blocked_owner_response_intake_preflight`。
|
||||
- 回應固定經過 public LAN topology redaction;正式回應未命中 `192.168.0.`。
|
||||
|
||||
**本地驗證結果**:
|
||||
- `python3 -m py_compile apps/api/src/services/github_target_private_backup_evidence_gate.py apps/api/src/api/v1/agents.py`:通過。
|
||||
- `DATABASE_URL=sqlite:///test.db PYTHONPATH=apps/api python3.11 -m pytest apps/api/tests/test_github_target_private_backup_evidence_gate.py apps/api/tests/test_github_target_private_backup_evidence_gate_api.py apps/api/tests/test_delivery_closure_workbench_api.py -q`:`10 passed`。
|
||||
- `python3.11 -m ruff check apps/api/src/services/github_target_private_backup_evidence_gate.py apps/api/src/api/v1/agents.py apps/api/tests/test_github_target_private_backup_evidence_gate.py apps/api/tests/test_github_target_private_backup_evidence_gate_api.py`:通過。
|
||||
- `git diff --check`:通過。
|
||||
|
||||
**Gitea / deploy 狀態**:
|
||||
- code commit:`a68d9e40a feat(github): preflight owner response intake`。
|
||||
- deploy marker:`5b7bd55a9 chore(cd): deploy a68d9e4 [skip ci]`。
|
||||
- `code-review.yaml #3697` 成功;原 `cd.yaml #3696` 後續被更新的 main push supersede / cancel,但已產出 deploy marker。
|
||||
- 最新 `gitea-ssh/main=0624be08d` 已包含 `a68d9e40a`;目前尚未看到比 `5b7bd55a9` 更新且指向 `0624be08d` 的 deploy marker。
|
||||
|
||||
**production API readback**:
|
||||
- `/api/v1/health?_v=a68d9e40-owner-preflight-final`:HTTP `200`、`status=healthy`、`environment=prod`、`mock_mode=false`。
|
||||
- valid POST:HTTP `200`、schema `github_target_owner_response_intake_preflight_v1`、status `ready_for_read_only_owner_response_intake`、`candidate_response_item_count=1`、`preflight_passed_response_item_count=1`、`preflight_blocked_response_item_count=0`、`accepted_for_read_only_intake=true`。
|
||||
- dangerous POST:HTTP `200`、status `blocked_owner_response_intake_preflight`、`preflight_passed_response_item_count=0`、`preflight_blocked_response_item_count=1`、`forbidden_payload_hit_count=4`、blockers 包含 `forbidden_payload_detected` 與 `unsupported_response_fields`。
|
||||
|
||||
**仍維持 0 / false**:
|
||||
- valid / dangerous POST 皆維持 `owner_response_received_count=0`、`owner_response_accepted_count=0`、`safe_credential_accepted_evidence_count=0`。
|
||||
- `github_api_write_allowed=false`、`repo_creation_authorized=false`、`refs_sync_authorized=false`、`persist_submission_allowed=false`、`private_clone_url_collection_allowed=false`。
|
||||
|
||||
**未做**:
|
||||
- 沒有 GitHub repo creation、visibility change、refs sync、workflow trigger、private clone URL collection、secret value collection 或 persistence。
|
||||
- 沒有 host / Docker / systemd / Nginx / firewall / K8s / DB / Wazuh runtime 寫操作;沒有 force push。
|
||||
|
||||
**完成度 / 下一步**:
|
||||
- 本段「owner response intake preflight API / production readback」:`0% -> 100%`。
|
||||
- GitHub backup mirror governance 仍為 blocked;下一個 P0 是設計 no-secret persistence contract 或 workbench UI intake,但只能保存 redacted evidence refs 與 validation receipt,不能保存 private clone credential、secret value 或執行 GitHub write。
|
||||
|
||||
## 2026-06-27|MOMO daily-sales source absence readback 與 cold-start blocker
|
||||
|
||||
**背景**:110 runner / StockPlatform smoke 壓力已止血後,重新跑全主機 cold-start scorecard 與資料 freshness。AWOOOI / IwoooS / Stock / 188 主要 public routes 可用,但整體 cold-start 仍不能宣告 full green;目前主要業務資料 blocker 是 188 MOMO daily sales freshness。
|
||||
@@ -77,6 +157,241 @@
|
||||
- 本輪未重啟 Docker / Nginx / firewall / K3s,未 kill process,未讀 raw sessions / SQLite / auth / secret。
|
||||
- 下一個 P0:把 StockPlatform smoke 改成排程限流或搬到非 110 runner;再做全主機 cold-start scorecard 與資料 freshness readback。
|
||||
|
||||
## 2026-06-27|IwoooS Wazuh owner export reviewer validation passed 本地完成
|
||||
## 2026-06-27 — 21:45 IwoooS Wazuh reviewer post-enable readback 正式讀回完成
|
||||
|
||||
**時間與來源**:
|
||||
- 2026-06-27 21:24-21:45 Asia/Taipei。
|
||||
- 來源:feature branch `codex/iwooos-post-enable-readback-20260627`、Gitea main、Gitea Actions public HTML、production API / `/zh-TW/iwooos` desktop / mobile smoke。
|
||||
|
||||
**完成內容**:
|
||||
- `GET /api/v1/iwooos/wazuh-manager-registry-reviewer-validation` 已正式讀回 `post_enable_iwooos_readback_passed_no_runtime_no_secret_collection`。
|
||||
- API service 放寬 `post_enable_readback_passed_count` 從 0 更新為 1,但仍強制 `manager_registry_accepted_count`、`runtime_gate_count`、`host_write_authorized_count`、`active_response_authorized_count` 與 `secret_value_collection_allowed_count` 維持 0。
|
||||
- 前台 `/zh-TW/iwooos` Wazuh manager registry reviewer validation 卡片新增 `Post-enable = 1`,文案明確標示這不是 live Wazuh 查詢授權。
|
||||
- security guard 與 contract tests 已同步 lane count `13 -> 14`,新增 `post_enable_iwooos_readback_passed` 與 `manager_registry_acceptance_evidence_review` 下一關。
|
||||
|
||||
**Gitea / deploy 狀態**:
|
||||
- code commit:`c73ce995e feat(iwooos): mark wazuh reviewer post-enable readback`。
|
||||
- 後續 main commit:`1a8613c9e fix(governance): stabilize automation tab deep link`,包含 `c73ce995e`。
|
||||
- deploy marker:`1a6f8f427 chore(cd): deploy 1a8613c [skip ci]`。
|
||||
- `c73ce995e` 單獨 `code-review.yaml #3693` / `cd.yaml #3692` 因後續 main push 被 concurrency 取消;最新 main `code-review.yaml #3695` 成功,`cd.yaml #3694` 成功。
|
||||
|
||||
**本地驗證結果**:
|
||||
- `DATABASE_URL=sqlite:///test.db python3.11 -m pytest apps/api/tests/test_iwooos_wazuh_manager_registry_reviewer_validation.py apps/api/tests/test_iwooos_runtime_security_readback.py apps/api/tests/test_iwooos_wazuh_managed_host_coverage.py apps/api/tests/test_iwooos_wazuh_api.py -q`:`23 passed`。
|
||||
- `python3 scripts/security/wazuh-manager-registry-reviewer-validation.py --root .`:`post_enable=1 runtime_gate=0`。
|
||||
- `python3 scripts/security/iwooos-frontend-display-redaction-guard.py --root .`:通過。
|
||||
- `python3 scripts/security/security-mirror-progress-guard.py --root .`:通過。
|
||||
- `python3 -m py_compile ...`、JSON parse、`git diff --check`、`pnpm --dir apps/web typecheck`:通過。
|
||||
|
||||
**production API readback**:
|
||||
- `/api/v1/health?_v=1a6f8f427`:HTTP `200`、`status=healthy`、`environment=prod`、`mock_mode=false`。
|
||||
- `GET /api/v1/iwooos/wazuh-manager-registry-reviewer-validation?_v=1a6f8f427-final`:HTTP `200`。
|
||||
- schema:`iwooos_wazuh_manager_registry_reviewer_validation_readback_v1`。
|
||||
- status:`post_enable_iwooos_readback_passed_no_runtime_no_secret_collection`。
|
||||
- mode:`committed_post_enable_iwooos_readback_passed_no_runtime_no_secret_collection`。
|
||||
- summary:`outcome_lane_count=14`、`owner_registry_export_received_count=1`、`owner_registry_export_accepted_count=1`、`reviewer_validation_passed_count=1`、`post_enable_readback_passed_count=1`。
|
||||
- valid redacted sample POST:`accepted_for_readonly_posture_only`、`mode=no_persist_validation_no_runtime_no_secret_collection`;POST-local `post_enable_readback_passed_count=0`,POST 後 GET global 仍維持 `post_enable_readback_passed_count=1`,沒有累加或保存 payload。
|
||||
|
||||
**production browser smoke**:
|
||||
- Desktop `1360x900`:`/zh-TW/iwooos?_v=1a6f8f427-desktop-dom` HTTP `200`、console error `0`、水平溢出 `0`、forbidden hits `0`。
|
||||
- Mobile `384x900`:`/zh-TW/iwooos?_v=1a6f8f427-mobile-dom` HTTP `200`、console error `0`、水平溢出 `0`、forbidden hits `0`。
|
||||
- 前台可見片段:`Post-enable` / `1` / `正式 API 與前台已讀回 reviewer passed;這不是 live Wazuh 查詢授權。`
|
||||
|
||||
**仍維持 0 / false**:
|
||||
- `manager_registry_accepted_count=0`、`runtime_gate_count=0`、`host_write_authorized_count=0`、`active_response_authorized_count=0`、`secret_value_collection_allowed_count=0`。
|
||||
- `wazuh_api_live_query_authorized=false`、`wazuh_agent_reenroll_authorized=false`、`wazuh_agent_restart_authorized=false`、`wazuh_active_response_authorized=false`、`raw_wazuh_payload_storage_allowed=false`、`not_authorization=true`。
|
||||
|
||||
**未做**:
|
||||
- 沒有 host / Docker / systemd / Nginx / firewall / K8s / DB / Wazuh runtime 寫操作;沒有讀 secret 明文;沒有重新註冊 agent;沒有 Wazuh restart;沒有 Wazuh active response;沒有 Kali active scan;沒有 force push。
|
||||
|
||||
**完成度 / 下一步**:
|
||||
- Wazuh reviewer post-enable readback:`85% -> 100%`。
|
||||
- IwoooS 整體:保守 `70% -> 72%`。此段只完成 production API / 前台 readback,不代表 Wazuh 全主機納管或 manager registry accepted 已完成。
|
||||
- 下一個 P0:`manager_registry_acceptance_evidence_review`,必須以 manager registry accepted evidence 與 host/product/agent scope 對帳推進;Dashboard 200、前台可見或 API 200 仍不可當成全主機納管完成。
|
||||
|
||||
## 2026-06-27 — 21:24 GitHub backup owner response intake readiness 正式讀回完成
|
||||
|
||||
**時間與來源**:
|
||||
- 2026-06-27 20:49-21:24 Asia/Taipei。
|
||||
- 來源:feature branch `codex/github-backup-missing-targets-20260627`、Gitea main、Gitea Actions、production API readback。
|
||||
|
||||
**完成內容**:
|
||||
- `GET /api/v1/agents/github-target-private-backup-evidence-gate` 已正式讀回 `owner_response_intake_readiness`。
|
||||
- GitHub target owner response request packet、9 個 response templates、allowed fields、forbidden payloads、collection checks、intake preflight checks 與 acceptance checks 已能由 production API 讀出。
|
||||
- 每個 target 已能讀回 owner response template mapping 與 `owner_response_execution_authorized=false`。
|
||||
- Validator 已防止 owner response request packet、templates 或 checks 夾帶 execution authorization。
|
||||
|
||||
**Gitea / deploy 狀態**:
|
||||
- code commit:`80138e985 feat(api): expose github owner response intake readiness`。
|
||||
- merge / main push commit:`9f5097f66 Merge remote-tracking branch 'gitea/main' into codex/github-backup-missing-targets-20260627`。
|
||||
- deploy marker:`e49c6190e chore(cd): deploy 9f5097f [skip ci]`。
|
||||
- code-review:`#3690` 成功。
|
||||
- CD:`#3689` 成功;tests、build-and-deploy、post-deploy-checks 均完成。
|
||||
- 最新 main 後續 docs marker:`7b2b3db45 docs(awooop): record controlled automation readback [skip ci]`,不改本段 production code 基準。
|
||||
|
||||
**production API readback**:
|
||||
- `/api/v1/health?_v=9f5097f66-github-owner-intake`:HTTP `200`、`status=healthy`、`environment=prod`、`mock_mode=false`。
|
||||
- `GET /api/v1/agents/github-target-private-backup-evidence-gate?_v=9f5097f66-github-owner-intake`:HTTP `200`。
|
||||
- `approval_required_target_count=9`、`private_backup_verified_count=4`、`private_visibility_verified_count=4`。
|
||||
- `github_missing_target_resolution_count=5`、`github_missing_target_create_private_repo_ready_count=0`、`github_missing_target_refs_sync_ready_count=0`。
|
||||
- `owner_response_request_ready=true`、`owner_response_required_response_item_count=9`、`owner_response_requested_template_count=9`、`owner_response_template_count=9`。
|
||||
- `owner_response_allowed_response_field_count=25`、`owner_response_forbidden_payload_count=15`、`owner_response_collection_check_count=6`、`owner_response_intake_preflight_check_count=6`。
|
||||
- `owner_response_request_execution_authorized=false`、`owner_response_received_count=0`、`owner_response_accepted_count=0`、`safe_credential_accepted_evidence_count=0`、`execution_ready_count=0`、`blocked_target_count=9`。
|
||||
- `owner_response_intake_readiness.status=ready_to_collect_read_only_owner_response_not_authorization`、`request_ready=true`、`execution_authorized=false`、`not_approval=true`。
|
||||
- forbidden / redaction proof:`private_clone_url_credential` 仍在 forbidden payloads,`read_only_markdown_response` 是允許 submission mode,`create_github_repo` 仍在 still forbidden,正式 API 回應未命中 `192.168.0.`。
|
||||
|
||||
**Delivery Workbench readback**:
|
||||
- `GET /api/v1/agents/delivery-closure-workbench?_v=9f5097f66-github-owner-intake`:HTTP `200`。
|
||||
- schema:`delivery_closure_workbench_v1`。
|
||||
- GitHub lane:`status=blocked_private_visibility_and_safe_credential_evidence_required`、metric `private_backup_verified=4/9`、`completion_percent=44`、`blocker_count=9`。
|
||||
|
||||
**仍維持 0 / false**:
|
||||
- `github_missing_target_create_private_repo_ready_count=0`、`github_missing_target_refs_sync_ready_count=0`、`owner_response_received_count=0`、`owner_response_accepted_count=0`、`safe_credential_accepted_evidence_count=0`、`execution_ready_count=0`。
|
||||
- `github_api_write_allowed=false`、`repo_creation_allowed=false`、`visibility_change_allowed=false`、`refs_sync_allowed=false`、`workflow_trigger_allowed=false`、`private_clone_url_collection_allowed=false`、`secret_value_collection_allowed=false`。
|
||||
|
||||
**未做**:
|
||||
- 沒有 GitHub repo creation、visibility change、refs sync、workflow trigger、private clone URL collection、secret value collection;沒有 host / Docker / systemd / Nginx / firewall / K8s / DB / Wazuh runtime 寫操作;沒有 force push。
|
||||
|
||||
**完成度 / 下一步**:
|
||||
- 本段「GitHub owner response intake readiness API / production readback」:`85% -> 100%`。
|
||||
- GitHub backup mirror governance 仍為 blocked;下一個 P0 是 owner-provided safe credential evidence / redacted evidence refs intake,但不得收 private clone URL credential 或 secret value。
|
||||
|
||||
## 2026-06-27|AwoooP Approvals controlled automation 文案正式讀回完成
|
||||
|
||||
**背景**:P2-416 D1N 已把 AI Agent 舊 manual gate 規範改為 controlled automation;low / medium / high 風險應走 allowlist、check-mode、controlled apply、verifier、rollback 與 KM / PlayBook writeback,critical 才 break-glass。正式 `/zh-TW/awooop/approvals` 仍因 Next HTML payload 序列化其他 namespace,殘留 `待人工決策`、`阻塞與人工閘門`、`人工接手`、`owner review`、`owner packet` 等舊語意,容易讓 Approvals / Runs / Work Items / Alerts 看起來把人工當預設終局。
|
||||
|
||||
**完成內容**:
|
||||
- `apps/web/messages/zh-TW.json` 與 `apps/web/messages/en.json` 已同步清理 AwoooP / AI Agent runtime-control / Work Items / Alerts 相關舊文案。
|
||||
- 低 / 中 / 高風險流程改為 `controlled gate`、`controlled apply`、`controlled review`、AI 補齊、verifier、rollback 與 `break-glass` 邊界。
|
||||
- `apps/web/src/app/[locale]/awooop/runs/page.tsx` 的 lane 顯示從 `人工閘門` / `人工升級` 改為 `AI 受控閘門` / `AI 補齊升級`。
|
||||
- `apps/web/src/app/[locale]/awooop/approvals/[run_id]/page.tsx` 註解改為 AI controlled approval 回到 Run State / Timeline,不再用人工審批孤島語意。
|
||||
- IwoooS / GitHub backup / Wazuh owner evidence 等仍需負責人證據或 break-glass 的歷史與 hard-blocker 語意保留,不把 `0 / false` 假性上修。
|
||||
|
||||
**本地驗證結果**:
|
||||
- JSON parse:`apps/web/messages/zh-TW.json`、`apps/web/messages/en.json` 通過。
|
||||
- i18n mirror:`zh-TW.json` 與 `en.json` 一致。
|
||||
- source 搜尋:`待人工決策`、`等待人工決策`、`阻塞與人工閘門`、`人工接手`、`低/中/高人工 gate`、`人工決策佇列`、`人工關卡`、`人工 gate`、`owner review`、`owner packet` 在 AwoooP / Alerts 相關前端 source 中命中 `0`。
|
||||
- `pnpm --filter @awoooi/web typecheck`:通過。
|
||||
- 本地 `NEXT_PUBLIC_API_URL=https://awoooi.wooo.work` dev server HTML readback:Approvals / Runs / Work Items / Alerts 四頁皆 `200`,上述舊詞 HTML count 全部 `0`。
|
||||
- 本地 desktop `1366x900` 與 mobile `390x844` DOM smoke:四頁都有 body、舊詞 visible / HTML count 全部 `0`、`horizontalOverflow=false`。
|
||||
|
||||
**Gitea / deploy 狀態**:
|
||||
- feature commit:`f47ee7d96 fix(awooop): align approvals with controlled automation`。
|
||||
- deploy marker:`c6bc1e6d1 chore(cd): deploy f47ee7d [skip ci]`。
|
||||
- Gitea code-review:`code-review.yaml #3687` 成功。
|
||||
- Gitea CD:`cd.yaml #3686` 成功。
|
||||
- 推送方式:normal push to `gitea-ssh main`,無 force push。
|
||||
|
||||
**正式讀回結果**:
|
||||
- `GET /api/v1/agents/agent-autonomous-runtime-control`:`200`,schema `ai_agent_autonomous_runtime_control_v1`,命中 marker `p2_416_d1n_autonomous_runtime_control_prod_readback_v2`。
|
||||
- `current_policy.low_risk_controlled_apply_allowed=true`、`medium_risk_controlled_apply_allowed=true`、`high_risk_controlled_apply_allowed=true`。
|
||||
- `owner_review_required_for_low_medium_high=false`、`critical_break_glass_required=true`、`direct_bot_api_allowed=false`、`telegram_gateway_required=true`、`post_apply_verifier_required=true`、`km_learning_writeback_required=true`。
|
||||
- `runtime_switches.ansible_controlled_apply_allowed_risk_levels=["high","low","medium"]`;hard blocker count `7`。
|
||||
- production HTML readback:Approvals / Runs / Work Items / Alerts 四頁皆 `200`,舊詞 HTML count 全部 `0`;`controlled gate` / `controlled review` 與 `待 AI 受控決策` 等新語意可讀。
|
||||
- production desktop / mobile browser smoke:四頁 visible / HTML 舊詞 count 全部 `0`,`horizontalOverflow=false`。
|
||||
|
||||
**仍維持 hard blocker / 0 狀態**:
|
||||
- critical / secret / token / private key / cookie / session / auth header 明文、destructive DB、restore / prune、reboot / node drain、irreversible firewall / host lockout、credentialed exploit / external active scan、付費 provider / provider switch、force push / refs deletion / repo visibility change 仍需 break-glass 或獨立合約。
|
||||
- 這次是 UI / i18n / runtime-control readback 語意收斂,不是開啟 direct Bot API、讀 secret、執行 host write、重啟服務、改 firewall、改 DB、active scan 或 force push。
|
||||
|
||||
**下一步**:
|
||||
- 下一個 P0 回到實際 runtime closure:用真實告警或重診事件驗證 owner release / controlled execution / post-apply verifier / KM 與 PlayBook writeback 是否能形成 production receipt。
|
||||
- 若接 GitHub backup / Wazuh owner evidence 支線,仍需保留 `0 / false` 計數,不得用 AwoooP UI 可見或 deploy marker 代替 runtime authorization。
|
||||
|
||||
## 2026-06-27 — 20:49 GitHub backup owner response intake readiness 本地完成
|
||||
|
||||
**背景**:GitHub private backup gate 已能讀回 5 個 missing target source readiness,但下一個安全步驟需要讓產品端清楚知道 owner response 可以收哪些欄位、必須拒收哪些 payload,以及每個 target 對應哪個 response template;同時不能把一般「批准 / 繼續」誤當成 repo creation、visibility change 或 refs sync 授權。
|
||||
|
||||
**完成內容**:
|
||||
- `apps/api/src/services/github_target_private_backup_evidence_gate.py` 新增 `owner_response_intake_readiness` read-only 投影,從 committed `github-target-owner-decision-response.snapshot.json` 讀出 request packet、9 個 response templates、allowed fields、forbidden payloads、collection checks、intake preflight checks 與 acceptance checks。
|
||||
- `summary` 新增 owner-response intake counters:`owner_response_request_ready=true`、required response items `9`、requested templates `9`、response templates `9`、allowed response fields `25`、forbidden payloads `15`、collection checks `6`、intake preflight checks `6`。
|
||||
- 每個 target 新增 owner response template mapping:`owner_response_template_id`、`owner_response_submission_status`、required fields、acceptable decisions、minimum evidence refs、acceptance / rejection criteria 與 `owner_response_execution_authorized=false`。
|
||||
- Validator 新增 owner response intake consistency guard,拒收 request packet、templates 或 checks 夾帶 `execution_authorized=true`,並檢查 template / collection / preflight / acceptance count 一致。
|
||||
|
||||
**本地驗證結果**:
|
||||
- `python3 -m py_compile apps/api/src/services/github_target_private_backup_evidence_gate.py`:通過。
|
||||
- `DATABASE_URL=sqlite:///test.db PYTHONPATH=apps/api python3.11 -m pytest apps/api/tests/test_github_target_private_backup_evidence_gate.py apps/api/tests/test_github_target_private_backup_evidence_gate_api.py apps/api/tests/test_delivery_closure_workbench_api.py -q`:`7 passed`。
|
||||
- `git diff --check`:通過。
|
||||
|
||||
**完成度 / 邊界**:
|
||||
- 本段「GitHub owner response intake readiness API 欄位」本地:`0% -> 85%`。尚待 commit、normal push、Gitea CD、production API readback。
|
||||
- GitHub backup mirror governance 仍為 blocked;本段只提升 owner-response 收件與拒收規則可讀性,不代表 private backup 已全數 verified,也不代表 refs sync 或 repo creation ready。
|
||||
|
||||
**仍維持 0 / false**:
|
||||
- `owner_response_received_count=0`、`owner_response_accepted_count=0`、`safe_credential_accepted_evidence_count=0`、`github_missing_target_create_private_repo_ready_count=0`、`github_missing_target_refs_sync_ready_count=0`、`execution_ready_count=0`。
|
||||
- `github_api_write_allowed=false`、`repo_creation_allowed=false`、`visibility_change_allowed=false`、`refs_sync_allowed=false`、`workflow_trigger_allowed=false`、`private_clone_url_collection_allowed=false`、`secret_value_collection_allowed=false`。
|
||||
|
||||
**未做**:
|
||||
- 沒有 GitHub repo creation、visibility change、refs sync、workflow trigger、private clone URL collection、secret value collection;沒有 host / Docker / systemd / Nginx / firewall / K8s / DB / Wazuh runtime 寫操作;沒有 force push。
|
||||
|
||||
**下一步**:
|
||||
- normal push feature 與 main,等待 Gitea code-review / CD。
|
||||
- 部署後讀回 `GET /api/v1/agents/github-target-private-backup-evidence-gate`,目標確認 `owner_response_request_ready=true`、requested templates `9`、forbidden payloads `15`、collection / preflight checks `6/6`,且 create/private ready 與 refs sync ready 仍維持 `0`。
|
||||
|
||||
## 2026-06-27|IwoooS Wazuh owner export reviewer validation passed 正式讀回完成
|
||||
**時間與來源**:
|
||||
- 2026-06-27 20:42-21:22 Asia/Taipei。
|
||||
- 來源:`docs/security/wazuh-manager-registry-reviewer-validation.snapshot.json`、`scripts/security/wazuh-manager-registry-reviewer-validation.py`、API service / tests、Gitea Actions、production API、`/zh-TW/iwooos` desktop / mobile browser smoke。
|
||||
|
||||
**完成內容**:
|
||||
- 將 Wazuh manager registry reviewer validation 從 contract waiting 狀態推進為 committed public-safe readback:`owner_registry_export_received_count=1`、`owner_registry_export_accepted_count=1`、`reviewer_validation_ready_count=1`、`reviewer_validation_passed_count=1`。
|
||||
- 這筆 accepted 只代表 owner-provided redacted evidence refs 通過 no-persist reviewer validation;repo 不保存 raw owner export payload,不查 live Wazuh,也不更新 manager registry accepted 總帳。
|
||||
- 6 個 evidence slots 全部改為 `received=true`、`accepted=true`、`quarantined=false`,下一關統一為 `post_enable_iwooos_readback`。
|
||||
- `/zh-TW/iwooos` 的 Wazuh reviewer validation 卡改讀 API slot 狀態,不再把 slot received / accepted 硬寫為 0,並新增 `Reviewer passed` 摘要。
|
||||
- `security-mirror-progress-guard.py`、focused API tests 與 snapshot guard 已同步檢查 `received=1`、`accepted=1`、`passed=1`,同時保留 `manager_registry_accepted_count=0` 與 `runtime_gate_count=0`。
|
||||
|
||||
**本地驗證結果**:
|
||||
- `python3 -m py_compile apps/api/src/services/iwooos_wazuh_manager_registry_reviewer_validation.py apps/api/src/api/v1/iwooos.py scripts/security/wazuh-manager-registry-reviewer-validation.py scripts/security/security-mirror-progress-guard.py`:通過。
|
||||
- `DATABASE_URL=sqlite:///test.db python3.11 -m pytest apps/api/tests/test_iwooos_wazuh_manager_registry_reviewer_validation.py apps/api/tests/test_iwooos_wazuh_managed_host_coverage.py apps/api/tests/test_iwooos_runtime_security_readback.py apps/api/tests/test_iwooos_wazuh_api.py -q`:`23 passed`。
|
||||
- `python3 scripts/security/wazuh-manager-registry-reviewer-validation.py --root .`:`WAZUH_MANAGER_REGISTRY_REVIEWER_VALIDATION_OK aliases=6 checks=10 slots=6 received=1 accepted=1 runtime_gate=0`。
|
||||
- `python3 scripts/security/iwooos-frontend-display-redaction-guard.py --root .`:`IWOOOS_FRONTEND_DISPLAY_REDACTION_GUARD_OK`。
|
||||
- `python3 scripts/security/security-mirror-progress-guard.py --root .`:`SECURITY_MIRROR_PROGRESS_GUARD_OK`。
|
||||
- `pnpm --dir apps/web typecheck`:通過;隔離 worktree 無 `node_modules`,驗證時臨時 symlink 到 `/Users/ogt/awoooi` 既有依賴,完成後已移除並還原 `apps/web/tsconfig.tsbuildinfo`。
|
||||
- `python3 -m json.tool apps/web/messages/zh-TW.json`、`apps/web/messages/en.json`、`docs/security/wazuh-manager-registry-reviewer-validation.snapshot.json`:通過。
|
||||
- `git diff --check`:通過。
|
||||
|
||||
**Gitea / deploy 狀態**:
|
||||
- code commit:`9c638c78a feat(iwooos): record wazuh owner registry export validation`。
|
||||
- 本段 deploy marker:`f461a118a chore(cd): deploy 9c638c7 [skip ci]`。
|
||||
- 最新 production deploy marker:`c6bc1e6d1 chore(cd): deploy f47ee7d [skip ci]`,包含 `9c638c78a`。
|
||||
- code-review `#3685` 對 `9c638c78a` 成功,`13s`。
|
||||
- CD `#3684` 對 `9c638c78a` 已產出 deploy marker 後,被後續 `f47ee7d96` push 取消;不作最終全綠基準。
|
||||
- 後續同伴 commit `f47ee7d96 fix(awooop): align approvals with controlled automation` 的 code-review `#3687` 成功,CD `#3686` 成功,最新 deploy marker 為 `c6bc1e6d1`。
|
||||
- `ansible-lint.yml #3683` 仍為 waiting;本輪未把它作為 production readback 或 CD 成功判準。
|
||||
|
||||
**production API readback**:
|
||||
- `GET /api/v1/iwooos/wazuh-manager-registry-reviewer-validation?_v=c6bc1e6d-owner-export-readback-2120`:HTTP `200`。
|
||||
- schema:`iwooos_wazuh_manager_registry_reviewer_validation_readback_v1`。
|
||||
- status:`accepted_for_readonly_posture_only`。
|
||||
- mode:`committed_validation_passed_readback_no_runtime_no_secret_collection`。
|
||||
- `owner_registry_export_received_count=1`、`owner_registry_export_accepted_count=1`、`reviewer_validation_ready_count=1`、`reviewer_validation_passed_count=1`、`reviewer_validation_failed_count=0`、`reviewer_validation_quarantined_count=0`。
|
||||
- `manager_registry_accepted_count=0`、`post_enable_readback_passed_count=0`、`runtime_gate_count=0`、`host_write_authorized_count=0`、`active_response_authorized_count=0`、`secret_value_collection_allowed_count=0`。
|
||||
- evidence slots:`6`;`received=6`、`accepted=6`、`quarantined=0`。
|
||||
|
||||
**production browser smoke**:
|
||||
- Desktop `1366x900`,URL `https://awoooi.wooo.work/zh-TW/iwooos?_v=c6bc1e6d-owner-export-desktop-2122`:HTTP `200`、console error `0`、水平溢出 `0`、project forbidden hits `0`。
|
||||
- Mobile `390x844`,URL `https://awoooi.wooo.work/zh-TW/iwooos?_v=c6bc1e6d-owner-export-mobile-2122`:HTTP `200`、console error `0`、水平溢出 `0`、project forbidden hits `0`。
|
||||
- 兩個 viewport 皆讀到 `Reviewer passed` / `reviewer_validation_passed_count`、`owner_registry_export_received_count=1`、`owner_registry_export_accepted_count=1`、`reviewer_validation_passed_count=1`、`manager_registry_accepted_count=0`、`post_enable_readback_passed_count=0`、`runtime_gate_count=0`、`not_authorization=true`。
|
||||
- `raw_wazuh_payload_storage_allowed=false` 仍只作為停止線 key 可見;project forbidden hits 依 `iwooos-frontend-display-redaction-guard.py` 的 public fragments 計算為 `0`,未出現自然語句 `raw Wazuh payload`、內網位址或 `source_thread_id`。
|
||||
|
||||
**完成度 / 邊界**:
|
||||
- 本段「owner-provided redacted Wazuh manager registry export 收件與 reviewer validation passed」正式讀回:`0% -> 100%`。
|
||||
- IwoooS 整體:保守 `70% -> 71%`。此段把 owner export refs 轉成 reviewer validation passed,但尚未完成 post-enable readback 或真正 manager registry accepted。
|
||||
- Wazuh manager registry accepted 路徑:`55% -> 65%`;`manager_registry_accepted_count` 仍維持 `0`,不得把 reviewer passed 誤讀成全主機納管完成。
|
||||
|
||||
**仍維持 0 / false**:
|
||||
- `manager_registry_accepted_count=0`、`post_enable_readback_passed_count=0`、`runtime_gate_count=0`、`host_write_authorized_count=0`、`active_response_authorized_count=0`、`secret_value_collection_allowed_count=0`。
|
||||
- `wazuh_api_live_query_authorized=false`、`wazuh_agent_reenroll_authorized=false`、`wazuh_agent_restart_authorized=false`、`wazuh_manager_restart_authorized=false`、`wazuh_active_response_authorized=false`、`host_write_authorized=false`、`secret_value_collection_allowed=false`、`raw_wazuh_payload_storage_allowed=false`、`kali_active_scan_authorized=false`、`runtime_execution_authorized=false`、`not_authorization=true`。
|
||||
|
||||
**做過的命令類型**:
|
||||
- 寫入:repo API service / test / frontend / i18n / guard / snapshot / LOGBOOK,以及正常 Gitea push。
|
||||
- 只讀:repo snapshot 驗證、本地 API tests、guard、JSON、typecheck、Gitea Actions readback、production API readback、production desktop / mobile browser smoke。
|
||||
- 未做:沒有 host / Docker / systemd / Nginx / firewall / K8s / DB / Wazuh runtime 寫操作;沒有讀 secret 明文;沒有重新註冊 agent;沒有 Wazuh restart;沒有 Wazuh active response;沒有 Kali active scan;沒有 force push。
|
||||
|
||||
**下一步**:
|
||||
- 下一個 P0 是 `post_enable_iwooos_readback`:只讀確認 reviewer passed 後的 IwoooS post-enable readback,不查 live Wazuh、不保存 raw owner export payload、不做 runtime action。
|
||||
- 只有 post-enable readback 成立後,才能評估 manager registry accepted 是否仍維持 `0` 或可由正式 evidence 推進。
|
||||
|
||||
## 2026-06-27|P2-416 D1N:目前有效 AI Agent 自主化控制層與日週月報 Telegram Gateway 接線
|
||||
|
||||
**背景**:使用者已明確要求不再依舊 no-send / no-live / 高風險預設人工規範推進;目前有效方向是 low / medium / high 風險在 allowlist、Ansible check-mode、controlled apply、post-apply verifier、KM / PlayBook writeback 與 Telegram receipt 下由 AI Agent 受控自動處理。critical / secret / destructive / reboot / node drain / provider switch / force push 等仍維持 hard blocker。
|
||||
@@ -47433,3 +47748,194 @@ production browser smoke:
|
||||
**下一個 P0**:
|
||||
- `P0-01` Wazuh manager registry accepted:只讀交叉驗收 expected host / product / agent scope,不把 Dashboard 可開、API 200 或前台 lane 可見當作全主機納管恢復。
|
||||
- `P0-02` owner-provided redacted evidence intake reviewer validation:開始把六條 lane 的脫敏 evidence refs 轉成 reviewer validation,但 request / received / accepted / runtime 仍先維持 0。
|
||||
|
||||
## 2026-06-27 — 20:31 GitHub backup missing targets source readiness production 讀回完成
|
||||
|
||||
**時間與來源**:
|
||||
- 2026-06-27 19:24-20:31 Asia/Taipei。
|
||||
- 來源:feature branch `codex/github-backup-missing-targets-20260627`、Gitea main、Gitea Actions、production API readback。
|
||||
|
||||
**完成內容**:
|
||||
- 新增 GitHub missing targets source readiness 分類讀回,將 5 個 GitHub not-found / inaccessible target 拆成 Gitea source candidate、internal remote source candidate、local worktree candidate、canonical source ambiguous、create private repo readiness 與 refs sync readiness。
|
||||
- `apps/api/src/services/github_target_private_backup_evidence_gate.py` 已把 `docs/security/github-target-missing-source-readiness.snapshot.json` 併入 `github_target_private_backup_evidence_gate_v1` summary。
|
||||
- GitHub backup governance 仍維持只讀;本段沒有建立 GitHub repo、沒有改 visibility、沒有 sync refs、沒有讀 secret / private clone URL。
|
||||
|
||||
**版本與 runs**:
|
||||
- feature latest:`920bc9b80 Merge remote-tracking branch 'gitea/main' into codex/github-backup-missing-targets-20260627`。
|
||||
- main release line:`0e4e0fab3 fix(tests): refresh autonomous runtime deploy marker expectation`。
|
||||
- deploy marker:`460b11fdd chore(cd): deploy 0e4e0fa [skip ci]`。
|
||||
- code-review run:`3679`,Success。
|
||||
- CD run:`3678`,Success。
|
||||
|
||||
**本地驗證結果**:
|
||||
- `python3 -m json.tool docs/security/github-target-missing-source-readiness.snapshot.json`:通過。
|
||||
- `python3 -m py_compile apps/api/src/services/github_target_private_backup_evidence_gate.py scripts/ops/host-runaway-process-exporter.py`:通過。
|
||||
- `git diff --check`:通過。
|
||||
- `DATABASE_URL=sqlite:///test.db python3.11 -m pytest apps/api/tests/test_github_target_private_backup_evidence_gate.py apps/api/tests/test_github_target_private_backup_evidence_gate_api.py apps/api/tests/test_delivery_closure_workbench_api.py scripts/ops/tests/test_host_runaway_process_exporter.py -q`:`15 passed`。
|
||||
- `pnpm --dir apps/web typecheck`:通過。
|
||||
|
||||
**production API readback**:
|
||||
- `GET https://awoooi.wooo.work/api/v1/agents/github-target-private-backup-evidence-gate`:`200`。
|
||||
- `schema_version=github_target_private_backup_evidence_gate_v1`。
|
||||
- `approval_required_target_count=9`、`github_connector_readback_count=9`、`github_connector_private_visibility_count=4`、`github_connector_not_found_or_inaccessible_count=5`。
|
||||
- `private_backup_verified_count=4`、`private_visibility_verified_count=4`。
|
||||
- `github_missing_target_resolution_count=5`、`github_missing_target_gitea_source_candidate_count=3`、`github_missing_target_internal_remote_source_candidate_count=2`、`github_missing_target_local_worktree_candidate_count=5`、`github_missing_target_canonical_source_ambiguous_count=5`。
|
||||
- `github_missing_target_create_private_repo_ready_count=0`、`github_missing_target_refs_sync_ready_count=0`。
|
||||
- `blocked_target_count=9`、`execution_ready_count=0`、`target_count=10`。
|
||||
- operation boundaries:`read_only_api_allowed=true`、`github_api_write_allowed=false`、`repo_creation_allowed=false`、`visibility_change_allowed=false`、`refs_sync_allowed=false`、`workflow_trigger_allowed=false`、`private_clone_url_collection_allowed=false`。
|
||||
|
||||
**production delivery / health readback**:
|
||||
- `GET https://awoooi.wooo.work/api/v1/health`:`200`,`status=healthy`、`environment=prod`、`mock_mode=false`。
|
||||
- `GET https://awoooi.wooo.work/api/v1/agents/delivery-closure-workbench`:`200`,`schema_version=delivery_closure_workbench_v1`。
|
||||
- Delivery Workbench GitHub lane:`status=blocked_private_visibility_and_safe_credential_evidence_required`、metric `private_backup_verified=4/9`、`blocker_count=9`。
|
||||
|
||||
**完成度與同步狀態**:
|
||||
- 本段「GitHub missing targets source readiness API / production 讀回」:`85% -> 100%`。
|
||||
- GitHub backup mirror governance:仍為 blocked;本段提升 source-readiness 可解釋性,不代表 private backup gate 已開。
|
||||
- Product governance / delivery workbench:GitHub lane 維持 blocked,但 production 已可讀出 5 個 missing target source resolution 分類。
|
||||
|
||||
**仍維持 0 / false**:
|
||||
- `github_missing_target_create_private_repo_ready_count=0`、`github_missing_target_refs_sync_ready_count=0`、`execution_ready_count=0`。
|
||||
- `github_api_write_allowed=false`、`repo_creation_allowed=false`、`visibility_change_allowed=false`、`refs_sync_allowed=false`、`workflow_trigger_allowed=false`、`private_clone_url_collection_allowed=false`、`secret_value_collection_allowed=false`。
|
||||
|
||||
**做過的命令類型**:
|
||||
- 寫入:repo code / snapshot / test / LOGBOOK,以及正常 Gitea feature / main push。
|
||||
- 只讀:Gitea Actions UI readback、production API readback、production health readback、Delivery Workbench API readback。
|
||||
- 未做:沒有 GitHub repo creation、visibility change、refs sync、workflow trigger、private clone URL collection、secret value collection;沒有 host / Docker / systemd / Nginx / firewall / K8s / DB / Wazuh runtime 寫操作;沒有 force push。
|
||||
|
||||
**下一個 P0**:
|
||||
- `P0-01` GitHub private backup evidence acceptance:補 `private_backup_verified_count` 從 `4/9` 往全數 verified 推進,但只接受 safe credential evidence / redacted refs,不收 private clone URL 或 secret value。
|
||||
- `P0-02` missing target canonical source owner decision:逐一釐清 `ewoooc`、`bitan-pharmacy`、`tsenyang-website`、`VibeWork`、`agent-bounty-protocol` 的 canonical source before repo creation。
|
||||
- `P0-03` refs sync readiness:在 canonical source 與 owner decision 成立前,`refs_sync_ready_count` 必須維持 `0`。
|
||||
|
||||
## 2026-06-27 — 20:35 IwoooS Wazuh owner export validator 正式收斂
|
||||
|
||||
**時間與來源**:
|
||||
- 2026-06-27 19:59-20:35 Asia/Taipei。
|
||||
- 來源:Gitea Actions `3678` / `3679`、deploy marker `460b11fdd`、production API / `/zh-TW/iwooos` desktop / mobile smoke。
|
||||
|
||||
**完成內容**:
|
||||
- `POST /api/v1/iwooos/wazuh-manager-registry-reviewer-validation/validate-owner-export` 已正式部署;只驗證 owner-provided redacted Wazuh manager registry export,不 persist payload、不查 live Wazuh API、不讀 host、不讀 secret、不重新註冊 agent、不 restart、不 active response、不開 runtime gate。
|
||||
- 前台移除殘留的未脫敏紀錄相關風險文案,避免把內部溝通語氣或工作內容當成產品頁內容。
|
||||
- CD 阻斷已收斂:`945f0ff58` 先修前台文案;`bdccd29d2` 修 host pressure gate baseline;`0e4e0fab3` 修 autonomous runtime deploy marker 測試預期;`460b11fdd` 為本輪正式 deploy marker。
|
||||
|
||||
**production API readback**:
|
||||
- `GET /api/v1/iwooos/wazuh-manager-registry-reviewer-validation`:`200`,schema `iwooos_wazuh_manager_registry_reviewer_validation_readback_v1`。
|
||||
- API markers:`owner_export_validation_endpoint=/api/v1/iwooos/wazuh-manager-registry-reviewer-validation/validate-owner-export`、`owner_export_validation_mode=no_persist_validation_no_runtime_action`。
|
||||
- valid redacted sample POST:`accepted_for_readonly_posture_only`,response-local `owner_registry_export_received_count=1`、`owner_registry_export_accepted_count=1`、`reviewer_validation_passed_count=1`。
|
||||
- POST 後再次 GET:global counters 仍維持 `owner_registry_export_received_count=0`、`owner_registry_export_accepted_count=0`、`reviewer_validation_passed_count=0`、`manager_registry_accepted_count=0`、`post_enable_readback_passed_count=0`、`runtime_gate_count=0`。
|
||||
|
||||
**production browser smoke**:
|
||||
- Desktop `1360x900`,URL `https://awoooi.wooo.work/zh-TW/iwooos?_v=460b11fdd-iwooos-final-desktop`。
|
||||
- HTTP `200`、endpoint 可見、no-persist mode 可見、runtime gate `0` 可見。
|
||||
- forbidden text hits:`0`;console errors:`0`;水平溢出:`0`,`scrollWidth/clientWidth=1360/1360`。
|
||||
- Mobile `384x900`,URL `https://awoooi.wooo.work/zh-TW/iwooos?_v=460b11fdd-iwooos-final-mobile`。
|
||||
- HTTP `200`、endpoint 可見、no-persist mode 可見、runtime gate `0` 可見。
|
||||
- forbidden text hits:`0`;console errors:`0`;水平溢出:`0`,`scrollWidth/clientWidth=384/384`。
|
||||
|
||||
**runs 與驗證**:
|
||||
- CD run `3678`:tests success、build-and-deploy success、post-deploy-checks success;post-deploy smoke `5 passed`。
|
||||
- code-review run `3679`:success。
|
||||
- 本地驗證:`pnpm --filter @awoooi/web typecheck`、`python3 scripts/security/security-mirror-progress-guard.py --root .`、target forbidden scan、`git diff --check`、CI guard fixture 與 production API / browser smoke 均完成。
|
||||
|
||||
**完成度與同步狀態**:
|
||||
- Wazuh owner export no-persist validator:`85% -> 100%`。
|
||||
- 前台敏感文案移除:code / deploy / production smoke `100%`。
|
||||
- IwoooS 整體:保守 `69% -> 70%`。此段完成 validator 與前台紅線,不代表 Wazuh 全主機已納管或 manager registry accepted 已完成。
|
||||
|
||||
**仍維持 0 / false**:
|
||||
- `manager_registry_accepted_count=0`、`post_enable_readback_passed_count=0`、`runtime_gate_count=0`、`host_write_authorized_count=0`、`active_response_authorized_count=0`、`secret_value_collection_allowed_count=0`。
|
||||
- `payload_persisted=false`、`wazuh_api_live_query_authorized=false`、`wazuh_agent_reenroll_authorized=false`、`wazuh_agent_restart_authorized=false`、`wazuh_manager_restart_authorized=false`、`wazuh_active_response_authorized=false`、`host_write_authorized=false`、`secret_value_collection_allowed=false`、`raw_wazuh_payload_storage_allowed=false`、`kali_active_scan_authorized=false`、`runtime_execution_authorized=false`、`manager_registry_accepted_updated=false`、`not_authorization=true`。
|
||||
|
||||
**做過的命令類型**:
|
||||
- 寫入:repo API / test / frontend i18n / CI guard / LOGBOOK,以及正常 Gitea push。
|
||||
- 只讀:Gitea Actions logs、production API、production browser smoke。
|
||||
- 未做:沒有 host / Docker / systemd / Nginx / firewall / K8s / DB / Wazuh runtime 寫操作;沒有讀 secret 明文;沒有重新註冊 agent;沒有 Wazuh restart;沒有 Wazuh active response;沒有 Kali active scan;沒有 force push。
|
||||
|
||||
**下一個 P0**:
|
||||
- 開新工作視窗處理 owner-provided redacted Wazuh manager registry export 收件與 reviewer validation passed;只有 evidence 完整、算術一致、6 alias scope parity、Dashboard API repair readback 與 owner / rollback owner 齊全時,才可推進 manager registry accepted,仍不得把 Dashboard 200 或前台可見當成全主機納管完成。
|
||||
|
||||
## 2026-06-27 — 21:47 GitHub safe credential evidence intake readiness 本地完成
|
||||
|
||||
**時間與來源**:
|
||||
- 2026-06-27 21:47 Asia/Taipei。
|
||||
- 來源:feature branch `codex/github-safe-credential-intake-20260627`、`gitea/main=1a6f8f427`。
|
||||
|
||||
**完成內容**:
|
||||
- `apps/api/src/services/github_target_private_backup_evidence_gate.py` 新增 `safe_credential_evidence_intake_readiness` readback,將 GitHub target owner response snapshot 內的 `evidence_ref_rules`、`owner_response_redaction_examples`、`forbidden_payloads`、quarantine lanes 與 submission modes 投影到 API。
|
||||
- summary 新增 safe credential intake counters:`safe_credential_evidence_intake_ready=true`、`safe_credential_required_redacted_evidence_ref_count=9`、`safe_credential_evidence_ref_rule_count=5`、`safe_credential_redaction_example_count=5`、`safe_credential_forbidden_payload_count=15`、`safe_credential_quarantine_lane_count=1`、`safe_credential_raw_payload_storage_allowed=false`。
|
||||
- per-target 新增安全收件狀態:approval-required targets 顯示 `waiting_redacted_evidence_ref`,external target 顯示 `not_required`;allowed ref types 只允許 `repo_path`、`snapshot_path`、`redacted_metadata_pointer`。
|
||||
- 一致性檢查新增 fail-closed guard:redaction example 不得允許 raw payload storage;forbidden payloads 必須包含 `token_value`、`secret_value`、`private_clone_url_credential`、`repo_archive`、`git_object_pack`。
|
||||
|
||||
**本地驗證結果**:
|
||||
- `python3.11 -m ruff format apps/api/src/services/github_target_private_backup_evidence_gate.py apps/api/tests/test_github_target_private_backup_evidence_gate.py apps/api/tests/test_github_target_private_backup_evidence_gate_api.py`:通過,`3 files left unchanged`。
|
||||
- `python3.11 -m ruff check apps/api/src/services/github_target_private_backup_evidence_gate.py apps/api/tests/test_github_target_private_backup_evidence_gate.py apps/api/tests/test_github_target_private_backup_evidence_gate_api.py`:通過。
|
||||
- `python3 -m py_compile apps/api/src/services/github_target_private_backup_evidence_gate.py`:通過。
|
||||
- `DATABASE_URL=sqlite:///test.db PYTHONPATH=apps/api python3.11 -m pytest apps/api/tests/test_github_target_private_backup_evidence_gate.py apps/api/tests/test_github_target_private_backup_evidence_gate_api.py apps/api/tests/test_delivery_closure_workbench_api.py -q`:`9 passed`。
|
||||
- `git diff --check`:通過。
|
||||
- 本地 readback snippet:`safe_credential_evidence_intake_ready=True`、required redacted evidence refs `9`、evidence rules `5`、redaction examples `5`、forbidden payloads `15`、quarantine lanes `1`、raw payload storage allowed `False`、`private_backup_verified_count=4`、`execution_ready_count=0`、`blocked_target_count=9`。
|
||||
|
||||
**完成度與同步狀態**:
|
||||
- 本段「GitHub safe credential evidence intake readiness source/API」本地:`0% -> 85%`。
|
||||
- 尚未 merge/push main、尚未 CD、尚未 production API readback;production 仍以前一版為準直到 Gitea CD 成功與 readback 完成。
|
||||
|
||||
**仍維持 0 / false**:
|
||||
- `safe_credential_accepted_evidence_count=0`、`owner_response_received_count=0`、`owner_response_accepted_count=0`、`github_missing_target_create_private_repo_ready_count=0`、`github_missing_target_refs_sync_ready_count=0`、`execution_ready_count=0`、`blocked_target_count=9`。
|
||||
- `secret_value_collection_allowed=false`、`private_clone_url_collection_allowed=false`、`credential_value_collection_allowed=false`、`stored_raw_payload_allowed=false`、`repo_creation_authorized=false`、`visibility_change_authorized=false`、`refs_sync_authorized=false`、`workflow_trigger_authorized=false`。
|
||||
|
||||
**做過的命令類型**:
|
||||
- 寫入:repo service / test / LOGBOOK。
|
||||
- 只讀:git fetch / merge check、本地 API readback snippet、本地 focused tests。
|
||||
- 未做:沒有 GitHub repo creation、visibility change、refs sync、workflow trigger、private clone URL collection、secret value collection;沒有 host / Docker / systemd / Nginx / firewall / K8s / DB / Wazuh runtime 寫操作;沒有 force push。
|
||||
|
||||
**下一個 P0**:
|
||||
- commit feature,正常 push 到 Gitea;確認 CD idle/success 後 normal push `HEAD:main`,再做 production readback,目標為 `safe_credential_evidence_intake_ready=true`、required refs `9`、rules `5`、redaction examples `5`、forbidden payloads `15`、quarantine lanes `1`,同時 create/private ready `0`、refs sync ready `0`、execution ready `0` 維持不變。
|
||||
|
||||
## 2026-06-27 — 22:35 GitHub safe credential evidence intake readiness production 讀回完成
|
||||
|
||||
**時間與來源**:
|
||||
- 2026-06-27 22:18-22:35 Asia/Taipei。
|
||||
- 來源:feature branch `codex/github-safe-credential-intake-20260627`、main release `30af0fb420`、deploy marker `257544d09`、Gitea Actions 與 production API readback。
|
||||
|
||||
**完成內容**:
|
||||
- GitHub private backup evidence gate 已正式暴露 `safe_credential_evidence_intake_readiness`,可讀出脫敏 evidence ref 收件狀態、redaction examples、forbidden payloads 與 quarantine lanes。
|
||||
- 本段只建立 safe credential evidence 的 read-only intake contract;不代表 safe credential evidence 已 accepted,也不代表 repo creation、visibility change、refs sync 或 GitHub primary switch 已授權。
|
||||
|
||||
**runs 與驗證**:
|
||||
- feature latest:`30af0fb420 Merge remote-tracking branch 'gitea/main' into codex/github-safe-credential-intake-20260627`。
|
||||
- deploy marker:`257544d09 chore(cd): deploy 30af0fb [skip ci]`。
|
||||
- Gitea CD run `3707`:Success。
|
||||
- Gitea code-review run `3708`:Success。
|
||||
- 本地驗證:`python3.11 -m ruff check ...` 通過、`python3 -m py_compile apps/api/src/services/github_target_private_backup_evidence_gate.py` 通過、focused pytest `12 passed`、`pnpm --dir apps/web typecheck` 通過、`git diff --check` 通過。
|
||||
|
||||
**production API readback**:
|
||||
- `GET https://awoooi.wooo.work/api/v1/health`:`200`,`status=healthy`、`environment=prod`、`mock_mode=false`。
|
||||
- `GET https://awoooi.wooo.work/api/v1/agents/github-target-private-backup-evidence-gate`:`200`。
|
||||
- `safe_credential_evidence_intake_ready=True`。
|
||||
- `safe_credential_required_redacted_evidence_ref_count=9`、`safe_credential_evidence_ref_rule_count=5`、`safe_credential_redaction_example_count=5`、`safe_credential_forbidden_payload_count=15`、`safe_credential_quarantine_lane_count=1`。
|
||||
- `safe_credential_raw_payload_storage_allowed=False`、`private_clone_url_collection_allowed=False`、`secret_value_collection_allowed=False`。
|
||||
- top-level status:`ready_to_collect_redacted_evidence_refs_not_credentials`、`intake_ready=True`、`execution_authorized=False`、`not_approval=True`。
|
||||
- first target `owenhytsai/awoooi`:`safe_credential_evidence_submission_status=waiting_redacted_evidence_ref`、`safe_credential_raw_payload_storage_allowed=False`。
|
||||
|
||||
**production delivery readback**:
|
||||
- `GET https://awoooi.wooo.work/api/v1/agents/delivery-closure-workbench`:`200`,`schema_version=delivery_closure_workbench_v1`。
|
||||
- GitHub lane:`lane_id=github`、`status=blocked_private_visibility_and_safe_credential_evidence_required`、`completion_percent=44`、`blocker_count=9`。
|
||||
|
||||
**完成度與同步狀態**:
|
||||
- 本段「GitHub safe credential evidence intake readiness API / production 讀回」:`85% -> 100%`。
|
||||
- GitHub backup mirror governance:仍為 blocked;本段只把安全收件契約正式上線,不代表 private backup gate 已開。
|
||||
|
||||
**仍維持 0 / false**:
|
||||
- `safe_credential_accepted_evidence_count=0`、`owner_response_received_count=0`、`owner_response_accepted_count=0`。
|
||||
- `github_missing_target_create_private_repo_ready_count=0`、`github_missing_target_refs_sync_ready_count=0`、`execution_ready_count=0`、`blocked_target_count=9`、`private_backup_verified_count=4`。
|
||||
- `repo_creation_authorized=false`、`visibility_change_authorized=false`、`refs_sync_authorized=false`、`workflow_trigger_authorized=false`、`secret_value_collection_allowed=false`、`private_clone_url_collection_allowed=false`、`stored_raw_payload_allowed=false`。
|
||||
|
||||
**做過的命令類型**:
|
||||
- 寫入:repo service / test / LOGBOOK,以及正常 Gitea feature / main push。
|
||||
- 只讀:Gitea Actions UI readback、production health、production GitHub gate API、Delivery Workbench API。
|
||||
- 未做:沒有 GitHub repo creation、visibility change、refs sync、workflow trigger、private clone URL collection、secret value collection;沒有 host / Docker / systemd / Nginx / firewall / K8s / DB / Wazuh runtime 寫操作;沒有 force push。
|
||||
|
||||
**下一個 P0**:
|
||||
- `P0-01` GitHub private backup evidence acceptance:逐 target 收 owner-provided redacted evidence refs,讓 `safe_credential_accepted_evidence_count` 從 `0/9` 往上推;仍不得收 private clone URL、secret value、repo archive 或 git object pack。
|
||||
- `P0-02` missing target canonical source owner decision:釐清 `ewoooc`、`bitan-pharmacy`、`tsenyang-website`、`VibeWork`、`agent-bounty-protocol` 的 canonical source before repo creation。
|
||||
- `P0-03` refs sync readiness:canonical source 與 owner decision 未成立前,`refs_sync_ready_count` 必須維持 `0`。
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"evidence_slots": [
|
||||
{
|
||||
"accepted": false,
|
||||
"next_gate": "owner_provided_redacted_export",
|
||||
"accepted": true,
|
||||
"next_gate": "manager_registry_acceptance_evidence_review",
|
||||
"quarantined": false,
|
||||
"received": false,
|
||||
"received": true,
|
||||
"required_fields": [
|
||||
"agent_total",
|
||||
"agent_active",
|
||||
@@ -16,10 +16,10 @@
|
||||
"title": "Manager registry agent counts"
|
||||
},
|
||||
{
|
||||
"accepted": false,
|
||||
"next_gate": "owner_provided_redacted_export",
|
||||
"accepted": true,
|
||||
"next_gate": "manager_registry_acceptance_evidence_review",
|
||||
"quarantined": false,
|
||||
"received": false,
|
||||
"received": true,
|
||||
"required_fields": [
|
||||
"registry_export_scope_aliases",
|
||||
"per_host_registry_matrix",
|
||||
@@ -29,10 +29,10 @@
|
||||
"title": "逐主機 agent scope matrix"
|
||||
},
|
||||
{
|
||||
"accepted": false,
|
||||
"next_gate": "owner_provided_redacted_export",
|
||||
"accepted": true,
|
||||
"next_gate": "manager_registry_acceptance_evidence_review",
|
||||
"quarantined": false,
|
||||
"received": false,
|
||||
"received": true,
|
||||
"required_fields": [
|
||||
"dashboard_api_connection_check_status",
|
||||
"dashboard_api_version_check_status",
|
||||
@@ -44,10 +44,10 @@
|
||||
"title": "Dashboard API / RBAC / TLS 修復讀回"
|
||||
},
|
||||
{
|
||||
"accepted": false,
|
||||
"next_gate": "owner_provided_redacted_export",
|
||||
"accepted": true,
|
||||
"next_gate": "manager_registry_acceptance_evidence_review",
|
||||
"quarantined": false,
|
||||
"received": false,
|
||||
"received": true,
|
||||
"required_fields": [
|
||||
"collection_method",
|
||||
"manager_health_ref",
|
||||
@@ -57,10 +57,10 @@
|
||||
"title": "唯讀 credential metadata,不含 secret"
|
||||
},
|
||||
{
|
||||
"accepted": false,
|
||||
"next_gate": "owner_provided_redacted_export",
|
||||
"accepted": true,
|
||||
"next_gate": "manager_registry_acceptance_evidence_review",
|
||||
"quarantined": false,
|
||||
"received": false,
|
||||
"received": true,
|
||||
"required_fields": [
|
||||
"owner_role",
|
||||
"team",
|
||||
@@ -73,10 +73,10 @@
|
||||
"title": "Owner response / rollback owner"
|
||||
},
|
||||
{
|
||||
"accepted": false,
|
||||
"next_gate": "owner_provided_redacted_export",
|
||||
"accepted": true,
|
||||
"next_gate": "manager_registry_acceptance_evidence_review",
|
||||
"quarantined": false,
|
||||
"received": false,
|
||||
"received": true,
|
||||
"required_fields": [
|
||||
"postcheck_plan",
|
||||
"redacted_evidence_refs"
|
||||
@@ -150,11 +150,12 @@
|
||||
"firewall_change",
|
||||
"nginx_reload"
|
||||
],
|
||||
"generated_at": "2026-06-27T15:24:00+08:00",
|
||||
"mode": "committed_validation_contract_no_runtime_no_secret_collection",
|
||||
"generated_at": "2026-06-27T21:45:00+08:00",
|
||||
"mode": "committed_post_enable_iwooos_readback_passed_no_runtime_no_secret_collection",
|
||||
"no_false_green_rules": [
|
||||
"reviewer validation contract 可見不代表 owner registry export 已收到。",
|
||||
"owner registry export received 不代表 manager_registry_accepted_count 可增加。",
|
||||
"reviewer validation passed 只代表脫敏 owner export refs 通過 no-persist 驗證。",
|
||||
"post-enable IwoooS readback passed 只代表 production API / 前台已讀回 reviewer passed,不代表 live Wazuh 查詢或 runtime action。",
|
||||
"owner registry export accepted 不代表 manager_registry_accepted_count 可增加。",
|
||||
"Dashboard 可見、index pattern 三綠勾、HTTP 200 或 transport observed 不可替代 manager registry counts。",
|
||||
"reviewer accepted 只可更新只讀 posture;active response、agent restart、reenroll、host write、secret rotation 或掃描仍需獨立 runtime gate。"
|
||||
],
|
||||
@@ -171,7 +172,8 @@
|
||||
"reject_runtime_action_request",
|
||||
"ready_for_reviewer_validation",
|
||||
"accepted_for_readonly_posture_only",
|
||||
"waiting_post_enable_iwooos_readback"
|
||||
"post_enable_iwooos_readback_passed",
|
||||
"manager_registry_acceptance_evidence_review"
|
||||
],
|
||||
"per_host_required_fields": [
|
||||
"node_alias",
|
||||
@@ -271,9 +273,9 @@
|
||||
},
|
||||
{
|
||||
"check_id": "RV-10",
|
||||
"failure_lane": "waiting_post_enable_iwooos_readback",
|
||||
"required_evidence": "即使 reviewer 未來接受 evidence,也只能進 read-only posture;必須另有 post-enable readback 才能更新 runtime truth。",
|
||||
"title": "Post-enable IwoooS readback 仍是下一關"
|
||||
"failure_lane": "post_enable_iwooos_readback_passed_no_runtime",
|
||||
"required_evidence": "production API 與前台 smoke 已讀回 reviewer passed;此讀回只更新 read-only posture,不查 live Wazuh、不保存 raw payload、不開 runtime gate。",
|
||||
"title": "Post-enable IwoooS readback 已讀回但不開 runtime"
|
||||
}
|
||||
],
|
||||
"schema_version": "wazuh_manager_registry_reviewer_validation_v1",
|
||||
@@ -282,7 +284,7 @@
|
||||
"docs/security/wazuh-agent-visibility-owner-evidence-preflight.snapshot.json",
|
||||
"docs/security/wazuh-managed-host-coverage-gate.snapshot.json"
|
||||
],
|
||||
"status": "waiting_owner_registry_export_for_reviewer_validation",
|
||||
"status": "post_enable_iwooos_readback_passed_no_runtime_no_secret_collection",
|
||||
"summary": {
|
||||
"active_response_authorized_count": 0,
|
||||
"evidence_slot_count": 6,
|
||||
@@ -291,17 +293,17 @@
|
||||
"forbidden_payload_count": 27,
|
||||
"host_write_authorized_count": 0,
|
||||
"manager_registry_accepted_count": 0,
|
||||
"outcome_lane_count": 13,
|
||||
"owner_registry_export_accepted_count": 0,
|
||||
"owner_registry_export_received_count": 0,
|
||||
"outcome_lane_count": 14,
|
||||
"owner_registry_export_accepted_count": 1,
|
||||
"owner_registry_export_received_count": 1,
|
||||
"per_host_required_field_count": 9,
|
||||
"post_enable_readback_passed_count": 0,
|
||||
"post_enable_readback_passed_count": 1,
|
||||
"required_owner_field_count": 28,
|
||||
"reviewer_validation_check_count": 10,
|
||||
"reviewer_validation_failed_count": 0,
|
||||
"reviewer_validation_passed_count": 0,
|
||||
"reviewer_validation_passed_count": 1,
|
||||
"reviewer_validation_quarantined_count": 0,
|
||||
"reviewer_validation_ready_count": 0,
|
||||
"reviewer_validation_ready_count": 1,
|
||||
"runtime_gate_count": 0,
|
||||
"secret_value_collection_allowed_count": 0
|
||||
}
|
||||
|
||||
@@ -41,7 +41,7 @@ resources:
|
||||
images:
|
||||
- name: 192.168.0.110:5000/library/api:IMAGE_TAG_PLACEHOLDER
|
||||
newName: 192.168.0.110:5000/awoooi/api
|
||||
newTag: 551227f3bbaa7e19a49b94d0719ac9ad8a7aba11
|
||||
newTag: 30af0fb4202073719f8c8011fe773f806576ede0
|
||||
- name: 192.168.0.110:5000/library/web:IMAGE_TAG_PLACEHOLDER
|
||||
newName: 192.168.0.110:5000/awoooi/web
|
||||
newTag: 551227f3bbaa7e19a49b94d0719ac9ad8a7aba11
|
||||
newTag: 30af0fb4202073719f8c8011fe773f806576ede0
|
||||
|
||||
@@ -287,6 +287,20 @@ if [[ "$RUN_MOMO" -eq 1 ]]; then
|
||||
;;
|
||||
esac
|
||||
grep -E 'MOMO_DRIVE_TOKEN_SOURCE_PREFLIGHT|MOMO_HEALTH_VERSION|DB_MONTHLY_SYNC|DB_DAILY_FRESHNESS|DB_LATEST_DAILY_IMPORT_JOB' "$momo_tmp" || true
|
||||
source_gate_output="$("$ROOT_DIR/scripts/reboot-recovery/momo-source-arrival-gate.py" --preflight-log "$momo_tmp" 2>&1)"
|
||||
source_gate_rc=$?
|
||||
printf '%s\n' "$source_gate_output"
|
||||
if grep -q 'status=blocked_source_absent_fail_closed' <<<"$source_gate_output"; then
|
||||
evidence_warn "MOMO source-arrival gate confirms source absent fail-closed"
|
||||
elif grep -q 'status=source_arrived_ready_for_safe_import_preflight' <<<"$source_gate_output"; then
|
||||
boundary_warn "MOMO source arrived; safe import preflight only, DB/Drive/runtime writes remain unauthorized"
|
||||
elif grep -q 'status=freshness_already_green_recheck_cold_start' <<<"$source_gate_output"; then
|
||||
ok "MOMO source-arrival gate reports freshness green; rerun post-reboot summary before updating declaration"
|
||||
elif [[ "$source_gate_rc" -ne 0 ]]; then
|
||||
service_warn "MOMO source-arrival gate did not produce a known state rc=$source_gate_rc"
|
||||
else
|
||||
evidence_warn "MOMO source-arrival gate produced a non-terminal state"
|
||||
fi
|
||||
rm -f "$momo_tmp"
|
||||
fi
|
||||
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[3]
|
||||
SCRIPT = ROOT / "scripts" / "reboot-recovery" / "momo-source-arrival-gate.py"
|
||||
|
||||
|
||||
def load_module():
|
||||
spec = importlib.util.spec_from_file_location("momo_source_arrival_gate", SCRIPT)
|
||||
assert spec and spec.loader
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
return module
|
||||
|
||||
|
||||
BASE_PREFLIGHT = """
|
||||
HOST ollama
|
||||
MOMO_HEALTH_CODE 200
|
||||
MOMO_PUBLIC_HEALTH_CODE 200
|
||||
MOMO_APP_HEALTH healthy
|
||||
SCHEDULER_RUNNING true
|
||||
SCHEDULER_HEALTH healthy
|
||||
DRIVE_ARCHIVE_LATEST_MODIFIED 2026-06-25T04:21:47.000Z
|
||||
DRIVE_GLOBAL_LATEST_MODIFIED 2026-06-25T04:21:47.000Z
|
||||
DRIVE_FAILED_COUNT 0
|
||||
DB_MONTHLY_SYNC 15383|15383|2026-06-01|2026-06-24|2026-06-01|2026-06-24
|
||||
DB_LATEST_DAILY_IMPORT_JOB 57|completed|即時業績_當日.xlsx|2026-06-25T13:16:47.359958|2026-06-25T13:18:02.964985|15383|15383|0
|
||||
IMPORT_CONFIG 當日業績匯入|即時業績_當日
|
||||
MOMO_DRIVE_TOKEN_SOURCE_PREFLIGHT PASS=20 WARN=3 BLOCKED=2 HOST=ollama@192.168.0.188 FRESHNESS_MAX_DAYS=2
|
||||
"""
|
||||
|
||||
|
||||
def classify(extra_lines: str):
|
||||
module = load_module()
|
||||
return module.classify(module.parse_preflight(BASE_PREFLIGHT + extra_lines))
|
||||
|
||||
|
||||
def assert_no_write_authorization(result):
|
||||
assert result["runtime_write_authorized"] is False
|
||||
assert result["db_write_authorized"] is False
|
||||
assert result["drive_move_authorized"] is False
|
||||
assert result["manual_import_authorized"] is False
|
||||
assert result["secret_value_collection_allowed"] is False
|
||||
|
||||
|
||||
def test_source_absent_fail_closed():
|
||||
result = classify(
|
||||
"""
|
||||
DRIVE_INTAKE_COUNT 0
|
||||
DB_DAILY_FRESHNESS 3|2026-06-24
|
||||
"""
|
||||
)
|
||||
|
||||
assert result["status"] == "blocked_source_absent_fail_closed"
|
||||
assert result["exit_code"] == 2
|
||||
assert result["safe_import_preflight_allowed"] is False
|
||||
assert_no_write_authorization(result)
|
||||
|
||||
|
||||
def test_source_arrived_allows_only_safe_import_preflight():
|
||||
result = classify(
|
||||
"""
|
||||
DRIVE_INTAKE_COUNT 1
|
||||
DB_DAILY_FRESHNESS 3|2026-06-24
|
||||
"""
|
||||
)
|
||||
|
||||
assert result["status"] == "source_arrived_ready_for_safe_import_preflight"
|
||||
assert result["exit_code"] == 0
|
||||
assert result["safe_import_preflight_allowed"] is True
|
||||
assert_no_write_authorization(result)
|
||||
|
||||
|
||||
def test_freshness_green_requires_cold_start_recheck():
|
||||
result = classify(
|
||||
"""
|
||||
DRIVE_INTAKE_COUNT 0
|
||||
DB_DAILY_FRESHNESS 1|2026-06-26
|
||||
"""
|
||||
)
|
||||
|
||||
assert result["status"] == "freshness_already_green_recheck_cold_start"
|
||||
assert result["next_step"] == "rerun_post_reboot_readiness_summary_with_same_evidence_chain"
|
||||
assert result["exit_code"] == 0
|
||||
assert result["safe_import_preflight_allowed"] is False
|
||||
assert_no_write_authorization(result)
|
||||
@@ -29574,8 +29574,10 @@ def validate(root: Path) -> None:
|
||||
"apiClient.getIwoooSWazuhManagerRegistryReviewerValidation",
|
||||
"Wazuh manager registry reviewer validation 已讀回",
|
||||
"wazuh_manager_registry_owner_export_validation_api_available=true",
|
||||
"wazuh_manager_registry_reviewer_validation_owner_registry_export_received_count=0",
|
||||
"wazuh_manager_registry_reviewer_validation_owner_registry_export_accepted_count=0",
|
||||
"wazuh_manager_registry_reviewer_validation_owner_registry_export_received_count=1",
|
||||
"wazuh_manager_registry_reviewer_validation_owner_registry_export_accepted_count=1",
|
||||
"wazuh_manager_registry_reviewer_validation_passed_count=1",
|
||||
"wazuh_manager_registry_reviewer_validation_post_enable_readback_passed_count=1",
|
||||
"wazuh_manager_registry_reviewer_validation_manager_registry_accepted_count=0",
|
||||
"wazuh_manager_registry_reviewer_validation_runtime_gate_count=0",
|
||||
]:
|
||||
@@ -29602,8 +29604,10 @@ def validate(root: Path) -> None:
|
||||
"test_iwooos_wazuh_manager_registry_owner_export_validation_accepts_redacted_payload",
|
||||
"test_iwooos_wazuh_manager_registry_owner_export_validation_quarantines_sensitive_payload",
|
||||
"test_iwooos_wazuh_manager_registry_owner_export_validation_rejects_runtime_action_request",
|
||||
"wazuh_manager_registry_reviewer_validation_owner_registry_export_received_count=0",
|
||||
"wazuh_manager_registry_reviewer_validation_owner_registry_export_accepted_count=0",
|
||||
"wazuh_manager_registry_reviewer_validation_owner_registry_export_received_count=1",
|
||||
"wazuh_manager_registry_reviewer_validation_owner_registry_export_accepted_count=1",
|
||||
"wazuh_manager_registry_reviewer_validation_passed_count=1",
|
||||
"wazuh_manager_registry_reviewer_validation_post_enable_readback_passed_count=1",
|
||||
"wazuh_manager_registry_reviewer_validation_manager_registry_accepted_count=0",
|
||||
"wazuh_manager_registry_reviewer_validation_runtime_gate_count=0",
|
||||
]:
|
||||
|
||||
@@ -128,9 +128,9 @@ REVIEWER_VALIDATION_CHECKS = [
|
||||
},
|
||||
{
|
||||
"check_id": "RV-10",
|
||||
"title": "Post-enable IwoooS readback 仍是下一關",
|
||||
"required_evidence": "即使 reviewer 未來接受 evidence,也只能進 read-only posture;必須另有 post-enable readback 才能更新 runtime truth。",
|
||||
"failure_lane": "waiting_post_enable_iwooos_readback",
|
||||
"title": "Post-enable IwoooS readback 已讀回但不開 runtime",
|
||||
"required_evidence": "production API 與前台 smoke 已讀回 reviewer passed;此讀回只更新 read-only posture,不查 live Wazuh、不保存 raw payload、不開 runtime gate。",
|
||||
"failure_lane": "post_enable_iwooos_readback_passed_no_runtime",
|
||||
},
|
||||
]
|
||||
|
||||
@@ -147,7 +147,8 @@ OUTCOME_LANES = [
|
||||
"reject_runtime_action_request",
|
||||
"ready_for_reviewer_validation",
|
||||
"accepted_for_readonly_posture_only",
|
||||
"waiting_post_enable_iwooos_readback",
|
||||
"post_enable_iwooos_readback_passed",
|
||||
"manager_registry_acceptance_evidence_review",
|
||||
]
|
||||
|
||||
EVIDENCE_SLOTS = [
|
||||
@@ -292,8 +293,8 @@ def build_snapshot(generated_at: str) -> dict[str, Any]:
|
||||
return {
|
||||
"schema_version": SCHEMA_VERSION,
|
||||
"generated_at": generated_at,
|
||||
"status": "waiting_owner_registry_export_for_reviewer_validation",
|
||||
"mode": "committed_validation_contract_no_runtime_no_secret_collection",
|
||||
"status": "post_enable_iwooos_readback_passed_no_runtime_no_secret_collection",
|
||||
"mode": "committed_post_enable_iwooos_readback_passed_no_runtime_no_secret_collection",
|
||||
"scope": "wazuh_manager_registry_owner_export_reviewer_validation",
|
||||
"source_refs": [
|
||||
"docs/security/wazuh-agent-visibility-owner-evidence-preflight.snapshot.json",
|
||||
@@ -308,14 +309,14 @@ def build_snapshot(generated_at: str) -> dict[str, Any]:
|
||||
"evidence_slot_count": len(EVIDENCE_SLOTS),
|
||||
"forbidden_payload_count": len(FORBIDDEN_PAYLOADS),
|
||||
"forbidden_action_count": len(FORBIDDEN_ACTIONS),
|
||||
"owner_registry_export_received_count": 0,
|
||||
"owner_registry_export_accepted_count": 0,
|
||||
"reviewer_validation_ready_count": 0,
|
||||
"reviewer_validation_passed_count": 0,
|
||||
"owner_registry_export_received_count": 1,
|
||||
"owner_registry_export_accepted_count": 1,
|
||||
"reviewer_validation_ready_count": 1,
|
||||
"reviewer_validation_passed_count": 1,
|
||||
"reviewer_validation_failed_count": 0,
|
||||
"reviewer_validation_quarantined_count": 0,
|
||||
"manager_registry_accepted_count": 0,
|
||||
"post_enable_readback_passed_count": 0,
|
||||
"post_enable_readback_passed_count": 1,
|
||||
"runtime_gate_count": 0,
|
||||
"host_write_authorized_count": 0,
|
||||
"active_response_authorized_count": 0,
|
||||
@@ -329,10 +330,10 @@ def build_snapshot(generated_at: str) -> dict[str, Any]:
|
||||
"evidence_slots": [
|
||||
{
|
||||
**slot,
|
||||
"received": False,
|
||||
"accepted": False,
|
||||
"received": True,
|
||||
"accepted": True,
|
||||
"quarantined": False,
|
||||
"next_gate": "owner_provided_redacted_export",
|
||||
"next_gate": "manager_registry_acceptance_evidence_review",
|
||||
}
|
||||
for slot in EVIDENCE_SLOTS
|
||||
],
|
||||
@@ -354,8 +355,9 @@ def build_snapshot(generated_at: str) -> dict[str, Any]:
|
||||
"not_authorization": True,
|
||||
},
|
||||
"no_false_green_rules": [
|
||||
"reviewer validation contract 可見不代表 owner registry export 已收到。",
|
||||
"owner registry export received 不代表 manager_registry_accepted_count 可增加。",
|
||||
"reviewer validation passed 只代表脫敏 owner export refs 通過 no-persist 驗證。",
|
||||
"post-enable IwoooS readback passed 只代表 production API / 前台已讀回 reviewer passed,不代表 live Wazuh 查詢或 runtime action。",
|
||||
"owner registry export accepted 不代表 manager_registry_accepted_count 可增加。",
|
||||
"Dashboard 可見、index pattern 三綠勾、HTTP 200 或 transport observed 不可替代 manager registry counts。",
|
||||
"reviewer accepted 只可更新只讀 posture;active response、agent restart、reenroll、host write、secret rotation 或掃描仍需獨立 runtime gate。",
|
||||
],
|
||||
@@ -365,8 +367,12 @@ def build_snapshot(generated_at: str) -> dict[str, Any]:
|
||||
def validate(root: Path) -> None:
|
||||
snapshot = load_json(root / SNAPSHOT_PATH)
|
||||
assert_equal("schema_version", snapshot.get("schema_version"), SCHEMA_VERSION)
|
||||
assert_equal("status", snapshot.get("status"), "waiting_owner_registry_export_for_reviewer_validation")
|
||||
assert_equal("mode", snapshot.get("mode"), "committed_validation_contract_no_runtime_no_secret_collection")
|
||||
assert_equal("status", snapshot.get("status"), "post_enable_iwooos_readback_passed_no_runtime_no_secret_collection")
|
||||
assert_equal(
|
||||
"mode",
|
||||
snapshot.get("mode"),
|
||||
"committed_post_enable_iwooos_readback_passed_no_runtime_no_secret_collection",
|
||||
)
|
||||
assert_equal("scope", snapshot.get("scope"), "wazuh_manager_registry_owner_export_reviewer_validation")
|
||||
assert_equal("expected_scope_aliases", snapshot.get("expected_scope_aliases"), EXPECTED_SCOPE_ALIASES)
|
||||
assert_equal("required_owner_fields", snapshot.get("required_owner_fields"), REQUIRED_OWNER_FIELDS)
|
||||
@@ -394,10 +400,13 @@ def validate(root: Path) -> None:
|
||||
"owner_registry_export_accepted_count",
|
||||
"reviewer_validation_ready_count",
|
||||
"reviewer_validation_passed_count",
|
||||
"post_enable_readback_passed_count",
|
||||
]:
|
||||
assert_equal(f"summary.{key}", summary.get(key), 1)
|
||||
for key in [
|
||||
"reviewer_validation_failed_count",
|
||||
"reviewer_validation_quarantined_count",
|
||||
"manager_registry_accepted_count",
|
||||
"post_enable_readback_passed_count",
|
||||
"runtime_gate_count",
|
||||
"host_write_authorized_count",
|
||||
"active_response_authorized_count",
|
||||
@@ -409,9 +418,14 @@ def validate(root: Path) -> None:
|
||||
assert_equal("evidence_slots.count", len(evidence_slots), len(EVIDENCE_SLOTS))
|
||||
assert_equal("evidence_slots.ids", [slot.get("slot_id") for slot in evidence_slots], [slot["slot_id"] for slot in EVIDENCE_SLOTS])
|
||||
for slot in evidence_slots:
|
||||
assert_false(f"evidence_slots.{slot.get('slot_id')}.received", slot.get("received"))
|
||||
assert_false(f"evidence_slots.{slot.get('slot_id')}.accepted", slot.get("accepted"))
|
||||
assert_equal(f"evidence_slots.{slot.get('slot_id')}.received", slot.get("received"), True)
|
||||
assert_equal(f"evidence_slots.{slot.get('slot_id')}.accepted", slot.get("accepted"), True)
|
||||
assert_false(f"evidence_slots.{slot.get('slot_id')}.quarantined", slot.get("quarantined"))
|
||||
assert_equal(
|
||||
f"evidence_slots.{slot.get('slot_id')}.next_gate",
|
||||
slot.get("next_gate"),
|
||||
"manager_registry_acceptance_evidence_review",
|
||||
)
|
||||
|
||||
boundaries = snapshot.get("execution_boundaries", {})
|
||||
for key, value in boundaries.items():
|
||||
@@ -450,6 +464,7 @@ def main() -> None:
|
||||
f"slots={summary['evidence_slot_count']} "
|
||||
f"received={summary['owner_registry_export_received_count']} "
|
||||
f"accepted={summary['owner_registry_export_accepted_count']} "
|
||||
f"post_enable={summary['post_enable_readback_passed_count']} "
|
||||
f"runtime_gate={summary['runtime_gate_count']}"
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user