Harden local phase 1b review path

This commit is contained in:
twentyOne2x 2026-05-29 14:16:12 +02:00
parent b9cb965591
commit ca96f5f8e3
5 changed files with 63 additions and 26 deletions

View file

@ -150,18 +150,17 @@ Pre-implementation repo truth:
Estimated implementation progress on this branch: Estimated implementation progress on this branch:
- B1 classifier foundation: 100 percent locally, pending staging calibration. - B1 classifier foundation: 100 percent locally, pending staging calibration.
- B2 routing layer: 70 percent locally behind a default-off feature flag. - B2 routing layer: 75 percent locally behind a default-off feature flag.
- Cross-domain top-2 review: 70 percent locally through mocked eval proof. - Cross-domain top-2 review: 75 percent locally through mocked eval proof.
- Local proof suite: 80 percent for router/eval/parser scope. - Local proof suite: 85 percent for router/eval/parser scope.
- Staging or VPS proof: 0 percent. - Staging or VPS proof: 0 percent.
Remaining delta: Remaining delta:
1. Decide whether the production Phase 1b transport stays Forgejo-first for cutover or switches direct to GitHub `decision-engine` before staging. 1. Decide whether the production Phase 1b transport stays Forgejo-first for cutover or switches direct to GitHub `decision-engine` before staging.
2. Add stronger idempotency for duplicate comments on retry after partial multi-agent success. 2. Update reporting/health compatibility beyond `review_records` if staging shows false readiness.
3. Update reporting/health compatibility beyond `review_records` if staging shows false readiness. 3. Prove against staging before production.
4. Prove against staging before production. 4. Deploy only an exact reviewed/tested SHA after Leo signoff.
5. Deploy only an exact reviewed/tested SHA after Leo signoff.
## Closure, Endpoint, And Deployment Truth ## Closure, Endpoint, And Deployment Truth

View file

@ -71,14 +71,13 @@ Branch truth:
## Completion Percent And Remaining Delta ## Completion Percent And Remaining Delta
Current completion on this branch: 70 percent local implementation behind a default-off feature flag. Current completion on this branch: 75 percent local implementation behind a default-off feature flag.
Remaining delta: Remaining delta:
1. Decide direct GitHub `decision-engine` comment transport versus Forgejo-first cutover compatibility. 1. Decide direct GitHub `decision-engine` comment transport versus Forgejo-first cutover compatibility.
2. Add duplicate-comment idempotency for retry after partial multi-agent success. 2. Prove with staging PRs and real daemon logs.
3. Prove with staging PRs and real daemon logs. 3. Update contributor/dashboard assumptions only where staging or tests prove breakage.
4. Update contributor/dashboard assumptions only where staging or tests prove breakage.
## Closure, Endpoint, And Deployment Truth ## Closure, Endpoint, And Deployment Truth
@ -138,6 +137,7 @@ Minimal DB field use:
- `domain_verdict`: keep aggregate non-Leo review verdict or aggregate verdict. - `domain_verdict`: keep aggregate non-Leo review verdict or aggregate verdict.
- `leo_verdict`: set `skipped` unless Leo is a required agent; if Leo is required, store Leo verdict. - `leo_verdict`: set `skipped` unless Leo is a required agent; if Leo is required, store Leo verdict.
- `review_records`: write one row per required reviewer attempt with reviewer agent, model, outcome, and notes. - `review_records`: write one row per required reviewer attempt with reviewer agent, model, outcome, and notes.
- review comments include a `PHASE1B_REVIEW` marker and the current local helper suppresses duplicate posts for the same PR and agent.
- audit log: route and all per-agent verdicts. - audit log: route and all per-agent verdicts.
This is a compatibility posture, not the ideal long-term schema. This is a compatibility posture, not the ideal long-term schema.

View file

