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-03 02:49:26 +00:00
1 parent b0d51b139c
commit 32620c4517
8 files changed
+200 -183

No files matched your search

+22 -7
View File
@@ -1,5 +1,15 @@
"""合并门禁:主分支保护 + 目录隔离(纯函数,无网络、无 GitHub 调用)。
A1 声明(服务端唯一门禁是 hard,不要拿本模块的模式推断服务端):
- 自 A1 起,服务端 POST /tasks/{id}/merge 一律按
cloud/merge_gate_hard 的 hard 求值;环境变量
CLOUD_MERGE_GATE_MODE 与请求体 merge_gate_mode 在服务端一律被
忽略,旧值 record/enforce/off 不再是服务端可选项。
- 本模块以下写到的 record/enforce/off 只是 #209 纯函数的历史语义:
它们只描述 evaluate_merge_gate / gate_from_request 这两个纯函数
本身,不再接线到任何服务端端点;纯函数单测保留这些语义,不代表
服务端会按此放行。需要服务端行为时只看 merge_gate_hard。
背景与诚实边界(先读,再用):
- 本模块**不能**替仓库开通主分支保护。保护是 GitHub 仓库设置,
只能由仓库管理员在 GitHub 侧开通;GitHub App id 5476837 的权限
@@ -23,8 +33,9 @@
不做 git 合并、不做目录检查、不做保护检查——那是本模块要补的
缺口,不是重复造检查。
模式(CLOUD_MERGE_GATE_MODE,默认 record):
- record(默认):未保护时必须显式确认(explicit_confirm)才放行;
模式(以下仅为本模块纯函数的历史语义,不是服务端行为;服务端
唯一门禁是 hard,见模块头 A1 声明):
- record(纯函数默认):未保护时必须显式确认(explicit_confirm)才放行;
无确认时阻断,错误码 branch_protection_risk_unconfirmed。
确认只覆盖“保护风险”,绝不覆盖目录隔离违规——越目录的写
只能靠 approved_exceptions(契约变更批准)放行,不能靠确认蒙混。
@@ -46,7 +57,9 @@ RISK_GATE_OFF = "merge gate is disabled (mode=off)"
def gate_mode_from_env(env=None):
"""环境变量里的模式;未设或非法时回落 record,绝不回落 off。"""
"""纯函数模式解析(非服务端行为,服务端不读它)。
环境变量里的模式;未设或非法时回落 record,绝不回落 off。"""
raw = (env if env is not None else os.environ).get(
"CLOUD_MERGE_GATE_MODE", "")
mode = (raw or "").strip().lower()
@@ -114,7 +127,7 @@ def check_directory_isolation(files_changed, owned_dir,
def evaluate_merge_gate(files_changed=None, owned_dir=None,
approved_exceptions=(), protection="unknown",
mode="record", explicit_confirm=False):
"""合并门禁纯函数。
"""合并门禁纯函数(非服务端门禁;服务端唯一门禁是 hard)。
files_changed=None 表示调用方没给 diff 证据:目录检查跳过
(directory=None),不因“没证据”就判违规,也不伪装成已检查。
@@ -151,14 +164,16 @@ def evaluate_merge_gate(files_changed=None, owned_dir=None,
def gate_from_request(body, env=None):
"""从 /merge 请求体 + 环境变量拼出门禁输入并求值。
"""从请求体 + 环境变量拼出门禁输入并求值(纯函数,非服务端路径。
服务端 POST /tasks/{id}/merge 不走本函数,一律按 hard 求值。
请求体字段(全是可选的;一个都不给时行为与旧接口一致,
只在响应里附带门禁结果,不阻断):
files_changed(str 列表)、owned_dir、approved_exceptions、
branch_protection、merge_gate_confirm(bool)。
模式优先取环境变量(运维定),环境变量未设时才取请求体
merge_gate_mode——请求体不能把运维定的 enforce 降级为 record。
模式(纯函数内)优先取环境变量,环境变量未设时才取请求体
merge_gate_mode——纯函数内请求体不能把环境变量定的 enforce
降级为 record;服务端则根本不读模式,一律 hard。
"""
body = body or {}
env_map = env if env is not None else os.environ