Skip to content

Required Noema Review ContextualWisdomLab/fast-mlsirm#1506@773b70f37138bc55b51e33ed0bdc9bb7f2db82a6 #6613

Required Noema Review ContextualWisdomLab/fast-mlsirm#1506@773b70f37138bc55b51e33ed0bdc9bb7f2db82a6

Required Noema Review ContextualWisdomLab/fast-mlsirm#1506@773b70f37138bc55b51e33ed0bdc9bb7f2db82a6 #6613

name: Required Noema Review
run-name: >-
Required Noema Review ${{ github.event.client_payload.target_repository ||
github.event.pull_request.base.repo.full_name || github.repository }}#${{
github.event.client_payload.pr_number || github.event.pull_request.number ||
'event' }}@${{
github.event.client_payload.pr_head_sha || github.event.pull_request.head.sha ||
github.sha }}
on:
pull_request_target:
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft, closed]
# Default-branch-only retry entrypoint; no caller-selected workflow ref.
repository_dispatch:
types: [noema-review]
concurrency:
# Workflow-level admission is required: a queued run cannot reach a job-level
# cancellation guard while the organization is at its Actions job ceiling.
group: >-
required-noema-review-${{
github.event.pull_request.base.repo.full_name ||
github.event.client_payload.target_repository || github.repository }}-${{
github.event.pull_request.number ||
github.event.client_payload.pr_number || github.run_id }}
cancel-in-progress: true
permissions:
contents: read
pull-requests: read
checks: read
id-token: write
jobs:
admit-current-head:
if: >-
github.event_name == 'repository_dispatch'
|| (
github.event_name == 'pull_request_target'
&& github.event.action != 'closed'
&& github.event.action != 'converted_to_draft'
&& github.event.pull_request.head.repo.full_name == github.repository
)
runs-on: ubuntu-24.04
timeout-minutes: 5
outputs:
admitted: ${{ steps.live_head.outputs.admitted }}
permissions:
contents: read
pull-requests: read
env:
GH_TOKEN: ${{ secrets.NOEMA_REVIEW_TOKEN || github.token }}
TARGET_REPOSITORY: ${{ github.event.pull_request.base.repo.full_name || github.event.client_payload.target_repository || github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number || github.event.client_payload.pr_number || '' }}
EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.client_payload.pr_head_sha || '' }}
steps:
- name: Admit only the exact live Noema head
id: live_head
run: |
set -euo pipefail
echo "admitted=false" >>"$GITHUB_OUTPUT"
if ! [[ "$TARGET_REPOSITORY" =~ ^ContextualWisdomLab/[A-Za-z0-9_.-]+$ ]] ||
! [[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]] ||
! [[ "$EXPECTED_HEAD_SHA" =~ ^[0-9a-f]{40}$ ]]; then
echo "::error::Noema admission rejected malformed pull request metadata."
exit 1
fi
live_pr="$(gh api "repos/${TARGET_REPOSITORY}/pulls/${PR_NUMBER}")"
live_head="$(jq -r '.head.sha // empty' <<<"$live_pr")"
live_state="$(jq -r '.state // empty' <<<"$live_pr")"
if [ "${live_head,,}" != "${EXPECTED_HEAD_SHA,,}" ] || [ "$live_state" != "open" ]; then
echo "::notice::Noema admission retired a stale trigger before review queue entry."
exit 0
fi
echo "admitted=true" >>"$GITHUB_OUTPUT"
echo "Exact live Noema head admitted for ${TARGET_REPOSITORY}#${PR_NUMBER}."
cancel-closed-pr-runs:
if: >-
github.event_name == 'pull_request_target' &&
(github.event.action == 'closed' || github.event.action == 'converted_to_draft')
runs-on: ubuntu-24.04
# Bound this job well short of GitHub's 360-minute platform default. Its
# only step is a single-repository, status-filtered gh api --paginate
# list-and-cancel sweep (up to 3 passes x 5 statuses), no branch update
# or merge -- lighter than pr-review-merge-scheduler.yml's scan-pr-queue
# job (PR #1702), which got timeout-minutes: 30 for a comparable
# single-repo scan that also dispatches a review and updates a branch.
timeout-minutes: 20
permissions:
actions: write
contents: read
env:
GH_TOKEN: ${{ github.token }}
TARGET_REPOSITORY: ${{ github.event.pull_request.base.repo.full_name || github.repository }}
INACTIVE_PR_NUMBER: ${{ github.event.pull_request.number }}
INACTIVE_PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PR_ACTION: ${{ github.event.action }}
CURRENT_RUN_ID: ${{ github.run_id }}
steps:
- name: Cancel queued and running Noema reviews for the inactive pull request
shell: bash
run: |
set -euo pipefail
live_target_matches() {
local live_pr_json live_state live_draft live_head
if ! live_pr_json="$(gh api "repos/${TARGET_REPOSITORY}/pulls/${INACTIVE_PR_NUMBER}" 2>/tmp/noema-inactive-gh-error)"; then
echo "::warning::Noema inactive-PR cleanup could not verify the live pull request; leaving runs unchanged." >&2
return 1
fi
live_state="$(jq -r '.state // ""' <<<"$live_pr_json")"
live_draft="$(jq -r '.draft // false' <<<"$live_pr_json")"
live_head="$(jq -r '.head.sha // ""' <<<"$live_pr_json")"
[ "$live_head" = "$INACTIVE_PR_HEAD_SHA" ] && {
{ [ "$PR_ACTION" = "closed" ] && [ "$live_state" = "closed" ]; } ||
{ [ "$PR_ACTION" = "converted_to_draft" ] && [ "$live_state" = "open" ] && [ "$live_draft" = "true" ]; }
}
}
# cancel_runs prints the number of runs it matched for $1's status
# on stdout (its only stdout output) so the multi-pass loop below
# can tell whether a pass found anything; all human-facing log
# lines go to stderr so they don't pollute that count.
#
# The runs list is scoped to this repository, not to a specific
# workflow file: noema-review.yml runs against sibling
# repositories only through the organization's required-workflow
# ruleset (README.md's "또 같이" / "siblings call it" section) and
# is never itself committed to those repositories, so
# actions/workflows/noema-review.yml/runs is not guaranteed to
# resolve there -- GitHub's List repository workflows family
# enumerates workflow files that exist in that repository's own
# tree. actions/runs plus the run object's own `.path` field is
# this repo's own already-proven pattern for this exact cross-repo
# cleanup (see strix.yml's identical job).
# Status stays a server-side filter -- bounding each query to only
# the currently active runs -- rather than an unfiltered
# per-workflow fetch filtered client-side, since noema-review.yml
# is this org's central, highest-volume review workflow and an
# unbounded history walk on every PR close is a real rate-limit
# and latency risk here.
cancel_runs() {
local status="$1"
if ! live_target_matches; then
echo "::notice::Noema inactive-PR cleanup target changed; leaving runs unchanged." >&2
echo 0
return 0
fi
local runs_url="repos/${TARGET_REPOSITORY}/actions/runs?status=${status}&per_page=100"
local runs_json
if ! runs_json="$(gh api --paginate "$runs_url" 2>/tmp/noema-close-gh-error)"; then
echo "::warning::Noema close cleanup could not inspect ${TARGET_REPOSITORY}; leaving runs unchanged." >&2
sed 's/^/ /' /tmp/noema-close-gh-error >&2 || true
echo 0
return 0
fi
local run_ids
# PR-scoped by two independent, OR'd signals -- neither alone
# covers every trigger this job serves. The rendered
# display_title (this workflow's own run-name, embedding the
# target repository/PR number/head SHA) is this workflow's
# original signal, and stays reliable for repository_dispatch
# and workflow_run triggers. But GitHub does not consistently
# render run-name for an organization-required-workflow
# pull_request_target run materialized in a sibling repository
# (Devin Review, PR #1507: "Sibling Noema runs evade
# cancellation") -- `name` and `display_title` can both collapse
# to the bare workflow name and the plain PR title there,
# matching neither the old `.name ==` filter nor the
# display_title prefix below. GitHub's own `pull_requests[]`
# array on the run object closes that gap: it is populated for
# this workflow's pull_request_target runs because the
# noema-review job itself only ever processes same-repository,
# non-fork pull requests (its own `if:` requires
# `head.repo.full_name == github.repository`), so the cross-fork
# "empty pull_requests[]" caveat that rules this field out
# elsewhere in this org's tooling does not apply here. Neither
# signal alone is sufficient for every trigger type, so this
# matches on either one -- never a bare head_sha, which two
# different open PRs can share (e.g. a duplicate PR opened from
# the same branch against another target) and which would let
# closing one cancel the other's still-needed run. Matching by
# PR number rather than by the closed PR's current head SHA also
# means historical-head runs from earlier pushes to this same PR
# are still caught. `.path` pins the workflow identity in place
# of the old `.name ==` filter: unlike `.name` (which, like
# display_title, only carries the bare workflow name for a
# required-workflow-ruleset run), `.path` was independently
# confirmed stable across both native and sibling contexts.
if ! run_ids="$(jq -r --arg pr "$INACTIVE_PR_NUMBER" \
--arg current "$CURRENT_RUN_ID" --arg target "$TARGET_REPOSITORY" '
.workflow_runs[]
| select((.id | tostring) != $current)
| select(.path == ".github/workflows/noema-review.yml")
| select((.name // "") | startswith("Required Noema Review"))
| select(
((.display_title // "") | startswith("Required Noema Review " + $target + "#" + $pr + "@"))
or ((.pull_requests // []) | any(.number == ($pr | tonumber)))
)
| .id
' <<<"$runs_json")"; then
echo "::warning::Noema close cleanup received invalid run data for ${TARGET_REPOSITORY}; leaving runs unchanged." >&2
echo 0
return 0
fi
local matched=0
while IFS= read -r run_id; do
[ -n "$run_id" ] || continue
if ! live_target_matches; then
echo "::notice::Noema inactive-PR cleanup target changed before cancellation; leaving runs unchanged." >&2
break
fi
matched=$((matched + 1))
if gh api --method POST "repos/${TARGET_REPOSITORY}/actions/runs/${run_id}/cancel" >/dev/null 2>/tmp/noema-close-cancel-error; then
echo "Cancelled Noema run ${run_id} in ${TARGET_REPOSITORY} for inactive PR #${INACTIVE_PR_NUMBER}." >&2
else
echo "::warning::Noema close cleanup could not cancel run ${run_id}; it may have finished or the token lacks Actions write access." >&2
sed 's/^/ /' /tmp/noema-close-cancel-error >&2 || true
fi
done <<<"$run_ids"
echo "$matched"
}
# A run can transition between the five active statuses between
# one status's fetch and the next (e.g. it is "requested" when the
# already-fetched "queued" list was read, then becomes "queued"
# moments later, after this pass has already moved past checking
# "queued") -- a real GitHub Actions run lifecycle race, not a
# hypothetical. A single sequential sweep can let such a run
# escape cancellation entirely. Re-scan every active status for up
# to three passes: always run at least two full passes (a run that
# slips through every status query in pass 1 has, by definition,
# settled into a checkable status by the time pass 2 queries it
# again), and only skip the third when both prior passes matched
# nothing, bounding the retries so API flakiness cannot loop this
# forever.
max_passes=3
pass=1
found_any=0
while [ "$pass" -le "$max_passes" ]; do
pass_matches=0
for active_status in queued in_progress requested waiting pending; do
matched="$(cancel_runs "$active_status")"
pass_matches=$((pass_matches + matched))
done
echo "Noema close cleanup pass ${pass}/${max_passes} matched ${pass_matches} run(s) across active statuses." >&2
if [ "$pass_matches" -gt 0 ]; then
found_any=1
fi
if [ "$pass" -ge 2 ] && [ "$pass_matches" -eq 0 ] && [ "$found_any" -eq 0 ]; then
break
fi
pass=$((pass + 1))
done
noema-review:
name: noema-review
needs: [admit-current-head]
runs-on: ubuntu-24.04
# No job-level timeout-minutes here, deliberately. This job's "Prepare
# Noema model verdict" step calls two_phase.py's call_llm synchronously
# via the contextual-orchestrator gateway and blocks on the model's own
# response -- a job-level wall-clock bound here would cap the model's
# reasoning/tool-use time directly, which docs/product-goal-directive.md
# #8 prohibits ("Model timeout은 application·Agent·Gateway 공통 상한 없이
# 기본 null이다"; "OpenCode·Strix·Noema의 모델당 2시간 이상을 수용한다"). An
# earlier version of this job set timeout-minutes: 210, reasoning it gave
# that step "the same ~180-minute allowance" PR #1707 gave an unrelated
# step -- that reasoning was wrong: #1707's poll_deadline_epoch bounds a
# step that polls GitHub for whether a *separately triggered* review
# process has posted a verdict yet (an async external wait), not a step
# that itself runs the model synchronously. Any fixed cap on a job whose
# body IS the synchronous model call is exactly the fixed inference-time
# cap the policy forbids. See
# docs/doctoring/autofix-and-noema-review-model-job-timeout-removal.md.
if: >-
needs.admit-current-head.outputs.admitted == 'true'
&& (
github.event_name == 'repository_dispatch'
|| (
github.event_name == 'pull_request_target'
&& github.event.action != 'closed'
&& github.event.action != 'converted_to_draft'
&& github.event.pull_request.head.repo.full_name == github.repository
)
)
permissions:
actions: write
checks: read
contents: read
id-token: write
pull-requests: read
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
TARGET_REPOSITORY: ${{ github.event.pull_request.base.repo.full_name || github.event.client_payload.target_repository || github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number || github.event.client_payload.pr_number || '' }}
EXPECTED_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.client_payload.pr_head_sha || '' }}
steps:
- name: Skip events without pull request context
if: env.PR_NUMBER == ''
run: |
echo "::notice::Noema review skipped: no pull request number is associated with this event."
- name: Resolve trusted Noema review source ref
if: env.PR_NUMBER != ''
id: trusted_source
env:
JOB_CONTEXT_JSON: ${{ toJSON(job) }}
GITHUB_CONTEXT_JSON: ${{ toJSON(github) }}
run: |
set -euo pipefail
python3 <<'PY' >>"$GITHUB_OUTPUT"
import json
import os
import re
import sys
try:
job_context = json.loads(os.environ.get("JOB_CONTEXT_JSON") or "{}")
github_context = json.loads(os.environ.get("GITHUB_CONTEXT_JSON") or "{}")
except json.JSONDecodeError as exc:
print(f"::error::Could not parse GitHub workflow context JSON: {exc}", file=sys.stderr)
raise SystemExit(1)
trusted_repository = str(
job_context.get("workflow_repository") or "ContextualWisdomLab/.github"
).strip()
trusted_ref = str(
job_context.get("workflow_sha") or github_context.get("workflow_sha") or ""
).strip()
workflow_ref = str(
job_context.get("workflow_ref") or github_context.get("workflow_ref") or ""
).strip()
if not trusted_ref:
trusted_ref = "main"
prefix = "ContextualWisdomLab/.github/.github/workflows/noema-review.yml@"
if workflow_ref.startswith(prefix):
trusted_ref = workflow_ref.split("@", 1)[1]
if trusted_repository != "ContextualWisdomLab/.github":
print("::error::Trusted Noema workflow repository resolved outside ContextualWisdomLab/.github.", file=sys.stderr)
raise SystemExit(1)
if not re.fullmatch(r"[0-9a-fA-F]{40}|refs/[^\s]+|[A-Za-z0-9._/-]+", trusted_ref):
print("::error::Trusted Noema workflow ref resolved to an invalid value.", file=sys.stderr)
raise SystemExit(1)
print(f"repository={trusted_repository}")
print(f"ref={trusted_ref}")
PY
- name: Materialize trusted Noema review gate
if: env.PR_NUMBER != ''
env:
GH_TOKEN: ${{ github.token }}
TRUSTED_SOURCE_REF: ${{ steps.trusted_source.outputs.ref }}
run: |
set -euo pipefail
if [[ ! "$TRUSTED_SOURCE_REF" =~ ^[0-9a-fA-F]{40}$ ]]; then
echo "::error::Trusted Noema source ref must resolve to the immutable workflow commit SHA before archive materialization."
exit 1
fi
trusted_archive="${RUNNER_TEMP}/trusted-noema-source.tar.gz"
api_url="${GITHUB_API_URL:-https://api.github.com}"
curl -fsSL \
-H "Authorization: Bearer ${GH_TOKEN}" \
-H "Accept: application/vnd.github+json" \
-o "$trusted_archive" \
"${api_url}/repos/ContextualWisdomLab/.github/tarball/${TRUSTED_SOURCE_REF}"
tar -xzf "$trusted_archive" -C "$GITHUB_WORKSPACE" --strip-components=1
test -f scripts/ci/noema_review_gate.py
- name: Reject a stale trigger before credential or model setup
if: env.PR_NUMBER != ''
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if [[ ! "$EXPECTED_HEAD_SHA" =~ ^[0-9a-f]{40}$ ]]; then
echo "::error::Noema trigger did not provide a canonical lowercase exact head SHA."
exit 1
fi
live_head="$(gh api "repos/${TARGET_REPOSITORY}/pulls/${PR_NUMBER}" --jq '.head.sha')"
if [ "${live_head,,}" != "${EXPECTED_HEAD_SHA,,}" ]; then
echo "::error::Noema trigger is stale; expected ${EXPECTED_HEAD_SHA}, observed ${live_head}."
exit 1
fi
- name: Cancel superseded Noema runs after live-head validation
if: github.event_name == 'pull_request_target' && env.PR_NUMBER != ''
env:
GH_TOKEN: ${{ github.token }}
CURRENT_RUN_ID: ${{ github.run_id }}
run: |
set -euo pipefail
declare -A seen=()
cancelled=0
for pass in 1 2; do
for active_status in queued in_progress requested waiting pending; do
if ! runs_json="$(gh api --paginate "repos/${TARGET_REPOSITORY}/actions/runs?status=${active_status}&per_page=100")"; then
echo "::warning::Could not inspect ${active_status} Noema runs for superseded heads."
continue
fi
# See the close-cleanup job's matching comment above cancel_runs's
# own selector for the full rationale: display_title only
# renders this workflow's PR/head-bearing run-name reliably for
# a native trigger, so a sibling-repository required-workflow
# run is additionally matched via GitHub's own pull_requests[]
# array (populated here because noema-review only ever
# processes same-repository, non-fork pull requests), and
# `.path` pins workflow identity where `.name` cannot. The
# live-head exclusion below is independently reinforced with a
# direct `.head_sha` comparison -- the run object's own
# head_sha field, unlike display_title, is populated and
# accurate regardless of run-name rendering, so it protects the
# current run even when its display_title never rendered a
# matching "@$head" suffix to exclude by.
if ! run_ids="$(jq -r --arg pr "$PR_NUMBER" --argjson current "$CURRENT_RUN_ID" \
--arg target "$TARGET_REPOSITORY" --arg head "$EXPECTED_HEAD_SHA" '
.workflow_runs[]
| select(.id < $current)
| select(.path == ".github/workflows/noema-review.yml")
| select((.name // "") | startswith("Required Noema Review"))
| select(
((.display_title // "") | startswith("Required Noema Review " + $target + "#" + $pr + "@"))
or ((.pull_requests // []) | any(.number == ($pr | tonumber)))
)
| select(((.display_title // "") | endswith("@" + $head)) | not)
| select(((.head_sha // "") | ascii_downcase) != ($head | ascii_downcase))
| .id
' <<<"$runs_json")"; then
echo "::warning::Could not parse ${active_status} Noema runs for superseded heads."
continue
fi
while IFS= read -r run_id; do
[ -n "$run_id" ] || continue
[ -z "${seen[$run_id]:-}" ] || continue
seen[$run_id]=1
# A transient failure here (rate limit, network blip) must
# never crash this step under set -e: this is a housekeeping
# cleanup, and letting an ancillary API hiccup fail the whole
# job would block a perfectly valid, live-head review over
# something unrelated to it. Treat "cannot verify" the same
# as "verified stale": stop cancelling rather than risk a
# wrong cancellation, but let the job continue.
if ! live_head="$(gh api "repos/${TARGET_REPOSITORY}/pulls/${PR_NUMBER}" --jq '.head.sha' 2>/tmp/noema-supersede-live-head-error)"; then
echo "::warning::Noema cleanup could not re-verify the live PR head before cancelling run ${run_id}; stopping cleanup rather than risking a wrong cancellation." >&2
sed 's/^/ /' /tmp/noema-supersede-live-head-error >&2 || true
exit 0
fi
if [ "${live_head,,}" != "${EXPECTED_HEAD_SHA,,}" ]; then
echo "::notice::Noema cleanup stopped because the PR head advanced."
exit 0
fi
if gh api --method POST "repos/${TARGET_REPOSITORY}/actions/runs/${run_id}/cancel" >/dev/null; then
cancelled=$((cancelled + 1))
echo "Cancelled superseded Noema run ${run_id} for PR #${PR_NUMBER}."
else
echo "::warning::Could not cancel superseded Noema run ${run_id}; it may already be terminal."
fi
done <<<"$run_ids"
done
echo "Superseded Noema cleanup pass ${pass}/2 complete."
done
echo "Cancelled ${cancelled} superseded Noema run(s) after live-head validation."
- name: Select fail-closed Noema reviewer credential
if: env.PR_NUMBER != ''
id: noema_credential
env:
NOEMA_GITHUB_APP_CLIENT_ID: ${{ vars.NOEMA_GITHUB_APP_CLIENT_ID || '' }}
NOEMA_GITHUB_APP_PRIVATE_KEY: ${{ secrets.NOEMA_GITHUB_APP_PRIVATE_KEY || '' }}
TOKEN_EXCHANGE_URL: ${{ vars.NOEMA_TOKEN_EXCHANGE_URL || vars.NOEMA_EXCHANGE_URL || '' }}
NOEMA_REVIEW_TOKEN: ${{ secrets.NOEMA_REVIEW_TOKEN }}
run: |
set -euo pipefail
if [[ ! "$TARGET_REPOSITORY" =~ ^ContextualWisdomLab/[A-Za-z0-9_.-]+$ ]]; then
echo "::error::Noema target repository must belong to ContextualWisdomLab; observed ${TARGET_REPOSITORY:-<empty>}."
exit 1
fi
repository_name="${TARGET_REPOSITORY#*/}"
echo "repository=$repository_name" >>"$GITHUB_OUTPUT"
if [ -n "${NOEMA_REVIEW_TOKEN:-}" ]; then
echo "source=pat" >>"$GITHUB_OUTPUT"
echo "::notice::Noema reviewer using the NOEMA_REVIEW_TOKEN secret fallback identity."
exit 0
fi
if [ -n "${NOEMA_GITHUB_APP_CLIENT_ID:-}" ] && [ -n "${NOEMA_GITHUB_APP_PRIVATE_KEY:-}" ]; then
echo "source=github-app" >>"$GITHUB_OUTPUT"
echo "::notice::Noema reviewer will mint a repository-scoped cwl-noema-review installation token."
exit 0
fi
if [ -n "${TOKEN_EXCHANGE_URL:-}" ]; then
echo "source=oidc" >>"$GITHUB_OUTPUT"
echo "::notice::Noema reviewer will use the configured OIDC app-token exchange."
exit 0
fi
echo "::error::Noema reviewer credential is unconfigured: set NOEMA_GITHUB_APP_CLIENT_ID with NOEMA_GITHUB_APP_PRIVATE_KEY, NOEMA_REVIEW_TOKEN, or NOEMA_TOKEN_EXCHANGE_URL. Review cannot be skipped."
exit 1
- name: Mint repository-scoped Noema GitHub App token
if: env.PR_NUMBER != '' && steps.noema_credential.outputs.source == 'github-app'
id: noema_github_app_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: Exchange Noema app token through OIDC
if: env.PR_NUMBER != '' && steps.noema_credential.outputs.source == 'oidc'
id: noema_oidc_token
env:
OIDC_AUDIENCE: ${{ vars.NOEMA_OIDC_AUDIENCE || 'cwl-noema-review' }}
TOKEN_EXCHANGE_URL: ${{ vars.NOEMA_TOKEN_EXCHANGE_URL || vars.NOEMA_EXCHANGE_URL || '' }}
run: |
set -euo pipefail
fail_unavailable() {
local message="$1"
echo "::error::$message"
exit 1
}
if [ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ] || [ -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ]; then
fail_unavailable "Noema app token exchange unavailable: OIDC request environment is missing."
fi
request_url="${ACTIONS_ID_TOKEN_REQUEST_URL}"
separator="&"
case "$request_url" in
*\?*) ;;
*) separator="?" ;;
esac
if ! oidc_response="$(
curl -fsS \
-H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
"${request_url}${separator}audience=${OIDC_AUDIENCE}"
)"; then
fail_unavailable "Noema app token exchange unavailable: OIDC token request did not complete."
fi
oidc_token="$(jq -r '.value // empty' <<<"$oidc_response")"
if [ -z "$oidc_token" ]; then
fail_unavailable "Noema app token exchange unavailable: OIDC token response was empty."
fi
if ! token_response="$(
curl -fsS \
-X POST \
-H "Content-Type: application/json" \
-H "Authorization: Bearer ${oidc_token}" \
--data "$(jq -cn --arg target_repository "$TARGET_REPOSITORY" '{target_repository:$target_repository}')" \
"${TOKEN_EXCHANGE_URL}"
)"; then
fail_unavailable "Noema app token exchange unavailable: app token request did not complete."
fi
app_token="$(jq -r '.token // empty' <<<"$token_response")"
if [ -z "$app_token" ]; then
fail_unavailable "Noema app token exchange unavailable: app token response was empty."
fi
echo "::add-mask::$app_token"
echo "token=$app_token" >>"$GITHUB_OUTPUT"
- name: Validate current pull request head
if: env.PR_NUMBER != ''
env:
GH_TOKEN: ${{ secrets.NOEMA_REVIEW_TOKEN || steps.noema_github_app_token.outputs.token || steps.noema_oidc_token.outputs.token }}
run: |
set -euo pipefail
if ! [[ "$EXPECTED_HEAD_SHA" =~ ^[0-9a-fA-F]{40}$ ]]; then
echo "::error::Noema expected head must be a full commit SHA."
exit 1
fi
pull_request_json="$(gh api "repos/${TARGET_REPOSITORY}/pulls/${PR_NUMBER}")"
live_state="$(jq -r '.state // empty' <<<"$pull_request_json")"
live_head_sha="$(jq -r '.head.sha // empty' <<<"$pull_request_json")"
if [ "$live_state" != "open" ] || [ "${live_head_sha,,}" != "${EXPECTED_HEAD_SHA,,}" ]; then
printf '::error::Noema review target is closed or stale. expected head=%s; live state=%s head=%s.\n' \
"$EXPECTED_HEAD_SHA" "${live_state:-missing}" "${live_head_sha:-missing}"
exit 1
fi
- name: Resolve Noema target repository visibility
if: env.PR_NUMBER != ''
id: target_visibility
env:
GH_TOKEN: ${{ secrets.NOEMA_REVIEW_TOKEN || steps.noema_github_app_token.outputs.token || steps.noema_oidc_token.outputs.token }}
run: |
set -euo pipefail
if [ -z "${GH_TOKEN:-}" ]; then
echo "::error::Noema target visibility cannot be resolved without the selected repository-scoped reviewer token."
exit 1
fi
visibility=""
for target_visibility_attempt in 1 2 3 4 5 6; do
if visibility="$(
gh api "/repos/${TARGET_REPOSITORY}" --jq '.visibility // (if .private then "private" else "public" end)'
)"; then
break
fi
visibility=""
if [ "$target_visibility_attempt" -lt 6 ]; then
echo "Repository visibility lookup failed (attempt ${target_visibility_attempt}/6), possibly a transient GitHub API rate limit; retrying after backoff." >&2
sleep "$(( target_visibility_attempt * 5 ))"
fi
done
case "$visibility" in
private|internal)
echo "require_zdr=true" >>"$GITHUB_OUTPUT"
echo "::notice::Private/internal target requires an attested ZDR-only review pool."
;;
public)
echo "require_zdr=false" >>"$GITHUB_OUTPUT"
;;
*)
echo "::error::Noema target repository visibility is missing or unsupported: ${visibility:-<empty>}."
exit 1
;;
esac
- name: Provision contextual-orchestrator review sidecar
if: env.PR_NUMBER != ''
env:
BYTEZ_API_KEY: ${{ secrets.BYTEZ_API_KEY }}
NVIDIA_NIM_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}
NVIDIA_NIM_API_KEY_SUB: ${{ secrets.NVIDIA_NIM_API_KEY_SUB }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
CONTEXTUAL_ORCHESTRATOR_REQUIRE_ZDR: ${{ steps.target_visibility.outputs.require_zdr }}
run: |
set -euo pipefail
bash "$GITHUB_WORKSPACE/scripts/ci/contextual_orchestrator_review_sidecar.sh"
- 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' }}
NOEMA_REVIEW_ACTOR: ${{ steps.noema_github_app_token.outputs['app-slug'] && format('{0}[bot]', steps.noema_github_app_token.outputs['app-slug']) || '' }}
NOEMA_REVIEW_INSTALLATION_ID: ${{ steps.noema_github_app_token.outputs['installation-id'] }}
run: |
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 prepare a verdict."
exit 1
fi
if [ -z "${CONTEXTUAL_ORCHESTRATOR_BASE_URL:-}" ] || [ -z "${CONTEXTUAL_ORCHESTRATOR_TOKEN_FILE:-}" ]; then
echo "::error::contextual-orchestrator review sidecar must be provisioned before Noema LLM review."
exit 1
fi
source "$GITHUB_WORKSPACE/scripts/ci/load_contextual_orchestrator_token.sh"
export NOEMA_LLM_API_URL="${CONTEXTUAL_ORCHESTRATOR_BASE_URL%/}/v1/chat/completions"
export NOEMA_LLM_MODEL="orchestrator/free"
export NOEMA_LLM_API_KEY="${CONTEXTUAL_ORCHESTRATOR_TOKEN}"
export NOEMA_LLM_VIA_ORCHESTRATOR=1
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"