Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
116 commits
Select commit Hold shift + click to select a range
2c247cd
test(strix): require runner-free PR supersession
seonghobae Sep 1, 2026
2a00416
merge: reconcile Strix supersession RED with current main
seonghobae Sep 1, 2026
2a2d54b
chore(ci): stage one-shot Strix control-plane supersession repair
seonghobae Sep 1, 2026
8e44383
fix(ci): materialize Strix repair expressions at runtime
seonghobae Sep 1, 2026
ab12c61
merge: reconcile Strix control-plane repair after scheduler landing
seonghobae Sep 1, 2026
c2b3f97
fix(ci): match Strix concurrency comment indentation
seonghobae Sep 1, 2026
43e108f
fix(ci): bootstrap hash-pinned Strix repair test environment
seonghobae Sep 1, 2026
3558a0d
fix(test): scope PR 1585 stale-run assertion to concurrency
seonghobae Sep 1, 2026
ff4e9b5
fix(ci): authorize PR 1585 workflow source repair
seonghobae Sep 1, 2026
2c53199
fix(ci): execute verified PR 1585 source repair with test runtime
seonghobae Sep 1, 2026
75ccf9c
fix(ci): isolate PR 1585 bootstrap audit clone
seonghobae Sep 1, 2026
7a1fca7
fix(test): scope Strix head-SHA exclusion to concurrency group
seonghobae Sep 1, 2026
32a1c9d
ci: rerun verified PR 1585 repair after test correction
seonghobae Sep 1, 2026
b5e0627
fix(ci): export verified PR 1585 workflow blobs
seonghobae Sep 1, 2026
97da5b8
chore(ci): export verified PR 1585 repair blobs
github-actions[bot] Sep 1, 2026
7394658
fix(strix): supersede predecessor PR runs before runner allocation
seonghobae Sep 1, 2026
3a0653f
ci: mark verified PR 1585 ready for integration
seonghobae Sep 1, 2026
778272c
chore(ci): reconcile Strix supersession with protected main and retir…
seonghobae Sep 1, 2026
9afcb58
ci: retry PR 1585 ready transition with maintainer authority
seonghobae Sep 1, 2026
9b90baf
chore(ci): retire unsuccessful PR 1585 ready helper
seonghobae Sep 1, 2026
c7098d2
test(strix): reject unordered same-PR native cancellation
seonghobae Sep 1, 2026
792123d
fix(strix): delegate stale-run cancellation to live-head scheduler
seonghobae Sep 1, 2026
1540411
chore(ci): stage bounded Strix stale-event contract repair
seonghobae Sep 1, 2026
f589b7d
test(strix): bind stale-run retirement to live-head scheduler
seonghobae Sep 1, 2026
5d43a07
chore(ci): retire superseded PR 1588 repair helper
seonghobae Sep 1, 2026
cd6a235
fix(strix): reconcile runner-free supersession onto current main
seonghobae Sep 1, 2026
9b06f1b
test(strix): require live-head checks around expensive scan
seonghobae Sep 1, 2026
68c82ad
chore(ci): add one-shot PR1588 exact-head repair
seonghobae Sep 1, 2026
36fb4ac
chore(ci): trigger PR1588 exact-head repair
seonghobae Sep 1, 2026
65162ac
fix(ci): make PR1588 repair workflow parse-safe
seonghobae Sep 1, 2026
95d0bca
chore(ci): retrigger parse-safe PR1588 repair
seonghobae Sep 1, 2026
0619337
ci: harden PR1588 exact-head repair
seonghobae Sep 1, 2026
772237c
ci: run hardened PR1588 repair
seonghobae Sep 1, 2026
572af2c
ci: make PR1588 repair parse-safe
seonghobae Sep 1, 2026
a9b5d0a
ci: rerun parse-safe PR1588 repair
seonghobae Sep 1, 2026
fb6f252
fix(strix): keep PR1588 repair aligned with unbounded inference
seonghobae Sep 1, 2026
42919a4
chore(strix): trigger unbounded-safe PR1588 repair
seonghobae Sep 1, 2026
3b33e86
ci: make PR1588 repair workflow parse-safe
seonghobae Sep 1, 2026
e6e857d
ci: run parse-safe PR1588 repair
seonghobae Sep 1, 2026
9780f98
ci: make PR1588 reconciliation current-main-safe
seonghobae Sep 1, 2026
7425fec
ci: run current-main PR1588 reconciliation
seonghobae Sep 1, 2026
7f8b1dd
ci: escape generated Actions expressions in PR1588 repair
seonghobae Sep 1, 2026
80bcde1
ci: rerun escaped PR1588 reconciliation
seonghobae Sep 1, 2026
d25b945
chore(strix): reconcile PR1588 with protected main
seonghobae Sep 1, 2026
6cf78cb
ci: execute reconciled PR1588 source repair
seonghobae Sep 1, 2026
d0a79fb
ci: add PR1588 runner bootstrap
seonghobae Sep 1, 2026
e799c50
ci: trigger PR1588 runner bootstrap
seonghobae Sep 1, 2026
9201d71
ci: route PR1588 repair canary to ubuntu-24.04
seonghobae Sep 1, 2026
f6b4bd7
ci: retrigger PR1588 runner bootstrap
seonghobae Sep 1, 2026
c7748ae
ci: move PR1588 repair helper to ubuntu-24.04
opencode-agent[bot] Sep 1, 2026
6806d1d
ci: run PR1588 exact-head repair on ubuntu-24.04
seonghobae Sep 1, 2026
1a61784
fix(strix): repair stale exact-head quick-gate contract
seonghobae Sep 1, 2026
8250091
ci: add one-shot Strix shell-contract reconciler
seonghobae Sep 1, 2026
7dd927a
chore(strix): rerun exact-head repair after stale-contract fix
seonghobae Sep 1, 2026
927fe71
ci: trigger one-shot Strix shell-contract reconciliation
seonghobae Sep 1, 2026
4241078
ci: add corrected one-shot Strix shell-contract reconciler
seonghobae Sep 1, 2026
f84720d
ci: trigger corrected Strix shell-contract reconciliation
seonghobae Sep 1, 2026
dfab16e
ci: retrigger PR1588 shell-contract reconciliation
seonghobae Sep 1, 2026
8eb964c
fix(ci): repair stale Strix timeout shell contract
seonghobae Sep 1, 2026
5d4697f
ci: retrigger repaired Strix shell-contract reconciliation
seonghobae Sep 1, 2026
13b117c
test(strix): align shell gate with live-head revalidation contract
opencode-agent[bot] Sep 1, 2026
9268ad3
ci: trigger exact-head PR1588 source reconciliation
seonghobae Sep 1, 2026
6a06bbb
fix(ci): distinguish stale Strix assertions from negative guards
seonghobae Sep 1, 2026
6951ce3
ci: rerun exact-head PR1588 source reconciliation
seonghobae Sep 1, 2026
261bcb4
fix(strix): preserve zero-timeout inference contract in repair
seonghobae Sep 1, 2026
8f64fb3
ci: trigger corrected PR1588 zero-timeout repair
seonghobae Sep 1, 2026
4fe50d6
fix(ci): verify PR1588 writer ref without PR API race
seonghobae Sep 1, 2026
77cc800
ci: rerun PR1588 with ref-bound writer guard
seonghobae Sep 1, 2026
510cf21
fix(ci): reconcile merged Strix timeout regression with zero-inferenc…
seonghobae Sep 1, 2026
07f1cba
ci: rerun PR1588 after timeout-contract reconciliation
seonghobae Sep 1, 2026
59de9c9
fix(ci): preserve bounded Strix timeout in PR1588 repair
seonghobae Sep 1, 2026
79270b4
ci: rerun bounded exact-head PR1588 repair
seonghobae Sep 1, 2026
691c2f8
fix(ci): ensure PR1588 repair push emits exact-head checks
seonghobae Sep 1, 2026
463eb5b
ci: rerun PR1588 exact-head repair with event-capable push
seonghobae Sep 1, 2026
c72803b
chore(ci): add PR 1588 no-timeout repair
seonghobae Sep 1, 2026
a502dc5
chore(ci): trigger PR 1588 no-timeout repair
seonghobae Sep 1, 2026
ee24a4e
fix(ci): permit guarded PR1588 source push with workflow token fallback
seonghobae Sep 1, 2026
93bdb2f
ci: rerun PR1588 source repair with guarded token fallback
seonghobae Sep 1, 2026
0694dc3
fix(strix): reconcile queue repair on current main
seonghobae Sep 1, 2026
0a3b1ba
ci(strix): retrigger exact-head queue repair on ubuntu-24.04
seonghobae Sep 1, 2026
9e8319f
ci(strix): repair helper writer credential scope
seonghobae Sep 1, 2026
d072acd
ci(strix): add Git-data reconciliation transport for PR #1588
seonghobae Sep 1, 2026
94b5512
ci(strix): trigger Git-data reconciliation for PR #1588
seonghobae Sep 1, 2026
838a5fd
ci(strix): retry exact-head PR1588 reconciler
seonghobae Sep 1, 2026
d69b965
merge(main): reconcile Strix live-head admission repair
seonghobae Sep 1, 2026
827a6c9
fix(noema): refresh reviewer App token before publication (#1616)
seonghobae Sep 1, 2026
e21db2f
chore(strix): run exact-head live-state repair
seonghobae Sep 1, 2026
cb38cc3
feat(metadata): reconcile fleet repository public surfaces
seonghobae Sep 1, 2026
fce0c0e
chore(ci): remove orphaned OpenCode dispatch bootstrap
seonghobae Sep 1, 2026
1f0c49b
merge(main): preserve metadata integration before bootstrap cleanup
seonghobae Sep 1, 2026
8f233aa
chore(strix): run parser-safe exact-head repair
seonghobae Sep 1, 2026
bfcbd16
chore(strix): remove inert one-shot repair helper
seonghobae Sep 1, 2026
8db410b
chore(strix): remove inert one-shot repair helper
seonghobae Sep 1, 2026
2ba6cc5
ci(strix): materialize exact-head live revalidation repair
seonghobae Sep 1, 2026
54fa1d2
ci(strix): add deterministic final-source repair transform
seonghobae Sep 1, 2026
29b731a
ci: run bounded PR 1619 causal repair
seonghobae Sep 1, 2026
0823ed2
ci(strix): simplify exact-head repair writer
seonghobae Sep 1, 2026
5f190b8
ci: make PR 1619 helper expression-safe
seonghobae Sep 1, 2026
45345b2
chore(ci): remove dead coverage requirements installer (#1621)
seonghobae Sep 1, 2026
84d0fc7
ci: reconstruct PR 1619 repair from current main
seonghobae Sep 1, 2026
7dcd52f
ci(opencode): make causal repair exact-head safe
seonghobae Sep 1, 2026
bf18aae
chore(ci): remove failed temporary PR 1619 writer
Sep 1, 2026
e95a90f
ci(opencode): repair same-repo status authority on exact head
seonghobae Sep 1, 2026
1ddc31f
chore(fuzz): remove dead duplicate OpenCode fuzz target (#1624)
seonghobae Sep 1, 2026
dd5ad8e
ci: add exact-head PR 1619 restack writer
seonghobae Sep 1, 2026
2e2abc8
ci(opencode): make PR 1619 restack writer self-contained
seonghobae Sep 1, 2026
9bda471
chore(ci): remove failed PR 1619 restack writer
Sep 1, 2026
638525a
ci(strix): align live publication contract with central gate
seonghobae Sep 1, 2026
624cc66
ci(strix): retrigger exact-head repair after stale test failure
seonghobae Sep 1, 2026
0a3ac6b
ci: rebuild PR 1619 repair from live main
seonghobae Sep 1, 2026
fc71c0a
fix(opencode): land verified live-main bootstrap cleanup
seonghobae Sep 1, 2026
c8b2fd4
fix(ci): make PR 1619 live-main repair fail-closed and exact
seonghobae Sep 1, 2026
66046ec
chore(ci): reconcile PR 1619 on protected main
seonghobae Sep 1, 2026
4ae90e1
chore(ci): remove orphaned OpenCode dispatch bootstrap (#1619)
seonghobae Sep 1, 2026
c3179ef
fix(strix): enforce live PR state at admission boundaries
github-actions[bot] Sep 1, 2026
2bb3c7c
Merge c3179ef847a3f8f7c5e73c645f8db9be86484881 into 4ae90e18b03a3a455…
seonghobae 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
262 changes: 262 additions & 0 deletions .github/actions/noema-review/two_phase.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
#!/usr/bin/env python3
"""Prepare and publish Noema verdicts across short-lived reviewer credentials.

The model phase can legitimately outlive a one-hour GitHub App installation
credential. This trusted helper therefore seals the already validated model
verdict to a runner-local file, then a later workflow step reopens that file
only after the reviewer credential has been refreshed. Publication always
re-fetches the live pull request and verifies its exact head and base before
submitting any review evidence.
"""

from __future__ import annotations

import argparse
import json
import os
import re
import stat
import sys
from pathlib import Path
from typing import Any

ROOT = Path(__file__).resolve().parents[3]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))

from scripts.ci import noema_review_gate as gate # noqa: E402

ENVELOPE_SCHEMA_VERSION = 1
MAX_ENVELOPE_BYTES = 2 * 1024 * 1024


def _canonical_head(value: str) -> str:
"""Return one canonical lowercase Git SHA or fail closed."""
head = value.strip().lower()
if not re.fullmatch(r"[0-9a-f]{40}", head):
raise RuntimeError("Noema two-phase handoff requires a canonical 40-character Git SHA")
return head


def _canonical_base(pull_request: dict[str, Any]) -> str:
"""Return the exact base commit that defined the reviewed diff/context."""
base = str(pull_request.get("baseRefOid") or "").strip().lower()
if not re.fullmatch(r"[0-9a-f]{40}", base):
raise RuntimeError("Noema two-phase handoff requires a canonical 40-character base SHA")
return base


def _reviewer_actor() -> str:
"""Return a verified independent reviewer actor for the active token."""
actor = gate.current_actor()
if not actor:
raise RuntimeError("Noema reviewer identity could not be verified")
if actor in gate.PRIMARY_REVIEW_AUTHORS:
raise RuntimeError(
f"Current token actor {actor!r} is already a primary review actor; "
"Noema requires an independent reviewer credential."
)
return actor


def _write_envelope(path: Path, payload: dict[str, Any]) -> None:
"""Create one private, non-following runner-local verdict envelope."""
encoded = (json.dumps(payload, separators=(",", ":"), sort_keys=True) + "\n").encode("utf-8")
if len(encoded) > MAX_ENVELOPE_BYTES:
raise RuntimeError("Noema verdict envelope exceeds the bounded handoff size")
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
fd = os.open(path, flags, 0o600)
try:
file_stat = os.fstat(fd)
if not stat.S_ISREG(file_stat.st_mode) or file_stat.st_nlink != 1:
raise RuntimeError("Noema verdict envelope target is not a private regular file")
view = memoryview(encoded)
written = 0
while written < len(view):
count = os.write(fd, view[written:])
if count <= 0:
raise RuntimeError("Noema verdict envelope write made no forward progress")
written += count
os.fsync(fd)
except BaseException:
os.close(fd)
path.unlink(missing_ok=True)
raise
else:
os.close(fd)


def _read_envelope(path: Path) -> dict[str, Any]:
"""Read and validate one sealed runner-local verdict envelope."""
flags = os.O_RDONLY
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
try:
fd = os.open(path, flags)
except OSError as exc:
raise RuntimeError("Noema verdict envelope is unavailable for publication") from exc
try:
file_stat = os.fstat(fd)
if not stat.S_ISREG(file_stat.st_mode) or file_stat.st_nlink != 1:
raise RuntimeError("Noema verdict envelope is not a regular single-link file")
if file_stat.st_mode & 0o077:
raise RuntimeError("Noema verdict envelope permissions are broader than owner-only")
if file_stat.st_size <= 0 or file_stat.st_size > MAX_ENVELOPE_BYTES:
raise RuntimeError("Noema verdict envelope size is outside the bounded contract")
chunks: list[bytes] = []
remaining = MAX_ENVELOPE_BYTES + 1
while remaining > 0:
chunk = os.read(fd, min(65536, remaining))
if not chunk:
break
chunks.append(chunk)
remaining -= len(chunk)
raw = b"".join(chunks)
if len(raw) > MAX_ENVELOPE_BYTES:
raise RuntimeError("Noema verdict envelope exceeded the bounded read limit")
finally:
os.close(fd)
try:
payload = json.loads(raw.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise RuntimeError("Noema verdict envelope is malformed") from exc
if not isinstance(payload, dict):
raise RuntimeError("Noema verdict envelope root must be an object")
return payload


def prepare_verdict(repo: str, number: int, expected_head: str, path: Path) -> int:
"""Run model review and seal its verdict without publishing GitHub evidence."""
expected = _canonical_head(expected_head)
pull_request = gate.fetch_pr(repo, number)
try:
gate.require_expected_head(pull_request, expected)
except RuntimeError:
print("Pull request is closed or stale; Noema verdict preparation skipped.")
return 0
expected_base = _canonical_base(pull_request)
actor = _reviewer_actor()
if pull_request.get("isDraft"):
print("PR is draft; Noema verdict preparation skipped.")
return 0
if gate.existing_noema_review(pull_request, actor):
print("Current head already has a Noema review; verdict preparation skipped.")
return 0

diff, truncated = gate.fetch_diff(repo, number)
changed_files = gate.fetch_changed_files(repo, number)
changed_paths = tuple(file_path for file_path, _status in changed_files)
review_context = gate.build_review_context(repo, number, pull_request, changed_files)
try:
verdict = gate.call_llm(
repo,
number,
pull_request,
diff,
truncated,
expected,
review_context,
changed_paths,
)
except gate.StaleHeadDuringRepairRetryError:
print("Pull request head changed during model repair retry; verdict was not sealed.")
return 0

_write_envelope(
path,
{
"schema_version": ENVELOPE_SCHEMA_VERSION,
"repository": repo,
"pull_request_number": number,
"expected_head": expected,
"expected_base": expected_base,
"verdict": verdict,
},
)
print(
f"Prepared Noema verdict for {repo}#{number} at head {expected} / base {expected_base}; "
"publication is deferred."
)
return 0


def publish_verdict(repo: str, number: int, expected_head: str, path: Path) -> int:
"""Publish a prepared verdict only with fresh exact-head/base reviewer authority."""
expected = _canonical_head(expected_head)
try:
payload = _read_envelope(path)
required_keys = {
"schema_version",
"repository",
"pull_request_number",
"expected_head",
"expected_base",
"verdict",
}
if set(payload) != required_keys:
raise RuntimeError("Noema verdict envelope fields do not match the trusted schema")
if payload["schema_version"] != ENVELOPE_SCHEMA_VERSION:
raise RuntimeError("Noema verdict envelope schema version is unsupported")
if payload["repository"] != repo or payload["pull_request_number"] != number:
raise RuntimeError("Noema verdict envelope target identity does not match publication")
if payload["expected_head"] != expected:
raise RuntimeError("Noema verdict envelope head does not match publication")
expected_base = str(payload["expected_base"]).strip().lower()
if not re.fullmatch(r"[0-9a-f]{40}", expected_base):
raise RuntimeError("Noema verdict envelope base does not contain a canonical Git SHA")
verdict = payload["verdict"]
if not isinstance(verdict, dict):
raise RuntimeError("Noema verdict envelope verdict must be an object")

current_pull_request = gate.fetch_pr(repo, number)
try:
gate.require_expected_head(current_pull_request, expected)
except RuntimeError:
print("Pull request closed or advanced after model review; prepared verdict was not published.")
return 0
if _canonical_base(current_pull_request) != expected_base:
print("Pull request base advanced after model review; stale prepared verdict was not published.")
return 0
actor = _reviewer_actor()
if current_pull_request.get("isDraft"):
print("PR became draft after model review; prepared verdict was not published.")
return 0
if gate.existing_noema_review(current_pull_request, actor):
print("Current head already has a Noema review; duplicate publication skipped.")
return 0
gate.submit_review(repo, number, current_pull_request, actor, verdict)
return 0
finally:
path.unlink(missing_ok=True)


def parse_args(argv: list[str]) -> argparse.Namespace:
"""Parse the trusted two-phase handoff command line."""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--repo", required=True)
parser.add_argument("--pr-number", required=True, type=int)
parser.add_argument("--expected-head", required=True)
modes = parser.add_mutually_exclusive_group(required=True)
modes.add_argument("--prepare-verdict-file", type=Path)
modes.add_argument("--publish-verdict-file", type=Path)
return parser.parse_args(argv)


def main(argv: list[str]) -> int:
"""Execute the selected prepare or publication phase."""
args = parse_args(argv)
if args.pr_number <= 0:
raise SystemExit("--pr-number must be positive")
if args.prepare_verdict_file is not None:
return prepare_verdict(args.repo, args.pr_number, args.expected_head, args.prepare_verdict_file)
return publish_verdict(args.repo, args.pr_number, args.expected_head, args.publish_verdict_file)


if __name__ == "__main__":
try:
raise SystemExit(main(sys.argv[1:]))
except RuntimeError as exc:
print(f"::error::{exc}", file=sys.stderr)
raise SystemExit(1) from exc
57 changes: 51 additions & 6 deletions .github/workflows/noema-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -552,8 +552,9 @@ jobs:
set -euo pipefail
bash "$GITHUB_WORKSPACE/scripts/ci/contextual_orchestrator_review_sidecar.sh"

- name: Run Noema LLM review and submit verdict
- name: Prepare Noema model verdict
if: env.PR_NUMBER != ''
id: noema_prepare
env:
GH_TOKEN: ${{ secrets.NOEMA_REVIEW_TOKEN || steps.noema_github_app_token.outputs.token || steps.noema_oidc_token.outputs.token }}
NOEMA_REVIEW_TOKEN_SOURCE: ${{ steps.noema_credential.outputs.source == 'pat' && 'noema-review-pat' || steps.noema_credential.outputs.source == 'github-app' && 'noema-review-github-app' || 'noema-review-app-oidc' }}
Expand All @@ -563,10 +564,11 @@ jobs:
set -euo pipefail
if [ -z "${PR_NUMBER:-}" ]; then
echo "No pull request number was available for this event; skipping."
echo "prepared=false" >>"$GITHUB_OUTPUT"
exit 0
fi
if [ -z "${GH_TOKEN:-}" ]; then
echo "::error::Noema reviewer credential selection succeeded but no token was minted; review cannot submit a verdict."
echo "::error::Noema reviewer credential selection succeeded but no token was minted; review cannot prepare a verdict."
exit 1
fi
if [ -z "${CONTEXTUAL_ORCHESTRATOR_BASE_URL:-}" ] || [ -z "${CONTEXTUAL_ORCHESTRATOR_TOKEN_FILE:-}" ]; then
Expand All @@ -578,7 +580,50 @@ jobs:
export NOEMA_LLM_MODEL="orchestrator/free"
export NOEMA_LLM_API_KEY="${CONTEXTUAL_ORCHESTRATOR_TOKEN}"
export NOEMA_LLM_VIA_ORCHESTRATOR=1
python3 -m scripts.ci.noema_review_gate \
--repo "$TARGET_REPOSITORY" \
--pr-number "$PR_NUMBER" \
--expected-head "$EXPECTED_HEAD_SHA"
verdict_file="${RUNNER_TEMP}/noema-verdict-envelope.json"
rm -f "$verdict_file"
python3 "$GITHUB_WORKSPACE/.github/actions/noema-review/two_phase.py" --repo "$TARGET_REPOSITORY" --pr-number "$PR_NUMBER" --expected-head "$EXPECTED_HEAD_SHA" --prepare-verdict-file "$verdict_file"
if [ -f "$verdict_file" ]; then
echo "prepared=true" >>"$GITHUB_OUTPUT"
else
echo "prepared=false" >>"$GITHUB_OUTPUT"
echo "::notice::Noema model phase produced no publishable envelope; publication is skipped."
fi

- name: Refresh repository-scoped Noema GitHub App token for publication
if: env.PR_NUMBER != '' && steps.noema_prepare.outputs.prepared == 'true' && steps.noema_credential.outputs.source == 'github-app'
id: noema_github_app_publication_token
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
with:
client-id: ${{ vars.NOEMA_GITHUB_APP_CLIENT_ID }}
private-key: ${{ secrets.NOEMA_GITHUB_APP_PRIVATE_KEY }}
owner: ContextualWisdomLab
repositories: ${{ steps.noema_credential.outputs.repository }}
permission-actions: read
permission-checks: read
permission-contents: read
permission-metadata: read
permission-pull-requests: write
permission-security-events: read
permission-statuses: read
permission-vulnerability-alerts: read

- name: Publish prepared Noema verdict on the exact live head
if: env.PR_NUMBER != '' && steps.noema_prepare.outputs.prepared == 'true'
env:
GH_TOKEN: ${{ steps.noema_credential.outputs.source == 'pat' && secrets.NOEMA_REVIEW_TOKEN || steps.noema_credential.outputs.source == 'github-app' && steps.noema_github_app_publication_token.outputs.token || steps.noema_credential.outputs.source == 'oidc' && steps.noema_oidc_token.outputs.token || '' }}
NOEMA_REVIEW_TOKEN_SOURCE: ${{ steps.noema_credential.outputs.source == 'pat' && 'noema-review-pat' || steps.noema_credential.outputs.source == 'github-app' && 'noema-review-github-app-refresh' || steps.noema_credential.outputs.source == 'oidc' && 'noema-review-app-oidc' || '' }}
NOEMA_REVIEW_ACTOR: ${{ steps.noema_github_app_publication_token.outputs['app-slug'] && format('{0}[bot]', steps.noema_github_app_publication_token.outputs['app-slug']) || '' }}
NOEMA_REVIEW_INSTALLATION_ID: ${{ steps.noema_github_app_publication_token.outputs['installation-id'] }}
run: |
set -euo pipefail
if [ -z "${GH_TOKEN:-}" ]; then
echo "::error::Noema publication has no credential for the explicitly selected reviewer source; refusing any GITHUB_TOKEN or author fallback."
exit 1
fi
verdict_file="${RUNNER_TEMP}/noema-verdict-envelope.json"
if [ ! -f "$verdict_file" ]; then
echo "::error::Noema prepared-verdict output claimed success but its private envelope is missing."
exit 1
fi
python3 "$GITHUB_WORKSPACE/.github/actions/noema-review/two_phase.py" --repo "$TARGET_REPOSITORY" --pr-number "$PR_NUMBER" --expected-head "$EXPECTED_HEAD_SHA" --publish-verdict-file "$verdict_file"
36 changes: 36 additions & 0 deletions .github/workflows/noema-token-lifetime-quality-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Noema Reviewer Token Lifetime CI

on:
pull_request:
paths:
- .github/workflows/noema-review.yml
- .github/actions/noema-review/two_phase.py
- tests/test_noema_reviewer_token_lifetime.py
- tests/test_noema_two_phase_handoff.py
- docs/doctoring/noema-review-token-lifetime.md
- docs/product-technical-gap-baseline.md
- CHANGELOG.md
- requirements-opencode-review-ci-hashes.txt
- .github/workflows/noema-token-lifetime-quality-ci.yml

permissions:
contents: read

jobs:
noema-reviewer-token-lifetime:
runs-on: ubuntu-24.04
timeout-minutes: 20
steps:
- name: Checkout exact source
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false
- name: Install pinned review CI dependencies
run: >-
python3 -m pip install --disable-pip-version-check --require-hashes --only-binary=:all: -r requirements-opencode-review-ci-hashes.txt
- name: Verify token-lifetime handoff contracts
run: |
set -euo pipefail
PYTHONPATH=. python3 -m pytest -q tests/test_noema_reviewer_token_lifetime.py tests/test_noema_two_phase_handoff.py
python3 -m compileall -q .github/actions/noema-review/two_phase.py tests/test_noema_reviewer_token_lifetime.py tests/test_noema_two_phase_handoff.py
git diff --check
6 changes: 0 additions & 6 deletions .github/workflows/opencode-review-dispatch.yml
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
Loading
Loading