fix: audit batch 2 visibility (I-1 shadow dropped rows, I-2 partial policy failure, I-5 failure policy table)

- I-1: evaluate_shadow counts dropped rows by cause (unparseable /
  unknown_tier / explicit / duplicate) with the identity
  audit_rows_total == routed_task_n + dropped_total; the shadow
  endpoint passes created_at/id through and reports audit totals
  next to sample counts.
- I-2 (option b): parse logs a warning naming each tier dropped for
  an invalid mode/requires; load attaches partial_failures to the
  policy; the worker records a task-level
  worker_routing_policy_load_failed audit for partial failures
  (same action as full failure, detail says failure=partial);
  the shadow report carries the policy fingerprint, dropped tiers
  and partial failures (no task context on that path, so no
  task-level audit there).
- I-5: coordinator gate audit lookup failure now raises
  _CoordGateLookupFailed and the merge converts it to escalate
  (no requeue, no round consumed); persisted-model lookup failure
  and required-tools check failure each write a named audit row
  before falling through to dispatch/allow; audit-loop evaluate
  failure stays escalate (regression test).
- Test fixtures: coordinator suites build audit_log via AuthStore
  as production's first real audit does, since their stubbed
  _audit never creates the table.
This commit is contained in:
host committed 2026-11-04 18:02:22 +00:00
1 parent 44e0f4f1d6
commit a7f77c8a98
7 files changed
+648 -40

No files matched your search

+16 -3
View File
@@ -25,11 +25,13 @@ from pathlib import Path
from types import SimpleNamespace
try:
from cloud.auth import AuthStore
from cloud.coordinator import build_report, evaluate_outcome
from cloud.server import init_db
from cloud.worker import Worker
except ImportError:
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from cloud.auth import AuthStore
from cloud.coordinator import build_report, evaluate_outcome
from cloud.server import init_db
from cloud.worker import Worker
@@ -111,6 +113,13 @@ class CoordFixTestCase(unittest.TestCase):
self.tmp = tempfile.TemporaryDirectory()
self.db_path = os.path.join(self.tmp.name, "control.db")
init_db(self.db_path)
# Audit batch 2 (I-5),与 test_audit_loop_coord.py 同款
# 理由:生产里 audit_log 由首次真实 _audit 经 AuthStore
# 建好,合流时表一定存在;本文件把 _audit 桩成列表,
# 表永远不会出现,夹具里按生产实际补建表,否则合流
# 测试会因缺表改走 escalate,与被测行为无关。
_store = AuthStore(self.db_path)
_store.conn.close()
self.env = {"XCM_API_KEY": "test-placeholder"}
self.audits = []
self.worker = Worker(self.db_path, "/fake/host.py",
@@ -200,10 +209,14 @@ class CoordFixTestCase(unittest.TestCase):
# 最旧 id 的子任务必须不在,且集合恰好 5000 条。
conn = sqlite3.connect(self.db_path)
try:
# Audit batch 2 (I-5):setUp 已按生产实际经 AuthStore
# 建好含 id 自增列的 audit_log 表,建表改 IF NOT
# EXISTS——生产表与本测试要的表在 id 语义上一致,
# tie-break 断言不受影响。
conn.execute(
"CREATE TABLE audit_log(id INTEGER PRIMARY KEY"
" AUTOINCREMENT, user_id TEXT, project_id TEXT,"
" action TEXT, detail TEXT,"
"CREATE TABLE IF NOT EXISTS audit_log(id INTEGER"
" PRIMARY KEY AUTOINCREMENT, user_id TEXT,"
" project_id TEXT, action TEXT, detail TEXT,"
" created_at DOUBLE PRECISION)")
now = time.time()
conn.executemany(