diff --git a/.github/workflows/noema-review.yml b/.github/workflows/noema-review.yml index 5c60782adb..712d4668a5 100644 --- a/.github/workflows/noema-review.yml +++ b/.github/workflows/noema-review.yml @@ -20,12 +20,10 @@ on: concurrency: group: >- noema-review-${{ - github.event_name == 'pull_request_target' && github.event.pull_request.base.repo.full_name || - github.event_name == 'repository_dispatch' && github.event.client_payload.target_repository || - github.repository }}-${{ github.event_name }}-${{ - github.event_name == 'pull_request_target' && format('pr-{0}', github.event.pull_request.number) || - github.event_name == 'workflow_run' && github.event.workflow_run.pull_requests[0].number && format('pr-{0}', github.event.workflow_run.pull_requests[0].number) || - github.event_name == 'repository_dispatch' && github.event.client_payload.pr_number || + github.event.pull_request.base.repo.full_name || + github.event.client_payload.target_repository || github.repository }}-${{ + github.event.pull_request.number || github.event.workflow_run.pull_requests[0].number || + github.event.client_payload.pr_number || github.run_id }} cancel-in-progress: true diff --git a/docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md b/docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md index 21b118ce90..a6bed47273 100644 --- a/docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md +++ b/docs/adr/0003-contextual-orchestrator-vendored-free-zdr.md @@ -24,7 +24,7 @@ all five, and auto-optimize routing by cost. 1. **Vendoring, pinned**: `scripts/ci/contextual_orchestrator_review_sidecar.sh` clones `ContextualWisdomLab/contextual-orchestrator` at an exact SHA - (`c107e3e52371993aa9c326fcc245e01c41fc3850` today) into `RUNNER_TEMP`. The + (`8cd99f139915131ba0239bce12a5d6a5fd85394e` today) into `RUNNER_TEMP`. The source's `requirements.lock` is installed with `--require-hashes` and `--no-deps`, so dependency resolution cannot silently move the reviewed runtime. @@ -190,3 +190,10 @@ all five, and auto-optimize routing by cost. amendment" (above) are closed, without requiring a manual re-audit. `docs/doctoring/contextual-orchestrator-strix-free-diversity-evidence.md` records that PR's own reasoning trail. +- **2026-08-31 amendment: Noema reviews independently of OpenCode.** Noema no + longer waits for an OpenCode approval, review-thread state, or other check + conclusions before calling the gateway and submitting its current-head + review. A colliding OpenCode reviewer credential fails closed. The Noema LLM + response must include a non-empty summary and an object-list `findings` + field; `request_changes` additionally requires a substantive finding, so a + bare decision cannot synthesize an evidence-free green review. diff --git a/scripts/ci/contextual_orchestrator_review_sidecar.sh b/scripts/ci/contextual_orchestrator_review_sidecar.sh index 2b83c321ab..e4984f643b 100755 --- a/scripts/ci/contextual_orchestrator_review_sidecar.sh +++ b/scripts/ci/contextual_orchestrator_review_sidecar.sh @@ -14,7 +14,7 @@ # (fail-closed zero-cost) pool. set -euo pipefail -ORCHESTRATOR_PIN_SHA="${ORCHESTRATOR_PIN_SHA:-c107e3e52371993aa9c326fcc245e01c41fc3850}" +ORCHESTRATOR_PIN_SHA="${ORCHESTRATOR_PIN_SHA:-8cd99f139915131ba0239bce12a5d6a5fd85394e}" ORCHESTRATOR_GIT_URL="${ORCHESTRATOR_GIT_URL:-https://github.com/ContextualWisdomLab/contextual-orchestrator.git}" # The Strix gate and Noema SSRF guard accept this one process-local origin. # Keep it fixed so an environment override cannot create an unvalidated sidecar. diff --git a/scripts/ci/noema_review_gate.py b/scripts/ci/noema_review_gate.py index c8c55b65e9..1f7fa40335 100644 --- a/scripts/ci/noema_review_gate.py +++ b/scripts/ci/noema_review_gate.py @@ -23,19 +23,6 @@ "opencode-agent[bot]", "opencode-agent", } -PRIMARY_REVIEW_MARKERS = ( - "OpenCode reviewed the current-head bounded evidence and found no blocking issues.", - "Result: APPROVE", - "opencode-review-control-v1", -) -REVIEW_BODY_HEAD_SHA_RE = re.compile(r"Head SHA:\s*`([0-9a-fA-F]{40})`") -IGNORED_RUNNING_CHECKS = { - "approve-after-primary-review", - "noema-review", - "Required Noema Review", -} -FAILED_CONCLUSIONS = {"FAILURE", "ERROR", "CANCELLED", "TIMED_OUT", "ACTION_REQUIRED", "STARTUP_FAILURE"} -RUNNING_STATES = {"QUEUED", "IN_PROGRESS", "PENDING", "REQUESTED", "WAITING", "EXPECTED"} MAX_DIFF_CHARS = 60000 MAX_CONTEXT_FILES = 12 MAX_FILE_CONTEXT_CHARS = 4000 @@ -183,83 +170,6 @@ def review_commit(review: dict[str, Any]) -> str: return ((review.get("commit") or {}).get("oid") or "").strip() -def review_body_head_sha(review: dict[str, Any]) -> str | None: - """Return the last explicit current-head SHA recorded in a review body.""" - matches = REVIEW_BODY_HEAD_SHA_RE.findall(str(review.get("body") or "")) - return matches[-1] if matches else None - - -def review_matches_current_head(review: dict[str, Any], head_sha: str) -> bool: - """Return whether commit and explicit review-body evidence match the live head.""" - if not head_sha or review_commit(review) != head_sha: - return False - body_head = review_body_head_sha(review) - return body_head is None or body_head.lower() == head_sha.lower() - - -def current_primary_approval(pr: dict[str, Any]) -> dict[str, Any] | None: - """Return the current-head OpenCode approval when it matches the contract.""" - head_sha = str(pr.get("headRefOid") or "") - reviews = (((pr.get("reviews") or {}).get("nodes")) or []) - for review in reversed(reviews): - if not review_matches_current_head(review, head_sha): - continue - if str(review.get("state") or "").upper() != "APPROVED": - continue - body = str(review.get("body") or "") - author = review_author(review) - if author in PRIMARY_REVIEW_AUTHORS and any(marker in body for marker in PRIMARY_REVIEW_MARKERS): - return review - return None - - -def has_current_changes_requested(pr: dict[str, Any]) -> bool: - """Return whether the current head has any changes-requested review.""" - head_sha = str(pr.get("headRefOid") or "") - reviews = (((pr.get("reviews") or {}).get("nodes")) or []) - for review in reversed(reviews): - if review_matches_current_head(review, head_sha) and str(review.get("state") or "").upper() == "CHANGES_REQUESTED": - return True - return False - - -def has_unresolved_threads(pr: dict[str, Any]) -> bool: - """Return whether any non-outdated review thread is unresolved.""" - threads = (((pr.get("reviewThreads") or {}).get("nodes")) or []) - return any(not thread.get("isResolved") and not thread.get("isOutdated") for thread in threads) - - -def check_label(node: dict[str, Any]) -> str: - """Return a human-readable label for a status context or check run.""" - if node.get("__typename") == "StatusContext": - return str(node.get("context") or "") - workflow = ((((node.get("checkSuite") or {}).get("workflowRun") or {}).get("workflow") or {}).get("name") or "") - name = str(node.get("name") or "") - return f"{workflow} / {name}" if workflow else name - - -def blocking_checks(pr: dict[str, Any]) -> list[str]: - """Return check contexts that should block Noema review.""" - contexts = ((((pr.get("statusCheckRollup") or {}).get("contexts") or {}).get("nodes")) or []) - blockers: list[str] = [] - for node in contexts: - label = check_label(node) - if label in IGNORED_RUNNING_CHECKS or str(node.get("name") or "") in IGNORED_RUNNING_CHECKS: - continue - if node.get("__typename") == "StatusContext": - state = str(node.get("state") or "").upper() - if state not in {"SUCCESS", "NEUTRAL"}: - blockers.append(f"{label}: {state}") - continue - status = str(node.get("status") or "").upper() - conclusion = str(node.get("conclusion") or "").upper() - if conclusion in FAILED_CONCLUSIONS: - blockers.append(f"{label}: {conclusion}") - elif status in RUNNING_STATES and conclusion not in {"SUCCESS", "NEUTRAL", "SKIPPED"}: - blockers.append(f"{label}: {status}") - return blockers - - def existing_noema_review(pr: dict[str, Any], actor: str) -> bool: """Return whether Noema already reviewed the current head.""" head_sha = str(pr.get("headRefOid") or "") @@ -588,6 +498,25 @@ def call_llm( decision = str(verdict.get("decision") or "").strip().lower() if decision not in {"approve", "request_changes", "comment"}: raise RuntimeError(f"Noema LLM returned unsupported decision: {decision!r}") + summary = verdict.get("summary") + if not isinstance(summary, str) or not summary.strip(): + raise RuntimeError("Noema LLM response did not contain a substantive summary") + findings = verdict.get("findings") + if not isinstance(findings, list) or any(not isinstance(finding, dict) for finding in findings): + raise RuntimeError("Noema LLM response findings must be a list of objects") + for finding in findings: + if ( + finding.get("severity") not in {"high", "medium", "low"} + or not isinstance(finding.get("file"), str) + or not finding["file"].strip() + or type(finding.get("line")) is not int + or finding["line"] <= 0 + or not isinstance(finding.get("message"), str) + or not finding["message"].strip() + ): + raise RuntimeError("Noema LLM response contained a malformed finding") + if decision == "request_changes" and not findings: + raise RuntimeError("Noema LLM request_changes response did not contain a substantive finding") return verdict @@ -647,36 +576,22 @@ def submit_review(repo: str, number: int, pr: dict[str, Any], actor: str, verdic def inspect_and_review(repo: str, number: int) -> int: - """Inspect PR state and submit Noema's LLM review when gates are clean.""" + """Inspect PR state and submit Noema's independent LLM review.""" pr = fetch_pr(repo, number) actor = current_actor() + if not actor: + raise RuntimeError("Noema reviewer identity could not be verified") if actor in PRIMARY_REVIEW_AUTHORS: - print( + raise RuntimeError( f"Current token actor {actor!r} is already a primary review actor; " - "Noema review skipped so GitHub receives an independent reviewer." + "Noema requires an independent reviewer credential." ) - return 0 if pr.get("isDraft"): print("PR is draft; Noema review skipped.") return 0 if existing_noema_review(pr, actor): print("Current head already has a Noema review; nothing to do.") return 0 - if not current_primary_approval(pr): - print("Current head does not have a primary OpenCode approval; Noema review skipped.") - return 0 - if has_current_changes_requested(pr): - print("Current head has requested changes; Noema review skipped.") - return 0 - if has_unresolved_threads(pr): - print("PR has unresolved review threads; Noema review skipped.") - return 0 - blockers = blocking_checks(pr) - if blockers: - print("Blocking checks remain; Noema review skipped:") - for blocker in blockers: - print(f"- {blocker}") - return 0 diff, truncated = fetch_diff(repo, number) review_context = build_review_context(repo, number, pr) verdict = call_llm(repo, number, pr, diff, truncated, review_context) diff --git a/tests/test_contextual_orchestrator_review_sidecar_contract.py b/tests/test_contextual_orchestrator_review_sidecar_contract.py index cbcf690d0b..0a63356dad 100644 --- a/tests/test_contextual_orchestrator_review_sidecar_contract.py +++ b/tests/test_contextual_orchestrator_review_sidecar_contract.py @@ -40,7 +40,7 @@ ) GATEWAY_MODEL = "contextual-orchestrator/orchestrator/free" -ORCH_PIN_SHA = "c107e3e52371993aa9c326fcc245e01c41fc3850" +ORCH_PIN_SHA = "8cd99f139915131ba0239bce12a5d6a5fd85394e" def _read(path: Path) -> str: diff --git a/tests/test_noema_review_gate.py b/tests/test_noema_review_gate.py index 408bb95b98..8855dffd39 100644 --- a/tests/test_noema_review_gate.py +++ b/tests/test_noema_review_gate.py @@ -91,106 +91,7 @@ def fake_run(args, stdin=None): noema.fetch_pr("owner/repo", 8) -def test_review_state_helpers_cover_current_head_logic(): - marker_body = "OpenCode reviewed the current-head bounded evidence and found no blocking issues." - current = review(body=marker_body) - old = review(commit="old", body=marker_body) - pr = make_pr(reviews={"nodes": [old, current]}) - - assert noema.review_author(current) == "opencode-agent" - assert noema.review_author({}) == "" - assert noema.review_commit(current) == "head" - assert noema.review_commit({}) == "" - assert noema.current_primary_approval(pr) == current - assert noema.current_primary_approval(make_pr(reviews={"nodes": [old]})) is None - assert noema.current_primary_approval(make_pr(reviews={"nodes": [review("COMMENTED", body=marker_body)]})) is None - assert noema.current_primary_approval(make_pr(reviews={"nodes": [review(login="human", body=marker_body)]})) is None - assert noema.current_primary_approval( - make_pr( - reviews={ - "nodes": [review(login="github-actions[bot]", body=marker_body)] - } - ) - ) is None - assert noema.has_current_changes_requested(make_pr(reviews={"nodes": [review("CHANGES_REQUESTED")]})) - assert not noema.has_current_changes_requested(make_pr(reviews={"nodes": [review("CHANGES_REQUESTED", commit="old")]})) - assert noema.has_unresolved_threads(make_pr(reviewThreads={"nodes": [{"isResolved": False, "isOutdated": False}]})) - assert not noema.has_unresolved_threads(make_pr(reviewThreads={"nodes": [{"isResolved": False, "isOutdated": True}]})) - - -def test_review_state_helpers_reject_explicit_previous_head_evidence(): - current_head = "a" * 40 - previous_head = "b" * 40 - approval_marker = "Result: APPROVE" - stale_approval = review( - commit=current_head, - body=f"{approval_marker}\n\n- Head SHA: `{previous_head}`", - ) - exact_approval = review( - commit=current_head, - body=f"{approval_marker}\n\n- Head SHA: `{current_head}`", - ) - stale_change_request = review( - "CHANGES_REQUESTED", - commit=current_head, - body=f"Result: REQUEST_CHANGES\n\n- Head SHA: `{previous_head}`", - ) - - assert noema.current_primary_approval( - make_pr(headRefOid=current_head, reviews={"nodes": [stale_approval]}) - ) is None - assert noema.current_primary_approval( - make_pr(headRefOid=current_head, reviews={"nodes": [exact_approval]}) - ) == exact_approval - assert not noema.has_current_changes_requested( - make_pr(headRefOid=current_head, reviews={"nodes": [stale_change_request]}) - ) - - -def test_check_helpers_and_existing_noema_review(): - status_context = {"__typename": "StatusContext", "context": "ci", "state": "FAILURE"} - check_run = { - "__typename": "CheckRun", - "name": "build", - "status": "COMPLETED", - "conclusion": "SUCCESS", - "checkSuite": {"workflowRun": {"workflow": {"name": "CI"}}}, - } - failed_run = { - "__typename": "CheckRun", - "name": "lint", - "status": "COMPLETED", - "conclusion": "FAILURE", - "checkSuite": {"workflowRun": {"workflow": {"name": "CI"}}}, - } - running_run = { - "__typename": "CheckRun", - "name": "slow", - "status": "IN_PROGRESS", - "conclusion": None, - "checkSuite": {"workflowRun": {"workflow": {"name": "CI"}}}, - } - - assert noema.check_label(status_context) == "ci" - assert noema.check_label(check_run) == "CI / build" - blockers = noema.blocking_checks( - make_pr( - statusCheckRollup={ - "contexts": { - "nodes": [ - status_context, - check_run, - failed_run, - running_run, - {"__typename": "CheckRun", "name": "Required Noema Review", "status": "IN_PROGRESS"}, - ] - } - } - ) - ) - assert "ci: FAILURE" in blockers - assert "CI / lint: FAILURE" in blockers - assert "CI / slow: IN_PROGRESS" in blockers +def test_existing_noema_review_matches_actor_and_head(): noema_marker = "" assert noema.existing_noema_review( make_pr(reviews={"nodes": [review(login="noema", body=noema_marker)]}), @@ -513,8 +414,7 @@ def test_format_findings_and_submit_review(monkeypatch): def test_inspect_and_review_skip_paths(monkeypatch): - marker_body = "OpenCode reviewed the current-head bounded evidence and found no blocking issues." - clean_pr = make_pr(reviews={"nodes": [review(body=marker_body)]}) + clean_pr = make_pr() calls = [] monkeypatch.setattr(noema, "fetch_pr", lambda repo, number: clean_pr) monkeypatch.setattr(noema, "current_actor", lambda: "noema") @@ -527,13 +427,8 @@ def test_inspect_and_review_skip_paths(monkeypatch): assert calls cases = [ - (make_pr(), "noema"), (make_pr(isDraft=True), "noema"), (make_pr(reviews={"nodes": [review(login="noema", body="")]}), "noema"), - (make_pr(reviews={"nodes": [review("CHANGES_REQUESTED"), review(body=marker_body)]}), "noema"), - (make_pr(reviews={"nodes": [review(body=marker_body)]}, reviewThreads={"nodes": [{"isResolved": False, "isOutdated": False}]}), "noema"), - (make_pr(reviews={"nodes": [review(body=marker_body)]}, statusCheckRollup={"contexts": {"nodes": [{"__typename": "StatusContext", "context": "ci", "state": "FAILURE"}]}}), "noema"), - (clean_pr, "opencode-agent"), ] for pr, actor in cases: calls.clear() @@ -542,6 +437,139 @@ def test_inspect_and_review_skip_paths(monkeypatch): assert noema.inspect_and_review("owner/repo", 7) == 0 assert calls == [] + monkeypatch.setattr(noema, "fetch_pr", lambda repo, number: clean_pr) + monkeypatch.setattr(noema, "current_actor", lambda: "") + with pytest.raises(RuntimeError, match="identity could not be verified"): + noema.inspect_and_review("owner/repo", 7) + + monkeypatch.setattr(noema, "current_actor", lambda: "opencode-agent") + with pytest.raises(RuntimeError, match="independent reviewer credential"): + noema.inspect_and_review("owner/repo", 7) + + +def test_inspect_and_review_does_not_wait_for_other_reviews_or_checks(monkeypatch): + pr = make_pr( + reviews={"nodes": [review("CHANGES_REQUESTED")]}, + reviewThreads={"nodes": [{"isResolved": False, "isOutdated": False}]}, + statusCheckRollup={"contexts": {"nodes": [{"__typename": "StatusContext", "context": "ci", "state": "FAILURE"}]}}, + ) + calls = [] + monkeypatch.setattr(noema, "fetch_pr", lambda repo, number: pr) + monkeypatch.setattr(noema, "current_actor", lambda: "noema") + monkeypatch.setattr(noema, "fetch_diff", lambda repo, number: ("diff", False)) + monkeypatch.setattr(noema, "build_review_context", lambda repo, number, value: "context") + monkeypatch.setattr(noema, "call_llm", lambda *args, **kwargs: {"decision": "approve", "summary": "ok"}) + monkeypatch.setattr(noema, "submit_review", lambda *args, **kwargs: calls.append(args)) + + assert noema.inspect_and_review("owner/repo", 7) == 0 + assert calls + + +def test_call_llm_rejects_empty_review_content(monkeypatch): + monkeypatch.setenv("NOEMA_LLM_API_URL", "https://llm.example/v1/chat/completions") + monkeypatch.setenv("NOEMA_LLM_API_KEY", "test-key") + + class Response: + def __enter__(self): + return self + + def __exit__(self, *args): + return None + + def read(self): + return json.dumps({"choices": [{"message": {"content": '{"decision":"approve"}'}}]}).encode() + + monkeypatch.setattr(noema.urllib.request.OpenerDirector, "open", lambda *args, **kwargs: Response()) + with pytest.raises(RuntimeError, match="substantive summary"): + noema.call_llm("owner/repo", 7, make_pr(), "diff", False) + + +@pytest.mark.parametrize("message", [[], {}, 0, " "]) +def test_call_llm_rejects_malformed_blocking_findings(monkeypatch, message): + monkeypatch.setenv("NOEMA_LLM_API_URL", "https://llm.example/v1/chat/completions") + monkeypatch.setenv("NOEMA_LLM_API_KEY", "test-key") + verdict = { + "decision": "request_changes", + "summary": "blocking issue", + "findings": [{"severity": "high", "file": "a.py", "line": 1, "message": message}], + } + + class Response: + def __enter__(self): + return self + + def __exit__(self, *args): + return None + + def read(self): + return json.dumps({"choices": [{"message": {"content": json.dumps(verdict)}}]}).encode() + + monkeypatch.setattr(noema.urllib.request.OpenerDirector, "open", lambda *args, **kwargs: Response()) + with pytest.raises(RuntimeError, match="malformed finding"): + noema.call_llm("owner/repo", 7, make_pr(), "diff", False) + + +@pytest.mark.parametrize( + ("findings", "error"), + [ + (None, "list of objects"), + ([0], "list of objects"), + ([{"severity": "info", "file": "a.py", "line": 1, "message": "bad"}], "malformed finding"), + ([{"severity": "high", "file": 1, "line": 1, "message": "bad"}], "malformed finding"), + ([{"severity": "high", "file": " ", "line": 1, "message": "bad"}], "malformed finding"), + ([{"severity": "high", "file": "a.py", "line": "1", "message": "bad"}], "malformed finding"), + ([{"severity": "high", "file": "a.py", "line": 0, "message": "bad"}], "malformed finding"), + ([], "substantive finding"), + ], +) +def test_call_llm_rejects_invalid_findings_contract(monkeypatch, findings, error): + monkeypatch.setenv("NOEMA_LLM_API_URL", "https://llm.example/v1/chat/completions") + monkeypatch.setenv("NOEMA_LLM_API_KEY", "test-key") + verdict = {"decision": "request_changes", "summary": "blocking issue", "findings": findings} + + class Response: + def __enter__(self): + return self + + def __exit__(self, *args): + return None + + def read(self): + return json.dumps({"choices": [{"message": {"content": json.dumps(verdict)}}]}).encode() + + monkeypatch.setattr(noema.urllib.request.OpenerDirector, "open", lambda *args, **kwargs: Response()) + with pytest.raises(RuntimeError, match=error): + noema.call_llm("owner/repo", 7, make_pr(), "diff", False) + + +@pytest.mark.parametrize( + "findings", + [ + [], + [ + {"severity": "low", "file": "a.py", "line": 1, "message": "note"}, + {"severity": "medium", "file": "b.py", "line": 2, "message": "check"}, + ], + ], +) +def test_call_llm_accepts_substantive_approve(monkeypatch, findings): + monkeypatch.setenv("NOEMA_LLM_API_URL", "https://llm.example/v1/chat/completions") + monkeypatch.setenv("NOEMA_LLM_API_KEY", "test-key") + verdict = {"decision": "approve", "summary": "No blocking issues found.", "findings": findings} + + class Response: + def __enter__(self): + return self + + def __exit__(self, *args): + return None + + def read(self): + return json.dumps({"choices": [{"message": {"content": json.dumps(verdict)}}]}).encode() + + monkeypatch.setattr(noema.urllib.request.OpenerDirector, "open", lambda *args, **kwargs: Response()) + assert noema.call_llm("owner/repo", 7, make_pr(), "diff", False) == verdict + def test_parse_args_and_main(monkeypatch): parsed = noema.parse_args(["--repo", "owner/repo", "--pr-number", "9"]) diff --git a/tests/test_repository_branch_coverage_javascript_and_noema.py b/tests/test_repository_branch_coverage_javascript_and_noema.py index d9a15a4244..6eb5ab9baf 100644 --- a/tests/test_repository_branch_coverage_javascript_and_noema.py +++ b/tests/test_repository_branch_coverage_javascript_and_noema.py @@ -139,25 +139,6 @@ def git_bytes(_root: Path, command: str, spec: str, *_args: str) -> bytes: assert projects[0][2].keys() == {"package.json", "package-lock.json"} -def test_noema_status_context_failure_is_blocking() -> None: - """A non-success legacy status context remains a concrete review blocker.""" - - pr = { - "statusCheckRollup": { - "contexts": { - "nodes": [ - { - "__typename": "StatusContext", - "context": "legacy-security", - "state": "failure", - } - ] - } - } - } - assert noema.blocking_checks(pr) == ["legacy-security: FAILURE"] - - def test_noema_fetch_diff_truncates_to_prompt_budget( monkeypatch: pytest.MonkeyPatch, ) -> None: diff --git a/tests/test_repository_branch_coverage_reporting_edges.py b/tests/test_repository_branch_coverage_reporting_edges.py index f9d985b04e..b4527147c8 100644 --- a/tests/test_repository_branch_coverage_reporting_edges.py +++ b/tests/test_repository_branch_coverage_reporting_edges.py @@ -102,26 +102,10 @@ def test_javascript_main_ignores_unmatched_coverage_records( assert "missing instrumentation" in capsys.readouterr().out -def test_noema_nonblocking_status_small_diff_and_empty_context_branches( +def test_noema_small_diff_and_empty_context_branches( monkeypatch: pytest.MonkeyPatch, ) -> None: - """Success statuses, small diffs, invalid thread lines, and empty sections stay clean.""" - - assert noema.blocking_checks( - { - "statusCheckRollup": { - "contexts": { - "nodes": [ - { - "__typename": "StatusContext", - "context": "legacy-security", - "state": "SUCCESS", - } - ] - } - } - } - ) == [] + """Small diffs, invalid thread lines, and empty sections stay clean.""" monkeypatch.setattr(noema, "run", lambda _args: "small diff") assert noema.fetch_diff("owner/repo", 1) == ("small diff", False) diff --git a/tests/test_required_workflow_queue_contract.py b/tests/test_required_workflow_queue_contract.py index 77594cc1fb..109917cd9d 100644 --- a/tests/test_required_workflow_queue_contract.py +++ b/tests/test_required_workflow_queue_contract.py @@ -252,6 +252,9 @@ def test_required_pull_request_workflows_cancel_superseded_runs() -> None: ) elif filename == "opencode-review.yml": assert "opencode-review-bootstrap-" in concurrency_contract + elif filename == "noema-review.yml": + assert "github.event.workflow_run.pull_requests[0].number" in concurrency_contract + assert "github.event_name }}" not in concurrency_contract else: if filename in {"codeql-pr.yml", "osv-scanner-pr.yml", "scorecard-pr.yml"}: assert "github.event_name == 'pull_request'" in concurrency_contract @@ -486,14 +489,14 @@ def test_required_workflow_trusted_source_refs_are_not_input_controlled() -> Non assert "GITHUB_CONTEXT_JSON: ${{ toJSON(github) }}" in workflow -def test_noema_workflow_run_followup_cannot_cancel_required_pr_event_review() -> None: - """Keep Noema workflow-run follow-ups isolated from PR-event reviews.""" +def test_noema_triggers_serialize_one_review_per_pull_request() -> None: + """Serialize every Noema trigger type for one pull request.""" workflow = workflow_text("noema-review.yml") concurrency_contract = workflow.split("permissions:", 1)[0] - assert "github.repository }}-${{ github.event_name }}-${{" in concurrency_contract - assert "github.event_name == 'workflow_run'" in concurrency_contract - assert "github.event_name == 'pull_request_target'" in concurrency_contract + assert "github.event.pull_request.number || github.event.workflow_run.pull_requests[0].number" in concurrency_contract + assert "github.event.client_payload.pr_number" in concurrency_contract + assert "github.event_name }}" not in concurrency_contract def test_noema_review_credentials_and_orchestrator_configuration_fail_closed() -> None: