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:
host committed 2026-11-04 21:03:32 +00:00
1 parent 593197a4a2
commit e6a1346420
8 files changed
+200 -183

No files matched your search

+16 -37
View File
@@ -1,24 +1,20 @@
"""服务端合并硬门禁:五项全查、缺一 409、fail-closed(纯函数)。
本模块堆叠在 #209(cloud/merge_gate.py)之上,不改 #209 的语义:
- #209 的 ``evaluate_merge_gate`` / ``gate_from_request`` 原样保留:
record 默认、无证据不阻断、unknown 只记风险不阻断。那些是
#209 的过渡语义,本模块不碰它们,所以 #209 的测试一字不用改。
- 本模块只新增一个模式值 ``hard``,以及服务端用它时的模式解析:
``hard_mode_from_env``。服务端(POST /tasks/{id}/merge)以本模块
的解析为准,#209 的 ``gate_from_request`` 不再是服务端的模式来源
(它仍可作纯函数使用,但服务端不再经由它决定模式——否则调用方
可用请求体 ``merge_gate_mode=record`` 把硬门禁降级,那是
fail 但照样合并的路径,本模块堵的就是它)。
本模块堆叠在 #209(cloud/merge_gate.py)之上,不改 #209 纯函数
本身的语义,但服务端不再经由它决定模式:
- #209 的 ``evaluate_merge_gate`` / ``gate_from_request`` 只是
纯函数(record 默认、无证据不阻断、unknown 只记风险不阻断),
自 A1 起不再接线到任何服务端端点;它们的 record/enforce/off
语义是纯函数的历史语义,不是服务端行为,不得据此推断服务端
会放行(见 cloud/merge_gate.py 头部的 A1 声明)。
- 服务端唯一门禁就是本模块的 hard:POST /tasks/{id}/merge
一律按 hard 求值;环境变量 CLOUD_MERGE_GATE_MODE 与请求体
``merge_gate_mode`` 在服务端一律被忽略——旧值 record/enforce/off
不再是可选项,设了也等同 hard,不存在“显式降级”的服务端路径。
(此前 hard_mode_from_env 曾把显式 record/enforce/off 原样沿用,
A1 已删除该解析与 HARD_MODES:删模式,不是改默认值。)
模式(环境变量 CLOUD_MERGE_GATE_MODE,运维定、调用方改不了):
- hard(未设或非法值时都落到 hard,fail-closed,绝不回落 record/off):
下面五项必须全部通过,缺一就阻断。调用方请求体里的
``merge_gate_mode`` 在服务端被忽略——模式只由环境变量决定。
- record / enforce / off(必须显式设置):原样沿用 #209 的语义,
属过渡/显式降级。record 下无证据调用仍按旧接口放行(只附带门禁
信息),enforce 下未保护一律阻断,off 下只记风险不阻断。切到
这三者是运维的显式决定,不是调用方的默认权利。
模式(服务端唯一:hard):
硬门禁五项(全部是调用方在 /merge 请求体里提交的证据;
本服务没有可信渠道直连 GitHub 核实,所以门禁强制的是“证据齐全
@@ -50,7 +46,8 @@ PR/Checks 的只读核对去覆盖):
分支保护(硬模式下的第六条隐含项,来自 #209 的保护定义):
``branch_protection`` 必须为 ``"protected"``。``"unprotected"``
即使带 ``merge_gate_confirm`` 也阻断——硬模式没有“确认即放行”
的逃生口,确认只在 record 模式下覆盖保护风险。``"unknown"``
的逃生口(旧 record 模式下确认可覆盖保护风险的语义已随 A1
删模式一并失效,服务端不再有确认放行的路径)。``"unknown"``
(未声明或非法值)同样阻断:unknown 不等于 protected,这是
#209 里“unknown 只记风险”的过渡语义在硬模式下的收紧。
@@ -77,8 +74,6 @@ from cloud.merge_gate import (
check_directory_isolation,
)
HARD_MODES = ("hard", "record", "enforce", "off")
_BLOCK_DESCRIPTIONS = {
"ci_evidence_missing": "CI 证据缺失(ci_status / ci_checks / required_checks 有一项未给)",
"ci_not_green": "CI 状态不是 success",
@@ -100,22 +95,6 @@ _BLOCK_DESCRIPTIONS = {
}
def hard_mode_from_env(env=None):
"""服务端有效的门禁模式:环境变量定,调用方改不了。
未设、空值或非法值一律回落 hard(fail-closed)。注意这与
#209 的 gate_mode_from_env 不同:后者非法值回落 record,那是
#209 纯函数的过渡语义;服务端模式解析必须 fail-closed,所以
这里绝不回落到 record/off。
"""
import os
raw = (env if env is not None else os.environ).get(
"CLOUD_MERGE_GATE_MODE", "")
mode = (raw or "").strip().lower()
return mode if mode in HARD_MODES else "hard"
def _ci_checks_index(ci_checks):
"""ci_checks 列表 -> {name: status};条目不合规时返回 None。"""
index = {}