Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
62b81ca
chore(ci): remove orphaned required-workflow-bootstrap job from dispa…
claude Aug 31, 2026
902d8ec
Merge origin/main into fix/hollow-path-opencode-dispatch-bootstrap
Aug 31, 2026
b00e374
fix(ci): bound required-workflow-bootstrap awk extraction to its own job
claude Aug 31, 2026
d841254
fix(ci): remove grep -q from test_strix_quick_gate.sh pipeline checks
claude Aug 31, 2026
d862626
Merge origin/main into fix/hollow-path-opencode-dispatch-bootstrap
claude Sep 1, 2026
a470f29
Merge remote-tracking branch 'origin/main' into fix/hollow-path-openc…
claude Sep 1, 2026
db106d5
test(ci): close main's post-#1546 scheduler coverage regression
claude Sep 1, 2026
85c2469
docs(gap-baseline): record post-#1546 scheduler coverage regression
claude Sep 1, 2026
6f40a06
test(ci): document nested REST fixture helpers
seonghobae Sep 1, 2026
0e0856e
Merge remote-tracking branch 'origin/main' into fix/hollow-path-openc…
claude Sep 1, 2026
febf097
fix(ci): repair review-dispatch blob pin after merging current main
claude Sep 1, 2026
d528a9f
Merge remote-tracking branch 'origin/fix/main-coverage-gap-scheduler-…
claude Sep 1, 2026
0be68ad
fix(tests): drain dispatch fixture stdin to break CI dependency cycle
seonghobae Sep 1, 2026
2bad915
Merge remote-tracking branch 'origin/main' into fix/hollow-path-openc…
claude Sep 1, 2026
22c7357
merge: sync with origin/main (4fff1e2e, includes #1564/#1587)
claude Sep 1, 2026
665946b
test(ci,noema): port stale-test fixes from #1598 (post-#1587/#1564)
claude Sep 1, 2026
f918bac
merge: sync with origin/main (5768f2bd, absorbs #1592's overlapping f…
claude Sep 1, 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
6 changes: 0 additions & 6 deletions .github/workflows/opencode-review-dispatch.yml
Comment thread
seonghobae marked this conversation as resolved.
Comment thread
seonghobae marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,6 @@ permissions:
contents: read

jobs:
required-workflow-bootstrap:
name: required-workflow-bootstrap
runs-on: ubuntu-latest
steps:
- run: echo "OpenCode repository-dispatch review run materialized."

validate-pr-metadata:
name: validate-pr-metadata
if: github.event_name == 'repository_dispatch'
Expand Down
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ this file. The format follows Keep a Changelog, and versioned releases follow
Semantic Versioning where the repository publishes a release.

## [Unreleased]
- Close a 99% `scripts/ci` coverage regression on protected main: merged #1546 added an
uncovered `live_head_matches` helper, an uncovered no-active/no-stale-runs fall-through in
`prepare_autofix_slot`, and an uncovered "current-head autofix run is already queued or
running" wait path in `pr_review_fix_scheduler.py::inspect_pr`, while the pre-existing
conflicted-draft and conflicted-unauthorized `inspect_pr` returns and the REST
`fetch_workflow_names_by_check_suite_rest` pagination/name-filtering/permission-denied paths
in `pr_review_merge_scheduler.py` remained untested. Every PR rebasing onto main inherited
this failure via the `coverage-evidence` required check regardless of its own diff; landed on
protected main via #1572, plus a `test_scheduler_wake_reuses_trusted_receipt_predicate`
SIGPIPE-flake fix (the fake dispatch `gh` shim now drains stdin before exiting, so it cannot
race the production `jq | gh api --input -` pipeline under `pipefail`).
- **Fix `opencode-review.yml` admission gaps around stale/out-of-order events (`#1568`).**
Building on the draft-poll exemption's live PR/head validation, Devin Review found two
further defects. (1) The concurrency group was keyed only by repository and PR number, so
Expand Down Expand Up @@ -232,6 +243,13 @@ Semantic Versioning where the repository publishes a release.
then rejected via 429/404/timeout). New regression tests pin the default
to the policy module's canonical value and forbid the total-routes
constant from reappearing as the account-cap fallback.
- Remove the orphaned `required-workflow-bootstrap` job from
`opencode-review-dispatch.yml`: it only echoed a materialization message and
was never referenced by any `needs:` clause in that repository_dispatch-only
workflow, which fires only in an already-trusted, non-PR context (the
matching trust-boundary bootstrap that the pattern is for stays in the
`pull_request_target`-triggered `opencode-review.yml`, whose branch-protection
contract still requires it).
- Fix a dangling reference #1468 left in `docs/product-goal-directive.md`
(flagged by Devin Review on that PR): the standing operating directive
still named the removed `free_family_diversity` evidence field instead of
Expand Down
48 changes: 48 additions & 0 deletions docs/product-technical-gap-baseline.md
Original file line number Diff line number Diff line change
Expand Up @@ -2344,6 +2344,54 @@ contract assertion, and `docs/adr/0003-contextual-orchestrator-vendored-free-zdr
"today" reference. Landed in the same PR (`#1463`) as the streaming revert,
not split out, since the revert is unsafe without it.

## 2026-09-01 post-#1546 `scripts/ci` coverage regression on protected main: root-caused and closed

**Context**: `#1546` (merged, exact head `5686de41660d51a7a7f22b8840dfa6ccfe5ff3f1`) reconciled
unbounded exact-head review agents and, as part of a 90-line expansion of
`scripts/ci/pr_review_fix_scheduler.py`, added a `live_head_matches` helper, a no-active/no-stale
fall-through branch in `prepare_autofix_slot`, and an "already queued or running" wait branch in
`inspect_pr` — none of which any test exercised directly. This compounded a narrower, older gap in
the same file (`inspect_pr`'s conflicted-draft and conflicted-unauthorized returns) and in
`scripts/ci/pr_review_merge_scheduler.py::fetch_workflow_names_by_check_suite_rest` (pagination,
missing-suite-id/blank-name filtering, non-access-error propagation), first found and attempted in
now-closed, unmerged `#1547`/`#1551`/`#1554` — none of whose evidence or diffs transferred here;
this pass re-derived the current gap from a clean `origin/main` clone rather than assuming those
predecessors were still accurate against `#1546`'s shifted line numbers and new branches. Verified
directly: `coverage report --show-missing` on unmodified `main` showed
`scripts/ci/pr_review_fix_scheduler.py` at 97% (missing 116-121, 459->466, 495, 503, 546) and
`scripts/ci/pr_review_merge_scheduler.py` at 99% (missing 1003, 1008->1005, 1012) — total repo-wide
99%, below the `pyproject.toml` `fail_under = 100` gate. Because `opencode-review-dispatch.yml`'s
`coverage-evidence` job measures the **merged** PR tree (base + head) and hard-fails below 100%,
every PR rebasing onto main inherited this failure regardless of its own diff — org-wide impact,
not scoped to one PR.

**Fix**: `#1567` (test-only, no production code) adds direct unit coverage for `live_head_matches`
(case-insensitive match, mismatch, malformed-payload paths), `prepare_autofix_slot`'s empty-run
fall-through, the `inspect_pr` conflicted-draft/conflicted-unauthorized/already-queued cases, and
the `fetch_workflow_names_by_check_suite_rest` pagination/filtering/error-propagation paths.
Verified on the fix commit (`db106d50f2134ece147bc5318e389aeb124d198c`): `coverage run -m pytest
tests -q` (2251 passed, 1 skipped, 21 subtests), `coverage report` (repo-wide 100%, both files
individually 100% statement and 100% branch), `interrogate` (100.0%).

**Devin Review raised a false positive on the fix itself**, claiming
`test_live_head_matches_compares_case_insensitively_and_fails_closed` left non-object-payload,
non-string-SHA, and wrong-length-SHA branches uncovered. Re-verified against the actual gate rather
than accepted at face value: `live_head_matches` has exactly one `if` statement (two arcs, both
exercised by the committed test), and its final `return (isinstance(...) and len(...) == 40 and
...)` is a single boolean expression with no `if`/`else` of its own — `coverage.py`'s branch mode
(what `fail_under = 100` actually measures here) tracks control-flow arcs between statements, not
sub-clause condition coverage within one expression. The cited cases are additional test
thoroughness, not something the gate is currently failing on; confirmed by a full-suite run on the
exact same head showing both files at 100% branch coverage with zero missing branches. Replied with
this evidence on the review thread and did not widen the PR's diff for a claim that does not hold
against this repo's own tooling.

**One test in the full suite remains a known, pre-existing flake**, unrelated to this change:
`tests/test_opencode_required_verdict_regression.py::test_scheduler_wake_reuses_trusted_receipt_predicate`
intermittently exits 141 (SIGPIPE) under full-suite parallel load; reproduces identically on
unmodified `origin/main` and passes cleanly in file isolation. Not remediated here — out of scope
for a coverage-gap-only PR, and not itself a coverage regression.

## 2026-09-01 naruon#1486 transport-crash: root cause, owner, status

**Live incident**: the required `noema-review` check on `ContextualWisdomLab/naruon#1486` crashed with an
Expand Down
132 changes: 106 additions & 26 deletions tests/test_noema_removed_file_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from __future__ import annotations

import base64
import json

from scripts.ci import noema_review_gate as noema

Expand All @@ -12,7 +13,14 @@ def test_fetch_changed_files_preserves_path_and_status(monkeypatch):
monkeypatch.setattr(
noema,
"run",
lambda args, stdin=None: "a.py\tmodified\n\nb.py\tremoved\nfuzz/x.py\tadded\n",
lambda args, stdin=None: (
json.dumps(["a.py", "modified"])
+ "\n\n"
+ json.dumps(["b.py", "removed"])
+ "\n"
+ json.dumps(["fuzz/x.py", "added"])
+ "\n"
),
)

assert noema.fetch_changed_files("owner/repo", 7) == [
Expand All @@ -22,78 +30,150 @@ def test_fetch_changed_files_preserves_path_and_status(monkeypatch):
]


def test_removed_file_context_uses_base_content(monkeypatch):
"""A deleted file must be reviewed from immutable pre-deletion evidence."""
def test_removed_file_context_uses_merge_base_content(monkeypatch):
"""A deleted file must be reviewed from immutable merge-base evidence."""
head_sha = "a" * 40
base_sha = "b" * 40
merge_base_sha = "c" * 40
encoded = base64.b64encode(b"def doomed():\n pass\n").decode("ascii")
calls: list[str] = []

def fake_run(args, stdin=None):
target = args[2]
calls.append(target)
if target.endswith("/files"):
return "fuzz/fuzz_opencode_normalize_output.py\tremoved\n"
if "contents/fuzz/fuzz_opencode_normalize_output.py?ref=base-sha" in target:
return json.dumps(["fuzz/fuzz_opencode_normalize_output.py", "removed"]) + "\n"
if target == f"repos/owner/repo/compare/{base_sha}...{head_sha}":
return merge_base_sha
if f"contents/fuzz/fuzz_opencode_normalize_output.py?ref={merge_base_sha}" in target:
return encoded
raise AssertionError(args)

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

context = noema.changed_file_context(
"owner/repo", 1486, "head-sha", "base-sha"
)
context = noema.changed_file_context("owner/repo", 1486, head_sha, base_sha)

assert "File removed in this PR. Pre-deletion content at base ref" in context
assert f"Pre-deletion content at merge base `{merge_base_sha}`" in context
assert "def doomed" in context
assert not any("ref=head-sha" in target for target in calls)
assert not any(f"ref={head_sha}" in target for target in calls)


def test_fetch_changed_files_rejects_malformed_json_line(monkeypatch):
"""A non-JSON line from the Files API must fail closed, not crash raw."""
monkeypatch.setattr(noema, "run", lambda args, stdin=None: "not json\n")

try:
noema.fetch_changed_files("owner/repo", 7)
except RuntimeError as exc:
assert "malformed" in str(exc)
else:
raise AssertionError("expected RuntimeError for malformed JSON line")


def test_fetch_changed_files_rejects_malformed_record_shape(monkeypatch):
"""A well-formed JSON line that is not a two-element string pair must fail closed."""
monkeypatch.setattr(
noema, "run", lambda args, stdin=None: json.dumps(["only-one-field"]) + "\n"
)

try:
noema.fetch_changed_files("owner/repo", 7)
except RuntimeError as exc:
assert "malformed" in str(exc)
else:
raise AssertionError("expected RuntimeError for malformed record shape")


def test_fetch_merge_base_sha_rejects_malformed_head_sha():
"""An invalid head SHA must be rejected before any network call is attempted."""
try:
noema.fetch_merge_base_sha("owner/repo", "a" * 40, "not-a-sha")
except RuntimeError as exc:
assert "PR head SHA was unavailable or malformed" in str(exc)
else:
raise AssertionError("expected RuntimeError for malformed head SHA")


def test_fetch_merge_base_sha_rejects_malformed_compare_response(monkeypatch):
"""A compare response lacking a valid merge-base SHA must fail closed."""
monkeypatch.setattr(noema, "run", lambda args, stdin=None: "")

try:
noema.fetch_merge_base_sha("owner/repo", "a" * 40, "b" * 40)
except RuntimeError as exc:
assert "did not contain a valid merge-base SHA" in str(exc)
else:
raise AssertionError("expected RuntimeError for malformed compare response")


def test_removed_file_context_section_without_merge_base_or_error():
"""No merge-base SHA and no recorded error must still be explicit, not silent."""
context = noema.removed_file_context_section("owner/repo", "gone.py", "", "")

assert "merge-base SHA unavailable for pre-deletion content" in context


def test_removed_file_context_section_empty_merge_base_content(monkeypatch):
"""An empty (non-UTF-8-decodable) merge-base blob must be reported, not silently dropped."""
monkeypatch.setattr(noema, "fetch_file_content_at_ref", lambda repo, path, ref: "")

context = noema.removed_file_context_section("owner/repo", "gone.py", "c" * 40, "")

assert "no UTF-8 text content available from merge-base content API" in context


def test_removed_file_context_fails_closed_without_base_sha(monkeypatch):
"""Missing base identity must be explicit and must not trigger a head fetch."""
"""Missing base identity must be explicit and must not trigger a content fetch."""
monkeypatch.setattr(
noema,
"fetch_changed_files",
lambda repo, number: [("gone.py", "removed")],
)
monkeypatch.setattr(
noema,
"fetch_head_file_content",
"fetch_file_content_at_ref",
lambda *args, **kwargs: (_ for _ in ()).throw(AssertionError("unexpected fetch")),
)

context = noema.changed_file_context("owner/repo", 7, "head-sha", "")
context = noema.changed_file_context("owner/repo", 7, "a" * 40, "")

assert "PR base SHA was unavailable or malformed" in context
assert "Merge-base lookup unavailable" in context

assert "base SHA unavailable" in context

def test_removed_file_merge_base_content_failure_is_distinct_from_head_failure(monkeypatch):
"""A merge-base content API failure must remain typed as merge-base evidence failure."""
head_sha = "a" * 40
base_sha = "b" * 40
merge_base_sha = "c" * 40

def test_removed_file_base_fetch_failure_is_distinct_from_head_failure(monkeypatch):
"""A base-side API failure must remain typed as base evidence failure."""
monkeypatch.setattr(
noema,
"fetch_changed_files",
lambda repo, number: [("gone.py", "removed")],
)
monkeypatch.setattr(
noema, "fetch_merge_base_sha", lambda repo, base, head: merge_base_sha
)

def fail_fetch(repo, path, ref):
raise RuntimeError("HTTP 502: token ***")

monkeypatch.setattr(noema, "fetch_head_file_content", fail_fetch)
monkeypatch.setattr(noema, "fetch_file_content_at_ref", fail_fetch)

context = noema.changed_file_context(
"owner/repo", 7, "head-sha", "base-sha"
)
context = noema.changed_file_context("owner/repo", 7, head_sha, base_sha)

assert "Unavailable from base content API" in context
assert "Unavailable from merge-base content API" in context
assert "Unavailable from head content API" not in context


def test_build_review_context_passes_live_base_ref(monkeypatch):
"""The GraphQL base identity must reach changed-file context construction."""
observed: list[tuple[str, int, str, str]] = []
observed: list[tuple[str, int, str, str, object]] = []
monkeypatch.setattr(noema, "review_thread_context", lambda pr: "")
monkeypatch.setattr(noema, "load_codegraph_context", lambda: "")

def fake_context(repo, number, head_sha, base_sha=""):
observed.append((repo, number, head_sha, base_sha))
def fake_context(repo, number, head_sha, base_sha="", changed_files=None):
observed.append((repo, number, head_sha, base_sha, changed_files))
return "files"

monkeypatch.setattr(noema, "changed_file_context", fake_context)
Expand All @@ -104,5 +184,5 @@ def fake_context(repo, number, head_sha, base_sha=""):
{"headRefOid": "head-sha", "baseRefOid": "base-sha"},
)

assert observed == [("owner/repo", 7, "head-sha", "base-sha")]
assert observed == [("owner/repo", 7, "head-sha", "base-sha", None)]
assert "## Changed file context\nfiles" in result
Loading
Loading