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
eeb10118d6
commit
382ec6d62e
5 files changed
+1512
-12
No files matched your search
@@ -89,6 +89,7 @@ from cloud.peer_messages import row_get as _row_get
|
||||
from cloud.marker_tools import parse_spawn_defaults as _parse_spawn_defaults
|
||||
from cloud.merge_gate import evaluate_merge_gate as _merge_gate_evaluate
|
||||
from cloud.merge_gate import protection_from_env as _merge_gate_protection
|
||||
from cloud.audit_wiring import pushback_block_for_task as _pushback_block
|
||||
from cloud.merge_gate_hard import blocked_audit_detail as _gate_blocked_detail
|
||||
from cloud.merge_gate_hard import evaluate_hard_merge_gate as _merge_gate_hard
|
||||
from cloud.merge_gate_hard import hard_mode_from_env as _merge_gate_hard_mode
|
||||
@@ -8087,6 +8088,18 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
|
||||
门禁阻断(任一模式)都写 merge_gate_blocked 审计后才回 409,
|
||||
这是第 3 轨自动推回的阻塞依据:推回包取响应 merge_gate 的
|
||||
blocks 码,不要解析审计文本;本接口不建推回任务、不改状态机。
|
||||
审计推回接线(cloud/audit_wiring,第 1 项接线缺口):五项
|
||||
检查之外再与链上推回阻塞做“与”:链上任一模块仍阻塞
|
||||
(merge_blocked)时,即使五项全过也 409、状态不变,并在
|
||||
merge_gate.blocks 里追加 audit_pushback_blocked 码、响应
|
||||
里附 audit_pushback 对象(merge_blocked / release_blocked
|
||||
/ blocked_by="audit_pushback" / round / failed_checks)。
|
||||
推回阻塞与门禁模式无关:record 模式的显式确认只覆盖
|
||||
保护风险,绝不覆盖推回阻塞;链读不到(audit_ledger 表
|
||||
不存在)视为无推回状态,其它读链失败一律 fail-closed
|
||||
409,不放行。注意:本接口没有独立的发布端点,
|
||||
release_blocked 随响应返回、由未来的发布端点用同一
|
||||
reader 强制;deploy.py 是运维脚本,不在本接口门禁内。
|
||||
本服务没有可信渠道直连 GitHub 核实 CI/保护:门禁强制的是
|
||||
“调用方提交的证据齐全且全绿”,谎报证据由审计与 PR/Checks
|
||||
只读核对覆盖,不在本接口的验证范围内。
|
||||
@@ -8147,6 +8160,46 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
|
||||
approved_exceptions=body.get("approved_exceptions") or (),
|
||||
protection=protection, mode=mode,
|
||||
explicit_confirm=bool(body.get("merge_gate_confirm")))
|
||||
# 审计推回接线:链上阻塞与门禁五项为“与”关系,且与模式
|
||||
# 无关。读链失败(表不存在除外,见 audit_wiring)时
|
||||
# fail-closed:不证明无阻塞就不放行。
|
||||
pushback = None
|
||||
try:
|
||||
conn = self._db()
|
||||
try:
|
||||
pushback = _pushback_block(conn, task_id)
|
||||
finally:
|
||||
conn.close()
|
||||
except Exception:
|
||||
pushback = None
|
||||
if pushback is None:
|
||||
try:
|
||||
with _auth_store(self.server.db_path) as store:
|
||||
store.audit(
|
||||
user_id, task["project_id"],
|
||||
"merge_gate_blocked",
|
||||
"%s mode=%s blocks=audit_pushback_state_unavailable"
|
||||
% (task_id, gate.get("mode", "hard")))
|
||||
except Exception:
|
||||
pass
|
||||
self._send_json(409, {
|
||||
"error": "audit pushback state unavailable",
|
||||
"merge_gate": gate,
|
||||
"audit_pushback": {
|
||||
"merge_blocked": True,
|
||||
"release_blocked": True,
|
||||
"blocked_by": "audit_pushback_state_unavailable",
|
||||
"round": None,
|
||||
"failed_checks": [],
|
||||
},
|
||||
})
|
||||
return
|
||||
gate["audit_pushback"] = pushback
|
||||
if pushback["merge_blocked"]:
|
||||
gate["allowed"] = False
|
||||
blocks = gate.setdefault("blocks", [])
|
||||
if "audit_pushback_blocked" not in blocks:
|
||||
blocks.append("audit_pushback_blocked")
|
||||
if not gate["allowed"]:
|
||||
with _auth_store(self.server.db_path) as store:
|
||||
store.audit(user_id, task["project_id"],
|
||||
|
||||
Reference in new issue
Block a user