v2 evidence: bind release evidence with ts/nonce, pin actual-only quota, ledger onboarding checklist, ledger failure outbox, read-only gate verifier (no live wiring)
- cloud/evidence_binding.py: bind/validate/nonce single-use store; release endpoint consumes nonce on first validated submission, replay -> 409 evidence_replayed, stale/future -> 409 evidence_stale, partial -> 400, absent -> old path + label. Binding blocks exact replay and stale evidence, NOT restamping (unsigned evidence cannot prove content freshness). - cloud/quota.py docstring: actual-only is the v2 integration position (already converged in v1 base via fef09ae, NOT in main); original PR semantics (#86 all-rows vs #90 actual-only) still pending coordinator confirmation at merge time. - cloud/ledger_onboarding.py: DDL checklist referencing PG_DDL/PG_MARKER_DDL constants, read-only verify_ledger_ready and ledger_setup_report; default stays OFF, PG DDL stays an operator action (never claimed as executed). - cloud/ledger_outbox.py + worker hook: ledger write failures go to a JSONL outbox in addition to the log and can be replayed; task outcome still unaffected (rule 7 kept), audit fail-closed untouched -- asymmetry documented, not silently changed. - cloud/gate_verification.py: read-only verifier over an injected reader (match/mismatch/skipped); live wiring in merge/release is NOT possible on the current contract (no repo/ref in body or task row, no guaranteed trusted token), proven in the module header; no fake verification claimed. - cloud/tests/test_v2_evidence.py: 18 new tests, all passing. (cherry picked from commit bd85e33cd5e846fa6ee5d7e2440182dbd9c758b0)
This commit is contained in:
1 parent
2dc35bd331
commit
773945d136
9 files changed
+1263
-2
No files matched your search
+77
-2
@@ -79,6 +79,9 @@ from cloud.peer_messages import ensure_followups_table as _peer_ensure_followups
|
||||
from cloud.peer_messages import peer_message_prefix as _peer_prefix
|
||||
from cloud.peer_messages import row_get as _row_get
|
||||
from cloud.marker_tools import parse_spawn_defaults as _parse_spawn_defaults
|
||||
from cloud.evidence_binding import EvidenceNonceStore as _EvidenceNonceStore
|
||||
from cloud.evidence_binding import binding_presence as _evidence_presence
|
||||
from cloud.evidence_binding import validate_bound_evidence as _validate_evidence
|
||||
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
|
||||
@@ -6431,7 +6434,14 @@ class CloudControlPlaneHandler(OAuthMixin, IdentityMemoryMixin, ProjectMixin, Au
|
||||
reader 强制;deploy.py 是运维脚本,不在本接口门禁内。
|
||||
本服务没有可信渠道直连 GitHub 核实 CI/保护:门禁强制的是
|
||||
“调用方提交的证据齐全且全绿”,谎报证据由审计与 PR/Checks
|
||||
只读核对覆盖,不在本接口的验证范围内。
|
||||
只读核对覆盖,不在本接口的验证范围内。集成 v2 补充
|
||||
(cloud/gate_verification):只读核对器本身已实现
|
||||
(注入式 reader、逐项比对、mismatch 列出),但当前
|
||||
合并/发布路径因请求体与任务行均无 repo/ref、且无可
|
||||
信求证通道保证而接不上实时求证——见该模块头的不可
|
||||
实现证明;当前真实控制仍是“证据齐全全绿 + 审计记
|
||||
outcome/blocks”,审计 detail 不存证据体全文,谎报
|
||||
发现靠事后比对,不是当场发现。
|
||||
"""
|
||||
row = self._fetch_task(task_id)
|
||||
if row is None:
|
||||
@@ -6992,7 +7002,16 @@ class CloudControlPlaneHandler(OAuthMixin, IdentityMemoryMixin, ProjectMixin, Au
|
||||
把它绑到任务链上会在最需要部署修复与回滚时反锁部署。
|
||||
200 时写一条显式的 release_task 审计作为发布后动作记
|
||||
录(检查本身是只读的,这条是检查完成后的显式动作,
|
||||
不是检查的副产品;锚点须在这条之后重取)。"""
|
||||
不是检查的副产品;锚点须在这条之后重取)。
|
||||
证据绑定(集成 v2,cloud/evidence_binding):请求体成
|
||||
对带 evidence_ts / evidence_nonce 时,服务端校验新鲜
|
||||
度窗口并单次消费 nonce,原样重交即 409
|
||||
release_blocked_evidence_replayed,陈旧/未来时间戳即
|
||||
409 release_blocked_evidence_stale;两者皆无时走旧路径
|
||||
并在成功响应标注 evidence_binding=absent。绑定防原样
|
||||
重放与陈旧证据,不防重盖戳(无签名);实时求证见
|
||||
cloud/gate_verification(当前路径因无 repo/ref 且无
|
||||
可信求证通道而接不上,见该模块头的不可实现证明)。"""
|
||||
row = self._fetch_task(task_id)
|
||||
if row is None:
|
||||
self._send_json(404, {"error": "task not found"})
|
||||
@@ -7014,6 +7033,58 @@ class CloudControlPlaneHandler(OAuthMixin, IdentityMemoryMixin, ProjectMixin, Au
|
||||
if hard_err:
|
||||
self._send_json(400, {"error": hard_err})
|
||||
return
|
||||
# 证据新鲜度与单次使用绑定(集成 v2,cloud/evidence_binding):
|
||||
# 体里成对带 evidence_ts / evidence_nonce 时才校验;两者
|
||||
# 皆无走旧路径(#259 契约兼容,成功响应标注 absent);
|
||||
# 只带其一是格式错误 400。校验通过的 nonce 在此次提交
|
||||
# 即消费(无论后续门禁放行与否),原样重交同一 nonce
|
||||
# 即 409 evidence_replayed;陈旧/未来时间戳即 409
|
||||
# evidence_stale。409 按本接口规则不写审计;nonce
|
||||
# 消费写 evidence_nonces 表(与审计链无关,不移动链头)。
|
||||
# 绑定防原样重放与陈旧证据,不防重盖戳(无签名证据
|
||||
# 无法识破旧内容盖新戳——见 evidence_binding 模块头)。
|
||||
binding = _evidence_presence(body)
|
||||
if binding == "partial":
|
||||
self._send_json(400, {"error":
|
||||
"evidence_ts and evidence_nonce"
|
||||
" must be supplied together"})
|
||||
return
|
||||
if binding == "bound":
|
||||
ok, reason = _validate_evidence(body)
|
||||
if not ok:
|
||||
if reason in ("evidence_too_old",
|
||||
"evidence_from_future"):
|
||||
self._send_json(409, {
|
||||
"error": "release blocked",
|
||||
"release_gate": {
|
||||
"allowed": False, "http_status": 409,
|
||||
"code": "release_blocked_evidence_stale",
|
||||
"blocked_by": "evidence_binding",
|
||||
"blocked_sources": ["evidence_binding"],
|
||||
},
|
||||
})
|
||||
else:
|
||||
self._send_json(400, {"error": reason})
|
||||
return
|
||||
_bconn = self._db()
|
||||
try:
|
||||
_store = _EvidenceNonceStore(_bconn)
|
||||
if not _store.consume(task_id,
|
||||
body["evidence_nonce"],
|
||||
body["evidence_ts"]):
|
||||
self._send_json(409, {
|
||||
"error": "release blocked",
|
||||
"release_gate": {
|
||||
"allowed": False, "http_status": 409,
|
||||
"code": "release_blocked_evidence_replayed",
|
||||
"blocked_by": "evidence_binding",
|
||||
"blocked_sources": ["evidence_binding"],
|
||||
},
|
||||
})
|
||||
return
|
||||
_bconn.commit()
|
||||
finally:
|
||||
_bconn.close()
|
||||
# 发布时刻的新证据:体里没给任何硬门禁字段时,hard_gate
|
||||
# 为 None -> 证据缺失 409,而不是用合并时的旧结论补。
|
||||
has_evidence = any(
|
||||
@@ -7060,6 +7131,10 @@ class CloudControlPlaneHandler(OAuthMixin, IdentityMemoryMixin, ProjectMixin, Au
|
||||
store.audit(user_id, task["project_id"], "release_task",
|
||||
task_id)
|
||||
task["release_gate"] = decision
|
||||
# 绑定出示标注(绝不谎称):bound = 本次提交带时间戳
|
||||
# /nonce 且通过单次校验;absent = 未出示绑定,走 #259
|
||||
# 旧路径(证据仍经硬门禁重算,但新鲜度未绑定)。
|
||||
task["evidence_binding"] = binding
|
||||
self._send_json(200, task)
|
||||
|
||||
# -- 运行时审批队列 ------------------------------------------------
|
||||
|
||||
Reference in new issue
Block a user