Repository navigation
ci: centralize law-agent quality on dedicated self-hosted runners #2581
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
seonghobae
merged 12 commits into
fix/sdp-ghas-analyses-pagination-20261002
from
feat/law-agent-central-ci
Oct 12, 2026
Merged
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
9e482e1
[verified] ci: centralize law-agent quality on isolated self-hosted r…
seonghobae 67bf8de
[verified] test: remove Hermes-home dependency from central law CI fi…
seonghobae 7558db6
fix: preserve law CI scratch on inconclusive PostgreSQL cleanup
seonghobae 8ba032a
test(codeql): keep current-time fixtures fresh
seonghobae 83cb10f
test(law-ci): reject symlinked cluster cleanup boundaries
seonghobae 70e701b
fix(law-ci): retain symlinked cluster evidence
seonghobae fcb997e
Merge protected main into law-agent central CI
seonghobae 431c575
merge: stack law-agent quality after runner-policy and fixture owners
seonghobae 5886df8
fix(ci): bind law migrations to source and installed checksums
seonghobae 8dff053
fix(ci): reject linked law migration ancestors before import
seonghobae a55eb2a
ci(law): reconcile reviewed law lane with current self-hosted base
seonghobae f31d7e6
fix(law): preserve spaced PostgreSQL socket paths
seonghobae File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| FROM ghcr.io/actions/actions-runner@sha256:e5496277be5d09bc968b3d64911b74e219ac4a3f2edce956a3ecf9271bea1ef4 | ||
| USER root | ||
| RUN apt-get update \ | ||
| && apt-get install -y --no-install-recommends postgresql-16 postgresql-client-16 libpq-dev python3-venv ca-certificates git curl \ | ||
| && apt-get clean \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
| USER runner | ||
| WORKDIR /home/runner |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| name: Law AI Agent central CI | ||
|
|
||
| on: | ||
| workflow_call: | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| concurrency: | ||
| group: law-ci-${{ github.workflow }}-${{ github.repository }}-${{ github.event.pull_request.number || format('{0}-{1}', github.event_name, github.ref) }} | ||
| cancel-in-progress: true | ||
|
|
||
| jobs: | ||
| quality: | ||
| runs-on: | ||
| group: CWL law CI | ||
| labels: [self-hosted, Linux, X64, law-ai-agent-ci] | ||
| timeout-minutes: 20 | ||
| strategy: | ||
| fail-fast: false | ||
| max-parallel: 1 | ||
| matrix: | ||
| python-version: ["3.12", "3.14"] | ||
| steps: | ||
| - name: Admit exact owned caller before checkout | ||
| id: admission | ||
| shell: bash | ||
| env: | ||
| CALLER_REPOSITORY: ${{ github.repository }} | ||
| CALLER_EVENT: ${{ github.event_name }} | ||
| CALLER_REF: ${{ github.ref }} | ||
| SOURCE_SHA: ${{ github.event.pull_request.head.sha || github.sha }} | ||
| CENTRAL_REPOSITORY: ${{ job.workflow_repository }} | ||
| CENTRAL_REF: ${{ job.workflow_ref }} | ||
| CENTRAL_SHA: ${{ job.workflow_sha }} | ||
| run: | | ||
| set -euo pipefail | ||
| python3 - <<'PY' | ||
| """Fail closed before checking out consumer or central source.""" | ||
| import json | ||
| import os | ||
| import re | ||
| import sys | ||
| from pathlib import Path | ||
| e = os.environ | ||
| owned = "ContextualWisdomLab/law-ai-agent" | ||
| valid = e["CALLER_REPOSITORY"] == owned | ||
| valid &= e["CENTRAL_REPOSITORY"] == "ContextualWisdomLab/.github" | ||
| valid &= bool(re.fullmatch(r"[0-9a-f]{40}", e["SOURCE_SHA"])) | ||
| valid &= bool(re.fullmatch(r"[0-9a-f]{40}", e["CENTRAL_SHA"])) | ||
| valid &= e["CENTRAL_REF"] == ( | ||
| "ContextualWisdomLab/.github/.github/workflows/law-ai-agent-ci.yml@" | ||
| + e["CENTRAL_SHA"] | ||
| ) | ||
| payload = json.loads(Path(e["GITHUB_EVENT_PATH"]).read_text()) | ||
| event = e["CALLER_EVENT"] | ||
| if event == "pull_request": | ||
| pr = payload.get("pull_request", {}) | ||
| head, base = pr.get("head", {}), pr.get("base", {}) | ||
| valid &= head.get("repo", {}).get("full_name") == owned | ||
| valid &= base.get("repo", {}).get("full_name") == owned | ||
| valid &= head.get("sha") == e["SOURCE_SHA"] | ||
| valid &= base.get("ref") == "main" | ||
| elif event == "push": | ||
| valid &= e["CALLER_REF"] == "refs/heads/main" | ||
| valid &= payload.get("ref") == "refs/heads/main" | ||
| valid &= payload.get("after") == e["SOURCE_SHA"] | ||
| valid &= payload.get("deleted") is False | ||
| elif event == "workflow_dispatch": | ||
| valid &= e["CALLER_REF"] == "refs/heads/main" | ||
| else: | ||
| valid = False | ||
| if not valid: | ||
| sys.exit("law-ci: admission refused") | ||
| with open(e["GITHUB_OUTPUT"], "a", encoding="utf-8") as output: | ||
| output.write("source_sha=" + e["SOURCE_SHA"] + "\n") | ||
| PY | ||
| - name: Checkout admitted consumer head | ||
| uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 verified commit | ||
| with: | ||
| repository: ContextualWisdomLab/law-ai-agent | ||
| ref: ${{ steps.admission.outputs.source_sha }} | ||
| path: source | ||
| persist-credentials: false | ||
| - name: Checkout this reusable workflow source | ||
| uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 verified commit | ||
| with: | ||
| repository: ${{ job.workflow_repository }} | ||
| ref: ${{ job.workflow_sha }} | ||
| path: central | ||
| persist-credentials: false | ||
| - name: Verify both checkout identities | ||
| shell: bash | ||
| env: | ||
| SOURCE_SHA: ${{ steps.admission.outputs.source_sha }} | ||
| CENTRAL_SHA: ${{ job.workflow_sha }} | ||
| run: | | ||
| set -euo pipefail | ||
| test "$(git -C "$GITHUB_WORKSPACE/source" rev-parse HEAD)" = "$SOURCE_SHA" | ||
| test "$(git -C "$GITHUB_WORKSPACE/central" rev-parse HEAD)" = "$CENTRAL_SHA" | ||
| - name: Provision pinned uv and matrix Python | ||
| uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7 dereferenced commit | ||
| with: | ||
| version: "0.12.5" | ||
| python-version: ${{ matrix.python-version }} | ||
| enable-cache: false | ||
| - name: Run isolated native database and artifact gates | ||
| shell: bash | ||
| env: | ||
| LAW_CI_SOURCE: ${{ github.workspace }}/source | ||
| LAW_CI_SOURCE_SHA: ${{ steps.admission.outputs.source_sha }} | ||
| LAW_CI_PYTHON_VERSION: ${{ matrix.python-version }} | ||
| run: bash "$GITHUB_WORKSPACE/central/scripts/ci/law_ai_agent_ci.sh" run | ||
| - name: Cleanup owned native cluster on cancellation | ||
| if: always() | ||
| shell: bash | ||
| run: bash "$GITHUB_WORKSPACE/central/scripts/ci/law_ai_agent_ci.sh" cleanup | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| # Law AI Agent 중앙 CI 이관 기록 | ||
|
|
||
| ## 범위와 상태 | ||
|
|
||
| - 2026-10-05 14:02 KST 사용자 승인. 중앙 소유자는 `ContextualWisdomLab/.github`다. | ||
| - 중앙 기준본: `main@37b10243cec3d160ecc9c1be75c71428b160a703`. | ||
| - 검증한 소비 기준본: `ContextualWisdomLab/law-ai-agent@301800bd9e2f1c0876ff3466b0de8013474c7d26`. | ||
| - 이 변경은 새 workflow, shell helper, 중앙 계약 테스트, 이 기록의 네 파일만 소유한다. | ||
| 기존 중앙 control/security/scanner와 소비 source는 수정하지 않는다. | ||
| - AGENTS, CLAUDE, master context, product directive, Project protocol, gap baseline, | ||
| `ContextualWisdomLab/naruon#974` spec을 읽었다. Project #1은 조회 당시 503개 항목이었다. | ||
| 조회한 앞 100개는 전체 인벤토리가 아니다. 이 작업의 통합 담당자는 Coordinator다. | ||
| - 소스 이관과 runner 운영은 다른 증거다. 보호된 병합, GitHub job 성공, 출시를 주장하지 않는다. | ||
|
|
||
| ## 신뢰 경계 | ||
|
|
||
| `workflow_call`만 허용한다. 중앙 public PR 이벤트는 private 소비 code를 checkout하지 않는다. | ||
| 첫 inline admission은 checkout 전에 exact caller repository, 이벤트와 SHA를 검사한다. | ||
| 허용 이벤트는 같은 저장소의 main 대상 PR, main push, main 수동 실행이다. | ||
| Fork, 다른 저장소, `pull_request_target`, mutable 중앙 ref, 잘못된 SHA는 실패한다. | ||
| Fork를 성공한 skip으로 처리하지 않는다. | ||
|
|
||
| 소비 checkout은 이벤트의 PR head 또는 github.sha다. 중앙 helper checkout은 | ||
| `job.workflow_repository`와 `job.workflow_sha`다. `github.workflow_sha`를 중앙 SHA로 쓰지 않는다. | ||
| 공식 문서는 job 필드가 “workflow file that defines the current job”을 가리킨다고 설명한다 | ||
| (GitHub, n.d.-a). 두 checkout의 실제 HEAD를 검사한다. 경로는 `source`와 `central`로 분리한다. | ||
| `persist-credentials: false`, `contents: read`, cache 비활성화를 적용한다. 별도 secret과 | ||
| `secrets: inherit`은 없다. 소비 concurrency prefix와 중앙 `law-ci` prefix는 달라야 한다. | ||
|
|
||
| 검증한 action 객체: | ||
|
|
||
| - checkout commit: `fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09`. | ||
| - 소비 setup-uv의 `94527f2e458b27549849d47d273a16bec83a01e9`는 annotated tag 객체다. | ||
| REST git tag 응답에서 commit `37802adc94f370d6bfd71619e3f0bf239e1f3b78`을 확인했다. | ||
| 중앙은 이 commit을 pin한다. uv 버전은 0.12.5다. | ||
|
|
||
| Runner selector는 group `CWL law CI`, labels `self-hosted/Linux/X64/law-ai-agent-ci`다. | ||
| Python 3.12/3.14 matrix는 `max-parallel: 1`, 각 job은 20분이다. | ||
| Reusable workflow의 runner 접근과 hosted billing은 caller context를 따른다 | ||
| (GitHub, n.d.-b). 중앙 소스 이관으로 private caller의 전역 admission 문제가 해결됐다는 주장은 하지 않는다. | ||
| 격리 runner의 운영 권한·자원·network 검증은 Coordinator의 별도 영수증이 필요하다. | ||
|
|
||
| 기존 중앙 workflow에는 Harden-Runner가 있다. 이 lane에는 효과가 없는 action을 복사하지 않았다. | ||
| 공급자 문서는 self-hosted runner에 Enterprise 구독과 runner infrastructure agent가 필요하며, | ||
| 구독 없이 action만 추가하면 no-op이라고 설명한다 (StepSecurity, n.d.). Egress 강제와 | ||
| host 격리는 runner provisioning 경계다. 이 기록은 해당 제어의 실제 활성화를 증명하지 않는다. | ||
|
|
||
| ## 실행 계약 | ||
|
|
||
| Helper는 BASH_SOURCE로 자기 경로를 찾는다. CWD에 의존하지 않는다. | ||
| 정확한 consumer commit을 `git archive`로 새 scratch source에 복원한다. | ||
| 이전 checkout의 dist, venv, cache를 사용하지 않는다. | ||
|
|
||
| - native PostgreSQL은 provisioned `pg_config --bindir`에서 찾는다. 런타임 apt/sudo 설치는 없다. | ||
| - RUNNER_TEMP의 짧은 `mktemp` immediate-child root, uid 소유권, mode 0700, | ||
| socket 0700, TCP off, test database만 사용한다. 모든 경로 component의 symlink를 거부한다. | ||
| - `LAW_AGENT_ROOT`와 소비 안전 코드가 실제 읽는 `LAW_AGENT_POSTGRES_ROOT`는 같은 scratch다. | ||
| - 서버와 연결의 statement 10초, lock 2초 제한을 유지한다. 실제 consumer 승인 함수 | ||
| `connect(..., test_only=True)`를 사용하여 migration 001/002를 commit한다. | ||
| committed baseline fixture가 suite 앞부분에서 필요하므로 전체 pytest 전에 초기화한다. | ||
| - uv locked sync, ruff, format, mypy, 전체 pytest/coverage90/subprocess coverage를 유지한다. | ||
| JUnit의 모든 suite에서 skip/failure/error 0과 test count 양수를 확인한다. | ||
| - 새 outdir에서 정확히 wheel 1개와 sdist 1개를 요구한다. uv의 자동 `.gitignore`는 | ||
| `--no-create-gitignore`로 막는다 (Astral, n.d.). | ||
| - 새 lane-local cache를 online prime한 뒤 다른 새 venv에 실제 offline wheel 설치를 한다. | ||
| source 밖에서 `python -I`로 installed prefix 안의 module, 두 migration resource와 CLI help를 확인한다. | ||
| - 생성 직후 trap을 설치한다. 기존 state marker는 덮어쓰거나 삭제하지 않는다. | ||
| child 실패 상태를 보존하고 cleanup 실패를 성공으로 바꾸지 않는다. | ||
| 항상 실행하는 workflow cleanup은 private uid/mode와 canonical immediate-child 범위를 다시 검사한다. | ||
|
|
||
| Shell은 Python interrogate 대상이 아니다. 모든 shell function 앞의 역할·실패 경계 주석과 | ||
| 모든 embedded Python block의 module docstring을 이 파일의 문서화 계약으로 사용한다. | ||
| 이는 전체 조직의 100% coverage/docstring gate가 실행됐다는 주장이 아니다. | ||
|
|
||
| ## 실제 검증 | ||
|
|
||
| 2026-10-05 KST, macOS 로컬의 실제 native PostgreSQL 18.6와 uv 0.12.5에서 실행했다. | ||
| 소비 source는 위의 정확한 committed SHA다. 소비 작업 중 추가된 미커밋 테스트는 포함하지 않는다. | ||
| 요청 당시 343개와 달리 현재 기준본의 실제 collection은 **345개**다. | ||
|
|
||
| | 실행 | 실제 결과 | | ||
| |---|---| | ||
| | Python 3.12.14 전체 소비 suite | 345 passed, skipped 0, coverage 98.95% | | ||
| | Python 3.14.7 전체 소비 suite | 345 passed, skipped 0, coverage 98.87% | | ||
| | 두 Python의 ruff / format / mypy | 통과 | | ||
| | 두 Python의 fresh wheel/sdist + offline 새 venv 설치 | 통과 | | ||
| | 두 Python의 source 밖 isolated resource / CLI help | 통과 | | ||
| | 두 Python의 native cluster stop와 scratch 제거 | 통과 | | ||
| | 중앙 admission TDD | 15 RED → 15 GREEN | | ||
| | 중앙 helper evidence TDD | 7 RED → 23 GREEN | | ||
| | 실행 계약 TDD | 2 RED → 25 GREEN | | ||
| | 실제 빌드 `.gitignore` 회귀 | RED 확인 후 수정·두 lane 재실행 통과 | | ||
| | 독립 리뷰 cleanup/trap 회귀 | nested/public root와 trap 순서 3 RED → 31 GREEN | | ||
|
|
||
| Synthetic tool fixture는 child exit 37의 전파, stop 실패, 재cleanup만 검사한다. | ||
| 실제 DB 검증으로 계산하지 않는다. 실제 DB 결과는 위의 두 native 실행뿐이다. | ||
| 독립 리뷰 뒤 강화한 최종 helper bytes는 아래 최종 checkpoint에서 다시 검증한다. | ||
|
|
||
| `actionlint 1.7.12`는 공식 `job.workflow_*` 필드를 아직 모른다. 처음 실행은 이 필드와 | ||
| custom label에서 실패했다. 임시 custom-label config와 이 세 필드의 exact error regex만 | ||
| ignore한 실행은 통과했다. Workflow expression 검사가 완전히 지원된다는 주장은 하지 않는다. | ||
| 공식 context 문서와 admission runtime 테스트를 별도 근거로 남긴다. | ||
| Shellcheck와 `bash -n`은 통과했다. 중앙 full-suite/100% coverage는 별도 실행 상태를 기록한다. | ||
|
|
||
| ## 최종 로컬 checkpoint — 14:42 KST | ||
|
|
||
| 독립 리뷰 수정 뒤 동일 helper bytes를 두 Python에서 다시 실행했다. | ||
| 3.12.14는 345 passed / skip 0 / 98.95%, 3.14.7은 345 passed / skip 0 / 98.87%다. | ||
| 양쪽 offline 실설치, prefix/resource/CLI help와 cleanup을 포함하여 exit 0이다. | ||
| 중앙 subset은 일반 환경과 `GITHUB_ACTIONS=true`에서 각각 **33 passed**다. | ||
| Shellcheck, bash 구문, 6개 embedded Python block compile, 제한적 actionlint와 diff check는 통과했다. | ||
| 중앙 전체 suite와 100% coverage/interrogate는 이 worker에서 실행하지 않았다. | ||
|
|
||
| 최종 core 파일 SHA-256: | ||
|
|
||
| | 파일 | SHA-256 | | ||
| |---|---| | ||
| | `.github/workflows/law-ai-agent-ci.yml` | `3aac714edfb5438c1f7c0eb6420fcff2f43b39692324cd6d7e86c4c103cfc164` | | ||
| | `scripts/ci/law_ai_agent_ci.sh` | `7ee3d7d4f7083d87b921530f4ee3c7ce7dd8041e9611f4429390aa58f9e4e935` | | ||
| | `tests/test_law_ai_agent_ci.py` | `8afd78b82f0c2a270375192eb35454735b1bb192b573bc89027d951b50900b35` | | ||
|
|
||
| 이 문서 자체의 hash는 Coordinator handoff에서 별도로 전달한다. | ||
| 다른 agent의 runner image/provisioning/review 파일은 shared worktree에 있지만 이 worker는 수정하지 않았다. | ||
|
|
||
| ## 남은 통합 확인 | ||
|
|
||
| 1. 독립 reviewer에게 final 네 파일 SHA-256을 전달한다. | ||
| 2. Coordinator가 격리 Linux runner에서 final helper exact bytes를 실행한다. | ||
| 3. 소비 thin SHA caller의 exact commit과 중앙 protected commit을 연결한다. | ||
| 4. GitHub current-head required checks, 독립 승인, protected merge를 따로 검증한다. | ||
| 5. 활성 기존 writer/cron의 재개는 Coordinator가 소유한다. 이 worker는 스케줄을 변경하지 않는다. | ||
|
|
||
| ## References (APA 7) | ||
|
|
||
| Astral. (n.d.). *Commands: uv build*. Retrieved October 5, 2026, from | ||
| https://docs.astral.sh/uv/reference/cli/ | ||
|
|
||
| GitHub. (n.d.-a). *Contexts reference*. Retrieved October 5, 2026, from | ||
| https://docs.github.com/en/actions/reference/workflows-and-actions/contexts | ||
|
|
||
| GitHub. (n.d.-b). *Reusing workflow configurations*. Retrieved October 5, 2026, from | ||
| https://docs.github.com/en/actions/reference/workflows-and-actions/reusing-workflow-configurations | ||
|
|
||
| StepSecurity. (n.d.). *Harden-Runner*. Retrieved October 5, 2026, from | ||
| https://github.com/step-security/harden-runner |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟨 Pull request code reaches unfiltered runner network
For same-repository PRs,
qualityexecutes the PR head on a runner whose network permits internal access. The runner label does not isolate egress, exposing reachable internal services to PR-controlled code.Was this helpful? React with 👍 or 👎 to provide feedback.