fix(gate-check): workspace-agent login aliases + -agent suffix in tags #1281

Closed
dev-lead wants to merge 2 commits from fix/gate-check-login-aliases into main
3 changed files with 144 additions and 10 deletions
+6 -9
View File
@@ -604,20 +604,17 @@ jobs:
set -euo pipefail
# `needs.*.result` is one of: success | failure | cancelled | skipped | null.
# We assert success per dep (not != failure) — see RFC §2 reasoning above.
# Null results are skipped: they come from Phase 3 (continue-on-error: true
# suppresses status) or from jobs still in-flight. The sentinel succeeds
# rather than blocking PRs on Phase 3 noise.
# Null results are skipped: they come from jobs still in-flight.
# The sentinel succeeds rather than blocking PRs on transient in-flight noise.
results='${{ toJSON(needs) }}'
echo "$results"
echo "$results" | python3 -c '
import json, sys
ns = json.load(sys.stdin)
# Phase 3 masked: jobs with continue-on-error: true may report "failure"
# Remove when mc#774 handler test failures are resolved.
PHASE3_MASKED = {"platform-build"}
# Exclude null (Phase 3 suppressed / in-flight) from the bad list.
# Phase 3 is over (mc#774 closed 2026-05-14, continue-on-error: false re-enabled).
# All non-null/non-skipped/non-cancelled deps must succeed.
bad = [(k, v.get("result")) for k, v in ns.items()
if v.get("result") not in ("success", None, "cancelled", "skipped") and k not in PHASE3_MASKED]
if v.get("result") not in ("success", None, "cancelled", "skipped")]
if bad:
print(f"FAIL: jobs not green:", file=sys.stderr)
for k, r in bad:
@@ -633,5 +630,5 @@ jobs:
if cancelled:
print(f"INFO: {len(cancelled)} job(s) masked by continue-on-error: " +
", ".join(k for k, _ in cancelled), file=sys.stderr)
print(f"OK: all {len(ns)} required jobs succeeded (or Phase-3 suppressed)")
print(f"OK: all {len(ns)} required jobs succeeded")
'
+15 -1
View File
@@ -94,8 +94,11 @@ class GiteaError(Exception):
# (/pulls/{N}/comments) since agents post on both.
# Matches [core-{role}-agent] VERDICT anywhere in the comment body.
# Matches [core-{role}] or [core-{role}-agent] VERDICT.
# Handles both canonical logins and workspace-agent naming conventions.
# The role is extracted and the -agent suffix is stripped so both formats match.
AGENT_TAG_RE = re.compile(
r"\[core-([a-z]+)-agent\]\s+(APPROVED|N/?A|CHANGES_REQUESTED|COMMENT|BLOCKED|ACK)\b",
r"\[core-([a-z]+)(?:-agent)?\]\s+(APPROVED|N/?A|CHANGES_REQUESTED|COMMENT|BLOCKED|ACK)\b",
)
# Map agent role → canonical login (from workspace registry)
@@ -113,8 +116,19 @@ AGENT_LOGIN_MAP = {
# Map alternate Gitea logins → canonical logins for gate matching.
# infra-sre is the engineers/core-devops agent (same team, same work).
# Without this alias, infra-sre comments/reviews never satisfy the engineers gate.
# core-qa-agent / core-security-agent: QA/security agents post with their full workspace
# login names rather than the canonical form — alias so their reviews/comments satisfy
# the gate even without the agent's canonical login being added to the org.
LOGIN_ALIASES = {
"infra-sre": "core-devops",
"core-qa-agent": "core-qa",
"core-security-agent": "core-security",
"core-uiux-agent": "core-uiux",
"core-be-agent": "core-be",
"core-fe-agent": "core-fe",
"core-offsec-agent": "core-offsec",
"core-lead-agent": "core-lead",
"core-devops-agent": "core-devops",
}
# SOP-6 tier → required agent groups
+123
View File
@@ -74,3 +74,126 @@ def test_signal_1_infra_sre_login_alias_resolved_to_core_devops(monkeypatch):
engineers = result["results"]["core-devops"]
assert engineers["verdict"] == "APPROVED"
assert engineers["group"] == "engineers"
def test_signal_1_core_qa_agent_alias_issue_comment(monkeypatch):
"""core-qa-agent posts [qa-agent] APPROVED as issue comment → gate satisfied via alias."""
mod = load_gate_check()
def fake_api_get(path):
# PR 901 has tier:medium label (AND gate: all of managers, engineers, qa, security)
if path == "/repos/molecule-ai/molecule-core/pulls/901":
return {
"number": 901,
"labels": [{"name": "tier:medium"}],
}
raise AssertionError(f"unexpected api_get: {path}")
def fake_api_list(path):
if path == "/repos/molecule-ai/molecule-core/issues/901/comments":
# All four agents post on the issue. QA uses the -agent workspace login alias.
return [
{
"id": 100,
"user": {"login": "core-lead"},
"body": "[core-lead-agent] APPROVED\n\nLGTM managers",
"created_at": "2026-05-14T08:00:00Z",
},
{
"id": 101,
"user": {"login": "core-devops"},
"body": "[core-devops-agent] APPROVED\n\nLGTM engineers",
"created_at": "2026-05-14T09:00:00Z",
},
{
"id": 200,
"user": {"login": "core-qa-agent"},
"body": "[core-qa-agent] APPROVED\n\nGo tests pass, coverage 100%",
"created_at": "2026-05-14T10:00:00Z",
},
{
"id": 300,
"user": {"login": "core-security"},
"body": "[core-security-agent] APPROVED\n\nNo secrets exposed",
"created_at": "2026-05-14T11:00:00Z",
},
]
if path == "/repos/molecule-ai/molecule-core/pulls/901/comments":
return []
if path == "/repos/molecule-ai/molecule-core/pulls/901/reviews":
return []
raise AssertionError(f"unexpected api_list: {path}")
monkeypatch.setattr(mod, "api_get", fake_api_get)
monkeypatch.setattr(mod, "api_list", fake_api_list)
result = mod.signal_1_comment_scan(901, "molecule-ai/molecule-core")
assert result["verdict"] == "CLEAR"
assert result["signal"] == "agent_tag_comments"
# core-qa-agent (aliased to core-qa) should satisfy qa gate
qa_gate = result["results"]["core-qa"]
assert qa_gate["verdict"] == "APPROVED"
assert qa_gate["group"] == "qa"
def test_signal_1_core_security_agent_alias_formal_review(monkeypatch):
"""core-security-agent posts formal APPROVED review → gate satisfied via alias."""
mod = load_gate_check()
def fake_api_get(path):
if path == "/repos/molecule-ai/molecule-core/pulls/902":
return {
"number": 902,
"labels": [{"name": "tier:medium"}],
}
raise AssertionError(f"unexpected api_get: {path}")
def fake_api_list(path):
if path == "/repos/molecule-ai/molecule-core/issues/902/comments":
# managers + engineers + qa post via issue comment
return [
{
"id": 100,
"user": {"login": "core-lead"},
"body": "[core-lead-agent] APPROVED",
"created_at": "2026-05-15T08:00:00Z",
},
{
"id": 101,
"user": {"login": "core-devops"},
"body": "[core-devops-agent] APPROVED",
"created_at": "2026-05-15T09:00:00Z",
},
{
"id": 200,
"user": {"login": "core-qa"},
"body": "[core-qa-agent] APPROVED",
"created_at": "2026-05-15T10:00:00Z",
},
]
if path == "/repos/molecule-ai/molecule-core/pulls/902/comments":
return []
if path == "/repos/molecule-ai/molecule-core/pulls/902/reviews":
# security posts formal APPROVED review (not an issue comment)
return [
{
"id": 300,
"user": {"login": "core-security-agent"},
"state": "APPROVED",
"submitted_at": "2026-05-15T11:00:00Z",
}
]
raise AssertionError(f"unexpected api_list: {path}")
monkeypatch.setattr(mod, "api_get", fake_api_get)
monkeypatch.setattr(mod, "api_list", fake_api_list)
result = mod.signal_1_comment_scan(902, "molecule-ai/molecule-core")
assert result["verdict"] == "CLEAR"
assert result["signal"] == "agent_tag_comments"
# core-security-agent (aliased to core-security) should satisfy security gate
security_gate = result["results"]["core-security"]
assert security_gate["verdict"] == "APPROVED"
assert security_gate["group"] == "security"