feat: configurable merge gate (branch protection + directory isolation) and skill body/appendix checks
- cloud/merge_gate.py: pure gate; directory check reuses the v0.3
git diff --name-only merge-base command; protection modes
record (default, explicit confirm required when unprotected),
enforce (always blocks when unprotected), off (explicit only).
Confirm covers protection risk only, never directory violations.
Protection status is caller/env-declared only; unknown is a
recorded risk, not a claim of protection.
- server POST /tasks/{id}/merge: applies the gate only when gate
evidence is supplied (or env declares protection); no-evidence
calls behave as before plus a merge_gate field in the response.
Blocked merges return 409 without changing status; confirmed
unprotected merges write a merge_gate_risk audit entry.
- skill_seed.py: whole-directory seeding evaluated and rejected
(cannot preserve tenant deletions; no benefit while the pack
holds only SKILL.md); SKILL.md-only behaviour unchanged.
- software-engineering SKILL.md: gate paragraph added to the body
and to the coordinator + client appendix sections (v0.2 text on
this base; port to v0.3 text on rebase after #199 merges).
- tests: 16 new tests covering gate modes, directory reuse,
body/appendix bidirectional assertions naming the missing side,
SKILL.md-only seeding and tenant-edit preservation.
This commit is contained in:
1 parent
44616a03dc
commit
d553f9549c
5 files changed
+565
No files matched your search
@@ -74,6 +74,7 @@ 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.merge_gate import gate_from_request as _merge_gate_from_request
|
||||
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
|
||||
@@ -7614,6 +7615,18 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
|
||||
必须是项目 maintainer 以上(approve_merge,403)、任务
|
||||
必须是 completed(其它状态一律 409,含已 merged 的重复
|
||||
调用,避免二次合并混淆)。成功后写审计 merge_task。
|
||||
|
||||
合并门禁(cloud/merge_gate,补 v0.3 技能文本检查在服务端
|
||||
缺失的缺口;v0.3 之前本接口只改状态,不查目录、不查保护):
|
||||
请求体可选字段 files_changed / owned_dir /
|
||||
approved_exceptions / branch_protection / merge_gate_confirm
|
||||
/ merge_gate_mode;一个都不给时行为与旧接口一致(只在响应
|
||||
里附带门禁结果,不阻断)。给了证据才按门禁判:越目录
|
||||
(v0.3 口径)或未保护且无确认(record 模式)或未保护
|
||||
(enforce 模式)时 409 且不改状态;未保护 + 确认时放行,
|
||||
风险记入响应与 merge_gate_risk 审计。确认只覆盖保护风险,
|
||||
绝不覆盖目录违规。保护状态 unknown 时记风险、不阻断——
|
||||
本服务没有可信渠道核实 GitHub 保护,unknown 不等于已保护。
|
||||
"""
|
||||
row = self._fetch_task(task_id)
|
||||
if row is None:
|
||||
@@ -7631,6 +7644,21 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
|
||||
"status": task["status"],
|
||||
})
|
||||
return
|
||||
body, jerr = self._read_json()
|
||||
if jerr:
|
||||
self._send_json(400, {"error": jerr})
|
||||
return
|
||||
if body.get("files_changed") is not None and not isinstance(
|
||||
body.get("files_changed"), list):
|
||||
self._send_json(400, {"error": "files_changed must be a list"})
|
||||
return
|
||||
gate = _merge_gate_from_request(body)
|
||||
if not gate["allowed"]:
|
||||
self._send_json(409, {
|
||||
"error": "merge gate blocked",
|
||||
"merge_gate": gate,
|
||||
})
|
||||
return
|
||||
now = time.time()
|
||||
conn = self._db()
|
||||
try:
|
||||
@@ -7649,6 +7677,11 @@ class CloudControlPlaneHandler(BaseHTTPRequestHandler):
|
||||
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