Skip to content
Merged
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
3 changes: 2 additions & 1 deletion .github/workflows/audit-central-ruleset.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ name: Central Required Workflow Ruleset Audit
on:
schedule:
- cron: "11 2 * * *"
workflow_dispatch: {}
repository_dispatch:
types: [audit-central-ruleset]
push:
branches: [main]
paths:
Expand Down
36 changes: 17 additions & 19 deletions .github/workflows/cloudflare-dns.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,29 @@
# push/dispatch runs. Pull requests run offline config validation only.
# CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID
#
# Default is DRY-RUN. Set input mode=apply to actually write.
# Default is DRY-RUN. Send a default-branch repository_dispatch with
# client_payload.mode=apply to actually write.
name: Cloudflare DNS

on:
workflow_dispatch:
inputs:
mode:
description: "dry-run (default, no writes) or apply (create zones + records)"
type: choice
default: dry-run
options:
- dry-run
- apply
prune:
description: "Delete Cloudflare records not present in zones.json (destructive)"
type: boolean
default: false
repository_dispatch:
types: [cloudflare-dns]
push:
branches: [main]
paths:
- "infra/cloudflare/zones.json"
- "infra/cloudflare/reconcile.sh"
- ".github/workflows/cloudflare-dns.yml"
# PRs validate the declarative config without Cloudflare secrets. Push and
# workflow_dispatch runs perform the API-backed dry-run/apply.
# repository_dispatch runs perform the API-backed dry-run/apply from main.
pull_request:
paths:
- "infra/cloudflare/zones.json"
- "infra/cloudflare/reconcile.sh"
- ".github/workflows/cloudflare-dns.yml"

# push-triggered runs are always dry-run (safe by default);
# only an explicit workflow_dispatch with mode=apply is allowed to write.
# only an explicit repository_dispatch with mode=apply is allowed to write.
concurrency:
group: cloudflare-dns-${{ github.ref }}
cancel-in-progress: false
Expand All @@ -52,7 +42,7 @@ permissions:

jobs:
reconcile:
name: Reconcile zones (${{ github.event.inputs.mode || 'dry-run' }})
name: Reconcile zones (${{ github.event.client_payload.mode || 'dry-run' }})
runs-on: ubuntu-latest
steps:
- name: Checkout
Expand Down Expand Up @@ -84,12 +74,20 @@ jobs:
env:
CF_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CF_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
CF_MODE: ${{ github.event.inputs.mode || 'dry-run' }}
CF_PRUNE: ${{ github.event.inputs.prune || 'false' }}
CF_MODE: ${{ github.event.client_payload.mode || 'dry-run' }}
CF_PRUNE: ${{ github.event.client_payload.prune || 'false' }}
CF_CONFIG: infra/cloudflare/zones.json
CF_ALLOW_DRY_RUN_TOKEN_FAILURE: ${{ github.event_name == 'push' && 'true' || 'false' }}
run: |
set -euo pipefail
if [ "$CF_MODE" != "dry-run" ] && [ "$CF_MODE" != "apply" ]; then
echo "::error::Cloudflare mode must be exactly dry-run or apply."
exit 1
fi
if [ "$CF_PRUNE" != "true" ] && [ "$CF_PRUNE" != "false" ]; then
echo "::error::Cloudflare prune must be exactly true or false."
exit 1
fi
if [ -z "${CF_API_TOKEN}" ] || [ -z "${CF_ACCOUNT_ID}" ]; then
if [ "${CF_MODE}" = "dry-run" ] && [ "${CF_ALLOW_DRY_RUN_TOKEN_FAILURE}" = "true" ]; then
echo "::warning::Cloudflare DNS dry-run skipped: CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID secrets are unavailable to this push run. Rotate or re-scope the org secrets before manual apply."
Expand Down
131 changes: 79 additions & 52 deletions .github/workflows/noema-review.yml
Original file line number Diff line number Diff line change
@@ -1,32 +1,31 @@
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 ||
github.event.workflow_run.pull_requests[0].number || 'event' }}@${{
github.event.client_payload.pr_head_sha || github.event.pull_request.head.sha ||
github.event.workflow_run.head_sha || github.sha }}

on:
pull_request_target:
types: [opened, synchronize, reopened, ready_for_review, closed]
workflow_run:
workflows: ["Required OpenCode Review", "Strix Security Scan"]
types: [completed]
workflow_dispatch:
inputs:
pr_number:
description: Pull request number to review
required: true
type: string
target_repository:
description: Repository that owns the pull request, in owner/name form
required: false
default: ""
type: string
# Default-branch-only retry entrypoint; no caller-selected workflow ref.
repository_dispatch:
types: [noema-review]

concurrency:
group: >-
noema-review-${{
github.event_name == 'pull_request_target' && github.event.pull_request.base.repo.full_name ||
github.event_name == 'workflow_dispatch' && github.event.inputs.target_repository ||
github.event_name == 'repository_dispatch' && github.event.client_payload.target_repository ||
github.repository }}-${{ github.event_name }}-${{
github.event_name == 'pull_request_target' && format('pr-{0}', github.event.pull_request.number) ||
github.event_name == 'workflow_run' && github.event.workflow_run.pull_requests[0].number && format('pr-{0}', github.event.workflow_run.pull_requests[0].number) ||
github.event_name == 'workflow_dispatch' && github.event.inputs.pr_number ||
github.event_name == 'repository_dispatch' && github.event.client_payload.pr_number ||
github.run_id }}
cancel-in-progress: true

