From ea25461bd3550f5bf4f3f657663235f6f610f52e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Fri, 9 Oct 2026 14:37:47 +0900 Subject: [PATCH 1/3] fix(ci): move package inspection to isolated self-hosted runners --- .github/actionlint.yaml | 19 ++++++ .../package-description-boundary.yml | 12 +++- GOAL.md | 59 +++++++++++++++++++ .../doctoring/package-description-boundary.md | 15 +++++ tests/test_audit_org_codeql_coverage.py | 20 ++++++- tests/test_package_description_boundary.py | 14 +++++ tests/test_sandboxed_web_e2e.py | 10 ++++ 7 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 .github/actionlint.yaml create mode 100644 GOAL.md diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml new file mode 100644 index 0000000000..270cf6707b --- /dev/null +++ b/.github/actionlint.yaml @@ -0,0 +1,19 @@ +# Custom labels used by the central isolated self-hosted runner pool. +self-hosted-runner: + labels: + - cwlab-ci-isolated + - cwlab-control + +# Configuration variables in array of strings defined in your repository or +# organization. `null` means disabling configuration variables check. +# Empty array means no configuration variable is allowed. +config-variables: null + +# Configuration for file paths. The keys are glob patterns to match to file +# paths relative to the repository root. The values are the configurations for +# the file paths. Note that the path separator is always '/'. +# The following configurations are available. +# +# "ignore" is an array of regular expression patterns. Matched error messages +# are ignored. This is similar to the "-ignore" command line option. +paths: {} diff --git a/.github/workflows/package-description-boundary.yml b/.github/workflows/package-description-boundary.yml index 1bd4cc9926..8241893628 100644 --- a/.github/workflows/package-description-boundary.yml +++ b/.github/workflows/package-description-boundary.yml @@ -61,7 +61,11 @@ permissions: jobs: build-distribution: - runs-on: ubuntu-latest + # Caller-controlled build backends run only on the disposable isolated pool. + # Never fall back to a GitHub-hosted runner for untrusted package code. + runs-on: + group: CWL CI isolated + labels: [self-hosted, linux, x64, cwlab-ci-isolated] timeout-minutes: 10 steps: - name: Check out the calling repository @@ -127,7 +131,11 @@ jobs: package-description-boundary: needs: build-distribution if: ${{ always() }} - runs-on: ubuntu-latest + # The trusted inspector also stays on the isolated pool. Missing capacity + # must remain an incomplete check, never a hosted fallback or silent pass. + runs-on: + group: CWL CI isolated + labels: [self-hosted, linux, x64, cwlab-ci-isolated] timeout-minutes: 10 steps: - name: Reject a failed input producer diff --git a/GOAL.md b/GOAL.md new file mode 100644 index 0000000000..4878536aff --- /dev/null +++ b/GOAL.md @@ -0,0 +1,59 @@ +# Goal — 중앙 self-hosted CI 및 독립 AI 리뷰 복구 + +상태: 실행 중. 로컬 구현·실제 CI·리뷰·승인·병합은 별도 단계다. + +## 사용자 승인 범위 + +- 전환 가능한 CI는 ContextualWisdomLab/.github 중앙 self-hosted runner로 전환한다. +- GitHub-hosted 전용 절차가 병목이라면 그 절차를 중단한다. 비실행을 보안 PASS로 표시하지 않는다. +- Noema/OpenCode는 self-hosted runner에서 실질적인 독립 리뷰를 실행한다. 실제 통과 판정에만 작성자와 다른 GitHub App 계정의 exact-head APPROVED를 게시한다. 가상의 두 번째 인간 개발자를 요구하지 않는다. +- 모델은 auto를 사용한다. personal LiteLLM의 기존 서비스 키 custody를 재사용한다. 중복 키 발급·master/provider 키 출력·무제한 비용 승인은 하지 않는다. +- guest04는 기존 queue-wait 담당의 no-start/no-reboot hold와 원격 협력 flock 경계를 유지한다. 이 lane은 VM·runner ACL을 변경하지 않는다. + +## 현행 근거 + +| 항목 | 관측 | 다음 검증 | +|---|---|---| +| 보호 중앙 main | fetch한 7554587c2e3106a388998bcad048a3d7121de25e | 변경 전 재조회 | +| package successor | #2261 closed, #2573 open / 41e9c45407e3f72d138da2ad24cdf934aa42fbe1 | 보존 및 current-head 수용 | +| 전체 runner 전환 owner | #2565 open / 6e924f32a2dbaa0950709c6d8f1391fc1058ef43 | owner 수리·실제 runner 수용 | +| AI/서비스 키 owner | #2560 open; 기존 LLM_GATEWAY_API_KEY metadata와 auto 변수는 peer receipt이며 실질 모델 실행 증거 아님 | serialized custodian·effective key/model·consumer 검증 | +| remora | 별도 중앙 worktree의 Noema/sidecar/model/App 코드 수정 확인 | 공동 파일 덮어쓰기 금지; package scope 전달 | +| 인증 | gh auth status는 실패했으나 authenticated GraphQL viewer 및 PR 읽기는 성공 | 토큰 폐기/재발급 금지; 실제 HTTP 경계로 판정 | +| gateway | 지정 TLS endpoint의 OpenAPI 수신, version 1.104.0 | credential custody 후 실제 auto 추론 | + +## 단계와 완료 기준 + +### G1 — package runner 전환과 실제 결함 수리 + +두 package job을 CWL CI isolated 그룹과 self-hosted/linux/x64/cwlab-ci-isolated labels로 지정했다. Hosted fallback은 없다. producer와 inspector 분리, called workflow exact SHA, credential 비영속화, artifact 실패 거부는 유지한다. + +로컬 focused: 39 passed 정상 및 GITHUB_ACTIONS=true. 전체 실행: 5,350 passed, 4 skipped, 40 subtests, 1 failed. 실패는 audit CodeQL fixture가 고정 날짜를 실제 시계에 비교하는 경계다. 이전 baseline 재현은 cwd가 원 checkout이어서 clean baseline 증거가 아니며 폐기하지 않고 이 한계를 보존한다. 실제 baseline cwd에서 재현한 뒤 deterministic clock fixture로 수리한다. + +Actionlint 1.7.12는 GitHub.com 공식 job.workflow_repository/sha를 모른다. 기존 ignore는 삭제한다. parser/schema 미지원은 미통과로 남긴다. custom labels 선언은 유지한다. cross-repo 실제 checkout은 별도 미수행이다. + +### G2 — 전체 중앙 runner 정책 수용 + +#2565가 전체 source routing을 소유한다. 이 lane은 누락 package job만 소유한다. owner의 source 전환은 완료 증거가 아니다. 모든 workflow source와 live registry 및 dynamic hosted producer를 각각 조사한다. 불가피한 hosted-only 중단은 lost evidence와 required consumer 영향을 명시한다. missing/offline runner를 hosted-only로 분류하지 않는다. + +완료: 실제 eligible self-hosted job assignment, 격리/cleanup canary, 현재 source의 terminal required Checks. ACL·VM 변경은 해당 운영 담당과 협력한다. + +## 최신 로컬 검증 + +실제 clean baseline cwd에서 audit 고정 날짜 fixture 실패를 재현했다. CLI fixture는 기준 시계를 고정하고 default-clock positive는 현재 analysis timestamp를 생성하도록 수리했다. 승인 기준이나 production freshness 상한은 변경하지 않았다. + +scratch가 home 아래에 있으므로 sandbox 테스트 5건이 실제 host home 차단 조건과 섞여 실패했다. 이 5건은 synthetic guest home을 명시해 launcher/path-traversal을 독립 검증하도록 보강했다. Production home-denial은 그대로다. + +최종 CI-mode 전체 실행: 5,351 passed / 4 skipped / 40 subtests, exit 0, 343.51초. HTTPError cleanup ResourceWarning은 출력에 남는다. Actionlint의 schema 오류는 suppress하지 않는다. 변경 파일은 아직 미commit/미push이며 독립 리뷰 및 current-head 원격 수용이 남는다. + +### G3 — LiteLLM auto 독립 AI 리뷰 및 조건부 승인 + +#2560 기존 transport/key 담당과 remora shared model/App 담당을 재사용한다. 전달 요청은 합의/실행 증거가 아니다. key를 chat/log/artifact로 출력하지 않는다. provider fallback·private retention 제한을 우회하지 않는다. + +완료: effective 서비스 키의 auto 허용 검증, 실제 authenticated auto 호출, Noema/OpenCode 각각 substantive verdict, exact head/base 재확인, author-distinct App formal approval, resolved threads와 required Checks 후 ordinary protected merge. 자신 계정 승인·synthetic status·admin bypass 금지. + +## 소유 파일 및 다음 실행 + +이 worktree는 package workflow/test/doctoring, .github/actionlint.yaml, GOAL.md를 소유한다. audit time fixture는 root-cause 수리 후보로 baseline 재현 후 최소 수정한다. shared Noema/OpenCode/model/relay와 다른 worktree에는 쓰지 않는다. + +다음: 시계 fixture baseline 재현·수리, actionlint ignore 제거, focused 및 전체 수용 재실행, owner와 credential/AI integration 인계 확인. 기존 목표 전체는 G2/G3 완료까지 유지한다. 로컬 PASS를 원격 승인/병합으로 보고하지 않는다. diff --git a/docs/doctoring/package-description-boundary.md b/docs/doctoring/package-description-boundary.md index 1121809d96..fb6f52296f 100644 --- a/docs/doctoring/package-description-boundary.md +++ b/docs/doctoring/package-description-boundary.md @@ -167,3 +167,18 @@ metadata descriptions must be identical or the gate fails closed. Published Mark links using GitHub's `blob/main`, `tree/main`, `master`, or `develop` forms are blocking `mutable-release-link` findings; release tags and exact commits remain valid. + +## Runner boundary + +Both jobs in `.github/workflows/package-description-boundary.yml` now require +`CWL CI isolated` with `self-hosted`, `linux`, `x64`, and +`cwlab-ci-isolated` labels. The producer executes caller-controlled packaging +code; the inspector executes the trusted central gate. Neither job has a +GitHub-hosted fallback. Missing isolated capacity therefore remains incomplete +hosted evidence and cannot become a silent pass. + +This is a package-gate slice of the broader all-workflow migration owned by +`ContextualWisdomLab/.github#2565`. It does not provision runners, widen group +access, or prove disposable cleanup. A real caller can be accepted only after +operator-provided group access, isolated capacity, cleanup canary, and fresh +exact-head terminal checks. diff --git a/tests/test_audit_org_codeql_coverage.py b/tests/test_audit_org_codeql_coverage.py index 0ffc8fa749..194540ad53 100644 --- a/tests/test_audit_org_codeql_coverage.py +++ b/tests/test_audit_org_codeql_coverage.py @@ -263,9 +263,13 @@ def test_null_latest_analysis_is_not_coverage() -> None: def test_audit_codeql_coverage_defaults_now_to_current_time() -> None: - repositories = [covered_by_recent_analysis("DefaultNowRepo", days_ago=0)] + """Exercise the real default clock with a genuinely current analysis.""" + repository = covered_by_recent_analysis("DefaultNowRepo", days_ago=0) + repository["latest_codeql_analysis"]["created_at"] = datetime.now( + timezone.utc + ).isoformat() - assert audit.audit_codeql_coverage(repositories) == [] + assert audit.audit_codeql_coverage([repository]) == [] def test_load_payload_reads_from_stdin(monkeypatch) -> None: @@ -299,7 +303,17 @@ def test_main_fail_path_reports_gaps_from_stdin(monkeypatch, capsys) -> None: assert "FAIL: 1 repositories have no CodeQL coverage" in captured.err -def test_main_pass_path_reports_from_file_arg(tmp_path, capsys) -> None: +def test_main_pass_path_reports_from_file_arg(tmp_path, capsys, monkeypatch) -> None: + """Keep the CLI freshness witness bound to the fixture's reference clock.""" + class FixedDatetime(datetime): + """Supply the same reference instant used by the analysis fixture.""" + + @classmethod + def now(cls, tz=None): + """Return the fixture clock in the caller's requested timezone.""" + return NOW.astimezone(tz) if tz is not None else NOW.replace(tzinfo=None) + + monkeypatch.setattr(audit, "datetime", FixedDatetime) payload_path = tmp_path / "repositories.json" payload_path.write_text( json.dumps([covered_by_default_setup("ELUNVERA"), covered_by_recent_analysis("Orgmetra")]), diff --git a/tests/test_package_description_boundary.py b/tests/test_package_description_boundary.py index f2adf26a3b..78bb267712 100644 --- a/tests/test_package_description_boundary.py +++ b/tests/test_package_description_boundary.py @@ -341,6 +341,20 @@ def test_workflow_is_callable_only() -> None: assert set(_workflow()[True]) == {"workflow_call"} +def test_workflow_runner_is_central_isolated_self_hosted() -> None: + """Both jobs reject GitHub-hosted fallback and use the isolated pool.""" + jobs = _workflow()["jobs"] + expected = { + "group": "CWL CI isolated", + "labels": ["self-hosted", "linux", "x64", "cwlab-ci-isolated"], + } + assert jobs["build-distribution"]["runs-on"] == expected + assert jobs["package-description-boundary"]["runs-on"] == expected + text = _WORKFLOW.read_text(encoding="utf-8") + assert "ubuntu-latest" not in text + assert "ubuntu-24.04" not in text + + def test_workflow_checks_out_the_gate_at_its_own_commit() -> None: """The caller must run the gate revision it pinned, not whatever main holds.""" steps = _workflow()["jobs"]["package-description-boundary"]["steps"] diff --git a/tests/test_sandboxed_web_e2e.py b/tests/test_sandboxed_web_e2e.py index 1b1cdf3722..e43aed95e0 100644 --- a/tests/test_sandboxed_web_e2e.py +++ b/tests/test_sandboxed_web_e2e.py @@ -1581,6 +1581,8 @@ def test_isolated_command_resolves_repo_local_launcher_against_sandboxed_cwd(mon resolution instead goes through the sandboxed-``cwd``-relative path this fix adds, not a PATH search. """ + # Model the guest home independently of the host scratch directory. + monkeypatch.setattr(Path, "home", lambda: tmp_path / "synthetic-home") monkeypatch.setattr(sandboxed_web_e2e.shutil, "which", lambda *_args, **_kwargs: None) sandbox = tmp_path / "sandbox" repo = sandbox / "repo" @@ -1613,6 +1615,8 @@ def test_isolated_command_resolves_bare_command_via_relative_path_entry(monkeypa ``shutil.which`` to ``None`` here proves resolution instead falls through to the cwd-anchored ``PATH`` search this fix adds. """ + # Model the guest home independently of the host scratch directory. + monkeypatch.setattr(Path, "home", lambda: tmp_path / "synthetic-home") monkeypatch.setattr(sandboxed_web_e2e.shutil, "which", lambda *_args, **_kwargs: None) sandbox = tmp_path / "sandbox" repo = sandbox / "repo" @@ -1646,6 +1650,8 @@ def test_isolated_command_translates_absolute_workspace_launcher_to_sandbox_moun that does not exist inside the sandbox, where only ``SANDBOX_MOUNT`` is bound, so the command would fail to launch there unchanged. """ + # Model the guest home independently of the host scratch directory. + monkeypatch.setattr(Path, "home", lambda: tmp_path / "synthetic-home") monkeypatch.setattr(sandboxed_web_e2e.shutil, "which", lambda *_args, **_kwargs: None) sandbox = tmp_path / "sandbox" repo = sandbox / "repo" @@ -1706,6 +1712,8 @@ def test_isolated_command_rejects_relative_path_entry_escaping_sandbox(monkeypat def test_isolated_command_rejects_repo_local_path_traversal(monkeypatch, tmp_path): """A repo-local launcher path that lexically escapes the sandbox root is still rejected.""" + # Model the guest home independently of the host scratch directory. + monkeypatch.setattr(Path, "home", lambda: tmp_path / "synthetic-home") monkeypatch.setattr(sandboxed_web_e2e.shutil, "which", lambda *_args, **_kwargs: None) sandbox = tmp_path / "sandbox" repo = sandbox / "repo" @@ -1726,6 +1734,8 @@ def test_isolated_command_rejects_repo_local_path_traversal(monkeypatch, tmp_pat def test_isolated_command_rejects_explicit_external_path(monkeypatch, tmp_path): """An explicit absolute path outside the workspace and bind roots is still rejected.""" + # Model the guest home independently of the host scratch directory. + monkeypatch.setattr(Path, "home", lambda: tmp_path / "synthetic-home") monkeypatch.setattr(sandboxed_web_e2e.shutil, "which", lambda *_args, **_kwargs: None) sandbox = tmp_path / "sandbox" repo = sandbox / "repo" From d2c96e687f55a194bcfe5ac3db647f47684fce25 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 13 Oct 2026 13:41:14 +0900 Subject: [PATCH 2/3] chore(ci): align package slice with main runner vocabulary Drop the session GOAL.md and restore .github/actionlint.yaml to main's bytes; main already declares cwlab-ci-isolated and cwlab-control. Full suite under GITHUB_ACTIONS=true: 5351 passed, 4 skipped, 40 subtests. --- .github/actionlint.yaml | 16 +---------- GOAL.md | 59 ----------------------------------------- 2 files changed, 1 insertion(+), 74 deletions(-) delete mode 100644 GOAL.md diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index 270cf6707b..dbd43187c2 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -1,19 +1,5 @@ -# Custom labels used by the central isolated self-hosted runner pool. +# Source validation only; labels do not attest runtime isolation. self-hosted-runner: labels: - cwlab-ci-isolated - cwlab-control - -# Configuration variables in array of strings defined in your repository or -# organization. `null` means disabling configuration variables check. -# Empty array means no configuration variable is allowed. -config-variables: null - -# Configuration for file paths. The keys are glob patterns to match to file -# paths relative to the repository root. The values are the configurations for -# the file paths. Note that the path separator is always '/'. -# The following configurations are available. -# -# "ignore" is an array of regular expression patterns. Matched error messages -# are ignored. This is similar to the "-ignore" command line option. -paths: {} diff --git a/GOAL.md b/GOAL.md deleted file mode 100644 index 4878536aff..0000000000 --- a/GOAL.md +++ /dev/null @@ -1,59 +0,0 @@ -# Goal — 중앙 self-hosted CI 및 독립 AI 리뷰 복구 - -상태: 실행 중. 로컬 구현·실제 CI·리뷰·승인·병합은 별도 단계다. - -## 사용자 승인 범위 - -- 전환 가능한 CI는 ContextualWisdomLab/.github 중앙 self-hosted runner로 전환한다. -- GitHub-hosted 전용 절차가 병목이라면 그 절차를 중단한다. 비실행을 보안 PASS로 표시하지 않는다. -- Noema/OpenCode는 self-hosted runner에서 실질적인 독립 리뷰를 실행한다. 실제 통과 판정에만 작성자와 다른 GitHub App 계정의 exact-head APPROVED를 게시한다. 가상의 두 번째 인간 개발자를 요구하지 않는다. -- 모델은 auto를 사용한다. personal LiteLLM의 기존 서비스 키 custody를 재사용한다. 중복 키 발급·master/provider 키 출력·무제한 비용 승인은 하지 않는다. -- guest04는 기존 queue-wait 담당의 no-start/no-reboot hold와 원격 협력 flock 경계를 유지한다. 이 lane은 VM·runner ACL을 변경하지 않는다. - -## 현행 근거 - -| 항목 | 관측 | 다음 검증 | -|---|---|---| -| 보호 중앙 main | fetch한 7554587c2e3106a388998bcad048a3d7121de25e | 변경 전 재조회 | -| package successor | #2261 closed, #2573 open / 41e9c45407e3f72d138da2ad24cdf934aa42fbe1 | 보존 및 current-head 수용 | -| 전체 runner 전환 owner | #2565 open / 6e924f32a2dbaa0950709c6d8f1391fc1058ef43 | owner 수리·실제 runner 수용 | -| AI/서비스 키 owner | #2560 open; 기존 LLM_GATEWAY_API_KEY metadata와 auto 변수는 peer receipt이며 실질 모델 실행 증거 아님 | serialized custodian·effective key/model·consumer 검증 | -| remora | 별도 중앙 worktree의 Noema/sidecar/model/App 코드 수정 확인 | 공동 파일 덮어쓰기 금지; package scope 전달 | -| 인증 | gh auth status는 실패했으나 authenticated GraphQL viewer 및 PR 읽기는 성공 | 토큰 폐기/재발급 금지; 실제 HTTP 경계로 판정 | -| gateway | 지정 TLS endpoint의 OpenAPI 수신, version 1.104.0 | credential custody 후 실제 auto 추론 | - -## 단계와 완료 기준 - -### G1 — package runner 전환과 실제 결함 수리 - -두 package job을 CWL CI isolated 그룹과 self-hosted/linux/x64/cwlab-ci-isolated labels로 지정했다. Hosted fallback은 없다. producer와 inspector 분리, called workflow exact SHA, credential 비영속화, artifact 실패 거부는 유지한다. - -로컬 focused: 39 passed 정상 및 GITHUB_ACTIONS=true. 전체 실행: 5,350 passed, 4 skipped, 40 subtests, 1 failed. 실패는 audit CodeQL fixture가 고정 날짜를 실제 시계에 비교하는 경계다. 이전 baseline 재현은 cwd가 원 checkout이어서 clean baseline 증거가 아니며 폐기하지 않고 이 한계를 보존한다. 실제 baseline cwd에서 재현한 뒤 deterministic clock fixture로 수리한다. - -Actionlint 1.7.12는 GitHub.com 공식 job.workflow_repository/sha를 모른다. 기존 ignore는 삭제한다. parser/schema 미지원은 미통과로 남긴다. custom labels 선언은 유지한다. cross-repo 실제 checkout은 별도 미수행이다. - -### G2 — 전체 중앙 runner 정책 수용 - -#2565가 전체 source routing을 소유한다. 이 lane은 누락 package job만 소유한다. owner의 source 전환은 완료 증거가 아니다. 모든 workflow source와 live registry 및 dynamic hosted producer를 각각 조사한다. 불가피한 hosted-only 중단은 lost evidence와 required consumer 영향을 명시한다. missing/offline runner를 hosted-only로 분류하지 않는다. - -완료: 실제 eligible self-hosted job assignment, 격리/cleanup canary, 현재 source의 terminal required Checks. ACL·VM 변경은 해당 운영 담당과 협력한다. - -## 최신 로컬 검증 - -실제 clean baseline cwd에서 audit 고정 날짜 fixture 실패를 재현했다. CLI fixture는 기준 시계를 고정하고 default-clock positive는 현재 analysis timestamp를 생성하도록 수리했다. 승인 기준이나 production freshness 상한은 변경하지 않았다. - -scratch가 home 아래에 있으므로 sandbox 테스트 5건이 실제 host home 차단 조건과 섞여 실패했다. 이 5건은 synthetic guest home을 명시해 launcher/path-traversal을 독립 검증하도록 보강했다. Production home-denial은 그대로다. - -최종 CI-mode 전체 실행: 5,351 passed / 4 skipped / 40 subtests, exit 0, 343.51초. HTTPError cleanup ResourceWarning은 출력에 남는다. Actionlint의 schema 오류는 suppress하지 않는다. 변경 파일은 아직 미commit/미push이며 독립 리뷰 및 current-head 원격 수용이 남는다. - -### G3 — LiteLLM auto 독립 AI 리뷰 및 조건부 승인 - -#2560 기존 transport/key 담당과 remora shared model/App 담당을 재사용한다. 전달 요청은 합의/실행 증거가 아니다. key를 chat/log/artifact로 출력하지 않는다. provider fallback·private retention 제한을 우회하지 않는다. - -완료: effective 서비스 키의 auto 허용 검증, 실제 authenticated auto 호출, Noema/OpenCode 각각 substantive verdict, exact head/base 재확인, author-distinct App formal approval, resolved threads와 required Checks 후 ordinary protected merge. 자신 계정 승인·synthetic status·admin bypass 금지. - -## 소유 파일 및 다음 실행 - -이 worktree는 package workflow/test/doctoring, .github/actionlint.yaml, GOAL.md를 소유한다. audit time fixture는 root-cause 수리 후보로 baseline 재현 후 최소 수정한다. shared Noema/OpenCode/model/relay와 다른 worktree에는 쓰지 않는다. - -다음: 시계 fixture baseline 재현·수리, actionlint ignore 제거, focused 및 전체 수용 재실행, owner와 credential/AI integration 인계 확인. 기존 목표 전체는 G2/G3 완료까지 유지한다. 로컬 PASS를 원격 승인/병합으로 보고하지 않는다. From 51711760d1644acf5d866c9b4c7fd6064101cc4f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 13 Oct 2026 21:09:21 +0900 Subject: [PATCH 3/3] fix(ci): carry accepted CodeQL self-hosted routing into package stack Reuse protected-main CodeQL workflow bytes to eliminate hosted fallback on the package PR stack. Preserve exact trusted-main control admission and route PR revisions only to CWL CI isolated. Align all three runner contract tests. CI-mode full suite: 5351 passed, 4 skipped, 40 subtests; focused both modes: 46 passed. Independent source and test delta reviews approved, not formal App approvals. --- .github/workflows/codeql-pr.yml | 12 +++++++++--- tests/test_codeql_pr_workflow_contract.py | 9 ++++++--- ...est_required_review_runner_image_contract.py | 17 ++++++++++++----- ...and_codeql_dispatch_runner_image_contract.py | 6 +++--- 4 files changed, 30 insertions(+), 14 deletions(-) diff --git a/.github/workflows/codeql-pr.yml b/.github/workflows/codeql-pr.yml index 356244f7fc..1be7ae0449 100644 --- a/.github/workflows/codeql-pr.yml +++ b/.github/workflows/codeql-pr.yml @@ -60,7 +60,9 @@ jobs: name: Detect CodeQL languages # Draft PRs get no runner; ready_for_review re-runs this on the same head. if: (github.event.action != 'closed') && (github.event.pull_request.draft != true || github.event.action == 'converted_to_draft' || github.event.action == 'closed') - runs-on: ${{ fromJSON(github.workflow_ref == 'ContextualWisdomLab/.github/.github/workflows/codeql-pr.yml@refs/heads/main' && '{"group":"CWL central control","labels":["self-hosted","linux","x64"]}' || '"ubuntu-24.04"') }} + runs-on: + group: ${{ (fromJSON(github.workflow_ref == 'ContextualWisdomLab/.github/.github/workflows/codeql-pr.yml@refs/heads/main' && '{"group":"CWL central control","labels":["self-hosted","linux","x64"]}' || '{"group":"CWL CI isolated","labels":["self-hosted","linux","x64","cwlab-ci-isolated"]}')).group }} + labels: ${{ (fromJSON(github.workflow_ref == 'ContextualWisdomLab/.github/.github/workflows/codeql-pr.yml@refs/heads/main' && '{"group":"CWL central control","labels":["self-hosted","linux","x64"]}' || '{"group":"CWL CI isolated","labels":["self-hosted","linux","x64","cwlab-ci-isolated"]}')).labels }} timeout-minutes: 5 permissions: contents: read @@ -164,7 +166,9 @@ jobs: # dependency exactly; the only case where it's genuinely skipped is a # closed PR, where this job being implicitly skipped too is fine because # closed PRs need no required check. - runs-on: ${{ fromJSON(github.workflow_ref == 'ContextualWisdomLab/.github/.github/workflows/codeql-pr.yml@refs/heads/main' && '{"group":"CWL central control","labels":["self-hosted","linux","x64"]}' || '"ubuntu-24.04"') }} + runs-on: + group: ${{ (fromJSON(github.workflow_ref == 'ContextualWisdomLab/.github/.github/workflows/codeql-pr.yml@refs/heads/main' && '{"group":"CWL central control","labels":["self-hosted","linux","x64"]}' || '{"group":"CWL CI isolated","labels":["self-hosted","linux","x64","cwlab-ci-isolated"]}')).group }} + labels: ${{ (fromJSON(github.workflow_ref == 'ContextualWisdomLab/.github/.github/workflows/codeql-pr.yml@refs/heads/main' && '{"group":"CWL central control","labels":["self-hosted","linux","x64"]}' || '{"group":"CWL CI isolated","labels":["self-hosted","linux","x64","cwlab-ci-isolated"]}')).labels }} # Verdict reads have exceeded five minutes; retain the ten-minute control budget. timeout-minutes: 10 permissions: @@ -367,7 +371,9 @@ jobs: && github.event.pull_request.state != 'closed' && needs.detect-languages.result == 'success' && needs.detect-languages.outputs.code == 'true' - runs-on: ${{ fromJSON(github.workflow_ref == 'ContextualWisdomLab/.github/.github/workflows/codeql-pr.yml@refs/heads/main' && '{"group":"CWL central control","labels":["self-hosted","linux","x64"]}' || '"ubuntu-24.04"') }} + runs-on: + group: ${{ (fromJSON(github.workflow_ref == 'ContextualWisdomLab/.github/.github/workflows/codeql-pr.yml@refs/heads/main' && '{"group":"CWL central control","labels":["self-hosted","linux","x64"]}' || '{"group":"CWL CI isolated","labels":["self-hosted","linux","x64","cwlab-ci-isolated"]}')).group }} + labels: ${{ (fromJSON(github.workflow_ref == 'ContextualWisdomLab/.github/.github/workflows/codeql-pr.yml@refs/heads/main' && '{"group":"CWL central control","labels":["self-hosted","linux","x64"]}' || '{"group":"CWL CI isolated","labels":["self-hosted","linux","x64","cwlab-ci-isolated"]}')).labels }} timeout-minutes: 5 permissions: contents: read diff --git a/tests/test_codeql_pr_workflow_contract.py b/tests/test_codeql_pr_workflow_contract.py index 82569e89c7..c19cac190f 100644 --- a/tests/test_codeql_pr_workflow_contract.py +++ b/tests/test_codeql_pr_workflow_contract.py @@ -1147,10 +1147,10 @@ def test_codeql_moved_head_requires_forward_ancestry(tmp_path: Path) -> None: assert enforce.returncode != 0 -def test_codeql_control_routing_keeps_pr_workflows_hosted() -> None: +def test_codeql_control_routing_keeps_pr_workflows_isolated() -> None: """Only the trusted main revision may request the restricted control group.""" workflow = WORKFLOW_PATH.read_text(encoding="utf-8") - selectors = [line.strip() for line in workflow.splitlines() if line.strip().startswith("runs-on:")] + selectors = [line.strip() for line in workflow.splitlines() if line.startswith(" group:")] trusted = "ContextualWisdomLab/.github/.github/workflows/codeql-pr.yml@refs/heads/main" assert len(selectors) == 3 for selector in selectors: @@ -1158,7 +1158,10 @@ def test_codeql_control_routing_keeps_pr_workflows_hosted() -> None: choices = re.findall(r"'([^']*)'", selector) assert choices[0] == trusted assert json.loads(choices[1]) == {"group": "CWL central control", "labels": ["self-hosted", "linux", "x64"]} - assert json.loads(choices[2]) == "ubuntu-24.04" + assert json.loads(choices[2]) == { + "group": "CWL CI isolated", + "labels": ["self-hosted", "linux", "x64", "cwlab-ci-isolated"], + } def test_codeql_pr_rejects_invalid_required_run_time(tmp_path: Path) -> None: diff --git a/tests/test_required_review_runner_image_contract.py b/tests/test_required_review_runner_image_contract.py index ab7ede7aa4..b299244ad7 100644 --- a/tests/test_required_review_runner_image_contract.py +++ b/tests/test_required_review_runner_image_contract.py @@ -95,10 +95,17 @@ def test_opencode_review_dispatch_uses_explicit_supported_image(self) -> None: unittest.main() -def test_codeql_pr_routes_trusted_main_to_control_and_pr_revisions_to_hosted() -> None: - """Separate short metadata work from model work without granting PR runner access.""" +def test_codeql_pr_routes_trusted_main_to_control_and_pr_revisions_to_isolated() -> None: + """Keep trusted workflow control separate from isolated PR execution.""" workflow = Path(".github/workflows/codeql-pr.yml").read_text() - assert workflow.count('"group":"CWL central control"') == 3 - assert workflow.count("github.workflow_ref == 'ContextualWisdomLab/.github/.github/workflows/codeql-pr.yml@refs/heads/main'") == 3 - assert workflow.count("|| '\"ubuntu-24.04\"'") == 3 + trusted = "github.workflow_ref == 'ContextualWisdomLab/.github/.github/workflows/codeql-pr.yml@refs/heads/main'" + selectors = [line for line in workflow.splitlines() if line.startswith(" group:")] + labels = [line for line in workflow.splitlines() if line.startswith(" labels:")] + assert len(selectors) == len(labels) == 3 + for selector, label in zip(selectors, labels, strict=True): + assert trusted in selector and trusted in label + assert '"group":"CWL central control"' in selector + assert '"group":"CWL CI isolated"' in selector + assert '"cwlab-ci-isolated"' in selector and '"cwlab-ci-isolated"' in label + assert '"ubuntu-24.04"' not in selector + label assert '"group":"CWL MCP remediation"' not in workflow diff --git a/tests/test_scheduler_and_codeql_dispatch_runner_image_contract.py b/tests/test_scheduler_and_codeql_dispatch_runner_image_contract.py index 42efce621e..d6cd65c9de 100644 --- a/tests/test_scheduler_and_codeql_dispatch_runner_image_contract.py +++ b/tests/test_scheduler_and_codeql_dispatch_runner_image_contract.py @@ -49,12 +49,12 @@ def test_hourly_review_repair_uses_explicit_supported_image(self) -> None: self.assertIn("labels: [self-hosted, linux, x64]", workflow) def test_codeql_pr_uses_explicit_supported_image(self) -> None: - """Require trusted-main control routing and Ubuntu fallback for all three jobs.""" + """Require trusted-main control routing and isolated fallback for all three jobs.""" workflow = CODEQL_PR.read_text(encoding="utf-8") self.assertNotIn("runs-on: ubuntu-latest", workflow) selectors = [ line.strip() for line in workflow.splitlines() - if line.strip().startswith("runs-on:") + if line.startswith(" group:") ] self.assertEqual(len(selectors), 3) for selector in selectors: @@ -64,7 +64,7 @@ def test_codeql_pr_uses_explicit_supported_image(self) -> None: ) self.assertIn('"group":"CWL central control"', selector) self.assertIn('"labels":["self-hosted","linux","x64"]', selector) - self.assertIn("|| '\"ubuntu-24.04\"'", selector) + self.assertIn("|| '{\"group\":\"CWL CI isolated\",\"labels\":[\"self-hosted\",\"linux\",\"x64\",\"cwlab-ci-isolated\"]}'", selector) def test_codeql_scan_dispatch_uses_explicit_supported_image(self) -> None: """Require validation, scan, and attempt wake jobs in the dedicated group."""