aps-agent/server/agent_core/audit.py

59 lines
3.4 KiB
Python
Raw Normal View History

2026-07-21 11:05:57 +08:00
# ============================================================
# 审计 v1(moduleId: core-audit, 可重生 ✅)
# plan.md §3.6 的最小实现:5W1H 事件 + 链式哈希(prev_hash → hash)
# M1 简化:锚定/WORM/保留策略后置;事件存于世界状态 auditEvents 表
# ============================================================
from __future__ import annotations # 前向类型引用
import hashlib # SHA-256 哈希链
import json # 规范化序列化
from datetime import datetime # 时间戳
from typing import Any # 类型标注
from server.timeutil import fmt_dt # 时间格式化
# 世界状态类型别名
World = dict[str, Any]
def write_audit(world: World, next_id, *, actor: str, category: str, action: str,
target: dict[str, Any], power: str, rationale: dict[str, Any],
result: str = "SUCCESS") -> dict[str, Any]:
"""写入一条审计事件(append-only;调用方负责随后 save)。
权力等级:P0(审计本身只追加,不改业务数据)。
Args:
world: 世界状态(事件追加到 auditEvents)
next_id: 发号函数
actor: 谁(用户/agent 标识)
category: 事件大类(§3.6.2:SESSION/PLAN/MODEL/TOOL/GATE/WORLD_WRITE/...)
action: 具体动作名(如 schedule.publish)
target: 作用对象 {type, id, ...}
power: 权力等级 P0-P3
rationale: 依据 {intent/evidence/confirmId/approver...}
result: 结果 SUCCESS/DENIED/FAILED
Returns:
写入的事件对象(含哈希)
"""
events = world.setdefault("auditEvents", []) # 审计表(缺失自动补)
prev_hash = events[-1]["hash"] if events else "GENESIS" # 链头:上一事件哈希或创世标记
at = fmt_dt(datetime.now()) # 单事件统一时间戳
event: dict[str, Any] = { # 5W1H 事件体(§3.6.1 的 M1 子集)
"id": next_id("audit"), # 事件 ID
"at": at, # When(前端契约字段)
"ts": at, # 兼容旧数据字段
"actor": actor, # Who
"category": category, # What(大类)
"action": action, # What(动作)
"target": target, # Where(对象)
"power": power, # 权力等级
"rationale": rationale, # Why(依据)
"result": result, # 结果
"prevHash": prev_hash, # 链式哈希:前驱
}
# 本事件哈希 = SHA256(前驱哈希 ‖ 事件体规范化 JSON)——任何中间篡改都会断链
payload = prev_hash + json.dumps(event, ensure_ascii=False, sort_keys=True)
event["hash"] = hashlib.sha256(payload.encode("utf-8")).hexdigest() # 计算并挂载
events.append(event) # 追加入链(append-only)
return event # 返回事件(供响应引用)