Skip to content
Draft
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
28 changes: 18 additions & 10 deletions .github/workflows/noema-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ on:
# Default-branch-only retry entrypoint; no caller-selected workflow ref.
repository_dispatch:
types: [noema-review]
# Organizations outside ContextualWisdomLab call this from a pull_request_target
# workflow; OIDC job_workflow_ref still names this exact file (noema ADR-0019).
workflow_call:

concurrency:
# Workflow-level admission is required: a queued run cannot reach a job-level
Expand Down Expand Up @@ -63,13 +66,15 @@ jobs:
NOEMA_GITHUB_APP_PRIVATE_KEY: ${{ secrets.NOEMA_GITHUB_APP_PRIVATE_KEY || '' }}
run: |
set -euo pipefail
if ! [[ "$TARGET_REPOSITORY" =~ ^ContextualWisdomLab/[A-Za-z0-9_.-]+$ ]] ||
if ! [[ "$TARGET_REPOSITORY" =~ ^[A-Za-z0-9][A-Za-z0-9-]{0,38}/[A-Za-z0-9_.-]+$ ]] ||
! [[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]] ||
! [[ "$EXPECTED_HEAD_SHA" =~ ^[0-9a-f]{40}$ ]]; then
echo "::error::Noema metadata credential rejected malformed target PR/head metadata."
exit 1
fi
echo "repository=${TARGET_REPOSITORY#*/}" >>"$GITHUB_OUTPUT"
# The App installation on the target owner is the consent (noema ADR-0019).
echo "owner=${TARGET_REPOSITORY%%/*}" >>"$GITHUB_OUTPUT"
if [ -n "${METADATA_TOKEN:-}" ]; then
echo "source=pat" >>"$GITHUB_OUTPUT"
elif [ -n "${NOEMA_GITHUB_APP_CLIENT_ID:-}" ] && [ -n "${NOEMA_GITHUB_APP_PRIVATE_KEY:-}" ]; then
Expand All @@ -85,7 +90,7 @@ jobs:
with:
client-id: ${{ vars.NOEMA_GITHUB_APP_CLIENT_ID }}
private-key: ${{ secrets.NOEMA_GITHUB_APP_PRIVATE_KEY }}
owner: ContextualWisdomLab
owner: ${{ steps.noema_metadata_credential.outputs.owner }}
repositories: ${{ steps.noema_metadata_credential.outputs.repository }}
permission-contents: read
permission-metadata: read
Expand All @@ -98,7 +103,7 @@ jobs:
run: |
set -euo pipefail
echo "admitted=false" >>"$GITHUB_OUTPUT"
if ! [[ "$TARGET_REPOSITORY" =~ ^ContextualWisdomLab/[A-Za-z0-9_.-]+$ ]] ||
if ! [[ "$TARGET_REPOSITORY" =~ ^[A-Za-z0-9][A-Za-z0-9-]{0,38}/[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."
Expand Down Expand Up @@ -509,13 +514,15 @@ jobs:
NOEMA_GITHUB_APP_PRIVATE_KEY: ${{ secrets.NOEMA_GITHUB_APP_PRIVATE_KEY || '' }}
run: |
set -euo pipefail
if ! [[ "$TARGET_REPOSITORY" =~ ^ContextualWisdomLab/[A-Za-z0-9_.-]+$ ]] ||
if ! [[ "$TARGET_REPOSITORY" =~ ^[A-Za-z0-9][A-Za-z0-9-]{0,38}/[A-Za-z0-9_.-]+$ ]] ||
! [[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]] ||
! [[ "$EXPECTED_HEAD_SHA" =~ ^[0-9a-f]{40}$ ]]; then
echo "::error::Noema metadata credential rejected malformed target PR/head metadata."
exit 1
fi
echo "repository=${TARGET_REPOSITORY#*/}" >>"$GITHUB_OUTPUT"
# The App installation on the target owner is the consent (noema ADR-0019).
echo "owner=${TARGET_REPOSITORY%%/*}" >>"$GITHUB_OUTPUT"
if [ -n "${METADATA_TOKEN:-}" ]; then
echo "source=pat" >>"$GITHUB_OUTPUT"
elif [ -n "${NOEMA_GITHUB_APP_CLIENT_ID:-}" ] && [ -n "${NOEMA_GITHUB_APP_PRIVATE_KEY:-}" ]; then
Expand All @@ -531,7 +538,7 @@ jobs:
with:
client-id: ${{ vars.NOEMA_GITHUB_APP_CLIENT_ID }}
private-key: ${{ secrets.NOEMA_GITHUB_APP_PRIVATE_KEY }}
owner: ContextualWisdomLab
owner: ${{ steps.noema_metadata_credential.outputs.owner }}
repositories: ${{ steps.noema_metadata_credential.outputs.repository }}
permission-contents: read
permission-metadata: read
Expand Down Expand Up @@ -642,12 +649,13 @@ jobs:
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>}."
if [[ ! "$TARGET_REPOSITORY" =~ ^[A-Za-z0-9][A-Za-z0-9-]{0,38}/[A-Za-z0-9_.-]+$ ]]; then
echo "::error::Noema target repository must be a valid owner/name; observed ${TARGET_REPOSITORY:-<empty>}."
exit 1
fi
repository_name="${TARGET_REPOSITORY#*/}"
echo "repository=$repository_name" >>"$GITHUB_OUTPUT"
echo "owner=${TARGET_REPOSITORY%%/*}" >>"$GITHUB_OUTPUT"

if [ -n "${NOEMA_REVIEW_TOKEN:-}" ]; then
echo "source=pat" >>"$GITHUB_OUTPUT"
Expand Down Expand Up @@ -677,7 +685,7 @@ jobs:
with:
client-id: ${{ vars.NOEMA_GITHUB_APP_CLIENT_ID }}
private-key: ${{ secrets.NOEMA_GITHUB_APP_PRIVATE_KEY }}
owner: ContextualWisdomLab
owner: ${{ steps.noema_credential.outputs.owner }}
repositories: ${{ steps.noema_credential.outputs.repository }}
permission-actions: read
permission-checks: read
Expand Down Expand Up @@ -965,7 +973,7 @@ jobs:
with:
client-id: ${{ vars.NOEMA_GITHUB_APP_CLIENT_ID }}
private-key: ${{ secrets.NOEMA_GITHUB_APP_PRIVATE_KEY }}
owner: ContextualWisdomLab
owner: ${{ steps.noema_credential.outputs.owner }}
repositories: ${{ steps.noema_credential.outputs.repository }}
permission-actions: read
permission-checks: read
Expand Down Expand Up @@ -1027,7 +1035,7 @@ jobs:
set -euo pipefail
if { [ "$GITHUB_REPOSITORY" != "ContextualWisdomLab/.github" ] &&
[ "$TARGET_REPOSITORY" != "$GITHUB_REPOSITORY" ]; } ||
! [[ "$TARGET_REPOSITORY" =~ ^ContextualWisdomLab/[A-Za-z0-9_.-]+$ ]] ||
! [[ "$TARGET_REPOSITORY" =~ ^[A-Za-z0-9][A-Za-z0-9-]{0,38}/[A-Za-z0-9_.-]+$ ]] ||
! [[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]] ||
! [[ "$EXPECTED_HEAD_SHA" =~ ^[0-9a-f]{40}$ ]] ||
! [[ "$EXPECTED_BASE_SHA" =~ ^[0-9a-f]{40}$ ]]; then
Expand Down
7 changes: 6 additions & 1 deletion tests/test_noema_native_metadata_credentials.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,14 @@ def test_native_metadata_selection_precedes_reads_and_preserves_pat_priority(tmp
"NOEMA_GITHUB_APP_CLIENT_ID": "synthetic-client" if source in {"app", "pat"} else "",
"NOEMA_GITHUB_APP_PRIVATE_KEY": "synthetic-key" if source in {"app", "pat"} else "",
"TOKEN_EXCHANGE_URL": "https://fixture.invalid/exchange" if source != "workflow" else ""})
if source in {"foreign", "malformed"}:
if source == "malformed":
assert result.returncode != 0
assert not output.exists()
elif source == "foreign":
# Another owner is admitted; its App installation is the consent (noema ADR-0019).
assert result.returncode == 0, result.stderr
assert "owner=OtherOwner" in output.read_text()
assert "repository=example" in output.read_text()
else:
assert result.returncode == 0, result.stderr
assert f"source={'github-app' if source == 'app' else 'workflow' if source == 'oidc' else source}" in output.read_text()
36 changes: 36 additions & 0 deletions tests/test_noema_review_installed_owner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Noema review targets any owner that installed the App, not a fixed organization."""

from pathlib import Path
import re

WORKFLOW = Path(".github/workflows/noema-review.yml")


def test_target_owner_is_not_a_fixed_organization() -> None:
workflow = WORKFLOW.read_text(encoding="utf-8")
# The workflow's own identity stays pinned; only targets are opened up.
assert "^ContextualWisdomLab/[A-Za-z0-9_.-]+$" not in workflow
assert "owner: ContextualWisdomLab" not in workflow
assert 'if trusted_repository != "ContextualWisdomLab/.github":' in workflow
pattern = re.compile(r"^[A-Za-z0-9][A-Za-z0-9-]{0,38}/[A-Za-z0-9_.-]+$")
assert "^[A-Za-z0-9][A-Za-z0-9-]{0,38}/[A-Za-z0-9_.-]+$" in workflow
assert pattern.match("HYOSUNG-ITX-AI-Business-Department/llm-gateway-console")
assert not pattern.match("-bad/owner")
assert not pattern.match("owner/with/extra")


def test_every_app_token_owner_comes_from_the_validated_target() -> None:
workflow = WORKFLOW.read_text(encoding="utf-8")
owners = re.findall(r"^\s+owner: (.+)$", workflow, re.MULTILINE)
assert owners and set(owners) <= {
"${{ steps.noema_metadata_credential.outputs.owner }}",
"${{ steps.noema_credential.outputs.owner }}",
}
assert workflow.count('echo "owner=${TARGET_REPOSITORY%%/*}" >>"$GITHUB_OUTPUT"') == 3


def test_other_organizations_can_call_the_central_review() -> None:
workflow = WORKFLOW.read_text(encoding="utf-8")
triggers = workflow.split("\nconcurrency:", 1)[0]
assert "\n workflow_call:\n" in triggers
assert "\n pull_request_target:\n" in triggers
2 changes: 1 addition & 1 deletion tests/test_noema_reviewer_token_lifetime.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def test_publication_step_uses_fresh_app_token_without_authority_fallback() -> N
refresh = _step_block(workflow, "Refresh repository-scoped Noema GitHub App token for publication")
publish = _step_block(workflow, "Publish prepared Noema verdict on the exact live head")

assert "owner: ContextualWisdomLab" in refresh
assert "owner: ${{ steps.noema_credential.outputs.owner }}" in refresh
assert "repositories: ${{ steps.noema_credential.outputs.repository }}" in refresh
assert "permission-pull-requests: write" in refresh
assert "permission-contents: read" in refresh
Expand Down
2 changes: 1 addition & 1 deletion tests/test_required_workflow_queue_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -1418,7 +1418,7 @@ def test_noema_review_mints_a_least_privilege_github_app_token() -> None:
)
assert "client-id: ${{ vars.NOEMA_GITHUB_APP_CLIENT_ID }}" in workflow
assert "private-key: ${{ secrets.NOEMA_GITHUB_APP_PRIVATE_KEY }}" in workflow
assert "owner: ContextualWisdomLab" in workflow
assert "owner: ${{ steps.noema_credential.outputs.owner }}" in workflow
assert "repositories: ${{ steps.noema_credential.outputs.repository }}" in workflow
for permission in (
"permission-actions: read",
Expand Down
Loading