diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index d86497b3f4..8b42e279fb 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -185,9 +185,9 @@ jobs: printf 'Authorized repository_dispatch actor=%s sender=%s target=%s.\n' "$DISPATCH_ACTOR" "$DISPATCH_SENDER" "$TARGET_REPOSITORY" fi - if ! [[ "$TARGET_REPOSITORY" =~ ^ContextualWisdomLab/[A-Za-z0-9_.-]+$ ]] || + if ! [[ "$TARGET_REPOSITORY" =~ ^(ContextualWisdomLab/[A-Za-z0-9_.-]+|HYOSUNG-ITX-AI-Business-Department/llm-gateway-console(-design)?)$ ]] || ! [[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]]; then - printf '::error::PR metadata validation rejected a target outside ContextualWisdomLab or an invalid pull request number. target=%s pr=%s\n' "${TARGET_REPOSITORY:-}" "${PR_NUMBER:-}" + printf '::error::PR metadata validation rejected a target outside the approved consumer scope or an invalid pull request number. target=%s pr=%s\n' "${TARGET_REPOSITORY:-}" "${PR_NUMBER:-}" exit 1 fi diff --git a/.github/workflows/opencode-review.yml b/.github/workflows/opencode-review.yml index 19ea58003f..5b3bcbb5e0 100644 --- a/.github/workflows/opencode-review.yml +++ b/.github/workflows/opencode-review.yml @@ -258,7 +258,7 @@ jobs: run: | set -euo pipefail echo "admitted=false" >>"$GITHUB_OUTPUT" - if ! [[ "$TARGET_REPOSITORY" =~ ^ContextualWisdomLab/[A-Za-z0-9_.-]+$ ]] || + if ! [[ "$TARGET_REPOSITORY" =~ ^(ContextualWisdomLab/[A-Za-z0-9_.-]+|HYOSUNG-ITX-AI-Business-Department/llm-gateway-console(-design)?)$ ]] || ! [[ "$PR_NUMBER" =~ ^[1-9][0-9]*$ ]] || ! [[ "$EXPECTED_HEAD_SHA" =~ ^[0-9a-f]{40}$ ]]; then echo "::error::OpenCode admission rejected malformed pull request metadata." diff --git a/docs/doctoring/hyosung-opencode-consumer-admission.md b/docs/doctoring/hyosung-opencode-consumer-admission.md new file mode 100644 index 0000000000..8102b66ebb --- /dev/null +++ b/docs/doctoring/hyosung-opencode-consumer-admission.md @@ -0,0 +1,63 @@ +# Hyosung OpenCode intake candidate + +Status: Proposed. Base: fb17ef556f94f673234aa557254ae52779e9a7b0. + +MLLO console and its design owner cannot use the canonical OpenCode intake: +the required caller and dispatch receiver reject their organization even when +the exact repository is configured in the dispatch allowlist. Four extracted +intake tests failed on the base. This candidate admits only +`HYOSUNG-ITX-AI-Business-Department/llm-gateway-console` and +`HYOSUNG-ITX-AI-Business-Department/llm-gateway-console-design`. + +The existing dispatch actor and sender must still match the same configured +identity. An empty or missing live target allowlist remains denied. Live PR +base/head, visibility and state checks remain authoritative. OIDC exchange, +metadata access, private-target ZDR, review publication identity and separate +status permissions are unchanged. The canonical workflow continues to use its +existing CO free route; no paid route or consumer workflow copy is introduced. + +## Remaining deployment prerequisites + +The JSON target inventory is a proposed configuration delta, not evidence that +the live repository variable changed. After protected owner review and merge, +reconcile the live variable without dropping concurrent entries. This candidate +does not change any GitHub variable or installation permission. + +Before consumer adoption, establish the OIDC-exchanged App identity can read each +private target and its live PR metadata; separately verify review publication and +status-write authority. A repository name match cannot prove any of those +capabilities. Verify one exact-head dispatch per consumer, matching private +visibility and immutable source/base/head, followed by an actual formal review +and required status. Missing App access must fail closed rather than widening +installation scope or substituting an unverified credential. Current tests use +local extracted guards and do not establish cross-organization runtime access. + +The organization scheduler/mention router retain their existing CWL-only scope; +this candidate enables the required-caller/direct authenticated-dispatch path, +not an unverified cross-organization scheduler rollout. Consumer installation of +the reviewed workflow contract remains separate work. Existing edge policy also +remains active; any policy conflict requires an explicit owner decision, not a +bypass in this admission change. + +Local validation: 128 passed, 1 skipped across intake, agent contract, Rust +coverage pin, live-Draft and required-verdict regressions under +`GITHUB_ACTIONS=true`. The dispatch content hash was recomputed with +`git hash-object`; no action/version pin was substituted. Hosted verification +and actual permission evidence remain outstanding. + +## Local lint process diagnosis + +Homebrew actionlint 1.7.12 blocked before starting ShellCheck on macOS. Its +upstream `process.go` writes the entire script into `StdinPipe` before calling +`Output`; scripts exceeding the pipe capacity deadlock. PID 35499 had no child +processes and its SIGQUIT dump showed two writes blocked with 130814 and 67165 +byte inputs. The process was terminated diagnostically, not treated as success. + +Official tag v1.7.12 (914e7df21a07ef503a81201c76d2b11c789d3fca) was cloned into +an isolated temporary directory. Replacing only that pipe write with +`cmd.Stdin = strings.NewReader(e.stdin)` allowed full lint to finish; upstream +`TestProcess` tests passed. No installed binary or workflow gate was changed. +Native actionlint with ShellCheck disabled passed workflow validation. Full +repaired-tool lint remained nonzero: its output exactly matched the clean +fb17ef556 baseline, with no admission-delta findings. These existing ShellCheck +findings remain unresolved and must not be reported as full lint success. diff --git a/scripts/ci/opencode_repository_dispatch_targets.json b/scripts/ci/opencode_repository_dispatch_targets.json index dd82dd1fd0..475518a095 100644 --- a/scripts/ci/opencode_repository_dispatch_targets.json +++ b/scripts/ci/opencode_repository_dispatch_targets.json @@ -53,6 +53,8 @@ "ContextualWisdomLab/semantic-data-portal", "ContextualWisdomLab/wardnet", "ContextualWisdomLab/xtrm-lead-pi-outbound", - "ContextualWisdomLab/xtrmLLMBatchPython" + "ContextualWisdomLab/xtrmLLMBatchPython", + "HYOSUNG-ITX-AI-Business-Department/llm-gateway-console", + "HYOSUNG-ITX-AI-Business-Department/llm-gateway-console-design" ] } diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 321d25bd57..61665a9156 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -2760,7 +2760,7 @@ def test_opencode_strix_security_regressions_are_closed(): config = load_opencode_jsonc() assert " validate-pr-metadata:\n" in workflow - assert "^ContextualWisdomLab/[A-Za-z0-9_.-]+$" in workflow + assert "^(ContextualWisdomLab/[A-Za-z0-9_.-]+|HYOSUNG-ITX-AI-Business-Department/llm-gateway-console(-design)?)$" in workflow assert ( "repository_dispatch metadata does not match the live pull request" in workflow ) diff --git a/tests/test_opencode_hyosung_repository_admission.py b/tests/test_opencode_hyosung_repository_admission.py new file mode 100644 index 0000000000..8af547bc37 --- /dev/null +++ b/tests/test_opencode_hyosung_repository_admission.py @@ -0,0 +1,33 @@ +"""Execute canonical intake guards for the two exact MLLO owners.""" + +import os +from pathlib import Path +import re +import subprocess +import textwrap + +import pytest + + +@pytest.mark.parametrize("suffix", ["", "-design"]) +def test_hyosung_intake_preserves_dispatch_authority(suffix): + """Exact scope cannot replace matching dispatcher identity and live metadata.""" + target = "HYOSUNG-ITX-AI-Business-Department/llm-gateway-console" + suffix + workflow = Path(".github/workflows/opencode-review-dispatch.yml").read_text() + section = workflow.split(" - name: Bind workflow inputs to live organization pull request metadata\n", 1)[1] + program = textwrap.dedent(section.split(" run: |\n", 1)[1].split(" pull_request_json=", 1)[0]) + env = dict(os.environ, EVENT_NAME="repository_dispatch", DISPATCH_ACTOR="opencode-agent[bot]", DISPATCH_SENDER="opencode-agent[bot]", ALLOWED_DISPATCH_ACTOR="opencode-agent[bot]", ALLOWED_DISPATCH_TARGETS=target, TARGET_REPOSITORY=target, PR_NUMBER="1") + for overrides, expected in (({}, 0), ({"DISPATCH_SENDER": "forged"}, 1), ({"ALLOWED_DISPATCH_TARGETS": ""}, 1), ({"PR_NUMBER": "0"}, 1)): + result = subprocess.run(["bash", "-c", program], env={**env, **overrides}, capture_output=True, text=True) + assert result.returncode == expected, result.stdout + + +@pytest.mark.parametrize("filename", ["opencode-review.yml", "opencode-review-dispatch.yml"]) +def test_only_exact_hyosung_names_pass(filename): + """Both guards reject near names even if the external allowlist lists them.""" + source = Path(".github/workflows", filename).read_text() + pattern, = re.findall(r'"\$TARGET_REPOSITORY" =~ (\^[^ ]+)', source) + owner = "HYOSUNG-ITX-AI-Business-Department/llm-gateway-console" + for target, accepted in ((owner, True), (owner + "-design", True), (owner + "-extra", False), (owner + "-design-extra", False), (owner + "/extra", False), (owner + "\n", False), ("other/llm-gateway-console", False)): + result = subprocess.run(["bash", "-c", '[[ "$TARGET_REPOSITORY" =~ ' + pattern + ' ]]'], env=dict(os.environ, TARGET_REPOSITORY=target), capture_output=True) + assert (result.returncode == 0) == accepted diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py index 2e733ac9e9..3e69366cb1 100644 --- a/tests/test_pr_review_autofix_nvidia_nim_contract.py +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -17,7 +17,7 @@ DOCTORING_RECORD = Path("docs/doctoring/hourly-nvidia-nim-autofix.md") CHANGELOG = Path("CHANGELOG.md") REVIEW_DISPATCH_WORKFLOW = Path(".github/workflows/opencode-review-dispatch.yml") -REVIEW_DISPATCH_BLOB_SHA = "d86497b3f43bebbabbb4f504eb5132cdf3b7b293" +REVIEW_DISPATCH_BLOB_SHA = "8b42e279fb3a9890eaf80000926f403fbc2f1807" def _workflow_text(path: Path) -> str: