Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
36 changes: 36 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,42 @@ this file. The format follows Keep a Changelog, and versioned releases follow
Semantic Versioning where the repository publishes a release.

## [Unreleased]
- Harden `noema_review_gate.py` against the bare-script import failure PR
#1497 introduced: it added an unconditional `from
scripts.ci.opencode_review_normalize_output import
changed_file_is_material` at module scope, which raises
`ModuleNotFoundError: No module named 'scripts'` when the script is run
directly (`python3 scripts/ci/noema_review_gate.py ...`, `sys.path[0]`
being the script's own directory rather than the repository root). This
was a live, org-wide Noema review outage (confirmed in
`ContextualWisdomLab/contextual-orchestrator#946`, run `33370760438`, job
`noema-review`) until PR #1501 fixed the active incident by changing
`noema-review.yml`'s call site to `python3 -m scripts.ci.noema_review_gate
...`, which also resolves the absolute import. This change is complementary defense-in-depth, not an active-outage
fix: it applies the same `if __package__: ... else: ...` conditional-import
fallback already used by `noema_review_handoff.py` directly to
`noema_review_gate.py`, so the script also runs correctly as a bare script
(not just as a module), and adds a regression test that runs `python3
scripts/ci/noema_review_gate.py --help` as a subprocess from the
repository root with `PYTHONPATH` cleared, reproducing that invocation
shape and proving the fix independently of #1501's workflow-level fix.
- Fix a broken CI contract test that was blocking every open `.github`-repo
PR: `test_strix_quick_gate.sh`'s
`assert_opencode_review_uses_codegraph_and_contextual_orchestrator` used an
`awk '/^ required-workflow-bootstrap:$/,/^[^ ]/'` range to isolate that
one job's YAML block in `opencode-review.yml`, intending to assert it has
no `if:` condition on any step (a real trust-boundary invariant: this
bootstrap job must never depend on event-payload fields). Because job keys
in that file are always 2-space indented, `/^[^ ]/` (a truly unindented
line) never matches anywhere in the `jobs:` section, so the range never
closed and silently swallowed every job defined after
`required-workflow-bootstrap` too — including the unrelated,
legitimate `if: github.event.action != 'closed'` on a completely different
job's step. `required-workflow-bootstrap` itself has always had zero `if:`
conditions; only the test's own job-scoping was wrong. Replaced the range
with an explicit awk state machine that starts at the bootstrap job header
and stops at the next 2-space-indented job key, so it correctly isolates
only that job's steps.
- Harden the review sidecar's per-account catalog cap against silent drift:
`contextual_orchestrator_review_launcher.py`'s two
`build_zdr_prioritized_catalog` call sites now source their
Expand Down
5 changes: 4 additions & 1 deletion scripts/ci/noema_review_gate.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
from collections.abc import Sequence
from typing import Any

from scripts.ci.opencode_review_normalize_output import changed_file_is_material
if __package__:
from scripts.ci.opencode_review_normalize_output import changed_file_is_material
else: # pragma: no cover - exercised by the standalone CLI regression test
from opencode_review_normalize_output import changed_file_is_material


PRIMARY_REVIEW_AUTHORS = {
Expand Down
6 changes: 5 additions & 1 deletion scripts/ci/test_strix_quick_gate.sh
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,11 @@ assert_opencode_review_uses_codegraph_and_contextual_orchestrator() {
assert_file_not_contains "$workflow_file" "Wait for trusted OpenCode approval review" "opencode pull_request bridge was removed to avoid duplicate required-check resource use"
assert_file_not_contains "$workflow_file" "Trusted OpenCode requested changes for head" "opencode pull_request bridge no longer reconsumes stale trusted review state"
assert_file_not_contains "$workflow_file" "github.event.pull_request.number == 240" "opencode review workflow must not hard-code repository-specific PR bypasses"
if awk '/^ required-workflow-bootstrap:$/,/^[^ ]/' "$bootstrap_file" | grep -q '^[[:space:]]*if:'; then
if awk '
/^ required-workflow-bootstrap:$/ { in_job = 1; print; next }
in_job && /^ [A-Za-z0-9_-]+:$/ { exit }
in_job { print }
' "$bootstrap_file" | grep -q '^[[:space:]]*if:'; then
record_failure "opencode required workflow bootstrap must not depend on required-workflow event payload fields"
fi
assert_file_contains "$workflow_file" 'github.event.client_payload.target_repository || github.repository' "opencode review scopes concurrency by target repository"
Expand Down
35 changes: 35 additions & 0 deletions tests/test_noema_review_gate.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,47 @@
import base64
import json
import os
import subprocess
import sys
from pathlib import Path

import pytest

from scripts.ci import noema_review_gate as noema


def test_standalone_cli_runs_from_repo_root_without_pythonpath():
"""Reproduce the production workflow invocation shape and prove it works.

The central ``noema-review.yml`` workflow runs this script as a bare
script (``python3 scripts/ci/noema_review_gate.py ...``) from the
repository root with no ``PYTHONPATH`` set. PR #1497 added an
unconditional ``from scripts.ci.opencode_review_normalize_output import
...`` at module scope, which crashes with ``ModuleNotFoundError: No
module named 'scripts'`` under that exact invocation because
``sys.path[0]`` is the script's own directory (``scripts/ci``), not the
repository root. This test drives the real production shape end to end.
"""
repo_root = Path(noema.__file__).resolve().parent.parent.parent
env = dict(os.environ)
env.pop("PYTHONPATH", None)

completed = subprocess.run(
[sys.executable, "scripts/ci/noema_review_gate.py", "--help"],
cwd=repo_root,
env=env,
check=False,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=10,
)

assert completed.returncode == 0
assert "noema_review_gate.py" in completed.stdout
assert "ModuleNotFoundError" not in completed.stderr


def fake_secret(*parts: str) -> str:
return "".join(parts)

Expand Down
Loading