code-to10 v6 A1: delete server merge-gate modes, hard only
Server POST /tasks/{id}/merge no longer has record/enforce/off:
hard_mode_from_env and HARD_MODES deleted (not re-defaulted),
handler always evaluates the hard gate, env CLOUD_MERGE_GATE_MODE
and body merge_gate_mode are ignored server-side. The record
'no-evidence passes' and 'unprotected+confirm passes with
merge_gate_risk' server paths are gone with the dead risk audit;
no production caller justified keeping them (only production
caller of the non-hard evaluator was the merge handler itself;
audit_loop_wiring already used the hard gate). cloud/merge_gate
pure functions keep their #209 semantics but are documented as
non-server: they are no longer wired to any endpoint.
Server-level record/enforce/off tests rewritten to the new
behavior (renamed, not deleted silently); pure-function tests
unchanged.
(cherry picked from commit 08e75edbd6d04701965c5db951fa054cfbb75bb0)
Reconcile-port: handler_lifecycle.py hunks redirected into cloud/server.py _merge_task (target keeps 6-mixin split; mixin file does not exist here); handler_support.py kept at HEAD (its A1 hunk is a docstring of _validate_hard_gate_body, ported to server.py copy); test mock paths cloud.handler_lifecycle.* rewritten to cloud.server.*.
This commit is contained in:
1 parent
593197a4a2
commit
e6a1346420
8 files changed
+200
-183
No files matched your search
+33
-50
@@ -99,7 +99,6 @@ 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
|
||||
from cloud.audit_loop_wiring import build_quota_check as _loop_quota_check
|
||||
@@ -114,7 +113,6 @@ from cloud.audit_loop_wiring import load_loop_state as _loop_load_state
|
||||
from cloud.ledger import LedgerStore as _LedgerStore
|
||||
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
|
||||
from cloud.tree import DEFAULT_MAX_DEPTH as _TREE_DEFAULT_DEPTH
|
||||
from cloud.tree import DEFAULT_MAX_ENTRIES as _TREE_DEFAULT_ENTRIES
|
||||
from cloud.tree import build_tree as _build_tree
|
||||
@@ -1828,11 +1826,10 @@ def sweep_stale_tasks(db_path, threshold_seconds=_STALE_SWEEP):
|
||||
|
||||
|
||||
def _validate_hard_gate_body(body):
|
||||
"""硬模式请求体类型校验:畸形字段回 400,不进门禁判定。
|
||||
"""硬门禁请求体类型校验:畸形字段回 400,不进门禁判定。
|
||||
|
||||
只在 hard 模式调用;record/enforce/off 下这些字段按 #209 语义
|
||||
被忽略,不因此 400。缺失字段不是畸形,是无证据,交门禁判 409。
|
||||
返回错误串,无错返回 None。
|
||||
服务端只有 hard 模式,该校验一律适用。缺失字段不是畸形,
|
||||
是无证据,交门禁判 409。返回错误串,无错返回 None。
|
||||
"""
|
||||
for field in ("ci_checks", "required_checks", "approved_exceptions"):
|
||||
value = body.get(field)
|
||||
@@ -6847,12 +6844,16 @@ class CloudControlPlaneHandler(OAuthMixin, IdentityMemoryMixin, ProjectMixin, Au
|
||||
必须是 completed(其它状态一律 409,含已 merged 的重复
|
||||
调用,避免二次合并混淆)。成功后写审计 merge_task。
|
||||
|
||||
合并门禁(cloud/merge_gate + cloud/merge_gate_hard,补 v0.3
|
||||
合并门禁(cloud/merge_gate_hard,唯一服务端门禁,补 v0.3
|
||||
技能文本检查在服务端缺失的缺口;v0.3 之前本接口只改状态,
|
||||
不查目录、不查保护):
|
||||
模式只由环境变量 CLOUD_MERGE_GATE_MODE 决定,请求体里的
|
||||
merge_gate_mode 在服务端被忽略(调用方不能自行降级):
|
||||
- hard(默认:环境变量未设或非法值都落到 hard):五项全查,
|
||||
服务端只有 hard 一种模式:环境变量 CLOUD_MERGE_GATE_MODE
|
||||
与请求体里的 merge_gate_mode 在服务端一律被忽略(旧值
|
||||
record/enforce/off 自 A1 起不再是服务端可选项,设了也等同
|
||||
hard;此前 record 下“无证据放行”、record 下“未保护 + 确认
|
||||
放行并记 merge_gate_risk”、off 下“只记风险不阻断”三条
|
||||
服务端路径已随删模式一并消失,不存在受控降级口)。
|
||||
hard:五项全查,
|
||||
缺一 409 且不改状态,并写 merge_gate_blocked 审计。五项:
|
||||
① CI 全绿(ci_status=success + ci_checks 全 success +
|
||||
required_checks 非空且全 success);② 目录隔离(v0.3 口径,
|
||||
@@ -6864,12 +6865,7 @@ class CloudControlPlaneHandler(OAuthMixin, IdentityMemoryMixin, ProjectMixin, Au
|
||||
另:branch_protection 必须为 protected;unprotected 即使带
|
||||
merge_gate_confirm 也阻断,unknown 同样阻断(unknown
|
||||
不等于 protected)。无证据不放行(fail-closed)。
|
||||
- record / enforce / off(必须显式设置,属过渡/显式降级):
|
||||
原样沿用 #209 语义:record 下无证据调用仍按旧接口放行
|
||||
(只在响应里附带门禁结果),未保护 + 无确认阻断、+ 确认
|
||||
放行并记 merge_gate_risk;enforce 下未保护一律阻断;
|
||||
off 下只记风险不阻断。确认只覆盖保护风险,绝不覆盖目录违规。
|
||||
门禁阻断(任一模式)都写 merge_gate_blocked 审计后才回 409,
|
||||
门禁阻断都写 merge_gate_blocked 审计后才回 409,
|
||||
这是第 3 轨自动推回的阻塞依据:推回包取响应 merge_gate 的
|
||||
blocks 码,不要解析审计文本;本接口不建推回任务、不改状态机。
|
||||
审计推回接线(cloud/audit_wiring,第 1 项接线缺口):五项
|
||||
@@ -6878,8 +6874,8 @@ class CloudControlPlaneHandler(OAuthMixin, IdentityMemoryMixin, ProjectMixin, Au
|
||||
merge_gate.blocks 里追加 audit_pushback_blocked 码、响应
|
||||
里附 audit_pushback 对象(merge_blocked / release_blocked
|
||||
/ blocked_by="audit_pushback" / round / failed_checks)。
|
||||
推回阻塞与门禁模式无关:record 模式的显式确认只覆盖
|
||||
保护风险,绝不覆盖推回阻塞;链读不到(audit_ledger 表
|
||||
推回阻塞与门禁无关:显式确认在 hard 下不覆盖任何一项,
|
||||
绝不覆盖推回阻塞;链读不到(audit_ledger 表
|
||||
不存在)视为无推回状态,其它读链失败一律 fail-closed
|
||||
409,不放行。注意:本接口没有独立的发布端点,
|
||||
release_blocked 随响应返回、由未来的发布端点用同一
|
||||
@@ -6921,36 +6917,27 @@ class CloudControlPlaneHandler(OAuthMixin, IdentityMemoryMixin, ProjectMixin, Au
|
||||
body.get("files_changed"), list):
|
||||
self._send_json(400, {"error": "files_changed must be a list"})
|
||||
return
|
||||
mode = _merge_gate_hard_mode()
|
||||
protection = body.get("branch_protection") \
|
||||
or _merge_gate_protection()
|
||||
if mode == "hard":
|
||||
hard_err = _validate_hard_gate_body(body)
|
||||
if hard_err:
|
||||
self._send_json(400, {"error": hard_err})
|
||||
return
|
||||
gate = _merge_gate_hard(
|
||||
files_changed=body.get("files_changed"),
|
||||
owned_dir=body.get("owned_dir"),
|
||||
approved_exceptions=body.get("approved_exceptions") or (),
|
||||
protection=protection,
|
||||
ci_status=body.get("ci_status"),
|
||||
ci_checks=body.get("ci_checks"),
|
||||
required_checks=body.get("required_checks"),
|
||||
contract_version=body.get("contract_version"),
|
||||
contract_frozen=body.get("contract_frozen", False),
|
||||
contract_expected_version=body.get(
|
||||
"contract_expected_version"),
|
||||
coordinator_approved=body.get("coordinator_approved"),
|
||||
unresolved_review_comments=body.get(
|
||||
"unresolved_review_comments"))
|
||||
else:
|
||||
gate = _merge_gate_evaluate(
|
||||
files_changed=body.get("files_changed"),
|
||||
owned_dir=body.get("owned_dir"),
|
||||
approved_exceptions=body.get("approved_exceptions") or (),
|
||||
protection=protection, mode=mode,
|
||||
explicit_confirm=bool(body.get("merge_gate_confirm")))
|
||||
hard_err = _validate_hard_gate_body(body)
|
||||
if hard_err:
|
||||
self._send_json(400, {"error": hard_err})
|
||||
return
|
||||
gate = _merge_gate_hard(
|
||||
files_changed=body.get("files_changed"),
|
||||
owned_dir=body.get("owned_dir"),
|
||||
approved_exceptions=body.get("approved_exceptions") or (),
|
||||
protection=protection,
|
||||
ci_status=body.get("ci_status"),
|
||||
ci_checks=body.get("ci_checks"),
|
||||
required_checks=body.get("required_checks"),
|
||||
contract_version=body.get("contract_version"),
|
||||
contract_frozen=body.get("contract_frozen", False),
|
||||
contract_expected_version=body.get(
|
||||
"contract_expected_version"),
|
||||
coordinator_approved=body.get("coordinator_approved"),
|
||||
unresolved_review_comments=body.get(
|
||||
"unresolved_review_comments"))
|
||||
# 审计推回接线:链上阻塞与门禁五项为“与”关系,且与模式
|
||||
# 无关。读链失败(表不存在除外,见 audit_wiring)时
|
||||
# fail-closed:不证明无阻塞就不放行。
|
||||
@@ -7019,10 +7006,6 @@ class CloudControlPlaneHandler(OAuthMixin, IdentityMemoryMixin, ProjectMixin, Au
|
||||
conn.close()
|
||||
with _auth_store(self.server.db_path) as store:
|
||||
store.audit(user_id, task["project_id"], "merge_task", task_id)
|
||||
if gate["protection"] == "unprotected":
|
||||
store.audit(user_id, task["project_id"],
|
||||
"merge_gate_risk",
|
||||
"%s: %s" % (task_id, "; ".join(gate["risks"])))
|
||||
task["merge_gate"] = gate
|
||||
self._send_json(200, task)
|
||||
|
||||
|
||||
Reference in new issue
Block a user