- ui/monitoring_tab.py (1546 dong) tach thanh presentation/monitoring/** (container + 7 tab/card + shared helper), ui/monitoring_tab.py con lai re-export shim de app.py khong doi. - infrastructure/telemetry/audit_logger.py: CanonicalAuditLogger, core/audit_log.py thanh wrapper mong, tuong thich nguoc 100% voi schema .jsonl cu. - application/monitoring/monitoring_query_service.py: MonitoringQueryService read-only, filter/sort/pagination, khong import PySide6. - Go circular import model_pricing<->usage_tracker va agent_security<-> agent_security_alert (core/agent_security_types.py moi). - infrastructure/sandbox/sandbox_capabilities.py: SandboxCapabilityMatrix theo OS (Windows/Linux/macOS), chua dau noi vao core/sandbox_manager.py. - conftest.py: sua loi checkout khong ten cowork_local khien pytest import nham thu muc khac. - 77 test moi, 167/167 pass. QA da xac nhan UI/business logic khong doi (xem evidence/report/unified_report.html). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
96 lines
3.5 KiB
Python
96 lines
3.5 KiB
Python
"""Task 2 — CanonicalAuditLogger.
|
|
|
|
Verifies: (1) the infrastructure class itself round-trips events correctly
|
|
and mirrors to a shared dir, (2) ``core/audit_log.py``'s wrapper functions
|
|
still behave exactly as before (same signatures, same dict schema, same
|
|
never-raise guarantee), and (3) old-format raw dicts (as written by the
|
|
pre-refactor ``core/audit_log.py``) still load correctly for backward
|
|
compatibility.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
from cowork_local.core import audit_log
|
|
from cowork_local.infrastructure.telemetry.audit_logger import (
|
|
KIND_MCP_CALL,
|
|
KIND_SECURITY_BLOCK,
|
|
CanonicalAuditEvent,
|
|
CanonicalAuditLogger,
|
|
)
|
|
|
|
|
|
def test_record_and_load_round_trip(tmp_path) -> None:
|
|
logger = CanonicalAuditLogger(tmp_path)
|
|
logger.set_identity("alice", "machine-1", role="admin")
|
|
logger.record(KIND_SECURITY_BLOCK, "run_command", False, detail="blocked it")
|
|
|
|
events = logger.load_events()
|
|
assert len(events) == 1
|
|
e = events[0]
|
|
assert e.kind == KIND_SECURITY_BLOCK
|
|
assert e.name == "run_command"
|
|
assert e.ok is False
|
|
assert e.detail == "blocked it"
|
|
assert e.account == "alice"
|
|
assert e.machine == "machine-1"
|
|
assert e.role == "admin"
|
|
|
|
|
|
def test_load_events_filters_by_kind(tmp_path) -> None:
|
|
logger = CanonicalAuditLogger(tmp_path)
|
|
logger.record(KIND_SECURITY_BLOCK, "a", False)
|
|
logger.record(KIND_MCP_CALL, "b", True)
|
|
|
|
only_mcp = logger.load_events(kind=KIND_MCP_CALL)
|
|
assert [e.name for e in only_mcp] == ["b"]
|
|
|
|
|
|
def test_shared_dir_mirroring(tmp_path) -> None:
|
|
shared = tmp_path / "shared"
|
|
logger = CanonicalAuditLogger(tmp_path / "audit")
|
|
logger.set_identity("bob", "machine-2", shared_dir=str(shared))
|
|
logger.record(KIND_MCP_CALL, "tool_x", True)
|
|
|
|
mirrored_files = list((shared / "telemetry" / "audit").glob("machine-2-*.jsonl"))
|
|
assert len(mirrored_files) == 1
|
|
|
|
|
|
def test_from_dict_is_tolerant_of_old_partial_rows() -> None:
|
|
old_row = {"ts": "2024-01-01T00:00:00", "kind": "tool_call", "name": "x", "ok": True}
|
|
event = CanonicalAuditEvent.from_dict(old_row)
|
|
assert event.detail == ""
|
|
assert event.account == ""
|
|
|
|
|
|
def test_record_never_raises_on_bad_directory(tmp_path) -> None:
|
|
bad_dir = tmp_path / "some_file.txt"
|
|
bad_dir.write_text("not a directory")
|
|
logger = CanonicalAuditLogger(bad_dir / "audit")
|
|
logger.record(KIND_SECURITY_BLOCK, "x", False) # must not raise
|
|
|
|
|
|
def test_core_audit_log_wrapper_same_schema_as_before(tmp_path, monkeypatch) -> None:
|
|
monkeypatch.setattr(audit_log, "AUDIT_DIR", tmp_path)
|
|
monkeypatch.setattr(audit_log, "_logger",
|
|
audit_log.CanonicalAuditLogger(tmp_path))
|
|
audit_log.set_identity("carol", "machine-3", role="user")
|
|
audit_log.record("permission", "install_package", True, detail="ok", agent_role="cowork")
|
|
|
|
events = audit_log.load_events()
|
|
assert len(events) == 1
|
|
e = events[0]
|
|
assert set(e.keys()) == {"ts", "kind", "agent_role", "name", "ok", "detail",
|
|
"account", "role", "machine"}
|
|
assert e["kind"] == "permission"
|
|
assert e["name"] == "install_package"
|
|
assert e["ok"] is True
|
|
assert e["agent_role"] == "cowork"
|
|
assert e["account"] == "carol"
|
|
|
|
# Raw file on disk still uses the exact pre-refactor schema/keys.
|
|
raw_line = next((tmp_path).glob("*.jsonl")).read_text(encoding="utf-8").splitlines()[0]
|
|
raw = json.loads(raw_line)
|
|
assert list(raw.keys()) == ["ts", "kind", "agent_role", "name", "ok", "detail",
|
|
"account", "role", "machine"]
|