feat(orchestrator): SC-7 蜂后落 main — git push 最优产物(#16)
CI / guardrails (pull_request) Successful in 11s
CI / tests (pull_request) Failing after 11m52s
CI / tests (push) Failing after 11m53s
CI / guardrails (push) Failing after 11m53s

让蜂后选最优后把产物合并到产物仓 main,run 交付一份连贯产物而非 N 个碎片分支:
- create 存 git grant 引用(repo_url + secret_ref,均非明文)到 run.metadata;凭据不存
- queen.promote_to_main:从 secret_ref 现取凭据 → clone base_branch → 写最优产物 → commit → push;
  _auth_url 嵌入并 URL-encode 凭据(不入日志);best-effort 不破坏终态
- main.py 终态:winner → promote_to_main → deliverable.promoted_to_main{branch,commit_sha}
- Dockerfile.orchestrator 加 git CLI
- test-queen 扩展(auth_url 编码 + no_git_grant 分支),14 检查全过;全套契约通过

注:真实 git push e2e 需重建 orchestrator 镜像 + 部署 + gitea 产物仓验证(后续);
当前纯代码 + 单元测完成。凭据经 secret_ref 现取、不存 run、不入日志(组织规则#8)。

影响:仅 orchestrator 终态聚合;不涉及 Manager 契约/计费/发布链路。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
gongzhiyong
2026-06-19 21:38:43 +08:00
co-authored by Claude Opus 4.8
parent 0c3af0425d
commit 2672a3d4b6
4 changed files with 126 additions and 4 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ FROM python:3.11-slim
# to create/teardown agent Pods + per-swarm key Secrets. Without it the k8s backend fails (0 agents).
# Pinned to the cluster minor (AKS 1.34) per kubectl skew policy. (linux/amd64 — AKS default node arch.)
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl ca-certificates \
&& apt-get install -y --no-install-recommends curl ca-certificates git \
&& KUBECTL_VERSION="$(curl -fsSL https://dl.k8s.io/release/stable-1.34.txt)" \
&& curl -fsSL "https://dl.k8s.io/release/${KUBECTL_VERSION}/bin/linux/amd64/kubectl" -o /usr/local/bin/kubectl \
&& chmod +x /usr/local/bin/kubectl \
+25 -2
View File
@@ -741,9 +741,20 @@ async def refresh_swarm_run_status(run):
# The Queen verdict was computed by the quality gate above (run.metadata['queen']); mark the
# selected winner on the deliverable so the result is one coherent pick, not N branches.
queen_summary = run.metadata.get("queen") or {}
if isinstance(deliverable, dict) and queen_summary.get("winner"):
deliverable["selected"] = queen_summary["winner"]
winner = queen_summary.get("winner") if isinstance(queen_summary, dict) else None
if isinstance(deliverable, dict) and winner:
deliverable["selected"] = winner
deliverable["candidate_count"] = queen_summary.get("candidate_count")
# SC-7: promote the winning artifact to the repo's main → one coherent deliverable on
# main, not N scattered agent branches. No-op when the run has no git grant.
try:
promo = await queen_mod.promote_to_main(run, tasks, winner.get("task_id"))
run.metadata["queen_promotion"] = promo
if isinstance(deliverable, dict) and promo.get("promoted"):
deliverable["promoted_to_main"] = {
"branch": promo.get("branch"), "commit_sha": promo.get("commit_sha")}
except Exception as exc:
logger.warning("queen promote_to_main failed for run %s: %s", run.swarm_id, exc)
await swarm_runtime.save_run(run)
# Benchmark Group B: grade the run's generated code against its held-out fixture tests in
@@ -1939,6 +1950,18 @@ async def create_swarm_run_from_request(
if created and run.status == "running":
await create_tasks_for_run(run, body)
await launch_swarm_agents(run, body)
# Persist the git grant REFERENCE (repo_url + secret_ref — both non-secret; the actual
# credential is resolved from secret_ref at promote time and never stored) so the Queen can
# push the winning artifact to the repo's main at run terminal (SC-7). Never store creds.
_git_grant = agent_launcher._first_git_grant(body)
if _git_grant:
_meta = _git_grant.get("metadata") or {}
run.metadata["git_grant"] = {
"repo_url": _meta.get("repo_url") or _git_grant.get("repo_url"),
"secret_ref": _git_grant.get("secret_ref"),
"base_branch": _meta.get("base_branch", "main"),
}
await swarm_runtime.save_run(run)
return {
"success": True,
+81
View File
@@ -154,3 +154,84 @@ async def aggregate_run(run, tasks) -> Dict[str, Any]:
except Exception as exc: # Queen never breaks the run's terminal path
logger.warning("queen: aggregate_run failed for %s: %s", getattr(run, "swarm_id", "?"), exc)
return {"winner": None, "candidate_count": 0, "reason": f"error:{exc!r}"}
def _auth_url(repo_url: str, user: Optional[str], pw: Optional[str]) -> str:
"""Embed credentials into an http(s) clone URL. URL-encodes the password (handles '@','/', etc).
The result is NEVER logged."""
import urllib.parse
if not user or not pw or "://" not in repo_url:
return repo_url
scheme, rest = repo_url.split("://", 1)
return f"{scheme}://{urllib.parse.quote(user, safe='')}:{urllib.parse.quote(pw, safe='')}@{rest}"
def _git_promote(env: Dict[str, str], base_branch: str, files: List[Any], swarm_id: str) -> Dict[str, Any]:
"""Sync git: clone base_branch → overwrite with winner files → commit → push base_branch.
Blocking (run via asyncio.to_thread). Credentials live only in the clone URL, never logged."""
import os
import shutil
import subprocess
import tempfile
repo_url = env["GIT_REPO_URL"]
auth_url = _auth_url(repo_url, env.get("GIT_USERNAME"), env.get("GIT_PASSWORD"))
workdir = tempfile.mkdtemp(prefix="queen-promote-")
repo_dir = os.path.join(workdir, "repo")
def git(*args, cwd=None):
return subprocess.run(["git", *args], cwd=cwd or workdir,
capture_output=True, text=True, timeout=180)
try:
if git("clone", "--depth", "1", "--branch", base_branch, auth_url, "repo").returncode != 0:
return {"promoted": False, "reason": "clone_failed"}
for f in files:
dest = os.path.join(repo_dir, f.path)
os.makedirs(os.path.dirname(dest) or repo_dir, exist_ok=True)
with open(dest, "w", encoding="utf-8") as fh:
fh.write(f.content)
git("config", "user.email", "queen@heicode.swarm", cwd=repo_dir)
git("config", "user.name", "HeiCode Queen", cwd=repo_dir)
git("add", "-A", cwd=repo_dir)
c = git("commit", "-m", f"queen: promote best swarm artifact ({swarm_id})", cwd=repo_dir)
if c.returncode != 0:
return {"promoted": False, "reason": "no_changes"}
if git("push", "origin", base_branch, cwd=repo_dir).returncode != 0:
return {"promoted": False, "reason": "push_failed"}
sha = git("rev-parse", "HEAD", cwd=repo_dir).stdout.strip()
return {"promoted": True, "commit_sha": sha, "branch": base_branch}
except Exception as exc:
return {"promoted": False, "reason": f"error:{exc!r}"}
finally:
shutil.rmtree(workdir, ignore_errors=True)
async def promote_to_main(run, tasks, winner_task_id: str) -> Dict[str, Any]:
"""SC-7: push the winning candidate's files to the artifact repo's base branch (main), so the
run delivers ONE coherent artifact, not N scattered agent branches. Credentials are resolved
from the grant's secret_ref at call time (never stored/logged). Best-effort: never raises."""
try:
grant = (run.metadata or {}).get("git_grant") or {}
repo_url = grant.get("repo_url")
if not repo_url:
return {"promoted": False, "reason": "no_git_grant"}
wtask = next((t for t in tasks if getattr(t, "task_id", None) == winner_task_id), None)
if wtask is None:
return {"promoted": False, "reason": "winner_not_found"}
from .quality import collect_generated_files
cf = collect_generated_files([wtask])
files = (cf.get("impl") or []) + (cf.get("agent_tests") or [])
if not files:
return {"promoted": False, "reason": "winner_no_files"}
from .agent_launcher import resolve_git_grant
env = resolve_git_grant({"resource_grants": [{
"resource_type": "git", "secret_ref": grant.get("secret_ref"),
"metadata": {"repo_url": repo_url, "base_branch": grant.get("base_branch", "main")},
}]})
if not env or "GIT_REPO_URL" not in env:
return {"promoted": False, "reason": "grant_unresolved"}
return await asyncio.to_thread(_git_promote, env, grant.get("base_branch", "main"),
files, getattr(run, "swarm_id", "?"))
except Exception as exc:
logger.warning("queen: promote_to_main failed for %s: %s", getattr(run, "swarm_id", "?"), exc)
return {"promoted": False, "reason": f"error:{exc!r}"}
+19 -1
View File
@@ -11,7 +11,9 @@ os.environ.setdefault("REDIS_FAKE", "1")
os.environ.setdefault("ALLOW_MEMORY_STORE", "1")
os.environ.setdefault("OPENAI_API_KEY", "test-key")
from orchestrator.queen import Candidate, select_best, should_bounce
import asyncio
from orchestrator.queen import Candidate, select_best, should_bounce, _auth_url, promote_to_main
_failures = []
@@ -58,6 +60,22 @@ check("no threshold → no bounce", should_bounce(_summary(0.0), None, cycles=0,
# unscored → honest, don't bounce
check("unscored → no bounce", should_bounce(_summary(None), 80.0, cycles=0, max_cycles=2) is False)
# --- SC-7 promote_to_main ---
# _auth_url embeds + URL-encodes creds; passthrough when missing
check("auth url embeds + encodes creds", _auth_url("http://h/r.git", "u", "p@ss") == "http://u:p%40ss@h/r.git")
check("auth url passthrough w/o creds", _auth_url("http://h/r.git", None, None) == "http://h/r.git")
class _RunNoGrant:
def __init__(self):
self.metadata = {}
self.swarm_id = "s1"
# promote is a no-op (not an error) when the run has no git grant
_promo = asyncio.run(promote_to_main(_RunNoGrant(), [], "t1"))
check("promote without grant → not promoted", _promo.get("promoted") is False and _promo.get("reason") == "no_git_grant")
if _failures:
print(f"\nFAILED: {len(_failures)} check(s): {_failures}")
raise SystemExit(1)