ci: harden trusted PR review evidence (#1896)

This commit is contained in:
Carlos Florêncio
2026-07-23 16:22:50 +01:00
committed by GitHub
parent 1c70495689
commit a225fbac3e
57 changed files with 2193 additions and 425 deletions
+61 -6
View File
@@ -15,6 +15,13 @@ OMISSION_NOTICE_RESERVE = 256
NO_FINDINGS_MARKER = "<!-- opencode-review:no-findings -->"
DIAGNOSTIC_MARKER = "<!-- opencode-review:diagnostic -->"
FALLBACK_MARKER = "<!-- opencode-review:fallback-findings -->"
TRUSTED_CLEAN_MARKER = "<!-- kandev-review: clean -->"
def workflow_run_marker() -> str:
run_id = required_env("GITHUB_RUN_ID")
attempt = required_env("GITHUB_RUN_ATTEMPT")
return f"<!-- kandev-review: workflow-run id={run_id} attempt={attempt} -->"
def required_env(name: str) -> str:
@@ -108,7 +115,7 @@ def read_status(path: Path) -> int:
def parse_findings(raw: str) -> tuple[list[Any] | None, str | None]:
match = re.search(r"<opencode_findings>\s*(.*?)\s*</opencode_findings>", raw, re.S)
match = re.fullmatch(r"\s*<opencode_findings>\s*(.*?)\s*</opencode_findings>\s*", raw, re.S)
if not match:
return None, "No parseable findings block was found."
payload_text = match.group(1).strip()
@@ -158,6 +165,49 @@ def no_findings_body() -> str:
)
def terminal_review_body(*, clean: bool, finding_count: int) -> str:
short_sha = required_env("HEAD_SHA")[:12]
model = required_env("OPENCODE_MODEL")
if clean:
return (
f"{TRUSTED_CLEAN_MARKER}\n{workflow_run_marker()}\n"
"**OpenCode review complete**\n\n"
"Trusted wrapper parsed an empty findings array.\n\n"
f"OpenCode model: `{model}`\n"
f"Commit: `{short_sha}`"
)
return (
f"{workflow_run_marker()}\n**OpenCode review requires action**\n\n"
f"Trusted wrapper parsed `{finding_count}` finding(s); action required before this review can be clean.\n\n"
f"OpenCode model: `{model}`\n"
f"Commit: `{short_sha}`"
)
def post_terminal_review(*, clean: bool, finding_count: int) -> bool:
repository = required_env("GITHUB_REPOSITORY")
pr_number = required_env("PR_NUMBER")
head_sha = required_env("HEAD_SHA")
result = gh_api(
[
"-X",
"POST",
f"repos/{repository}/pulls/{pr_number}/reviews",
"-f",
f"commit_id={head_sha}",
"-f",
"event=COMMENT",
"-f",
f"body={terminal_review_body(clean=clean, finding_count=finding_count)}",
]
)
if result.returncode == 0:
print("Posted OpenCode terminal pull-request review.")
return True
print(f"Could not post OpenCode terminal review: {result.stderr.strip()}", file=sys.stderr)
return False
def fallback_body(rejected: list[dict[str, Any]], overflow: list[dict[str, Any]]) -> tuple[str, int, int]:
model = required_env("OPENCODE_MODEL")
short_sha = required_env("HEAD_SHA")[:12]
@@ -326,9 +376,12 @@ def handle_diagnostic(
*snippet("OpenCode findings output", raw),
],
)
posted_terminal_review = post_terminal_review(clean=False, finding_count=0)
upsert_issue_comment(DIAGNOSTIC_MARKER, diagnostic_body(reason, status, stdout, stderr))
print(f"OpenCode did not produce parseable findings: {reason}", file=sys.stderr)
return 1 if fail_check else 0
if not posted_terminal_review:
print("OpenCode terminal diagnostic review could not be posted.", file=sys.stderr)
return 1
def post_findings(args: argparse.Namespace) -> int:
@@ -357,11 +410,11 @@ def post_findings(args: argparse.Namespace) -> int:
stdout,
stderr,
raw,
fail_check=False,
fail_check=True,
)
if not findings:
posted_no_findings = upsert_issue_comment(NO_FINDINGS_MARKER, no_findings_body())
posted_terminal_review = post_terminal_review(clean=True, finding_count=0)
append_summary(
args.summary,
"OpenCode review",
@@ -372,7 +425,7 @@ def post_findings(args: argparse.Namespace) -> int:
*snippet("OpenCode stderr", stderr),
],
)
return 0 if posted_no_findings else 1
return 0 if posted_terminal_review else 1
changed_paths = set(args.files.read_text(encoding="utf-8").splitlines())
commentable_lines = None
@@ -427,6 +480,8 @@ def post_findings(args: argparse.Namespace) -> int:
fallback_comment, fallback_included, fallback_omitted = fallback_body(unplaced_findings, overflow_findings)
posted_fallback = upsert_issue_comment(FALLBACK_MARKER, fallback_comment)
posted_terminal_review = post_terminal_review(clean=False, finding_count=len(findings))
summary_lines = [
f"Parsed findings: `{len(findings)}`",
f"Inline comments posted: `{posted}`",
@@ -441,7 +496,7 @@ def post_findings(args: argparse.Namespace) -> int:
summary_lines.extend(["", *snippet("OpenCode stdout", stdout), *snippet("OpenCode stderr", stderr)])
append_summary(args.summary, "OpenCode review", summary_lines)
print(f"Posted {posted} OpenCode inline comments; preserved {fallback_included} fallback findings.")
return 0 if posted_fallback else 1
return 0 if posted_fallback and posted_terminal_review else 1
def build_parser() -> argparse.ArgumentParser:
+93 -11
View File
@@ -59,9 +59,12 @@ class OpenCodeReviewScriptTest(unittest.TestCase):
raise SystemExit(0)
joined = " ".join(args)
if "/pulls/" in joined and os.environ.get("INLINE_FAIL") == "1":
if "/pulls/" in joined and "/comments" in joined and os.environ.get("INLINE_FAIL") == "1":
print("line is not part of the diff", file=sys.stderr)
raise SystemExit(1)
if "/reviews" in joined and os.environ.get("TERMINAL_REVIEW_FAIL") == "1":
print("terminal review unavailable", file=sys.stderr)
raise SystemExit(1)
if "/issues/" in joined and os.environ.get("ISSUE_COMMENT_FAIL") == "1":
print("issue comments unavailable", file=sys.stderr)
raise SystemExit(1)
@@ -80,9 +83,11 @@ class OpenCodeReviewScriptTest(unittest.TestCase):
output: str,
inline_fail: bool = False,
issue_comment_fail: bool = False,
terminal_review_fail: bool = False,
gh_bin: Path | None = None,
include_patch: bool = False,
) -> subprocess.CompletedProcess[str]:
self.calls_path.unlink(missing_ok=True)
self.output_path.write_text(output, encoding="utf-8")
env = {
**os.environ,
@@ -91,12 +96,16 @@ class OpenCodeReviewScriptTest(unittest.TestCase):
"GITHUB_REPOSITORY": "kdlbs/kandev",
"PR_NUMBER": "42",
"HEAD_SHA": "0123456789abcdef",
"GITHUB_RUN_ID": "27340000005",
"GITHUB_RUN_ATTEMPT": "2",
"OPENCODE_MODEL": "opencode-go/minimax-m3",
}
if inline_fail:
env["INLINE_FAIL"] = "1"
if issue_comment_fail:
env["ISSUE_COMMENT_FAIL"] = "1"
if terminal_review_fail:
env["TERMINAL_REVIEW_FAIL"] = "1"
command = [
sys.executable,
str(SCRIPT),
@@ -137,10 +146,68 @@ class OpenCodeReviewScriptTest(unittest.TestCase):
bodies.append(call[index + 1][len("body=") :])
return bodies
def test_missing_findings_block_posts_diagnostic_without_failing(self) -> None:
def terminal_review_calls(self) -> list[list[str]]:
return [call for call in self.read_calls() if any("/reviews" in arg for arg in call)]
def terminal_review_body(self, call: list[str]) -> str:
return next(arg[len("body=") :] for arg in call if arg.startswith("body="))
def test_empty_findings_post_trusted_exact_head_clean_terminal_review(self) -> None:
result = self.run_script(output="<opencode_findings>[]</opencode_findings>\n")
self.assertEqual(result.returncode, 0, result.stderr)
terminal_calls = self.terminal_review_calls()
self.assertEqual(len(terminal_calls), 1, terminal_calls)
call = terminal_calls[0]
self.assertIn("repos/kdlbs/kandev/pulls/42/reviews", call)
self.assertIn("commit_id=0123456789abcdef", call)
self.assertIn("event=COMMENT", call)
body = self.terminal_review_body(call)
self.assertIn("<!-- kandev-review: clean -->", body)
self.assertIn("<!-- kandev-review: workflow-run id=27340000005 attempt=2 -->", body)
self.assertIn("OpenCode review complete", body)
def test_nonempty_findings_post_blocked_terminal_review_without_clean_marker(self) -> None:
findings = [{"path": "src/app.ts", "line": 99, "title": "Bad line", "body": "This line moved."}]
result = self.run_script(output=f"<opencode_findings>{json.dumps(findings)}</opencode_findings>\n")
self.assertEqual(result.returncode, 0, result.stderr)
terminal_calls = self.terminal_review_calls()
self.assertEqual(len(terminal_calls), 1, terminal_calls)
body = self.terminal_review_body(terminal_calls[0])
self.assertNotIn("<!-- kandev-review: clean -->", body)
self.assertIn("<!-- kandev-review: workflow-run id=27340000005 attempt=2 -->", body)
self.assertIn("action required", body.lower())
def test_diagnostic_paths_never_post_clean_terminal_review(self) -> None:
for output, status in [("not findings", "0\n"), ("<opencode_findings>[]</opencode_findings>", "2\n")]:
with self.subTest(output=output, status=status):
self.status_path.write_text(status, encoding="utf-8")
result = self.run_script(output=output)
self.assertNotEqual(result.returncode, 0)
terminal_calls = self.terminal_review_calls()
self.assertEqual(len(terminal_calls), 1, terminal_calls)
self.assertNotIn("<!-- kandev-review: clean -->", self.terminal_review_body(terminal_calls[0]))
self.assertIn("<!-- kandev-review: workflow-run id=27340000005 attempt=2 -->", self.terminal_review_body(terminal_calls[0]))
def test_terminal_review_failure_fails_the_step(self) -> None:
result = self.run_script(
output="<opencode_findings>[]</opencode_findings>\n", terminal_review_fail=True
)
self.assertNotEqual(result.returncode, 0)
self.assertIn("Could not post OpenCode terminal review", result.stderr)
def test_diagnostic_terminal_review_failure_fails_the_step(self) -> None:
result = self.run_script(output="not findings", terminal_review_fail=True)
self.assertNotEqual(result.returncode, 0)
self.assertIn("Could not post OpenCode terminal review", result.stderr)
def test_missing_findings_block_posts_diagnostic_and_fails(self) -> None:
result = self.run_script(output="OpenCode refused to read external_directory (/tmp/*)\n")
self.assertEqual(result.returncode, 0)
self.assertNotEqual(result.returncode, 0)
self.assertIn("did not produce parseable findings", result.stderr)
self.assertTrue(
any("<!-- opencode-review:diagnostic -->" in body for body in self.bodies()),
@@ -148,10 +215,10 @@ class OpenCodeReviewScriptTest(unittest.TestCase):
)
self.assertIn("No parseable findings block", self.summary_path.read_text())
def test_invalid_findings_json_posts_diagnostic_without_failing(self) -> None:
def test_invalid_findings_json_posts_diagnostic_and_fails(self) -> None:
result = self.run_script(output="<opencode_findings>{}</opencode_findings>\n")
self.assertEqual(result.returncode, 0)
self.assertNotEqual(result.returncode, 0)
self.assertIn("did not produce parseable findings", result.stderr)
self.assertTrue(any("<!-- opencode-review:diagnostic -->" in body for body in self.bodies()))
self.assertIn("not an array", self.summary_path.read_text())
@@ -160,7 +227,7 @@ class OpenCodeReviewScriptTest(unittest.TestCase):
result = self.run_script(output="<opencode_findings>```json\n[]\n```</opencode_findings>\n")
self.assertEqual(result.returncode, 0, result.stderr)
self.assertTrue(any("<!-- opencode-review:no-findings -->" in body for body in self.bodies()))
self.assertTrue(any("<!-- kandev-review: clean -->" in body for body in self.bodies()))
def test_missing_gh_binary_reports_gracefully(self) -> None:
result = self.run_script(
@@ -171,14 +238,29 @@ class OpenCodeReviewScriptTest(unittest.TestCase):
self.assertNotEqual(result.returncode, 0)
self.assertIn("Binary not found", result.stderr)
def test_valid_empty_array_posts_no_findings_comment(self) -> None:
def test_valid_empty_array_does_not_post_legacy_no_findings_issue_comment(self) -> None:
result = self.run_script(output="<opencode_findings>[]</opencode_findings>\n")
self.assertEqual(result.returncode, 0, result.stderr)
bodies = self.bodies()
self.assertTrue(any("<!-- opencode-review:no-findings -->" in body for body in bodies))
self.assertFalse(any("<!-- opencode-review:no-findings -->" in body for body in bodies))
self.assertFalse(any("<!-- opencode-review:diagnostic -->" in body for body in bodies))
def test_parse_findings_rejects_prefix_suffix_duplicate_and_empty_then_nonempty_blocks(self) -> None:
invalid_outputs = [
"prefix <opencode_findings>[]</opencode_findings>",
"<opencode_findings>[]</opencode_findings> suffix",
"<opencode_findings>[]</opencode_findings><opencode_findings>[]</opencode_findings>",
"<opencode_findings>[]</opencode_findings><opencode_findings>[{\"path\":\"src/app.ts\",\"line\":1,\"title\":\"bad\",\"body\":\"bad\"}]</opencode_findings>",
]
for output in invalid_outputs:
with self.subTest(output=output):
result = self.run_script(output=output)
self.assertNotEqual(result.returncode, 0)
terminal_calls = self.terminal_review_calls()
self.assertEqual(len(terminal_calls), 1, terminal_calls)
self.assertNotIn("<!-- kandev-review: clean -->", self.terminal_review_body(terminal_calls[0]))
def test_inline_failures_are_posted_as_one_fallback_comment(self) -> None:
result = self.run_script(
output=textwrap.dedent(
@@ -224,7 +306,7 @@ class OpenCodeReviewScriptTest(unittest.TestCase):
)
self.assertEqual(result.returncode, 0, result.stderr)
pull_comment_calls = [call for call in self.read_calls() if any("/pulls/" in arg for arg in call)]
pull_comment_calls = [call for call in self.read_calls() if any("/pulls/" in arg and "/comments" in arg for arg in call)]
self.assertEqual(len(pull_comment_calls), 2, pull_comment_calls)
self.assertTrue(any("line=9" in arg for call in pull_comment_calls for arg in call))
self.assertTrue(any("line=10" in arg for call in pull_comment_calls for arg in call))
@@ -259,7 +341,7 @@ class OpenCodeReviewScriptTest(unittest.TestCase):
)
self.assertEqual(result.returncode, 0, result.stderr)
pull_comment_calls = [call for call in self.read_calls() if any("/pulls/" in arg for arg in call)]
pull_comment_calls = [call for call in self.read_calls() if any("/pulls/" in arg and "/comments" in arg for arg in call)]
self.assertEqual(len(pull_comment_calls), 1, pull_comment_calls)
self.assertTrue(any("line=10" in arg for arg in pull_comment_calls[0]))
self.assertFalse(any("line=99" in arg for call in self.read_calls() for arg in call))
@@ -296,7 +378,7 @@ class OpenCodeReviewScriptTest(unittest.TestCase):
)
self.assertEqual(result.returncode, 0, result.stderr)
pull_comment_calls = [call for call in self.read_calls() if any("/pulls/" in arg for arg in call)]
pull_comment_calls = [call for call in self.read_calls() if any("/pulls/" in arg and "/comments" in arg for arg in call)]
self.assertEqual(len(pull_comment_calls), 1, pull_comment_calls)
self.assertTrue(any("line=9" in arg for arg in pull_comment_calls[0]))
self.assertFalse(any("line=10" in arg for call in self.read_calls() for arg in call))
+86
View File
@@ -94,6 +94,35 @@ if [[ "$explicit_model_env_count" != "2" ]]; then
fi
pass "OpenCode posting steps explicitly pass OPENCODE_MODEL"
run_id_env_count="$(count_occurrences 'GITHUB_RUN_ID: ${{ github.run_id }}')"
run_attempt_env_count="$(count_occurrences 'GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }}')"
if [[ "$run_id_env_count" -ne 2 || "$run_attempt_env_count" -ne 2 ]]; then
fail "Dedicated terminal posting passes immutable workflow run identity"
fi
pass "Dedicated terminal posting passes immutable workflow run identity"
trusted_environment_count="$(count_occurrences 'environment: opencode-review-trusted')"
if [[ "$trusted_environment_count" -ne 1 ]]; then
fail "Same-repository trusted review uses the protected environment"
fi
if grep -q 'OPENCODE_REVIEW_APP_PRIVATE_KEY' "$WORKFLOW" || ! grep -q 'OPENCODE_REVIEW_ENV_APP_PRIVATE_KEY' "$WORKFLOW"; then
fail "Trusted review uses only the environment-scoped App private key"
fi
pass "Same-repository trusted review isolates App credentials in the protected environment"
for harness_file in \
.agents/agents/pr-poller.md \
.codex/agents/pr-poller.toml \
.cursor/agents/pr-poller.md \
.opencode/agents/pr-poller.md \
.agents/skills/pr-fixup/SKILL.md \
.agents/skills/planner-orchestration/SKILL.md; do
if ! grep -q 'OpenCode App.*trusted_producer=true\|trusted_producer=true.*OpenCode App\|OpenCode producer.*trusted_producer=true' "$ROOT_DIR/$harness_file"; then
fail "Harness requires trusted producer only for the dedicated OpenCode App"
fi
done
pass "Harness preserves generic selected-reviewer qualification without App provenance"
patch_capability_count="$(count_occurrences 'post-findings --help | grep -q -- "--patch"')"
if [[ "$patch_capability_count" != "2" ]]; then
fail "OpenCode posting steps check trusted parser support before passing review.patch"
@@ -126,3 +155,60 @@ if [[ -n "$invalid_artifact_upload_refs" ]]; then
fail "OpenCode review artifact uploads use immutable action refs"
fi
pass "OpenCode review artifact uploads use immutable action refs"
if rg -q '^ pull_request:' "$WORKFLOW"; then
fail "OpenCode review runs only from pull_request_target base workflow"
fi
pass "OpenCode review runs only from pull_request_target base workflow"
app_token_action='actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0'
if [[ "$(count_occurrences "$app_token_action")" != "1" ]]; then
fail "Only same-repository trusted review mints a pinned dedicated App token"
fi
pass "Only same-repository trusted review mints a pinned dedicated App token"
if [[ "$(count_occurrences 'permission-pull-requests: write')" != "1" ]]; then
fail "Dedicated App tokens explicitly request pull-request write permission"
fi
pass "Dedicated App tokens explicitly request pull-request write permission"
if [[ "$(count_occurrences 'GH_TOKEN: ${{ steps.app-token.outputs.token }}')" != "1" ]]; then
fail "Only dedicated App tokens are supplied to terminal posting"
fi
pass "Only dedicated App tokens are supplied to terminal posting"
if [[ "$(count_occurrences 'GH_TOKEN: ${{ github.token }}')" != "1" ]]; then
fail "Only advisory fork posting uses github.token"
fi
pass "Only advisory fork posting uses github.token"
if [[ "$(count_occurrences 'ref: ${{ github.event.pull_request.head.sha }}')" != "2" ]] || [[ "$(count_occurrences 'git rev-parse HEAD')" != "2" ]]; then
fail "Both review paths use and assert immutable PR head SHA checkouts"
fi
pass "Both review paths use and assert immutable PR head SHA checkouts"
if [[ "$(count_occurrences 'RANGE="$BASE_SHA...$HEAD_SHA"')" != "2" ]] || rg -q 'incremental|BEFORE|AFTER' "$WORKFLOW"; then
fail "Both review paths always review the full base-to-head PR range"
fi
pass "Both review paths always review the full base-to-head PR range"
if [[ "$(count_matches regex '^[[:space:]]+pull-requests: read$')" != "1" ]] || [[ "$(count_matches regex '^[[:space:]]+pull-requests: write$')" != "1" ]]; then
fail "Same-repository token is read-only and advisory fork publishing is the only write job"
fi
pass "Same-repository token is read-only and advisory fork publishing is the only write job"
model_steps="$(awk '/- name: Run OpenCode review/{in_run=1} /- name: Create dedicated OpenCode review App token/{in_run=0} in_run' "$WORKFLOW")"
if rg -q '(OPENCODE_REVIEW_APP_PRIVATE_KEY|steps\.app-token\.outputs\.token)' <<<"$model_steps"; then
fail "OpenCode model step must not receive dedicated App credentials"
fi
pass "OpenCode model step does not receive dedicated App credentials"
if ! rg -A 10 '^ strip-safe-to-review:' "$WORKFLOW" | rg -q 'issues: write'; then
fail "Fork safe-to-review cleanup has issues write permission"
fi
pass "Fork safe-to-review cleanup has issues write permission"
if [[ "$(count_occurrences "$app_token_action")" != "1" ]]; then
fail "Only same-repository review mints the dedicated App token"
fi
pass "Only same-repository review mints the dedicated App token"
+249 -21
View File
@@ -2,7 +2,7 @@
# pr-state — emit a raw JSON snapshot of PR CI and review state.
#
# Usage:
# scripts/pr-state [--summary] [--all] <PR>
# scripts/pr-state [--summary] [--all] [--trusted-reviewer <login>] <PR>
# scripts/pr-state --comment <comment_id>
#
# Raw output:
@@ -10,11 +10,18 @@
# - pr: { number, branch }
# - since: { head_oid, committed_at } or null
# - checks: [{ type, name, workflow, status, conclusion, details_url, target_url, run_id, job_id }]
# - checks_head_sha: <SHA or null>
# - checks_snapshot_complete: <boolean>
# - review_threads: [{ thread_id, is_resolved, path, comment_id, comment_created_at, author, body_preview }]
# - hidden_unresolved_threads: [{ thread_id, comment_id, author, path, body_preview }]
# - unresolved_review_thread_count: <N>
# - filtered_review_thread_count: <N>
# - reviews: [{ author, state, submitted_at, html_url }]
# - reviews: [{ author, state, commit_id, body, submitted_at, html_url }]
# - review_evidence: { known, complete, opening_head_sha, closing_head_sha,
# current_head_sha, evidence_head_sha, exact_current_head_reviews,
# trusted_default_producer_exact_current_head_reviews,
# blocked_exact_current_head_reviews, blocked_exact_current_head_review_count,
# active_changes_requested, active_changes_requested_count }
# - issue_comments: [{ author, body, created_at, url }]
# - errors: [{ source, message }]
#
@@ -24,10 +31,17 @@
# - since: { head_oid, committed_at } or null
# - failed_checks: [{ name, workflow, status, conclusion, run_id, job_id, details_url, target_url }]
# - pending_checks: [{ name, workflow, status, run_id, job_id, details_url, target_url }]
# - checks_head_sha: <SHA or null>
# - checks_snapshot_complete: <boolean>
# - unresolved_review_thread_count: <N>
# - filtered_review_thread_count: <N>
# - unresolved_threads: [{ thread_id, comment_id, author, path, body_preview }]
# - hidden_unresolved_threads: [{ thread_id, comment_id, author, path, body_preview }]
# - review_evidence: { known, complete, opening_head_sha, closing_head_sha,
# current_head_sha, evidence_head_sha, exact_current_head_reviews,
# trusted_default_producer_exact_current_head_reviews,
# blocked_exact_current_head_reviews, blocked_exact_current_head_review_count,
# active_changes_requested, active_changes_requested_count }
# - summary_notes: [<short human-readable notes>]
# - errors: [{ source, message }]
#
@@ -50,6 +64,7 @@ usage() {
INCLUDE_ALL=0
SUMMARY=0
TRUSTED_REVIEWER=''
COMMENT_ID=''
PR=''
while [[ $# -gt 0 ]]; do
@@ -62,6 +77,13 @@ while [[ $# -gt 0 ]]; do
SUMMARY=1
shift
;;
--trusted-reviewer)
shift
[[ $# -gt 0 && -z "${TRUSTED_REVIEWER:-}" ]] || usage 1
TRUSTED_REVIEWER=$1
[[ -n "$TRUSTED_REVIEWER" && "$TRUSTED_REVIEWER" != -* ]] || usage 1
shift
;;
--comment)
shift
[[ $# -gt 0 && -z "${COMMENT_ID:-}" && -z "${PR:-}" ]] || usage 1
@@ -103,9 +125,13 @@ fi
errors='[]'
pr_json='{}'
checks_json='[]'
checks_head_sha=''
checks_snapshot_complete=false
review_threads_json='[]'
hidden_unresolved_threads_json='[]'
reviews_json='[]'
reviews_api_json='[]'
reviews_known=false
issue_comments_json='[]'
since_json='null'
unresolved_review_thread_count=null
@@ -177,20 +203,6 @@ extract_checks_json() {
((.state // "") | lower_or_null) as $state
| if $state == "pending" or $state == "expected" then null else $state end
end;
def preferred_check_order:
if .conclusion == "success" then 0
elif .status != "completed" then 1
elif (
.conclusion != null
and .conclusion != "neutral"
and .conclusion != "skipped"
and .conclusion != "cancelled"
) then 2
elif .conclusion == "neutral" then 3
elif .conclusion == "skipped" then 4
elif .conclusion == "cancelled" then 5
else 6
end;
$checks_in[0]
| to_entries
| map(.value + {_index: .key} | {
@@ -209,10 +221,18 @@ extract_checks_json() {
| group_by([(.workflow // ""), .name])
| map({
first_index: (map(._index) | min),
items: sort_by(preferred_check_order, ._index)
items: sort_by((.run_id | tonumber? // -1), (.job_id | tonumber? // -1), ._index)
})
| sort_by(.first_index)
| map(.items[])
| map(
.items as $items
| ($items | last) as $latest
| if ($latest.conclusion == "cancelled" or $latest.conclusion == "skipped" or $latest.conclusion == "neutral") then
([$items[] | select(.conclusion == "failure" or .conclusion == "timed_out" or .conclusion == "error" or .conclusion == "action_required")] | last) // $latest
else
$latest
end
)
| map(del(._index))
'
}
@@ -227,7 +247,15 @@ extract_issue_comments_json() {
author: (.author.login // "unknown"),
body: (.body // ""),
created_at: (.createdAt // .created_at // null),
url: (.url // null)
url: (.url // null),
actionable: (
(((.author.login // "unknown") == "github-actions") or ((.author.login // "unknown") == "github-actions[bot]"))
and (
(((.body // "") | gsub("^[[:space:]]+|[[:space:]]+$"; ""))
| test("^<!-- opencode-review:no-findings -->\\n\\*\\*OpenCode review complete\\*\\*\\n\\nOpenCode model: `[^`\\n]+`\\n\\nNo suggestions found for commit `[0-9A-Fa-f]{7,40}`\\.$")
| not)
)
)
})
'
}
@@ -241,12 +269,170 @@ extract_reviews_json() {
| map({
author: (.user.login // "unknown"),
state: ((.state // "unknown") | ascii_downcase),
commit_id: (.commit_id // null),
body: (.body // ""),
submitted_at: (.submitted_at // null),
html_url: (.html_url // null)
})
'
}
review_evidence_json() {
local opening_head_oid=$1
local closing_head_oid=$2
local checks_head_oid=$3
local reviews_known=$4
local reviews_api_json=$5
local is_cross_repository=$6
local trusted_workflow_json=$7
jq -cn --arg opening_head_oid "$opening_head_oid" --arg closing_head_oid "$closing_head_oid" --arg checks_head_oid "$checks_head_oid" --arg trusted_reviewer "$TRUSTED_REVIEWER" --argjson is_cross_repository "$is_cross_repository" --argjson reviews_known "$reviews_known" --argjson trusted_workflow "$trusted_workflow_json" \
--slurpfile reviews <(printf '%s\n' "$reviews_api_json") '
def normalized_reviews:
$reviews[0] | map({
author: (.user.login // "unknown"),
state: ((.state // "unknown") | ascii_downcase),
commit_id: (.commit_id // null),
body: (.body // ""),
submitted_at: (.submitted_at // null),
html_url: (.html_url // null)
});
def markdown_prose_lines:
reduce (split("\n")[]) as $line (
{fence: null, lines: []};
($line | (capture("^[[:space:]]*(?<marker>`{3,}|~{3,})")? // {marker: null}) | .marker) as $marker
| if .fence == null then
if $marker == null then .lines += [$line]
else .fence = ($marker[0:1])
end
elif $marker != null and ($marker[0:1]) == .fence then
.fence = null
else .
end
)
| .lines;
def has_positive_blocker_label:
markdown_prose_lines
| any(. | gsub("\\*\\*|__|_|\\*"; "") | test("^[[:space:]]*(?:>\\s*)?(?:(?:[-*+]\\s+)|(?:[0-9]+\\.\\s+))?blockers?[[:space:]]*:[[:space:]]*(?!0\\b|none\\b|no\\b)\\S"; "i"));
def has_explicit_blocker_statement:
markdown_prose_lines
| any(
.
| gsub("^[[:space:]]*(?:(?:[-*+]\\s+)|(?:[0-9]+\\.\\s+))?(?:\\*\\*|__|_|\\*)?"; "")
| test("^(?:blocked|changes[ _-]?requested|action required|must fix|required fix|please fix)\\b"; "i")
);
def review_eligibility:
if ((.body | test("\\|\\s*blocker\\s*\\|\\s*[1-9][0-9]*\\s*\\|"; "i")) or (.body | has_positive_blocker_label) or (.body | has_explicit_blocker_statement)) then {eligibility: "blocked", verdict: "blocker"}
elif .state == "approved" then {eligibility: "eligible", verdict: "approved"}
elif .state == "commented" and (.body | test("<!-- kandev-review: clean -->"; "i")) then {eligibility: "eligible", verdict: "clean"}
elif .state == "commented" then {eligibility: "unknown", verdict: "unknown"}
else {eligibility: "ineligible", verdict: .state}
end;
def trusted_default_producer:
(.body | capture("<!-- kandev-review: workflow-run id=(?<id>[0-9]+) attempt=(?<attempt>[0-9]+) -->")? // null) as $marker
| $marker != null
and $trusted_reviewer != ""
and $is_cross_repository == false
and .author == $trusted_reviewer
and .state == "commented"
and (.body | contains("<!-- kandev-review: clean -->"))
and $trusted_workflow.complete == true
and $marker.id == ($trusted_workflow.id | tostring)
and $marker.attempt == ($trusted_workflow.attempt | tostring);
($opening_head_oid != "" and $closing_head_oid != "" and $checks_head_oid != "" and $opening_head_oid == $closing_head_oid and $opening_head_oid == $checks_head_oid and $reviews_known) as $complete
| (normalized_reviews) as $all
| (if $complete then [$all[] | select(.commit_id == $opening_head_oid) | . + review_eligibility + {trusted_default_producer: trusted_default_producer}] else [] end) as $exact
| {
known: $complete,
complete: $complete,
opening_head_sha: (if $opening_head_oid == "" then null else $opening_head_oid end),
closing_head_sha: (if $closing_head_oid == "" then null else $closing_head_oid end),
checks_head_sha: (if $checks_head_oid == "" then null else $checks_head_oid end),
current_head_sha: (if $closing_head_oid == "" then null else $closing_head_oid end),
evidence_head_sha: (if $complete then $opening_head_oid else null end),
is_cross_repository: $is_cross_repository,
trusted_workflow_run: $trusted_workflow,
exact_current_head_reviews: $exact,
trusted_default_producer_exact_current_head_reviews: [$exact[] | select(.trusted_default_producer)],
blocked_exact_current_head_reviews: [$exact[] | select(.eligibility == "blocked")],
active_changes_requested: (if $complete then
[$all[] | select(.commit_id == $opening_head_oid)] | group_by(.author)
| map(
map(select(
.state == "approved"
or .state == "changes_requested"
or .state == "dismissed"
or (.state == "commented" and (.body | test("<!-- kandev-review: clean -->"; "i")))
))
| sort_by(.submitted_at // "")
| last
)
| map(select(.state == "changes_requested"))
else [] end)
}
| .active_changes_requested_count = (.active_changes_requested | length)
| .blocked_exact_current_head_review_count = (.blocked_exact_current_head_reviews | length)
| .trusted_producer = (if $complete | not or $trusted_workflow.known != true then "unknown"
elif $trusted_workflow.complete == true and (.trusted_default_producer_exact_current_head_reviews | length) > 0 then "true"
else "false"
end)
'
}
fetch_trusted_workflow_json() {
local owner=$1 repo=$2 pr_number=$3 head_sha=$4
local empty='{"known":false,"complete":false,"id":null,"attempt":null,"status":null,"conclusion":null,"job_complete":false}'
if [[ -z "$TRUSTED_REVIEWER" ]]; then
printf '%s\n' "$empty"
return
fi
if [[ "$is_cross_repository" != "false" || -z "$owner" || -z "$repo" || -z "$pr_number" || -z "$head_sha" ]]; then
add_error "trusted_workflow" "skipped: same-repository PR identity or stable head unavailable"
printf '%s\n' "$empty"
return
fi
local runs_pages runs selected run_id attempt jobs_pages
if ! runs_pages="$(gh_json api --paginate "repos/$owner/$repo/actions/workflows/opencode-code-review.yml/runs?event=pull_request_target&per_page=100")"; then
add_error "trusted_workflow" "workflow runs API failed"
printf '%s\n' "$empty"
return
fi
runs="$(jq -cs '[.[].workflow_runs[]?]' <<<"$runs_pages")"
selected="$(jq -c --argjson pr "$pr_number" --arg head "$head_sha" '
map(select(
(.id | type == "number") and (.run_number | type == "number") and (.run_attempt | type == "number")
and .path == ".github/workflows/opencode-code-review.yml"
and .event == "pull_request_target"
and ((.pull_requests | type) == "array")
and any(.pull_requests[]?; (.number == $pr) and (.head.sha == $head))
)) | sort_by(.run_number, .run_attempt, .id) | last // empty
' <<<"$runs")"
if [[ -z "$selected" ]]; then
add_error "trusted_workflow" "no authentic workflow run matched PR head"
printf '%s\n' "$empty"
return
fi
run_id="$(jq -r '.id' <<<"$selected")"
attempt="$(jq -r '.run_attempt' <<<"$selected")"
if ! jobs_pages="$(gh_json api --paginate "repos/$owner/$repo/actions/runs/$run_id/attempts/$attempt/jobs?per_page=100")"; then
add_error "trusted_workflow" "workflow jobs API failed"
jq -c '. + {known: true, complete: false, attempt: .run_attempt, job_complete: false} | {known, complete, id, attempt, status, conclusion, job_complete}' <<<"$selected"
return
fi
jq -cs --argjson run "$selected" '
[.[].jobs[]?] as $jobs
| ($jobs | any(.[]; .name == "opencode-review-same-repo" and .status == "completed" and .conclusion == "success")) as $job_complete
| {
known: true,
complete: (($run.status == "completed") and ($run.conclusion == "success") and $job_complete),
id: $run.id,
attempt: $run.run_attempt,
status: ($run.status // null),
conclusion: ($run.conclusion // null),
job_complete: $job_complete
}
' <<<"$jobs_pages"
}
extract_review_threads_json() {
local threads_nodes_json=$1
local since=$2
@@ -300,6 +486,8 @@ summarize_state_json() {
{
pr,
since,
checks_head_sha,
checks_snapshot_complete,
failed_checks: [
effective_checks[]
| select(terminal_failure_status and bad_conclusion)
@@ -312,6 +500,8 @@ summarize_state_json() {
],
unresolved_review_thread_count,
filtered_review_thread_count,
review_evidence,
actionable_issue_comment_count: ([.issue_comments[] | select(.actionable)] | length),
unresolved_threads: [
.review_threads[]
| select(.is_resolved == false)
@@ -459,16 +649,19 @@ fi
comments_json='[]'
status_json='[]'
resolved_pr_number=''
if pr_view_json="$(gh_json pr view "$PR" --json number,headRefName,statusCheckRollup,comments,url)"; then
is_cross_repository=null
if pr_view_json="$(gh_json pr view "$PR" --json number,headRefName,headRefOid,isCrossRepository,statusCheckRollup,comments,url)"; then
resolved_pr_number="$(jq -r '.number // ""' <<<"$pr_view_json")"
if pr_url_owner="$(jq -r '.url | capture("github.com/(?<owner>[^/]+)/(?<repo>[^/]+)/pull/[0-9]+").owner // ""' <<<"$pr_view_json" 2>/dev/null)" && [[ -n "$pr_url_owner" ]]; then
repo_owner="$pr_url_owner"
repo_name="$(jq -r '.url | capture("github.com/(?<owner>[^/]+)/(?<repo>[^/]+)/pull/[0-9]+").repo // ""' <<<"$pr_view_json")"
fi
pr_json="$(jq -c '{number: .number, branch: (.headRefName // "unknown")}' <<<"$pr_view_json")"
pr_json="$(jq -c '{number: .number, branch: (.headRefName // "unknown"), is_cross_repository: (if has("isCrossRepository") then .isCrossRepository else null end)}' <<<"$pr_view_json")"
is_cross_repository="$(jq -c 'if has("isCrossRepository") then .isCrossRepository else null end' <<<"$pr_view_json")"
comments_json="$(jq -c '.comments // []' <<<"$pr_view_json")"
status_json="$(jq -c '.statusCheckRollup // []' <<<"$pr_view_json")"
checks_json="$(extract_checks_json "$status_json")"
checks_head_sha="$(jq -r '.headRefOid // ""' <<<"$pr_view_json")"
else
if [[ "$PR" =~ ^[0-9]+$ ]]; then
pr_json="$(jq -cn --argjson number "$PR" '{number: $number, branch: "unknown"}')"
@@ -498,6 +691,7 @@ if [[ -n "$repo_owner" && -n "$repo_name" && -n "$resolved_pr_number" ]]; then
if reviews_pages_json="$(gh_json api --paginate -X GET "repos/$repo_owner/$repo_name/pulls/$resolved_pr_number/reviews")"; then
reviews_api_json="$(jq -cs 'add // []' <<<"$reviews_pages_json")"
reviews_json="$(extract_reviews_json "$reviews_api_json" "$since")"
reviews_known=true
else
add_error "reviews" "gh api pulls reviews failed"
fi
@@ -505,6 +699,8 @@ else
add_error "reviews" "skipped: resolved PR identity unavailable"
fi
opening_head_oid="$(jq -r '.head_oid // ""' <<<"$since_json")"
if [[ -n "$repo_owner" && -n "$repo_name" && -n "$resolved_pr_number" ]]; then
if threads_nodes_json="$(fetch_review_threads_nodes_json "$repo_owner" "$repo_name" "$resolved_pr_number")"; then
review_threads_json="$(extract_review_threads_json "$threads_nodes_json" "$since")"
@@ -529,6 +725,30 @@ elif [[ -z "$resolved_pr_number" ]]; then
add_error "review_threads" "skipped: resolved PR identity unavailable"
fi
trusted_workflow_json="$(fetch_trusted_workflow_json "$repo_owner" "$repo_name" "$resolved_pr_number" "$opening_head_oid")"
if [[ -n "$TRUSTED_REVIEWER" && "$is_cross_repository" == "false" && "$(jq -r '.known' <<<"$trusted_workflow_json")" != "true" ]]; then
add_error "trusted_workflow" "authenticated workflow evidence unavailable"
fi
closing_head_oid=''
if [[ -n "$repo_owner" && -n "$repo_name" && -n "$resolved_pr_number" ]]; then
if closing_since_json="$(fetch_head_commit_json "$repo_owner" "$repo_name" "$resolved_pr_number")"; then
closing_head_oid="$(jq -r '.head_oid // ""' <<<"$closing_since_json")"
if [[ -z "$closing_head_oid" ]]; then
add_error "closing_head" "head commit SHA unavailable after evidence collection"
fi
else
add_error "closing_head" "gh api graphql closing head commit failed"
fi
fi
if [[ -n "$opening_head_oid" && -n "$closing_head_oid" && "$opening_head_oid" != "$closing_head_oid" ]]; then
add_error "review_evidence" "head changed during evidence collection"
fi
if [[ -n "$checks_head_sha" && -n "$closing_head_oid" && "$checks_head_sha" == "$closing_head_oid" ]]; then
checks_snapshot_complete=true
fi
review_evidence_json="$(review_evidence_json "$opening_head_oid" "$closing_head_oid" "$checks_head_sha" "$reviews_known" "$reviews_api_json" "$is_cross_repository" "$trusted_workflow_json")"
state_tmp_dir="$(mktemp -d)"
cleanup_state_tmp() {
rm -rf "$state_tmp_dir"
@@ -541,6 +761,7 @@ printf '%s\n' "$checks_json" >"$state_tmp_dir/checks.json"
printf '%s\n' "$review_threads_json" >"$state_tmp_dir/review_threads.json"
printf '%s\n' "$hidden_unresolved_threads_json" >"$state_tmp_dir/hidden_unresolved_threads.json"
printf '%s\n' "$reviews_json" >"$state_tmp_dir/reviews.json"
printf '%s\n' "$review_evidence_json" >"$state_tmp_dir/review_evidence.json"
printf '%s\n' "$issue_comments_json" >"$state_tmp_dir/issue_comments.json"
printf '%s\n' "$errors" >"$state_tmp_dir/errors.json"
@@ -551,7 +772,10 @@ state_json="$(jq -cn \
--slurpfile review_threads "$state_tmp_dir/review_threads.json" \
--slurpfile hidden_unresolved_threads "$state_tmp_dir/hidden_unresolved_threads.json" \
--slurpfile reviews "$state_tmp_dir/reviews.json" \
--slurpfile review_evidence "$state_tmp_dir/review_evidence.json" \
--slurpfile issue_comments "$state_tmp_dir/issue_comments.json" \
--arg checks_head_sha "$checks_head_sha" \
--argjson checks_snapshot_complete "$checks_snapshot_complete" \
--argjson unresolved_review_thread_count "$unresolved_review_thread_count" \
--argjson filtered_review_thread_count "$filtered_review_thread_count" \
--slurpfile errors "$state_tmp_dir/errors.json" \
@@ -559,12 +783,16 @@ state_json="$(jq -cn \
pr: $pr[0],
since: $since[0],
checks: $checks[0],
checks_head_sha: (if $checks_head_sha == "" then null else $checks_head_sha end),
checks_snapshot_complete: $checks_snapshot_complete,
review_threads: $review_threads[0],
hidden_unresolved_threads: $hidden_unresolved_threads[0],
unresolved_review_thread_count: $unresolved_review_thread_count,
filtered_review_thread_count: $filtered_review_thread_count,
reviews: $reviews[0],
review_evidence: $review_evidence[0],
issue_comments: $issue_comments[0],
actionable_issue_comment_count: ([$issue_comments[0][] | select(.actionable)] | length),
errors: $errors[0]
}')"
+674 -15
View File
@@ -92,6 +92,35 @@ JSON
exit 0
fi
if [[ "$*" == *"actions/workflows/opencode-code-review.yml/runs"* ]]; then
if [[ "${GH_FAIL_TRUSTED_WORKFLOW:-0}" == "1" ]]; then
echo "workflow api failed" >&2
exit 1
fi
if [[ "${GH_HEAD_SEQUENCE:-stable}" == "after-workflow" ]]; then
: >"${GH_HEAD_COUNTER_FILE:?}"
fi
if [[ "${GH_TRUSTED_MULTI:-0}" == "1" ]]; then
cat <<'JSON'
{"workflow_runs":[{"id":901,"path":".github/workflows/opencode-code-review.yml","event":"pull_request_target","status":"completed","conclusion":"failure","run_number":11,"run_attempt":1,"pull_requests":[{"number":123,"head":{"sha":"abc123"}}]},{"id":900,"path":".github/workflows/opencode-code-review.yml","event":"pull_request_target","status":"completed","conclusion":"success","run_number":10,"run_attempt":2,"pull_requests":[{"number":123,"head":{"sha":"abc123"}}]}]}
JSON
exit 0
fi
conclusion="${GH_TRUSTED_WORKFLOW_CONCLUSION:-success}"
status="${GH_TRUSTED_WORKFLOW_STATUS:-completed}"
cat <<JSON
{"workflow_runs":[{"id":900,"path":".github/workflows/opencode-code-review.yml","event":"pull_request_target","status":"$status","conclusion":"$conclusion","run_number":10,"run_attempt":2,"pull_requests":[{"number":123,"head":{"sha":"abc123"}}]}]}
JSON
exit 0
fi
if [[ "$*" == *"actions/runs/900/attempts/2/jobs"* ]]; then
cat <<'JSON'
{"jobs":[{"name":"opencode-review-same-repo","status":"completed","conclusion":"success"}]}
JSON
exit 0
fi
if [[ "$1" == "pr" && "$2" == "view" && "$4" == "--json" ]]; then
if [[ "${GH_NO_PR_URL:-0}" == "1" ]]; then
pr_url='null'
@@ -102,6 +131,8 @@ if [[ "$1" == "pr" && "$2" == "view" && "$4" == "--json" ]]; then
{
"number": 123,
"headRefName": "feat/pr-state",
"headRefOid": "${GH_CHECKS_HEAD:-abc123}",
"isCrossRepository": ${GH_CROSS_REPOSITORY:-false},
"url": $pr_url,
"comments": [
{
@@ -114,6 +145,37 @@ if [[ "$1" == "pr" && "$2" == "view" && "$4" == "--json" ]]; then
"body": "**Claude finished review**\\n| Blocker | 1 |\\n| Suggestion | 2 |\\n**Verdict:** Ready with suggestions",
"createdAt": "2026-06-01T13:00:00Z",
"url": "https://github.com/kdlbs/kandev/pull/123#issuecomment-2"
},
{
"author": { "login": "github-actions" },
"body": "<!-- opencode-review:no-findings -->\\n**OpenCode review complete**\\n\\nOpenCode model: \`opencode-go/minimax-m3\`\\n\\nNo suggestions found for commit \`abc1234\`.",
"createdAt": "2026-06-01T13:01:00Z",
"url": "https://github.com/kdlbs/kandev/pull/123#issuecomment-3"
},
{
"author": { "login": "other-bot[bot]" },
"body": "<!-- opencode-review:no-findings -->\\n**OpenCode review complete**\\n\\nOpenCode model: \`opencode-go/minimax-m3\`\\n\\nNo suggestions found for commit \`abc1234\`.",
"createdAt": "2026-06-01T13:02:00Z"
},
{
"author": { "login": "github-actions" },
"body": "Review quotes <!-- opencode-review:no-findings --> later",
"createdAt": "2026-06-01T13:03:00Z"
},
{
"author": { "login": "github-actions" },
"body": "<!-- opencode-review:no-findings -->\\n**OpenCode review complete**\\nBlocker: real issue",
"createdAt": "2026-06-01T13:04:00Z"
},
{
"author": { "login": "github-actions" },
"body": "<!-- opencode-review:diagnostic --> failure",
"createdAt": "2026-06-01T13:05:00Z"
},
{
"author": { "login": "github-actions" },
"body": "<!-- opencode-review:fallback-findings --> finding",
"createdAt": "2026-06-01T13:06:00Z"
}
],
"statusCheckRollup": [
@@ -184,6 +246,22 @@ if [[ "$1" == "pr" && "$2" == "view" && "$4" == "--json" ]]; then
"conclusion": "FAILURE",
"detailsUrl": "https://github.com/kdlbs/kandev/actions/runs/27340000007/job/55150000007"
},
{
"__typename": "CheckRun",
"name": "late-cancelled-failure",
"workflowName": "CI",
"status": "COMPLETED",
"conclusion": "FAILURE",
"detailsUrl": "https://github.com/kdlbs/kandev/actions/runs/27340000008/job/55150000008"
},
{
"__typename": "CheckRun",
"name": "late-cancelled-failure",
"workflowName": "CI",
"status": "COMPLETED",
"conclusion": "CANCELLED",
"detailsUrl": "https://github.com/kdlbs/kandev/actions/runs/27340000009/job/55150000009"
},
{
"__typename": "StatusContext",
"context": "external pending",
@@ -197,10 +275,198 @@ JSON
fi
if [[ "$1" == "api" && "$2" == "--paginate" && "$3" == "-X" && "$4" == "GET" && "$5" == "repos/kdlbs/kandev/pulls/123/reviews" ]]; then
if [[ "${GH_REVIEW_SCENARIO:-default}" == "old-head" ]]; then
printf '%s\n' '[
{"user":{"login":"greptile-apps[bot]"},"state":"APPROVED","commit_id":"old-head-sha","submitted_at":"2026-06-01T13:30:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "exact-head" ]]; then
printf '%s\n' '[
{"user":{"login":"greptile-apps[bot]"},"state":"APPROVED","commit_id":"abc123","body":"Reviewed and approved.","submitted_at":"2026-06-01T13:30:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "github-actions-clean" ]]; then
printf '%s\n' '[
{"user":{"login":"github-actions[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"<!-- kandev-review: clean -->\\n**OpenCode review complete**","submitted_at":"2026-06-01T13:30:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "dedicated-app-clean" ]]; then
printf '%s\n' '[
{"user":{"login":"opencode-review-app[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"<!-- kandev-review: clean -->\\n<!-- kandev-review: workflow-run id=900 attempt=2 -->\\n**OpenCode review complete**","submitted_at":"2026-06-01T13:30:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "dedicated-app-wrong-run" ]]; then
printf '%s\n' '[
{"user":{"login":"opencode-review-app[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"<!-- kandev-review: clean -->\\n<!-- kandev-review: workflow-run id=899 attempt=1 -->","submitted_at":"2026-06-01T13:30:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "github-actions-old-head" ]]; then
printf '%s\n' '[
{"user":{"login":"github-actions[bot]"},"state":"COMMENTED","commit_id":"old-head-sha","body":"<!-- kandev-review: clean -->\\n**OpenCode review complete**","submitted_at":"2026-06-01T13:30:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "exact-dismissed" ]]; then
printf '%s\n' '[
{"user":{"login":"greptile-apps[bot]"},"state":"DISMISSED","commit_id":"abc123","body":"Dismissed after update.","submitted_at":"2026-06-01T13:30:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "approved-blocker" ]]; then
printf '%s\n' '[
{"user":{"login":"greptile-apps[bot]"},"state":"APPROVED","commit_id":"abc123","body":"Blocker: the approval does not waive this required fix.","submitted_at":"2026-06-01T13:30:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "comment-blocker" ]]; then
printf '%s\n' '[
{"user":{"login":"greptile-apps[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"Blocker: please fix the race before merge.","submitted_at":"2026-06-01T13:30:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "plain-selected-blocker" ]]; then
printf '%s\n' '[
{"user":{"login":"greptile-apps[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"Blocker: race condition in the retry loop.","submitted_at":"2026-06-01T13:30:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "plain-nonselected-blocker" ]]; then
printf '%s\n' '[
{"user":{"login":"greptile-apps[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"<!-- kandev-review: clean -->\nNo blockers.","submitted_at":"2026-06-01T13:30:00Z"},
{"user":{"login":"cubic-dev-ai[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"No blockers in unrelated files.\nBlockers: race condition in this path.","submitted_at":"2026-06-01T13:31:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "negated-colon-labels" ]]; then
printf '%s\n' '[
{"user":{"login":"one[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"Blocker: 0","submitted_at":"2026-06-01T13:30:00Z"},
{"user":{"login":"two[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"Blockers: 0","submitted_at":"2026-06-01T13:31:00Z"},
{"user":{"login":"three[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"Blocker: none","submitted_at":"2026-06-01T13:32:00Z"},
{"user":{"login":"four[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"Blockers: no issues","submitted_at":"2026-06-01T13:33:00Z"},
{"user":{"login":"five[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"No blocker: all clear","submitted_at":"2026-06-01T13:34:00Z"},
{"user":{"login":"six[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"No blockers: all clear","submitted_at":"2026-06-01T13:35:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "negated-then-positive-label" ]]; then
printf '%s\n' '[
{"user":{"login":"greptile-apps[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"No blockers: all clear\nBlocker: race condition","submitted_at":"2026-06-01T13:30:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "formatted-negative-labels" ]]; then
printf '%s\n' '[
{"user":{"login":"one[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"**Blocker:** none","submitted_at":"2026-06-01T13:30:00Z"},
{"user":{"login":"two[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"> **Blockers:** 0","submitted_at":"2026-06-01T13:31:00Z"},
{"user":{"login":"three[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"_Blocker:_ no issue","submitted_at":"2026-06-01T13:32:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "formatted-positive-labels" ]]; then
printf '%s\n' '[
{"user":{"login":"one[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"**Blocker:** race condition","submitted_at":"2026-06-01T13:30:00Z"},
{"user":{"login":"two[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"> **Blockers:** retry race","submitted_at":"2026-06-01T13:31:00Z"},
{"user":{"login":"three[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"- _Blocker:_ missing lock","submitted_at":"2026-06-01T13:32:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "single-asterisk-labels" ]]; then
printf '%s\n' '[
{"user":{"login":"one[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"*Blocker:* race condition","submitted_at":"2026-06-01T13:30:00Z"},
{"user":{"login":"two[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"*Blocker:* none","submitted_at":"2026-06-01T13:31:00Z"},
{"user":{"login":"three[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"*Blockers:* 0","submitted_at":"2026-06-01T13:32:00Z"},
{"user":{"login":"four[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"* Blocker: race condition","submitted_at":"2026-06-01T13:33:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "comment-clean" ]]; then
printf '%s\n' '[
{"user":{"login":"greptile-apps[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"<!-- kandev-review: clean -->\nNo findings.","submitted_at":"2026-06-01T13:30:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "comment-zero-table" ]]; then
printf '%s\n' '[
{"user":{"login":"greptile-apps[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"<!-- kandev-review: clean -->\n| Blocker | 0 |\nNo findings.","submitted_at":"2026-06-01T13:30:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "comment-no-blockers" ]]; then
printf '%s\n' '[
{"user":{"login":"greptile-apps[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"<!-- kandev-review: clean -->\nNo blockers. No findings.","submitted_at":"2026-06-01T13:30:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "comment-positive-table" ]]; then
printf '%s\n' '[
{"user":{"login":"greptile-apps[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"<!-- kandev-review: clean -->\n| Blocker | 2 |\nMust fix before merge.","submitted_at":"2026-06-01T13:30:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "clean-plus-current-blocker" ]]; then
printf '%s\n' '[
{"user":{"login":"greptile-apps[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"<!-- kandev-review: clean -->\nNo findings.","submitted_at":"2026-06-01T13:30:00Z"},
{"user":{"login":"cubic-dev-ai[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"Must fix this race before merge.","submitted_at":"2026-06-01T13:31:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "clean-plus-old-blocker" ]]; then
printf '%s\n' '[
{"user":{"login":"greptile-apps[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"<!-- kandev-review: clean -->\nNo findings.","submitted_at":"2026-06-01T13:30:00Z"},
{"user":{"login":"cubic-dev-ai[bot]"},"state":"COMMENTED","commit_id":"old-head-sha","body":"Must fix this old issue before merge.","submitted_at":"2026-06-01T13:31:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "changes-dismissed" ]]; then
printf '%s\n' '[
{"user":{"login":"cubic-dev-ai[bot]"},"state":"CHANGES_REQUESTED","commit_id":"abc123","submitted_at":"2026-06-01T13:30:00Z"},
{"user":{"login":"cubic-dev-ai[bot]"},"state":"DISMISSED","commit_id":"abc123","submitted_at":"2026-06-01T13:45:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "changes-current-clean" ]]; then
printf '%s\n' '[
{"user":{"login":"cubic-dev-ai[bot]"},"state":"CHANGES_REQUESTED","commit_id":"abc123","submitted_at":"2026-06-01T13:30:00Z"},
{"user":{"login":"cubic-dev-ai[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"<!-- kandev-review: clean -->\nNo findings.","submitted_at":"2026-06-01T13:45:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "old-change-current-clean" ]]; then
printf '%s\n' '[
{"user":{"login":"cubic-dev-ai[bot]"},"state":"CHANGES_REQUESTED","commit_id":"old-head-sha","submitted_at":"2026-06-01T13:30:00Z"},
{"user":{"login":"cubic-dev-ai[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"<!-- kandev-review: clean -->\\nNo findings.","submitted_at":"2026-06-01T13:45:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "current-change-old-approval" ]]; then
printf '%s\n' '[
{"user":{"login":"cubic-dev-ai[bot]"},"state":"CHANGES_REQUESTED","commit_id":"abc123","submitted_at":"2026-06-01T13:30:00Z"},
{"user":{"login":"cubic-dev-ai[bot]"},"state":"APPROVED","commit_id":"old-head-sha","submitted_at":"2026-06-01T13:45:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "quoted-blocker" ]]; then
printf '%s\n' '[
{"user":{"login":"greptile-apps[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"<!-- kandev-review: clean -->\\nPrior review quoted: please fix the race before merge.","submitted_at":"2026-06-01T13:30:00Z"}
]'
exit 0
fi
if [[ "${GH_REVIEW_SCENARIO:-default}" == "fenced-blocker-example" ]]; then
printf '%s\n' '[
{"user":{"login":"greptile-apps[bot]"},"state":"COMMENTED","commit_id":"abc123","body":"<!-- kandev-review: clean -->\nNo findings.\n\n```text\nplease fix the example\nBlocker: illustrative only\n```","submitted_at":"2026-06-01T13:30:00Z"}
]'
exit 0
fi
printf '%s\n' '[
{
"user": { "login": "greptile-apps[bot]" },
"state": "COMMENTED",
"commit_id": "old-head-sha",
"submitted_at": "2026-06-01T10:30:00Z"
}
]'
@@ -208,24 +474,35 @@ if [[ "$1" == "api" && "$2" == "--paginate" && "$3" == "-X" && "$4" == "GET" &&
{
"user": { "login": "cubic-dev-ai[bot]" },
"state": "CHANGES_REQUESTED",
"commit_id": "abc123",
"submitted_at": "2026-06-01T13:30:00Z"
}
]'
exit 0
fi
if [[ "$1" == "api" && "$2" == "graphql" ]]; then
if [[ "$1" == "api" && "$2" == "graphql" ]]; then
if [[ "$*" == *"headRefOid"* ]]; then
head_oid="abc123"
if [[ "${GH_HEAD_SEQUENCE:-stable}" == "race" ]]; then
if [[ -f "${GH_HEAD_COUNTER_FILE:?}" ]]; then
head_oid="def456"
else
: >"$GH_HEAD_COUNTER_FILE"
fi
elif [[ "${GH_HEAD_SEQUENCE:-stable}" == "after-workflow" && -f "${GH_HEAD_COUNTER_FILE:?}" ]]; then
head_oid="def456"
fi
printf '%s\n' '{
"data": {
"repository": {
"pullRequest": {
"headRefOid": "abc123",
"headRefOid": "'"$head_oid"'",
"commits": {
"nodes": [
{
"commit": {
"oid": "abc123",
"oid": "'"$head_oid"'",
"committedDate": "2026-06-01T12:00:00Z"
}
}
@@ -381,12 +658,13 @@ test_snapshot_happy_path() {
assert_jq "pr number" '.pr.number == 123' "$json"
assert_jq "branch" '.pr.branch == "feat/pr-state"' "$json"
assert_jq "since timestamp" '.since.committed_at == "2026-06-01T12:00:00Z"' "$json"
assert_jq "checks count" '.checks | length == 9' "$json"
assert_jq "duplicate cancelled check prefers success first" '[.checks[] | select(.name == "web lint")][0] | .conclusion == "success" and .run_id == "27340000001"' "$json"
assert_jq "checks collapse duplicate workflow attempts" '.checks | length < 10' "$json"
assert_jq "latest duplicate check uses newest attempt" '[.checks[] | select(.name == "web lint")][0] | .conclusion == "success" and .run_id == "27340000001"' "$json"
assert_jq "failed check preserved" '.checks[] | select(.name == "e2e") | .conclusion == "failure"' "$json"
assert_jq "check run id" '.checks[] | select(.name == "e2e") | .run_id == "27340000002"' "$json"
assert_jq "nested workflow name" '.checks[] | select(.name == "e2e") | .workflow == "CI"' "$json"
assert_jq "duplicate check prefers non-skipped first" '[.checks[] | select(.name == "opencode-review-same-repo")][0] | .conclusion == "success" and .run_id == "27340000005"' "$json"
assert_jq "duplicate cancellation retains prior failure" '[.checks[] | select(.name == "late-cancelled-failure")][0] | .conclusion == "failure" and .run_id == "27340000008"' "$json"
assert_jq "pending status context conclusion normalized" '.checks[] | select(.name == "external pending") | .status == "pending" and .conclusion == null' "$json"
assert_jq "threads count" '.review_threads | length == 1' "$json"
assert_jq "total unresolved count includes historical unresolved thread" '.unresolved_review_thread_count == 2' "$json"
@@ -395,12 +673,369 @@ test_snapshot_happy_path() {
assert_jq "thread comment timestamp" '.review_threads[] | select(.thread_id == "PRRT_1") | .comment_created_at == "2026-06-01T13:00:00Z"' "$json"
assert_jq "reviews count" '.reviews | length == 1' "$json"
assert_jq "review author" '.reviews[] | select(.author == "cubic-dev-ai[bot]") | .author == "cubic-dev-ai[bot]"' "$json"
assert_jq "issue comments count" '.issue_comments | length == 1' "$json"
assert_jq "issue comment author" '.issue_comments[] | select(.author == "github-actions") | .body | contains("Verdict")' "$json"
assert_jq "issue comments count" '.issue_comments | length == 7' "$json"
assert_jq "issue comment author" 'any(.issue_comments[]; .author == "github-actions" and (.body | contains("Verdict")))' "$json"
assert_jq "only GitHub Actions noncanonical output is actionable" '([.issue_comments[] | select(.actionable == false)] | length) == 2 and .actionable_issue_comment_count == 5' "$json"
assert_jq "non-GitHub-Actions canonical marker is nonactionable while workflow diagnostic and fallback remain actionable" 'any(.issue_comments[]; .author == "other-bot[bot]" and .actionable == false) and all(.issue_comments[] | select(.body | test("Blocker: real|diagnostic|fallback"; "i")); .actionable == true)' "$json"
assert_jq "no errors" '.errors == []' "$json"
assert_jq "raw review evidence is complete" '.review_evidence.complete == true and .review_evidence.current_head_sha == "abc123"' "$json"
assert_jq "active change request surfaced" '.review_evidence.active_changes_requested_count == 1' "$json"
pass "snapshot happy path"
}
test_old_head_review_does_not_qualify() {
local tmp
make_tmp_dir tmp
make_mock_gh "$tmp/bin"
local json
GH_REVIEW_SCENARIO=old-head PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/out.json"
json="$(<"$tmp/out.json")"
assert_jq "old head review evidence is complete but not exact" '.review_evidence.complete == true and .review_evidence.exact_current_head_reviews == []' "$json"
pass "old-head review does not qualify"
}
test_exact_head_selected_review_qualifies() {
local tmp
make_tmp_dir tmp
make_mock_gh "$tmp/bin"
local json
GH_REVIEW_SCENARIO=exact-head PATH="$tmp/bin:$PATH" "$SCRIPT" --summary 123 >"$tmp/out.json"
json="$(<"$tmp/out.json")"
assert_jq "summary exact selected review evidence" '.review_evidence.complete == true and .review_evidence.exact_current_head_reviews[0].author == "greptile-apps[bot]" and .review_evidence.exact_current_head_reviews[0].body == "Reviewed and approved." and .review_evidence.exact_current_head_reviews[0].eligibility == "eligible"' "$json"
pass "exact-head selected review qualifies"
}
test_clean_marker_requires_configured_dedicated_app_provenance() {
local tmp
make_tmp_dir tmp
make_mock_gh "$tmp/bin"
local exact_json
GH_REVIEW_SCENARIO=github-actions-clean PATH="$tmp/bin:$PATH" "$SCRIPT" --summary 123 >"$tmp/unconfigured.json"
local unconfigured_json
unconfigured_json="$(<"$tmp/unconfigured.json")"
assert_jq "unconfigured marker is not trusted evidence" '.review_evidence.exact_current_head_reviews[0].trusted_default_producer == false and .review_evidence.trusted_default_producer_exact_current_head_reviews == []' "$unconfigured_json"
GH_REVIEW_SCENARIO=dedicated-app-clean PATH="$tmp/bin:$PATH" "$SCRIPT" --summary --trusted-reviewer 'opencode-review-app[bot]' 123 >"$tmp/exact.json"
exact_json="$(<"$tmp/exact.json")"
assert_jq "matching dedicated App has authenticated OpenCode provenance" '.review_evidence.exact_current_head_reviews[0].author == "opencode-review-app[bot]" and .review_evidence.exact_current_head_reviews[0].eligibility == "eligible" and .review_evidence.exact_current_head_reviews[0].trusted_default_producer == true and .review_evidence.trusted_workflow_run.id == 900 and .review_evidence.trusted_workflow_run.complete == true and .review_evidence.trusted_producer == "true"' "$exact_json"
local old_json
GH_REVIEW_SCENARIO=github-actions-old-head PATH="$tmp/bin:$PATH" "$SCRIPT" --summary --trusted-reviewer 'github-actions[bot]' 123 >"$tmp/old.json"
old_json="$(<"$tmp/old.json")"
assert_jq "old head actions review never becomes trusted evidence" '.review_evidence.exact_current_head_reviews == [] and .review_evidence.trusted_default_producer_exact_current_head_reviews == []' "$old_json"
pass "clean marker requires configured dedicated app provenance"
}
test_trusted_workflow_requires_latest_success_and_matching_marker() {
local tmp
make_tmp_dir tmp
make_mock_gh "$tmp/bin"
local json
for conclusion in failure neutral cancelled skipped; do
GH_TRUSTED_WORKFLOW_CONCLUSION="$conclusion" GH_REVIEW_SCENARIO=dedicated-app-clean PATH="$tmp/bin:$PATH" "$SCRIPT" --summary --trusted-reviewer 'opencode-review-app[bot]' 123 >"$tmp/$conclusion.json"
json="$(<"$tmp/$conclusion.json")"
assert_jq "latest $conclusion workflow invalidates old clean review" '.review_evidence.trusted_workflow_run.complete == false and .review_evidence.trusted_producer == "false" and .review_evidence.trusted_default_producer_exact_current_head_reviews == []' "$json"
done
GH_TRUSTED_WORKFLOW_STATUS=in_progress GH_TRUSTED_WORKFLOW_CONCLUSION=null GH_REVIEW_SCENARIO=dedicated-app-clean PATH="$tmp/bin:$PATH" "$SCRIPT" --summary --trusted-reviewer 'opencode-review-app[bot]' 123 >"$tmp/pending.json"
json="$(<"$tmp/pending.json")"
assert_jq "latest pending workflow invalidates old clean review" '.review_evidence.trusted_workflow_run.complete == false and .review_evidence.trusted_default_producer_exact_current_head_reviews == []' "$json"
GH_REVIEW_SCENARIO=dedicated-app-wrong-run PATH="$tmp/bin:$PATH" "$SCRIPT" --summary --trusted-reviewer 'opencode-review-app[bot]' 123 >"$tmp/wrong-marker.json"
json="$(<"$tmp/wrong-marker.json")"
assert_jq "wrong workflow marker is untrusted" '.review_evidence.trusted_workflow_run.complete == true and .review_evidence.trusted_default_producer_exact_current_head_reviews == []' "$json"
GH_TRUSTED_MULTI=1 GH_REVIEW_SCENARIO=dedicated-app-clean PATH="$tmp/bin:$PATH" "$SCRIPT" --summary --trusted-reviewer 'opencode-review-app[bot]' 123 >"$tmp/reversed-generations.json"
json="$(<"$tmp/reversed-generations.json")"
assert_jq "newer authentic failure beats older success despite reverse API order" '.review_evidence.trusted_workflow_run.id == 901 and .review_evidence.trusted_workflow_run.complete == false and .review_evidence.trusted_default_producer_exact_current_head_reviews == []' "$json"
GH_FAIL_TRUSTED_WORKFLOW=1 GH_REVIEW_SCENARIO=dedicated-app-clean PATH="$tmp/bin:$PATH" "$SCRIPT" --summary --trusted-reviewer 'opencode-review-app[bot]' 123 >"$tmp/api-failure.json"
json="$(<"$tmp/api-failure.json")"
assert_jq "workflow API failure is unknown and untrusted" '.review_evidence.trusted_workflow_run.known == false and .review_evidence.trusted_producer == "unknown" and .review_evidence.trusted_default_producer_exact_current_head_reviews == []' "$json"
pass "trusted workflow requires latest success and matching marker"
}
test_fork_and_unknown_provenance_are_untrusted() {
local tmp
make_tmp_dir tmp
make_mock_gh "$tmp/bin"
local fork_json unknown_json
GH_CROSS_REPOSITORY=true GH_REVIEW_SCENARIO=dedicated-app-clean PATH="$tmp/bin:$PATH" "$SCRIPT" --summary --trusted-reviewer 'opencode-review-app[bot]' 123 >"$tmp/fork.json"
fork_json="$(<"$tmp/fork.json")"
assert_jq "fork App marker is untrusted" '.pr.is_cross_repository == true and .review_evidence.is_cross_repository == true and .review_evidence.trusted_default_producer_exact_current_head_reviews == []' "$fork_json"
GH_CROSS_REPOSITORY=null GH_REVIEW_SCENARIO=dedicated-app-clean PATH="$tmp/bin:$PATH" "$SCRIPT" --summary --trusted-reviewer 'opencode-review-app[bot]' 123 >"$tmp/unknown.json"
unknown_json="$(<"$tmp/unknown.json")"
assert_jq "unknown repository provenance is untrusted" '.pr.is_cross_repository == null and .review_evidence.is_cross_repository == null and .review_evidence.trusted_default_producer_exact_current_head_reviews == []' "$unknown_json"
pass "fork and unknown provenance are untrusted"
}
test_head_change_during_collection_invalidates_review_evidence() {
local tmp
make_tmp_dir tmp
make_mock_gh "$tmp/bin"
local json
GH_HEAD_SEQUENCE=race GH_HEAD_COUNTER_FILE="$tmp/head-calls" GH_REVIEW_SCENARIO=exact-head PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/out.json"
json="$(<"$tmp/out.json")"
assert_jq "head race is incomplete and cannot qualify" '.review_evidence.known == false and .review_evidence.complete == false and .review_evidence.opening_head_sha == "abc123" and .review_evidence.closing_head_sha == "def456" and .review_evidence.current_head_sha == "def456" and .review_evidence.evidence_head_sha == null and .review_evidence.exact_current_head_reviews == []' "$json"
pass "head change during collection invalidates review evidence"
}
test_head_change_during_authenticated_workflow_collection_invalidates_trust() {
local tmp
make_tmp_dir tmp
make_mock_gh "$tmp/bin"
local json
GH_HEAD_SEQUENCE=after-workflow GH_HEAD_COUNTER_FILE="$tmp/workflow-called" GH_REVIEW_SCENARIO=dedicated-app-clean PATH="$tmp/bin:$PATH" "$SCRIPT" --summary --trusted-reviewer 'opencode-review-app[bot]' 123 >"$tmp/out.json"
json="$(<"$tmp/out.json")"
assert_jq "post-workflow head race invalidates authenticated trust" '.review_evidence.complete == false and .review_evidence.opening_head_sha == "abc123" and .review_evidence.closing_head_sha == "def456" and .review_evidence.trusted_default_producer_exact_current_head_reviews == []' "$json"
pass "head change during authenticated workflow collection invalidates trust"
}
test_checks_head_mismatch_invalidates_combined_evidence() {
local tmp
make_tmp_dir tmp
make_mock_gh "$tmp/bin"
local json
GH_CHECKS_HEAD=old-checks-sha GH_REVIEW_SCENARIO=exact-head PATH="$tmp/bin:$PATH" "$SCRIPT" --summary 123 >"$tmp/out.json"
json="$(<"$tmp/out.json")"
assert_jq "checks and reviews cannot be paired across heads" '.checks_head_sha == "old-checks-sha" and .checks_snapshot_complete == false and .review_evidence.known == false and .review_evidence.complete == false and .review_evidence.checks_head_sha == "old-checks-sha" and .review_evidence.exact_current_head_reviews == []' "$json"
pass "checks head mismatch invalidates combined evidence"
}
test_exact_head_dismissed_review_is_not_eligible() {
local tmp
make_tmp_dir tmp
make_mock_gh "$tmp/bin"
local json
GH_REVIEW_SCENARIO=exact-dismissed PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/out.json"
json="$(<"$tmp/out.json")"
assert_jq "dismissed exact review is ineligible" '.review_evidence.exact_current_head_reviews[0].state == "dismissed" and .review_evidence.exact_current_head_reviews[0].eligibility == "ineligible"' "$json"
pass "exact-head dismissed review is not eligible"
}
test_approved_review_with_blocker_is_not_eligible() {
local tmp
make_tmp_dir tmp
make_mock_gh "$tmp/bin"
local json
GH_REVIEW_SCENARIO=approved-blocker PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/out.json"
json="$(<"$tmp/out.json")"
assert_jq "approved review body blocker wins" '.review_evidence.exact_current_head_reviews[0].state == "approved" and .review_evidence.exact_current_head_reviews[0].eligibility == "blocked" and .review_evidence.exact_current_head_reviews[0].verdict == "blocker"' "$json"
pass "approved review with blocker is not eligible"
}
test_commented_blocker_is_not_eligible() {
local tmp
make_tmp_dir tmp
make_mock_gh "$tmp/bin"
local json
GH_REVIEW_SCENARIO=comment-blocker PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/out.json"
json="$(<"$tmp/out.json")"
assert_jq "commented blocker is blocked" '(.review_evidence.exact_current_head_reviews[0].body | contains("Blocker")) and .review_evidence.exact_current_head_reviews[0].eligibility == "blocked" and .review_evidence.exact_current_head_reviews[0].verdict == "blocker"' "$json"
pass "commented blocker is not eligible"
}
test_plain_blocker_labels_are_blocked_and_aggregated() {
local tmp
make_tmp_dir tmp
make_mock_gh "$tmp/bin"
local selected_json
GH_REVIEW_SCENARIO=plain-selected-blocker PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/selected.json"
selected_json="$(<"$tmp/selected.json")"
assert_jq "plain selected blocker is blocked" '.review_evidence.exact_current_head_reviews[0].eligibility == "blocked" and .review_evidence.blocked_exact_current_head_review_count == 1' "$selected_json"
local nonselected_json
GH_REVIEW_SCENARIO=plain-nonselected-blocker PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/nonselected.json"
nonselected_json="$(<"$tmp/nonselected.json")"
assert_jq "plain nonselected blocker aggregates despite no blockers text" '.review_evidence.exact_current_head_reviews[0].eligibility == "eligible" and .review_evidence.blocked_exact_current_head_review_count == 1 and .review_evidence.blocked_exact_current_head_reviews[0].author == "cubic-dev-ai[bot]"' "$nonselected_json"
pass "plain blocker labels are blocked and aggregated"
}
test_colon_blocker_labels_are_line_anchored_and_scan_all_lines() {
local tmp
make_tmp_dir tmp
make_mock_gh "$tmp/bin"
local negative_json
GH_REVIEW_SCENARIO=negated-colon-labels PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/negative.json"
negative_json="$(<"$tmp/negative.json")"
assert_jq "zero and negated colon labels are nonblocking" '.review_evidence.blocked_exact_current_head_review_count == 0 and ([.review_evidence.exact_current_head_reviews[].eligibility] | all(. != "blocked"))' "$negative_json"
local mixed_json
GH_REVIEW_SCENARIO=negated-then-positive-label PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/mixed.json"
mixed_json="$(<"$tmp/mixed.json")"
assert_jq "later positive label wins" '.review_evidence.blocked_exact_current_head_review_count == 1 and .review_evidence.exact_current_head_reviews[0].eligibility == "blocked"' "$mixed_json"
pass "colon blocker labels are line anchored and scan all lines"
}
test_formatted_colon_labels_handle_emphasis_after_colon() {
local tmp
make_tmp_dir tmp
make_mock_gh "$tmp/bin"
local negative_json
GH_REVIEW_SCENARIO=formatted-negative-labels PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/negative.json"
negative_json="$(<"$tmp/negative.json")"
assert_jq "formatted negative labels are nonblocking" '.review_evidence.blocked_exact_current_head_review_count == 0 and ([.review_evidence.exact_current_head_reviews[].eligibility] | all(. != "blocked"))' "$negative_json"
local positive_json
GH_REVIEW_SCENARIO=formatted-positive-labels PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/positive.json"
positive_json="$(<"$tmp/positive.json")"
assert_jq "formatted positive labels block" '.review_evidence.blocked_exact_current_head_review_count == 3 and ([.review_evidence.exact_current_head_reviews[].eligibility] | all(. == "blocked"))' "$positive_json"
pass "formatted colon labels handle emphasis after colon"
}
test_single_asterisk_emphasis_and_list_markers_are_safe() {
local tmp
make_tmp_dir tmp
make_mock_gh "$tmp/bin"
local json
GH_REVIEW_SCENARIO=single-asterisk-labels PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/out.json"
json="$(<"$tmp/out.json")"
assert_jq "single asterisk forms classify correctly" '.review_evidence.blocked_exact_current_head_review_count == 2 and .review_evidence.exact_current_head_reviews[0].eligibility == "blocked" and .review_evidence.exact_current_head_reviews[1].eligibility != "blocked" and .review_evidence.exact_current_head_reviews[2].eligibility != "blocked" and .review_evidence.exact_current_head_reviews[3].eligibility == "blocked"' "$json"
pass "single asterisk emphasis and list markers are safe"
}
test_explicit_clean_commented_review_is_eligible() {
local tmp
make_tmp_dir tmp
make_mock_gh "$tmp/bin"
local json
GH_REVIEW_SCENARIO=comment-clean PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/out.json"
json="$(<"$tmp/out.json")"
assert_jq "explicit clean comment is eligible" '.review_evidence.exact_current_head_reviews[0].eligibility == "eligible" and .review_evidence.exact_current_head_reviews[0].verdict == "clean"' "$json"
pass "explicit clean commented review is eligible"
}
test_zero_blocker_summaries_remain_clean_with_explicit_marker() {
local tmp
make_tmp_dir tmp
make_mock_gh "$tmp/bin"
local table_json
GH_REVIEW_SCENARIO=comment-zero-table PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/table.json"
table_json="$(<"$tmp/table.json")"
assert_jq "zero blocker table is clean" '.review_evidence.exact_current_head_reviews[0].eligibility == "eligible" and .review_evidence.exact_current_head_reviews[0].verdict == "clean"' "$table_json"
local no_blockers_json
GH_REVIEW_SCENARIO=comment-no-blockers PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/no-blockers.json"
no_blockers_json="$(<"$tmp/no-blockers.json")"
assert_jq "no blockers text is clean" '.review_evidence.exact_current_head_reviews[0].eligibility == "eligible" and .review_evidence.exact_current_head_reviews[0].verdict == "clean"' "$no_blockers_json"
pass "zero blocker summaries remain clean with explicit marker"
}
test_positive_blocker_evidence_overrides_clean_marker() {
local tmp
make_tmp_dir tmp
make_mock_gh "$tmp/bin"
local json
GH_REVIEW_SCENARIO=comment-positive-table PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/out.json"
json="$(<"$tmp/out.json")"
assert_jq "positive blocker overrides clean marker" '.review_evidence.exact_current_head_reviews[0].eligibility == "blocked" and .review_evidence.exact_current_head_reviews[0].verdict == "blocker"' "$json"
pass "positive blocker evidence overrides clean marker"
}
test_exact_head_blockers_from_any_author_are_aggregated() {
local tmp
make_tmp_dir tmp
make_mock_gh "$tmp/bin"
local current_json
GH_REVIEW_SCENARIO=clean-plus-current-blocker PATH="$tmp/bin:$PATH" "$SCRIPT" --summary 123 >"$tmp/current.json"
current_json="$(<"$tmp/current.json")"
assert_jq "other author's current blocker is aggregated" '.review_evidence.blocked_exact_current_head_review_count == 1 and .review_evidence.blocked_exact_current_head_reviews[0].author == "cubic-dev-ai[bot]" and .review_evidence.blocked_exact_current_head_reviews[0].eligibility == "blocked"' "$current_json"
local old_json
GH_REVIEW_SCENARIO=clean-plus-old-blocker PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/old.json"
old_json="$(<"$tmp/old.json")"
assert_jq "old-head blocker is excluded" '.review_evidence.blocked_exact_current_head_review_count == 0 and .review_evidence.exact_current_head_reviews[0].eligibility == "eligible"' "$old_json"
pass "exact-head blockers from any author are aggregated"
}
test_latest_decisive_review_state_controls_active_change_requests() {
local tmp
make_tmp_dir tmp
make_mock_gh "$tmp/bin"
local blocked_json
PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/blocked.json"
blocked_json="$(<"$tmp/blocked.json")"
assert_jq "latest change request blocks" '.review_evidence.active_changes_requested_count == 1 and .review_evidence.active_changes_requested[0].author == "cubic-dev-ai[bot]"' "$blocked_json"
local cleared_json
GH_REVIEW_SCENARIO=changes-dismissed PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/cleared.json"
cleared_json="$(<"$tmp/cleared.json")"
assert_jq "later decisive dismissal clears change request" '.review_evidence.active_changes_requested_count == 0' "$cleared_json"
local clean_json
GH_REVIEW_SCENARIO=changes-current-clean PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/clean.json"
clean_json="$(<"$tmp/clean.json")"
assert_jq "later clean current-head review clears change request" '.review_evidence.active_changes_requested_count == 0' "$clean_json"
local old_change_current_clean_json
GH_REVIEW_SCENARIO=old-change-current-clean PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/old-change-current-clean.json"
old_change_current_clean_json="$(<"$tmp/old-change-current-clean.json")"
assert_jq "old-head change request does not block a clean current head" '.review_evidence.exact_current_head_reviews[0].eligibility == "eligible" and .review_evidence.active_changes_requested_count == 0' "$old_change_current_clean_json"
local current_change_old_approval_json
GH_REVIEW_SCENARIO=current-change-old-approval PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/current-change-old-approval.json"
current_change_old_approval_json="$(<"$tmp/current-change-old-approval.json")"
assert_jq "old-head approval cannot clear a current-head change request" '.review_evidence.active_changes_requested_count == 1 and .review_evidence.active_changes_requested[0].commit_id == "abc123"' "$current_change_old_approval_json"
pass "latest decisive review state controls active change requests"
}
test_quoted_blocker_language_does_not_block_clean_review() {
local tmp
make_tmp_dir tmp
make_mock_gh "$tmp/bin"
local json
GH_REVIEW_SCENARIO=quoted-blocker PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/out.json"
json="$(<"$tmp/out.json")"
assert_jq "quoted blocker text preserves explicit clean review" '.review_evidence.exact_current_head_reviews[0].eligibility == "eligible" and .review_evidence.blocked_exact_current_head_review_count == 0' "$json"
pass "quoted blocker language does not block clean review"
}
test_fenced_blocker_examples_do_not_block_clean_review() {
local tmp
make_tmp_dir tmp
make_mock_gh "$tmp/bin"
local json
GH_REVIEW_SCENARIO=fenced-blocker-example PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/out.json"
json="$(<"$tmp/out.json")"
assert_jq "fenced blocker examples preserve explicit clean review" '.review_evidence.exact_current_head_reviews[0].eligibility == "eligible" and .review_evidence.blocked_exact_current_head_review_count == 0' "$json"
pass "fenced blocker examples do not block clean review"
}
test_partial_failure_records_error_but_keeps_other_data() {
local tmp
make_tmp_dir tmp
@@ -411,8 +1046,8 @@ test_partial_failure_records_error_but_keeps_other_data() {
json="$(<"$tmp/out.json")"
assert_jq "reviews empty on failure" '.reviews == []' "$json"
assert_jq "checks still present" '.checks | length == 9' "$json"
assert_jq "new issue comments still present" '.issue_comments | length == 1' "$json"
assert_jq "checks still present" '.checks | length < 9' "$json"
assert_jq "new issue comments still present" '.issue_comments | length == 7' "$json"
assert_jq "partial failure recorded" '.errors | length == 1' "$json"
assert_jq "partial failure source" '.errors[0].source == "reviews"' "$json"
pass "partial failure records error but keeps other data"
@@ -442,7 +1077,7 @@ test_repo_failure_skips_review_threads() {
GH_FAIL_REPO=1 GH_NO_PR_URL=1 PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/out.json"
json="$(<"$tmp/out.json")"
assert_jq "checks still present on repo failure" '.checks | length == 9' "$json"
assert_jq "checks still present on repo failure" '.checks | length < 9' "$json"
assert_jq "review threads empty on repo failure" '.review_threads == []' "$json"
assert_jq "unresolved count unknown on repo failure" '.unresolved_review_thread_count == null' "$json"
assert_jq "repo failure recorded" '.errors[] | select(.source == "repo") | .message == "gh repo view failed"' "$json"
@@ -459,14 +1094,15 @@ test_graphql_failure_records_error_but_keeps_other_data() {
GH_FAIL_GRAPHQL=1 PATH="$tmp/bin:$PATH" "$SCRIPT" 123 >"$tmp/out.json"
json="$(<"$tmp/out.json")"
assert_jq "checks still present on graphql failure" '.checks | length == 9' "$json"
assert_jq "checks still present on graphql failure" '.checks | length < 9' "$json"
assert_jq "reviews still present on graphql failure" '.reviews | length == 2' "$json"
assert_jq "review threads empty on graphql failure" '.review_threads == []' "$json"
assert_jq "unresolved count unknown on graphql failure" '.unresolved_review_thread_count == null' "$json"
assert_jq "graphql failure records both errors" '.errors | length == 2' "$json"
assert_jq "graphql failure records opening and closing head errors" '.errors | length == 3' "$json"
assert_jq "graphql failure records since error" '.errors[] | select(.source == "since") | .message == "gh api graphql head commit failed; including historical comments"' "$json"
assert_jq "graphql failure records review_threads error" '.errors[] | select(.source == "review_threads") | .message == "gh api graphql reviewThreads failed"' "$json"
assert_jq "since fallback includes all historical comments" '.issue_comments | length == 2' "$json"
assert_jq "graphql failure records closing head error" '.errors[] | select(.source == "closing_head") | .message == "gh api graphql closing head commit failed"' "$json"
assert_jq "since fallback includes all historical comments" '.issue_comments | length == 8' "$json"
pass "graphql failure records error but keeps other data"
}
@@ -498,7 +1134,7 @@ test_all_flag_includes_historical_comments_and_reviews() {
json="$(<"$tmp/out.json")"
assert_jq "since omitted in all mode" '.since == null' "$json"
assert_jq "all issue comments present" '.issue_comments | length == 2' "$json"
assert_jq "all issue comments present" '.issue_comments | length == 8' "$json"
assert_jq "all reviews present" '.reviews | length == 2' "$json"
assert_jq "all review threads present" '.review_threads | length == 2' "$json"
assert_jq "all mode keeps historical thread comment" '.review_threads[] | select(.thread_id == "PRRT_1") | .comment_id == 111' "$json"
@@ -516,9 +1152,10 @@ test_summary_mode_returns_compact_fixup_state() {
assert_jq "summary keeps pr" '.pr.number == 123' "$json"
assert_jq "summary keeps since" '.since.committed_at == "2026-06-01T12:00:00Z"' "$json"
assert_jq "summary failed check count" '.failed_checks | length == 2' "$json"
assert_jq "summary failed check count" '.failed_checks | length == 3' "$json"
assert_jq "summary failed check" '.failed_checks[] | select(.name == "e2e") | .conclusion == "failure" and .run_id == "27340000002"' "$json"
assert_jq "summary reports failed duplicate over skipped" '.failed_checks[] | select(.name == "opencode-review-fork") | .conclusion == "failure" and .run_id == "27340000007"' "$json"
assert_jq "summary retains failure before a duplicate cancellation" '.failed_checks[] | select(.name == "late-cancelled-failure") | .conclusion == "failure" and .run_id == "27340000008"' "$json"
assert_jq "summary ignores cancelled duplicate check" '[.failed_checks[] | select(.name == "web lint")] | length == 0' "$json"
assert_jq "summary dedupes skipped duplicate check" '[.failed_checks[], .pending_checks[] | select(.name == "opencode-review-same-repo")] | length == 0' "$json"
assert_jq "summary pending check count" '.pending_checks | length == 2' "$json"
@@ -626,6 +1263,28 @@ test_comment_mode_rejects_incompatible_flags() {
}
test_snapshot_happy_path
test_old_head_review_does_not_qualify
test_exact_head_selected_review_qualifies
test_clean_marker_requires_configured_dedicated_app_provenance
test_trusted_workflow_requires_latest_success_and_matching_marker
test_fork_and_unknown_provenance_are_untrusted
test_head_change_during_collection_invalidates_review_evidence
test_head_change_during_authenticated_workflow_collection_invalidates_trust
test_checks_head_mismatch_invalidates_combined_evidence
test_exact_head_dismissed_review_is_not_eligible
test_approved_review_with_blocker_is_not_eligible
test_commented_blocker_is_not_eligible
test_plain_blocker_labels_are_blocked_and_aggregated
test_colon_blocker_labels_are_line_anchored_and_scan_all_lines
test_formatted_colon_labels_handle_emphasis_after_colon
test_single_asterisk_emphasis_and_list_markers_are_safe
test_explicit_clean_commented_review_is_eligible
test_zero_blocker_summaries_remain_clean_with_explicit_marker
test_positive_blocker_evidence_overrides_clean_marker
test_exact_head_blockers_from_any_author_are_aggregated
test_fenced_blocker_examples_do_not_block_clean_review
test_latest_decisive_review_state_controls_active_change_requests
test_quoted_blocker_language_does_not_block_clean_review
test_partial_failure_records_error_but_keeps_other_data
test_pr_view_failure_with_non_numeric_ref_keeps_schema
test_repo_failure_skips_review_threads