@ -12,20 +12,14 @@ DOMAIN_AGENT_MAP: dict[str, str] = {
"entertainment": "Clay", "entertainment": "Clay",
"health": "Vida", "health": "Vida",
"ai-alignment": "Theseus", "ai-alignment": "Theseus",
"ai-systems": "Theseus",
"space-development": "Astra", "space-development": "Astra",
"space": "Astra",
"robotics": "Astra",
"energy": "Astra",
"manufacturing": "Astra",
"advanced-manufacturing": "Astra",
"mechanisms": "Rio", "mechanisms": "Rio",
"living-capital": "Rio", "living-capital": "Rio",
"living-agents": "Theseus", "living-agents": "Theseus",
"teleohumanity": "Leo", "teleohumanity": "Leo",
"grand-strategy": "Leo", "grand-strategy": "Leo",
"critical-systems": "Theseus", "critical-systems": "Theseus",
"collective-intelligence": "Leo", "collective-intelligence": "Theseus",
"teleological-economics": "Rio", "teleological-economics": "Rio",
"cultural-dynamics": "Clay", "cultural-dynamics": "Clay",
} }
@ -37,7 +31,7 @@ VALID_DOMAINS: frozenset[str] = frozenset(DOMAIN_AGENT_MAP.keys())
_AGENT_PRIMARY_DOMAIN: dict[str, str] = { _AGENT_PRIMARY_DOMAIN: dict[str, str] = {
"rio": "internet-finance", "rio": "internet-finance",
"clay": "entertainment", "clay": "entertainment",
"theseus": "ai-systems", "theseus": "ai-alignment",
"vida": "health", "vida": "health",
"astra": "space-development", "astra": "space-development",
"leo": "grand-strategy", "leo": "grand-strategy",

View file

@ -80,6 +80,30 @@ def _phase1b_compat_verdicts(agent_verdicts: dict[str, str]) -> tuple[str, str]:
return leo_verdict, domain_verdict return leo_verdict, domain_verdict
def _phase1b_review_marker(pr_number: int, agent: str) -> str:
return f"<!-- PHASE1B_REVIEW:PR={pr_number}:AGENT={agent.upper()} -->"
async def _post_phase1b_review_comment(pr_number: int, agent: str, review_text: str) -> bool:
"""Post a routed review comment once per PR/agent marker."""
marker = _phase1b_review_marker(pr_number, agent)
comments = await forgejo_api("GET", repo_path(f"issues/{pr_number}/comments"))
if isinstance(comments, list):
for comment in comments:
body = comment.get("body", "") if isinstance(comment, dict) else ""
if marker in body:
logger.info("PR #%d: Phase 1b %s review comment already posted", pr_number, agent)
return False
body = review_text if marker in review_text else f"{marker}\n{review_text}"
result = await forgejo_api(
"POST",
repo_path(f"issues/{pr_number}/comments"),
{"body": body},
)
return result is not None
async def _evaluate_pr_phase1b( async def _evaluate_pr_phase1b(
conn, conn,
pr_number: int, pr_number: int,
@ -133,18 +157,14 @@ async def _evaluate_pr_phase1b(
agent_verdicts[agent] = verdict agent_verdicts[agent] = verdict
usage_by_agent[agent] = usage usage_by_agent[agent] = usage
await forgejo_api( await _post_phase1b_review_comment(pr_number, agent, review_text)
"POST",
repo_path(f"issues/{pr_number}/comments"),
{"body": review_text},
)
db.record_review( db.record_review(
conn, conn,
pr_number, pr_number,
"approved" if verdict == "approve" else "rejected", "approved" if verdict == "approve" else "rejected",
domain=domain, domain=domain,
agent=route.primary_agent, agent=agent,
reviewer=agent, reviewer=agent,
reviewer_model=_phase1b_review_model(agent, tier), reviewer_model=_phase1b_review_model(agent, tier),
rejection_reason=",".join(parse_issues(review_text)) if verdict == "request_changes" else None, rejection_reason=",".join(parse_issues(review_text)) if verdict == "request_changes" else None,

View file

@ -6,7 +6,7 @@ from unittest.mock import AsyncMock
import pytest import pytest
from lib import config from lib import config
from lib.evaluate import _evaluate_pr_phase1b, evaluate_pr from lib.evaluate import _evaluate_pr_phase1b, _post_phase1b_review_comment, evaluate_pr
@pytest.fixture @pytest.fixture
@ -116,6 +116,8 @@ async def _fake_agent_review_reject_vida(_diff, _files, agent, _route_context, t
async def _fake_forgejo_api(method, path, body=None, token=None): async def _fake_forgejo_api(method, path, body=None, token=None):
if method == "GET" and "comments" in path:
return []
if method == "GET" and "pulls/" in path: if method == "GET" and "pulls/" in path:
return {"user": {"login": "contributor"}} return {"user": {"login": "contributor"}}
return {"id": 1} return {"id": 1}
@ -154,6 +156,10 @@ async def test_phase1b_cross_domain_approves_after_all_required_agents(phase1b_c
assert row["domain_agent"] in {"Theseus", "Rio"} assert row["domain_agent"] in {"Theseus", "Rio"}
review_count = conn.execute("SELECT COUNT(*) AS n FROM review_records WHERE pr_number = 1").fetchone()["n"] review_count = conn.execute("SELECT COUNT(*) AS n FROM review_records WHERE pr_number = 1").fetchone()["n"]
assert review_count == 2 assert review_count == 2
reviewers = {
row["agent"] for row in conn.execute("SELECT agent FROM review_records WHERE pr_number = 1").fetchall()
}
assert reviewers == {"Theseus", "Rio"}
post_formal.assert_awaited_once() post_formal.assert_awaited_once()
@ -212,3 +218,21 @@ async def test_evaluate_pr_flag_uses_phase1b_and_not_legacy_reviewers(phase1b_co
assert result["agent_verdicts"] == {"Rio": "approve"} assert result["agent_verdicts"] == {"Rio": "approve"}
legacy_domain.assert_not_awaited() legacy_domain.assert_not_awaited()
legacy_leo.assert_not_awaited() legacy_leo.assert_not_awaited()
@pytest.mark.asyncio
async def test_phase1b_review_comment_is_idempotent(monkeypatch):
calls = []
async def fake_api(method, path, body=None, token=None):
calls.append((method, path, body))
if method == "GET":
return [{"body": "<!-- PHASE1B_REVIEW:PR=7:AGENT=RIO -->\nold review"}]
return {"id": 1}
monkeypatch.setattr("lib.evaluate.forgejo_api", fake_api)
posted = await _post_phase1b_review_comment(7, "Rio", "new review\n<!-- VERDICT:RIO:APPROVE -->")
assert posted is False
assert [call[0] for call in calls] == ["GET"]