aps-agent/server/agent_core/audit.py

59 lines
3.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# ============================================================
# 审计 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 # 返回事件(供响应引用)