# 审计事件独立介质镜像黄金测试(plan.md §3.6 / 矩阵「审计 append-only」) # ============================================================ # 覆盖:镜像追加/读取、改 world 不影响镜像、租户隔离、启用/禁用开关、 # 链校验仍可作用于镜像事件、gov_audit 镜像优先。 from __future__ import annotations import copy from pathlib import Path from server.agent_core import audit from server.agent_core.audit_mirror import AuditMirror, mirror_enabled def _event(i: int = 1) -> dict: return {"id": i, "at": "2026-08-01T00:00:00+08:00", "ts": "2026-08-01T00:00:00+08:00", "actor": "tester", "category": "TEST", "action": f"test.{i}", "target": {"type": "TEST"}, "power": "P0", "rationale": {}, "evidenceRefs": [], "beforeSnapshot": None, "result": "SUCCESS", "prevHash": "GENESIS", "hash": "0" * 64} def test_mirror_append_and_read(tmp_path: Path): """镜像追加/读取:追加事件可读回,计数正确。""" m = AuditMirror("tenant-a", "world-1", mirror_dir=str(tmp_path)) assert m.read_events() == [] m.append_event(_event(1)) m.append_event(_event(2)) events = m.read_events() assert len(events) == 2 assert [e["id"] for e in events] == [1, 2] assert m.count() == 2 def test_mirror_isolated_per_tenant_world(tmp_path: Path): """租户/项目隔离:不同 scope 各自独立文件。""" a = AuditMirror("tenant-a", "world-1", mirror_dir=str(tmp_path)) b = AuditMirror("tenant-b", "world-2", mirror_dir=str(tmp_path)) a.append_event(_event(1)) assert b.read_events() == [] assert a.path != b.path assert a.path.exists() and not b.path.exists() def test_mirror_unaffected_by_world_mutation(tmp_path: Path, monkeypatch): """改 world 不影响镜像:镜像保留原始事件(独立介质证据)。""" m = AuditMirror("tenant-a", "world-1", mirror_dir=str(tmp_path)) m.append_event(_event(1)) # 模拟业务进程篡改 world 副本 forged = copy.deepcopy(_event(1)) forged["actor"] = "hacker" world = {"auditEvents": [forged]} del world # world 修改不影响镜像 events = m.read_events() assert events[0]["actor"] == "tester" # 镜像保留原始值 def test_mirror_enabled_switch(tmp_path: Path, monkeypatch): """启用/禁用开关:APS_AUDIT_MIRROR=1 启用,缺省禁用。""" monkeypatch.delenv("APS_AUDIT_MIRROR", raising=False) monkeypatch.delenv("APS_AUDIT_MIRROR_DIR", raising=False) assert mirror_enabled() is False monkeypatch.setenv("APS_AUDIT_MIRROR", "1") assert mirror_enabled() is True def test_write_audit_mirrors_when_enabled(tmp_path: Path, monkeypatch): """write_audit 启用镜像时把事件写入独立介质。""" monkeypatch.setenv("APS_AUDIT_MIRROR", "1") monkeypatch.setenv("APS_AUDIT_MIRROR_DIR", str(tmp_path / "mirror")) monkeypatch.setattr("server.auth.context.get_identity", lambda: _FakeIdentity()) import server.state.projects as projects_module monkeypatch.setattr(projects_module, "get_project_store", lambda: _FakeProjectStore()) world: dict = {} audit.write_audit(world, lambda _k: 1, actor="tester", category="TEST", action="test.mirror", target={"type": "T"}, power="P0", rationale={}) assert world["auditEvents"][0]["id"] == 1 m = AuditMirror("tenant-fake", "world-fake", mirror_dir=str(tmp_path / "mirror")) events = m.read_events() assert len(events) == 1 assert events[0]["action"] == "test.mirror" class _FakeIdentity: tenant_uuid = "tenant-fake" user_id = None username = None fullname = None roles = () class _FakeProjectStore: def active_world_key(self) -> str: return "world-fake" def test_mirror_events_pass_chain_verify(tmp_path: Path): """链校验仍可作用于镜像事件(镜像保留完整哈希链)。""" import hashlib import json from server.agent_core.registry import verify_audit_chain prev = "GENESIS" events = [] m = AuditMirror("tenant-a", "world-1", mirror_dir=str(tmp_path)) for i in range(3): ev = _event(i + 1) ev["prevHash"] = prev # 链式前驱(第二事件起指向前一事件 hash) body = {k: v for k, v in ev.items() if k != "hash"} # 与 verify_audit_chain 剥离方式一致 payload = prev + json.dumps(body, ensure_ascii=False, sort_keys=True) ev["hash"] = hashlib.sha256(payload.encode()).hexdigest() prev = ev["hash"] events.append(ev) m.append_event(ev) assert verify_audit_chain(m.read_events())["ok"] is True