Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
aac99bb
fix(security): update pip audit lock past pysec-2026-3721
seonghobae Aug 21, 2026
84ee91b
feat: route autofix through contextual orchestrator
seonghobae Aug 21, 2026
d195ef4
chore(security): restore pip-audit lock owner scope
seonghobae Aug 21, 2026
b9cffcf
chore: schedule contextual-orchestrator hourly review repair (#1178)
seonghobae Aug 21, 2026
0e9d24c
Revert "chore: schedule contextual-orchestrator hourly review repair …
seonghobae Aug 21, 2026
08c92c4
Reapply "chore: schedule contextual-orchestrator hourly review repair…
seonghobae Aug 21, 2026
94a2804
fix(security): use osv scanner output-file flag
seonghobae Aug 21, 2026
859d3b7
fix(test): track osv scanner output-file option
seonghobae Aug 21, 2026
90ea841
fix(security): keep reporter output contract
seonghobae Aug 21, 2026
592deef
docs: complete fixture initializer docstring coverage
seonghobae Aug 21, 2026
4ed00ac
fix(codeql): bind merge analysis to merge commit SHA (#1206)
seonghobae Aug 21, 2026
94e2b28
fix(codeql): keep merge upload ref in documented form
seonghobae Aug 21, 2026
4d3d24a
fix(actions): deduplicate workflow-run scheduler scans (#1203)
seonghobae Aug 21, 2026
3016543
fix(review): fail closed when required check is not a verdict (#1002)
seonghobae Aug 21, 2026
4c659d2
Merge protected main into pip-audit scheduler root
seonghobae Aug 21, 2026
3b91220
fix(review): reject external heads in privileged paths
seonghobae Aug 21, 2026
dbb3c8a
fix(codeql): verify merge preview identity
seonghobae Aug 21, 2026
33cdcad
fix(codeql): authenticate merge preview fetch
seonghobae Aug 21, 2026
801c2f1
fix(codeql): bind analysis to exact current merge tree
seonghobae Aug 21, 2026
61ecd32
Merge protected main into exact merge-preview repair
seonghobae Aug 21, 2026
e5a7ac8
test: refresh review dispatch blob pin after restack
seonghobae Aug 21, 2026
1ff33c9
fix(codeql): fetch merge ancestry before preview
seonghobae Aug 23, 2026
997e4f1
Merge protected main into CodeQL review repair
seonghobae Aug 23, 2026
bee244e
merge(main): refresh CodeQL review owner
seonghobae Aug 24, 2026
069bfa6
merge(main): refresh CodeQL review owner
seonghobae Aug 24, 2026
dafcf87
fix(review): validate exact VCS dependency licenses
seonghobae Aug 24, 2026
88579ae
test(review): keep VCS license fixtures whitespace-clean
seonghobae Aug 24, 2026
863c343
test(review): prove VCS license gate coverage
seonghobae Aug 24, 2026
98af23e
fix(review): reject VCS license metadata redirects
seonghobae Aug 24, 2026
09c2489
Merge protected main into review verdict owner
seonghobae Aug 25, 2026
0b5dbd1
fix(review): reject dot-only VCS repository names
seonghobae Aug 25, 2026
59fdd8c
Merge protected main and clarify the MPL-2.0 policy exception
seonghobae Aug 25, 2026
234bbd6
Merge protected main into review-verdict owner
seonghobae Aug 25, 2026
b69d9c8
fix(ci): normalize merged scheduler files at EOF
seonghobae Aug 25, 2026
748e881
merge: converge review-verdict owner with protected main
seonghobae Aug 26, 2026
27a8bd5
fix(codeql): resolve fork heads through exact PR ref
seonghobae Aug 26, 2026
2d00301
Merge branch 'main' into fix/pip-audit-pip-2621
opencode-agent[bot] Aug 26, 2026
2e044cb
Merge main into fix/pip-audit-pip-2621, reconciling 8 conflicted files
claude Aug 30, 2026
105b1bf
Merge branch 'main' into fix/pip-audit-pip-2621
opencode-agent[bot] Aug 30, 2026
b2388fa
Merge remote-tracking branch 'origin/main' into fix/pip-audit-pip-2621
claude Aug 30, 2026
090b032
fix(scheduler): preserve draft dispatch budget when Strix is busy
seonghobae Aug 30, 2026
187f74b
Merge protected main into fix/pip-audit-pip-2621
seonghobae Aug 30, 2026
c4ec814
fix(scheduler): prioritize before applying queue cap
seonghobae Aug 30, 2026
b580a36
fix(scheduler): bound prioritized queue hydration
seonghobae Aug 30, 2026
b051f5d
fix(scheduler): preserve fallback priority authority
seonghobae Aug 30, 2026
207d94b
fix(review): fail closed before every Noema skip path
seonghobae Aug 31, 2026
e44650a
Merge protected main into Noema fail-closed review repair
seonghobae Aug 31, 2026
8bf311c
fix(review): paginate Noema approval evidence
seonghobae Aug 31, 2026
b8f4adb
Merge protected main into review-verdict repair
seonghobae Aug 31, 2026
6e32ebe
merge: main@f2f91b80 into #1198 (fix/pip-audit-pip-2621)
seonghobae Sep 5, 2026
0f5775e
Merge branch 'main' into fix/pip-audit-pip-2621
opencode-agent[bot] Sep 6, 2026
c7c844e
Merge branch 'main' into fix/pip-audit-pip-2621
opencode-agent[bot] Sep 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 36 additions & 9 deletions scripts/ci/noema_review_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,14 +321,6 @@ def graphql(query: str, **fields: str | int) -> dict[str, Any]:
}
}
}
reviews(last: 100) {
nodes {
state
body
author { login }
commit { oid }
}
}
statusCheckRollup {
contexts(first: 100) {
nodes {
Expand Down Expand Up @@ -356,13 +348,48 @@ def graphql(query: str, **fields: str | int) -> dict[str, Any]:
"""


def fetch_complete_reviews(repo: str, number: int) -> list[dict[str, Any]]:
"""Return every pull-request review normalized to the GraphQL node shape."""
document = json.loads(
run(
[
"gh",
"api",
"--paginate",
"--slurp",
f"repos/{repo}/pulls/{number}/reviews",
]
)
or "[]"
)
if not isinstance(document, list) or any(
not isinstance(page, list) for page in document
):
raise RuntimeError("GitHub returned malformed paginated review evidence")
reviews: list[dict[str, Any]] = []
for page in document:
for review in page:
if not isinstance(review, dict):
raise RuntimeError("GitHub returned malformed review evidence")
reviews.append(
{
"state": review.get("state"),
"body": review.get("body"),
"author": {"login": ((review.get("user") or {}).get("login"))},
"commit": {"oid": review.get("commit_id")},
}
)
return reviews


def fetch_pr(repo: str, number: int) -> dict[str, Any]:
"""Fetch the pull request data required for Noema review gating."""
owner, name = split_repo(repo)
data = graphql(PR_QUERY, owner=owner, name=name, number=number)
pr = data.get("data", {}).get("repository", {}).get("pullRequest")
if not pr:
raise RuntimeError(f"PR #{number} was not found in {repo}")
pr["reviews"] = {"nodes": fetch_complete_reviews(repo, number)}
return pr


Expand Down Expand Up @@ -1779,7 +1806,7 @@ def inspect_and_review(repo: str, number: int, expected_head: str) -> int:
"Noema requires an independent reviewer credential."
)
if pr.get("isDraft"):
print("PR is draft; Noema review skipped.")
print("PR is draft; Noema review skipped after primary OpenCode approval.")
return 0
if existing_noema_review(pr, actor):
print("Current head already has a Noema review; nothing to do.")
Expand Down
88 changes: 87 additions & 1 deletion tests/test_noema_review_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -735,13 +735,18 @@ def test_split_repo_and_graphql(monkeypatch):

def fake_run(args, stdin=None):
calls.append((args, stdin))
if "--paginate" in args:
return "[[]]"
return '{"data":{"repository":{"pullRequest":{"number":7}}}}'

monkeypatch.setattr(noema, "run", fake_run)
assert noema.graphql("query", owner="owner", number=7)["data"]["repository"]["pullRequest"]["number"] == 7
assert "-f" in calls[0][0]
assert "-F" in calls[0][0]
assert noema.fetch_pr("owner/repo", 7) == {"number": 7}
assert noema.fetch_pr("owner/repo", 7) == {
"number": 7,
"reviews": {"nodes": []},
}

monkeypatch.setattr(noema, "graphql", lambda *args, **kwargs: {"data": {"repository": {"pullRequest": None}}})
with pytest.raises(RuntimeError, match="was not found"):
Expand Down Expand Up @@ -2613,6 +2618,7 @@ def test_format_review_evidence_renders_only_structured_entries():
assert any("falsified" in line and "source trace passes" in line for line in lines)



def test_parse_args_and_main(monkeypatch):
parsed = noema.parse_args(
["--repo", "owner/repo", "--pr-number", "9", "--expected-head", "a" * 40]
Expand Down Expand Up @@ -2647,3 +2653,83 @@ def test_parse_args_and_main(monkeypatch):
noema.main(
["--repo", "owner/repo", "--pr-number", "9", "--expected-head", "A" * 40]
)


def test_fetch_pr_keeps_exact_head_approval_older_than_one_hundred_reviews(monkeypatch):
"""The gate must not lose a valid approval behind GitHub's review page size."""
head_sha = "a" * 40
approval = {
"state": "APPROVED",
"body": "Result: APPROVE",
"user": {"login": "opencode-agent"},
"commit_id": head_sha,
}
later_comments = [
{
"state": "COMMENTED",
"body": f"later review event {index}",
"user": {"login": "reviewer"},
"commit_id": head_sha,
}
for index in range(100)
]
monkeypatch.setattr(
noema,
"graphql",
lambda *args, **kwargs: {
"data": {
"repository": {
"pullRequest": make_pr(
headRefOid=head_sha,
reviews={"nodes": later_comments},
)
}
}
},
)
calls = []

def fake_run(args, stdin=None):
calls.append(args)
return json.dumps([[approval, *later_comments]])

monkeypatch.setattr(noema, "run", fake_run)

pr = noema.fetch_pr("owner/repo", 7)

nodes = pr["reviews"]["nodes"]
assert len(nodes) == 101
assert nodes[0] == {
"state": "APPROVED",
"body": "Result: APPROVE",
"author": {"login": "opencode-agent"},
"commit": {"oid": head_sha},
}
assert calls == [
[
"gh",
"api",
"--paginate",
"--slurp",
"repos/owner/repo/pulls/7/reviews",
]
]


@pytest.mark.parametrize(
("payload", "message"),
[
("{}", "malformed paginated review evidence"),
("[[null]]", "malformed review evidence"),
],
)
def test_fetch_complete_reviews_fails_closed_on_malformed_evidence(
monkeypatch,
payload,
message,
):
"""Malformed review pages must not become an empty approval history."""
monkeypatch.setattr(noema, "run", lambda *args, **kwargs: payload)

with pytest.raises(RuntimeError, match=message):
noema.fetch_complete_reviews("owner/repo", 7)
Loading