audit: wire pushback gate to hard merge gate and hash-chain ledger (gap 1)
Track 3 decisions (audit_pushback) now really block the server merge
endpoint and really land on the track 4 hash chain, via the new
cloud/audit_wiring.py translation layer:
- POST /tasks/{id}/merge ANDs the chain pushback block
(merge_blocked/release_blocked/blocked_by=audit_pushback/round/
failed_checks, replayed per module+contract version) with the
hard gate's five checks in every gate mode; either side blocks
-> 409, status unchanged, audit_pushback_blocked appended to
merge_gate.blocks. Ledger reads that fail (other than a missing
audit_ledger table) fail closed to 409.
- Every pushback/fix/re-audit/rebuild/escalation step is written
to the ledger with track 3 fields authoritative and track 4
storage authoritative; CONDITIONAL maps to fail at the boundary
with the original preserved, history never rewritten.
- Hard-gate 409 blocks codes feed pushback packages machine-first
(branch protection survives as its own extra check).
- Re-audit QA guard: a claimed pass with un-rerun or failed QA
links never reaches the chain.
- Track 4 compat: one audit_wiring_event action for escalation /
blocked-quota / audit-sample kinds, counted in the timeline.
- Resolved tension (documented in audit_wiring.py): a meta-review
correction to fail re-blocks at once, but a correction to pass
does NOT unblock by itself -- track 3 schedules a re-audit,
it does not release the gate; track 4 failure counts still use
effective verdicts, so the two views may disagree while a
re-audit is pending.
Tests: cloud/tests/test_audit_wiring.py (18 items), incl. a real
HTTP end-to-end chain (fail -> 409 -> fix -> re-audit pass ->
200) and a separate QA-link chain test.
This commit is contained in:
1 parent
04a7a4280b
commit
4c38e3c452
5 files changed
+1512
-12
No files matched your search
+69
-1
@@ -153,9 +153,21 @@ ASSESSMENT_ACTION = "audit_third_failure_assessment"
|
||||
TEST_CHANGE_ACTION = "audit_test_change"
|
||||
META_REVIEW_ACTION = "audit_meta_review"
|
||||
REBUILD_NOTICE_ACTION = "audit_rebuild_notice"
|
||||
# Wiring compat action (audit-gap-1 wiring, PR #244 -> #241): track 3
|
||||
# event kinds with no native track-4 action -- escalation outcomes,
|
||||
# blocked-quota outcomes, and audit-sample selections. These are
|
||||
# annotations on the timeline, not verdicts: they never feed
|
||||
# module_failure_counts, and only the blocking kinds move the
|
||||
# wiring gate (see cloud/audit_wiring.py). The action name is
|
||||
# intentionally generic so no second ledger vocabulary grows here:
|
||||
# one compat action, with the track-3 kind inside the detail.
|
||||
WIRING_EVENT_ACTION = "audit_wiring_event"
|
||||
WIRING_KINDS = ("escalate", "escalate_refreeze", "blocked_quota",
|
||||
"audit_sample")
|
||||
TIMELINE_ACTIONS = (VERDICT_ACTION, PUSHBACK_ACTION, REBUILD_ACTION,
|
||||
ASSESSMENT_ACTION, TEST_CHANGE_ACTION,
|
||||
META_REVIEW_ACTION, REBUILD_NOTICE_ACTION)
|
||||
META_REVIEW_ACTION, REBUILD_NOTICE_ACTION,
|
||||
WIRING_EVENT_ACTION)
|
||||
|
||||
_VERDICT_VALUES = ("pass", "fail")
|
||||
_FAILURE_SOURCES = ("audit", "acceptance")
|
||||
@@ -1049,6 +1061,8 @@ def audit_timeline(conn, task_id=None, module_id=None,
|
||||
1 for e in entries if e["action"] == META_REVIEW_ACTION),
|
||||
"rebuild_notices": sum(
|
||||
1 for e in entries if e["action"] == REBUILD_NOTICE_ACTION),
|
||||
"wiring_events": sum(
|
||||
1 for e in entries if e["action"] == WIRING_EVENT_ACTION),
|
||||
"counts_by_contract": counts_by_contract,
|
||||
"anomalies": anomalies,
|
||||
}
|
||||
@@ -1291,6 +1305,55 @@ def append_rebuild_notice(conn, task_id, project_id, module_id,
|
||||
task_id=task_id, created_at=created_at)
|
||||
|
||||
|
||||
def append_wiring_event(conn, task_id, project_id, module_id,
|
||||
contract_version, wiring_kind, target=None,
|
||||
reason="", round_no=None, root_cause=None,
|
||||
failed_checks=None, user_id="system",
|
||||
created_at=None):
|
||||
"""记一次接线兼容事件(审计加固第 1 项接线缺口新增)。
|
||||
|
||||
- wiring_kind: escalate | escalate_refreeze | blocked_quota |
|
||||
audit_sample(第 3 轨词汇,原文进链;其余第 3 轨事件
|
||||
各有原生动作,不走这里,见 cloud/audit_wiring.py)。
|
||||
- 本动作不参与 module_failure_counts(不是结论),时间线
|
||||
只把它计入 wiring_events;门禁是否阻塞由接线层按 kind
|
||||
判定(escalate / escalate_refreeze / blocked_quota 阻塞,
|
||||
audit_sample 不动状态)。
|
||||
- root_cause 原文保存(contract 与 design 的区分只在
|
||||
这里保留,audit_rebuild 行只存 contract_design)。
|
||||
校验失败抛 ValueError,绝不写半条记录。
|
||||
"""
|
||||
_require_non_empty_str(task_id, "task_id")
|
||||
_require_non_empty_str(module_id, "module_id")
|
||||
_require_non_empty_str(contract_version, "contract_version")
|
||||
if wiring_kind not in WIRING_KINDS:
|
||||
raise ValueError(
|
||||
"wiring_kind must be one of %s" % (WIRING_KINDS,))
|
||||
if round_no is not None:
|
||||
_require_int_ge(round_no, "round_no", 0)
|
||||
if failed_checks is not None and not isinstance(failed_checks, list):
|
||||
raise ValueError("failed_checks must be a list or None")
|
||||
if target is not None:
|
||||
_require_non_empty_str(target, "target")
|
||||
if root_cause is not None:
|
||||
_require_non_empty_str(root_cause, "root_cause")
|
||||
if not isinstance(reason, str):
|
||||
raise ValueError("reason must be a string")
|
||||
detail = json.dumps({
|
||||
"module_id": module_id,
|
||||
"contract_version": contract_version,
|
||||
"wiring_kind": wiring_kind,
|
||||
"target": target,
|
||||
"reason": reason,
|
||||
"round_no": round_no,
|
||||
"root_cause": root_cause,
|
||||
"failed_checks": failed_checks or [],
|
||||
}, sort_keys=True, ensure_ascii=False)
|
||||
return append_entry(conn, user_id, project_id,
|
||||
WIRING_EVENT_ACTION, detail=detail,
|
||||
task_id=task_id, created_at=created_at)
|
||||
|
||||
|
||||
class LedgerStore:
|
||||
"""db_path 版账本存储(测试与脚本用;服务端走连接版函数)。
|
||||
|
||||
@@ -1391,3 +1454,8 @@ class LedgerStore:
|
||||
entry = append_rebuild_notice(self.conn, *args, **kwargs)
|
||||
self.conn.commit()
|
||||
return entry
|
||||
|
||||
def append_wiring_event(self, *args, **kwargs):
|
||||
entry = append_wiring_event(self.conn, *args, **kwargs)
|
||||
self.conn.commit()
|
||||
return entry
|
||||
Reference in new issue
Block a user