feat(agents): 新增 AI action audit ledger
Some checks failed
Code Review / ai-code-review (push) Successful in 12s
Ansible / Reboot Recovery Contract / validate (push) Has been cancelled
CD Pipeline / tests (push) Successful in 1m54s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled

This commit is contained in:
Your Name
2026-06-19 00:50:43 +08:00
parent f390cddb4d
commit e13f716c00
9 changed files with 1306 additions and 136 deletions

View File

@@ -0,0 +1,323 @@
"""
P2-410 AI Agent action audit ledger snapshot.
Loads the latest committed action audit ledger. This module validates read-only
event templates and verifier receipt gates. It never writes audit DB rows,
timeline events, KM, PlayBook trust, Gateway queues, Telegram messages, secrets,
hosts, Kubernetes resources, or production state.
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
from src.services.snapshot_paths import default_evaluations_dir
_DEFAULT_EVALUATIONS_DIR = default_evaluations_dir(Path(__file__))
_SNAPSHOT_PATTERN = "ai_agent_action_audit_ledger_*.json"
_SCHEMA_VERSION = "ai_agent_action_audit_ledger_v1"
_RUNTIME_AUTHORITY = "agent_action_audit_ledger_no_live_write_committed_snapshot"
_EXPECTED_CURRENT_TASK = "P2-410"
_EXPECTED_NEXT_TASK = "P2-411"
_EXPECTED_SOURCE_SCHEMAS = {
"ai_agent_low_medium_risk_whitelist_v1",
"ai_agent_high_risk_owner_review_queue_v1",
"ai_agent_task_result_audit_trail_v1",
"awoooi_sre_digest_no_send_preview_v1",
"awoooi_work_items_report_source_gap_owner_review_v1",
"telegram_notification_egress_no_new_bypass_guard_v1",
"governance_automation_inventory_readback_v1",
}
_TRUE_TRUTH_FLAGS = {
"p2_408_whitelist_loaded",
"p2_409_owner_queue_loaded",
"p2_103_result_audit_loaded",
"p2_110c_sre_digest_loaded",
"p2_110e_work_items_loaded",
"telegram_no_new_bypass_loaded",
"audit_event_templates_ready",
"verifier_receipt_gates_ready",
"immutable_event_required",
"redacted_evidence_refs_required",
"read_only_mode",
}
_FALSE_TRUTH_FLAGS = {
"audit_db_write_enabled",
"timeline_write_enabled",
"km_write_enabled",
"playbook_trust_write_enabled",
"gateway_queue_write_enabled",
"telegram_send_enabled",
"bot_api_call_enabled",
"receipt_production_write_enabled",
"production_write_enabled",
"secret_read_enabled",
"paid_api_call_enabled",
"host_write_enabled",
"kubectl_action_enabled",
"destructive_operation_enabled",
}
_ZERO_TRUTH_COUNTS = {
"audit_db_write_count_24h",
"timeline_write_count_24h",
"km_write_count_24h",
"playbook_trust_write_count_24h",
"gateway_queue_write_count_24h",
"telegram_send_count_24h",
"bot_api_call_count_24h",
"receipt_production_write_count_24h",
"production_write_count_24h",
"secret_read_count_24h",
"paid_api_call_count_24h",
"host_write_count_24h",
"kubectl_action_count_24h",
"destructive_operation_count_24h",
}
_FALSE_EVENT_FLAGS = {
"audit_db_write_allowed",
"timeline_write_allowed",
"km_write_allowed",
"playbook_trust_write_allowed",
"gateway_queue_write_allowed",
"telegram_send_allowed",
"production_write_allowed",
}
_FALSE_BOUNDARY_FLAGS = _FALSE_TRUTH_FLAGS
_ZERO_ROLLUP_FIELDS = {
"audit_db_write_count",
"timeline_write_count",
"km_write_count",
"playbook_trust_write_count",
"gateway_queue_write_count",
"telegram_send_count",
"bot_api_call_count",
"receipt_production_write_count",
"production_write_count",
"secret_read_count",
"paid_api_call_count",
"host_write_count",
"kubectl_action_count",
"destructive_operation_count",
"owner_response_received_count",
"owner_response_accepted_count",
}
_FORBIDDEN_PUBLIC_TERMS = {
"批准" + "",
"In app " + "browser",
"My request for " + "Codex",
"codex_" + "delegation",
"source_" + "thread_id",
"chain_of_thought",
"private reasoning text",
"authorization_header",
"telegram token value",
"raw_payload",
"raw prompt",
"internal collaboration transcript",
}
def load_latest_ai_agent_action_audit_ledger(
evaluations_dir: Path | None = None,
) -> dict[str, Any]:
"""Load the newest committed P2-410 action audit ledger snapshot."""
directory = evaluations_dir or _DEFAULT_EVALUATIONS_DIR
candidates = sorted(directory.glob(_SNAPSHOT_PATTERN))
if not candidates:
raise FileNotFoundError(f"no AI Agent action audit ledger snapshots found in {directory}")
latest = candidates[-1]
with latest.open(encoding="utf-8") as handle:
payload = json.load(handle)
if not isinstance(payload, dict):
raise ValueError(f"{latest}: expected JSON object")
label = str(latest)
_require_schema(payload, label)
_require_sources(payload, label)
_require_audit_truth(payload, label)
_require_audit_event_templates(payload, label)
_require_verifier_receipt_gates(payload, label)
_require_activation_boundaries(payload, label)
_require_redaction_contract(payload, label)
_require_rollups(payload, label)
_require_no_forbidden_public_terms(payload, label)
return payload
def _require_schema(payload: dict[str, Any], label: str) -> None:
if payload.get("schema_version") != _SCHEMA_VERSION:
raise ValueError(f"{label}: expected schema_version={_SCHEMA_VERSION}")
status = payload.get("program_status") or {}
expected = {
"overall_completion_percent": 100,
"current_priority": "P0",
"current_task_id": _EXPECTED_CURRENT_TASK,
"next_task_id": _EXPECTED_NEXT_TASK,
"read_only_mode": True,
"runtime_authority": _RUNTIME_AUTHORITY,
}
mismatches = _mismatches(status, expected)
if mismatches:
raise ValueError(f"{label}: program_status mismatch: {mismatches}")
if not status.get("status_note"):
raise ValueError(f"{label}: program_status.status_note is required")
def _require_sources(payload: dict[str, Any], label: str) -> None:
if not payload.get("source_refs"):
raise ValueError(f"{label}: source_refs must not be empty")
sources = payload.get("source_readbacks") or []
schemas = {item.get("source_schema_version") for item in sources}
missing = sorted(_EXPECTED_SOURCE_SCHEMAS - schemas)
if missing:
raise ValueError(f"{label}: missing source schemas: {missing}")
for item in sources:
readback_id = item.get("readback_id") or "<missing>"
for field in ("source_ref", "endpoint", "owner_agent", "status", "key_readback", "next_action"):
if not item.get(field):
raise ValueError(f"{label}: source readback {readback_id} missing {field}")
def _require_audit_truth(payload: dict[str, Any], label: str) -> None:
truth = payload.get("audit_truth") or {}
missing_true = sorted(flag for flag in _TRUE_TRUTH_FLAGS if truth.get(flag) is not True)
if missing_true:
raise ValueError(f"{label}: audit truth flags must remain true: {missing_true}")
unsafe_false = sorted(flag for flag in _FALSE_TRUTH_FLAGS if truth.get(flag) is not False)
if unsafe_false:
raise ValueError(f"{label}: audit truth flags must remain false: {unsafe_false}")
non_zero = sorted(field for field in _ZERO_TRUTH_COUNTS if truth.get(field) != 0)
if non_zero:
raise ValueError(f"{label}: audit truth counts must remain zero: {non_zero}")
if not truth.get("truth_note"):
raise ValueError(f"{label}: audit_truth.truth_note is required")
def _require_audit_event_templates(payload: dict[str, Any], label: str) -> None:
events = payload.get("audit_event_templates") or []
if not events:
raise ValueError(f"{label}: audit_event_templates must not be empty")
source_ids = {item.get("readback_id") for item in payload.get("source_readbacks") or []}
risk_tiers = {event.get("risk_tier") for event in events}
if not {"low", "medium", "high", "critical"}.issubset(risk_tiers):
raise ValueError(f"{label}: audit event templates must cover low, medium, high, and critical")
for event in events:
event_id = event.get("audit_event_id") or "<missing>"
if event.get("immutable_event_required") is not True:
raise ValueError(f"{label}: event {event_id}.immutable_event_required must remain true")
unsafe = sorted(flag for flag in _FALSE_EVENT_FLAGS if event.get(flag) is not False)
if unsafe:
raise ValueError(f"{label}: event {event_id} write/send flags must remain false: {unsafe}")
if event.get("side_effect_count") != 0:
raise ValueError(f"{label}: event {event_id}.side_effect_count must remain zero")
for field in ("source_readback_ids", "required_audit_fields", "required_evidence_refs", "blocked_writes", "next_gate"):
if not event.get(field):
raise ValueError(f"{label}: event {event_id} missing {field}")
missing_sources = sorted(set(event.get("source_readback_ids") or []) - source_ids)
if missing_sources:
raise ValueError(f"{label}: event {event_id} references missing source readbacks: {missing_sources}")
def _require_verifier_receipt_gates(payload: dict[str, Any], label: str) -> None:
gates = payload.get("verifier_receipt_gates") or []
if len(gates) < 1:
raise ValueError(f"{label}: verifier_receipt_gates must not be empty")
for gate in gates:
gate_id = gate.get("gate_id") or "<missing>"
if not gate.get("required_checks"):
raise ValueError(f"{label}: verifier gate {gate_id} missing required_checks")
if not gate.get("failure_if_missing"):
raise ValueError(f"{label}: verifier gate {gate_id} missing failure_if_missing")
for field in ("live_verifier_allowed", "receipt_write_allowed", "runtime_action_allowed"):
if gate.get(field) is not False:
raise ValueError(f"{label}: verifier gate {gate_id}.{field} must remain false")
def _require_activation_boundaries(payload: dict[str, Any], label: str) -> None:
boundaries = payload.get("activation_boundaries") or {}
required_true = {
"committed_snapshot_read_allowed",
"audit_event_template_preview_allowed",
"verifier_receipt_gate_preview_allowed",
"governance_ui_projection_allowed",
}
missing = sorted(field for field in required_true if boundaries.get(field) is not True)
if missing:
raise ValueError(f"{label}: activation boundaries must remain true: {missing}")
unsafe = sorted(field for field in _FALSE_BOUNDARY_FLAGS if boundaries.get(field) is not False)
if unsafe:
raise ValueError(f"{label}: activation boundaries must remain false: {unsafe}")
def _require_redaction_contract(payload: dict[str, Any], label: str) -> None:
contract = payload.get("display_redaction_contract") or {}
required_false = {
"unsafe_payload_display_allowed",
"private_reasoning_display_allowed",
"secret_value_display_allowed",
"raw_prompt_display_allowed",
"work_window_transcript_display_allowed",
}
if contract.get("redaction_required") is not True:
raise ValueError(f"{label}: redaction_required must remain true")
unsafe = sorted(field for field in required_false if contract.get(field) is not False)
if unsafe:
raise ValueError(f"{label}: display redaction flags must remain false: {unsafe}")
if not contract.get("allowed_display_fields") or not contract.get("blocked_display_fields"):
raise ValueError(f"{label}: display redaction contract must list allowed and blocked fields")
def _require_rollups(payload: dict[str, Any], label: str) -> None:
rollups = payload.get("rollups") or {}
events = payload.get("audit_event_templates") or []
gates = payload.get("verifier_receipt_gates") or []
sources = payload.get("source_readbacks") or []
expected_counts = {
"source_readback_count": len(sources),
"audit_event_template_count": len(events),
"verifier_receipt_gate_count": len(gates),
"low_medium_event_count": sum(1 for event in events if event.get("risk_tier") in {"low", "medium"}),
"high_risk_event_count": sum(1 for event in events if event.get("risk_tier") == "high"),
"critical_event_count": sum(1 for event in events if event.get("risk_tier") == "critical"),
"report_gap_event_count": sum(
1 for event in events if any("p2_110" in source for source in event.get("source_readback_ids") or [])
),
"telegram_event_count": sum(
1
for event in events
if any("telegram" in source for source in event.get("source_readback_ids") or [])
),
"required_audit_field_count": sum(len(event.get("required_audit_fields") or []) for event in events),
"blocked_runtime_action_count": len(
{
blocked
for event in events
for blocked in event.get("blocked_writes") or []
}
),
}
mismatches = _mismatches(rollups, expected_counts)
if mismatches:
raise ValueError(f"{label}: rollup counts mismatch: {mismatches}")
non_zero = sorted(field for field in _ZERO_ROLLUP_FIELDS if rollups.get(field) != 0)
if non_zero:
raise ValueError(f"{label}: live write/send rollups must remain zero: {non_zero}")
def _require_no_forbidden_public_terms(payload: dict[str, Any], label: str) -> None:
haystack = json.dumps(payload, ensure_ascii=False)
hits = sorted(term for term in _FORBIDDEN_PUBLIC_TERMS if term in haystack)
if hits:
raise ValueError(f"{label}: forbidden public terms detected: {hits}")
def _mismatches(source: dict[str, Any], expected: dict[str, Any]) -> dict[str, Any]:
return {
field: {"expected": value, "actual": source.get(field)}
for field, value in expected.items()
if source.get(field) != value
}