Expand All @@ -47,7 +46,7 @@ jobs:
name: noema-review
runs-on: ubuntu-latest
if: >-
github.event_name == 'workflow_dispatch'
github.event_name == 'repository_dispatch'
|| (
github.event_name == 'workflow_run'
&& github.event.workflow_run.conclusion != 'cancelled'
Expand All @@ -59,8 +58,8 @@ jobs:
)
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
TARGET_REPOSITORY: ${{ github.event.pull_request.base.repo.full_name || github.event.inputs.target_repository || github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number || github.event.workflow_run.pull_requests[0].number || github.event.inputs.pr_number || '' }}
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.workflow_run.pull_requests[0].number || github.event.client_payload.pr_number || '' }}
steps:
- name: Skip events without pull request context
if: env.PR_NUMBER == ''
Expand Down Expand Up @@ -136,46 +135,78 @@ jobs:
tar -xzf "$trusted_archive" -C "$GITHUB_WORKSPACE" --strip-components=1
test -f scripts/ci/noema_review_gate.py

- name: Exchange Noema app token
- name: Select fail-closed Noema reviewer credential
if: env.PR_NUMBER != ''
id: noema_app_token
id: noema_credential
env:
OIDC_AUDIENCE: ${{ vars.NOEMA_OIDC_AUDIENCE || 'cwl-noema-review' }}
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

fail_unavailable() {
local message="$1"
echo "available=false" >>"$GITHUB_OUTPUT"
echo "::error::$message"
if [[ ! "$TARGET_REPOSITORY" =~ ^ContextualWisdomLab/[A-Za-z0-9_.-]+$ ]]; then
echo "::error::Noema target repository must belong to ContextualWisdomLab; observed ${TARGET_REPOSITORY:-<empty>}."
exit 1
}

mark_unconfigured() {
local message="$1"
echo "available=false" >>"$GITHUB_OUTPUT"
echo "::notice::$message"
exit 0
}
fi
repository_name="${TARGET_REPOSITORY#*/}"
echo "repository=$repository_name" >>"$GITHUB_OUTPUT"

# PAT fallback: when a NOEMA_REVIEW_TOKEN secret is present, use it as
# the reviewer identity directly and skip the OIDC app-token exchange.
# This lets the independent second reviewer satisfy the two-reviewer
# merge rule without deploying the Noema Worker. The secret value is
# never emitted as a step output; the review step reads it from secrets.
if [ -n "${NOEMA_REVIEW_TOKEN:-}" ]; then
echo "available=true" >>"$GITHUB_OUTPUT"
echo "source=pat" >>"$GITHUB_OUTPUT"
echo "::notice::Noema reviewer using the NOEMA_REVIEW_TOKEN secret fallback identity."
exit 0
fi

if [ -z "${TOKEN_EXCHANGE_URL:-}" ]; then
mark_unconfigured "Noema app token exchange unconfigured: NOEMA_TOKEN_EXCHANGE_URL or NOEMA_EXCHANGE_URL is not configured; Noema review skipped until the exchange service is deployed."
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
Expand Down Expand Up @@ -217,32 +248,28 @@ jobs:
fi

echo "::add-mask::$app_token"
{
echo "available=true"
echo "token=$app_token"
} >>"$GITHUB_OUTPUT"
echo "token=$app_token" >>"$GITHUB_OUTPUT"

- name: Run Noema LLM review and submit verdict
if: env.PR_NUMBER != ''
env:
GH_TOKEN: ${{ secrets.NOEMA_REVIEW_TOKEN || steps.noema_app_token.outputs.token }}
NOEMA_APP_TOKEN_AVAILABLE: ${{ steps.noema_app_token.outputs.available }}
NOEMA_REVIEW_TOKEN_SOURCE: ${{ steps.noema_app_token.outputs.source == 'pat' && 'noema-review-pat' || 'noema-review-app-oidc' }}
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_LLM_API_URL: ${{ vars.NOEMA_LLM_API_URL || '' }}
NOEMA_LLM_MODEL: ${{ vars.NOEMA_LLM_MODEL || '' }}
NOEMA_LLM_API_KEY: ${{ secrets.NOEMA_LLM_API_KEY || '' }}
NOEMA_LLM_API_KEY: ${{ secrets.NOEMA_LLM_API_KEY || secrets.OPENAI_API_KEY || '' }}
run: |
set -euo pipefail
if [ -z "${PR_NUMBER:-}" ]; then
echo "No pull request number was available for this event; skipping."
exit 0
fi
if [ "${NOEMA_APP_TOKEN_AVAILABLE:-}" != "true" ]; then
echo "::notice::Noema app token exchange is not configured; review skipped until Noema is deployed."
exit 0
fi
if [ -z "${GH_TOKEN:-}" ]; then
echo "::error::Noema app token is unavailable; review cannot submit a verdict."
echo "::error::Noema reviewer credential selection succeeded but no token was minted; review cannot submit a verdict."
exit 1
fi
if [ -z "${NOEMA_LLM_API_URL:-}" ] || [ -z "${NOEMA_LLM_MODEL:-}" ] || [ -z "${NOEMA_LLM_API_KEY:-}" ]; then
echo "::error::Noema LLM is unconfigured: NOEMA_LLM_API_URL, NOEMA_LLM_MODEL, and NOEMA_LLM_API_KEY (or OPENAI_API_KEY) are required."
exit 1
fi
python3 scripts/ci/noema_review_gate.py \
Expand Down
Loading
Loading