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

+37 -29
View File
@@ -1,12 +1,16 @@
"""主分支保护 + 目录隔离门禁(cloud/merge_gate)与技能工程双向断言。
门禁口径:
- 目录隔离复用 v0.3 技能文本的 git diff --name-only 检查,不另造;
服务端 POST /tasks/{id}/merge 在此之前只改状态、不查目录、不查保护。
- 保护默认 record:未保护 + 无确认阻断,未保护 + 确认放行并记风险;
enforce 下未保护一律阻断;unknown 记风险、不阻断(代码无法核实
GitHub 保护,unknown 不等于已保护,也不等于未保护)。
- 显式确认只覆盖保护风险,绝不覆盖目录违规。
门禁口径(纯函数部分,见 A1 声明):
- 本文件的纯函数用例测 cloud/merge_gate 本身:目录隔离复用 v0.3
技能文本的 git diff --name-only 检查,不另造;纯函数内 record
默认:未保护 + 无确认阻断,未保护 + 确认放行并记风险;
enforce 下未保护一律阻断;unknown 记风险、不阻断。
- 这些纯函数语义不是服务端行为:自 A1 起服务端 POST
/tasks/{id}/merge 一律按 hard 求值,旧模式环境变量在服务端被
忽略;本文件末尾的服务端用例已按新行为改写(旧 record/enforce
服务端用例不再断言放行)。
- 显式确认只覆盖保护风险,绝不覆盖目录违规(纯函数内;服务端
hard 下确认不覆盖任何一项)。
技能工程:
- 正文与附录双向断言:每个新关键词在正文、附录都要出现,失败
信息指明缺失的一侧(正文缺失 / 附录缺失),不是笼统的 assertIn。
@@ -260,17 +264,19 @@ class MergeEndpointGateTestCase(unittest.TestCase):
conn.close()
return task
def test_no_gate_fields_behaves_like_old_endpoint(self):
# 本用例测的是 record 模式的过渡语义(#209 原意):默认已是
# hard,见 test_merge_gate_hard.py 的默认 409 用例;这里显式
# 切 record 才沿用“无证据放行、只附带门禁信息”。
def test_record_env_no_longer_passes_no_fields(self):
# A1:服务端不再有 record 模式。旧用例名
# test_no_gate_fields_behaves_like_old_endpoint 已改名改语义:
# record 环境变量被忽略,无证据调用按 hard 409、状态不变。
os.environ["CLOUD_MERGE_GATE_MODE"] = "record"
task = self._completed_task()
status, merged = self._req(
status, body = self._req(
"POST", "/tasks/%s/merge" % task["id"], {}, self.owner_key)
self.assertEqual(status, 200)
self.assertEqual(merged["status"], "merged")
self.assertIn("merge_gate", merged)
self.assertEqual(status, 409)
self.assertEqual(body["merge_gate"]["mode"], "hard")
_s, got = self._req("GET", "/tasks/%s" % task["id"],
key=self.owner_key)
self.assertEqual(got["status"], "completed")
def test_directory_violation_blocks_without_changing_status(self):
task = self._completed_task()
@@ -286,28 +292,30 @@ class MergeEndpointGateTestCase(unittest.TestCase):
key=self.owner_key)
self.assertEqual(got["status"], "completed")
def test_unprotected_confirm_allows_and_audits_risk(self):
# record 模式语义:硬模式下未保护 + 确认仍阻断,见硬门禁测试。
def test_record_env_unprotected_confirm_no_longer_passes(self):
# A1:旧用例 test_unprotected_confirm_allows_and_audits_risk
# 已改名改语义。record 下“未保护 + 确认放行并记
# merge_gate_risk”的服务端路径已消失:确认在 hard 下不覆盖
# 任何一项,且不再写 merge_gate_risk。
os.environ["CLOUD_MERGE_GATE_MODE"] = "record"
task = self._completed_task()
status, body = self._req(
"POST", "/tasks/%s/merge" % task["id"],
{"branch_protection": "unprotected"},
self.owner_key)
self.assertEqual(status, 409)
task2 = self._completed_task()
status, merged = self._req(
"POST", "/tasks/%s/merge" % task2["id"],
{"branch_protection": "unprotected",
"merge_gate_confirm": True},
self.owner_key)
self.assertEqual(status, 200)
self.assertEqual(merged["merge_gate"]["protection"], "unprotected")
self.assertEqual(status, 409)
self.assertIn("branch_protection_unprotected",
body["merge_gate"]["blocks"])
audit = self.auth.list_audit(self.project["id"])
self.assertTrue(any(a["action"] == "merge_gate_risk"
for a in audit))
self.assertFalse(any(a["action"] == "merge_gate_risk"
for a in audit))
def test_enforce_env_blocks_even_with_confirm(self):
def test_enforce_env_is_ignored_and_hard_blocks(self):
# A1:旧用例 test_enforce_env_blocks_even_with_confirm 改写:
# enforce 环境变量被忽略,阻断码是 hard 门禁的
# branch_protection_unprotected,不是纯函数的
# branch_protection_enforce。
os.environ["CLOUD_MERGE_GATE_MODE"] = "enforce"
os.environ["CLOUD_BRANCH_PROTECTION"] = "unprotected"
task = self._completed_task()
@@ -316,7 +324,7 @@ class MergeEndpointGateTestCase(unittest.TestCase):
{"merge_gate_confirm": True},
self.owner_key)
self.assertEqual(status, 409)
self.assertIn("branch_protection_enforce",
self.assertIn("branch_protection_unprotected",
body["merge_gate"]["blocks"])