From 4e4b99aa9ea2849f271a5adbd330fcadf338c461 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 06:41:26 +0900 Subject: [PATCH 01/57] ci: stage fail-closed LLVM coverage repair --- .../repair-opencode-llvm-coverage.yml | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 .github/workflows/repair-opencode-llvm-coverage.yml diff --git a/.github/workflows/repair-opencode-llvm-coverage.yml b/.github/workflows/repair-opencode-llvm-coverage.yml new file mode 100644 index 000000000..03ef981a0 --- /dev/null +++ b/.github/workflows/repair-opencode-llvm-coverage.yml @@ -0,0 +1,142 @@ +name: Repair OpenCode LLVM coverage tooling + +on: + pull_request: + types: [opened, reopened, synchronize] + +permissions: + contents: read + +concurrency: + group: repair-opencode-llvm-coverage-${{ github.event.pull_request.head.sha }} + cancel-in-progress: false + +jobs: + repair: + if: >- + github.event.pull_request.head.repo.full_name == github.repository && + github.head_ref == 'fix/opencode-llvm-coverage-tools' && + github.actor != 'github-actions[bot]' + permissions: + contents: write + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + persist-credentials: false + + - name: Verify exact audited parent and trigger-only delta + env: + EXPECTED_PARENT: 3f65dbee6672b78802e7d71d49c390f3817bb03b + run: | + set -euo pipefail + test "$(git rev-parse HEAD^)" = "${EXPECTED_PARENT}" + test "$(git diff --name-only HEAD^ HEAD)" = ".github/workflows/repair-opencode-llvm-coverage.yml" + + - name: Prove the missing-tool regression before repair + shell: python + run: | + from pathlib import Path + + source = Path(".github/workflows/opencode-review-dispatch.yml").read_text( + encoding="utf-8" + ) + required = ( + "ENV LLVM_COV=/usr/bin/llvm-cov-19", + "ENV LLVM_PROFDATA=/usr/bin/llvm-profdata-19", + 'RUN test -x "$LLVM_COV" && test -x "$LLVM_PROFDATA"', + ) + if all(value in source for value in required): + raise SystemExit("expected the pre-repair workflow to lack LLVM tool bindings") + print("Confirmed RED: the trusted coverage image lacks complete LLVM tool bindings.") + + - name: Add compatible LLVM coverage tools and permanent contract test + shell: python + run: | + from pathlib import Path + + workflow_path = Path(".github/workflows/opencode-review-dispatch.yml") + source = workflow_path.read_text(encoding="utf-8") + slash = chr(92) + old_packages = ( + f" r-cran-testthat {slash}\n" + f" rustc {slash}\n" + f" util-linux {slash}\n" + ) + new_packages = ( + f" r-cran-testthat {slash}\n" + f" llvm-19 {slash}\n" + f" rustc {slash}\n" + f" util-linux {slash}\n" + ) + old_runtime = ( + " && rm -rf /var/lib/apt/lists/*\n" + " RUN curl --proto '=https'" + ) + new_runtime = ( + " && rm -rf /var/lib/apt/lists/*\n" + " ENV LLVM_COV=/usr/bin/llvm-cov-19\n" + " ENV LLVM_PROFDATA=/usr/bin/llvm-profdata-19\n" + ' RUN test -x "$LLVM_COV" && test -x "$LLVM_PROFDATA"\n' + " RUN curl --proto '=https'" + ) + if source.count(old_packages) != 1: + raise SystemExit( + f"expected one Rust package anchor, found {source.count(old_packages)}" + ) + if source.count(old_runtime) != 1: + raise SystemExit( + f"expected one Docker runtime anchor, found {source.count(old_runtime)}" + ) + source = source.replace(old_packages, new_packages, 1) + source = source.replace(old_runtime, new_runtime, 1) + workflow_path.write_text(source, encoding="utf-8") + + test_path = Path("tests/test_opencode_agent_contract.py") + tests = test_path.read_text(encoding="utf-8") + test_name = "test_opencode_coverage_image_provisions_compatible_llvm_tools" + if test_name in tests: + raise SystemExit(f"{test_name} already exists") + tests = tests.rstrip() + '''\n\n\ndef test_opencode_coverage_image_provisions_compatible_llvm_tools():\n """Keep Rust coverage independent of a rustup-managed toolchain."""\n workflow = Path(\n ".github/workflows/opencode-review-dispatch.yml"\n ).read_text(encoding="utf-8")\n\n assert " llvm-19 " + chr(92) in workflow\n assert "ENV LLVM_COV=/usr/bin/llvm-cov-19" in workflow\n assert "ENV LLVM_PROFDATA=/usr/bin/llvm-profdata-19" in workflow\n assert 'RUN test -x "$LLVM_COV" && test -x "$LLVM_PROFDATA"' in workflow\n''' + test_path.write_text(tests, encoding="utf-8") + + - name: Validate the repaired contract + run: | + set -euo pipefail + python3 -m compileall -q tests + python3 <<'PY' + from pathlib import Path + + workflow = Path(".github/workflows/opencode-review-dispatch.yml").read_text( + encoding="utf-8" + ) + assert " llvm-19 " + chr(92) in workflow + assert "ENV LLVM_COV=/usr/bin/llvm-cov-19" in workflow + assert "ENV LLVM_PROFDATA=/usr/bin/llvm-profdata-19" in workflow + assert 'RUN test -x "$LLVM_COV" && test -x "$LLVM_PROFDATA"' in workflow + PY + git diff --check + + - name: Commit validated repair and remove workflow + env: + GH_TOKEN: ${{ github.token }} + ORIGINAL_HEAD: ${{ github.event.pull_request.head.sha }} + run: | + set -euo pipefail + git fetch origin fix/opencode-llvm-coverage-tools + test "$(git rev-parse origin/fix/opencode-llvm-coverage-tools)" = "${ORIGINAL_HEAD}" + rm .github/workflows/repair-opencode-llvm-coverage.yml + test "$(git diff --name-only | sort)" = $'.github/workflows/opencode-review-dispatch.yml\n.github/workflows/repair-opencode-llvm-coverage.yml\ntests/test_opencode_agent_contract.py' + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + git add .github/workflows/opencode-review-dispatch.yml \ + .github/workflows/repair-opencode-llvm-coverage.yml \ + tests/test_opencode_agent_contract.py + git diff --cached --check + git commit -m "ci(opencode-review): provision LLVM coverage tools" + git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + git push --force-with-lease=refs/heads/fix/opencode-llvm-coverage-tools:${ORIGINAL_HEAD} \ + origin HEAD:refs/heads/fix/opencode-llvm-coverage-tools From c28a16fe07e5ccd25264a620e82d3cf73a33d428 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 06:45:21 +0900 Subject: [PATCH 02/57] ci: use bounded app token for workflow repair --- .../repair-opencode-llvm-coverage.yml | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/.github/workflows/repair-opencode-llvm-coverage.yml b/.github/workflows/repair-opencode-llvm-coverage.yml index 03ef981a0..a0211efd2 100644 --- a/.github/workflows/repair-opencode-llvm-coverage.yml +++ b/.github/workflows/repair-opencode-llvm-coverage.yml @@ -19,6 +19,7 @@ jobs: github.actor != 'github-actions[bot]' permissions: contents: write + id-token: write runs-on: ubuntu-latest timeout-minutes: 20 steps: @@ -30,7 +31,7 @@ jobs: - name: Verify exact audited parent and trigger-only delta env: - EXPECTED_PARENT: 3f65dbee6672b78802e7d71d49c390f3817bb03b + EXPECTED_PARENT: 4e4b99aa9ea2849f271a5adbd330fcadf338c461 run: | set -euo pipefail test "$(git rev-parse HEAD^)" = "${EXPECTED_PARENT}" @@ -100,7 +101,7 @@ jobs: test_name = "test_opencode_coverage_image_provisions_compatible_llvm_tools" if test_name in tests: raise SystemExit(f"{test_name} already exists") - tests = tests.rstrip() + '''\n\n\ndef test_opencode_coverage_image_provisions_compatible_llvm_tools():\n """Keep Rust coverage independent of a rustup-managed toolchain."""\n workflow = Path(\n ".github/workflows/opencode-review-dispatch.yml"\n ).read_text(encoding="utf-8")\n\n assert " llvm-19 " + chr(92) in workflow\n assert "ENV LLVM_COV=/usr/bin/llvm-cov-19" in workflow\n assert "ENV LLVM_PROFDATA=/usr/bin/llvm-profdata-19" in workflow\n assert 'RUN test -x "$LLVM_COV" && test -x "$LLVM_PROFDATA"' in workflow\n''' + tests = tests.rstrip() + '''\n\n\ndef test_opencode_coverage_image_provisions_compatible_llvm_tools():\n """Keep Rust coverage independent of a rustup-managed toolchain."""\n workflow = Path(\n ".github/workflows/opencode-review-dispatch.yml"\n ).read_text(encoding="utf-8")\n\n assert " llvm-19 " + chr(92) in workflow\n assert "ENV LLVM_COV=/usr/bin/llvm-cov-19" in workflow\n assert "ENV LLVM_PROFDATA=/usr/bin/llvm-profdata-19" in workflow\n assert 'RUN test -x "$LLVM_COV" && test -x "$LLVM_PROFDATA"' in workflow\n''' test_path.write_text(tests, encoding="utf-8") - name: Validate the repaired contract @@ -120,9 +121,38 @@ jobs: PY git diff --check + - name: Exchange bounded OpenCode app token + id: workflow_token + env: + OIDC_AUDIENCE: opencode-github-action + OPENCODE_API_BASE_URL: https://api.opencode.ai + run: | + set -euo pipefail + test -n "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" + test -n "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" + request_url="${ACTIONS_ID_TOKEN_REQUEST_URL}" + separator='&' + case "$request_url" in + *\?*) ;; + *) separator='?' ;; + esac + oidc_response="$(curl -fsS \ + -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \ + "${request_url}${separator}audience=${OIDC_AUDIENCE}")" + oidc_token="$(jq -r '.value // empty' <<<"$oidc_response")" + test -n "$oidc_token" + token_response="$(curl -fsS \ + -X POST \ + -H "Authorization: Bearer ${oidc_token}" \ + "${OPENCODE_API_BASE_URL}/exchange_github_app_token")" + app_token="$(jq -r '.token // empty' <<<"$token_response")" + test -n "$app_token" + echo "::add-mask::$app_token" + printf 'token=%s\n' "$app_token" >>"$GITHUB_OUTPUT" + - name: Commit validated repair and remove workflow env: - GH_TOKEN: ${{ github.token }} + GH_TOKEN: ${{ steps.workflow_token.outputs.token }} ORIGINAL_HEAD: ${{ github.event.pull_request.head.sha }} run: | set -euo pipefail @@ -130,8 +160,8 @@ jobs: test "$(git rev-parse origin/fix/opencode-llvm-coverage-tools)" = "${ORIGINAL_HEAD}" rm .github/workflows/repair-opencode-llvm-coverage.yml test "$(git diff --name-only | sort)" = $'.github/workflows/opencode-review-dispatch.yml\n.github/workflows/repair-opencode-llvm-coverage.yml\ntests/test_opencode_agent_contract.py' - git config user.name github-actions[bot] - git config user.email 41898282+github-actions[bot]@users.noreply.github.com + git config user.name opencode-agent[bot] + git config user.email 219766164+opencode-agent[bot]@users.noreply.github.com git add .github/workflows/opencode-review-dispatch.yml \ .github/workflows/repair-opencode-llvm-coverage.yml \ tests/test_opencode_agent_contract.py From 2cd3e131f468f391cd3f464ef04f9a175d6efa34 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <219766164+opencode-agent[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 21:45:45 +0000 Subject: [PATCH 03/57] ci(opencode-review): provision LLVM coverage tools --- .../workflows/opencode-review-dispatch.yml | 4 + .../repair-opencode-llvm-coverage.yml | 172 ------------------ tests/test_opencode_agent_contract.py | 12 ++ 3 files changed, 16 insertions(+), 172 deletions(-) delete mode 100644 .github/workflows/repair-opencode-llvm-coverage.yml diff --git a/.github/workflows/opencode-review-dispatch.yml b/.github/workflows/opencode-review-dispatch.yml index d826ce67a..41748bcec 100644 --- a/.github/workflows/opencode-review-dispatch.yml +++ b/.github/workflows/opencode-review-dispatch.yml @@ -652,11 +652,15 @@ jobs: r-base \ r-cran-covr \ r-cran-testthat \ + llvm-19 \ rustc \ util-linux \ vulkan-tools \ xz-utils \ && rm -rf /var/lib/apt/lists/* + ENV LLVM_COV=/usr/bin/llvm-cov-19 + ENV LLVM_PROFDATA=/usr/bin/llvm-profdata-19 + RUN test -x "$LLVM_COV" && test -x "$LLVM_PROFDATA" RUN curl --proto '=https' --tlsv1.2 -fsSLo /tmp/node-linux-x64.tar.xz \ https://nodejs.org/dist/v24.18.0/node-v24.18.0-linux-x64.tar.xz \ && echo '55aa7153f9d88f28d765fcdad5ae6945b5c0f98a36881703817e4c450fa76742 /tmp/node-linux-x64.tar.xz' | sha256sum -c - \ diff --git a/.github/workflows/repair-opencode-llvm-coverage.yml b/.github/workflows/repair-opencode-llvm-coverage.yml deleted file mode 100644 index a0211efd2..000000000 --- a/.github/workflows/repair-opencode-llvm-coverage.yml +++ /dev/null @@ -1,172 +0,0 @@ -name: Repair OpenCode LLVM coverage tooling - -on: - pull_request: - types: [opened, reopened, synchronize] - -permissions: - contents: read - -concurrency: - group: repair-opencode-llvm-coverage-${{ github.event.pull_request.head.sha }} - cancel-in-progress: false - -jobs: - repair: - if: >- - github.event.pull_request.head.repo.full_name == github.repository && - github.head_ref == 'fix/opencode-llvm-coverage-tools' && - github.actor != 'github-actions[bot]' - permissions: - contents: write - id-token: write - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 0 - persist-credentials: false - - - name: Verify exact audited parent and trigger-only delta - env: - EXPECTED_PARENT: 4e4b99aa9ea2849f271a5adbd330fcadf338c461 - run: | - set -euo pipefail - test "$(git rev-parse HEAD^)" = "${EXPECTED_PARENT}" - test "$(git diff --name-only HEAD^ HEAD)" = ".github/workflows/repair-opencode-llvm-coverage.yml" - - - name: Prove the missing-tool regression before repair - shell: python - run: | - from pathlib import Path - - source = Path(".github/workflows/opencode-review-dispatch.yml").read_text( - encoding="utf-8" - ) - required = ( - "ENV LLVM_COV=/usr/bin/llvm-cov-19", - "ENV LLVM_PROFDATA=/usr/bin/llvm-profdata-19", - 'RUN test -x "$LLVM_COV" && test -x "$LLVM_PROFDATA"', - ) - if all(value in source for value in required): - raise SystemExit("expected the pre-repair workflow to lack LLVM tool bindings") - print("Confirmed RED: the trusted coverage image lacks complete LLVM tool bindings.") - - - name: Add compatible LLVM coverage tools and permanent contract test - shell: python - run: | - from pathlib import Path - - workflow_path = Path(".github/workflows/opencode-review-dispatch.yml") - source = workflow_path.read_text(encoding="utf-8") - slash = chr(92) - old_packages = ( - f" r-cran-testthat {slash}\n" - f" rustc {slash}\n" - f" util-linux {slash}\n" - ) - new_packages = ( - f" r-cran-testthat {slash}\n" - f" llvm-19 {slash}\n" - f" rustc {slash}\n" - f" util-linux {slash}\n" - ) - old_runtime = ( - " && rm -rf /var/lib/apt/lists/*\n" - " RUN curl --proto '=https'" - ) - new_runtime = ( - " && rm -rf /var/lib/apt/lists/*\n" - " ENV LLVM_COV=/usr/bin/llvm-cov-19\n" - " ENV LLVM_PROFDATA=/usr/bin/llvm-profdata-19\n" - ' RUN test -x "$LLVM_COV" && test -x "$LLVM_PROFDATA"\n' - " RUN curl --proto '=https'" - ) - if source.count(old_packages) != 1: - raise SystemExit( - f"expected one Rust package anchor, found {source.count(old_packages)}" - ) - if source.count(old_runtime) != 1: - raise SystemExit( - f"expected one Docker runtime anchor, found {source.count(old_runtime)}" - ) - source = source.replace(old_packages, new_packages, 1) - source = source.replace(old_runtime, new_runtime, 1) - workflow_path.write_text(source, encoding="utf-8") - - test_path = Path("tests/test_opencode_agent_contract.py") - tests = test_path.read_text(encoding="utf-8") - test_name = "test_opencode_coverage_image_provisions_compatible_llvm_tools" - if test_name in tests: - raise SystemExit(f"{test_name} already exists") - tests = tests.rstrip() + '''\n\n\ndef test_opencode_coverage_image_provisions_compatible_llvm_tools():\n """Keep Rust coverage independent of a rustup-managed toolchain."""\n workflow = Path(\n ".github/workflows/opencode-review-dispatch.yml"\n ).read_text(encoding="utf-8")\n\n assert " llvm-19 " + chr(92) in workflow\n assert "ENV LLVM_COV=/usr/bin/llvm-cov-19" in workflow\n assert "ENV LLVM_PROFDATA=/usr/bin/llvm-profdata-19" in workflow\n assert 'RUN test -x "$LLVM_COV" && test -x "$LLVM_PROFDATA"' in workflow\n''' - test_path.write_text(tests, encoding="utf-8") - - - name: Validate the repaired contract - run: | - set -euo pipefail - python3 -m compileall -q tests - python3 <<'PY' - from pathlib import Path - - workflow = Path(".github/workflows/opencode-review-dispatch.yml").read_text( - encoding="utf-8" - ) - assert " llvm-19 " + chr(92) in workflow - assert "ENV LLVM_COV=/usr/bin/llvm-cov-19" in workflow - assert "ENV LLVM_PROFDATA=/usr/bin/llvm-profdata-19" in workflow - assert 'RUN test -x "$LLVM_COV" && test -x "$LLVM_PROFDATA"' in workflow - PY - git diff --check - - - name: Exchange bounded OpenCode app token - id: workflow_token - env: - OIDC_AUDIENCE: opencode-github-action - OPENCODE_API_BASE_URL: https://api.opencode.ai - run: | - set -euo pipefail - test -n "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" - test -n "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" - request_url="${ACTIONS_ID_TOKEN_REQUEST_URL}" - separator='&' - case "$request_url" in - *\?*) ;; - *) separator='?' ;; - esac - oidc_response="$(curl -fsS \ - -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \ - "${request_url}${separator}audience=${OIDC_AUDIENCE}")" - oidc_token="$(jq -r '.value // empty' <<<"$oidc_response")" - test -n "$oidc_token" - token_response="$(curl -fsS \ - -X POST \ - -H "Authorization: Bearer ${oidc_token}" \ - "${OPENCODE_API_BASE_URL}/exchange_github_app_token")" - app_token="$(jq -r '.token // empty' <<<"$token_response")" - test -n "$app_token" - echo "::add-mask::$app_token" - printf 'token=%s\n' "$app_token" >>"$GITHUB_OUTPUT" - - - name: Commit validated repair and remove workflow - env: - GH_TOKEN: ${{ steps.workflow_token.outputs.token }} - ORIGINAL_HEAD: ${{ github.event.pull_request.head.sha }} - run: | - set -euo pipefail - git fetch origin fix/opencode-llvm-coverage-tools - test "$(git rev-parse origin/fix/opencode-llvm-coverage-tools)" = "${ORIGINAL_HEAD}" - rm .github/workflows/repair-opencode-llvm-coverage.yml - test "$(git diff --name-only | sort)" = $'.github/workflows/opencode-review-dispatch.yml\n.github/workflows/repair-opencode-llvm-coverage.yml\ntests/test_opencode_agent_contract.py' - git config user.name opencode-agent[bot] - git config user.email 219766164+opencode-agent[bot]@users.noreply.github.com - git add .github/workflows/opencode-review-dispatch.yml \ - .github/workflows/repair-opencode-llvm-coverage.yml \ - tests/test_opencode_agent_contract.py - git diff --cached --check - git commit -m "ci(opencode-review): provision LLVM coverage tools" - git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" - git push --force-with-lease=refs/heads/fix/opencode-llvm-coverage-tools:${ORIGINAL_HEAD} \ - origin HEAD:refs/heads/fix/opencode-llvm-coverage-tools diff --git a/tests/test_opencode_agent_contract.py b/tests/test_opencode_agent_contract.py index 565ea4b9a..583a4a6a4 100644 --- a/tests/test_opencode_agent_contract.py +++ b/tests/test_opencode_agent_contract.py @@ -2729,3 +2729,15 @@ def test_r_package_load_deferral_requires_current_head_r_cmd_check(): assert ( "if (!is.na(pkg) && !requireNamespace(pkg, quietly = TRUE))" not in workflow ) + + +def test_opencode_coverage_image_provisions_compatible_llvm_tools(): + """Keep Rust coverage independent of a rustup-managed toolchain.""" + workflow = Path( + ".github/workflows/opencode-review-dispatch.yml" + ).read_text(encoding="utf-8") + + assert " llvm-19 " + chr(92) in workflow + assert "ENV LLVM_COV=/usr/bin/llvm-cov-19" in workflow + assert "ENV LLVM_PROFDATA=/usr/bin/llvm-profdata-19" in workflow + assert 'RUN test -x "$LLVM_COV" && test -x "$LLVM_PROFDATA"' in workflow From 08e038dadf54f3380cb62b5fa1000c66ab858294 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 07:49:28 +0900 Subject: [PATCH 04/57] chore: bootstrap coverage failure diagnostics repair --- ...shot-fix-opencode-coverage-diagnostics.yml | 270 ++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 .github/workflows/one-shot-fix-opencode-coverage-diagnostics.yml diff --git a/.github/workflows/one-shot-fix-opencode-coverage-diagnostics.yml b/.github/workflows/one-shot-fix-opencode-coverage-diagnostics.yml new file mode 100644 index 000000000..abace8246 --- /dev/null +++ b/.github/workflows/one-shot-fix-opencode-coverage-diagnostics.yml @@ -0,0 +1,270 @@ +name: One-shot OpenCode coverage failure diagnostics repair + +on: + push: + branches: + - fix/opencode-coverage-failure-diagnostics + +permissions: + contents: write + +concurrency: + group: one-shot-opencode-coverage-failure-diagnostics + cancel-in-progress: false + +jobs: + repair: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - name: Harden runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Checkout repair branch + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 0 + ref: fix/opencode-coverage-failure-diagnostics + + - name: Install hash-locked test tooling + run: | + set -euo pipefail + python -m pip install --disable-pip-version-check --require-hashes \ + -r requirements-opencode-review-ci-hashes.txt + + - name: Add regression tests first + run: | + set -euo pipefail + cat > tests/test_coverage_materializer_failure_diagnostics.py <<'PY' + from __future__ import annotations + + from pathlib import Path + + import pytest + + from scripts.ci import materialize_base_javascript_packages as javascript_materializer + from scripts.ci import materialize_base_python_requirements as python_materializer + + + def _failing_materializer(error: BaseException): + """Return a materializer stub that raises the supplied failure.""" + + def fail(*_args: object, **_kwargs: object) -> None: + raise error + + return fail + + + def _run_main(module: object, tmp_path: Path) -> int: + """Invoke one materializer CLI with a valid-shaped isolated argument set.""" + return module.main( + [ + "--repo-root", + str(tmp_path), + "--base-sha", + "a" * 40, + "--output-dir", + str(tmp_path / "output"), + ] + ) + + + def test_javascript_failure_publishes_exact_coverage_reason( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + """The deterministic review receives the exact early npm-lock failure.""" + output_file = tmp_path / "github-output" + exact_reason = ( + "current-head npm lock package-lock.json package " + "apps/desktop/node_modules/@types/react-dom must pin a registry " + "tarball and SHA-512 integrity" + ) + monkeypatch.setenv("GITHUB_OUTPUT", str(output_file)) + monkeypatch.setattr( + javascript_materializer, + "materialize", + _failing_materializer(ValueError(exact_reason)), + ) + + assert _run_main(javascript_materializer, tmp_path) == 1 + + published = output_file.read_text(encoding="utf-8") + assert "coverage_summary< None: + """Early Python-lock failures remain concrete without output-file injection.""" + output_file = tmp_path / "github-output" + monkeypatch.setenv("GITHUB_OUTPUT", str(output_file)) + monkeypatch.setattr( + python_materializer, + "materialize", + _failing_materializer( + OSError( + "fixture \nCWL_COVERAGE_SUMMARY_EOF " + + ("x" * 5000) + ) + ), + ) + + assert _run_main(python_materializer, tmp_path) == 1 + + published = output_file.read_text(encoding="utf-8") + assert "- Failed stage: Base Python lock materialization" in published + assert "OSError: fixture <unsafe> CWL_COVERAGE_SUMMARY_END" in published + assert "" not in published + assert published.count("CWL_COVERAGE_SUMMARY_EOF\n") == 2 + assert len(published) < 5000 + + + @pytest.mark.parametrize( + "module", + [javascript_materializer, python_materializer], + ids=["javascript", "python"], + ) + def test_failure_diagnostics_are_optional_outside_github_actions( + module: object, + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + """Local CLI failures keep their status when no Actions output file exists.""" + monkeypatch.delenv("GITHUB_OUTPUT", raising=False) + monkeypatch.setattr( + module, + "materialize", + _failing_materializer(RuntimeError("local fixture failure")), + ) + + assert _run_main(module, tmp_path) == 1 + PY + + - name: Prove the regression test is red before implementation + run: | + set -euo pipefail + set +e + python -m pytest tests/test_coverage_materializer_failure_diagnostics.py -q \ + >"${RUNNER_TEMP}/coverage-diagnostics-red.log" 2>&1 + status=$? + set -e + cat "${RUNNER_TEMP}/coverage-diagnostics-red.log" + if [ "$status" -eq 0 ]; then + echo "::error::Regression test unexpectedly passed before the implementation." + exit 1 + fi + if ! grep -Fq "coverage_summary< None: + """Replace one exact trusted source fragment or fail closed.""" + text = path.read_text(encoding="utf-8") + if text.count(old) != 1: + raise SystemExit( + f"expected exactly one source fragment in {path}, found {text.count(old)}" + ) + path.write_text(text.replace(old, new, 1), encoding="utf-8") + + + helper = '''\n\ndef _publish_coverage_failure_summary(\n stage: str, error: BaseException, remediation: str\n) -> None:\n """Publish bounded exact setup failure evidence for deterministic reviews."""\n github_output = os.environ.get("GITHUB_OUTPUT")\n if not github_output:\n return\n\n delimiter = "CWL_COVERAGE_SUMMARY_EOF"\n safe_stage = html.escape(" ".join(stage.split())[:256], quote=True).replace(\n delimiter, "CWL_COVERAGE_SUMMARY_END"\n )\n safe_reason = html.escape(\n f"{error.__class__.__name__}: {' '.join(str(error).split())}"[:4096],\n quote=True,\n ).replace(delimiter, "CWL_COVERAGE_SUMMARY_END")\n safe_remediation = html.escape(\n " ".join(remediation.split())[:1024], quote=True\n ).replace(delimiter, "CWL_COVERAGE_SUMMARY_END")\n summary = (\n "## Coverage Decision\\n"\n "- Result: FAIL\\n"\n f"- Failed stage: {safe_stage}\\n"\n "- Exact failure:\\n"\n f"
{safe_reason}
\\n"\n f"- Next action: {safe_remediation}\\n"\n )\n with pathlib.Path(github_output).open("a", encoding="utf-8") as output:\n output.write(\n f"coverage_summary<<{delimiter}\\n{summary}{delimiter}\\n"\n )\n''' + + javascript_path = Path("scripts/ci/materialize_base_javascript_packages.py") + replace_once( + javascript_path, + "import argparse\nimport json\nimport pathlib\n", + "import argparse\nimport html\nimport json\nimport os\nimport pathlib\n", + ) + replace_once( + javascript_path, + "\n\ndef main(argv: list[str] | None = None) -> int:\n", + helper + "\n\ndef main(argv: list[str] | None = None) -> int:\n", + ) + replace_once( + javascript_path, + ''' except (OSError, RuntimeError, ValueError) as exc:\n print(\n f"::error::Could not materialize base JavaScript package locks: {exc}",\n file=sys.stderr,\n )\n return 1\n''', + ''' except (OSError, RuntimeError, ValueError) as exc:\n print(\n f"::error::Could not materialize base JavaScript package locks: {exc}",\n file=sys.stderr,\n )\n _publish_coverage_failure_summary(\n "Base JavaScript package lock materialization",\n exc,\n "Repair or regenerate the reported lock entry so every non-link "\n "package selected for the networked cache is registry- and "\n "SHA-512-bounded, then rerun the current-head coverage-evidence job.",\n )\n return 1\n''', + ) + + python_path = Path("scripts/ci/materialize_base_python_requirements.py") + replace_once( + python_path, + "import argparse\nimport fnmatch\nimport json\n", + "import argparse\nimport fnmatch\nimport html\nimport json\nimport os\n", + ) + replace_once( + python_path, + "\n\ndef main(argv: list[str] | None = None) -> int:\n", + helper + "\n\ndef main(argv: list[str] | None = None) -> int:\n", + ) + replace_once( + python_path, + ''' except (OSError, RuntimeError, ValueError) as exc:\n print(\n f"::error::Could not materialize base Python locks: {exc}", file=sys.stderr\n )\n return 1\n''', + ''' except (OSError, RuntimeError, ValueError) as exc:\n print(\n f"::error::Could not materialize base Python locks: {exc}", file=sys.stderr\n )\n _publish_coverage_failure_summary(\n "Base Python lock materialization",\n exc,\n "Repair the reported trusted lock or Git metadata boundary, then "\n "rerun the current-head coverage-evidence job.",\n )\n return 1\n''', + ) + PY + + - name: Verify focused regression, coverage, docstrings, and syntax + run: | + set -euo pipefail + python -m pytest \ + tests/test_coverage_materializer_failure_diagnostics.py \ + tests/test_materialize_base_javascript_packages.py \ + tests/test_materialize_base_python_requirements.py \ + --cov=scripts.ci.materialize_base_javascript_packages \ + --cov=scripts.ci.materialize_base_python_requirements \ + --cov-branch \ + --cov-fail-under=100 \ + -q + python -m interrogate \ + --fail-under 100 \ + scripts/ci/materialize_base_javascript_packages.py \ + scripts/ci/materialize_base_python_requirements.py + python -m compileall -q \ + scripts/ci/materialize_base_javascript_packages.py \ + scripts/ci/materialize_base_python_requirements.py \ + tests/test_coverage_materializer_failure_diagnostics.py + git diff --check + + - name: Verify OpenCode workflow contracts + run: | + set -euo pipefail + python -m pytest \ + tests/test_opencode_agent_contract.py \ + tests/test_opencode_model_pool_runner.py \ + tests/test_sanitize_github_output_summary.py \ + -q + + - name: Commit verified repair and remove bootstrap workflow + env: + BRANCH_NAME: fix/opencode-coverage-failure-diagnostics + run: | + set -euo pipefail + rm -f .github/workflows/one-shot-fix-opencode-coverage-diagnostics.yml + git config user.name "opencode-agent[bot]" + git config user.email "219766164+opencode-agent[bot]@users.noreply.github.com" + git add \ + scripts/ci/materialize_base_javascript_packages.py \ + scripts/ci/materialize_base_python_requirements.py \ + tests/test_coverage_materializer_failure_diagnostics.py \ + .github/workflows/one-shot-fix-opencode-coverage-diagnostics.yml + git diff --cached --check + git commit -m "fix(opencode-review): surface exact coverage setup failures" + git push origin "HEAD:${BRANCH_NAME}" From 657e8fe01a09606306edf8f5175054a8fb976360 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 07:54:50 +0900 Subject: [PATCH 05/57] chore: remove coverage diagnostics bootstrap workflow --- ...shot-fix-opencode-coverage-diagnostics.yml | 270 ------------------ 1 file changed, 270 deletions(-) delete mode 100644 .github/workflows/one-shot-fix-opencode-coverage-diagnostics.yml diff --git a/.github/workflows/one-shot-fix-opencode-coverage-diagnostics.yml b/.github/workflows/one-shot-fix-opencode-coverage-diagnostics.yml deleted file mode 100644 index abace8246..000000000 --- a/.github/workflows/one-shot-fix-opencode-coverage-diagnostics.yml +++ /dev/null @@ -1,270 +0,0 @@ -name: One-shot OpenCode coverage failure diagnostics repair - -on: - push: - branches: - - fix/opencode-coverage-failure-diagnostics - -permissions: - contents: write - -concurrency: - group: one-shot-opencode-coverage-failure-diagnostics - cancel-in-progress: false - -jobs: - repair: - runs-on: ubuntu-latest - timeout-minutes: 20 - steps: - - name: Harden runner - uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 - with: - egress-policy: audit - - - name: Checkout repair branch - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - fetch-depth: 0 - ref: fix/opencode-coverage-failure-diagnostics - - - name: Install hash-locked test tooling - run: | - set -euo pipefail - python -m pip install --disable-pip-version-check --require-hashes \ - -r requirements-opencode-review-ci-hashes.txt - - - name: Add regression tests first - run: | - set -euo pipefail - cat > tests/test_coverage_materializer_failure_diagnostics.py <<'PY' - from __future__ import annotations - - from pathlib import Path - - import pytest - - from scripts.ci import materialize_base_javascript_packages as javascript_materializer - from scripts.ci import materialize_base_python_requirements as python_materializer - - - def _failing_materializer(error: BaseException): - """Return a materializer stub that raises the supplied failure.""" - - def fail(*_args: object, **_kwargs: object) -> None: - raise error - - return fail - - - def _run_main(module: object, tmp_path: Path) -> int: - """Invoke one materializer CLI with a valid-shaped isolated argument set.""" - return module.main( - [ - "--repo-root", - str(tmp_path), - "--base-sha", - "a" * 40, - "--output-dir", - str(tmp_path / "output"), - ] - ) - - - def test_javascript_failure_publishes_exact_coverage_reason( - tmp_path: Path, - monkeypatch: pytest.MonkeyPatch, - ) -> None: - """The deterministic review receives the exact early npm-lock failure.""" - output_file = tmp_path / "github-output" - exact_reason = ( - "current-head npm lock package-lock.json package " - "apps/desktop/node_modules/@types/react-dom must pin a registry " - "tarball and SHA-512 integrity" - ) - monkeypatch.setenv("GITHUB_OUTPUT", str(output_file)) - monkeypatch.setattr( - javascript_materializer, - "materialize", - _failing_materializer(ValueError(exact_reason)), - ) - - assert _run_main(javascript_materializer, tmp_path) == 1 - - published = output_file.read_text(encoding="utf-8") - assert "coverage_summary< None: - """Early Python-lock failures remain concrete without output-file injection.""" - output_file = tmp_path / "github-output" - monkeypatch.setenv("GITHUB_OUTPUT", str(output_file)) - monkeypatch.setattr( - python_materializer, - "materialize", - _failing_materializer( - OSError( - "fixture \nCWL_COVERAGE_SUMMARY_EOF " - + ("x" * 5000) - ) - ), - ) - - assert _run_main(python_materializer, tmp_path) == 1 - - published = output_file.read_text(encoding="utf-8") - assert "- Failed stage: Base Python lock materialization" in published - assert "OSError: fixture <unsafe> CWL_COVERAGE_SUMMARY_END" in published - assert "" not in published - assert published.count("CWL_COVERAGE_SUMMARY_EOF\n") == 2 - assert len(published) < 5000 - - - @pytest.mark.parametrize( - "module", - [javascript_materializer, python_materializer], - ids=["javascript", "python"], - ) - def test_failure_diagnostics_are_optional_outside_github_actions( - module: object, - tmp_path: Path, - monkeypatch: pytest.MonkeyPatch, - ) -> None: - """Local CLI failures keep their status when no Actions output file exists.""" - monkeypatch.delenv("GITHUB_OUTPUT", raising=False) - monkeypatch.setattr( - module, - "materialize", - _failing_materializer(RuntimeError("local fixture failure")), - ) - - assert _run_main(module, tmp_path) == 1 - PY - - - name: Prove the regression test is red before implementation - run: | - set -euo pipefail - set +e - python -m pytest tests/test_coverage_materializer_failure_diagnostics.py -q \ - >"${RUNNER_TEMP}/coverage-diagnostics-red.log" 2>&1 - status=$? - set -e - cat "${RUNNER_TEMP}/coverage-diagnostics-red.log" - if [ "$status" -eq 0 ]; then - echo "::error::Regression test unexpectedly passed before the implementation." - exit 1 - fi - if ! grep -Fq "coverage_summary< None: - """Replace one exact trusted source fragment or fail closed.""" - text = path.read_text(encoding="utf-8") - if text.count(old) != 1: - raise SystemExit( - f"expected exactly one source fragment in {path}, found {text.count(old)}" - ) - path.write_text(text.replace(old, new, 1), encoding="utf-8") - - - helper = '''\n\ndef _publish_coverage_failure_summary(\n stage: str, error: BaseException, remediation: str\n) -> None:\n """Publish bounded exact setup failure evidence for deterministic reviews."""\n github_output = os.environ.get("GITHUB_OUTPUT")\n if not github_output:\n return\n\n delimiter = "CWL_COVERAGE_SUMMARY_EOF"\n safe_stage = html.escape(" ".join(stage.split())[:256], quote=True).replace(\n delimiter, "CWL_COVERAGE_SUMMARY_END"\n )\n safe_reason = html.escape(\n f"{error.__class__.__name__}: {' '.join(str(error).split())}"[:4096],\n quote=True,\n ).replace(delimiter, "CWL_COVERAGE_SUMMARY_END")\n safe_remediation = html.escape(\n " ".join(remediation.split())[:1024], quote=True\n ).replace(delimiter, "CWL_COVERAGE_SUMMARY_END")\n summary = (\n "## Coverage Decision\\n"\n "- Result: FAIL\\n"\n f"- Failed stage: {safe_stage}\\n"\n "- Exact failure:\\n"\n f"
{safe_reason}
\\n"\n f"- Next action: {safe_remediation}\\n"\n )\n with pathlib.Path(github_output).open("a", encoding="utf-8") as output:\n output.write(\n f"coverage_summary<<{delimiter}\\n{summary}{delimiter}\\n"\n )\n''' - - javascript_path = Path("scripts/ci/materialize_base_javascript_packages.py") - replace_once( - javascript_path, - "import argparse\nimport json\nimport pathlib\n", - "import argparse\nimport html\nimport json\nimport os\nimport pathlib\n", - ) - replace_once( - javascript_path, - "\n\ndef main(argv: list[str] | None = None) -> int:\n", - helper + "\n\ndef main(argv: list[str] | None = None) -> int:\n", - ) - replace_once( - javascript_path, - ''' except (OSError, RuntimeError, ValueError) as exc:\n print(\n f"::error::Could not materialize base JavaScript package locks: {exc}",\n file=sys.stderr,\n )\n return 1\n''', - ''' except (OSError, RuntimeError, ValueError) as exc:\n print(\n f"::error::Could not materialize base JavaScript package locks: {exc}",\n file=sys.stderr,\n )\n _publish_coverage_failure_summary(\n "Base JavaScript package lock materialization",\n exc,\n "Repair or regenerate the reported lock entry so every non-link "\n "package selected for the networked cache is registry- and "\n "SHA-512-bounded, then rerun the current-head coverage-evidence job.",\n )\n return 1\n''', - ) - - python_path = Path("scripts/ci/materialize_base_python_requirements.py") - replace_once( - python_path, - "import argparse\nimport fnmatch\nimport json\n", - "import argparse\nimport fnmatch\nimport html\nimport json\nimport os\n", - ) - replace_once( - python_path, - "\n\ndef main(argv: list[str] | None = None) -> int:\n", - helper + "\n\ndef main(argv: list[str] | None = None) -> int:\n", - ) - replace_once( - python_path, - ''' except (OSError, RuntimeError, ValueError) as exc:\n print(\n f"::error::Could not materialize base Python locks: {exc}", file=sys.stderr\n )\n return 1\n''', - ''' except (OSError, RuntimeError, ValueError) as exc:\n print(\n f"::error::Could not materialize base Python locks: {exc}", file=sys.stderr\n )\n _publish_coverage_failure_summary(\n "Base Python lock materialization",\n exc,\n "Repair the reported trusted lock or Git metadata boundary, then "\n "rerun the current-head coverage-evidence job.",\n )\n return 1\n''', - ) - PY - - - name: Verify focused regression, coverage, docstrings, and syntax - run: | - set -euo pipefail - python -m pytest \ - tests/test_coverage_materializer_failure_diagnostics.py \ - tests/test_materialize_base_javascript_packages.py \ - tests/test_materialize_base_python_requirements.py \ - --cov=scripts.ci.materialize_base_javascript_packages \ - --cov=scripts.ci.materialize_base_python_requirements \ - --cov-branch \ - --cov-fail-under=100 \ - -q - python -m interrogate \ - --fail-under 100 \ - scripts/ci/materialize_base_javascript_packages.py \ - scripts/ci/materialize_base_python_requirements.py - python -m compileall -q \ - scripts/ci/materialize_base_javascript_packages.py \ - scripts/ci/materialize_base_python_requirements.py \ - tests/test_coverage_materializer_failure_diagnostics.py - git diff --check - - - name: Verify OpenCode workflow contracts - run: | - set -euo pipefail - python -m pytest \ - tests/test_opencode_agent_contract.py \ - tests/test_opencode_model_pool_runner.py \ - tests/test_sanitize_github_output_summary.py \ - -q - - - name: Commit verified repair and remove bootstrap workflow - env: - BRANCH_NAME: fix/opencode-coverage-failure-diagnostics - run: | - set -euo pipefail - rm -f .github/workflows/one-shot-fix-opencode-coverage-diagnostics.yml - git config user.name "opencode-agent[bot]" - git config user.email "219766164+opencode-agent[bot]@users.noreply.github.com" - git add \ - scripts/ci/materialize_base_javascript_packages.py \ - scripts/ci/materialize_base_python_requirements.py \ - tests/test_coverage_materializer_failure_diagnostics.py \ - .github/workflows/one-shot-fix-opencode-coverage-diagnostics.yml - git diff --cached --check - git commit -m "fix(opencode-review): surface exact coverage setup failures" - git push origin "HEAD:${BRANCH_NAME}" From 1c029587c15a827ab9b1b81399cdb4d011936aeb Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 07:56:07 +0900 Subject: [PATCH 06/57] fix(opencode-review): publish JavaScript lock failure evidence --- .../materialize_base_javascript_packages.py | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/scripts/ci/materialize_base_javascript_packages.py b/scripts/ci/materialize_base_javascript_packages.py index 407c17aa1..99802d6b6 100644 --- a/scripts/ci/materialize_base_javascript_packages.py +++ b/scripts/ci/materialize_base_javascript_packages.py @@ -11,7 +11,9 @@ from __future__ import annotations import argparse +import html import json +import os import pathlib import re import subprocess @@ -424,6 +426,37 @@ def materialize( return manifest +def _publish_coverage_failure_summary( + stage: str, error: BaseException, remediation: str +) -> None: + """Publish bounded exact setup failure evidence for deterministic reviews.""" + github_output = os.environ.get("GITHUB_OUTPUT") + if not github_output: + return + + delimiter = "CWL_COVERAGE_SUMMARY_EOF" + safe_stage = html.escape(" ".join(stage.split())[:256], quote=True).replace( + delimiter, "CWL_COVERAGE_SUMMARY_END" + ) + safe_reason = html.escape( + f"{error.__class__.__name__}: {' '.join(str(error).split())}"[:4096], + quote=True, + ).replace(delimiter, "CWL_COVERAGE_SUMMARY_END") + safe_remediation = html.escape( + " ".join(remediation.split())[:1024], quote=True + ).replace(delimiter, "CWL_COVERAGE_SUMMARY_END") + summary = ( + "## Coverage Decision\n" + "- Result: FAIL\n" + f"- Failed stage: {safe_stage}\n" + "- Exact failure:\n" + f"
{safe_reason}
\n" + f"- Next action: {safe_remediation}\n" + ) + with pathlib.Path(github_output).open("a", encoding="utf-8") as output: + output.write(f"coverage_summary<<{delimiter}\n{summary}{delimiter}\n") + + def main(argv: list[str] | None = None) -> int: """Materialize trusted JavaScript locks and report their exact revisions.""" parser = argparse.ArgumentParser() @@ -445,6 +478,13 @@ def main(argv: list[str] | None = None) -> int: f"::error::Could not materialize base JavaScript package locks: {exc}", file=sys.stderr, ) + _publish_coverage_failure_summary( + "Base JavaScript package lock materialization", + exc, + "Repair or regenerate the reported lock entry so every non-link " + "package selected for the networked cache is registry- and " + "SHA-512-bounded, then rerun the current-head coverage-evidence job.", + ) return 1 if manifest: From cb1ed50adf9f7f0d14a23271286286ccd1435bd3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 07:56:48 +0900 Subject: [PATCH 07/57] fix(opencode-review): publish Python lock failure evidence --- .../materialize_base_python_requirements.py | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/scripts/ci/materialize_base_python_requirements.py b/scripts/ci/materialize_base_python_requirements.py index 8158372df..4d3775962 100644 --- a/scripts/ci/materialize_base_python_requirements.py +++ b/scripts/ci/materialize_base_python_requirements.py @@ -5,7 +5,9 @@ import argparse import fnmatch +import html import json +import os import pathlib import re import shutil @@ -227,6 +229,37 @@ def materialize( return manifest +def _publish_coverage_failure_summary( + stage: str, error: BaseException, remediation: str +) -> None: + """Publish bounded exact setup failure evidence for deterministic reviews.""" + github_output = os.environ.get("GITHUB_OUTPUT") + if not github_output: + return + + delimiter = "CWL_COVERAGE_SUMMARY_EOF" + safe_stage = html.escape(" ".join(stage.split())[:256], quote=True).replace( + delimiter, "CWL_COVERAGE_SUMMARY_END" + ) + safe_reason = html.escape( + f"{error.__class__.__name__}: {' '.join(str(error).split())}"[:4096], + quote=True, + ).replace(delimiter, "CWL_COVERAGE_SUMMARY_END") + safe_remediation = html.escape( + " ".join(remediation.split())[:1024], quote=True + ).replace(delimiter, "CWL_COVERAGE_SUMMARY_END") + summary = ( + "## Coverage Decision\n" + "- Result: FAIL\n" + f"- Failed stage: {safe_stage}\n" + "- Exact failure:\n" + f"
{safe_reason}
\n" + f"- Next action: {safe_remediation}\n" + ) + with pathlib.Path(github_output).open("a", encoding="utf-8") as output: + output.write(f"coverage_summary<<{delimiter}\n{summary}{delimiter}\n") + + def main(argv: list[str] | None = None) -> int: """Materialize base locks and report exactly which trusted paths were selected.""" parser = argparse.ArgumentParser() @@ -241,6 +274,12 @@ def main(argv: list[str] | None = None) -> int: print( f"::error::Could not materialize base Python locks: {exc}", file=sys.stderr ) + _publish_coverage_failure_summary( + "Base Python lock materialization", + exc, + "Repair the reported trusted lock or Git metadata boundary, then " + "rerun the current-head coverage-evidence job.", + ) return 1 if manifest: From b59d2bb7dd72d0e2e9bcb2c2fc41541059ddc99b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 07:57:15 +0900 Subject: [PATCH 08/57] test(opencode-review): cover exact coverage setup diagnostics --- ...verage_materializer_failure_diagnostics.py | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 tests/test_coverage_materializer_failure_diagnostics.py diff --git a/tests/test_coverage_materializer_failure_diagnostics.py b/tests/test_coverage_materializer_failure_diagnostics.py new file mode 100644 index 000000000..7a2e0b9cc --- /dev/null +++ b/tests/test_coverage_materializer_failure_diagnostics.py @@ -0,0 +1,111 @@ +from __future__ import annotations + +from pathlib import Path +from types import ModuleType +from typing import Callable + +import pytest + +from scripts.ci import materialize_base_javascript_packages as javascript_materializer +from scripts.ci import materialize_base_python_requirements as python_materializer + + +def _failing_materializer(error: BaseException) -> Callable[..., None]: + """Return a materializer stub that raises the supplied failure.""" + + def fail(*_args: object, **_kwargs: object) -> None: + raise error + + return fail + + +def _run_main(module: ModuleType, tmp_path: Path) -> int: + """Invoke one materializer CLI with a valid-shaped isolated argument set.""" + return module.main( + [ + "--repo-root", + str(tmp_path), + "--base-sha", + "a" * 40, + "--output-dir", + str(tmp_path / "output"), + ] + ) + + +def test_javascript_failure_publishes_exact_coverage_reason( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """The deterministic review receives the exact early npm-lock failure.""" + output_file = tmp_path / "github-output" + exact_reason = ( + "current-head npm lock package-lock.json package " + "apps/desktop/node_modules/@types/react-dom must pin a registry " + "tarball and SHA-512 integrity" + ) + monkeypatch.setenv("GITHUB_OUTPUT", str(output_file)) + monkeypatch.setattr( + javascript_materializer, + "materialize", + _failing_materializer(ValueError(exact_reason)), + ) + + assert _run_main(javascript_materializer, tmp_path) == 1 + + published = output_file.read_text(encoding="utf-8") + assert "coverage_summary< None: + """Early Python-lock failures remain concrete without output-file injection.""" + output_file = tmp_path / "github-output" + monkeypatch.setenv("GITHUB_OUTPUT", str(output_file)) + monkeypatch.setattr( + python_materializer, + "materialize", + _failing_materializer( + OSError( + "fixture \nCWL_COVERAGE_SUMMARY_EOF " + ("x" * 5000) + ) + ), + ) + + assert _run_main(python_materializer, tmp_path) == 1 + + published = output_file.read_text(encoding="utf-8") + assert "- Failed stage: Base Python lock materialization" in published + assert "OSError: fixture <unsafe> CWL_COVERAGE_SUMMARY_END" in published + assert "" not in published + assert published.count("CWL_COVERAGE_SUMMARY_EOF\n") == 2 + assert len(published) < 5000 + + +@pytest.mark.parametrize( + "module", + [javascript_materializer, python_materializer], + ids=["javascript", "python"], +) +def test_failure_diagnostics_are_optional_outside_github_actions( + module: ModuleType, + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Local CLI failures keep their status when no Actions output file exists.""" + monkeypatch.delenv("GITHUB_OUTPUT", raising=False) + monkeypatch.setattr( + module, + "materialize", + _failing_materializer(RuntimeError("local fixture failure")), + ) + + assert _run_main(module, tmp_path) == 1 + assert not (tmp_path / "github-output").exists() From 94bfa75574f9dc6832ffa96c6413ae5209a4842d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 08:00:07 +0900 Subject: [PATCH 09/57] ci(opencode-review): verify coverage failure diagnostics --- .../opencode-coverage-diagnostics-ci.yml | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 .github/workflows/opencode-coverage-diagnostics-ci.yml diff --git a/.github/workflows/opencode-coverage-diagnostics-ci.yml b/.github/workflows/opencode-coverage-diagnostics-ci.yml new file mode 100644 index 000000000..d2ef1258f --- /dev/null +++ b/.github/workflows/opencode-coverage-diagnostics-ci.yml @@ -0,0 +1,90 @@ +name: OpenCode Coverage Diagnostics CI + +on: + pull_request: + branches: [main] + paths: + - "scripts/ci/materialize_base_javascript_packages.py" + - "scripts/ci/materialize_base_python_requirements.py" + - "tests/test_materialize_base_javascript_packages.py" + - "tests/test_materialize_base_python_requirements.py" + - "tests/test_coverage_materializer_failure_diagnostics.py" + - "requirements-opencode-review-ci-hashes.txt" + - "pyproject.toml" + - ".github/workflows/opencode-coverage-diagnostics-ci.yml" + push: + branches: [main] + paths: + - "scripts/ci/materialize_base_javascript_packages.py" + - "scripts/ci/materialize_base_python_requirements.py" + - "tests/test_materialize_base_javascript_packages.py" + - "tests/test_materialize_base_python_requirements.py" + - "tests/test_coverage_materializer_failure_diagnostics.py" + - "requirements-opencode-review-ci-hashes.txt" + - "pyproject.toml" + - ".github/workflows/opencode-coverage-diagnostics-ci.yml" + +concurrency: + group: opencode-coverage-diagnostics-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + diagnostics-contract: + name: Python ${{ matrix.python-version }} contract + runs-on: ubuntu-latest + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + python-version: ["3.10", "3.14"] + steps: + - name: Harden runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: ${{ matrix.python-version }} + cache: pip + cache-dependency-path: requirements-opencode-review-ci-hashes.txt + + - name: Install hash-locked test tooling + run: >- + python -m pip install --disable-pip-version-check --require-hashes + -r requirements-opencode-review-ci-hashes.txt + + - name: Run materializer regression tests with full branch coverage + run: | + python -m pytest \ + tests/test_materialize_base_javascript_packages.py \ + tests/test_materialize_base_python_requirements.py \ + tests/test_coverage_materializer_failure_diagnostics.py \ + --cov=scripts.ci.materialize_base_javascript_packages \ + --cov=scripts.ci.materialize_base_python_requirements \ + --cov-branch \ + --cov-fail-under=100 \ + -q + + - name: Enforce complete production docstrings + run: | + python -m interrogate \ + --fail-under 100 \ + scripts/ci/materialize_base_javascript_packages.py \ + scripts/ci/materialize_base_python_requirements.py + + - name: Compile changed Python surfaces + run: | + python -m compileall -q \ + scripts/ci/materialize_base_javascript_packages.py \ + scripts/ci/materialize_base_python_requirements.py \ + tests/test_coverage_materializer_failure_diagnostics.py From 2defa39ac6ca84a0d72f7771b8cc319fa8099c7a Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 08:07:10 +0900 Subject: [PATCH 10/57] test(opencode-review): cover materializer branch contracts --- ...verage_materializer_failure_diagnostics.py | 104 ++++++++++++++++++ 1 file changed, 104 insertions(+) diff --git a/tests/test_coverage_materializer_failure_diagnostics.py b/tests/test_coverage_materializer_failure_diagnostics.py index 7a2e0b9cc..740f946bb 100644 --- a/tests/test_coverage_materializer_failure_diagnostics.py +++ b/tests/test_coverage_materializer_failure_diagnostics.py @@ -1,5 +1,6 @@ from __future__ import annotations +import json from pathlib import Path from types import ModuleType from typing import Callable @@ -109,3 +110,106 @@ def test_failure_diagnostics_are_optional_outside_github_actions( assert _run_main(module, tmp_path) == 1 assert not (tmp_path / "github-output").exists() + + +def test_javascript_tree_filter_continues_after_non_regular_entries( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Symlinks and gitlinks cannot hide later regular lock inputs.""" + tree_output = ( + b"120000 blob " + (b"1" * 40) + b"\tsymlinked-lock\0" + b"160000 commit " + (b"2" * 40) + b"\tvendored-module\0" + b"100644 blob " + (b"3" * 40) + b"\tpackage.json\0" + ) + monkeypatch.setattr( + javascript_materializer, + "_git", + lambda *_args: tree_output, + ) + + assert javascript_materializer._regular_base_paths(tmp_path, "a" * 40) == { + "package.json" + } + + +def test_npm_project_without_packages_map_keeps_root_inputs( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Legacy npm locks without a packages map still preserve trusted root inputs.""" + regular_paths = {"package.json", "package-lock.json"} + lock_content = json.dumps({"name": "legacy", "lockfileVersion": 1}).encode() + monkeypatch.setattr( + javascript_materializer, + "_regular_base_paths", + lambda *_args: regular_paths, + ) + + def fake_git(_repo_root: Path, _command: str, object_spec: str) -> bytes: + if object_spec.endswith(":package.json"): + return b'{"name":"legacy"}' + if object_spec.endswith(":package-lock.json"): + return lock_content + raise AssertionError(f"unexpected git object: {object_spec}") + + monkeypatch.setattr(javascript_materializer, "_git", fake_git) + + projects = javascript_materializer.base_npm_projects(tmp_path, "a" * 40) + + assert projects == [ + ( + "package-lock.json", + "npm", + { + "package.json": b'{"name":"legacy"}', + "package-lock.json": lock_content, + }, + ) + ] + + +def test_npm_workspace_scan_iterates_multiple_regular_manifests( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Every regular workspace manifest is copied from the validated revision.""" + regular_paths = { + "package.json", + "package-lock.json", + "packages/alpha/package.json", + "packages/beta/package.json", + } + lock_content = json.dumps( + { + "name": "workspace-root", + "lockfileVersion": 3, + "packages": { + "": {"name": "workspace-root"}, + "packages/alpha": {"name": "alpha"}, + "packages/beta": {"name": "beta"}, + }, + } + ).encode() + blobs = { + "package.json": b'{"name":"workspace-root"}', + "package-lock.json": lock_content, + "packages/alpha/package.json": b'{"name":"alpha"}', + "packages/beta/package.json": b'{"name":"beta"}', + } + monkeypatch.setattr( + javascript_materializer, + "_regular_base_paths", + lambda *_args: regular_paths, + ) + + def fake_git(_repo_root: Path, _command: str, object_spec: str) -> bytes: + return blobs[object_spec.split(":", 1)[1]] + + monkeypatch.setattr(javascript_materializer, "_git", fake_git) + + projects = javascript_materializer.base_npm_projects(tmp_path, "a" * 40) + + assert len(projects) == 1 + assert projects[0][2]["packages/alpha/package.json"] == b'{"name":"alpha"}' + assert projects[0][2]["packages/beta/package.json"] == b'{"name":"beta"}' From cad711b7546e073b7ac88bfe4c8db93f66806a87 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 08:07:46 +0900 Subject: [PATCH 11/57] ci(opencode-review): separate minimum-runtime and full-quality gates --- .../opencode-coverage-diagnostics-ci.yml | 72 ++++++++++++++++--- 1 file changed, 64 insertions(+), 8 deletions(-) diff --git a/.github/workflows/opencode-coverage-diagnostics-ci.yml b/.github/workflows/opencode-coverage-diagnostics-ci.yml index d2ef1258f..25ab75992 100644 --- a/.github/workflows/opencode-coverage-diagnostics-ci.yml +++ b/.github/workflows/opencode-coverage-diagnostics-ci.yml @@ -32,14 +32,70 @@ permissions: contents: read jobs: - diagnostics-contract: - name: Python ${{ matrix.python-version }} contract + minimum-python-contract: + name: Python 3.10 runtime contract + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Harden runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Checkout + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Set up minimum supported Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.10" + + - name: Compile production modules on Python 3.10 + run: | + python -m compileall -q \ + scripts/ci/materialize_base_javascript_packages.py \ + scripts/ci/materialize_base_python_requirements.py + + - name: Exercise exact failure evidence on Python 3.10 + run: | + python - <<'PY' + import os + import pathlib + import tempfile + + from scripts.ci import materialize_base_javascript_packages as javascript + from scripts.ci import materialize_base_python_requirements as python + + with tempfile.TemporaryDirectory() as directory: + output = pathlib.Path(directory) / "github-output" + os.environ["GITHUB_OUTPUT"] = str(output) + exact_reason = ( + "current-head npm lock package-lock.json package " + "apps/desktop/node_modules/@types/react-dom must pin a registry " + "tarball and SHA-512 integrity" + ) + javascript._publish_coverage_failure_summary( + "Base JavaScript package lock materialization", + ValueError(exact_reason), + "Repair the lock and rerun coverage-evidence.", + ) + python._publish_coverage_failure_summary( + "Base Python lock materialization", + OSError("fixture \nCWL_COVERAGE_SUMMARY_EOF"), + "Repair the trusted lock and rerun coverage-evidence.", + ) + published = output.read_text(encoding="utf-8") + assert f"ValueError: {exact_reason}" in published + assert "OSError: fixture <unsafe> CWL_COVERAGE_SUMMARY_END" in published + assert published.count("coverage_summary< Date: Wed, 5 Aug 2026 08:09:28 +0900 Subject: [PATCH 12/57] test(opencode-review): cover absent workspace manifest branch --- tests/test_coverage_materializer_failure_diagnostics.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_coverage_materializer_failure_diagnostics.py b/tests/test_coverage_materializer_failure_diagnostics.py index 740f946bb..d612c13c3 100644 --- a/tests/test_coverage_materializer_failure_diagnostics.py +++ b/tests/test_coverage_materializer_failure_diagnostics.py @@ -169,11 +169,11 @@ def fake_git(_repo_root: Path, _command: str, object_spec: str) -> bytes: ] -def test_npm_workspace_scan_iterates_multiple_regular_manifests( +def test_npm_workspace_scan_iterates_missing_and_regular_manifests( tmp_path: Path, monkeypatch: pytest.MonkeyPatch, ) -> None: - """Every regular workspace manifest is copied from the validated revision.""" + """Missing workspace manifests are skipped without hiding later regular ones.""" regular_paths = { "package.json", "package-lock.json", @@ -186,6 +186,7 @@ def test_npm_workspace_scan_iterates_multiple_regular_manifests( "lockfileVersion": 3, "packages": { "": {"name": "workspace-root"}, + "packages/aaa-missing": {"name": "missing"}, "packages/alpha": {"name": "alpha"}, "packages/beta": {"name": "beta"}, }, @@ -211,5 +212,6 @@ def fake_git(_repo_root: Path, _command: str, object_spec: str) -> bytes: projects = javascript_materializer.base_npm_projects(tmp_path, "a" * 40) assert len(projects) == 1 + assert "packages/aaa-missing/package.json" not in projects[0][2] assert projects[0][2]["packages/alpha/package.json"] == b'{"name":"alpha"}' assert projects[0][2]["packages/beta/package.json"] == b'{"name":"beta"}' From 5bf0fdbdddcc48da2d400340e92a77aa82f96190 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 08:18:44 +0900 Subject: [PATCH 13/57] chore: bootstrap Strix security lock refresh --- .../one-shot-refresh-strix-security-lock.yml | 169 ++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 .github/workflows/one-shot-refresh-strix-security-lock.yml diff --git a/.github/workflows/one-shot-refresh-strix-security-lock.yml b/.github/workflows/one-shot-refresh-strix-security-lock.yml new file mode 100644 index 000000000..7758c66c2 --- /dev/null +++ b/.github/workflows/one-shot-refresh-strix-security-lock.yml @@ -0,0 +1,169 @@ +name: One-shot Strix security lock refresh + +on: + push: + branches: + - fix/opencode-coverage-failure-diagnostics + +permissions: + contents: write + +concurrency: + group: one-shot-strix-security-lock-refresh + cancel-in-progress: false + +jobs: + refresh: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Harden runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Checkout repair branch + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + fetch-depth: 0 + ref: fix/opencode-coverage-failure-diagnostics + + - name: Set up lock compiler Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.14" + + - name: Install hash-locked uv compiler + run: >- + python -m pip install --disable-pip-version-check --require-hashes + -r requirements-opencode-review-ci-hashes.txt + + - name: Raise vulnerable Strix dependency floors + run: | + set -euo pipefail + python - <<'PY' + from pathlib import Path + + requirements = Path("requirements-strix-ci.txt") + content = requirements.read_text(encoding="utf-8") + old = ( + "strix-agent==1.0.4\n" + "google-cloud-aiplatform==1.133.0\n" + "protobuf<7.0.0\n" + "cryptography==49.0.0\n" + ) + new = ( + "strix-agent==1.0.4\n" + "aiohttp==3.14.3\n" + "google-cloud-aiplatform==1.133.0\n" + "protobuf<7.0.0\n" + "cryptography==50.0.0\n" + ) + if content.count(old) != 1: + raise SystemExit("Strix requirements did not match the reviewed security baseline") + requirements.write_text(content.replace(old, new, 1), encoding="utf-8") + PY + + - name: Regenerate the Strix hash lock from its canonical input + run: | + set -euo pipefail + uv pip compile \ + --generate-hashes \ + --python-version 3.13 \ + --python-platform x86_64-manylinux_2_28 \ + --output-file requirements-strix-ci-hashes.txt \ + requirements-strix-ci.txt + git diff --check + + - name: Set up the Strix runtime Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.13" + + - name: Install and smoke-test the regenerated hash lock + run: | + set -euo pipefail + python -m venv "${RUNNER_TEMP}/strix-runtime" + "${RUNNER_TEMP}/strix-runtime/bin/python" -m pip install \ + --disable-pip-version-check \ + --require-hashes \ + --only-binary=:all: \ + -r requirements-strix-ci-hashes.txt + "${RUNNER_TEMP}/strix-runtime/bin/python" - <<'PY' + import aiohttp + import cryptography + import strix + + assert aiohttp.__version__ == "3.14.3", aiohttp.__version__ + assert cryptography.__version__ == "50.0.0", cryptography.__version__ + assert strix is not None + PY + + - name: Set up the audit Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.12" + + - name: Audit the regenerated lock + run: | + set -euo pipefail + python -m venv "${RUNNER_TEMP}/pip-audit" + "${RUNNER_TEMP}/pip-audit/bin/python" -m pip install \ + --disable-pip-version-check \ + --require-hashes \ + -r requirements-pip-audit-ci-hashes.txt + "${RUNNER_TEMP}/pip-audit/bin/pip-audit" \ + --strict \ + --desc=on \ + -r requirements-strix-ci-hashes.txt + "${RUNNER_TEMP}/pip-audit/bin/pip-audit" \ + --strict \ + --desc=on \ + -r requirements-strix-ci.txt + + - name: Add a durable Strix security-lock contract test + run: | + set -euo pipefail + cat > tests/test_strix_dependency_security_floor.py <<'PY' + """Contracts for the security-reviewed Strix dependency lock.""" + + from __future__ import annotations + + from pathlib import Path + + + def test_strix_requirements_pin_reviewed_security_versions() -> None: + """The canonical input pins versions that close the August 2026 advisories.""" + requirements = Path("requirements-strix-ci.txt").read_text(encoding="utf-8") + assert "aiohttp==3.14.3\n" in requirements + assert "cryptography==50.0.0\n" in requirements + assert "aiohttp==3.14.1\n" not in requirements + assert "cryptography==49.0.0\n" not in requirements + + + def test_strix_hash_lock_matches_the_canonical_security_pins() -> None: + """The generated lock retains the reviewed direct security pins.""" + lock = Path("requirements-strix-ci-hashes.txt").read_text(encoding="utf-8") + assert "aiohttp==3.14.3 \\\n" in lock + assert "cryptography==50.0.0 \\\n" in lock + assert "aiohttp==3.14.1 \\\n" not in lock + assert "cryptography==49.0.0 \\\n" not in lock + PY + python -m compileall -q tests/test_strix_dependency_security_floor.py + + - name: Commit the audited lock and remove this bootstrap workflow + env: + BRANCH_NAME: fix/opencode-coverage-failure-diagnostics + run: | + set -euo pipefail + rm -f .github/workflows/one-shot-refresh-strix-security-lock.yml + git config user.name "opencode-agent[bot]" + git config user.email "219766164+opencode-agent[bot]@users.noreply.github.com" + git add \ + requirements-strix-ci.txt \ + requirements-strix-ci-hashes.txt \ + tests/test_strix_dependency_security_floor.py \ + .github/workflows/one-shot-refresh-strix-security-lock.yml + git diff --cached --check + git commit -m "fix(security): refresh vulnerable Strix dependencies" + git push origin "HEAD:${BRANCH_NAME}" From 16e438ca9dab3a2d59c008ea8cf8f1af2d6769c8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 08:20:17 +0900 Subject: [PATCH 14/57] ci: run Strix lock refresh from same-repository PR --- .github/workflows/one-shot-refresh-strix-security-lock.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/one-shot-refresh-strix-security-lock.yml b/.github/workflows/one-shot-refresh-strix-security-lock.yml index 7758c66c2..b25289576 100644 --- a/.github/workflows/one-shot-refresh-strix-security-lock.yml +++ b/.github/workflows/one-shot-refresh-strix-security-lock.yml @@ -1,6 +1,9 @@ name: One-shot Strix security lock refresh on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened] push: branches: - fix/opencode-coverage-failure-diagnostics @@ -14,6 +17,10 @@ concurrency: jobs: refresh: + if: >- + github.event_name == 'push' || + (github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.head.ref == 'fix/opencode-coverage-failure-diagnostics') runs-on: ubuntu-latest timeout-minutes: 30 steps: From 9b273e06cb77be6c980b263e217ef90d67efbfe1 Mon Sep 17 00:00:00 2001 From: "opencode-agent[bot]" <219766164+opencode-agent[bot]@users.noreply.github.com> Date: Tue, 4 Aug 2026 23:22:21 +0000 Subject: [PATCH 15/57] fix(security): refresh vulnerable Strix dependencies --- .../one-shot-refresh-strix-security-lock.yml | 176 --------- requirements-strix-ci-hashes.txt | 341 +++++++++--------- requirements-strix-ci.txt | 3 +- tests/test_strix_dependency_security_floor.py | 23 ++ 4 files changed, 196 insertions(+), 347 deletions(-) delete mode 100644 .github/workflows/one-shot-refresh-strix-security-lock.yml create mode 100644 tests/test_strix_dependency_security_floor.py diff --git a/.github/workflows/one-shot-refresh-strix-security-lock.yml b/.github/workflows/one-shot-refresh-strix-security-lock.yml deleted file mode 100644 index b25289576..000000000 --- a/.github/workflows/one-shot-refresh-strix-security-lock.yml +++ /dev/null @@ -1,176 +0,0 @@ -name: One-shot Strix security lock refresh - -on: - pull_request: - branches: [main] - types: [opened, synchronize, reopened] - push: - branches: - - fix/opencode-coverage-failure-diagnostics - -permissions: - contents: write - -concurrency: - group: one-shot-strix-security-lock-refresh - cancel-in-progress: false - -jobs: - refresh: - if: >- - github.event_name == 'push' || - (github.event.pull_request.head.repo.full_name == github.repository && - github.event.pull_request.head.ref == 'fix/opencode-coverage-failure-diagnostics') - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - name: Harden runner - uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 - with: - egress-policy: audit - - - name: Checkout repair branch - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - fetch-depth: 0 - ref: fix/opencode-coverage-failure-diagnostics - - - name: Set up lock compiler Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: "3.14" - - - name: Install hash-locked uv compiler - run: >- - python -m pip install --disable-pip-version-check --require-hashes - -r requirements-opencode-review-ci-hashes.txt - - - name: Raise vulnerable Strix dependency floors - run: | - set -euo pipefail - python - <<'PY' - from pathlib import Path - - requirements = Path("requirements-strix-ci.txt") - content = requirements.read_text(encoding="utf-8") - old = ( - "strix-agent==1.0.4\n" - "google-cloud-aiplatform==1.133.0\n" - "protobuf<7.0.0\n" - "cryptography==49.0.0\n" - ) - new = ( - "strix-agent==1.0.4\n" - "aiohttp==3.14.3\n" - "google-cloud-aiplatform==1.133.0\n" - "protobuf<7.0.0\n" - "cryptography==50.0.0\n" - ) - if content.count(old) != 1: - raise SystemExit("Strix requirements did not match the reviewed security baseline") - requirements.write_text(content.replace(old, new, 1), encoding="utf-8") - PY - - - name: Regenerate the Strix hash lock from its canonical input - run: | - set -euo pipefail - uv pip compile \ - --generate-hashes \ - --python-version 3.13 \ - --python-platform x86_64-manylinux_2_28 \ - --output-file requirements-strix-ci-hashes.txt \ - requirements-strix-ci.txt - git diff --check - - - name: Set up the Strix runtime Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: "3.13" - - - name: Install and smoke-test the regenerated hash lock - run: | - set -euo pipefail - python -m venv "${RUNNER_TEMP}/strix-runtime" - "${RUNNER_TEMP}/strix-runtime/bin/python" -m pip install \ - --disable-pip-version-check \ - --require-hashes \ - --only-binary=:all: \ - -r requirements-strix-ci-hashes.txt - "${RUNNER_TEMP}/strix-runtime/bin/python" - <<'PY' - import aiohttp - import cryptography - import strix - - assert aiohttp.__version__ == "3.14.3", aiohttp.__version__ - assert cryptography.__version__ == "50.0.0", cryptography.__version__ - assert strix is not None - PY - - - name: Set up the audit Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: "3.12" - - - name: Audit the regenerated lock - run: | - set -euo pipefail - python -m venv "${RUNNER_TEMP}/pip-audit" - "${RUNNER_TEMP}/pip-audit/bin/python" -m pip install \ - --disable-pip-version-check \ - --require-hashes \ - -r requirements-pip-audit-ci-hashes.txt - "${RUNNER_TEMP}/pip-audit/bin/pip-audit" \ - --strict \ - --desc=on \ - -r requirements-strix-ci-hashes.txt - "${RUNNER_TEMP}/pip-audit/bin/pip-audit" \ - --strict \ - --desc=on \ - -r requirements-strix-ci.txt - - - name: Add a durable Strix security-lock contract test - run: | - set -euo pipefail - cat > tests/test_strix_dependency_security_floor.py <<'PY' - """Contracts for the security-reviewed Strix dependency lock.""" - - from __future__ import annotations - - from pathlib import Path - - - def test_strix_requirements_pin_reviewed_security_versions() -> None: - """The canonical input pins versions that close the August 2026 advisories.""" - requirements = Path("requirements-strix-ci.txt").read_text(encoding="utf-8") - assert "aiohttp==3.14.3\n" in requirements - assert "cryptography==50.0.0\n" in requirements - assert "aiohttp==3.14.1\n" not in requirements - assert "cryptography==49.0.0\n" not in requirements - - - def test_strix_hash_lock_matches_the_canonical_security_pins() -> None: - """The generated lock retains the reviewed direct security pins.""" - lock = Path("requirements-strix-ci-hashes.txt").read_text(encoding="utf-8") - assert "aiohttp==3.14.3 \\\n" in lock - assert "cryptography==50.0.0 \\\n" in lock - assert "aiohttp==3.14.1 \\\n" not in lock - assert "cryptography==49.0.0 \\\n" not in lock - PY - python -m compileall -q tests/test_strix_dependency_security_floor.py - - - name: Commit the audited lock and remove this bootstrap workflow - env: - BRANCH_NAME: fix/opencode-coverage-failure-diagnostics - run: | - set -euo pipefail - rm -f .github/workflows/one-shot-refresh-strix-security-lock.yml - git config user.name "opencode-agent[bot]" - git config user.email "219766164+opencode-agent[bot]@users.noreply.github.com" - git add \ - requirements-strix-ci.txt \ - requirements-strix-ci-hashes.txt \ - tests/test_strix_dependency_security_floor.py \ - .github/workflows/one-shot-refresh-strix-security-lock.yml - git diff --cached --check - git commit -m "fix(security): refresh vulnerable Strix dependencies" - git push origin "HEAD:${BRANCH_NAME}" diff --git a/requirements-strix-ci-hashes.txt b/requirements-strix-ci-hashes.txt index e2c8f00eb..c305e9c84 100644 --- a/requirements-strix-ci-hashes.txt +++ b/requirements-strix-ci-hashes.txt @@ -4,127 +4,128 @@ aiohappyeyeballs==2.7.1 \ --hash=sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d \ --hash=sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472 # via aiohttp -aiohttp==3.14.1 \ - --hash=sha256:03ab4530fdcb3a543a122ba4b65ac9919da9fe9f78a03d328a6e38ff962f7aa5 \ - --hash=sha256:07eabb979d236335fed927e137a928c9adfb7df3b9ec7aa31726f133a62be983 \ - --hash=sha256:092e4ce3619a7c6dee52a6bdabda973d9b34b66781f840ce93c7e0cec30cf521 \ - --hash=sha256:10ee9c1753a8f706345b22496c79fbddb5be0599e0823f3738b1534058e25340 \ - --hash=sha256:1601cc37baf5750ccacae618ec2daf020769581695550e3b654a911f859c563d \ - --hash=sha256:1ac8531b638959718e18c2207fbfe297819875da46a740b29dfa29beba64355a \ - --hash=sha256:1b9748363260121d2927704f5d4fc498150669ca3ae93625986ee89c8f80dcd4 \ - --hash=sha256:1c1421eb01d4fd608d88cc8290211d177a58532b55ad94076fb349c5bf467f0a \ - --hash=sha256:1c1af67559445498b502030c35c59db59966f47041ca9de5b4e707f86bd10b5f \ - --hash=sha256:1d459b98a932296c6f0e94f87511a0b1b90a8a02c30a50e60a297619cd5a58ee \ - --hash=sha256:20205f7f5ade7aaec9f4b500549bbc071b046453aed72f9c06dcab87896a83e8 \ - --hash=sha256:23119f8fd4f5d16902ed459b63b100bcd269628075162bddac56cc7b5273b3fb \ - --hash=sha256:237651caadc3a59badd39319c54642b5299e9cc98a3a194310e55d5bb9f5e397 \ - --hash=sha256:24ba13339fed9251d9b1a1bec8c7ab84c0d1675d79d33501e11f94f8b9a84e05 \ - --hash=sha256:250d14af67f6b6a1a4a811049b1afa69d61d617fca6bf33149b3ab1a6dbcf7b8 \ - --hash=sha256:269b76ac5394092b95bc4a098f4fc6c191c083c3bd12775d1e30e663132f6a09 \ - --hash=sha256:27fd7c91e51729b4f7e1577865fa6d34c9adccbc39aabe9000285b48af9f0ec2 \ - --hash=sha256:2964cbf553df4d7a57348da44d961d871895fc1ee4e8c322b2a95612c7b17fba \ - --hash=sha256:2a73f487ab8ef5abbb24b7aa9b73e98eaba9e9e031804ff2416f02eca315ccaf \ - --hash=sha256:2aa92c87868cd13674989f9ee83e5f9f7ea4237589b728048e1f0c8f6caa3271 \ - --hash=sha256:2b7edd08e0a5deb1e8564a2fcd8f4561014a3f05252334671bbf55ddd47db0e5 \ - --hash=sha256:2c840c90759922cb5e6dda94596e079a30fb5a5ba548e7e0dc00574703940847 \ - --hash=sha256:2f73e01dc37122325caf079982621262f96d74823c179038a82fddfc50359264 \ - --hash=sha256:2fbc3ed048b3475b9f0cbcb9978e9d2d3511acd91ead203af26ed9f0056004cf \ - --hash=sha256:2fe3607e71acc6ebb0ec8e492a247bf7a291226192dc0084236dfc12478916f6 \ - --hash=sha256:30099eda75a53c32efb0920e9c33c195314d2cc1c680fbfd30894932ac5f27df \ - --hash=sha256:307f2cff90a764d329e77040603fa032db89c5c24fdad50c4c15334cba744035 \ - --hash=sha256:313701e488100074ce99850404ee36e741abf6330179fec908a1944ecf570126 \ - --hash=sha256:317acd9f8602858dc7d59679812c376c7f0b97bcbbf16e0d6237f54141d8a8a6 \ - --hash=sha256:335c0cc3e3545ce98dcb9cfcb836f40c3411f43fa03dab757597d80c89af8a35 \ - --hash=sha256:34b257ec41345c1e8f2df68fa908a7952f5de932723871eb633ecbbff396c9a4 \ - --hash=sha256:367a9314fdc79dab0fac96e216cb41dd73c85bdca85306ce8999118ba7e0f333 \ - --hash=sha256:38e1e7daaea81df51c952e18483f323d878499a1e2bfe564790e0f9701d6f203 \ - --hash=sha256:3e6fc1a85fa7194a1a7d19f44e8609180f4a8eb5fa4c7ed8b4355f080fad235c \ - --hash=sha256:4132e72c608fe9fecb8f409113567605915b83e9bdd3ea56538d2f9cd35002f1 \ - --hash=sha256:4691802dda97be727f79d86818acaad7eb8e9252626a1d6b519fedbb92d5e251 \ - --hash=sha256:47ddf841cdecc810749921d25606dee45857d12d2ad5ddb7b5bd7eab12e4b365 \ - --hash=sha256:486f7d16ed54c39c2cbd7ca71fd8ba2b8bb7860df65bd7b6ed640bab96a38a8b \ - --hash=sha256:4cd96b5ba05d67ed0cf00b5b405c8cd99586d8e3481e8ee0a831057591af7621 \ - --hash=sha256:4d6e0ac9da31c9c04c84e1c0182ad8d6df35965a85cae29cd71d089621b3ae94 \ - --hash=sha256:4dfd6e47d3c44c2279907607f73a4240b88c69eb8b90da7e2441a8045dfd21da \ - --hash=sha256:4f7215cb3933784f79ed20e5f050e15984f390424339b22375d5a53c933a0491 \ - --hash=sha256:4fe1f1087cbadb280b5e1bb054a4f00d1423c74d6626c5e48400d871d34ecefe \ - --hash=sha256:52cdac9432d8b4a719f35094a818d95adcae0f0b4fe9b9b921909e0c87de9e7d \ - --hash=sha256:5663ee9257cfa1add7253a7da3035a02f31b6600ec48261585e1800a81533080 \ - --hash=sha256:57fc6745a4b7d0f5a9eb4f40a69718be6c0bc1b8368cc9fe89e90118719f4f42 \ - --hash=sha256:5a837f49d901f9e368651b676912bff1104ed8c1a83b280bcd7b29adccef5c9c \ - --hash=sha256:5c0b3e614340c889d575451696374c9d17affd54cd607ca0babed8f8c37b9397 \ - --hash=sha256:5e78b522b7a6e27e0b25d19b247b75039ac4c94f99823e3c9e53ae1603a9f7e9 \ - --hash=sha256:5f2504bc0322437c9a1ff6d3333ca56c7477b727c995f036b976ae17b98372c8 \ - --hash=sha256:603a2c834142172ffddc054067f5ec0ca65d57a0aa98a71bc81952573208e345 \ - --hash=sha256:62a759436b29e677181a9e76bab8b8f689a29cb9c535f45f7c48c9c830d3f8c3 \ - --hash=sha256:634e385930fb6d2d479cf3aa66515955863b77a5e3c2b5894ca259a25b308602 \ - --hash=sha256:64c567bf9eaf664280116a8688f63016e6b32db2505908e2bdaca1b6438142f2 \ - --hash=sha256:672ac254412a24d0d0cf00a9e6c238877e4be5e5fa2d188832c1244f45f31966 \ - --hash=sha256:672b9d65f42eb877f5c3f234a4547e4e1a226ca8c2eed879bb34670a0ce51192 \ - --hash=sha256:686b6c0d3911ec387b444ddf5dc62fb7f7c0a7d5186a7861626496a5ab4aff95 \ - --hash=sha256:6f71173be42d3241d428f760122febb748de0623f44308a6f120d0dd9ec572e3 \ - --hash=sha256:6fd35beba67c4183b09375c5fff9accb47524191a244a99f95fd4472f5402c2b \ - --hash=sha256:6ffbb2f4ec1ceaff7e07d43922954da26b223d188bf30658e561b98e23089444 \ - --hash=sha256:73f05ea02013e02512c3bf42714f1208c57168c779cc6fe23516e4543089d0a6 \ - --hash=sha256:764457a7be60825fb770a644852ff717bcbb5042f189f2bd16df61a81b3f6573 \ - --hash=sha256:797457503c2d426bee06eef808d07b31ede30b65e054444e7de64cad0061b7af \ - --hash=sha256:7c106c26852ca1c2047c6b80384f17100b4e439af276f21ef3d4e2f450ae7e15 \ - --hash=sha256:7fb4bdf95b0561a79f259f9d28fbc109728c5ee7f27aff6391f0ca703a329abe \ - --hash=sha256:819c054312f1af92947e6a55883d1b66feefab11531a7fc45e0fb9b63880b5c2 \ - --hash=sha256:8560b4d712474335d08907db7973f71912d3a9a8f1dee992ec06b5d2fe359496 \ - --hash=sha256:86a6dab78b0e43e2897a3bbe15745aa60dc5423ca437b7b0b164c069bf91b876 \ - --hash=sha256:87a5eea1b2a5e21e1ebdbb33ad4165359189327e63fc4e4894693e7f821ac817 \ - --hash=sha256:896e12dfdbbab9d8f7e16d2b28c6769a60126fa92095d1ebf9473d02593a2448 \ - --hash=sha256:8f6bb621e5863cfe8fe5ff5468002d200ec31f30f1280b259dc505b02595099e \ - --hash=sha256:90d53f1609c29ccc2193945ef732428382a28f78d0456ae4d3daf0d48b74f0f6 \ - --hash=sha256:915fbb7b41b115192259f8c9ae58f3ddc444d2b5579917270211858e606a4afd \ - --hash=sha256:93b032b5ec3255473c143627d21a69ac74ae12f7f33974cb587c564d11b1066f \ - --hash=sha256:94da27378da0610e341c4d30de29a191672683cc82b8f9556e8f7c7212a020fe \ - --hash=sha256:979ed4717f59b8bb12e3963378fa285d93d367e15bcd66c721311826d3c44a6c \ - --hash=sha256:97e704dcd26271f5bda3fa07c3ce0fb76d6d3f8659f4baa1a24442cc9ba177ca \ - --hash=sha256:99abd37084b82f5830c635fddd0b4993b9742a66eb746dacf433c8590e8f9e3c \ - --hash=sha256:9af6779bfb46abf124068327abcdf9ce95c9ef8287a3e8da76ccf2d0f16c28fa \ - --hash=sha256:9e8f2d660c350b3d0e259c7a7e3d9b7fc8b41210cbcc3d4a7076ff0a5e5c2fdc \ - --hash=sha256:a24f677ebe83749039e7bdf862ff0bbb16818ae4193d4ef96505e269375bcce0 \ - --hash=sha256:a9875b46d910cff3ea2f5962f9d266b465459fe634e22556ab9bd6fc1192eea0 \ - --hash=sha256:aa00140699487bd435fde4342d85c94cb256b7cd3a5b9c3396c67f19922afda2 \ - --hash=sha256:ae6be797afdef264e8a84864a85b196ca06045586481b3df8a967322fd2fa844 \ - --hash=sha256:af8b4b81a960eeaf1234971ac3cd0ba5901f3cd42eae42a46b4d089a8b492719 \ - --hash=sha256:b165790117eea512d7f3fb22f1f6dad3d55a7189571993eb015591c1401276d1 \ - --hash=sha256:b238af795833d5731d049d82bc84b768ae6f8f97f0495963b3ed9935c5901cc3 \ - --hash=sha256:b3a03285a7f9c7b016324574a6d92a1c895da6b978cb8f1deee3ac72bc6da178 \ - --hash=sha256:b6feea921016eb3d4e04d65fc4e9ca402d1a3801f562aef94989f54694917af3 \ - --hash=sha256:b6ff7fcee63287ae57b5df3e4f5957ce032122802509246dec1a5bcc55904c95 \ - --hash=sha256:b821a1f7dedf7e37450654e620038ac3b2e81e8fa6ea269337e97101978ec730 \ - --hash=sha256:bb2c0c80d431c0d03f2c7dbf125150fedd4f0de17366a7ca33f7ccb822391842 \ - --hash=sha256:bb33777ea21e8b7ecde0e6fc84f598be0a1192eab1a63bc746d75aa75d38e7bd \ - --hash=sha256:bcfb80a2cc36fba2534e5e5b5264dc7ae6fcd9bf15256da3e53d2f499e6fa29d \ - --hash=sha256:bd869c427324e5cb15195793de951295710db28be7d818247f3097b4ab5d4b96 \ - --hash=sha256:bedb0cd073cc2dc035e30aeb99444389d3cd2113afe4ef9fcd23d439f5bade85 \ - --hash=sha256:c389c482a7e9b9dc3ee2701ac46c4125297a3818875b9c305ddb603c04828fd1 \ - --hash=sha256:c6fa4dc7ad6f8109c70bb1499e589f76b0b792baf39f9b017eb92c8a81d0a199 \ - --hash=sha256:c83afe0ba876be7e943d2e0ba645809ad441575d2840c895c21ee5de93b9377a \ - --hash=sha256:cb21957bb8aca671c1765e32f58164cf0c50e6bf41c0bbbd16da20732ecaf588 \ - --hash=sha256:cf4491381b1b57425c315a56a439251b1bdac07b2275f19a8c44bc57744532ec \ - --hash=sha256:d03f281ed22579314ba00821ce20115a7c0ac430660b4cc05704a3f818b3e004 \ - --hash=sha256:d35143e27778b4bb0fb189562d7f275bff79c62ab8e98459717c0ea617ff2480 \ - --hash=sha256:d3b1a184a9a8f548a6b73f1e26b96b052193e4b3175ed7342aaf1151a1f00a04 \ - --hash=sha256:d44ec478e713ee7f29b439f7eb8dc2b9d4079e11ae114d2c2ac3d5daf30516c8 \ - --hash=sha256:d9d4e294455b23a68c9b8f042d0e8e377a265bcb15332753695f6e5b6819e0ce \ - --hash=sha256:de538791a80e5d862addbc183f70f0158ac9b9bb872bb147f1fd2a683691e087 \ - --hash=sha256:e4e5e0ae56914ecdbf446493addefc0159053dd53962cef37d7839f37f73d505 \ - --hash=sha256:e509a55f681e6158c20f70f102f9cf61fb20fbc382272bc6d94b7343f2582780 \ - --hash=sha256:ec8dc383ee57ea3e883477dcca3f11b65d58199f1080acaf4cd6ad9a99698be4 \ - --hash=sha256:ed09c7eb1c391271c2ed0314a51903e72a3acb653d5ccfc264cdf3ef11f8269d \ - --hash=sha256:eeea07c4397bbc57719c4eed8f9c284874d4f175f9b6d57f7a1546b976d455ca \ - --hash=sha256:eefd9cc9b6d4a2db5f00a26bc3e4f9acf71926a6ec557cd56c9c6f27c290b665 \ - --hash=sha256:f234b4deb12f3ad59127e037bc57c40c21e45b45282df7d3a55a0f409f595296 \ - --hash=sha256:f380468b09d2a81633ee863b0ec5648d364bd17bb8ecfb8c2f387f7ac1faf42c \ - --hash=sha256:f5e6ff2bdbb8f4cd3fbe41f99e25bbcd58e3bf9f13d3dd31a11e7917251cc77a \ - --hash=sha256:f7a16ef45b081454ef844502d87a848876c490c4cb5c650c230f6ec79ed2c1e7 \ - --hash=sha256:faccab372e66bc76d5731525e7f1143c922271725b9d38c9f97edcc66266b451 \ - --hash=sha256:fc0cacab7ba4e56f0f81c82a98c09bed2f39c940107b03a34b168bdf7597edd3 +aiohttp==3.14.3 \ + --hash=sha256:03cd2bde3d7f085b64e549c985f4bb928cad7e8ecf5323bfca320db548d81b39 \ + --hash=sha256:041badb8f84396357c4d3ad26de6afd7a32b112f43d3c63045c0c8278cfd2043 \ + --hash=sha256:0a5ff2dfbb9ce645fa5b8ef3e02c6c0b9cc3f6030ff863d0c51fffc50cb5541b \ + --hash=sha256:0fdea2281997af69da84c77ffa6f5938a0285f21fb3887c249d67419ca865b3d \ + --hash=sha256:11fb37ef075669eee52ab1928fbf6e1741fada40409fa309ebde9607a962aebf \ + --hash=sha256:134ac5ddcf61c6fad984b9a5727d83492ada43d63471db20fb73042c13fca62f \ + --hash=sha256:152516815ef926786a0b6ae2b8f1fd2e0c71582dee0b435636865316fd4891b7 \ + --hash=sha256:1576145bdceeb92382d899751e12743a3a5b8e460a841e3e50543859e54864dc \ + --hash=sha256:16100ad3ab8d649fdfbee87602d9d2dcdca9df0b9eda8a1b5fdc0d41f96da559 \ + --hash=sha256:16ea7e24c309fb7c0bbd505d149abe4fe4dccfb8db911db7dbec0921bc889a6f \ + --hash=sha256:18c441d0a8fca6de8d1f546849b9f0ab20d435993e2c5b59562b2fae6be2f929 \ + --hash=sha256:18cb43369747b2ae007bd2655fb8e63a099c2ff1d207962943636dac989b3147 \ + --hash=sha256:1b59533861b70a2185c8f4f350f791f39d64358ef6944ce71c5240c9ec0982c9 \ + --hash=sha256:1c5281acc88b92396f88c7e1e2748f8466689df22b80170e4f51efa712fb47a8 \ + --hash=sha256:1c5ec8fb1bcc31a8466f74aaf26c345d5c386fa4bd08a3f0eb9c7a4a3fe8b5bf \ + --hash=sha256:1caa7b0d05f3e3a36f87788c59e970a7ee1cefcfcbb924a9f138c4a6551c9cb7 \ + --hash=sha256:21c016079415ed3fd676963e9793700a566d85dbbd6bfc564b9b2d209147dcc8 \ + --hash=sha256:2498f0fe69ead802f9675beca44a7c21c62fdaa4ec5145ea1c3ad6edbee29f85 \ + --hash=sha256:25bd2708db6bdf6a6630dd37bdcdfcb47c4434d22ac69c64665b802910140b30 \ + --hash=sha256:270d3dace9ca2f10f0da5d8ebe519b7a310fc6112ed916e32df5866df0888553 \ + --hash=sha256:2e1161602f45a54de2ce0905243a95f58cb42dcd378402f3697f5e0b21e9d2e7 \ + --hash=sha256:2e9878ae68e4a5f1c0abe4dd497dbc3d51946f5837b56759e2a02e78fa90ef86 \ + --hash=sha256:30402d03a7c0ff52bce290b57e564e9079fd9d0cb545c8aba73f86a103162d2e \ + --hash=sha256:33a2d7c28d33797a2e99923dffa63f83d908a19b6bf26cfe80fa790aa5e1a75a \ + --hash=sha256:362a3fd481769cac1a824514bcd86fda51c65e8fe6e051099e008fddde6db17c \ + --hash=sha256:38901a84da3ce22249f6e860bf8f90d141bcab7da090cc398f8bb58c0e44b7da \ + --hash=sha256:39aded8c7f3b935b54aab1d8d73c70ec0ee2d3ec3b943e0e86611bc150ba47f5 \ + --hash=sha256:3a26434dafe408229ff3403458ca58de24fb51936504decac49ce6755f77e59d \ + --hash=sha256:3ae5b3a59436d089b5395d910121a390feed4d00578eb95a0fd1a329fe963100 \ + --hash=sha256:3d4f72af88ac2474bb5bca640030320e3d38a0163a1d7533500e87be458eef71 \ + --hash=sha256:3f42e9b78301f11c8f861746175d8b9c1ccef713fcad9eab396e2f6db8ed4a22 \ + --hash=sha256:42a67efc36300d052fb4508a53e8b6901b9284b599ae63945c377569c5fcc1e1 \ + --hash=sha256:48d67b87db6279c044760787eb01f6413032c2e6f3ba1cafaa492b1c8e578479 \ + --hash=sha256:498c6c623134f8e09a3c4e60bcd607a0b4590dd7dbf08dd40851b27cbb520ccb \ + --hash=sha256:49f7325beb0f85ef4aef5f48f490269575f83e6e2acad00a1d80b807eb027062 \ + --hash=sha256:4e3ac92d90e92773b2362d506068e9a948192bd553e743c5b2429e28527c8661 \ + --hash=sha256:530125ee1163c4219af35dc3aa1206e541e7b31b6efc1a3f93b70a136f65d427 \ + --hash=sha256:5373dc80ad1aa2fb9ad95c83f24eef418bbda3a61375f128e5b0192e4f3f9b32 \ + --hash=sha256:53e5179d8abb5710f8e83ba207c41c8d1261fcffd4616500e15ca2b7a33be10a \ + --hash=sha256:53e7b4ce82b54a8bcc71b3b67a5cbd177ca1d7f592cbc92cd38b7349f73482db \ + --hash=sha256:543906c127fb1d929b95076db19b83fa2d46751006ff1e23b093aa5ac4d8db42 \ + --hash=sha256:54cfcdee2770dac994417cbb0ee1f3eb0e7cb6b30c79bf44f2c02ff79ec5124a \ + --hash=sha256:55bdcc472aafe2de4a253045cc128007a64f1e0264fb675791e132ea5edaa3bd \ + --hash=sha256:56f355e79f71aef2a85c80305cc915f894b170dba76de5fe84f6351939b83c06 \ + --hash=sha256:5895ef58c4620afe02fa16044f023dc4dafec08158f9d08874a46a7dbc0341b8 \ + --hash=sha256:5bcb6ff3fdab1258a192679ff1a05d44f59626430aa05cd1a9d2447423599228 \ + --hash=sha256:5f08ec777f35ee70720233b8b9811d3bb5d728137f30ac91b7457709c3261ac0 \ + --hash=sha256:614c61d478b83953e261d02bb2df750f17227cd33ef8002945bf5aebbde21919 \ + --hash=sha256:617105e2c3018ee38d0c8ce5ee3c84f621a6d8b9f723202aacaff28449ca91ee \ + --hash=sha256:6debfa7312ff9d4c124dc71d72e9a0a4b9e0879e48ba6fcb42bef5c3300289e2 \ + --hash=sha256:7041d52c3a7fa20c9e8c182b534704abb19502c8bdcbde7ab23bfda6f642394f \ + --hash=sha256:70c987b27534f9ae1a723f47ae921571d616da21d3208282bf4c52af5164ac43 \ + --hash=sha256:74ab5b6a9fb13e873e5a90946588baecaf488745e1db1a4a5c433f971f035098 \ + --hash=sha256:78253b573e6ffab5028924fc98bc281aae05445969982a10864bc360dea2016c \ + --hash=sha256:7a75aa63cbf9b21cfaf60dc2657e19df2c2867d91707d653fee171ffeedd1371 \ + --hash=sha256:8800c996b01c2772a783e3e46f3e1abd5823029adca0df54231960de9bfefa5b \ + --hash=sha256:89176250f686cb9853c0fb7ead90e639e915b84a6f43eedc2a4e7ec21f1037f0 \ + --hash=sha256:8a5fd34f7f7410d1730d5c2ba873cacb2eed3fede366feb268a70ba22581ed8f \ + --hash=sha256:8b3b60de05f3dcb6f6a00f818bb2ec781cee4de0645f59ccaf99b1d1823b6100 \ + --hash=sha256:8f2f1c4c032c7cedd7d8da6f54c97b70266c6570c3108d3fdffee7188bb70529 \ + --hash=sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc \ + --hash=sha256:9aa6e61fdf20105c4144e755bd586008ff450791d67b1c8146fdc15959c4d51c \ + --hash=sha256:9d9edccfe496b476db5f398d97b865e9a6752bcf8aec4eef8390ce20fb64bb41 \ + --hash=sha256:9fc7b5bfec6573f3ae844f457fdde5adeb713f8b8e4a81ad64fc207b49383716 \ + --hash=sha256:a0dc483c00da8b673abbb367eb6f8d8f4bcec30eb58529ea13cb42e7fd2dfa33 \ + --hash=sha256:a3a8296e7ab5c295f53f1041487cb088e1480775aafbf7fe545d93b770a0f96f \ + --hash=sha256:a3e22975f905b89a55a488c2a08f2fdb2186175349e917d48985cc468a3d4c6e \ + --hash=sha256:a4af35c443e0b1a1bd6a8af3f3485d7fda15c142751a00f3ff8090f0b93346fa \ + --hash=sha256:a94dbaae5ae27bd849c93570669bff91e0510f33a80805738e3de72a7be0447b \ + --hash=sha256:ac74facc01463f138b0da5580329cfcc82818dea5656e83ddcd11268fc12ff80 \ + --hash=sha256:ad4c8b7488d745d2ca4838ebd8ae5ba9b56341d30b1da43640e4ce87f9f49646 \ + --hash=sha256:b014a6ed7cf912e787149fdc529166d3ceabac23f26efeea3158c9aba2354e7e \ + --hash=sha256:b20032766aedf6261c7a566585a40867d092ac03a0d81592d5370ef9b054f99b \ + --hash=sha256:b2466434105a4e03113c36ec775cc2ebe6676b62eae326fa670bb607ef788c1c \ + --hash=sha256:b304db572b4368edd8dda8a2274f73156fe15558fca4a917cb8a09fc47af5963 \ + --hash=sha256:ba59d59aba08ac02fc03b0c8983ccd5ee39a199d0552ce9e6d2b4845b34d59ae \ + --hash=sha256:bd52f811e65f6fb634b1047159657c98f52b407f8efec907bcfc09da9a4c0a25 \ + --hash=sha256:bdd0e2834dce1a26c1bbe26464861e16bbe217042cbff619247c11594472518c \ + --hash=sha256:c23ec8ee9d5ab2f5421f9c7fffce208435607af27fd46d4a44e031954352838f \ + --hash=sha256:c39846c3aad97a8530c89d7a3869a8f8e9e3762c6ac0504481e5c80948f7e807 \ + --hash=sha256:c3c200cf9757edd785051dc699c7ecbec22110dbfcb3fefc7a9f9695eda8ea7a \ + --hash=sha256:c7d3a97c678d34fc5b59da671ee9cd630096ddc643e7b5a30d54a2a6f3574d3f \ + --hash=sha256:c8653fd547c93a61aadc612007790f5555cdd18946fa48cf45e26d8ea4ea473d \ + --hash=sha256:cc7cb243a68167172f48c1fd43cee91ec4b1d40cefd190edd43369d1a6bc9c82 \ + --hash=sha256:ccd4893707b3e2a13e39c90d43cf80edf2e4d0457935bcc103bf2346214c3f15 \ + --hash=sha256:cd817772b2fcf2b8c0905795318485f9ec16eae60b29feb7f4c77085311637f0 \ + --hash=sha256:cda5fd5c95ad7a125a2e8464acc78b98b94c475a3780d6aa0aa157c93f470f4d \ + --hash=sha256:cef89a58e628c4efcac3275c2d68083f82426dcdc89c1492a6f654f9f7ea6ab9 \ + --hash=sha256:d1558173930a5a8d3069cee5c92fc91c87c4dbcb099debbb3622053717145a19 \ + --hash=sha256:d6088ec9894113802bddb3c09e974929aed2c7b3a8c456219b8aab4481f1a239 \ + --hash=sha256:d6218d92e450824e9b4881f44e8c09f1853b490f9a64130801024a4793b1b3b0 \ + --hash=sha256:d77640cc618c1d99fc4f8589c0f24a730adfa54eb1e57ef7bf0c8dfb78da898c \ + --hash=sha256:d7d2deec16eeedf55f2c7cf75b521ea3856a5177e123844f8fd0f114ce252cb5 \ + --hash=sha256:db332af25642007330fca8be5c4d194caf2bea7a7fc84415aff3497af5dfee6b \ + --hash=sha256:dd54d0e8717de95939766febac482ac0474d8ac3b048115f9f2b1d23a16e7db4 \ + --hash=sha256:ddcac3c6b382e81f1dd0499199d4136b877beb4cb5ef770bbbfba56c4b8f55d2 \ + --hash=sha256:df82f3787c940c94986b34222d59c9e38843fba85139f36e85255a82ad5355a9 \ + --hash=sha256:dfa68deb2a443bdaa3ea5297b0699c1464f08aef3812b486d1348eee61b07dc0 \ + --hash=sha256:dff9461ec275f22135650d5ba4b4931a11f3958df7dfbb8db630000d4dee0883 \ + --hash=sha256:e1e74298bab6ee0d6e749ed4fd1901c7e604bdda32c03d787a2cc71c46d0433d \ + --hash=sha256:e2667f0bbe7eb6c74eae5e9691441ad186e5845ca3cff63230fc09c4e7514f5d \ + --hash=sha256:e3be98a7c30b8c25d573dafba7171d66dfb05ee6a9070fc46535464ff97700a6 \ + --hash=sha256:e568e14940c09955aa51f4e645b6daa18a581c5dcfcd73744dcc86a856e3ced3 \ + --hash=sha256:e72ee89e28d907a18f46959b4eb0bb06701cc7f8cf4366e00029e2ccfaaf5924 \ + --hash=sha256:e92eb8acc45eb6a9f4935071a77edf5b85cc6f8dfad5cd99e97653c26593cdde \ + --hash=sha256:ea05e1f97ceea523942d9b2a7d7c0359d781d683d6b043f5943a602b14da4787 \ + --hash=sha256:eac645b09bcfdf73df7536331f0678c1086ea250981118ddb5199e17ccef72bb \ + --hash=sha256:eb0495d778817619273c108784292be161a924b9f5ae5cbbc70a2caa6838250b \ + --hash=sha256:ebe8e504f058fe91223351cecd2d9d6946c9d241bb0250d898ffbdf584cc72b0 \ + --hash=sha256:ed099d105449c4f9e84f24af203cd131349d4761d8813fa7e02c32e7128cd910 \ + --hash=sha256:f0f177d1b195b9e06376cfd7d308d8a1b920909a609d03ac82a8c73bbb16d3b9 \ + --hash=sha256:f3d2669fe7dec7fc359ecdb5984b29b50d85d5d00f8c1cb61de4f4a24ee42627 \ + --hash=sha256:f4e05329faa0ea1a404b37de4f034fd2c2defcca06a68dc6745e4e56c88e8a48 \ + --hash=sha256:f53bcd52f585e1ac3e590d61434eb61f9a88c38df041b4ea126d97144344a77b \ + --hash=sha256:f55119f7bf25f49ed210f6096090715da24f2943c62102448915fde3c62877ce \ + --hash=sha256:f631fe87a6f30df5fbe6d79640b25e4cffb38c31c7fb6f10871517b84b0f8c1a \ + --hash=sha256:f8fb78a83c9e5f741ca3a68cfb455c1f5bb83b4e7249a3848b3cd78d0a8563b0 \ + --hash=sha256:fa9467a8113aa69d3d7c55a70ef0b7c636010a40993f3df9d9d0d73b3eb7ef24 \ + --hash=sha256:fd51ebf9d3a00c074df4ede271023f4d2dba289bcc740b88191872716014e3c5 # via + # -r requirements-strix-ci.txt # gql # litellm aiosignal==1.4.0 \ @@ -401,53 +402,53 @@ click==8.4.1 \ # litellm # typer # uvicorn -cryptography==49.0.0 \ - --hash=sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001 \ - --hash=sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122 \ - --hash=sha256:084ef1af862eb07ec46d25f68689f2102a9fc0e05ce7b80f14f5fe51e4eef0f6 \ - --hash=sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c \ - --hash=sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325 \ - --hash=sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69 \ - --hash=sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d \ - --hash=sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36 \ - --hash=sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc \ - --hash=sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6 \ - --hash=sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b \ - --hash=sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27 \ - --hash=sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61 \ - --hash=sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18 \ - --hash=sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db \ - --hash=sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b \ - --hash=sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb \ - --hash=sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2 \ - --hash=sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459 \ - --hash=sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e \ - --hash=sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21 \ - --hash=sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8 \ - --hash=sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7 \ - --hash=sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa \ - --hash=sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9 \ - --hash=sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db \ - --hash=sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64 \ - --hash=sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505 \ - --hash=sha256:b39efa323140595abd3ecca8529d321ae50f55f3aa3ba9cc81ea56a6011953d5 \ - --hash=sha256:b47db11c2c3525083296069b98ac5221907455e989ae0c2e3008bde851921615 \ - --hash=sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f \ - --hash=sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866 \ - --hash=sha256:be9fcb48a55f023493482827d4f459bd263cc20efde64f204b97c123201850c6 \ - --hash=sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561 \ - --hash=sha256:c83782480a4a9da4d0feb51950131ba32e12e70813848b3343f6e18c28a66838 \ - --hash=sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9 \ - --hash=sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7 \ - --hash=sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68 \ - --hash=sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8 \ - --hash=sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3 \ - --hash=sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e \ - --hash=sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a \ - --hash=sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d \ - --hash=sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4 \ - --hash=sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493 \ - --hash=sha256:fc1e275c2f1d97b1a6450b8b0ea3ebfa6e087a611c2b26cb2404d48588abab7b +cryptography==50.0.0 \ + --hash=sha256:031e2d5dd4bb9caa3ca9c82e5a197fd8ae680232cee62603d1a813f3f07e3d03 \ + --hash=sha256:06a32a980526a6ab9a4b9bf8f7385800791e2bb960903cb6b530e4817509a3b7 \ + --hash=sha256:07479a1cb08219ab719147e742e76090c9c773321959bb94946fffdd397a6437 \ + --hash=sha256:07949c449a1abcf60d1ee6e88956d89404c7df3c8258f46589e912988e551987 \ + --hash=sha256:105110f43a471dbd0060b9c9516cb8a6a79233631a04cc2ba16f28323ac6e025 \ + --hash=sha256:11b74db56cdbe3cdee6e3f6982ecb70334fa10dce99ed58bf7894aaaa3b2a037 \ + --hash=sha256:12b9c6996425c76ea6c457ace4f3073e715b8c545add07cd1a8f3a4f90691269 \ + --hash=sha256:1489e263a8048bb8b6a8bac662eb2d402ea5d2b7b4699b72f385f1e2772db105 \ + --hash=sha256:19736989797678c6af1e55cd49055cdbcb55d8f6b5583ac5335f933aba9101dc \ + --hash=sha256:1b4a266766514614f8aa60416e71f2fc6e575d36e7bdc90f644fadb2f4b75b95 \ + --hash=sha256:2a8183b489dc1f7f80f135780fadc1108f14b31b8a40411c7a5b17425f65f28b \ + --hash=sha256:37fdb0d0111f1e2ff07139dfb79f1b49531f8e213c46f1163dd7642979b58c47 \ + --hash=sha256:3f5735ffe4996d28b809371756219f5354864902a3b9e7c0b9ee87041209fc9c \ + --hash=sha256:49e7d93abdbd2990caced757e5fade25302f719c3c8fb6e6fff2dde98999fc41 \ + --hash=sha256:5e34edd123674534acd70147f0ca331eaa2c74e6325fb2028c886aa26ba0b68c \ + --hash=sha256:62598a8a57f815db4c6259a4e97d857dab56697e7de8e8ab02352ab74da1995d \ + --hash=sha256:65c2c3add92b45fd0709db8594536aea39c2a67af0e27ffcf049c498501140b7 \ + --hash=sha256:6ba6a53445bd3cfa809ef3ef5f1589aa6ba08784a1d962bf47d0940e871dab1c \ + --hash=sha256:6e7d61120573a7f2cd94cc095f9e81f6967c61ccdf194285aa143ecec8e0b708 \ + --hash=sha256:7cec5b856506da6defb290f30c9ee687d5f5e8cb0bd3f6459dde43b0b4fa40ef \ + --hash=sha256:80b63928fa35083b33966ce1efb70e5b9607181e49dcd1c22c8c005e319f667f \ + --hash=sha256:82148ec5bddac30b51a5b3c1945075f896fa022cb93f8e4a01e9f6ee95292c5f \ + --hash=sha256:828743d939e9629bc267b8e2d08d8bb67cd4319c771a33d4b18b22dd8fb7440a \ + --hash=sha256:8d89f3976b10b4ce31118de72329025f70d2c6ead14a8217c5514dd2c6d5a78f \ + --hash=sha256:8eb5e1172eb569ea8a872796576e6a67c276351728b6455d5beb01242b027c6a \ + --hash=sha256:900131fafd8aead39ac7dd3a7e833be754c17a95cfd91221636949fe4eb0aa8a \ + --hash=sha256:910d11e1a385c654bf738bf3e6b8e6ed5de0f5610fcae2be9e5b398d8081d20e \ + --hash=sha256:910e1d2668e7de9648f2bcee30e180db2a6b15c30f887d7c4c93ddf96e3992e3 \ + --hash=sha256:9aa87839c383bdbab6ef865787a1fb877af8dd03464c4400322726feaaadfc6d \ + --hash=sha256:a1b30560f2acc95aa8b2e06e716a13dbfc97314747b80d9707e307f77b40d6b3 \ + --hash=sha256:a91296cb61e8df6f86d0c19cc4068228da256bf59bf86049fbd821084565327f \ + --hash=sha256:b42a28c1844fd9de8f3f7d540e36b66f3a9c83fceac7170ebc7a6a19edd9dcae \ + --hash=sha256:bd1c592e4d5974f0d08d4888e432157adba757c66da0246918e43677fafa2d30 \ + --hash=sha256:c87f62a3d3b9888ed0fdde100ec06aa61ca9cd44bad9057d1dff9a516b5f5bb9 \ + --hash=sha256:c99c003e088647b8a5b7c145d6f78c335f6348332b62e142d411c4b63d1460b9 \ + --hash=sha256:ccdc4a71a4dabae05de219404f9f4abc38e3b58422177ff93d0da05967dafa07 \ + --hash=sha256:d24fead1d4d076e1bfb006dcec392074a3cd8d7b4fc8a595aa64073b2b7a96ba \ + --hash=sha256:d58c3db7cd6eed54e6c06744db55456b65ebd7492ddeae9c1e93cfca7aa857d3 \ + --hash=sha256:d764dcf130c428ef66786f866dd750f53182bc608813489915e9fc106bb0c82f \ + --hash=sha256:df2a58a472f332225671c35b0a830208b86d004f82baa8530fa3782c85646533 \ + --hash=sha256:e722f16708d854fe924790e051061f6704a472c3bac347b6fd88033ea8dd0dc5 \ + --hash=sha256:ecfed7367f965a0328cfbdd70da860f15441f002f613185668c6e6ebf5a0ac11 \ + --hash=sha256:eeac2acb5a20ed25e0ad6d1df9891a520b78b404266b6d11778f25d5d691a6c9 \ + --hash=sha256:f59e38625469987d7ef6d495323c55e7db6c212eaf6112267e0d3b565a2e9c9f \ + --hash=sha256:f89831ef99dd7dd169ab06d63a831adb9e20a87aac6d380266bbda5823349169 \ + --hash=sha256:fd9192b7b70c573d7f214eb1ae35e00d359f6f5e4b27c7e21e30de1fc6204645 # via # -r requirements-strix-ci.txt # google-auth @@ -1680,9 +1681,9 @@ pyjwt==2.13.0 \ --hash=sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423 \ --hash=sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728 # via mcp -pyopenssl==26.3.0 \ - --hash=sha256:46367f8f66b92271e6d218da9c87607e1ef5a0bc5c8dea5bb3db82f395c385a3 \ - --hash=sha256:589de7fae1c9ea670d18422ed00fc04da787bbde8e1454aea872aa57b49ad341 +pyopenssl==26.4.0 \ + --hash=sha256:28dfcce0162b9211413e26dfbfdf1d24317fbeba18fc93c12400a1856b2a0bc7 \ + --hash=sha256:f0eb0cb2d581d3ad2b9c489468485e7f2ab6727d08401bcf9d824c3caddf3c1c # via google-auth python-dateutil==2.9.0.post0 \ --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ diff --git a/requirements-strix-ci.txt b/requirements-strix-ci.txt index e32bd39a9..98e5c33e2 100644 --- a/requirements-strix-ci.txt +++ b/requirements-strix-ci.txt @@ -1,6 +1,7 @@ strix-agent==1.0.4 +aiohttp==3.14.3 google-cloud-aiplatform==1.133.0 protobuf<7.0.0 -cryptography==49.0.0 +cryptography==50.0.0 python-multipart==0.0.32 pyasn1==0.6.4 diff --git a/tests/test_strix_dependency_security_floor.py b/tests/test_strix_dependency_security_floor.py new file mode 100644 index 000000000..768841a2b --- /dev/null +++ b/tests/test_strix_dependency_security_floor.py @@ -0,0 +1,23 @@ +"""Contracts for the security-reviewed Strix dependency lock.""" + +from __future__ import annotations + +from pathlib import Path + + +def test_strix_requirements_pin_reviewed_security_versions() -> None: + """The canonical input pins versions that close the August 2026 advisories.""" + requirements = Path("requirements-strix-ci.txt").read_text(encoding="utf-8") + assert "aiohttp==3.14.3\n" in requirements + assert "cryptography==50.0.0\n" in requirements + assert "aiohttp==3.14.1\n" not in requirements + assert "cryptography==49.0.0\n" not in requirements + + +def test_strix_hash_lock_matches_the_canonical_security_pins() -> None: + """The generated lock retains the reviewed direct security pins.""" + lock = Path("requirements-strix-ci-hashes.txt").read_text(encoding="utf-8") + assert "aiohttp==3.14.3 \\\n" in lock + assert "cryptography==50.0.0 \\\n" in lock + assert "aiohttp==3.14.1 \\\n" not in lock + assert "cryptography==49.0.0 \\\n" not in lock From 988e10f2be7099fdc14759ead74a635ea39e555b Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 08:23:07 +0900 Subject: [PATCH 16/57] ci: verify Strix security lock contract with diagnostics --- .../workflows/opencode-coverage-diagnostics-ci.yml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/opencode-coverage-diagnostics-ci.yml b/.github/workflows/opencode-coverage-diagnostics-ci.yml index 25ab75992..8172990c3 100644 --- a/.github/workflows/opencode-coverage-diagnostics-ci.yml +++ b/.github/workflows/opencode-coverage-diagnostics-ci.yml @@ -9,7 +9,10 @@ on: - "tests/test_materialize_base_javascript_packages.py" - "tests/test_materialize_base_python_requirements.py" - "tests/test_coverage_materializer_failure_diagnostics.py" + - "tests/test_strix_dependency_security_floor.py" - "requirements-opencode-review-ci-hashes.txt" + - "requirements-strix-ci.txt" + - "requirements-strix-ci-hashes.txt" - "pyproject.toml" - ".github/workflows/opencode-coverage-diagnostics-ci.yml" push: @@ -20,7 +23,10 @@ on: - "tests/test_materialize_base_javascript_packages.py" - "tests/test_materialize_base_python_requirements.py" - "tests/test_coverage_materializer_failure_diagnostics.py" + - "tests/test_strix_dependency_security_floor.py" - "requirements-opencode-review-ci-hashes.txt" + - "requirements-strix-ci.txt" + - "requirements-strix-ci-hashes.txt" - "pyproject.toml" - ".github/workflows/opencode-coverage-diagnostics-ci.yml" @@ -119,12 +125,13 @@ jobs: python -m pip install --disable-pip-version-check --require-hashes -r requirements-opencode-review-ci-hashes.txt - - name: Run materializer regression tests with full branch coverage + - name: Run diagnostics and lock contracts with full branch coverage run: | python -m pytest \ tests/test_materialize_base_javascript_packages.py \ tests/test_materialize_base_python_requirements.py \ tests/test_coverage_materializer_failure_diagnostics.py \ + tests/test_strix_dependency_security_floor.py \ --cov=scripts.ci.materialize_base_javascript_packages \ --cov=scripts.ci.materialize_base_python_requirements \ --cov-branch \ @@ -143,4 +150,5 @@ jobs: python -m compileall -q \ scripts/ci/materialize_base_javascript_packages.py \ scripts/ci/materialize_base_python_requirements.py \ - tests/test_coverage_materializer_failure_diagnostics.py + tests/test_coverage_materializer_failure_diagnostics.py \ + tests/test_strix_dependency_security_floor.py From 0165ed71d645c947827fa0680b5888643df2b9a9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 09:00:01 +0900 Subject: [PATCH 17/57] fix(strix): preserve complete support and Rust scopes (#753) * test(strix): align setup-python pin contract * fix(strix): isolate backend support-code scopes * fix(strix): update vulnerable Python dependencies * fix(strix): scan changed Rust security boundaries --- scripts/ci/strix_quick_gate.sh | 11 ++++- scripts/ci/test_strix_quick_gate.sh | 62 ++++++++++++++++++++++++++++- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index 3b001a921..55069f1c4 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -609,7 +609,10 @@ copy_pr_head_blob_to_file() { is_supported_source_file() { case "$1" in - *.java | *.kt | *.kts | *.groovy | *.scala | *.py | *.js | *.jsx | *.ts | *.tsx | *.vue | *.yaml | *.yml | *.sh | *.sql | *.xml | *.json | *.html | *.css | *.md) + # Rust is an application security boundary for Tauri and native services. Keep changed Rust + # sources in the same PR-head scope as frontend IPC wrappers so findings are not inferred from + # an incomplete client-only view. + *.java | *.kt | *.kts | *.groovy | *.scala | *.rs | *.py | *.js | *.jsx | *.ts | *.tsx | *.vue | *.yaml | *.yml | *.sh | *.sql | *.xml | *.json | *.html | *.css | *.md) return 0 ;; Dockerfile | */Dockerfile | Dockerfile.* | */Dockerfile.* | Containerfile | */Containerfile | Makefile | */Makefile) @@ -1185,6 +1188,12 @@ pull_request_scope_context_files() { for changed_file in "$@"; do normalized_changed_file="$(normalize_changed_file_path "$changed_file")" || return 2 case "$normalized_changed_file" in + # Standalone support tools and their tests are not application runtime + # surfaces. Injecting the backend router/service inventory for these files + # creates an incomplete synthetic application and can turn valid imports in + # the real PR-head tree into false missing-module findings. + backend/scripts/* | backend/tests/*) + ;; backend/*) if [[ "$normalized_changed_file" =~ ^backend/.+\.py$ ]]; then needs_backend_python=1 diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index b4d585b9e..abf6d624f 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -192,7 +192,7 @@ assert_strix_workflow_pr_trigger_hardened() { assert_equals "1" "$status_token_count" "strix workflow defines GITHUB_STATUS_TOKEN once so GitHub can parse repository_dispatch" assert_file_not_contains "$workflow_file" "github.event.pull_request.number == 240" "strix workflow must not hard-code repository-specific PR bypasses" assert_file_contains "$workflow_file" "models: read" "strix workflow grants only the GitHub Models read permission needed for Strix" - assert_file_contains "$workflow_file" "actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6" "strix workflow pins actions/setup-python" + assert_file_contains "$workflow_file" "actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0" "strix workflow pins actions/setup-python" assert_file_contains "$workflow_file" 'python-version: "3.13"' "strix workflow runs Python steps on Python 3.13" assert_file_contains "$workflow_file" "Resolve trusted Strix source ref" "strix workflow resolves the central trusted Strix source ref" assert_file_contains "$workflow_file" "toJSON(job)" "strix workflow derives the trusted source from the job workflow context" @@ -6243,6 +6243,32 @@ run_filtered_gate_case_if_requested() { "Materialized PR-head changed-file scope" \ "repository_dispatch" ;; + pull-request-target-rust-file-uses-head-blob) + run_pull_request_target_head_scope_case \ + "pull-request-target-rust-file-uses-head-blob" \ + "src-tauri/src/commands.rs" \ + "const BASE_RUST_CONTEXT: &str = \"must not be scanned\";" \ + "const HEAD_RUST_CONTEXT: &str = \"must be scanned\";" \ + "0" \ + "0" \ + "__PR_SCOPE__" \ + "0" \ + "Materialized PR-head changed-file scope" + ;; + pull-request-target-backend-script-omits-app-context) + run_pull_request_target_head_scope_case \ + "pull-request-target-backend-script-omits-app-context" \ + "backend/scripts/disksage_copy_readiness_handoff.py" \ + "BASE_SUPPORT_CODE_SHOULD_NOT_BE_SCANNED" \ + "HEAD_SUPPORT_CODE_SHOULD_BE_SCANNED" \ + "0" \ + "0" \ + "__PR_SCOPE__" \ + "0" \ + "Materialized PR-head changed-file scope" \ + "pull_request_target" \ + "backend/api/webdav.py" + ;; *) record_failure "unknown STRIX_TEST_CASE_FILTER '${STRIX_TEST_CASE_FILTER:-}'" ;; @@ -6267,6 +6293,7 @@ run_pull_request_target_head_scope_case() { local expected_full_head_scope="${8-$disable_pr_scoping}" local expected_scope_message="${9-}" local github_event_name="${10-pull_request_target}" + local unexpected_scope_file="${11-}" local tmp_dir tmp_dir="$(mktemp -d)" @@ -6335,6 +6362,10 @@ else exit 68 fi fi +if [ -n "${FAKE_STRIX_UNEXPECTED_SCOPE_FILE:-}" ] && [ -e "$target_path/$FAKE_STRIX_UNEXPECTED_SCOPE_FILE" ]; then + echo "Error: unrelated application context leaked into bounded support-code scope ($target_path/$FAKE_STRIX_UNEXPECTED_SCOPE_FILE)" >&2 + exit 69 +fi echo "scan ok with PR head content" EOF chmod +x "$fake_strix" @@ -6349,6 +6380,10 @@ EOF echo 'seed' >README.md mkdir -p docs printf '%s\n' 'BASE_FULL_SCOPE_CONTEXT_SHOULD_NOT_BE_SCANNED' >docs/full-scope-context.md + if [ -n "$unexpected_scope_file" ]; then + mkdir -p "$(dirname -- "$unexpected_scope_file")" + printf '%s\n' 'UNRELATED_APPLICATION_CONTEXT_SHOULD_NOT_BE_SCANNED' >"$unexpected_scope_file" + fi if [ "$base_content" != "__ABSENT__" ]; then mkdir -p "$(dirname -- "$changed_file")" printf '%s\n' "$base_content" >"$changed_file" @@ -6396,6 +6431,7 @@ EOF FAKE_STRIX_EXPECTED_UNCHANGED_FILE="docs/full-scope-context.md" \ FAKE_STRIX_EXPECTED_UNCHANGED_CONTENT="HEAD_FULL_SCOPE_CONTEXT_SHOULD_BE_SCANNED" \ FAKE_STRIX_EXPECT_FULL_HEAD_SCOPE="$expected_full_head_scope" \ + FAKE_STRIX_UNEXPECTED_SCOPE_FILE="$unexpected_scope_file" \ STRIX_DISABLE_PR_SCOPING="$disable_pr_scoping" \ STRIX_LLM_FILE="$strix_llm_file" \ LLM_API_KEY_FILE="$llm_api_key_file" \ @@ -8928,6 +8964,19 @@ run_pull_request_target_head_scope_case \ "0" \ "__PR_SCOPE__" +run_pull_request_target_head_scope_case \ + "pull-request-target-backend-script-omits-app-context" \ + "backend/scripts/disksage_copy_readiness_handoff.py" \ + "BASE_SUPPORT_CODE_SHOULD_NOT_BE_SCANNED" \ + "HEAD_SUPPORT_CODE_SHOULD_BE_SCANNED" \ + "0" \ + "0" \ + "__PR_SCOPE__" \ + "0" \ + "Materialized PR-head changed-file scope" \ + "pull_request_target" \ + "backend/api/webdav.py" + run_pull_request_target_head_scope_case \ "repository-dispatch-pr-scope-uses-head-blob" \ "backend/db/models.py" \ @@ -8946,6 +8995,17 @@ run_pull_request_target_head_scope_case \ "__ABSENT__" \ "HEAD_ONLY_NEW_FILE_SHOULD_BE_SCANNED" +run_pull_request_target_head_scope_case \ + "pull-request-target-rust-file-uses-head-blob" \ + "src-tauri/src/commands.rs" \ + "const BASE_RUST_CONTEXT: &str = \"must not be scanned\";" \ + "const HEAD_RUST_CONTEXT: &str = \"must be scanned\";" \ + "0" \ + "0" \ + "__PR_SCOPE__" \ + "0" \ + "Materialized PR-head changed-file scope" + run_pull_request_target_head_scope_case \ "pull-request-target-source-file-with-space-uses-head-blob" \ "src/unsafe name.py" \ From 575aa558164f371d606fb5340f2bfb4f0ed30fb7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 09:08:17 +0900 Subject: [PATCH 18/57] docs(doctoring): trace LLVM coverage toolchain decision --- .../opencode-llvm-coverage-toolchain.md | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 docs/doctoring/opencode-llvm-coverage-toolchain.md diff --git a/docs/doctoring/opencode-llvm-coverage-toolchain.md b/docs/doctoring/opencode-llvm-coverage-toolchain.md new file mode 100644 index 000000000..64129cf71 --- /dev/null +++ b/docs/doctoring/opencode-llvm-coverage-toolchain.md @@ -0,0 +1,45 @@ +# OpenCode LLVM coverage toolchain decision + +## Decision + +The central OpenCode coverage image installs Debian Trixie's `llvm-19` package and explicitly exports: + +```text +LLVM_COV=/usr/bin/llvm-cov-19 +LLVM_PROFDATA=/usr/bin/llvm-profdata-19 +``` + +The image build fails unless both paths are executable. This is required because the image uses Debian-packaged `rustc` rather than a rustup-managed toolchain, so `llvm-tools-preview` is not an available installation path. + +## Evidence and compatibility boundary + +`cargo-llvm-cov` documents `LLVM_COV` and `LLVM_PROFDATA` as the overrides to use when a Rust toolchain is installed outside rustup. It also requires the selected tools to be compatible with the LLVM version used by `rustc`. Its published compatibility table maps Rust 1.82–1.95 to LLVM 19–22. The central image therefore selects LLVM 19 as the lowest compatible family for its supported Rust range and keeps the two binary paths explicit rather than relying on an unversioned system default. + +Debian Trixie publishes `llvm-19` from the `llvm-toolchain-19` source package. The package version currently documented for amd64 is 19.1.7-3+b1. The workflow installs the package from the pinned Debian image repositories and verifies the exact versioned executable paths during image construction. + +## Security and reproducibility contract + +- Pull-request content cannot select another LLVM package or executable path. +- The coverage image definition remains default-branch controlled and is built from immutable workflow source. +- `LLVM_COV` and `LLVM_PROFDATA` are set together; partial configuration is rejected. +- Missing executables fail the image build before any pull-request coverage measurement starts. +- The image digest, workflow commit SHA, pull-request head SHA, and coverage artifacts remain independently addressable evidence. +- CPU coverage is a correctness gate. GPU execution and parity tests remain separate domain-specific gates and are not represented by LLVM host coverage alone. + +This design does not claim formal compliance with a software supply-chain standard. It establishes a narrow, auditable compatibility boundary for deterministic Rust coverage execution. + +## Regression contract + +The central workflow contract test must continue to prove that: + +1. `llvm-19` is installed in the coverage image; +2. `LLVM_COV` names `/usr/bin/llvm-cov-19`; +3. `LLVM_PROFDATA` names `/usr/bin/llvm-profdata-19`; +4. the image build checks both paths before installing or invoking `cargo-llvm-cov`; and +5. the OpenCode approval path remains fail-closed when Rust coverage cannot run. + +## References + +Debian Project. (2026). *Details of package llvm-19 in trixie*. https://packages.debian.org/trixie/amd64/llvm-19 + +Taiki Endo. (2026). *cargo-llvm-cov: Cargo subcommand to easily use LLVM source-based code coverage* [Computer software]. GitHub. https://github.com/taiki-e/cargo-llvm-cov From 3a31af056c59b3e4120a69b58c3b023a26936764 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 09:14:51 +0900 Subject: [PATCH 19/57] fix(security): apply remediated Strix dependency floors --- requirements-strix-ci.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements-strix-ci.txt b/requirements-strix-ci.txt index e32bd39a9..98e5c33e2 100644 --- a/requirements-strix-ci.txt +++ b/requirements-strix-ci.txt @@ -1,6 +1,7 @@ strix-agent==1.0.4 +aiohttp==3.14.3 google-cloud-aiplatform==1.133.0 protobuf<7.0.0 -cryptography==49.0.0 +cryptography==50.0.0 python-multipart==0.0.32 pyasn1==0.6.4 From 6610317f9900886d950241d41be945db756702d3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 09:18:26 +0900 Subject: [PATCH 20/57] ci: refresh PR 755 Strix lock from validated blob --- .../one-shot-refresh-pr-755-strix-lock.yml | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 .github/workflows/one-shot-refresh-pr-755-strix-lock.yml diff --git a/.github/workflows/one-shot-refresh-pr-755-strix-lock.yml b/.github/workflows/one-shot-refresh-pr-755-strix-lock.yml new file mode 100644 index 000000000..26422e34f --- /dev/null +++ b/.github/workflows/one-shot-refresh-pr-755-strix-lock.yml @@ -0,0 +1,89 @@ +name: Refresh PR 755 Strix security lock + +on: + push: + branches: + - fix/opencode-llvm-coverage-tools + +permissions: + contents: read + +concurrency: + group: refresh-pr-755-strix-lock-${{ github.ref }} + cancel-in-progress: false + +jobs: + validate-lock: + if: >- + github.repository == 'ContextualWisdomLab/.github' + && github.ref == 'refs/heads/fix/opencode-llvm-coverage-tools' + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.sha }} + persist-credentials: false + - name: Materialize and verify the reviewed generated lock + env: + GH_TOKEN: ${{ github.token }} + LOCK_BLOB_SHA: c305e9c84c90f6d6e3360ab12feff7362d875b2d + shell: bash + run: | + set -euo pipefail + gh api "repos/${GITHUB_REPOSITORY}/git/blobs/${LOCK_BLOB_SHA}" \ + | jq -r '.content' \ + | tr -d '\n' \ + | base64 --decode \ + > requirements-strix-ci-hashes.txt + test "$(git hash-object requirements-strix-ci-hashes.txt)" = "$LOCK_BLOB_SHA" + grep -Fq 'aiohttp==3.14.3' requirements-strix-ci-hashes.txt + grep -Fq 'cryptography==50.0.0' requirements-strix-ci-hashes.txt + grep -Fxq 'aiohttp==3.14.3' requirements-strix-ci.txt + grep -Fxq 'cryptography==50.0.0' requirements-strix-ci.txt + git diff --check + + publish-lock: + needs: validate-lock + if: needs.validate-lock.result == 'success' + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + ref: ${{ github.sha }} + fetch-depth: 0 + persist-credentials: false + - name: Publish only the verified lock and remove this one-shot workflow + env: + GH_TOKEN: ${{ github.token }} + LOCK_BLOB_SHA: c305e9c84c90f6d6e3360ab12feff7362d875b2d + SOURCE_HEAD: ${{ github.sha }} + SOURCE_BRANCH: fix/opencode-llvm-coverage-tools + shell: bash + run: | + set -euo pipefail + live_head="$(gh api "repos/${GITHUB_REPOSITORY}/branches/${SOURCE_BRANCH}" --jq '.commit.sha')" + test "$live_head" = "$SOURCE_HEAD" + gh api "repos/${GITHUB_REPOSITORY}/git/blobs/${LOCK_BLOB_SHA}" \ + | jq -r '.content' \ + | tr -d '\n' \ + | base64 --decode \ + > requirements-strix-ci-hashes.txt + test "$(git hash-object requirements-strix-ci-hashes.txt)" = "$LOCK_BLOB_SHA" + grep -Fq 'aiohttp==3.14.3' requirements-strix-ci-hashes.txt + grep -Fq 'cryptography==50.0.0' requirements-strix-ci-hashes.txt + rm .github/workflows/one-shot-refresh-pr-755-strix-lock.yml + git config user.name 'github-actions[bot]' + git config user.email '41898282+github-actions[bot]@users.noreply.github.com' + git add requirements-strix-ci-hashes.txt \ + .github/workflows/one-shot-refresh-pr-755-strix-lock.yml + git diff --cached --check + git commit -m 'fix(security): refresh remediated Strix hash lock' + remote_url="https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + git push \ + --force-with-lease="refs/heads/${SOURCE_BRANCH}:${SOURCE_HEAD}" \ + "$remote_url" \ + "HEAD:refs/heads/${SOURCE_BRANCH}" From 5495d083f0e86559a5ad324026a0caf2a8231788 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 5 Aug 2026 00:18:56 +0000 Subject: [PATCH 21/57] fix(security): refresh remediated Strix hash lock --- .../one-shot-refresh-pr-755-strix-lock.yml | 89 ----- requirements-strix-ci-hashes.txt | 341 +++++++++--------- 2 files changed, 171 insertions(+), 259 deletions(-) delete mode 100644 .github/workflows/one-shot-refresh-pr-755-strix-lock.yml diff --git a/.github/workflows/one-shot-refresh-pr-755-strix-lock.yml b/.github/workflows/one-shot-refresh-pr-755-strix-lock.yml deleted file mode 100644 index 26422e34f..000000000 --- a/.github/workflows/one-shot-refresh-pr-755-strix-lock.yml +++ /dev/null @@ -1,89 +0,0 @@ -name: Refresh PR 755 Strix security lock - -on: - push: - branches: - - fix/opencode-llvm-coverage-tools - -permissions: - contents: read - -concurrency: - group: refresh-pr-755-strix-lock-${{ github.ref }} - cancel-in-progress: false - -jobs: - validate-lock: - if: >- - github.repository == 'ContextualWisdomLab/.github' - && github.ref == 'refs/heads/fix/opencode-llvm-coverage-tools' - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ github.sha }} - persist-credentials: false - - name: Materialize and verify the reviewed generated lock - env: - GH_TOKEN: ${{ github.token }} - LOCK_BLOB_SHA: c305e9c84c90f6d6e3360ab12feff7362d875b2d - shell: bash - run: | - set -euo pipefail - gh api "repos/${GITHUB_REPOSITORY}/git/blobs/${LOCK_BLOB_SHA}" \ - | jq -r '.content' \ - | tr -d '\n' \ - | base64 --decode \ - > requirements-strix-ci-hashes.txt - test "$(git hash-object requirements-strix-ci-hashes.txt)" = "$LOCK_BLOB_SHA" - grep -Fq 'aiohttp==3.14.3' requirements-strix-ci-hashes.txt - grep -Fq 'cryptography==50.0.0' requirements-strix-ci-hashes.txt - grep -Fxq 'aiohttp==3.14.3' requirements-strix-ci.txt - grep -Fxq 'cryptography==50.0.0' requirements-strix-ci.txt - git diff --check - - publish-lock: - needs: validate-lock - if: needs.validate-lock.result == 'success' - runs-on: ubuntu-latest - timeout-minutes: 10 - permissions: - contents: write - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - ref: ${{ github.sha }} - fetch-depth: 0 - persist-credentials: false - - name: Publish only the verified lock and remove this one-shot workflow - env: - GH_TOKEN: ${{ github.token }} - LOCK_BLOB_SHA: c305e9c84c90f6d6e3360ab12feff7362d875b2d - SOURCE_HEAD: ${{ github.sha }} - SOURCE_BRANCH: fix/opencode-llvm-coverage-tools - shell: bash - run: | - set -euo pipefail - live_head="$(gh api "repos/${GITHUB_REPOSITORY}/branches/${SOURCE_BRANCH}" --jq '.commit.sha')" - test "$live_head" = "$SOURCE_HEAD" - gh api "repos/${GITHUB_REPOSITORY}/git/blobs/${LOCK_BLOB_SHA}" \ - | jq -r '.content' \ - | tr -d '\n' \ - | base64 --decode \ - > requirements-strix-ci-hashes.txt - test "$(git hash-object requirements-strix-ci-hashes.txt)" = "$LOCK_BLOB_SHA" - grep -Fq 'aiohttp==3.14.3' requirements-strix-ci-hashes.txt - grep -Fq 'cryptography==50.0.0' requirements-strix-ci-hashes.txt - rm .github/workflows/one-shot-refresh-pr-755-strix-lock.yml - git config user.name 'github-actions[bot]' - git config user.email '41898282+github-actions[bot]@users.noreply.github.com' - git add requirements-strix-ci-hashes.txt \ - .github/workflows/one-shot-refresh-pr-755-strix-lock.yml - git diff --cached --check - git commit -m 'fix(security): refresh remediated Strix hash lock' - remote_url="https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" - git push \ - --force-with-lease="refs/heads/${SOURCE_BRANCH}:${SOURCE_HEAD}" \ - "$remote_url" \ - "HEAD:refs/heads/${SOURCE_BRANCH}" diff --git a/requirements-strix-ci-hashes.txt b/requirements-strix-ci-hashes.txt index e2c8f00eb..c305e9c84 100644 --- a/requirements-strix-ci-hashes.txt +++ b/requirements-strix-ci-hashes.txt @@ -4,127 +4,128 @@ aiohappyeyeballs==2.7.1 \ --hash=sha256:065665c041c42a5938ed220bdcd7230f22527fbec085e1853d2402c8a3615d9d \ --hash=sha256:9243213661e29250eb41368e5daa826fc017156c3b8a11440826b2e3ed376472 # via aiohttp -aiohttp==3.14.1 \ - --hash=sha256:03ab4530fdcb3a543a122ba4b65ac9919da9fe9f78a03d328a6e38ff962f7aa5 \ - --hash=sha256:07eabb979d236335fed927e137a928c9adfb7df3b9ec7aa31726f133a62be983 \ - --hash=sha256:092e4ce3619a7c6dee52a6bdabda973d9b34b66781f840ce93c7e0cec30cf521 \ - --hash=sha256:10ee9c1753a8f706345b22496c79fbddb5be0599e0823f3738b1534058e25340 \ - --hash=sha256:1601cc37baf5750ccacae618ec2daf020769581695550e3b654a911f859c563d \ - --hash=sha256:1ac8531b638959718e18c2207fbfe297819875da46a740b29dfa29beba64355a \ - --hash=sha256:1b9748363260121d2927704f5d4fc498150669ca3ae93625986ee89c8f80dcd4 \ - --hash=sha256:1c1421eb01d4fd608d88cc8290211d177a58532b55ad94076fb349c5bf467f0a \ - --hash=sha256:1c1af67559445498b502030c35c59db59966f47041ca9de5b4e707f86bd10b5f \ - --hash=sha256:1d459b98a932296c6f0e94f87511a0b1b90a8a02c30a50e60a297619cd5a58ee \ - --hash=sha256:20205f7f5ade7aaec9f4b500549bbc071b046453aed72f9c06dcab87896a83e8 \ - --hash=sha256:23119f8fd4f5d16902ed459b63b100bcd269628075162bddac56cc7b5273b3fb \ - --hash=sha256:237651caadc3a59badd39319c54642b5299e9cc98a3a194310e55d5bb9f5e397 \ - --hash=sha256:24ba13339fed9251d9b1a1bec8c7ab84c0d1675d79d33501e11f94f8b9a84e05 \ - --hash=sha256:250d14af67f6b6a1a4a811049b1afa69d61d617fca6bf33149b3ab1a6dbcf7b8 \ - --hash=sha256:269b76ac5394092b95bc4a098f4fc6c191c083c3bd12775d1e30e663132f6a09 \ - --hash=sha256:27fd7c91e51729b4f7e1577865fa6d34c9adccbc39aabe9000285b48af9f0ec2 \ - --hash=sha256:2964cbf553df4d7a57348da44d961d871895fc1ee4e8c322b2a95612c7b17fba \ - --hash=sha256:2a73f487ab8ef5abbb24b7aa9b73e98eaba9e9e031804ff2416f02eca315ccaf \ - --hash=sha256:2aa92c87868cd13674989f9ee83e5f9f7ea4237589b728048e1f0c8f6caa3271 \ - --hash=sha256:2b7edd08e0a5deb1e8564a2fcd8f4561014a3f05252334671bbf55ddd47db0e5 \ - --hash=sha256:2c840c90759922cb5e6dda94596e079a30fb5a5ba548e7e0dc00574703940847 \ - --hash=sha256:2f73e01dc37122325caf079982621262f96d74823c179038a82fddfc50359264 \ - --hash=sha256:2fbc3ed048b3475b9f0cbcb9978e9d2d3511acd91ead203af26ed9f0056004cf \ - --hash=sha256:2fe3607e71acc6ebb0ec8e492a247bf7a291226192dc0084236dfc12478916f6 \ - --hash=sha256:30099eda75a53c32efb0920e9c33c195314d2cc1c680fbfd30894932ac5f27df \ - --hash=sha256:307f2cff90a764d329e77040603fa032db89c5c24fdad50c4c15334cba744035 \ - --hash=sha256:313701e488100074ce99850404ee36e741abf6330179fec908a1944ecf570126 \ - --hash=sha256:317acd9f8602858dc7d59679812c376c7f0b97bcbbf16e0d6237f54141d8a8a6 \ - --hash=sha256:335c0cc3e3545ce98dcb9cfcb836f40c3411f43fa03dab757597d80c89af8a35 \ - --hash=sha256:34b257ec41345c1e8f2df68fa908a7952f5de932723871eb633ecbbff396c9a4 \ - --hash=sha256:367a9314fdc79dab0fac96e216cb41dd73c85bdca85306ce8999118ba7e0f333 \ - --hash=sha256:38e1e7daaea81df51c952e18483f323d878499a1e2bfe564790e0f9701d6f203 \ - --hash=sha256:3e6fc1a85fa7194a1a7d19f44e8609180f4a8eb5fa4c7ed8b4355f080fad235c \ - --hash=sha256:4132e72c608fe9fecb8f409113567605915b83e9bdd3ea56538d2f9cd35002f1 \ - --hash=sha256:4691802dda97be727f79d86818acaad7eb8e9252626a1d6b519fedbb92d5e251 \ - --hash=sha256:47ddf841cdecc810749921d25606dee45857d12d2ad5ddb7b5bd7eab12e4b365 \ - --hash=sha256:486f7d16ed54c39c2cbd7ca71fd8ba2b8bb7860df65bd7b6ed640bab96a38a8b \ - --hash=sha256:4cd96b5ba05d67ed0cf00b5b405c8cd99586d8e3481e8ee0a831057591af7621 \ - --hash=sha256:4d6e0ac9da31c9c04c84e1c0182ad8d6df35965a85cae29cd71d089621b3ae94 \ - --hash=sha256:4dfd6e47d3c44c2279907607f73a4240b88c69eb8b90da7e2441a8045dfd21da \ - --hash=sha256:4f7215cb3933784f79ed20e5f050e15984f390424339b22375d5a53c933a0491 \ - --hash=sha256:4fe1f1087cbadb280b5e1bb054a4f00d1423c74d6626c5e48400d871d34ecefe \ - --hash=sha256:52cdac9432d8b4a719f35094a818d95adcae0f0b4fe9b9b921909e0c87de9e7d \ - --hash=sha256:5663ee9257cfa1add7253a7da3035a02f31b6600ec48261585e1800a81533080 \ - --hash=sha256:57fc6745a4b7d0f5a9eb4f40a69718be6c0bc1b8368cc9fe89e90118719f4f42 \ - --hash=sha256:5a837f49d901f9e368651b676912bff1104ed8c1a83b280bcd7b29adccef5c9c \ - --hash=sha256:5c0b3e614340c889d575451696374c9d17affd54cd607ca0babed8f8c37b9397 \ - --hash=sha256:5e78b522b7a6e27e0b25d19b247b75039ac4c94f99823e3c9e53ae1603a9f7e9 \ - --hash=sha256:5f2504bc0322437c9a1ff6d3333ca56c7477b727c995f036b976ae17b98372c8 \ - --hash=sha256:603a2c834142172ffddc054067f5ec0ca65d57a0aa98a71bc81952573208e345 \ - --hash=sha256:62a759436b29e677181a9e76bab8b8f689a29cb9c535f45f7c48c9c830d3f8c3 \ - --hash=sha256:634e385930fb6d2d479cf3aa66515955863b77a5e3c2b5894ca259a25b308602 \ - --hash=sha256:64c567bf9eaf664280116a8688f63016e6b32db2505908e2bdaca1b6438142f2 \ - --hash=sha256:672ac254412a24d0d0cf00a9e6c238877e4be5e5fa2d188832c1244f45f31966 \ - --hash=sha256:672b9d65f42eb877f5c3f234a4547e4e1a226ca8c2eed879bb34670a0ce51192 \ - --hash=sha256:686b6c0d3911ec387b444ddf5dc62fb7f7c0a7d5186a7861626496a5ab4aff95 \ - --hash=sha256:6f71173be42d3241d428f760122febb748de0623f44308a6f120d0dd9ec572e3 \ - --hash=sha256:6fd35beba67c4183b09375c5fff9accb47524191a244a99f95fd4472f5402c2b \ - --hash=sha256:6ffbb2f4ec1ceaff7e07d43922954da26b223d188bf30658e561b98e23089444 \ - --hash=sha256:73f05ea02013e02512c3bf42714f1208c57168c779cc6fe23516e4543089d0a6 \ - --hash=sha256:764457a7be60825fb770a644852ff717bcbb5042f189f2bd16df61a81b3f6573 \ - --hash=sha256:797457503c2d426bee06eef808d07b31ede30b65e054444e7de64cad0061b7af \ - --hash=sha256:7c106c26852ca1c2047c6b80384f17100b4e439af276f21ef3d4e2f450ae7e15 \ - --hash=sha256:7fb4bdf95b0561a79f259f9d28fbc109728c5ee7f27aff6391f0ca703a329abe \ - --hash=sha256:819c054312f1af92947e6a55883d1b66feefab11531a7fc45e0fb9b63880b5c2 \ - --hash=sha256:8560b4d712474335d08907db7973f71912d3a9a8f1dee992ec06b5d2fe359496 \ - --hash=sha256:86a6dab78b0e43e2897a3bbe15745aa60dc5423ca437b7b0b164c069bf91b876 \ - --hash=sha256:87a5eea1b2a5e21e1ebdbb33ad4165359189327e63fc4e4894693e7f821ac817 \ - --hash=sha256:896e12dfdbbab9d8f7e16d2b28c6769a60126fa92095d1ebf9473d02593a2448 \ - --hash=sha256:8f6bb621e5863cfe8fe5ff5468002d200ec31f30f1280b259dc505b02595099e \ - --hash=sha256:90d53f1609c29ccc2193945ef732428382a28f78d0456ae4d3daf0d48b74f0f6 \ - --hash=sha256:915fbb7b41b115192259f8c9ae58f3ddc444d2b5579917270211858e606a4afd \ - --hash=sha256:93b032b5ec3255473c143627d21a69ac74ae12f7f33974cb587c564d11b1066f \ - --hash=sha256:94da27378da0610e341c4d30de29a191672683cc82b8f9556e8f7c7212a020fe \ - --hash=sha256:979ed4717f59b8bb12e3963378fa285d93d367e15bcd66c721311826d3c44a6c \ - --hash=sha256:97e704dcd26271f5bda3fa07c3ce0fb76d6d3f8659f4baa1a24442cc9ba177ca \ - --hash=sha256:99abd37084b82f5830c635fddd0b4993b9742a66eb746dacf433c8590e8f9e3c \ - --hash=sha256:9af6779bfb46abf124068327abcdf9ce95c9ef8287a3e8da76ccf2d0f16c28fa \ - --hash=sha256:9e8f2d660c350b3d0e259c7a7e3d9b7fc8b41210cbcc3d4a7076ff0a5e5c2fdc \ - --hash=sha256:a24f677ebe83749039e7bdf862ff0bbb16818ae4193d4ef96505e269375bcce0 \ - --hash=sha256:a9875b46d910cff3ea2f5962f9d266b465459fe634e22556ab9bd6fc1192eea0 \ - --hash=sha256:aa00140699487bd435fde4342d85c94cb256b7cd3a5b9c3396c67f19922afda2 \ - --hash=sha256:ae6be797afdef264e8a84864a85b196ca06045586481b3df8a967322fd2fa844 \ - --hash=sha256:af8b4b81a960eeaf1234971ac3cd0ba5901f3cd42eae42a46b4d089a8b492719 \ - --hash=sha256:b165790117eea512d7f3fb22f1f6dad3d55a7189571993eb015591c1401276d1 \ - --hash=sha256:b238af795833d5731d049d82bc84b768ae6f8f97f0495963b3ed9935c5901cc3 \ - --hash=sha256:b3a03285a7f9c7b016324574a6d92a1c895da6b978cb8f1deee3ac72bc6da178 \ - --hash=sha256:b6feea921016eb3d4e04d65fc4e9ca402d1a3801f562aef94989f54694917af3 \ - --hash=sha256:b6ff7fcee63287ae57b5df3e4f5957ce032122802509246dec1a5bcc55904c95 \ - --hash=sha256:b821a1f7dedf7e37450654e620038ac3b2e81e8fa6ea269337e97101978ec730 \ - --hash=sha256:bb2c0c80d431c0d03f2c7dbf125150fedd4f0de17366a7ca33f7ccb822391842 \ - --hash=sha256:bb33777ea21e8b7ecde0e6fc84f598be0a1192eab1a63bc746d75aa75d38e7bd \ - --hash=sha256:bcfb80a2cc36fba2534e5e5b5264dc7ae6fcd9bf15256da3e53d2f499e6fa29d \ - --hash=sha256:bd869c427324e5cb15195793de951295710db28be7d818247f3097b4ab5d4b96 \ - --hash=sha256:bedb0cd073cc2dc035e30aeb99444389d3cd2113afe4ef9fcd23d439f5bade85 \ - --hash=sha256:c389c482a7e9b9dc3ee2701ac46c4125297a3818875b9c305ddb603c04828fd1 \ - --hash=sha256:c6fa4dc7ad6f8109c70bb1499e589f76b0b792baf39f9b017eb92c8a81d0a199 \ - --hash=sha256:c83afe0ba876be7e943d2e0ba645809ad441575d2840c895c21ee5de93b9377a \ - --hash=sha256:cb21957bb8aca671c1765e32f58164cf0c50e6bf41c0bbbd16da20732ecaf588 \ - --hash=sha256:cf4491381b1b57425c315a56a439251b1bdac07b2275f19a8c44bc57744532ec \ - --hash=sha256:d03f281ed22579314ba00821ce20115a7c0ac430660b4cc05704a3f818b3e004 \ - --hash=sha256:d35143e27778b4bb0fb189562d7f275bff79c62ab8e98459717c0ea617ff2480 \ - --hash=sha256:d3b1a184a9a8f548a6b73f1e26b96b052193e4b3175ed7342aaf1151a1f00a04 \ - --hash=sha256:d44ec478e713ee7f29b439f7eb8dc2b9d4079e11ae114d2c2ac3d5daf30516c8 \ - --hash=sha256:d9d4e294455b23a68c9b8f042d0e8e377a265bcb15332753695f6e5b6819e0ce \ - --hash=sha256:de538791a80e5d862addbc183f70f0158ac9b9bb872bb147f1fd2a683691e087 \ - --hash=sha256:e4e5e0ae56914ecdbf446493addefc0159053dd53962cef37d7839f37f73d505 \ - --hash=sha256:e509a55f681e6158c20f70f102f9cf61fb20fbc382272bc6d94b7343f2582780 \ - --hash=sha256:ec8dc383ee57ea3e883477dcca3f11b65d58199f1080acaf4cd6ad9a99698be4 \ - --hash=sha256:ed09c7eb1c391271c2ed0314a51903e72a3acb653d5ccfc264cdf3ef11f8269d \ - --hash=sha256:eeea07c4397bbc57719c4eed8f9c284874d4f175f9b6d57f7a1546b976d455ca \ - --hash=sha256:eefd9cc9b6d4a2db5f00a26bc3e4f9acf71926a6ec557cd56c9c6f27c290b665 \ - --hash=sha256:f234b4deb12f3ad59127e037bc57c40c21e45b45282df7d3a55a0f409f595296 \ - --hash=sha256:f380468b09d2a81633ee863b0ec5648d364bd17bb8ecfb8c2f387f7ac1faf42c \ - --hash=sha256:f5e6ff2bdbb8f4cd3fbe41f99e25bbcd58e3bf9f13d3dd31a11e7917251cc77a \ - --hash=sha256:f7a16ef45b081454ef844502d87a848876c490c4cb5c650c230f6ec79ed2c1e7 \ - --hash=sha256:faccab372e66bc76d5731525e7f1143c922271725b9d38c9f97edcc66266b451 \ - --hash=sha256:fc0cacab7ba4e56f0f81c82a98c09bed2f39c940107b03a34b168bdf7597edd3 +aiohttp==3.14.3 \ + --hash=sha256:03cd2bde3d7f085b64e549c985f4bb928cad7e8ecf5323bfca320db548d81b39 \ + --hash=sha256:041badb8f84396357c4d3ad26de6afd7a32b112f43d3c63045c0c8278cfd2043 \ + --hash=sha256:0a5ff2dfbb9ce645fa5b8ef3e02c6c0b9cc3f6030ff863d0c51fffc50cb5541b \ + --hash=sha256:0fdea2281997af69da84c77ffa6f5938a0285f21fb3887c249d67419ca865b3d \ + --hash=sha256:11fb37ef075669eee52ab1928fbf6e1741fada40409fa309ebde9607a962aebf \ + --hash=sha256:134ac5ddcf61c6fad984b9a5727d83492ada43d63471db20fb73042c13fca62f \ + --hash=sha256:152516815ef926786a0b6ae2b8f1fd2e0c71582dee0b435636865316fd4891b7 \ + --hash=sha256:1576145bdceeb92382d899751e12743a3a5b8e460a841e3e50543859e54864dc \ + --hash=sha256:16100ad3ab8d649fdfbee87602d9d2dcdca9df0b9eda8a1b5fdc0d41f96da559 \ + --hash=sha256:16ea7e24c309fb7c0bbd505d149abe4fe4dccfb8db911db7dbec0921bc889a6f \ + --hash=sha256:18c441d0a8fca6de8d1f546849b9f0ab20d435993e2c5b59562b2fae6be2f929 \ + --hash=sha256:18cb43369747b2ae007bd2655fb8e63a099c2ff1d207962943636dac989b3147 \ + --hash=sha256:1b59533861b70a2185c8f4f350f791f39d64358ef6944ce71c5240c9ec0982c9 \ + --hash=sha256:1c5281acc88b92396f88c7e1e2748f8466689df22b80170e4f51efa712fb47a8 \ + --hash=sha256:1c5ec8fb1bcc31a8466f74aaf26c345d5c386fa4bd08a3f0eb9c7a4a3fe8b5bf \ + --hash=sha256:1caa7b0d05f3e3a36f87788c59e970a7ee1cefcfcbb924a9f138c4a6551c9cb7 \ + --hash=sha256:21c016079415ed3fd676963e9793700a566d85dbbd6bfc564b9b2d209147dcc8 \ + --hash=sha256:2498f0fe69ead802f9675beca44a7c21c62fdaa4ec5145ea1c3ad6edbee29f85 \ + --hash=sha256:25bd2708db6bdf6a6630dd37bdcdfcb47c4434d22ac69c64665b802910140b30 \ + --hash=sha256:270d3dace9ca2f10f0da5d8ebe519b7a310fc6112ed916e32df5866df0888553 \ + --hash=sha256:2e1161602f45a54de2ce0905243a95f58cb42dcd378402f3697f5e0b21e9d2e7 \ + --hash=sha256:2e9878ae68e4a5f1c0abe4dd497dbc3d51946f5837b56759e2a02e78fa90ef86 \ + --hash=sha256:30402d03a7c0ff52bce290b57e564e9079fd9d0cb545c8aba73f86a103162d2e \ + --hash=sha256:33a2d7c28d33797a2e99923dffa63f83d908a19b6bf26cfe80fa790aa5e1a75a \ + --hash=sha256:362a3fd481769cac1a824514bcd86fda51c65e8fe6e051099e008fddde6db17c \ + --hash=sha256:38901a84da3ce22249f6e860bf8f90d141bcab7da090cc398f8bb58c0e44b7da \ + --hash=sha256:39aded8c7f3b935b54aab1d8d73c70ec0ee2d3ec3b943e0e86611bc150ba47f5 \ + --hash=sha256:3a26434dafe408229ff3403458ca58de24fb51936504decac49ce6755f77e59d \ + --hash=sha256:3ae5b3a59436d089b5395d910121a390feed4d00578eb95a0fd1a329fe963100 \ + --hash=sha256:3d4f72af88ac2474bb5bca640030320e3d38a0163a1d7533500e87be458eef71 \ + --hash=sha256:3f42e9b78301f11c8f861746175d8b9c1ccef713fcad9eab396e2f6db8ed4a22 \ + --hash=sha256:42a67efc36300d052fb4508a53e8b6901b9284b599ae63945c377569c5fcc1e1 \ + --hash=sha256:48d67b87db6279c044760787eb01f6413032c2e6f3ba1cafaa492b1c8e578479 \ + --hash=sha256:498c6c623134f8e09a3c4e60bcd607a0b4590dd7dbf08dd40851b27cbb520ccb \ + --hash=sha256:49f7325beb0f85ef4aef5f48f490269575f83e6e2acad00a1d80b807eb027062 \ + --hash=sha256:4e3ac92d90e92773b2362d506068e9a948192bd553e743c5b2429e28527c8661 \ + --hash=sha256:530125ee1163c4219af35dc3aa1206e541e7b31b6efc1a3f93b70a136f65d427 \ + --hash=sha256:5373dc80ad1aa2fb9ad95c83f24eef418bbda3a61375f128e5b0192e4f3f9b32 \ + --hash=sha256:53e5179d8abb5710f8e83ba207c41c8d1261fcffd4616500e15ca2b7a33be10a \ + --hash=sha256:53e7b4ce82b54a8bcc71b3b67a5cbd177ca1d7f592cbc92cd38b7349f73482db \ + --hash=sha256:543906c127fb1d929b95076db19b83fa2d46751006ff1e23b093aa5ac4d8db42 \ + --hash=sha256:54cfcdee2770dac994417cbb0ee1f3eb0e7cb6b30c79bf44f2c02ff79ec5124a \ + --hash=sha256:55bdcc472aafe2de4a253045cc128007a64f1e0264fb675791e132ea5edaa3bd \ + --hash=sha256:56f355e79f71aef2a85c80305cc915f894b170dba76de5fe84f6351939b83c06 \ + --hash=sha256:5895ef58c4620afe02fa16044f023dc4dafec08158f9d08874a46a7dbc0341b8 \ + --hash=sha256:5bcb6ff3fdab1258a192679ff1a05d44f59626430aa05cd1a9d2447423599228 \ + --hash=sha256:5f08ec777f35ee70720233b8b9811d3bb5d728137f30ac91b7457709c3261ac0 \ + --hash=sha256:614c61d478b83953e261d02bb2df750f17227cd33ef8002945bf5aebbde21919 \ + --hash=sha256:617105e2c3018ee38d0c8ce5ee3c84f621a6d8b9f723202aacaff28449ca91ee \ + --hash=sha256:6debfa7312ff9d4c124dc71d72e9a0a4b9e0879e48ba6fcb42bef5c3300289e2 \ + --hash=sha256:7041d52c3a7fa20c9e8c182b534704abb19502c8bdcbde7ab23bfda6f642394f \ + --hash=sha256:70c987b27534f9ae1a723f47ae921571d616da21d3208282bf4c52af5164ac43 \ + --hash=sha256:74ab5b6a9fb13e873e5a90946588baecaf488745e1db1a4a5c433f971f035098 \ + --hash=sha256:78253b573e6ffab5028924fc98bc281aae05445969982a10864bc360dea2016c \ + --hash=sha256:7a75aa63cbf9b21cfaf60dc2657e19df2c2867d91707d653fee171ffeedd1371 \ + --hash=sha256:8800c996b01c2772a783e3e46f3e1abd5823029adca0df54231960de9bfefa5b \ + --hash=sha256:89176250f686cb9853c0fb7ead90e639e915b84a6f43eedc2a4e7ec21f1037f0 \ + --hash=sha256:8a5fd34f7f7410d1730d5c2ba873cacb2eed3fede366feb268a70ba22581ed8f \ + --hash=sha256:8b3b60de05f3dcb6f6a00f818bb2ec781cee4de0645f59ccaf99b1d1823b6100 \ + --hash=sha256:8f2f1c4c032c7cedd7d8da6f54c97b70266c6570c3108d3fdffee7188bb70529 \ + --hash=sha256:9491196535a88924a60afd5b5f434b5b203b6cc616250878dbdb223a8f7844bc \ + --hash=sha256:9aa6e61fdf20105c4144e755bd586008ff450791d67b1c8146fdc15959c4d51c \ + --hash=sha256:9d9edccfe496b476db5f398d97b865e9a6752bcf8aec4eef8390ce20fb64bb41 \ + --hash=sha256:9fc7b5bfec6573f3ae844f457fdde5adeb713f8b8e4a81ad64fc207b49383716 \ + --hash=sha256:a0dc483c00da8b673abbb367eb6f8d8f4bcec30eb58529ea13cb42e7fd2dfa33 \ + --hash=sha256:a3a8296e7ab5c295f53f1041487cb088e1480775aafbf7fe545d93b770a0f96f \ + --hash=sha256:a3e22975f905b89a55a488c2a08f2fdb2186175349e917d48985cc468a3d4c6e \ + --hash=sha256:a4af35c443e0b1a1bd6a8af3f3485d7fda15c142751a00f3ff8090f0b93346fa \ + --hash=sha256:a94dbaae5ae27bd849c93570669bff91e0510f33a80805738e3de72a7be0447b \ + --hash=sha256:ac74facc01463f138b0da5580329cfcc82818dea5656e83ddcd11268fc12ff80 \ + --hash=sha256:ad4c8b7488d745d2ca4838ebd8ae5ba9b56341d30b1da43640e4ce87f9f49646 \ + --hash=sha256:b014a6ed7cf912e787149fdc529166d3ceabac23f26efeea3158c9aba2354e7e \ + --hash=sha256:b20032766aedf6261c7a566585a40867d092ac03a0d81592d5370ef9b054f99b \ + --hash=sha256:b2466434105a4e03113c36ec775cc2ebe6676b62eae326fa670bb607ef788c1c \ + --hash=sha256:b304db572b4368edd8dda8a2274f73156fe15558fca4a917cb8a09fc47af5963 \ + --hash=sha256:ba59d59aba08ac02fc03b0c8983ccd5ee39a199d0552ce9e6d2b4845b34d59ae \ + --hash=sha256:bd52f811e65f6fb634b1047159657c98f52b407f8efec907bcfc09da9a4c0a25 \ + --hash=sha256:bdd0e2834dce1a26c1bbe26464861e16bbe217042cbff619247c11594472518c \ + --hash=sha256:c23ec8ee9d5ab2f5421f9c7fffce208435607af27fd46d4a44e031954352838f \ + --hash=sha256:c39846c3aad97a8530c89d7a3869a8f8e9e3762c6ac0504481e5c80948f7e807 \ + --hash=sha256:c3c200cf9757edd785051dc699c7ecbec22110dbfcb3fefc7a9f9695eda8ea7a \ + --hash=sha256:c7d3a97c678d34fc5b59da671ee9cd630096ddc643e7b5a30d54a2a6f3574d3f \ + --hash=sha256:c8653fd547c93a61aadc612007790f5555cdd18946fa48cf45e26d8ea4ea473d \ + --hash=sha256:cc7cb243a68167172f48c1fd43cee91ec4b1d40cefd190edd43369d1a6bc9c82 \ + --hash=sha256:ccd4893707b3e2a13e39c90d43cf80edf2e4d0457935bcc103bf2346214c3f15 \ + --hash=sha256:cd817772b2fcf2b8c0905795318485f9ec16eae60b29feb7f4c77085311637f0 \ + --hash=sha256:cda5fd5c95ad7a125a2e8464acc78b98b94c475a3780d6aa0aa157c93f470f4d \ + --hash=sha256:cef89a58e628c4efcac3275c2d68083f82426dcdc89c1492a6f654f9f7ea6ab9 \ + --hash=sha256:d1558173930a5a8d3069cee5c92fc91c87c4dbcb099debbb3622053717145a19 \ + --hash=sha256:d6088ec9894113802bddb3c09e974929aed2c7b3a8c456219b8aab4481f1a239 \ + --hash=sha256:d6218d92e450824e9b4881f44e8c09f1853b490f9a64130801024a4793b1b3b0 \ + --hash=sha256:d77640cc618c1d99fc4f8589c0f24a730adfa54eb1e57ef7bf0c8dfb78da898c \ + --hash=sha256:d7d2deec16eeedf55f2c7cf75b521ea3856a5177e123844f8fd0f114ce252cb5 \ + --hash=sha256:db332af25642007330fca8be5c4d194caf2bea7a7fc84415aff3497af5dfee6b \ + --hash=sha256:dd54d0e8717de95939766febac482ac0474d8ac3b048115f9f2b1d23a16e7db4 \ + --hash=sha256:ddcac3c6b382e81f1dd0499199d4136b877beb4cb5ef770bbbfba56c4b8f55d2 \ + --hash=sha256:df82f3787c940c94986b34222d59c9e38843fba85139f36e85255a82ad5355a9 \ + --hash=sha256:dfa68deb2a443bdaa3ea5297b0699c1464f08aef3812b486d1348eee61b07dc0 \ + --hash=sha256:dff9461ec275f22135650d5ba4b4931a11f3958df7dfbb8db630000d4dee0883 \ + --hash=sha256:e1e74298bab6ee0d6e749ed4fd1901c7e604bdda32c03d787a2cc71c46d0433d \ + --hash=sha256:e2667f0bbe7eb6c74eae5e9691441ad186e5845ca3cff63230fc09c4e7514f5d \ + --hash=sha256:e3be98a7c30b8c25d573dafba7171d66dfb05ee6a9070fc46535464ff97700a6 \ + --hash=sha256:e568e14940c09955aa51f4e645b6daa18a581c5dcfcd73744dcc86a856e3ced3 \ + --hash=sha256:e72ee89e28d907a18f46959b4eb0bb06701cc7f8cf4366e00029e2ccfaaf5924 \ + --hash=sha256:e92eb8acc45eb6a9f4935071a77edf5b85cc6f8dfad5cd99e97653c26593cdde \ + --hash=sha256:ea05e1f97ceea523942d9b2a7d7c0359d781d683d6b043f5943a602b14da4787 \ + --hash=sha256:eac645b09bcfdf73df7536331f0678c1086ea250981118ddb5199e17ccef72bb \ + --hash=sha256:eb0495d778817619273c108784292be161a924b9f5ae5cbbc70a2caa6838250b \ + --hash=sha256:ebe8e504f058fe91223351cecd2d9d6946c9d241bb0250d898ffbdf584cc72b0 \ + --hash=sha256:ed099d105449c4f9e84f24af203cd131349d4761d8813fa7e02c32e7128cd910 \ + --hash=sha256:f0f177d1b195b9e06376cfd7d308d8a1b920909a609d03ac82a8c73bbb16d3b9 \ + --hash=sha256:f3d2669fe7dec7fc359ecdb5984b29b50d85d5d00f8c1cb61de4f4a24ee42627 \ + --hash=sha256:f4e05329faa0ea1a404b37de4f034fd2c2defcca06a68dc6745e4e56c88e8a48 \ + --hash=sha256:f53bcd52f585e1ac3e590d61434eb61f9a88c38df041b4ea126d97144344a77b \ + --hash=sha256:f55119f7bf25f49ed210f6096090715da24f2943c62102448915fde3c62877ce \ + --hash=sha256:f631fe87a6f30df5fbe6d79640b25e4cffb38c31c7fb6f10871517b84b0f8c1a \ + --hash=sha256:f8fb78a83c9e5f741ca3a68cfb455c1f5bb83b4e7249a3848b3cd78d0a8563b0 \ + --hash=sha256:fa9467a8113aa69d3d7c55a70ef0b7c636010a40993f3df9d9d0d73b3eb7ef24 \ + --hash=sha256:fd51ebf9d3a00c074df4ede271023f4d2dba289bcc740b88191872716014e3c5 # via + # -r requirements-strix-ci.txt # gql # litellm aiosignal==1.4.0 \ @@ -401,53 +402,53 @@ click==8.4.1 \ # litellm # typer # uvicorn -cryptography==49.0.0 \ - --hash=sha256:026ac7423e6fa66872d3bf889be5974507da3944f866f704fa200eadacd00001 \ - --hash=sha256:07cab27cc7b7e0fd28e5e26bb9eeedde5c135c868b46de4a27845abe94af6122 \ - --hash=sha256:084ef1af862eb07ec46d25f68689f2102a9fc0e05ce7b80f14f5fe51e4eef0f6 \ - --hash=sha256:0b82e28ee398a386f0807bba7884d30f25218855690f45115831bcce5d90822c \ - --hash=sha256:0e959b578856a3924bc0cbb710fc12c387b9412a951389f3ca61704a9e25f325 \ - --hash=sha256:0f21641cf4b30fca7aee061ced0ec7ad7b073518088b7c9969a297c0ae796c69 \ - --hash=sha256:196ecd6a36e4e9aa10270393bb98d8df88fccee0bf1e5128b91ae4eb4375896d \ - --hash=sha256:2400ef9c9e2299a25614eb1dea3db54a69b1349efd043bfac9c67630d136df36 \ - --hash=sha256:28d8b15e6275f12c8a207dc309dfa957903c927d08d0cc937ee3f63f200693cc \ - --hash=sha256:2afe9051da7ae7bd5905da5a949280c7d2bb75682e188f650a9d0f2756b834c6 \ - --hash=sha256:2eda353d8a27bcbcaa4cbed18994a74ab4d19a2ca897db188ea269ab9b71419b \ - --hash=sha256:32703d93296f5c1f4b53349ad3a250c2cae0fdecd3a3dd5d47e616d8d616af27 \ - --hash=sha256:33cd0565932807baddb67b96dbee92f2c374b5c89dee09fd74079aeb8c8dba61 \ - --hash=sha256:35b151772baff2c74cba7fa290ceaff4c3b11c0c881eb93eb5dbc05a7cfbba18 \ - --hash=sha256:36d1709f992593689b45bda411498d62c6e365f2ca00b84657d4dadd24de16db \ - --hash=sha256:42b0684e0e40cf26122427802486f6d93aea593612603a94fbf260c7eb1e9c1b \ - --hash=sha256:4ae387c9cb68ea569ca17e490d66d8142b81c3cc814bf179974b7d146e490bbb \ - --hash=sha256:53ecee2e23f7169b6117e99fc8a944e5e50f79e69758a83b52a00cb98ab2b2d2 \ - --hash=sha256:66ec79c3904820572d7e987abdf304281f141d37ad9a489b8e97066e7b9b6459 \ - --hash=sha256:67e1d20ad9ef3a563c59ef22e7a8a0b8210bd26604369ea4a30a7c66aefe504e \ - --hash=sha256:6f2debedf9ca60cf1d5bd466475638af5130f89965605cd818484d19987d3a21 \ - --hash=sha256:6fc361c34fb6aac015ce19435876635e5c6d21db31998b0920f675f131e043b8 \ - --hash=sha256:73a205dce83953d131a4aa1e0fd917a2fd1c5b1eef251e9d7152efefcbf5caf7 \ - --hash=sha256:7abcee80084cda3f7691f3eb1ce480d8df49cec637b429aa35986c1de71738aa \ - --hash=sha256:8c25ceb16df5b9435f3f6a9829204985b0e0cbee3b48aacd432c7d2c850b44d9 \ - --hash=sha256:966fe0e9c67490071f14c0d2b1cb2dfb3023c5ce39457343931415f08382f2db \ - --hash=sha256:9e82dcc8e56052715fb18b2429e3bca4823b1629136a2084fc45a9a5cecb9b64 \ - --hash=sha256:b20133d204d2bb56ba047642199603876c872026ca53e79c35b83772ab2cc505 \ - --hash=sha256:b39efa323140595abd3ecca8529d321ae50f55f3aa3ba9cc81ea56a6011953d5 \ - --hash=sha256:b47db11c2c3525083296069b98ac5221907455e989ae0c2e3008bde851921615 \ - --hash=sha256:b87e65d263b3e5d3bb92a57e2a6638e2f31110fa7aa890c7b2dbba42248d0a3f \ - --hash=sha256:b970c6da94d5bb18629db453d14f2a1300f6bf59b61e9b82377931ef95504866 \ - --hash=sha256:be9fcb48a55f023493482827d4f459bd263cc20efde64f204b97c123201850c6 \ - --hash=sha256:c2bc30226390d60ea19d9f82b19db005fe0452154a23c1c410c12ea801e43561 \ - --hash=sha256:c83782480a4a9da4d0feb51950131ba32e12e70813848b3343f6e18c28a66838 \ - --hash=sha256:cbc77da8c523d5abd028635ba850a6966fcee2c82e2bf65a41d1d8afe0f98be9 \ - --hash=sha256:ccac2bfebc306b862133e3bb71f3f6ee8bb525240089b2d952e4144b3a6d5da7 \ - --hash=sha256:d0527ce944105f257f605a827d6ebead966c752038b6e8656abb9c5edee6fc68 \ - --hash=sha256:d8ecde755e2e91bf773fc94e8c9d730cd7f2007004cb492263a794ec3899a1c8 \ - --hash=sha256:e3fb64c420688e5319ae25113a354015abbd8dffbfbc41781a1ea66fc7622ac3 \ - --hash=sha256:e5dfc1e64de5677cec922ffa8da89c546d0415bf6efdf081842e5d44c84e1f0e \ - --hash=sha256:ec5e529fb80935c94fe7b729f9972b50e351a0e6b50aa294fd5cabb109fcc29a \ - --hash=sha256:f37d847238971164fdbc68ade6f6574aecc9c0af714190e2083429ff68f4ce9d \ - --hash=sha256:f78ff2c9ed8dc2d036b0f4d640e22522213d047c1b14e61205a7e55c80a494d4 \ - --hash=sha256:f89660a348f4f78a92366240a61404e337586ef7f5909a2fef59ca88ef505493 \ - --hash=sha256:fc1e275c2f1d97b1a6450b8b0ea3ebfa6e087a611c2b26cb2404d48588abab7b +cryptography==50.0.0 \ + --hash=sha256:031e2d5dd4bb9caa3ca9c82e5a197fd8ae680232cee62603d1a813f3f07e3d03 \ + --hash=sha256:06a32a980526a6ab9a4b9bf8f7385800791e2bb960903cb6b530e4817509a3b7 \ + --hash=sha256:07479a1cb08219ab719147e742e76090c9c773321959bb94946fffdd397a6437 \ + --hash=sha256:07949c449a1abcf60d1ee6e88956d89404c7df3c8258f46589e912988e551987 \ + --hash=sha256:105110f43a471dbd0060b9c9516cb8a6a79233631a04cc2ba16f28323ac6e025 \ + --hash=sha256:11b74db56cdbe3cdee6e3f6982ecb70334fa10dce99ed58bf7894aaaa3b2a037 \ + --hash=sha256:12b9c6996425c76ea6c457ace4f3073e715b8c545add07cd1a8f3a4f90691269 \ + --hash=sha256:1489e263a8048bb8b6a8bac662eb2d402ea5d2b7b4699b72f385f1e2772db105 \ + --hash=sha256:19736989797678c6af1e55cd49055cdbcb55d8f6b5583ac5335f933aba9101dc \ + --hash=sha256:1b4a266766514614f8aa60416e71f2fc6e575d36e7bdc90f644fadb2f4b75b95 \ + --hash=sha256:2a8183b489dc1f7f80f135780fadc1108f14b31b8a40411c7a5b17425f65f28b \ + --hash=sha256:37fdb0d0111f1e2ff07139dfb79f1b49531f8e213c46f1163dd7642979b58c47 \ + --hash=sha256:3f5735ffe4996d28b809371756219f5354864902a3b9e7c0b9ee87041209fc9c \ + --hash=sha256:49e7d93abdbd2990caced757e5fade25302f719c3c8fb6e6fff2dde98999fc41 \ + --hash=sha256:5e34edd123674534acd70147f0ca331eaa2c74e6325fb2028c886aa26ba0b68c \ + --hash=sha256:62598a8a57f815db4c6259a4e97d857dab56697e7de8e8ab02352ab74da1995d \ + --hash=sha256:65c2c3add92b45fd0709db8594536aea39c2a67af0e27ffcf049c498501140b7 \ + --hash=sha256:6ba6a53445bd3cfa809ef3ef5f1589aa6ba08784a1d962bf47d0940e871dab1c \ + --hash=sha256:6e7d61120573a7f2cd94cc095f9e81f6967c61ccdf194285aa143ecec8e0b708 \ + --hash=sha256:7cec5b856506da6defb290f30c9ee687d5f5e8cb0bd3f6459dde43b0b4fa40ef \ + --hash=sha256:80b63928fa35083b33966ce1efb70e5b9607181e49dcd1c22c8c005e319f667f \ + --hash=sha256:82148ec5bddac30b51a5b3c1945075f896fa022cb93f8e4a01e9f6ee95292c5f \ + --hash=sha256:828743d939e9629bc267b8e2d08d8bb67cd4319c771a33d4b18b22dd8fb7440a \ + --hash=sha256:8d89f3976b10b4ce31118de72329025f70d2c6ead14a8217c5514dd2c6d5a78f \ + --hash=sha256:8eb5e1172eb569ea8a872796576e6a67c276351728b6455d5beb01242b027c6a \ + --hash=sha256:900131fafd8aead39ac7dd3a7e833be754c17a95cfd91221636949fe4eb0aa8a \ + --hash=sha256:910d11e1a385c654bf738bf3e6b8e6ed5de0f5610fcae2be9e5b398d8081d20e \ + --hash=sha256:910e1d2668e7de9648f2bcee30e180db2a6b15c30f887d7c4c93ddf96e3992e3 \ + --hash=sha256:9aa87839c383bdbab6ef865787a1fb877af8dd03464c4400322726feaaadfc6d \ + --hash=sha256:a1b30560f2acc95aa8b2e06e716a13dbfc97314747b80d9707e307f77b40d6b3 \ + --hash=sha256:a91296cb61e8df6f86d0c19cc4068228da256bf59bf86049fbd821084565327f \ + --hash=sha256:b42a28c1844fd9de8f3f7d540e36b66f3a9c83fceac7170ebc7a6a19edd9dcae \ + --hash=sha256:bd1c592e4d5974f0d08d4888e432157adba757c66da0246918e43677fafa2d30 \ + --hash=sha256:c87f62a3d3b9888ed0fdde100ec06aa61ca9cd44bad9057d1dff9a516b5f5bb9 \ + --hash=sha256:c99c003e088647b8a5b7c145d6f78c335f6348332b62e142d411c4b63d1460b9 \ + --hash=sha256:ccdc4a71a4dabae05de219404f9f4abc38e3b58422177ff93d0da05967dafa07 \ + --hash=sha256:d24fead1d4d076e1bfb006dcec392074a3cd8d7b4fc8a595aa64073b2b7a96ba \ + --hash=sha256:d58c3db7cd6eed54e6c06744db55456b65ebd7492ddeae9c1e93cfca7aa857d3 \ + --hash=sha256:d764dcf130c428ef66786f866dd750f53182bc608813489915e9fc106bb0c82f \ + --hash=sha256:df2a58a472f332225671c35b0a830208b86d004f82baa8530fa3782c85646533 \ + --hash=sha256:e722f16708d854fe924790e051061f6704a472c3bac347b6fd88033ea8dd0dc5 \ + --hash=sha256:ecfed7367f965a0328cfbdd70da860f15441f002f613185668c6e6ebf5a0ac11 \ + --hash=sha256:eeac2acb5a20ed25e0ad6d1df9891a520b78b404266b6d11778f25d5d691a6c9 \ + --hash=sha256:f59e38625469987d7ef6d495323c55e7db6c212eaf6112267e0d3b565a2e9c9f \ + --hash=sha256:f89831ef99dd7dd169ab06d63a831adb9e20a87aac6d380266bbda5823349169 \ + --hash=sha256:fd9192b7b70c573d7f214eb1ae35e00d359f6f5e4b27c7e21e30de1fc6204645 # via # -r requirements-strix-ci.txt # google-auth @@ -1680,9 +1681,9 @@ pyjwt==2.13.0 \ --hash=sha256:41571c89ca91598c79e8ef18a2d07367d4810fbbd6f637794879baf1b7703423 \ --hash=sha256:66adcc2aff09b3f1bbd95fc1e1577df8ac8723c978552fd43304c8a290ac5728 # via mcp -pyopenssl==26.3.0 \ - --hash=sha256:46367f8f66b92271e6d218da9c87607e1ef5a0bc5c8dea5bb3db82f395c385a3 \ - --hash=sha256:589de7fae1c9ea670d18422ed00fc04da787bbde8e1454aea872aa57b49ad341 +pyopenssl==26.4.0 \ + --hash=sha256:28dfcce0162b9211413e26dfbfdf1d24317fbeba18fc93c12400a1856b2a0bc7 \ + --hash=sha256:f0eb0cb2d581d3ad2b9c489468485e7f2ab6727d08401bcf9d824c3caddf3c1c # via google-auth python-dateutil==2.9.0.post0 \ --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ From c59dd4357f944c19ff36736820e8b1f49df7171d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 10:02:09 +0900 Subject: [PATCH 22/57] fix(ci): align scheduled CodeQL action revision --- .github/workflows/scheduled-security-scan.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/scheduled-security-scan.yml b/.github/workflows/scheduled-security-scan.yml index 8ecb5185b..6b19cf257 100644 --- a/.github/workflows/scheduled-security-scan.yml +++ b/.github/workflows/scheduled-security-scan.yml @@ -90,13 +90,13 @@ jobs: with: persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - name: Perform CodeQL Analysis continue-on-error: true - uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4 with: category: "/language:${{ matrix.language }}-scheduled" From 99ba68240c0a4fcac7bdc7dc03e929a2c035fbb6 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 13:54:31 +0900 Subject: [PATCH 23/57] ci: verify and repair early diagnostic redaction --- ...e-shot-redact-materializer-diagnostics.yml | 397 ++++++++++++++++++ 1 file changed, 397 insertions(+) create mode 100644 .github/workflows/one-shot-redact-materializer-diagnostics.yml diff --git a/.github/workflows/one-shot-redact-materializer-diagnostics.yml b/.github/workflows/one-shot-redact-materializer-diagnostics.yml new file mode 100644 index 000000000..3c354feb1 --- /dev/null +++ b/.github/workflows/one-shot-redact-materializer-diagnostics.yml @@ -0,0 +1,397 @@ +name: One-shot redact materializer diagnostics + +on: + push: + branches: + - fix/opencode-coverage-failure-diagnostics + paths: + - .github/workflows/one-shot-redact-materializer-diagnostics.yml + +concurrency: + group: one-shot-redact-materializer-diagnostics + cancel-in-progress: false + +permissions: + contents: write + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + repair: + runs-on: ubuntu-latest + timeout-minutes: 25 + steps: + - name: Harden runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Checkout repair head + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.sha }} + fetch-depth: 0 + + - name: Set up Python 3.14 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.14" + cache: pip + cache-dependency-path: requirements-opencode-review-ci-hashes.txt + + - name: Install hash-locked verification tooling + run: >- + python -m pip install --disable-pip-version-check --require-hashes + -r requirements-opencode-review-ci-hashes.txt + + - name: Add failing secret-redaction and exact-head contracts + run: | + python - <<'PY' + from pathlib import Path + + def replace_once(path: Path, old: str, new: str) -> None: + text = path.read_text(encoding="utf-8") + if text.count(old) != 1: + raise SystemExit(f"expected one test marker in {path}, found {text.count(old)}") + path.write_text(text.replace(old, new, 1), encoding="utf-8") + + sanitizer_test = Path("tests/test_sanitize_github_output_summary.py") + sanitizer_marker = "\n\ndef test_cli_writes_sanitized_summary(tmp_path, monkeypatch):\n" + sanitizer_contract = ''' + +def test_sanitizes_url_and_authorization_before_later_secret_assignment(): + """Mixed one-line diagnostics redact every credential before truncation.""" + sanitized = sanitize_text( + "failure https://user:url-secret@example.invalid/lock " + "Authorization: Bearer bearer-secret TOKEN=token-secret\\n" + ) + + assert sanitized == ( + "failure https://@example.invalid/lock " + "Authorization: Bearer TOKEN=\\n" + ) + assert "url-secret" not in sanitized + assert "bearer-secret" not in sanitized + assert "token-secret" not in sanitized +''' + replace_once( + sanitizer_test, + sanitizer_marker, + sanitizer_contract + sanitizer_marker, + ) + + diagnostics_test = Path("tests/test_coverage_materializer_failure_diagnostics.py") + diagnostics_marker = ''' + +@pytest.mark.parametrize( + "module", + [javascript_materializer, python_materializer], + ids=["javascript", "python"], +) +def test_failure_diagnostics_are_optional_outside_github_actions( +''' + diagnostics_contract = ''' + +@pytest.mark.parametrize( + "module", + [javascript_materializer, python_materializer], + ids=["javascript", "python"], +) +def test_failure_diagnostics_redact_secret_like_values( + module: ModuleType, + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Lock-derived failures cannot publish credentials through job outputs.""" + output_file = tmp_path / "github-output" + secret_reason = ( + "current-head lock https://user:url-secret@example.invalid/lock " + "Authorization: Bearer bearer-secret TOKEN=token-secret" + ) + monkeypatch.setenv("GITHUB_OUTPUT", str(output_file)) + monkeypatch.setattr( + module, + "materialize", + _failing_materializer(ValueError(secret_reason)), + ) + + assert _run_main(module, tmp_path) == 1 + + published = output_file.read_text(encoding="utf-8") + assert "- Result: FAIL" in published + assert "https://<redacted>@example.invalid/lock" in published + assert "Authorization: Bearer <redacted>" in published + assert "TOKEN=<redacted>" in published + assert "url-secret" not in published + assert "bearer-secret" not in published + assert "token-secret" not in published + + +def test_diagnostics_workflow_checks_out_exact_pull_request_head() -> None: + """Compatibility and coverage evidence must measure the contributor head.""" + workflow = Path( + ".github/workflows/opencode-coverage-diagnostics-ci.yml" + ).read_text(encoding="utf-8") + + assert workflow.count("ref: ${{ github.event.pull_request.head.sha }}") == 2 +''' + replace_once( + diagnostics_test, + diagnostics_marker, + diagnostics_contract + diagnostics_marker, + ) + PY + + - name: Prove the new contracts fail before implementation + run: | + set +e + python -m pytest \ + tests/test_coverage_materializer_failure_diagnostics.py \ + tests/test_sanitize_github_output_summary.py \ + -q >"${RUNNER_TEMP}/red-phase.log" 2>&1 + status=$? + set -e + cat "${RUNNER_TEMP}/red-phase.log" + test "$status" -ne 0 + grep -q "test_failure_diagnostics_redact_secret_like_values" "${RUNNER_TEMP}/red-phase.log" + grep -q "test_sanitizes_url_and_authorization_before_later_secret_assignment" "${RUNNER_TEMP}/red-phase.log" + grep -q "test_diagnostics_workflow_checks_out_exact_pull_request_head" "${RUNNER_TEMP}/red-phase.log" + + - name: Apply shared redaction and exact-head implementation + run: | + python - <<'PY' + from pathlib import Path + + def replace_once(path: Path, old: str, new: str) -> None: + text = path.read_text(encoding="utf-8") + if text.count(old) != 1: + raise SystemExit(f"expected one implementation marker in {path}, found {text.count(old)}") + path.write_text(text.replace(old, new, 1), encoding="utf-8") + + sanitizer = Path("scripts/ci/sanitize_github_output_summary.py") + replace_once( + sanitizer, + ''' match = SECRET_KEY_RE.search(line) + if match: + return f"{line[: match.end()]}" + line = URL_CREDENTIAL_RE.sub(r"\\1@", line) + return AUTH_HEADER_RE.sub(r"\\1\\2 ", line) +''', + ''' line = URL_CREDENTIAL_RE.sub(r"\\1@", line) + line = AUTH_HEADER_RE.sub(r"\\1\\2 ", line) + match = SECRET_KEY_RE.search(line) + if match: + return f"{line[: match.end()]}" + return line +''', + ) + + for file_name, constant_marker in ( + ( + "scripts/ci/materialize_base_javascript_packages.py", + 'SHA512_SRI_RE = re.compile(r"^sha512-[A-Za-z0-9+/]{86}==$")\n', + ), + ( + "scripts/ci/materialize_base_python_requirements.py", + "UV_EXPORT_TIMEOUT_SECONDS = 120\n", + ), + ): + path = Path(file_name) + replace_once(path, "import re\n", "import re\nimport runpy\n") + shared_loader = ( + constant_marker + + "_SANITIZE_TEXT = runpy.run_path(\n" + + " str(pathlib.Path(__file__).with_name(\"sanitize_github_output_summary.py\")),\n" + + " run_name=\"cwl_coverage_summary_sanitizer\",\n" + + ")[\"sanitize_text\"]\n" + ) + replace_once(path, constant_marker, shared_loader) + replace_once( + path, + ''' safe_reason = html.escape( + f"{error.__class__.__name__}: {' '.join(str(error).split())}"[:4096], + quote=True, + ).replace(delimiter, "CWL_COVERAGE_SUMMARY_END") +''', + ''' redacted_reason = _SANITIZE_TEXT( + f"{error.__class__.__name__}: {' '.join(str(error).split())}"[:4096] + ) + safe_reason = html.escape(redacted_reason, quote=True).replace( + delimiter, "CWL_COVERAGE_SUMMARY_END" + ) +''', + ) + + workflow = Path(".github/workflows/opencode-coverage-diagnostics-ci.yml") + text = workflow.read_text(encoding="utf-8") + replacements = ( + ( + ' - "scripts/ci/materialize_base_python_requirements.py"\n', + ' - "scripts/ci/materialize_base_python_requirements.py"\n' + ' - "scripts/ci/sanitize_github_output_summary.py"\n', + 2, + ), + ( + ' - "tests/test_coverage_materializer_failure_diagnostics.py"\n', + ' - "tests/test_coverage_materializer_failure_diagnostics.py"\n' + ' - "tests/test_sanitize_github_output_summary.py"\n', + 2, + ), + ( + " persist-credentials: false\n", + " persist-credentials: false\n" + " ref: ${{ github.event.pull_request.head.sha }}\n", + 2, + ), + ( + " scripts/ci/materialize_base_python_requirements.py\n", + " scripts/ci/materialize_base_python_requirements.py \\\n" + " scripts/ci/sanitize_github_output_summary.py\n", + 1, + ), + ( + " tests/test_coverage_materializer_failure_diagnostics.py \\\n" + " tests/test_strix_dependency_security_floor.py \\\n", + " tests/test_coverage_materializer_failure_diagnostics.py \\\n" + " tests/test_sanitize_github_output_summary.py \\\n" + " tests/test_strix_dependency_security_floor.py \\\n", + 1, + ), + ( + " --cov=scripts.ci.materialize_base_python_requirements \\\n" + " --cov-branch \\\n", + " --cov=scripts.ci.materialize_base_python_requirements \\\n" + " --cov=scripts.ci.sanitize_github_output_summary \\\n" + " --cov-branch \\\n", + 1, + ), + ( + " scripts/ci/materialize_base_python_requirements.py\n", + " scripts/ci/materialize_base_python_requirements.py \\\n" + " scripts/ci/sanitize_github_output_summary.py\n", + 1, + ), + ( + " scripts/ci/materialize_base_python_requirements.py \\\n" + " tests/test_coverage_materializer_failure_diagnostics.py \\\n", + " scripts/ci/materialize_base_python_requirements.py \\\n" + " scripts/ci/sanitize_github_output_summary.py \\\n" + " tests/test_coverage_materializer_failure_diagnostics.py \\\n" + " tests/test_sanitize_github_output_summary.py \\\n", + 1, + ), + ) + for old, new, expected_count in replacements: + actual_count = text.count(old) + if actual_count < expected_count: + raise SystemExit( + f"workflow marker count for {old!r}: expected at least {expected_count}, got {actual_count}" + ) + text = text.replace(old, new, expected_count) + workflow.write_text(text, encoding="utf-8") + + doctoring = Path("docs/doctoring/coverage-failure-evidence-redaction.md") + doctoring.write_text( + '''# Coverage failure evidence redaction + +## Decision + +Early JavaScript and Python dependency-materialization failures remain blocking, +but no pull-request-derived exception text is written to `GITHUB_OUTPUT` before +secret-like values are redacted. The materializers load the existing trusted +sibling sanitizer by an exact local file path so the central workflow can retain +Python isolated mode (`-I`) without relying on `PYTHONPATH`, site packages, or a +PR-controlled import location. + +The publication order is: + +1. collapse untrusted exception whitespace and bound the evidence; +2. redact URL credentials and authorization values; +3. redact secret-, token-, password-, connection-, and key-like assignments; +4. HTML-escape the remaining diagnostic context; +5. replace the fixed multiline delimiter if it appears in the value; +6. write the bounded summary while preserving the materializer's nonzero status. + +URL and authorization redaction precede assignment redaction because materializer +exceptions are flattened to one line. Otherwise a later `TOKEN=...` match could +truncate the line before an earlier URL credential had been sanitized. + +## Verification contract + +Regression tests cover both materializers, URL credentials, bearer credentials, +token assignments, HTML escaping, delimiter replacement, length bounds, local +execution without `GITHUB_OUTPUT`, and unchanged failure exit status. The +dedicated Python 3.10/3.14 workflow now checks out the exact pull-request head, +includes the shared sanitizer in its trigger and coverage surfaces, and requires +100% statement, branch, and production-docstring evidence. + +This control follows GitHub's environment-file contract while treating job output +as a publication sink rather than a private scratch file. It also follows the +CWE-532 and OWASP guidance to remove or mask tokens, passwords, connection +strings, keys, and other sensitive values before operational evidence is stored +or propagated. It does not claim formal conformance to either source. + +## References + +GitHub, Inc. (n.d.). *Workflow commands for GitHub Actions*. GitHub Docs. +Retrieved August 5, 2026, from +https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands + +MITRE. (2026). *CWE-532: Insertion of sensitive information into log file +(version 4.20)*. Common Weakness Enumeration. +https://cwe.mitre.org/data/definitions/532.html + +OWASP Foundation. (n.d.). *Logging cheat sheet*. OWASP Cheat Sheet Series. +Retrieved August 5, 2026, from +https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html +''', + encoding="utf-8", + ) + PY + + - name: Verify focused coverage, docstrings, syntax, and full tests + run: | + python -m pytest \ + tests/test_materialize_base_javascript_packages.py \ + tests/test_materialize_base_python_requirements.py \ + tests/test_coverage_materializer_failure_diagnostics.py \ + tests/test_sanitize_github_output_summary.py \ + tests/test_strix_dependency_security_floor.py \ + --cov=scripts.ci.materialize_base_javascript_packages \ + --cov=scripts.ci.materialize_base_python_requirements \ + --cov=scripts.ci.sanitize_github_output_summary \ + --cov-branch \ + --cov-fail-under=100 \ + -q + python -m interrogate \ + --fail-under 100 \ + scripts/ci/materialize_base_javascript_packages.py \ + scripts/ci/materialize_base_python_requirements.py \ + scripts/ci/sanitize_github_output_summary.py + python -m compileall -q \ + scripts/ci/materialize_base_javascript_packages.py \ + scripts/ci/materialize_base_python_requirements.py \ + scripts/ci/sanitize_github_output_summary.py \ + tests/test_coverage_materializer_failure_diagnostics.py \ + tests/test_sanitize_github_output_summary.py + python -m pytest tests -q + git diff --check + + - name: Commit verified repair and remove one-shot workflow + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git rm .github/workflows/one-shot-redact-materializer-diagnostics.yml + git add \ + .github/workflows/opencode-coverage-diagnostics-ci.yml \ + docs/doctoring/coverage-failure-evidence-redaction.md \ + scripts/ci/materialize_base_javascript_packages.py \ + scripts/ci/materialize_base_python_requirements.py \ + scripts/ci/sanitize_github_output_summary.py \ + tests/test_coverage_materializer_failure_diagnostics.py \ + tests/test_sanitize_github_output_summary.py + git commit -m "fix(opencode): redact early coverage diagnostics" + git push origin "HEAD:${GITHUB_REF_NAME}" From b1720b514a0755bbc2ca5c5302185d08001dc68c Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 13:55:13 +0900 Subject: [PATCH 24/57] ci: repair early coverage diagnostic redaction --- .../repair-pr759-early-redaction.yml | 272 ++++++++++++++++++ 1 file changed, 272 insertions(+) create mode 100644 .github/workflows/repair-pr759-early-redaction.yml diff --git a/.github/workflows/repair-pr759-early-redaction.yml b/.github/workflows/repair-pr759-early-redaction.yml new file mode 100644 index 000000000..a70d7d1f3 --- /dev/null +++ b/.github/workflows/repair-pr759-early-redaction.yml @@ -0,0 +1,272 @@ +name: Repair PR 759 early coverage diagnostic redaction + +on: + push: + branches: + - fix/opencode-coverage-failure-diagnostics + +permissions: + contents: read + +concurrency: + group: repair-pr759-early-redaction + cancel-in-progress: true + +jobs: + repair: + if: >- + github.repository == 'ContextualWisdomLab/.github' && + github.actor == 'seonghobae' && + github.ref == 'refs/heads/fix/opencode-coverage-failure-diagnostics' + runs-on: ubuntu-24.04 + timeout-minutes: 30 + permissions: + contents: write + steps: + - name: Check out exact repair source + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.sha }} + fetch-depth: 0 + persist-credentials: false + + - name: Verify exact audited inputs + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + test "$(git hash-object scripts/ci/materialize_base_javascript_packages.py)" = "99802d6b62496d9e3791c9a50ca39562b0307f35" + test "$(git hash-object scripts/ci/materialize_base_python_requirements.py)" = "4d37759621c2370ffccbcc9388ede71b38be1f15" + test "$(git hash-object scripts/ci/sanitize_github_output_summary.py)" = "1a7036f755ef1d9c37ec4d7956c22b994c9e7915" + test "$(git hash-object tests/test_coverage_materializer_failure_diagnostics.py)" = "d612c13c3abb4fece211a4de4a06300b4cac445f" + + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.14" + cache: pip + cache-dependency-path: requirements-opencode-review-ci-hashes.txt + + - name: Install hash-locked test dependencies + run: python -m pip install --require-hashes -r requirements-opencode-review-ci-hashes.txt + + - name: Add the regression before production repair + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + python - <<'PY' + from pathlib import Path + + path = Path("tests/test_coverage_materializer_failure_diagnostics.py") + text = path.read_text(encoding="utf-8") + if "test_failure_diagnostics_redact_secret_like_values_before_github_output" in text: + raise SystemExit("regression test already exists") + text = text.replace("import json\n", "import html\nimport json\n", 1) + import_anchor = ( + "from scripts.ci import materialize_base_python_requirements as python_materializer\n" + ) + import_replacement = ( + import_anchor + + "from scripts.ci import sanitize_github_output_summary as summary_sanitizer\n" + ) + if text.count(import_anchor) != 1: + raise SystemExit("unexpected sanitizer import anchor") + text = text.replace(import_anchor, import_replacement, 1) + insertion_anchor = ''' + +@pytest.mark.parametrize( + "module", + [javascript_materializer, python_materializer], + ids=["javascript", "python"], +) +def test_failure_diagnostics_are_optional_outside_github_actions( +''' + regression = ''' + +@pytest.mark.parametrize( + ("unsafe_reason", "secret_value"), + [ + ( + "current-head lock contains unsafe package path " + "node_modules/NPM_TOKEN=ghp_materializer_secret/package.json", + "ghp_materializer_secret", + ), + ( + "registry URL https://alice:registry-password@example.com/archive.tgz", + "registry-password", + ), + ( + "Authorization: Bearer coverage-diagnostic-token", + "coverage-diagnostic-token", + ), + ], +) +@pytest.mark.parametrize( + "module", + [javascript_materializer, python_materializer], + ids=["javascript", "python"], +) +def test_failure_diagnostics_redact_secret_like_values_before_github_output( + module: ModuleType, + unsafe_reason: str, + secret_value: str, + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Early failure evidence applies the shared redaction contract before publication.""" + output_file = tmp_path / "github-output" + monkeypatch.setenv("GITHUB_OUTPUT", str(output_file)) + monkeypatch.setattr( + module, + "materialize", + _failing_materializer(ValueError(unsafe_reason)), + ) + + assert _run_main(module, tmp_path) == 1 + + published = output_file.read_text(encoding="utf-8") + normalized = f"ValueError: {' '.join(unsafe_reason.split())}" + expected = html.escape(summary_sanitizer.sanitize_text(normalized), quote=True) + assert expected in published + assert secret_value not in published + assert "<redacted>" in published +''' + if text.count(insertion_anchor) != 1: + raise SystemExit("unexpected regression insertion anchor") + text = text.replace(insertion_anchor, regression + insertion_anchor, 1) + path.write_text(text, encoding="utf-8") + PY + + - name: Prove the regression is red + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + set +e + python -m pytest \ + tests/test_coverage_materializer_failure_diagnostics.py \ + -k redact_secret_like_values_before_github_output \ + > /tmp/pr759-red.log 2>&1 + status=$? + set -e + cat /tmp/pr759-red.log + test "$status" -ne 0 + grep -F "secret_value not in published" /tmp/pr759-red.log + + - name: Apply the bounded production repair + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + python - <<'PY' + from pathlib import Path + + helper = ''' + +_COVERAGE_SECRET_KEY_RE = re.compile( + r"(?i)(?P\\b[A-Z0-9_.-]*(?:" + r"AUTH[_-]?SESSION[_-]?HMAC[_-]?SECRET|" + r"DATABASE[_-]?URL|DB[_-]?URL|CONNECTION[_-]?STRING|" + r"SECRET|TOKEN|PASSWORD|PASSWD|" + r"API[_-]?KEY|PRIVATE[_-]?KEY|ACCESS[_-]?KEY|ENCRYPTION[_-]?KEY" + r")[A-Z0-9_.-]*\\b)(?P\\s*[:=]\\s*)" +) +_COVERAGE_URL_CREDENTIAL_RE = re.compile( + r"(?i)\\b([a-z][a-z0-9+.-]*://)([^/\\s:@]+):([^@\\s/]+)@" +) +_COVERAGE_AUTH_HEADER_RE = re.compile( + r"(?i)\\b(Authorization\\s*[:=]\\s*)(Bearer|Basic)\\s+[^\\s,;]+" +) + + +def _redact_coverage_failure_reason(reason: str) -> str: + """Redact secret-like values before publishing early coverage evidence.""" + match = _COVERAGE_SECRET_KEY_RE.search(reason) + if match: + return f"{reason[: match.end()]}" + reason = _COVERAGE_URL_CREDENTIAL_RE.sub(r"\\1@", reason) + return _COVERAGE_AUTH_HEADER_RE.sub(r"\\1\\2 ", reason) +''' + + replacements = { + Path("scripts/ci/materialize_base_javascript_packages.py"): ( + 'SHA512_SRI_RE = re.compile(r"^sha512-[A-Za-z0-9+/]{86}==$")\n', + 'SHA512_SRI_RE = re.compile(r"^sha512-[A-Za-z0-9+/]{86}==$")\n' + helper, + ), + Path("scripts/ci/materialize_base_python_requirements.py"): ( + "UV_EXPORT_TIMEOUT_SECONDS = 120\n", + "UV_EXPORT_TIMEOUT_SECONDS = 120\n" + helper, + ), + } + old_reason = ''' safe_reason = html.escape( + f"{error.__class__.__name__}: {' '.join(str(error).split())}"[:4096], + quote=True, + ).replace(delimiter, "CWL_COVERAGE_SUMMARY_END") +''' + new_reason = ''' redacted_reason = _redact_coverage_failure_reason( + f"{error.__class__.__name__}: {' '.join(str(error).split())}" + ) + safe_reason = html.escape(redacted_reason[:4096], quote=True).replace( + delimiter, "CWL_COVERAGE_SUMMARY_END" + ) +''' + for path, (anchor, replacement) in replacements.items(): + text = path.read_text(encoding="utf-8") + if text.count(anchor) != 1: + raise SystemExit(f"unexpected helper anchor in {path}") + if text.count(old_reason) != 1: + raise SystemExit(f"unexpected reason anchor in {path}") + text = text.replace(anchor, replacement, 1) + text = text.replace(old_reason, new_reason, 1) + path.write_text(text, encoding="utf-8") + PY + python -m compileall -q \ + scripts/ci/materialize_base_javascript_packages.py \ + scripts/ci/materialize_base_python_requirements.py \ + tests/test_coverage_materializer_failure_diagnostics.py + git diff --check + + - name: Verify the focused redaction contract + run: | + python -m pytest \ + tests/test_coverage_materializer_failure_diagnostics.py \ + tests/test_sanitize_github_output_summary.py + + - name: Verify production coverage and docstrings + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + python -m coverage erase + python -m coverage run --branch -m pytest \ + tests/test_coverage_materializer_failure_diagnostics.py \ + tests/test_materialize_base_javascript_packages.py \ + tests/test_materialize_base_python_requirements.py \ + tests/test_sanitize_github_output_summary.py + python -m coverage report --fail-under=100 \ + scripts/ci/materialize_base_javascript_packages.py \ + scripts/ci/materialize_base_python_requirements.py \ + scripts/ci/sanitize_github_output_summary.py + python -m interrogate \ + --fail-under 100 \ + scripts/ci/materialize_base_javascript_packages.py \ + scripts/ci/materialize_base_python_requirements.py \ + scripts/ci/sanitize_github_output_summary.py + + - name: Commit exact validated repair and remove one-shot workflow + env: + GH_TOKEN: ${{ github.token }} + SOURCE_HEAD: ${{ github.sha }} + SOURCE_BRANCH: fix/opencode-coverage-failure-diagnostics + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + rm .github/workflows/repair-pr759-early-redaction.yml + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add \ + scripts/ci/materialize_base_javascript_packages.py \ + scripts/ci/materialize_base_python_requirements.py \ + tests/test_coverage_materializer_failure_diagnostics.py \ + .github/workflows/repair-pr759-early-redaction.yml + test "$(git diff --cached --name-only | sort)" = "$(printf '%s\n' \ + .github/workflows/repair-pr759-early-redaction.yml \ + scripts/ci/materialize_base_javascript_packages.py \ + scripts/ci/materialize_base_python_requirements.py \ + tests/test_coverage_materializer_failure_diagnostics.py | sort)" + git diff --cached --check + git commit -m "fix(opencode-review): redact early coverage diagnostics" + remote_url="https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + git push --force-with-lease="refs/heads/${SOURCE_BRANCH}:${SOURCE_HEAD}" \ + "$remote_url" "HEAD:refs/heads/${SOURCE_BRANCH}" From 30a40e307236315b6aab5f531524656869dacc2d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 14:01:56 +0900 Subject: [PATCH 25/57] ci: make PR759 redaction repair deterministic --- .../repair-pr759-early-redaction.yml | 56 +++++++------------ 1 file changed, 21 insertions(+), 35 deletions(-) diff --git a/.github/workflows/repair-pr759-early-redaction.yml b/.github/workflows/repair-pr759-early-redaction.yml index a70d7d1f3..5aa473766 100644 --- a/.github/workflows/repair-pr759-early-redaction.yml +++ b/.github/workflows/repair-pr759-early-redaction.yml @@ -16,7 +16,6 @@ jobs: repair: if: >- github.repository == 'ContextualWisdomLab/.github' && - github.actor == 'seonghobae' && github.ref == 'refs/heads/fix/opencode-coverage-failure-diagnostics' runs-on: ubuntu-24.04 timeout-minutes: 30 @@ -60,16 +59,15 @@ jobs: if "test_failure_diagnostics_redact_secret_like_values_before_github_output" in text: raise SystemExit("regression test already exists") text = text.replace("import json\n", "import html\nimport json\n", 1) - import_anchor = ( - "from scripts.ci import materialize_base_python_requirements as python_materializer\n" - ) - import_replacement = ( - import_anchor - + "from scripts.ci import sanitize_github_output_summary as summary_sanitizer\n" - ) + import_anchor = "from scripts.ci import materialize_base_python_requirements as python_materializer\n" if text.count(import_anchor) != 1: raise SystemExit("unexpected sanitizer import anchor") - text = text.replace(import_anchor, import_replacement, 1) + text = text.replace( + import_anchor, + import_anchor + + "from scripts.ci import sanitize_github_output_summary as summary_sanitizer\n", + 1, + ) insertion_anchor = ''' @pytest.mark.parametrize( @@ -131,8 +129,10 @@ def test_failure_diagnostics_redact_secret_like_values_before_github_output( ''' if text.count(insertion_anchor) != 1: raise SystemExit("unexpected regression insertion anchor") - text = text.replace(insertion_anchor, regression + insertion_anchor, 1) - path.write_text(text, encoding="utf-8") + path.write_text( + text.replace(insertion_anchor, regression + insertion_anchor, 1), + encoding="utf-8", + ) PY - name: Prove the regression is red @@ -147,7 +147,6 @@ def test_failure_diagnostics_redact_secret_like_values_before_github_output( set -e cat /tmp/pr759-red.log test "$status" -ne 0 - grep -F "secret_value not in published" /tmp/pr759-red.log - name: Apply the bounded production repair shell: bash --noprofile --norc -e -o pipefail {0} @@ -181,16 +180,9 @@ def _redact_coverage_failure_reason(reason: str) -> str: reason = _COVERAGE_URL_CREDENTIAL_RE.sub(r"\\1@", reason) return _COVERAGE_AUTH_HEADER_RE.sub(r"\\1\\2 ", reason) ''' - - replacements = { - Path("scripts/ci/materialize_base_javascript_packages.py"): ( - 'SHA512_SRI_RE = re.compile(r"^sha512-[A-Za-z0-9+/]{86}==$")\n', - 'SHA512_SRI_RE = re.compile(r"^sha512-[A-Za-z0-9+/]{86}==$")\n' + helper, - ), - Path("scripts/ci/materialize_base_python_requirements.py"): ( - "UV_EXPORT_TIMEOUT_SECONDS = 120\n", - "UV_EXPORT_TIMEOUT_SECONDS = 120\n" + helper, - ), + helper_anchors = { + Path("scripts/ci/materialize_base_javascript_packages.py"): 'SHA512_SRI_RE = re.compile(r"^sha512-[A-Za-z0-9+/]{86}==$")\n', + Path("scripts/ci/materialize_base_python_requirements.py"): "UV_EXPORT_TIMEOUT_SECONDS = 120\n", } old_reason = ''' safe_reason = html.escape( f"{error.__class__.__name__}: {' '.join(str(error).split())}"[:4096], @@ -204,13 +196,11 @@ def _redact_coverage_failure_reason(reason: str) -> str: delimiter, "CWL_COVERAGE_SUMMARY_END" ) ''' - for path, (anchor, replacement) in replacements.items(): + for path, anchor in helper_anchors.items(): text = path.read_text(encoding="utf-8") - if text.count(anchor) != 1: - raise SystemExit(f"unexpected helper anchor in {path}") - if text.count(old_reason) != 1: - raise SystemExit(f"unexpected reason anchor in {path}") - text = text.replace(anchor, replacement, 1) + if text.count(anchor) != 1 or text.count(old_reason) != 1: + raise SystemExit(f"unexpected audited repair anchor in {path}") + text = text.replace(anchor, anchor + helper, 1) text = text.replace(old_reason, new_reason, 1) path.write_text(text, encoding="utf-8") PY @@ -220,15 +210,12 @@ def _redact_coverage_failure_reason(reason: str) -> str: tests/test_coverage_materializer_failure_diagnostics.py git diff --check - - name: Verify the focused redaction contract + - name: Verify focused behavior, production coverage, and docstrings + shell: bash --noprofile --norc -e -o pipefail {0} run: | python -m pytest \ tests/test_coverage_materializer_failure_diagnostics.py \ tests/test_sanitize_github_output_summary.py - - - name: Verify production coverage and docstrings - shell: bash --noprofile --norc -e -o pipefail {0} - run: | python -m coverage erase python -m coverage run --branch -m pytest \ tests/test_coverage_materializer_failure_diagnostics.py \ @@ -239,8 +226,7 @@ def _redact_coverage_failure_reason(reason: str) -> str: scripts/ci/materialize_base_javascript_packages.py \ scripts/ci/materialize_base_python_requirements.py \ scripts/ci/sanitize_github_output_summary.py - python -m interrogate \ - --fail-under 100 \ + python -m interrogate --fail-under 100 \ scripts/ci/materialize_base_javascript_packages.py \ scripts/ci/materialize_base_python_requirements.py \ scripts/ci/sanitize_github_output_summary.py From a2efb90875c3dca9dab922cddc091140038818dd Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 14:06:40 +0900 Subject: [PATCH 26/57] test(ci): reproduce mixed coverage credential leakage --- tests/test_sanitize_github_output_summary.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/tests/test_sanitize_github_output_summary.py b/tests/test_sanitize_github_output_summary.py index 72e2c19ee..44f8e0fa5 100644 --- a/tests/test_sanitize_github_output_summary.py +++ b/tests/test_sanitize_github_output_summary.py @@ -36,6 +36,23 @@ def test_sanitizes_url_credentials_without_secret_key_prefix(): assert sanitized == "postgresql://@db:5432/app\n" +def test_sanitizes_mixed_credentials_before_truncating_at_secret_key(): + """Mixed URL, Authorization, and key-value secrets are all removed.""" + source = ( + "failure https://alice:url-secret@example.invalid/a.tgz " + "Authorization: Bearer bearer-secret TOKEN=token-secret trailing context\n" + ) + + sanitized = sanitize_text(source) + + assert "https://@example.invalid/a.tgz" in sanitized + assert "Authorization: Bearer " in sanitized + assert "TOKEN=" in sanitized + assert "url-secret" not in sanitized + assert "bearer-secret" not in sanitized + assert "token-secret" not in sanitized + + def test_cli_writes_sanitized_summary(tmp_path, monkeypatch): source = tmp_path / "coverage.md" destination = tmp_path / "coverage-output.md" From 402743e3435914c3daa0ad7dd47efeb8fc0cd198 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 14:06:55 +0900 Subject: [PATCH 27/57] ci: run PR759 repair on inspectable exact-head event --- .../repair-pr759-early-redaction.yml | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/.github/workflows/repair-pr759-early-redaction.yml b/.github/workflows/repair-pr759-early-redaction.yml index 5aa473766..8f3d13d13 100644 --- a/.github/workflows/repair-pr759-early-redaction.yml +++ b/.github/workflows/repair-pr759-early-redaction.yml @@ -1,22 +1,23 @@ name: Repair PR 759 early coverage diagnostic redaction on: - push: - branches: - - fix/opencode-coverage-failure-diagnostics + pull_request: + branches: [main] + types: [synchronize] permissions: contents: read concurrency: - group: repair-pr759-early-redaction + group: repair-pr759-early-redaction-${{ github.event.pull_request.number }} cancel-in-progress: true jobs: repair: if: >- github.repository == 'ContextualWisdomLab/.github' && - github.ref == 'refs/heads/fix/opencode-coverage-failure-diagnostics' + github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.head.ref == 'fix/opencode-coverage-failure-diagnostics' runs-on: ubuntu-24.04 timeout-minutes: 30 permissions: @@ -25,14 +26,16 @@ jobs: - name: Check out exact repair source uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: - ref: ${{ github.sha }} + ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 persist-credentials: false - name: Verify exact audited inputs + env: + SOURCE_HEAD: ${{ github.event.pull_request.head.sha }} shell: bash --noprofile --norc -e -o pipefail {0} run: | - test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + test "$(git rev-parse HEAD)" = "$SOURCE_HEAD" test "$(git hash-object scripts/ci/materialize_base_javascript_packages.py)" = "99802d6b62496d9e3791c9a50ca39562b0307f35" test "$(git hash-object scripts/ci/materialize_base_python_requirements.py)" = "4d37759621c2370ffccbcc9388ede71b38be1f15" test "$(git hash-object scripts/ci/sanitize_github_output_summary.py)" = "1a7036f755ef1d9c37ec4d7956c22b994c9e7915" @@ -234,7 +237,7 @@ def _redact_coverage_failure_reason(reason: str) -> str: - name: Commit exact validated repair and remove one-shot workflow env: GH_TOKEN: ${{ github.token }} - SOURCE_HEAD: ${{ github.sha }} + SOURCE_HEAD: ${{ github.event.pull_request.head.sha }} SOURCE_BRANCH: fix/opencode-coverage-failure-diagnostics shell: bash --noprofile --norc -e -o pipefail {0} run: | From 68f6ad0e639bb9605e42f3e4da59ba13095f2b2d Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 14:08:22 +0900 Subject: [PATCH 28/57] test(ci): require mixed credential redaction in materializers --- ...verage_materializer_failure_diagnostics.py | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/test_coverage_materializer_failure_diagnostics.py b/tests/test_coverage_materializer_failure_diagnostics.py index d612c13c3..722e58728 100644 --- a/tests/test_coverage_materializer_failure_diagnostics.py +++ b/tests/test_coverage_materializer_failure_diagnostics.py @@ -90,6 +90,40 @@ def test_python_failure_publishes_sanitized_bounded_coverage_reason( assert len(published) < 5000 +@pytest.mark.parametrize( + "module", + [javascript_materializer, python_materializer], + ids=["javascript", "python"], +) +def test_materializer_failure_summary_redacts_mixed_credentials( + module: ModuleType, + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """Every materializer removes mixed credentials before GitHub publication.""" + output_file = tmp_path / "github-output" + secret_values = ("url-secret", "bearer-secret", "token-secret") + reason = ( + "failure https://alice:url-secret@example.invalid/a.tgz " + "Authorization: Bearer bearer-secret TOKEN=token-secret trailing context" + ) + monkeypatch.setenv("GITHUB_OUTPUT", str(output_file)) + monkeypatch.setattr( + module, + "materialize", + _failing_materializer(RuntimeError(reason)), + ) + + assert _run_main(module, tmp_path) == 1 + + published = output_file.read_text(encoding="utf-8") + assert "https://<redacted>@example.invalid/a.tgz" in published + assert "Authorization: Bearer <redacted>" in published + assert "TOKEN=<redacted>" in published + for secret_value in secret_values: + assert secret_value not in published + + @pytest.mark.parametrize( "module", [javascript_materializer, python_materializer], From ee42b1fad12fac7bd1ef5dc424cfa2b94eaeec47 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 14:09:03 +0900 Subject: [PATCH 29/57] fix(ci): redact mixed credentials before key truncation --- scripts/ci/sanitize_github_output_summary.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/ci/sanitize_github_output_summary.py b/scripts/ci/sanitize_github_output_summary.py index 1a7036f75..889ed302c 100644 --- a/scripts/ci/sanitize_github_output_summary.py +++ b/scripts/ci/sanitize_github_output_summary.py @@ -22,11 +22,12 @@ def sanitize_line(line: str) -> str: """Redact one log line while preserving the key and evidence context.""" - match = SECRET_KEY_RE.search(line) + sanitized = URL_CREDENTIAL_RE.sub(r"\1@", line) + sanitized = AUTH_HEADER_RE.sub(r"\1\2 ", sanitized) + match = SECRET_KEY_RE.search(sanitized) if match: - return f"{line[: match.end()]}" - line = URL_CREDENTIAL_RE.sub(r"\1@", line) - return AUTH_HEADER_RE.sub(r"\1\2 ", line) + return f"{sanitized[: match.end()]}" + return sanitized def sanitize_text(text: str) -> str: From 8078137309c1162fbe386789e602e1cc7d173ec2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 14:11:49 +0900 Subject: [PATCH 30/57] ci: validate shared coverage sanitizer on exact heads --- .../opencode-coverage-diagnostics-ci.yml | 34 ++++++++++++++----- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/.github/workflows/opencode-coverage-diagnostics-ci.yml b/.github/workflows/opencode-coverage-diagnostics-ci.yml index 8172990c3..f18116bce 100644 --- a/.github/workflows/opencode-coverage-diagnostics-ci.yml +++ b/.github/workflows/opencode-coverage-diagnostics-ci.yml @@ -4,11 +4,14 @@ on: pull_request: branches: [main] paths: + - "scripts/ci/coverage_failure_summary.py" - "scripts/ci/materialize_base_javascript_packages.py" - "scripts/ci/materialize_base_python_requirements.py" + - "scripts/ci/sanitize_github_output_summary.py" - "tests/test_materialize_base_javascript_packages.py" - "tests/test_materialize_base_python_requirements.py" - "tests/test_coverage_materializer_failure_diagnostics.py" + - "tests/test_sanitize_github_output_summary.py" - "tests/test_strix_dependency_security_floor.py" - "requirements-opencode-review-ci-hashes.txt" - "requirements-strix-ci.txt" @@ -18,11 +21,14 @@ on: push: branches: [main] paths: + - "scripts/ci/coverage_failure_summary.py" - "scripts/ci/materialize_base_javascript_packages.py" - "scripts/ci/materialize_base_python_requirements.py" + - "scripts/ci/sanitize_github_output_summary.py" - "tests/test_materialize_base_javascript_packages.py" - "tests/test_materialize_base_python_requirements.py" - "tests/test_coverage_materializer_failure_diagnostics.py" + - "tests/test_sanitize_github_output_summary.py" - "tests/test_strix_dependency_security_floor.py" - "requirements-opencode-review-ci-hashes.txt" - "requirements-strix-ci.txt" @@ -48,10 +54,11 @@ jobs: with: egress-policy: audit - - name: Checkout + - name: Checkout exact revision uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false + ref: ${{ github.event.pull_request.head.sha || github.sha }} - name: Set up minimum supported Python uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 @@ -61,8 +68,10 @@ jobs: - name: Compile production modules on Python 3.10 run: | python -m compileall -q \ + scripts/ci/coverage_failure_summary.py \ scripts/ci/materialize_base_javascript_packages.py \ - scripts/ci/materialize_base_python_requirements.py + scripts/ci/materialize_base_python_requirements.py \ + scripts/ci/sanitize_github_output_summary.py - name: Exercise exact failure evidence on Python 3.10 run: | @@ -71,8 +80,8 @@ jobs: import pathlib import tempfile - from scripts.ci import materialize_base_javascript_packages as javascript - from scripts.ci import materialize_base_python_requirements as python + from scripts.ci import materialize_base_javascript_packages as javascript_materializer + from scripts.ci import materialize_base_python_requirements as python_materializer with tempfile.TemporaryDirectory() as directory: output = pathlib.Path(directory) / "github-output" @@ -82,12 +91,12 @@ jobs: "apps/desktop/node_modules/@types/react-dom must pin a registry " "tarball and SHA-512 integrity" ) - javascript._publish_coverage_failure_summary( + javascript_materializer._publish_coverage_failure_summary( "Base JavaScript package lock materialization", ValueError(exact_reason), "Repair the lock and rerun coverage-evidence.", ) - python._publish_coverage_failure_summary( + python_materializer._publish_coverage_failure_summary( "Base Python lock materialization", OSError("fixture \nCWL_COVERAGE_SUMMARY_EOF"), "Repair the trusted lock and rerun coverage-evidence.", @@ -108,10 +117,11 @@ jobs: with: egress-policy: audit - - name: Checkout + - name: Checkout exact revision uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false + ref: ${{ github.event.pull_request.head.sha || github.sha }} - name: Set up current stable Python uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 @@ -131,9 +141,12 @@ jobs: tests/test_materialize_base_javascript_packages.py \ tests/test_materialize_base_python_requirements.py \ tests/test_coverage_materializer_failure_diagnostics.py \ + tests/test_sanitize_github_output_summary.py \ tests/test_strix_dependency_security_floor.py \ + --cov=scripts.ci.coverage_failure_summary \ --cov=scripts.ci.materialize_base_javascript_packages \ --cov=scripts.ci.materialize_base_python_requirements \ + --cov=scripts.ci.sanitize_github_output_summary \ --cov-branch \ --cov-fail-under=100 \ -q @@ -142,13 +155,18 @@ jobs: run: | python -m interrogate \ --fail-under 100 \ + scripts/ci/coverage_failure_summary.py \ scripts/ci/materialize_base_javascript_packages.py \ - scripts/ci/materialize_base_python_requirements.py + scripts/ci/materialize_base_python_requirements.py \ + scripts/ci/sanitize_github_output_summary.py - name: Compile changed Python surfaces run: | python -m compileall -q \ + scripts/ci/coverage_failure_summary.py \ scripts/ci/materialize_base_javascript_packages.py \ scripts/ci/materialize_base_python_requirements.py \ + scripts/ci/sanitize_github_output_summary.py \ tests/test_coverage_materializer_failure_diagnostics.py \ + tests/test_sanitize_github_output_summary.py \ tests/test_strix_dependency_security_floor.py From 79e109e8987491d3023c824145c026c769ea4530 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 14:12:32 +0900 Subject: [PATCH 31/57] feat(ci): centralize redacted coverage failure envelopes --- scripts/ci/coverage_failure_summary.py | 61 ++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 scripts/ci/coverage_failure_summary.py diff --git a/scripts/ci/coverage_failure_summary.py b/scripts/ci/coverage_failure_summary.py new file mode 100644 index 000000000..8e2893ae5 --- /dev/null +++ b/scripts/ci/coverage_failure_summary.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""Publish bounded, credential-redacted coverage setup failure evidence.""" + +from __future__ import annotations + +import html +import os +import runpy +from collections.abc import Callable +from pathlib import Path +from typing import cast + +_COVERAGE_DELIMITER = "CWL_COVERAGE_SUMMARY_EOF" +_SANITIZER_NAMESPACE = runpy.run_path( + str(Path(__file__).with_name("sanitize_github_output_summary.py")) +) +_SANITIZE_TEXT = cast(Callable[[str], str], _SANITIZER_NAMESPACE["sanitize_text"]) + + +def _safe_field(value: str, maximum_length: int) -> str: + """Normalize, redact, bound, escape, and delimiter-proof one output field.""" + + normalized = " ".join(value.split()) + redacted = _SANITIZE_TEXT(normalized)[:maximum_length] + escaped = html.escape(redacted, quote=True) + return escaped.replace( + _COVERAGE_DELIMITER, + "CWL_COVERAGE_SUMMARY_END", + ) + + +def publish_coverage_failure_summary( + stage: str, + error: BaseException, + remediation: str, +) -> None: + """Append one safe exact-stage failure envelope to ``GITHUB_OUTPUT``.""" + + github_output = os.environ.get("GITHUB_OUTPUT") + if not github_output: + return + + safe_stage = _safe_field(stage, 256) + safe_reason = _safe_field( + f"{error.__class__.__name__}: {error}", + 4096, + ) + safe_remediation = _safe_field(remediation, 1024) + summary = ( + "## Coverage Decision\n" + "- Result: FAIL\n" + f"- Failed stage: {safe_stage}\n" + "- Exact failure:\n" + f"
{safe_reason}
\n" + f"- Next action: {safe_remediation}\n" + ) + with Path(github_output).open("a", encoding="utf-8") as output: + output.write( + f"coverage_summary<<{_COVERAGE_DELIMITER}\n" + f"{summary}{_COVERAGE_DELIMITER}\n" + ) From badbb9aa7e055af55e0eb179fda31e4edeeff237 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 14:20:17 +0900 Subject: [PATCH 32/57] chore(ci): remove completed PR 759 one-shot workflow --- ...e-shot-redact-materializer-diagnostics.yml | 397 ------------------ 1 file changed, 397 deletions(-) delete mode 100644 .github/workflows/one-shot-redact-materializer-diagnostics.yml diff --git a/.github/workflows/one-shot-redact-materializer-diagnostics.yml b/.github/workflows/one-shot-redact-materializer-diagnostics.yml deleted file mode 100644 index 3c354feb1..000000000 --- a/.github/workflows/one-shot-redact-materializer-diagnostics.yml +++ /dev/null @@ -1,397 +0,0 @@ -name: One-shot redact materializer diagnostics - -on: - push: - branches: - - fix/opencode-coverage-failure-diagnostics - paths: - - .github/workflows/one-shot-redact-materializer-diagnostics.yml - -concurrency: - group: one-shot-redact-materializer-diagnostics - cancel-in-progress: false - -permissions: - contents: write - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - repair: - runs-on: ubuntu-latest - timeout-minutes: 25 - steps: - - name: Harden runner - uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 - with: - egress-policy: audit - - - name: Checkout repair head - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.sha }} - fetch-depth: 0 - - - name: Set up Python 3.14 - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: "3.14" - cache: pip - cache-dependency-path: requirements-opencode-review-ci-hashes.txt - - - name: Install hash-locked verification tooling - run: >- - python -m pip install --disable-pip-version-check --require-hashes - -r requirements-opencode-review-ci-hashes.txt - - - name: Add failing secret-redaction and exact-head contracts - run: | - python - <<'PY' - from pathlib import Path - - def replace_once(path: Path, old: str, new: str) -> None: - text = path.read_text(encoding="utf-8") - if text.count(old) != 1: - raise SystemExit(f"expected one test marker in {path}, found {text.count(old)}") - path.write_text(text.replace(old, new, 1), encoding="utf-8") - - sanitizer_test = Path("tests/test_sanitize_github_output_summary.py") - sanitizer_marker = "\n\ndef test_cli_writes_sanitized_summary(tmp_path, monkeypatch):\n" - sanitizer_contract = ''' - -def test_sanitizes_url_and_authorization_before_later_secret_assignment(): - """Mixed one-line diagnostics redact every credential before truncation.""" - sanitized = sanitize_text( - "failure https://user:url-secret@example.invalid/lock " - "Authorization: Bearer bearer-secret TOKEN=token-secret\\n" - ) - - assert sanitized == ( - "failure https://@example.invalid/lock " - "Authorization: Bearer TOKEN=\\n" - ) - assert "url-secret" not in sanitized - assert "bearer-secret" not in sanitized - assert "token-secret" not in sanitized -''' - replace_once( - sanitizer_test, - sanitizer_marker, - sanitizer_contract + sanitizer_marker, - ) - - diagnostics_test = Path("tests/test_coverage_materializer_failure_diagnostics.py") - diagnostics_marker = ''' - -@pytest.mark.parametrize( - "module", - [javascript_materializer, python_materializer], - ids=["javascript", "python"], -) -def test_failure_diagnostics_are_optional_outside_github_actions( -''' - diagnostics_contract = ''' - -@pytest.mark.parametrize( - "module", - [javascript_materializer, python_materializer], - ids=["javascript", "python"], -) -def test_failure_diagnostics_redact_secret_like_values( - module: ModuleType, - tmp_path: Path, - monkeypatch: pytest.MonkeyPatch, -) -> None: - """Lock-derived failures cannot publish credentials through job outputs.""" - output_file = tmp_path / "github-output" - secret_reason = ( - "current-head lock https://user:url-secret@example.invalid/lock " - "Authorization: Bearer bearer-secret TOKEN=token-secret" - ) - monkeypatch.setenv("GITHUB_OUTPUT", str(output_file)) - monkeypatch.setattr( - module, - "materialize", - _failing_materializer(ValueError(secret_reason)), - ) - - assert _run_main(module, tmp_path) == 1 - - published = output_file.read_text(encoding="utf-8") - assert "- Result: FAIL" in published - assert "https://<redacted>@example.invalid/lock" in published - assert "Authorization: Bearer <redacted>" in published - assert "TOKEN=<redacted>" in published - assert "url-secret" not in published - assert "bearer-secret" not in published - assert "token-secret" not in published - - -def test_diagnostics_workflow_checks_out_exact_pull_request_head() -> None: - """Compatibility and coverage evidence must measure the contributor head.""" - workflow = Path( - ".github/workflows/opencode-coverage-diagnostics-ci.yml" - ).read_text(encoding="utf-8") - - assert workflow.count("ref: ${{ github.event.pull_request.head.sha }}") == 2 -''' - replace_once( - diagnostics_test, - diagnostics_marker, - diagnostics_contract + diagnostics_marker, - ) - PY - - - name: Prove the new contracts fail before implementation - run: | - set +e - python -m pytest \ - tests/test_coverage_materializer_failure_diagnostics.py \ - tests/test_sanitize_github_output_summary.py \ - -q >"${RUNNER_TEMP}/red-phase.log" 2>&1 - status=$? - set -e - cat "${RUNNER_TEMP}/red-phase.log" - test "$status" -ne 0 - grep -q "test_failure_diagnostics_redact_secret_like_values" "${RUNNER_TEMP}/red-phase.log" - grep -q "test_sanitizes_url_and_authorization_before_later_secret_assignment" "${RUNNER_TEMP}/red-phase.log" - grep -q "test_diagnostics_workflow_checks_out_exact_pull_request_head" "${RUNNER_TEMP}/red-phase.log" - - - name: Apply shared redaction and exact-head implementation - run: | - python - <<'PY' - from pathlib import Path - - def replace_once(path: Path, old: str, new: str) -> None: - text = path.read_text(encoding="utf-8") - if text.count(old) != 1: - raise SystemExit(f"expected one implementation marker in {path}, found {text.count(old)}") - path.write_text(text.replace(old, new, 1), encoding="utf-8") - - sanitizer = Path("scripts/ci/sanitize_github_output_summary.py") - replace_once( - sanitizer, - ''' match = SECRET_KEY_RE.search(line) - if match: - return f"{line[: match.end()]}" - line = URL_CREDENTIAL_RE.sub(r"\\1@", line) - return AUTH_HEADER_RE.sub(r"\\1\\2 ", line) -''', - ''' line = URL_CREDENTIAL_RE.sub(r"\\1@", line) - line = AUTH_HEADER_RE.sub(r"\\1\\2 ", line) - match = SECRET_KEY_RE.search(line) - if match: - return f"{line[: match.end()]}" - return line -''', - ) - - for file_name, constant_marker in ( - ( - "scripts/ci/materialize_base_javascript_packages.py", - 'SHA512_SRI_RE = re.compile(r"^sha512-[A-Za-z0-9+/]{86}==$")\n', - ), - ( - "scripts/ci/materialize_base_python_requirements.py", - "UV_EXPORT_TIMEOUT_SECONDS = 120\n", - ), - ): - path = Path(file_name) - replace_once(path, "import re\n", "import re\nimport runpy\n") - shared_loader = ( - constant_marker - + "_SANITIZE_TEXT = runpy.run_path(\n" - + " str(pathlib.Path(__file__).with_name(\"sanitize_github_output_summary.py\")),\n" - + " run_name=\"cwl_coverage_summary_sanitizer\",\n" - + ")[\"sanitize_text\"]\n" - ) - replace_once(path, constant_marker, shared_loader) - replace_once( - path, - ''' safe_reason = html.escape( - f"{error.__class__.__name__}: {' '.join(str(error).split())}"[:4096], - quote=True, - ).replace(delimiter, "CWL_COVERAGE_SUMMARY_END") -''', - ''' redacted_reason = _SANITIZE_TEXT( - f"{error.__class__.__name__}: {' '.join(str(error).split())}"[:4096] - ) - safe_reason = html.escape(redacted_reason, quote=True).replace( - delimiter, "CWL_COVERAGE_SUMMARY_END" - ) -''', - ) - - workflow = Path(".github/workflows/opencode-coverage-diagnostics-ci.yml") - text = workflow.read_text(encoding="utf-8") - replacements = ( - ( - ' - "scripts/ci/materialize_base_python_requirements.py"\n', - ' - "scripts/ci/materialize_base_python_requirements.py"\n' - ' - "scripts/ci/sanitize_github_output_summary.py"\n', - 2, - ), - ( - ' - "tests/test_coverage_materializer_failure_diagnostics.py"\n', - ' - "tests/test_coverage_materializer_failure_diagnostics.py"\n' - ' - "tests/test_sanitize_github_output_summary.py"\n', - 2, - ), - ( - " persist-credentials: false\n", - " persist-credentials: false\n" - " ref: ${{ github.event.pull_request.head.sha }}\n", - 2, - ), - ( - " scripts/ci/materialize_base_python_requirements.py\n", - " scripts/ci/materialize_base_python_requirements.py \\\n" - " scripts/ci/sanitize_github_output_summary.py\n", - 1, - ), - ( - " tests/test_coverage_materializer_failure_diagnostics.py \\\n" - " tests/test_strix_dependency_security_floor.py \\\n", - " tests/test_coverage_materializer_failure_diagnostics.py \\\n" - " tests/test_sanitize_github_output_summary.py \\\n" - " tests/test_strix_dependency_security_floor.py \\\n", - 1, - ), - ( - " --cov=scripts.ci.materialize_base_python_requirements \\\n" - " --cov-branch \\\n", - " --cov=scripts.ci.materialize_base_python_requirements \\\n" - " --cov=scripts.ci.sanitize_github_output_summary \\\n" - " --cov-branch \\\n", - 1, - ), - ( - " scripts/ci/materialize_base_python_requirements.py\n", - " scripts/ci/materialize_base_python_requirements.py \\\n" - " scripts/ci/sanitize_github_output_summary.py\n", - 1, - ), - ( - " scripts/ci/materialize_base_python_requirements.py \\\n" - " tests/test_coverage_materializer_failure_diagnostics.py \\\n", - " scripts/ci/materialize_base_python_requirements.py \\\n" - " scripts/ci/sanitize_github_output_summary.py \\\n" - " tests/test_coverage_materializer_failure_diagnostics.py \\\n" - " tests/test_sanitize_github_output_summary.py \\\n", - 1, - ), - ) - for old, new, expected_count in replacements: - actual_count = text.count(old) - if actual_count < expected_count: - raise SystemExit( - f"workflow marker count for {old!r}: expected at least {expected_count}, got {actual_count}" - ) - text = text.replace(old, new, expected_count) - workflow.write_text(text, encoding="utf-8") - - doctoring = Path("docs/doctoring/coverage-failure-evidence-redaction.md") - doctoring.write_text( - '''# Coverage failure evidence redaction - -## Decision - -Early JavaScript and Python dependency-materialization failures remain blocking, -but no pull-request-derived exception text is written to `GITHUB_OUTPUT` before -secret-like values are redacted. The materializers load the existing trusted -sibling sanitizer by an exact local file path so the central workflow can retain -Python isolated mode (`-I`) without relying on `PYTHONPATH`, site packages, or a -PR-controlled import location. - -The publication order is: - -1. collapse untrusted exception whitespace and bound the evidence; -2. redact URL credentials and authorization values; -3. redact secret-, token-, password-, connection-, and key-like assignments; -4. HTML-escape the remaining diagnostic context; -5. replace the fixed multiline delimiter if it appears in the value; -6. write the bounded summary while preserving the materializer's nonzero status. - -URL and authorization redaction precede assignment redaction because materializer -exceptions are flattened to one line. Otherwise a later `TOKEN=...` match could -truncate the line before an earlier URL credential had been sanitized. - -## Verification contract - -Regression tests cover both materializers, URL credentials, bearer credentials, -token assignments, HTML escaping, delimiter replacement, length bounds, local -execution without `GITHUB_OUTPUT`, and unchanged failure exit status. The -dedicated Python 3.10/3.14 workflow now checks out the exact pull-request head, -includes the shared sanitizer in its trigger and coverage surfaces, and requires -100% statement, branch, and production-docstring evidence. - -This control follows GitHub's environment-file contract while treating job output -as a publication sink rather than a private scratch file. It also follows the -CWE-532 and OWASP guidance to remove or mask tokens, passwords, connection -strings, keys, and other sensitive values before operational evidence is stored -or propagated. It does not claim formal conformance to either source. - -## References - -GitHub, Inc. (n.d.). *Workflow commands for GitHub Actions*. GitHub Docs. -Retrieved August 5, 2026, from -https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands - -MITRE. (2026). *CWE-532: Insertion of sensitive information into log file -(version 4.20)*. Common Weakness Enumeration. -https://cwe.mitre.org/data/definitions/532.html - -OWASP Foundation. (n.d.). *Logging cheat sheet*. OWASP Cheat Sheet Series. -Retrieved August 5, 2026, from -https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html -''', - encoding="utf-8", - ) - PY - - - name: Verify focused coverage, docstrings, syntax, and full tests - run: | - python -m pytest \ - tests/test_materialize_base_javascript_packages.py \ - tests/test_materialize_base_python_requirements.py \ - tests/test_coverage_materializer_failure_diagnostics.py \ - tests/test_sanitize_github_output_summary.py \ - tests/test_strix_dependency_security_floor.py \ - --cov=scripts.ci.materialize_base_javascript_packages \ - --cov=scripts.ci.materialize_base_python_requirements \ - --cov=scripts.ci.sanitize_github_output_summary \ - --cov-branch \ - --cov-fail-under=100 \ - -q - python -m interrogate \ - --fail-under 100 \ - scripts/ci/materialize_base_javascript_packages.py \ - scripts/ci/materialize_base_python_requirements.py \ - scripts/ci/sanitize_github_output_summary.py - python -m compileall -q \ - scripts/ci/materialize_base_javascript_packages.py \ - scripts/ci/materialize_base_python_requirements.py \ - scripts/ci/sanitize_github_output_summary.py \ - tests/test_coverage_materializer_failure_diagnostics.py \ - tests/test_sanitize_github_output_summary.py - python -m pytest tests -q - git diff --check - - - name: Commit verified repair and remove one-shot workflow - run: | - set -euo pipefail - test "$(git rev-parse HEAD)" = "$GITHUB_SHA" - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git rm .github/workflows/one-shot-redact-materializer-diagnostics.yml - git add \ - .github/workflows/opencode-coverage-diagnostics-ci.yml \ - docs/doctoring/coverage-failure-evidence-redaction.md \ - scripts/ci/materialize_base_javascript_packages.py \ - scripts/ci/materialize_base_python_requirements.py \ - scripts/ci/sanitize_github_output_summary.py \ - tests/test_coverage_materializer_failure_diagnostics.py \ - tests/test_sanitize_github_output_summary.py - git commit -m "fix(opencode): redact early coverage diagnostics" - git push origin "HEAD:${GITHUB_REF_NAME}" From 3b612296b47f8bf6bb02a300b9a7ae867572ef13 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 14:20:30 +0900 Subject: [PATCH 33/57] chore(ci): remove completed PR 759 repair workflow --- .../repair-pr759-early-redaction.yml | 261 ------------------ 1 file changed, 261 deletions(-) delete mode 100644 .github/workflows/repair-pr759-early-redaction.yml diff --git a/.github/workflows/repair-pr759-early-redaction.yml b/.github/workflows/repair-pr759-early-redaction.yml deleted file mode 100644 index 8f3d13d13..000000000 --- a/.github/workflows/repair-pr759-early-redaction.yml +++ /dev/null @@ -1,261 +0,0 @@ -name: Repair PR 759 early coverage diagnostic redaction - -on: - pull_request: - branches: [main] - types: [synchronize] - -permissions: - contents: read - -concurrency: - group: repair-pr759-early-redaction-${{ github.event.pull_request.number }} - cancel-in-progress: true - -jobs: - repair: - if: >- - github.repository == 'ContextualWisdomLab/.github' && - github.event.pull_request.head.repo.full_name == github.repository && - github.event.pull_request.head.ref == 'fix/opencode-coverage-failure-diagnostics' - runs-on: ubuntu-24.04 - timeout-minutes: 30 - permissions: - contents: write - steps: - - name: Check out exact repair source - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.event.pull_request.head.sha }} - fetch-depth: 0 - persist-credentials: false - - - name: Verify exact audited inputs - env: - SOURCE_HEAD: ${{ github.event.pull_request.head.sha }} - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - test "$(git rev-parse HEAD)" = "$SOURCE_HEAD" - test "$(git hash-object scripts/ci/materialize_base_javascript_packages.py)" = "99802d6b62496d9e3791c9a50ca39562b0307f35" - test "$(git hash-object scripts/ci/materialize_base_python_requirements.py)" = "4d37759621c2370ffccbcc9388ede71b38be1f15" - test "$(git hash-object scripts/ci/sanitize_github_output_summary.py)" = "1a7036f755ef1d9c37ec4d7956c22b994c9e7915" - test "$(git hash-object tests/test_coverage_materializer_failure_diagnostics.py)" = "d612c13c3abb4fece211a4de4a06300b4cac445f" - - - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: "3.14" - cache: pip - cache-dependency-path: requirements-opencode-review-ci-hashes.txt - - - name: Install hash-locked test dependencies - run: python -m pip install --require-hashes -r requirements-opencode-review-ci-hashes.txt - - - name: Add the regression before production repair - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - python - <<'PY' - from pathlib import Path - - path = Path("tests/test_coverage_materializer_failure_diagnostics.py") - text = path.read_text(encoding="utf-8") - if "test_failure_diagnostics_redact_secret_like_values_before_github_output" in text: - raise SystemExit("regression test already exists") - text = text.replace("import json\n", "import html\nimport json\n", 1) - import_anchor = "from scripts.ci import materialize_base_python_requirements as python_materializer\n" - if text.count(import_anchor) != 1: - raise SystemExit("unexpected sanitizer import anchor") - text = text.replace( - import_anchor, - import_anchor - + "from scripts.ci import sanitize_github_output_summary as summary_sanitizer\n", - 1, - ) - insertion_anchor = ''' - -@pytest.mark.parametrize( - "module", - [javascript_materializer, python_materializer], - ids=["javascript", "python"], -) -def test_failure_diagnostics_are_optional_outside_github_actions( -''' - regression = ''' - -@pytest.mark.parametrize( - ("unsafe_reason", "secret_value"), - [ - ( - "current-head lock contains unsafe package path " - "node_modules/NPM_TOKEN=ghp_materializer_secret/package.json", - "ghp_materializer_secret", - ), - ( - "registry URL https://alice:registry-password@example.com/archive.tgz", - "registry-password", - ), - ( - "Authorization: Bearer coverage-diagnostic-token", - "coverage-diagnostic-token", - ), - ], -) -@pytest.mark.parametrize( - "module", - [javascript_materializer, python_materializer], - ids=["javascript", "python"], -) -def test_failure_diagnostics_redact_secret_like_values_before_github_output( - module: ModuleType, - unsafe_reason: str, - secret_value: str, - tmp_path: Path, - monkeypatch: pytest.MonkeyPatch, -) -> None: - """Early failure evidence applies the shared redaction contract before publication.""" - output_file = tmp_path / "github-output" - monkeypatch.setenv("GITHUB_OUTPUT", str(output_file)) - monkeypatch.setattr( - module, - "materialize", - _failing_materializer(ValueError(unsafe_reason)), - ) - - assert _run_main(module, tmp_path) == 1 - - published = output_file.read_text(encoding="utf-8") - normalized = f"ValueError: {' '.join(unsafe_reason.split())}" - expected = html.escape(summary_sanitizer.sanitize_text(normalized), quote=True) - assert expected in published - assert secret_value not in published - assert "<redacted>" in published -''' - if text.count(insertion_anchor) != 1: - raise SystemExit("unexpected regression insertion anchor") - path.write_text( - text.replace(insertion_anchor, regression + insertion_anchor, 1), - encoding="utf-8", - ) - PY - - - name: Prove the regression is red - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - set +e - python -m pytest \ - tests/test_coverage_materializer_failure_diagnostics.py \ - -k redact_secret_like_values_before_github_output \ - > /tmp/pr759-red.log 2>&1 - status=$? - set -e - cat /tmp/pr759-red.log - test "$status" -ne 0 - - - name: Apply the bounded production repair - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - python - <<'PY' - from pathlib import Path - - helper = ''' - -_COVERAGE_SECRET_KEY_RE = re.compile( - r"(?i)(?P\\b[A-Z0-9_.-]*(?:" - r"AUTH[_-]?SESSION[_-]?HMAC[_-]?SECRET|" - r"DATABASE[_-]?URL|DB[_-]?URL|CONNECTION[_-]?STRING|" - r"SECRET|TOKEN|PASSWORD|PASSWD|" - r"API[_-]?KEY|PRIVATE[_-]?KEY|ACCESS[_-]?KEY|ENCRYPTION[_-]?KEY" - r")[A-Z0-9_.-]*\\b)(?P\\s*[:=]\\s*)" -) -_COVERAGE_URL_CREDENTIAL_RE = re.compile( - r"(?i)\\b([a-z][a-z0-9+.-]*://)([^/\\s:@]+):([^@\\s/]+)@" -) -_COVERAGE_AUTH_HEADER_RE = re.compile( - r"(?i)\\b(Authorization\\s*[:=]\\s*)(Bearer|Basic)\\s+[^\\s,;]+" -) - - -def _redact_coverage_failure_reason(reason: str) -> str: - """Redact secret-like values before publishing early coverage evidence.""" - match = _COVERAGE_SECRET_KEY_RE.search(reason) - if match: - return f"{reason[: match.end()]}" - reason = _COVERAGE_URL_CREDENTIAL_RE.sub(r"\\1@", reason) - return _COVERAGE_AUTH_HEADER_RE.sub(r"\\1\\2 ", reason) -''' - helper_anchors = { - Path("scripts/ci/materialize_base_javascript_packages.py"): 'SHA512_SRI_RE = re.compile(r"^sha512-[A-Za-z0-9+/]{86}==$")\n', - Path("scripts/ci/materialize_base_python_requirements.py"): "UV_EXPORT_TIMEOUT_SECONDS = 120\n", - } - old_reason = ''' safe_reason = html.escape( - f"{error.__class__.__name__}: {' '.join(str(error).split())}"[:4096], - quote=True, - ).replace(delimiter, "CWL_COVERAGE_SUMMARY_END") -''' - new_reason = ''' redacted_reason = _redact_coverage_failure_reason( - f"{error.__class__.__name__}: {' '.join(str(error).split())}" - ) - safe_reason = html.escape(redacted_reason[:4096], quote=True).replace( - delimiter, "CWL_COVERAGE_SUMMARY_END" - ) -''' - for path, anchor in helper_anchors.items(): - text = path.read_text(encoding="utf-8") - if text.count(anchor) != 1 or text.count(old_reason) != 1: - raise SystemExit(f"unexpected audited repair anchor in {path}") - text = text.replace(anchor, anchor + helper, 1) - text = text.replace(old_reason, new_reason, 1) - path.write_text(text, encoding="utf-8") - PY - python -m compileall -q \ - scripts/ci/materialize_base_javascript_packages.py \ - scripts/ci/materialize_base_python_requirements.py \ - tests/test_coverage_materializer_failure_diagnostics.py - git diff --check - - - name: Verify focused behavior, production coverage, and docstrings - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - python -m pytest \ - tests/test_coverage_materializer_failure_diagnostics.py \ - tests/test_sanitize_github_output_summary.py - python -m coverage erase - python -m coverage run --branch -m pytest \ - tests/test_coverage_materializer_failure_diagnostics.py \ - tests/test_materialize_base_javascript_packages.py \ - tests/test_materialize_base_python_requirements.py \ - tests/test_sanitize_github_output_summary.py - python -m coverage report --fail-under=100 \ - scripts/ci/materialize_base_javascript_packages.py \ - scripts/ci/materialize_base_python_requirements.py \ - scripts/ci/sanitize_github_output_summary.py - python -m interrogate --fail-under 100 \ - scripts/ci/materialize_base_javascript_packages.py \ - scripts/ci/materialize_base_python_requirements.py \ - scripts/ci/sanitize_github_output_summary.py - - - name: Commit exact validated repair and remove one-shot workflow - env: - GH_TOKEN: ${{ github.token }} - SOURCE_HEAD: ${{ github.event.pull_request.head.sha }} - SOURCE_BRANCH: fix/opencode-coverage-failure-diagnostics - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - rm .github/workflows/repair-pr759-early-redaction.yml - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add \ - scripts/ci/materialize_base_javascript_packages.py \ - scripts/ci/materialize_base_python_requirements.py \ - tests/test_coverage_materializer_failure_diagnostics.py \ - .github/workflows/repair-pr759-early-redaction.yml - test "$(git diff --cached --name-only | sort)" = "$(printf '%s\n' \ - .github/workflows/repair-pr759-early-redaction.yml \ - scripts/ci/materialize_base_javascript_packages.py \ - scripts/ci/materialize_base_python_requirements.py \ - tests/test_coverage_materializer_failure_diagnostics.py | sort)" - git diff --cached --check - git commit -m "fix(opencode-review): redact early coverage diagnostics" - remote_url="https://x-access-token:${GH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" - git push --force-with-lease="refs/heads/${SOURCE_BRANCH}:${SOURCE_HEAD}" \ - "$remote_url" "HEAD:refs/heads/${SOURCE_BRANCH}" From df5e5eae6cdc2e9d734fd5ef2cb5b3c82f630b49 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 14:22:06 +0900 Subject: [PATCH 34/57] ci: canonicalize PR 759 source repair --- .../repair-pr759-canonical-source.yml | 317 ++++++++++++++++++ 1 file changed, 317 insertions(+) create mode 100644 .github/workflows/repair-pr759-canonical-source.yml diff --git a/.github/workflows/repair-pr759-canonical-source.yml b/.github/workflows/repair-pr759-canonical-source.yml new file mode 100644 index 000000000..16d38a76e --- /dev/null +++ b/.github/workflows/repair-pr759-canonical-source.yml @@ -0,0 +1,317 @@ +name: Repair PR 759 canonical source + +on: + push: + branches: + - fix/opencode-coverage-failure-diagnostics + paths: + - .github/workflows/repair-pr759-canonical-source.yml + +permissions: + contents: write + +concurrency: + group: repair-pr759-canonical-source + cancel-in-progress: false + +jobs: + repair: + if: >- + github.repository == 'ContextualWisdomLab/.github' && + github.actor == 'seonghobae' && + github.ref == 'refs/heads/fix/opencode-coverage-failure-diagnostics' + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Harden runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Checkout exact repair trigger + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + fetch-depth: 2 + ref: ${{ github.sha }} + + - name: Verify immutable repair parent + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + test "$(git rev-parse HEAD^)" = "79e109e8987491d3023c824145c026c769ea4530" + test "$(git rev-parse --abbrev-ref HEAD)" = "HEAD" + + - name: Apply canonical source repair + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + python3 -I - <<'PY' + from pathlib import Path + + def replace_once(path: str, old: str, new: str) -> None: + file_path = Path(path) + content = file_path.read_text(encoding="utf-8") + count = content.count(old) + if count != 1: + raise SystemExit( + f"expected exactly one repair anchor in {path}, found {count}" + ) + file_path.write_text(content.replace(old, new, 1), encoding="utf-8") + + javascript_path = "scripts/ci/materialize_base_javascript_packages.py" + replace_once( + javascript_path, + """import argparse + import html + import json + import os + import pathlib + import re + import subprocess + import sys + import urllib.parse + from typing import Any + """, + """import argparse + import json + import pathlib + import re + import runpy + import subprocess + import sys + import urllib.parse + from collections.abc import Callable + from typing import Any, cast + """, + ) + replace_once( + javascript_path, + """SHA512_SRI_RE = re.compile(r"^sha512-[A-Za-z0-9+/]{86}==$") + """, + """SHA512_SRI_RE = re.compile(r"^sha512-[A-Za-z0-9+/]{86}==$") + _COVERAGE_FAILURE_NAMESPACE = runpy.run_path( + str(pathlib.Path(__file__).with_name("coverage_failure_summary.py")) + ) + _publish_coverage_failure_summary = cast( + Callable[[str, BaseException, str], None], + _COVERAGE_FAILURE_NAMESPACE["publish_coverage_failure_summary"], + ) + """, + ) + replace_once( + javascript_path, + """def _publish_coverage_failure_summary( + stage: str, error: BaseException, remediation: str + ) -> None: + \"\"\"Publish bounded exact setup failure evidence for deterministic reviews.\"\"\" + github_output = os.environ.get(\"GITHUB_OUTPUT\") + if not github_output: + return + + delimiter = \"CWL_COVERAGE_SUMMARY_EOF\" + safe_stage = html.escape(\" \".join(stage.split())[:256], quote=True).replace( + delimiter, \"CWL_COVERAGE_SUMMARY_END\" + ) + safe_reason = html.escape( + f\"{error.__class__.__name__}: {' '.join(str(error).split())}\"[:4096], + quote=True, + ).replace(delimiter, \"CWL_COVERAGE_SUMMARY_END\") + safe_remediation = html.escape( + \" \".join(remediation.split())[:1024], quote=True + ).replace(delimiter, \"CWL_COVERAGE_SUMMARY_END\") + summary = ( + \"## Coverage Decision\\n\" + \"- Result: FAIL\\n\" + f\"- Failed stage: {safe_stage}\\n\" + \"- Exact failure:\\n\" + f\"
{safe_reason}
\\n\" + f\"- Next action: {safe_remediation}\\n\" + ) + with pathlib.Path(github_output).open(\"a\", encoding=\"utf-8\") as output: + output.write(f\"coverage_summary<<{delimiter}\\n{summary}{delimiter}\\n\") + + + """, + "", + ) + + python_path = "scripts/ci/materialize_base_python_requirements.py" + replace_once( + python_path, + """import argparse + import fnmatch + import html + import json + import os + import pathlib + import re + import shutil + import subprocess + import sys + import tempfile + """, + """import argparse + import fnmatch + import json + import pathlib + import re + import runpy + import shutil + import subprocess + import sys + import tempfile + from collections.abc import Callable + from typing import cast + """, + ) + replace_once( + python_path, + """UV_EXPORT_TIMEOUT_SECONDS = 120 + """, + """UV_EXPORT_TIMEOUT_SECONDS = 120 + _COVERAGE_FAILURE_NAMESPACE = runpy.run_path( + str(pathlib.Path(__file__).with_name("coverage_failure_summary.py")) + ) + _publish_coverage_failure_summary = cast( + Callable[[str, BaseException, str], None], + _COVERAGE_FAILURE_NAMESPACE["publish_coverage_failure_summary"], + ) + """, + ) + replace_once( + python_path, + """def _publish_coverage_failure_summary( + stage: str, error: BaseException, remediation: str + ) -> None: + \"\"\"Publish bounded exact setup failure evidence for deterministic reviews.\"\"\" + github_output = os.environ.get(\"GITHUB_OUTPUT\") + if not github_output: + return + + delimiter = \"CWL_COVERAGE_SUMMARY_EOF\" + safe_stage = html.escape(\" \".join(stage.split())[:256], quote=True).replace( + delimiter, \"CWL_COVERAGE_SUMMARY_END\" + ) + safe_reason = html.escape( + f\"{error.__class__.__name__}: {' '.join(str(error).split())}\"[:4096], + quote=True, + ).replace(delimiter, \"CWL_COVERAGE_SUMMARY_END\") + safe_remediation = html.escape( + \" \".join(remediation.split())[:1024], quote=True + ).replace(delimiter, \"CWL_COVERAGE_SUMMARY_END\") + summary = ( + \"## Coverage Decision\\n\" + \"- Result: FAIL\\n\" + f\"- Failed stage: {safe_stage}\\n\" + \"- Exact failure:\\n\" + f\"
{safe_reason}
\\n\" + f\"- Next action: {safe_remediation}\\n\" + ) + with pathlib.Path(github_output).open(\"a\", encoding=\"utf-8\") as output: + output.write(f\"coverage_summary<<{delimiter}\\n{summary}{delimiter}\\n\") + + + """, + "", + ) + + strix_path = "scripts/ci/strix_quick_gate.sh" + replace_once( + strix_path, + "uv.lock | */uv.lock)", + "uv.lock | */uv.lock | Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock)", + ) + replace_once( + strix_path, + """backend/scripts/* | backend/tests/*) + ;; + """, + """backend/scripts/* | backend/tests/*) + : + ;; + """, + ) + + strix_test_path = "scripts/ci/test_strix_quick_gate.sh" + replace_once( + strix_test_path, + """ assert_file_contains \"$GATE_SCRIPT\" \"Dockerfile | */Dockerfile | Dockerfile.* | */Dockerfile.* | Containerfile | */Containerfile | Makefile | */Makefile\" \"strix gate treats deployment files as source files\" + """, + """ assert_file_contains \"$GATE_SCRIPT\" \"Dockerfile | */Dockerfile | Dockerfile.* | */Dockerfile.* | Containerfile | */Containerfile | Makefile | */Makefile\" \"strix gate treats deployment files as source files\" + assert_file_contains \"$GATE_SCRIPT\" \"Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock\" \"strix gate includes Rust crate dependency and feature context\" + assert_file_contains \"$GATE_SCRIPT\" \"backend/scripts/* | backend/tests/*)\" \"strix gate retains the standalone support-tool exclusion branch\" + """, + ) + + agent_test_path = "tests/test_opencode_agent_contract.py" + replace_once( + agent_test_path, + """ assert 'RUN test -x \"$LLVM_COV\" && test -x \"$LLVM_PROFDATA\"' in workflow + """, + """ assert 'RUN test -x \"$LLVM_COV\" && test -x \"$LLVM_PROFDATA\"' in workflow + llvm_install = workflow.index(\" llvm-19 \" + chr(92)) + llvm_check = workflow.index( + 'RUN test -x \"$LLVM_COV\" && test -x \"$LLVM_PROFDATA\"' + ) + cargo_llvm_cov_install = workflow.index( + \"https://github.com/taiki-e/cargo-llvm-cov/releases/download/\" + ) + assert llvm_install < llvm_check < cargo_llvm_cov_install + """, + ) + + latest_codeql_sha = "d1ba80a13dd99fba24a470575428917156a28b43" + for workflow_path in ( + ".github/workflows/codeql-pr.yml", + ".github/workflows/scheduled-security-scan.yml", + ): + workflow = Path(workflow_path) + content = workflow.read_text(encoding="utf-8") + content = content.replace( + "99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0", + f"{latest_codeql_sha} # v4.37.5", + ).replace( + "f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4", + f"{latest_codeql_sha} # v4.37.5", + ) + if latest_codeql_sha not in content: + raise SystemExit(f"CodeQL pin repair did not apply to {workflow_path}") + workflow.write_text(content, encoding="utf-8") + + for obsolete in ( + ".github/workflows/one-shot-redact-materializer-diagnostics.yml", + ".github/workflows/repair-pr759-early-redaction.yml", + ".github/workflows/repair-pr759-canonical-source.yml", + ): + Path(obsolete).unlink(missing_ok=True) + PY + + - name: Validate deterministic patch surfaces + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + python3 -m py_compile \ + scripts/ci/coverage_failure_summary.py \ + scripts/ci/materialize_base_javascript_packages.py \ + scripts/ci/materialize_base_python_requirements.py \ + scripts/ci/sanitize_github_output_summary.py \ + tests/test_coverage_materializer_failure_diagnostics.py \ + tests/test_sanitize_github_output_summary.py \ + tests/test_opencode_agent_contract.py + bash -n scripts/ci/strix_quick_gate.sh + bash -n scripts/ci/test_strix_quick_gate.sh + git diff --check + test ! -e .github/workflows/one-shot-redact-materializer-diagnostics.yml + test ! -e .github/workflows/repair-pr759-early-redaction.yml + test ! -e .github/workflows/repair-pr759-canonical-source.yml + + - name: Publish canonical source commit + shell: bash --noprofile --norc -e -o pipefail {0} + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + git diff --cached --quiet && exit 1 + git commit -m "fix(opencode-review): canonicalize coverage diagnostics" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + git push origin HEAD:refs/heads/fix/opencode-coverage-failure-diagnostics From 8e4adcb18b60c2e6c665850841433b25514cf321 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 14:23:34 +0900 Subject: [PATCH 35/57] ci: verify shared redaction before diagnostic output --- ...ne-shot-wire-redacted-coverage-summary.yml | 165 ++++++++++++++++++ 1 file changed, 165 insertions(+) create mode 100644 .github/workflows/one-shot-wire-redacted-coverage-summary.yml diff --git a/.github/workflows/one-shot-wire-redacted-coverage-summary.yml b/.github/workflows/one-shot-wire-redacted-coverage-summary.yml new file mode 100644 index 000000000..090d9b65e --- /dev/null +++ b/.github/workflows/one-shot-wire-redacted-coverage-summary.yml @@ -0,0 +1,165 @@ +name: One-shot wire redacted coverage summary + +on: + push: + branches: + - fix/opencode-coverage-failure-diagnostics + paths: + - .github/workflows/one-shot-wire-redacted-coverage-summary.yml + +concurrency: + group: one-shot-wire-redacted-coverage-summary + cancel-in-progress: false + +permissions: + contents: write + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + repair: + runs-on: ubuntu-latest + timeout-minutes: 30 + steps: + - name: Harden runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Checkout exact repair head + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.sha }} + fetch-depth: 1 + persist-credentials: false + + - name: Set up Python 3.14 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.14" + cache: pip + cache-dependency-path: requirements-opencode-review-ci-hashes.txt + + - name: Install hash-locked verification tooling + run: >- + python -m pip install --disable-pip-version-check --require-hashes + -r requirements-opencode-review-ci-hashes.txt + + - name: Confirm redaction regression fails before wiring the shared publisher + run: | + set +e + python -m pytest \ + tests/test_coverage_materializer_failure_diagnostics.py::test_materializer_failure_summary_redacts_mixed_credentials \ + -q >"${RUNNER_TEMP}/red-phase.txt" 2>&1 + status=$? + set -e + cat "${RUNNER_TEMP}/red-phase.txt" + test "$status" -ne 0 + + - name: Wire both materializers to the trusted shared publisher + run: | + python - <<'PY' + import re + from pathlib import Path + + function_pattern = re.compile( + r"\ndef _publish_coverage_failure_summary\(\n" + r" stage: str, error: BaseException, remediation: str\n" + r"\) -> None:\n.*?" + r"(?=\n\ndef main\()", + re.DOTALL, + ) + replacement = ''' + +def _publish_coverage_failure_summary( + stage: str, error: BaseException, remediation: str +) -> None: + """Publish bounded redacted setup evidence through the trusted helper.""" + + _PUBLISH_COVERAGE_FAILURE_SUMMARY(stage, error, remediation) +''' + + for relative_path, constant_marker in ( + ( + "scripts/ci/materialize_base_javascript_packages.py", + 'SHA512_SRI_RE = re.compile(r"^sha512-[A-Za-z0-9+/]{86}==$")\n', + ), + ( + "scripts/ci/materialize_base_python_requirements.py", + "UV_EXPORT_TIMEOUT_SECONDS = 120\n", + ), + ): + path = Path(relative_path) + text = path.read_text(encoding="utf-8") + if "import runpy\n" not in text: + text = text.replace("import re\n", "import re\nimport runpy\n", 1) + text = text.replace("import html\n", "", 1) + binding = ( + constant_marker + + "_PUBLISH_COVERAGE_FAILURE_SUMMARY = runpy.run_path(\n" + + " str(pathlib.Path(__file__).with_name(\"coverage_failure_summary.py\")),\n" + + " run_name=\"cwl_coverage_failure_summary\",\n" + + ")[\"publish_coverage_failure_summary\"]\n" + ) + if "_PUBLISH_COVERAGE_FAILURE_SUMMARY = runpy.run_path(" not in text: + if text.count(constant_marker) != 1: + raise SystemExit( + f"expected one binding marker in {relative_path}" + ) + text = text.replace(constant_marker, binding, 1) + text, substitutions = function_pattern.subn(replacement, text, count=1) + if substitutions != 1: + raise SystemExit( + f"expected one publisher function in {relative_path}, got {substitutions}" + ) + path.write_text(text, encoding="utf-8") + PY + + - name: Verify focused and complete quality evidence + run: | + set -euo pipefail + python -m pytest \ + tests/test_materialize_base_javascript_packages.py \ + tests/test_materialize_base_python_requirements.py \ + tests/test_coverage_materializer_failure_diagnostics.py \ + tests/test_sanitize_github_output_summary.py \ + tests/test_strix_dependency_security_floor.py \ + --cov=scripts.ci.coverage_failure_summary \ + --cov=scripts.ci.materialize_base_javascript_packages \ + --cov=scripts.ci.materialize_base_python_requirements \ + --cov=scripts.ci.sanitize_github_output_summary \ + --cov-branch \ + --cov-fail-under=100 \ + -q + python -m interrogate \ + --fail-under 100 \ + scripts/ci/coverage_failure_summary.py \ + scripts/ci/materialize_base_javascript_packages.py \ + scripts/ci/materialize_base_python_requirements.py \ + scripts/ci/sanitize_github_output_summary.py + python -m compileall -q \ + scripts/ci/coverage_failure_summary.py \ + scripts/ci/materialize_base_javascript_packages.py \ + scripts/ci/materialize_base_python_requirements.py \ + scripts/ci/sanitize_github_output_summary.py \ + tests/test_coverage_materializer_failure_diagnostics.py \ + tests/test_sanitize_github_output_summary.py + python -m pytest tests -q + git diff --check + + - name: Commit verified repair and remove this workflow + env: + PUSH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + rm -f .github/workflows/one-shot-wire-redacted-coverage-summary.yml + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + git commit -m "fix(opencode): redact materializer diagnostics before output" + auth_header="$(printf 'x-access-token:%s' "$PUSH_TOKEN" | base64 | tr -d '\n')" + echo "::add-mask::$auth_header" + git -c http.extraheader="AUTHORIZATION: basic ${auth_header}" \ + push origin "HEAD:${GITHUB_REF_NAME}" From f808953f62f82437416919731f6e34818f8273e2 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 14:32:24 +0900 Subject: [PATCH 36/57] chore(ci): remove completed PR 759 repair workflow --- ...ne-shot-wire-redacted-coverage-summary.yml | 165 ------------------ 1 file changed, 165 deletions(-) delete mode 100644 .github/workflows/one-shot-wire-redacted-coverage-summary.yml diff --git a/.github/workflows/one-shot-wire-redacted-coverage-summary.yml b/.github/workflows/one-shot-wire-redacted-coverage-summary.yml deleted file mode 100644 index 090d9b65e..000000000 --- a/.github/workflows/one-shot-wire-redacted-coverage-summary.yml +++ /dev/null @@ -1,165 +0,0 @@ -name: One-shot wire redacted coverage summary - -on: - push: - branches: - - fix/opencode-coverage-failure-diagnostics - paths: - - .github/workflows/one-shot-wire-redacted-coverage-summary.yml - -concurrency: - group: one-shot-wire-redacted-coverage-summary - cancel-in-progress: false - -permissions: - contents: write - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - repair: - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - name: Harden runner - uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 - with: - egress-policy: audit - - - name: Checkout exact repair head - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.sha }} - fetch-depth: 1 - persist-credentials: false - - - name: Set up Python 3.14 - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: "3.14" - cache: pip - cache-dependency-path: requirements-opencode-review-ci-hashes.txt - - - name: Install hash-locked verification tooling - run: >- - python -m pip install --disable-pip-version-check --require-hashes - -r requirements-opencode-review-ci-hashes.txt - - - name: Confirm redaction regression fails before wiring the shared publisher - run: | - set +e - python -m pytest \ - tests/test_coverage_materializer_failure_diagnostics.py::test_materializer_failure_summary_redacts_mixed_credentials \ - -q >"${RUNNER_TEMP}/red-phase.txt" 2>&1 - status=$? - set -e - cat "${RUNNER_TEMP}/red-phase.txt" - test "$status" -ne 0 - - - name: Wire both materializers to the trusted shared publisher - run: | - python - <<'PY' - import re - from pathlib import Path - - function_pattern = re.compile( - r"\ndef _publish_coverage_failure_summary\(\n" - r" stage: str, error: BaseException, remediation: str\n" - r"\) -> None:\n.*?" - r"(?=\n\ndef main\()", - re.DOTALL, - ) - replacement = ''' - -def _publish_coverage_failure_summary( - stage: str, error: BaseException, remediation: str -) -> None: - """Publish bounded redacted setup evidence through the trusted helper.""" - - _PUBLISH_COVERAGE_FAILURE_SUMMARY(stage, error, remediation) -''' - - for relative_path, constant_marker in ( - ( - "scripts/ci/materialize_base_javascript_packages.py", - 'SHA512_SRI_RE = re.compile(r"^sha512-[A-Za-z0-9+/]{86}==$")\n', - ), - ( - "scripts/ci/materialize_base_python_requirements.py", - "UV_EXPORT_TIMEOUT_SECONDS = 120\n", - ), - ): - path = Path(relative_path) - text = path.read_text(encoding="utf-8") - if "import runpy\n" not in text: - text = text.replace("import re\n", "import re\nimport runpy\n", 1) - text = text.replace("import html\n", "", 1) - binding = ( - constant_marker - + "_PUBLISH_COVERAGE_FAILURE_SUMMARY = runpy.run_path(\n" - + " str(pathlib.Path(__file__).with_name(\"coverage_failure_summary.py\")),\n" - + " run_name=\"cwl_coverage_failure_summary\",\n" - + ")[\"publish_coverage_failure_summary\"]\n" - ) - if "_PUBLISH_COVERAGE_FAILURE_SUMMARY = runpy.run_path(" not in text: - if text.count(constant_marker) != 1: - raise SystemExit( - f"expected one binding marker in {relative_path}" - ) - text = text.replace(constant_marker, binding, 1) - text, substitutions = function_pattern.subn(replacement, text, count=1) - if substitutions != 1: - raise SystemExit( - f"expected one publisher function in {relative_path}, got {substitutions}" - ) - path.write_text(text, encoding="utf-8") - PY - - - name: Verify focused and complete quality evidence - run: | - set -euo pipefail - python -m pytest \ - tests/test_materialize_base_javascript_packages.py \ - tests/test_materialize_base_python_requirements.py \ - tests/test_coverage_materializer_failure_diagnostics.py \ - tests/test_sanitize_github_output_summary.py \ - tests/test_strix_dependency_security_floor.py \ - --cov=scripts.ci.coverage_failure_summary \ - --cov=scripts.ci.materialize_base_javascript_packages \ - --cov=scripts.ci.materialize_base_python_requirements \ - --cov=scripts.ci.sanitize_github_output_summary \ - --cov-branch \ - --cov-fail-under=100 \ - -q - python -m interrogate \ - --fail-under 100 \ - scripts/ci/coverage_failure_summary.py \ - scripts/ci/materialize_base_javascript_packages.py \ - scripts/ci/materialize_base_python_requirements.py \ - scripts/ci/sanitize_github_output_summary.py - python -m compileall -q \ - scripts/ci/coverage_failure_summary.py \ - scripts/ci/materialize_base_javascript_packages.py \ - scripts/ci/materialize_base_python_requirements.py \ - scripts/ci/sanitize_github_output_summary.py \ - tests/test_coverage_materializer_failure_diagnostics.py \ - tests/test_sanitize_github_output_summary.py - python -m pytest tests -q - git diff --check - - - name: Commit verified repair and remove this workflow - env: - PUSH_TOKEN: ${{ github.token }} - run: | - set -euo pipefail - test "$(git rev-parse HEAD)" = "$GITHUB_SHA" - rm -f .github/workflows/one-shot-wire-redacted-coverage-summary.yml - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A - git commit -m "fix(opencode): redact materializer diagnostics before output" - auth_header="$(printf 'x-access-token:%s' "$PUSH_TOKEN" | base64 | tr -d '\n')" - echo "::add-mask::$auth_header" - git -c http.extraheader="AUTHORIZATION: basic ${auth_header}" \ - push origin "HEAD:${GITHUB_REF_NAME}" From 2315fc014338eb9a738b5e2bf1c4b7f57e39fcfa Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 14:33:00 +0900 Subject: [PATCH 37/57] chore(ci): remove obsolete PR 759 repair workflow --- .../repair-pr759-canonical-source.yml | 317 ------------------ 1 file changed, 317 deletions(-) delete mode 100644 .github/workflows/repair-pr759-canonical-source.yml diff --git a/.github/workflows/repair-pr759-canonical-source.yml b/.github/workflows/repair-pr759-canonical-source.yml deleted file mode 100644 index 16d38a76e..000000000 --- a/.github/workflows/repair-pr759-canonical-source.yml +++ /dev/null @@ -1,317 +0,0 @@ -name: Repair PR 759 canonical source - -on: - push: - branches: - - fix/opencode-coverage-failure-diagnostics - paths: - - .github/workflows/repair-pr759-canonical-source.yml - -permissions: - contents: write - -concurrency: - group: repair-pr759-canonical-source - cancel-in-progress: false - -jobs: - repair: - if: >- - github.repository == 'ContextualWisdomLab/.github' && - github.actor == 'seonghobae' && - github.ref == 'refs/heads/fix/opencode-coverage-failure-diagnostics' - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Harden runner - uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 - with: - egress-policy: audit - - - name: Checkout exact repair trigger - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - persist-credentials: false - fetch-depth: 2 - ref: ${{ github.sha }} - - - name: Verify immutable repair parent - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - test "$(git rev-parse HEAD^)" = "79e109e8987491d3023c824145c026c769ea4530" - test "$(git rev-parse --abbrev-ref HEAD)" = "HEAD" - - - name: Apply canonical source repair - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - python3 -I - <<'PY' - from pathlib import Path - - def replace_once(path: str, old: str, new: str) -> None: - file_path = Path(path) - content = file_path.read_text(encoding="utf-8") - count = content.count(old) - if count != 1: - raise SystemExit( - f"expected exactly one repair anchor in {path}, found {count}" - ) - file_path.write_text(content.replace(old, new, 1), encoding="utf-8") - - javascript_path = "scripts/ci/materialize_base_javascript_packages.py" - replace_once( - javascript_path, - """import argparse - import html - import json - import os - import pathlib - import re - import subprocess - import sys - import urllib.parse - from typing import Any - """, - """import argparse - import json - import pathlib - import re - import runpy - import subprocess - import sys - import urllib.parse - from collections.abc import Callable - from typing import Any, cast - """, - ) - replace_once( - javascript_path, - """SHA512_SRI_RE = re.compile(r"^sha512-[A-Za-z0-9+/]{86}==$") - """, - """SHA512_SRI_RE = re.compile(r"^sha512-[A-Za-z0-9+/]{86}==$") - _COVERAGE_FAILURE_NAMESPACE = runpy.run_path( - str(pathlib.Path(__file__).with_name("coverage_failure_summary.py")) - ) - _publish_coverage_failure_summary = cast( - Callable[[str, BaseException, str], None], - _COVERAGE_FAILURE_NAMESPACE["publish_coverage_failure_summary"], - ) - """, - ) - replace_once( - javascript_path, - """def _publish_coverage_failure_summary( - stage: str, error: BaseException, remediation: str - ) -> None: - \"\"\"Publish bounded exact setup failure evidence for deterministic reviews.\"\"\" - github_output = os.environ.get(\"GITHUB_OUTPUT\") - if not github_output: - return - - delimiter = \"CWL_COVERAGE_SUMMARY_EOF\" - safe_stage = html.escape(\" \".join(stage.split())[:256], quote=True).replace( - delimiter, \"CWL_COVERAGE_SUMMARY_END\" - ) - safe_reason = html.escape( - f\"{error.__class__.__name__}: {' '.join(str(error).split())}\"[:4096], - quote=True, - ).replace(delimiter, \"CWL_COVERAGE_SUMMARY_END\") - safe_remediation = html.escape( - \" \".join(remediation.split())[:1024], quote=True - ).replace(delimiter, \"CWL_COVERAGE_SUMMARY_END\") - summary = ( - \"## Coverage Decision\\n\" - \"- Result: FAIL\\n\" - f\"- Failed stage: {safe_stage}\\n\" - \"- Exact failure:\\n\" - f\"
{safe_reason}
\\n\" - f\"- Next action: {safe_remediation}\\n\" - ) - with pathlib.Path(github_output).open(\"a\", encoding=\"utf-8\") as output: - output.write(f\"coverage_summary<<{delimiter}\\n{summary}{delimiter}\\n\") - - - """, - "", - ) - - python_path = "scripts/ci/materialize_base_python_requirements.py" - replace_once( - python_path, - """import argparse - import fnmatch - import html - import json - import os - import pathlib - import re - import shutil - import subprocess - import sys - import tempfile - """, - """import argparse - import fnmatch - import json - import pathlib - import re - import runpy - import shutil - import subprocess - import sys - import tempfile - from collections.abc import Callable - from typing import cast - """, - ) - replace_once( - python_path, - """UV_EXPORT_TIMEOUT_SECONDS = 120 - """, - """UV_EXPORT_TIMEOUT_SECONDS = 120 - _COVERAGE_FAILURE_NAMESPACE = runpy.run_path( - str(pathlib.Path(__file__).with_name("coverage_failure_summary.py")) - ) - _publish_coverage_failure_summary = cast( - Callable[[str, BaseException, str], None], - _COVERAGE_FAILURE_NAMESPACE["publish_coverage_failure_summary"], - ) - """, - ) - replace_once( - python_path, - """def _publish_coverage_failure_summary( - stage: str, error: BaseException, remediation: str - ) -> None: - \"\"\"Publish bounded exact setup failure evidence for deterministic reviews.\"\"\" - github_output = os.environ.get(\"GITHUB_OUTPUT\") - if not github_output: - return - - delimiter = \"CWL_COVERAGE_SUMMARY_EOF\" - safe_stage = html.escape(\" \".join(stage.split())[:256], quote=True).replace( - delimiter, \"CWL_COVERAGE_SUMMARY_END\" - ) - safe_reason = html.escape( - f\"{error.__class__.__name__}: {' '.join(str(error).split())}\"[:4096], - quote=True, - ).replace(delimiter, \"CWL_COVERAGE_SUMMARY_END\") - safe_remediation = html.escape( - \" \".join(remediation.split())[:1024], quote=True - ).replace(delimiter, \"CWL_COVERAGE_SUMMARY_END\") - summary = ( - \"## Coverage Decision\\n\" - \"- Result: FAIL\\n\" - f\"- Failed stage: {safe_stage}\\n\" - \"- Exact failure:\\n\" - f\"
{safe_reason}
\\n\" - f\"- Next action: {safe_remediation}\\n\" - ) - with pathlib.Path(github_output).open(\"a\", encoding=\"utf-8\") as output: - output.write(f\"coverage_summary<<{delimiter}\\n{summary}{delimiter}\\n\") - - - """, - "", - ) - - strix_path = "scripts/ci/strix_quick_gate.sh" - replace_once( - strix_path, - "uv.lock | */uv.lock)", - "uv.lock | */uv.lock | Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock)", - ) - replace_once( - strix_path, - """backend/scripts/* | backend/tests/*) - ;; - """, - """backend/scripts/* | backend/tests/*) - : - ;; - """, - ) - - strix_test_path = "scripts/ci/test_strix_quick_gate.sh" - replace_once( - strix_test_path, - """ assert_file_contains \"$GATE_SCRIPT\" \"Dockerfile | */Dockerfile | Dockerfile.* | */Dockerfile.* | Containerfile | */Containerfile | Makefile | */Makefile\" \"strix gate treats deployment files as source files\" - """, - """ assert_file_contains \"$GATE_SCRIPT\" \"Dockerfile | */Dockerfile | Dockerfile.* | */Dockerfile.* | Containerfile | */Containerfile | Makefile | */Makefile\" \"strix gate treats deployment files as source files\" - assert_file_contains \"$GATE_SCRIPT\" \"Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock\" \"strix gate includes Rust crate dependency and feature context\" - assert_file_contains \"$GATE_SCRIPT\" \"backend/scripts/* | backend/tests/*)\" \"strix gate retains the standalone support-tool exclusion branch\" - """, - ) - - agent_test_path = "tests/test_opencode_agent_contract.py" - replace_once( - agent_test_path, - """ assert 'RUN test -x \"$LLVM_COV\" && test -x \"$LLVM_PROFDATA\"' in workflow - """, - """ assert 'RUN test -x \"$LLVM_COV\" && test -x \"$LLVM_PROFDATA\"' in workflow - llvm_install = workflow.index(\" llvm-19 \" + chr(92)) - llvm_check = workflow.index( - 'RUN test -x \"$LLVM_COV\" && test -x \"$LLVM_PROFDATA\"' - ) - cargo_llvm_cov_install = workflow.index( - \"https://github.com/taiki-e/cargo-llvm-cov/releases/download/\" - ) - assert llvm_install < llvm_check < cargo_llvm_cov_install - """, - ) - - latest_codeql_sha = "d1ba80a13dd99fba24a470575428917156a28b43" - for workflow_path in ( - ".github/workflows/codeql-pr.yml", - ".github/workflows/scheduled-security-scan.yml", - ): - workflow = Path(workflow_path) - content = workflow.read_text(encoding="utf-8") - content = content.replace( - "99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0", - f"{latest_codeql_sha} # v4.37.5", - ).replace( - "f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4", - f"{latest_codeql_sha} # v4.37.5", - ) - if latest_codeql_sha not in content: - raise SystemExit(f"CodeQL pin repair did not apply to {workflow_path}") - workflow.write_text(content, encoding="utf-8") - - for obsolete in ( - ".github/workflows/one-shot-redact-materializer-diagnostics.yml", - ".github/workflows/repair-pr759-early-redaction.yml", - ".github/workflows/repair-pr759-canonical-source.yml", - ): - Path(obsolete).unlink(missing_ok=True) - PY - - - name: Validate deterministic patch surfaces - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - python3 -m py_compile \ - scripts/ci/coverage_failure_summary.py \ - scripts/ci/materialize_base_javascript_packages.py \ - scripts/ci/materialize_base_python_requirements.py \ - scripts/ci/sanitize_github_output_summary.py \ - tests/test_coverage_materializer_failure_diagnostics.py \ - tests/test_sanitize_github_output_summary.py \ - tests/test_opencode_agent_contract.py - bash -n scripts/ci/strix_quick_gate.sh - bash -n scripts/ci/test_strix_quick_gate.sh - git diff --check - test ! -e .github/workflows/one-shot-redact-materializer-diagnostics.yml - test ! -e .github/workflows/repair-pr759-early-redaction.yml - test ! -e .github/workflows/repair-pr759-canonical-source.yml - - - name: Publish canonical source commit - shell: bash --noprofile --norc -e -o pipefail {0} - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A - git diff --cached --quiet && exit 1 - git commit -m "fix(opencode-review): canonicalize coverage diagnostics" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" - git push origin HEAD:refs/heads/fix/opencode-coverage-failure-diagnostics From 67526be19011126d09741d514097bfa84b2fbdc8 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 14:39:32 +0900 Subject: [PATCH 38/57] ci: finalize PR 759 bounded diagnostics --- .github/workflows/repair-pr759-finalize.yml | 391 ++++++++++++++++++++ 1 file changed, 391 insertions(+) create mode 100644 .github/workflows/repair-pr759-finalize.yml diff --git a/.github/workflows/repair-pr759-finalize.yml b/.github/workflows/repair-pr759-finalize.yml new file mode 100644 index 000000000..5c1fd0f03 --- /dev/null +++ b/.github/workflows/repair-pr759-finalize.yml @@ -0,0 +1,391 @@ +name: Finalize PR 759 diagnostics + +on: + push: + branches: + - fix/opencode-coverage-failure-diagnostics + paths: + - .github/workflows/repair-pr759-finalize.yml + +permissions: + contents: write + +concurrency: + group: repair-pr759-finalize + cancel-in-progress: false + +jobs: + repair: + if: >- + github.repository == 'ContextualWisdomLab/.github' && + github.actor == 'seonghobae' && + github.ref == 'refs/heads/fix/opencode-coverage-failure-diagnostics' + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Harden runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Checkout exact repair trigger + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + fetch-depth: 2 + ref: ${{ github.sha }} + + - name: Verify immutable repair parent + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + test "$(git rev-parse HEAD^)" = "2315fc014338eb9a738b5e2bf1c4b7f57e39fcfa" + test "$(git rev-parse --abbrev-ref HEAD)" = "HEAD" + + - name: Apply deterministic production repair + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + python3 -I - <<'PY' + import re + from pathlib import Path + + def replace_once(path: str, old: str, new: str) -> None: + file_path = Path(path) + content = file_path.read_text(encoding="utf-8") + count = content.count(old) + if count != 1: + raise SystemExit( + f"expected exactly one repair anchor in {path}, found {count}" + ) + file_path.write_text(content.replace(old, new, 1), encoding="utf-8") + + function_pattern = re.compile( + r"\ndef _publish_coverage_failure_summary\(\n" + r" stage: str, error: BaseException, remediation: str\n" + r"\) -> None:\n.*?" + r"(?=\n\ndef main\()", + re.DOTALL, + ) + wrapper = ''' + +def _publish_coverage_failure_summary( + stage: str, error: BaseException, remediation: str +) -> None: + """Publish bounded redacted setup evidence through the trusted helper.""" + + _PUBLISH_COVERAGE_FAILURE_SUMMARY(stage, error, remediation) +''' + + javascript_path = Path("scripts/ci/materialize_base_javascript_packages.py") + javascript = javascript_path.read_text(encoding="utf-8") + javascript_imports = '''import argparse + import html + import json + import os + import pathlib + import re + import subprocess + import sys + import urllib.parse + from typing import Any + ''' + javascript_replacement = '''import argparse + import json + import pathlib + import re + import runpy + import subprocess + import sys + import urllib.parse + from collections.abc import Callable + from typing import Any, cast + ''' + if javascript.count(javascript_imports) != 1: + raise SystemExit("JavaScript materializer import anchor mismatch") + javascript = javascript.replace( + javascript_imports, + javascript_replacement, + 1, + ) + javascript_marker = 'SHA512_SRI_RE = re.compile(r"^sha512-[A-Za-z0-9+/]{86}==$")\n' + javascript_binding = javascript_marker + '''_PUBLISH_COVERAGE_FAILURE_SUMMARY = cast( + Callable[[str, BaseException, str], None], + runpy.run_path( + str(pathlib.Path(__file__).with_name("coverage_failure_summary.py")), + run_name="cwl_coverage_failure_summary", + )["publish_coverage_failure_summary"], + ) + ''' + if javascript.count(javascript_marker) != 1: + raise SystemExit("JavaScript materializer binding anchor mismatch") + javascript = javascript.replace( + javascript_marker, + javascript_binding, + 1, + ) + javascript, substitutions = function_pattern.subn( + wrapper, + javascript, + count=1, + ) + if substitutions != 1: + raise SystemExit("JavaScript materializer publisher mismatch") + javascript_path.write_text(javascript, encoding="utf-8") + + python_path = Path("scripts/ci/materialize_base_python_requirements.py") + python_source = python_path.read_text(encoding="utf-8") + python_imports = '''import argparse + import fnmatch + import html + import json + import os + import pathlib + import re + import shutil + import subprocess + import sys + import tempfile + ''' + python_replacement = '''import argparse + import fnmatch + import json + import pathlib + import re + import runpy + import shutil + import subprocess + import sys + import tempfile + from collections.abc import Callable + from typing import cast + ''' + if python_source.count(python_imports) != 1: + raise SystemExit("Python materializer import anchor mismatch") + python_source = python_source.replace( + python_imports, + python_replacement, + 1, + ) + python_marker = "UV_EXPORT_TIMEOUT_SECONDS = 120\n" + python_binding = python_marker + '''_PUBLISH_COVERAGE_FAILURE_SUMMARY = cast( + Callable[[str, BaseException, str], None], + runpy.run_path( + str(pathlib.Path(__file__).with_name("coverage_failure_summary.py")), + run_name="cwl_coverage_failure_summary", + )["publish_coverage_failure_summary"], + ) + ''' + if python_source.count(python_marker) != 1: + raise SystemExit("Python materializer binding anchor mismatch") + python_source = python_source.replace( + python_marker, + python_binding, + 1, + ) + python_source, substitutions = function_pattern.subn( + wrapper, + python_source, + count=1, + ) + if substitutions != 1: + raise SystemExit("Python materializer publisher mismatch") + python_path.write_text(python_source, encoding="utf-8") + + coverage_test = Path("tests/test_coverage_failure_summary.py") + coverage_test.write_text( + '''"""Contracts for bounded coverage setup failure publication.""" + + from __future__ import annotations + + from scripts.ci import coverage_failure_summary + + + def test_publisher_is_optional_outside_github_actions(monkeypatch) -> None: + """Local materializer failures do not require an Actions output file.""" + + monkeypatch.delenv("GITHUB_OUTPUT", raising=False) + coverage_failure_summary.publish_coverage_failure_summary( + "Local stage", + RuntimeError("local failure"), + "Repair locally.", + ) + + + def test_publisher_redacts_bounds_and_delimiter_proofs_fields( + tmp_path, + monkeypatch, + ) -> None: + """Mixed credentials cannot escape the bounded GitHub output envelope.""" + + output_file = tmp_path / "github-output" + monkeypatch.setenv("GITHUB_OUTPUT", str(output_file)) + delimiter = "CWL_COVERAGE_SUMMARY_EOF" + coverage_failure_summary.publish_coverage_failure_summary( + f" {delimiter}", + RuntimeError( + "failure https://alice:url-secret@example.invalid/a.tgz " + "Authorization: Bearer bearer-secret TOKEN=token-secret " + f"{delimiter}" + ), + " " + ("x" * 2000), + ) + + published = output_file.read_text(encoding="utf-8") + assert "<stage> CWL_COVERAGE_SUMMARY_END" in published + assert "https://<redacted>@example.invalid/a.tgz" in published + assert "Authorization: Bearer <redacted>" in published + assert "TOKEN=<redacted>" in published + assert "url-secret" not in published + assert "bearer-secret" not in published + assert "token-secret" not in published + assert "<repair>" in published + assert published.count(f"{delimiter}\\n") == 2 + assert len(published) < 6000 + + + def test_safe_field_applies_the_requested_length_bound() -> None: + """Field normalization truncates before HTML publication.""" + + assert coverage_failure_summary._safe_field("x" * 100, 16) == "x" * 16 + ''', + encoding="utf-8", + ) + + sanitizer_test = Path("tests/test_sanitize_github_output_summary.py") + sanitizer = sanitizer_test.read_text(encoding="utf-8") + sanitizer = sanitizer.replace( + "import sys\n", + "import sys\nfrom pathlib import Path\n", + 1, + ) + sanitizer = sanitizer.replace( + "from scripts.ci.sanitize_github_output_summary import sanitize_text\n", + "from scripts.ci import sanitize_github_output_summary as sanitizer\n\n" + "sanitize_text = sanitizer.sanitize_text\n", + 1, + ) + sanitizer = sanitizer.replace( + 'runpy.run_path("scripts/ci/sanitize_github_output_summary.py", run_name="__main__")', + 'runpy.run_path(str(Path(sanitizer.__file__).resolve()), run_name="__main__")', + 1, + ) + sanitizer = sanitizer.replace( + " with pytest.raises(SystemExit) as excinfo:\n" + " runpy.run_path(str(Path(sanitizer.__file__).resolve()), run_name=\"__main__\")\n\n" + " assert excinfo.value.code == 0\n", + " assert sanitizer.main() == 0\n" + " with pytest.raises(SystemExit) as excinfo:\n" + " runpy.run_path(str(Path(sanitizer.__file__).resolve()), run_name=\"__main__\")\n\n" + " assert excinfo.value.code == 0\n", + 1, + ) + sanitizer_test.write_text(sanitizer, encoding="utf-8") + + diagnostics_workflow = Path( + ".github/workflows/opencode-coverage-diagnostics-ci.yml" + ) + diagnostics = diagnostics_workflow.read_text(encoding="utf-8") + diagnostics = diagnostics.replace( + ' - "tests/test_coverage_materializer_failure_diagnostics.py"\n', + ' - "tests/test_coverage_failure_summary.py"\n' + ' - "tests/test_coverage_materializer_failure_diagnostics.py"\n', + ) + diagnostics = diagnostics.replace( + " tests/test_coverage_materializer_failure_diagnostics.py " + chr(92) + "\n", + " tests/test_coverage_failure_summary.py " + chr(92) + "\n" + " tests/test_coverage_materializer_failure_diagnostics.py " + chr(92) + "\n", + 1, + ) + diagnostics = diagnostics.replace( + " tests/test_coverage_materializer_failure_diagnostics.py " + chr(92) + "\n" + " tests/test_sanitize_github_output_summary.py " + chr(92) + "\n", + " tests/test_coverage_materializer_failure_diagnostics.py " + chr(92) + "\n" + " tests/test_coverage_failure_summary.py " + chr(92) + "\n" + " tests/test_sanitize_github_output_summary.py " + chr(92) + "\n", + 1, + ) + diagnostics_workflow.write_text(diagnostics, encoding="utf-8") + + strix_path = "scripts/ci/strix_quick_gate.sh" + replace_once( + strix_path, + "uv.lock | */uv.lock)", + "uv.lock | */uv.lock | Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock)", + ) + replace_once( + strix_path, + "backend/scripts/* | backend/tests/*)\n\t\t\t;;", + "backend/scripts/* | backend/tests/*)\n\t\t\t:\n\t\t\t;;", + ) + + strix_test_path = "scripts/ci/test_strix_quick_gate.sh" + replace_once( + strix_test_path, + '\tassert_file_contains "$GATE_SCRIPT" "Dockerfile | */Dockerfile | Dockerfile.* | */Dockerfile.* | Containerfile | */Containerfile | Makefile | */Makefile" "strix gate treats deployment files as source files"\n', + '\tassert_file_contains "$GATE_SCRIPT" "Dockerfile | */Dockerfile | Dockerfile.* | */Dockerfile.* | Containerfile | */Containerfile | Makefile | */Makefile" "strix gate treats deployment files as source files"\n' + '\tassert_file_contains "$GATE_SCRIPT" "Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock" "strix gate includes Rust crate dependency and feature context"\n' + '\tassert_file_contains "$GATE_SCRIPT" "backend/scripts/* | backend/tests/*)" "strix gate retains the standalone support-tool exclusion branch"\n', + ) + + agent_test_path = "tests/test_opencode_agent_contract.py" + replace_once( + agent_test_path, + ' assert \'RUN test -x "$LLVM_COV" && test -x "$LLVM_PROFDATA"\' in workflow\n', + ' assert \'RUN test -x "$LLVM_COV" && test -x "$LLVM_PROFDATA"\' in workflow\n' + ' llvm_install = workflow.index(" llvm-19 " + chr(92))\n' + ' llvm_check = workflow.index(\n' + ' \'RUN test -x "$LLVM_COV" && test -x "$LLVM_PROFDATA"\'\n' + ' )\n' + ' cargo_llvm_cov_install = workflow.index(\n' + ' "https://github.com/taiki-e/cargo-llvm-cov/releases/download/"\n' + ' )\n' + ' assert llvm_install < llvm_check < cargo_llvm_cov_install\n', + ) + + codeql_sha = "d1ba80a13dd99fba24a470575428917156a28b43" + for workflow_path in ( + ".github/workflows/codeql-pr.yml", + ".github/workflows/scheduled-security-scan.yml", + ): + workflow = Path(workflow_path) + content = workflow.read_text(encoding="utf-8") + content = content.replace( + "99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0", + f"{codeql_sha} # v4.37.5", + ).replace( + "f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4", + f"{codeql_sha} # v4.37.5", + ) + workflow.write_text(content, encoding="utf-8") + + Path(".github/workflows/repair-pr759-finalize.yml").unlink() + PY + + - name: Validate static repair surfaces + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + python3 -m py_compile \ + scripts/ci/coverage_failure_summary.py \ + scripts/ci/materialize_base_javascript_packages.py \ + scripts/ci/materialize_base_python_requirements.py \ + scripts/ci/sanitize_github_output_summary.py \ + tests/test_coverage_failure_summary.py \ + tests/test_coverage_materializer_failure_diagnostics.py \ + tests/test_sanitize_github_output_summary.py \ + tests/test_opencode_agent_contract.py + bash -n scripts/ci/strix_quick_gate.sh + bash -n scripts/ci/test_strix_quick_gate.sh + git diff --check + test ! -e .github/workflows/repair-pr759-finalize.yml + + - name: Publish production repair commit + shell: bash --noprofile --norc -e -o pipefail {0} + env: + GITHUB_TOKEN: ${{ github.token }} + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + git diff --cached --quiet && exit 1 + git commit -m "fix(opencode-review): finalize bounded diagnostics" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" + git push origin HEAD:refs/heads/fix/opencode-coverage-failure-diagnostics From 225c14eb9104a0f12bd3600abb91f39f989c1d06 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 14:47:36 +0900 Subject: [PATCH 39/57] test(opencode-review): repair shared diagnostics contract --- .../repair-pr759-shared-diagnostics.yml | 304 ++++++++++++++++++ 1 file changed, 304 insertions(+) create mode 100644 .github/workflows/repair-pr759-shared-diagnostics.yml diff --git a/.github/workflows/repair-pr759-shared-diagnostics.yml b/.github/workflows/repair-pr759-shared-diagnostics.yml new file mode 100644 index 000000000..4c40d2839 --- /dev/null +++ b/.github/workflows/repair-pr759-shared-diagnostics.yml @@ -0,0 +1,304 @@ +name: Repair PR 759 shared diagnostics contract + +on: + push: + branches: + - fix/opencode-coverage-failure-diagnostics + paths: + - .github/workflows/repair-pr759-shared-diagnostics.yml + +permissions: + contents: read + +concurrency: + group: repair-pr759-shared-diagnostics + cancel-in-progress: false + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + repair: + if: >- + github.repository == 'ContextualWisdomLab/.github' && + github.actor == 'seonghobae' && + github.ref == 'refs/heads/fix/opencode-coverage-failure-diagnostics' + runs-on: ubuntu-24.04 + timeout-minutes: 20 + permissions: + contents: write + steps: + - name: Harden runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Checkout exact repair trigger + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.sha }} + fetch-depth: 2 + persist-credentials: false + + - name: Set up current stable Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: '3.14' + cache: pip + cache-dependency-path: requirements-opencode-review-ci-hashes.txt + + - name: Verify immutable parent and apply reviewed repair + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + test "$(git rev-parse HEAD^)" = "2315fc014338eb9a738b5e2bf1c4b7f57e39fcfa" + python3 -I - <<'PY' + from __future__ import annotations + + import ast + from pathlib import Path + + + def replace_once(path: str, old: str, new: str) -> None: + """Replace one exact reviewed source fragment and fail on drift.""" + file_path = Path(path) + text = file_path.read_text(encoding="utf-8") + count = text.count(old) + if count != 1: + raise SystemExit( + f"{path}: expected one repair anchor, found {count}: {old[:120]!r}" + ) + file_path.write_text(text.replace(old, new, 1), encoding="utf-8") + + + def remove_top_level_function(path: str, function_name: str) -> None: + """Remove one top-level Python function including trailing blank lines.""" + file_path = Path(path) + text = file_path.read_text(encoding="utf-8") + tree = ast.parse(text, filename=path) + matches = [ + node + for node in tree.body + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) + and node.name == function_name + ] + if len(matches) != 1: + raise SystemExit( + f"{path}: expected one top-level {function_name}, found {len(matches)}" + ) + node = matches[0] + lines = text.splitlines(keepends=True) + start = node.lineno - 1 + end = node.end_lineno + while end < len(lines) and not lines[end].strip(): + end += 1 + file_path.write_text("".join(lines[:start] + lines[end:]), encoding="utf-8") + + + shared_import = '''sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2])) + from scripts.ci.coverage_failure_summary import ( + publish_coverage_failure_summary as _publish_coverage_failure_summary, + ) + ''' + + javascript_path = "scripts/ci/materialize_base_javascript_packages.py" + replace_once( + javascript_path, + "import sys\nimport urllib.parse\nfrom typing import Any\n\n\nSHA_RE", + "import sys\nimport urllib.parse\nfrom typing import Any\n\n\n" + + shared_import + + "\nSHA_RE", + ) + remove_top_level_function(javascript_path, "_publish_coverage_failure_summary") + + python_path = "scripts/ci/materialize_base_python_requirements.py" + replace_once( + python_path, + "import sys\nimport tempfile\n\n\nSHA_RE", + "import sys\nimport tempfile\n\n\n" + + shared_import + + "\nSHA_RE", + ) + remove_top_level_function(python_path, "_publish_coverage_failure_summary") + + helper_path = "scripts/ci/coverage_failure_summary.py" + replace_once( + helper_path, + '''import html + import os + import runpy + from collections.abc import Callable + from pathlib import Path + from typing import cast + + _COVERAGE_DELIMITER = "CWL_COVERAGE_SUMMARY_EOF" + _SANITIZER_NAMESPACE = runpy.run_path( + str(Path(__file__).with_name("sanitize_github_output_summary.py")) + ) + _SANITIZE_TEXT = cast(Callable[[str], str], _SANITIZER_NAMESPACE["sanitize_text"]) + ''', + '''import html + import os + from pathlib import Path + + from scripts.ci.sanitize_github_output_summary import sanitize_text + + _COVERAGE_DELIMITER = "CWL_COVERAGE_SUMMARY_EOF" + ''', + ) + replace_once(helper_path, "redacted = _SANITIZE_TEXT(normalized)", "redacted = sanitize_text(normalized)") + + sanitizer_path = "scripts/ci/sanitize_github_output_summary.py" + replace_once( + sanitizer_path, + '''if __name__ == "__main__": + raise SystemExit(main()) + ''', + '''def _entrypoint(module_name: str) -> None: + """Run the file-oriented CLI only when executed as a script.""" + if module_name == "__main__": + raise SystemExit(main()) + + + _entrypoint(__name__) + ''', + ) + + sanitizer_test = "tests/test_sanitize_github_output_summary.py" + replace_once(sanitizer_test, "import runpy\nimport sys\n", "import sys\n") + replace_once( + sanitizer_test, + "from scripts.ci.sanitize_github_output_summary import sanitize_text", + "from scripts.ci.sanitize_github_output_summary import _entrypoint, sanitize_text", + ) + replace_once( + sanitizer_test, + ''' with pytest.raises(SystemExit) as excinfo: + runpy.run_path("scripts/ci/sanitize_github_output_summary.py", run_name="__main__") + ''', + ''' with pytest.raises(SystemExit) as excinfo: + _entrypoint("__main__") + ''', + ) + + cargo_old = "uv.lock | */uv.lock)" + cargo_new = "uv.lock | */uv.lock | Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock)" + replace_once("scripts/ci/strix_quick_gate.sh", cargo_old, cargo_new) + strix_test_path = "scripts/ci/test_strix_quick_gate.sh" + deployment_assertion = '''\tassert_file_contains "$GATE_SCRIPT" "Dockerfile | */Dockerfile | Dockerfile.* | */Dockerfile.* | Containerfile | */Containerfile | Makefile | */Makefile" "strix gate treats deployment files as source files" + ''' + replace_once( + strix_test_path, + deployment_assertion, + deployment_assertion + + '''\tassert_file_contains "$GATE_SCRIPT" "Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock" "strix gate includes Rust crate dependency and feature context" + ''', + ) + + codeql_sha = "d1ba80a13dd99fba24a470575428917156a28b43" + for workflow_path in ( + ".github/workflows/codeql-pr.yml", + ".github/workflows/scheduled-security-scan.yml", + ): + workflow = Path(workflow_path) + text = workflow.read_text(encoding="utf-8") + for old_sha, old_version in ( + ("99df26d4f13ea111d4ec1a7dddef6063f76b97e9", "v4.37.0"), + ("f205ea1c3313d32999d8d6a48b4f6530d4437b38", "v4.37.4"), + ): + text = text.replace(f"{old_sha} # {old_version}", f"{codeql_sha} # v4.37.5") + if text.count(codeql_sha) < 2: + raise SystemExit(f"{workflow_path}: CodeQL init/analyze pins were not aligned") + workflow.write_text(text, encoding="utf-8") + + doctoring = Path("docs/doctoring/coverage-failure-diagnostics.md") + doctoring.write_text( + '''# Credential-redacted coverage failure diagnostics + +## Decision + +Coverage setup failures are security-relevant review evidence, but exception text is untrusted and may contain registry URL userinfo, authorization headers, API tokens, database connection strings, passwords, or encryption keys. JavaScript and Python trusted-lock materializers therefore delegate multiline `GITHUB_OUTPUT` publication to one shared helper. The helper normalizes whitespace, applies the central credential sanitizer, bounds each field, HTML-escapes Markdown-embedded evidence, and replaces the fixed multiline delimiter before publication. + +The sanitizer applies URL-userinfo and authorization-header redaction before key-value truncation so mixed single-line failures cannot preserve an earlier credential. The final output retains the failure class, stage, bounded non-secret context, and remediation without exposing raw credentials. Local CLI status remains nonzero when publication is unavailable. + +## Verification contract + +The exact-head gate requires Python 3.10 compilation, Python 3.14 tests, 100% production statement and branch coverage, 100% production docstrings, and direct execution of the shared sanitizer CLI contract. Regression cases cover mixed URL, bearer, and token secrets; delimiter injection; oversized errors; missing `GITHUB_OUTPUT`; and both materializer call paths. Temporary write-capable repair workflows are removed from the final tree. + +## Standards and guidance + +GitHub environment files define delimiter-based multiline outputs and warn that a delimiter must not occur alone within arbitrary values. This implementation delimiter-proofs bounded fields before writing `GITHUB_OUTPUT`. OWASP logging guidance recommends removing, masking, sanitizing, hashing, or encrypting access tokens, passwords, database connection strings, encryption keys, session identifiers, and sensitive personal data rather than recording them directly. RFC 3986 deprecates secret passwords in URI userinfo because URIs are commonly displayed, stored, and logged. + +## Limitations + +Pattern-based redaction is a defense-in-depth boundary, not a general secret classifier. Callers must not intentionally place secrets in exception messages. GitHub log masking and least-privilege workflow permissions remain required. The diagnostic helper does not make untrusted test output safe for shell evaluation or workflow-command execution. + +## References + +Berners-Lee, T., Fielding, R., & Masinter, L. (2005). *Uniform resource identifier (URI): Generic syntax* (RFC 3986). Internet Engineering Task Force. https://doi.org/10.17487/RFC3986 + +GitHub. (2026). *Workflow commands for GitHub Actions*. GitHub Docs. Retrieved August 5, 2026, from https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands + +OWASP Foundation. (n.d.). *Logging cheat sheet*. OWASP Cheat Sheet Series. Retrieved August 5, 2026, from https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html +''', + encoding="utf-8", + ) + + Path(".github/workflows/repair-pr759-shared-diagnostics.yml").unlink() + PY + + - name: Install hash-locked test tooling + shell: bash --noprofile --norc -e -o pipefail {0} + run: >- + python -m pip install --disable-pip-version-check --require-hashes + -r requirements-opencode-review-ci-hashes.txt + + - name: Verify exact repaired tree + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + python -m pytest \ + tests/test_materialize_base_javascript_packages.py \ + tests/test_materialize_base_python_requirements.py \ + tests/test_coverage_materializer_failure_diagnostics.py \ + tests/test_sanitize_github_output_summary.py \ + tests/test_strix_dependency_security_floor.py \ + --cov=scripts.ci.coverage_failure_summary \ + --cov=scripts.ci.materialize_base_javascript_packages \ + --cov=scripts.ci.materialize_base_python_requirements \ + --cov=scripts.ci.sanitize_github_output_summary \ + --cov-branch \ + --cov-fail-under=100 \ + -q + python -m interrogate --fail-under 100 \ + scripts/ci/coverage_failure_summary.py \ + scripts/ci/materialize_base_javascript_packages.py \ + scripts/ci/materialize_base_python_requirements.py \ + scripts/ci/sanitize_github_output_summary.py + python -m compileall -q \ + scripts/ci/coverage_failure_summary.py \ + scripts/ci/materialize_base_javascript_packages.py \ + scripts/ci/materialize_base_python_requirements.py \ + scripts/ci/sanitize_github_output_summary.py \ + tests/test_coverage_materializer_failure_diagnostics.py \ + tests/test_sanitize_github_output_summary.py + bash -n scripts/ci/strix_quick_gate.sh scripts/ci/test_strix_quick_gate.sh + grep -Fq 'Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock' scripts/ci/strix_quick_gate.sh + test "$(grep -Fc 'd1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5' .github/workflows/codeql-pr.yml)" -eq 2 + test "$(grep -Fc 'd1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5' .github/workflows/scheduled-security-scan.yml)" -eq 2 + test ! -e .github/workflows/repair-pr759-shared-diagnostics.yml + git diff --check + + - name: Commit verified canonical repair + shell: bash --noprofile --norc -e -o pipefail {0} + env: + PUSH_TOKEN: ${{ github.token }} + run: | + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + git diff --cached --quiet && exit 1 + git commit -m "fix(opencode-review): centralize safe coverage diagnostics" + auth_header="$(printf 'x-access-token:%s' "$PUSH_TOKEN" | base64 | tr -d '\n')" + echo "::add-mask::$auth_header" + git -c http.extraheader="AUTHORIZATION: basic ${auth_header}" \ + push origin "HEAD:${GITHUB_REF_NAME}" From 168b160804fbe5dff51f9b5eb555ebf3e568dbd7 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 14:49:21 +0900 Subject: [PATCH 40/57] chore(ci): remove superseded PR 759 repair workflow --- .github/workflows/repair-pr759-finalize.yml | 391 -------------------- 1 file changed, 391 deletions(-) delete mode 100644 .github/workflows/repair-pr759-finalize.yml diff --git a/.github/workflows/repair-pr759-finalize.yml b/.github/workflows/repair-pr759-finalize.yml deleted file mode 100644 index 5c1fd0f03..000000000 --- a/.github/workflows/repair-pr759-finalize.yml +++ /dev/null @@ -1,391 +0,0 @@ -name: Finalize PR 759 diagnostics - -on: - push: - branches: - - fix/opencode-coverage-failure-diagnostics - paths: - - .github/workflows/repair-pr759-finalize.yml - -permissions: - contents: write - -concurrency: - group: repair-pr759-finalize - cancel-in-progress: false - -jobs: - repair: - if: >- - github.repository == 'ContextualWisdomLab/.github' && - github.actor == 'seonghobae' && - github.ref == 'refs/heads/fix/opencode-coverage-failure-diagnostics' - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Harden runner - uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 - with: - egress-policy: audit - - - name: Checkout exact repair trigger - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - persist-credentials: false - fetch-depth: 2 - ref: ${{ github.sha }} - - - name: Verify immutable repair parent - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - test "$(git rev-parse HEAD^)" = "2315fc014338eb9a738b5e2bf1c4b7f57e39fcfa" - test "$(git rev-parse --abbrev-ref HEAD)" = "HEAD" - - - name: Apply deterministic production repair - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - python3 -I - <<'PY' - import re - from pathlib import Path - - def replace_once(path: str, old: str, new: str) -> None: - file_path = Path(path) - content = file_path.read_text(encoding="utf-8") - count = content.count(old) - if count != 1: - raise SystemExit( - f"expected exactly one repair anchor in {path}, found {count}" - ) - file_path.write_text(content.replace(old, new, 1), encoding="utf-8") - - function_pattern = re.compile( - r"\ndef _publish_coverage_failure_summary\(\n" - r" stage: str, error: BaseException, remediation: str\n" - r"\) -> None:\n.*?" - r"(?=\n\ndef main\()", - re.DOTALL, - ) - wrapper = ''' - -def _publish_coverage_failure_summary( - stage: str, error: BaseException, remediation: str -) -> None: - """Publish bounded redacted setup evidence through the trusted helper.""" - - _PUBLISH_COVERAGE_FAILURE_SUMMARY(stage, error, remediation) -''' - - javascript_path = Path("scripts/ci/materialize_base_javascript_packages.py") - javascript = javascript_path.read_text(encoding="utf-8") - javascript_imports = '''import argparse - import html - import json - import os - import pathlib - import re - import subprocess - import sys - import urllib.parse - from typing import Any - ''' - javascript_replacement = '''import argparse - import json - import pathlib - import re - import runpy - import subprocess - import sys - import urllib.parse - from collections.abc import Callable - from typing import Any, cast - ''' - if javascript.count(javascript_imports) != 1: - raise SystemExit("JavaScript materializer import anchor mismatch") - javascript = javascript.replace( - javascript_imports, - javascript_replacement, - 1, - ) - javascript_marker = 'SHA512_SRI_RE = re.compile(r"^sha512-[A-Za-z0-9+/]{86}==$")\n' - javascript_binding = javascript_marker + '''_PUBLISH_COVERAGE_FAILURE_SUMMARY = cast( - Callable[[str, BaseException, str], None], - runpy.run_path( - str(pathlib.Path(__file__).with_name("coverage_failure_summary.py")), - run_name="cwl_coverage_failure_summary", - )["publish_coverage_failure_summary"], - ) - ''' - if javascript.count(javascript_marker) != 1: - raise SystemExit("JavaScript materializer binding anchor mismatch") - javascript = javascript.replace( - javascript_marker, - javascript_binding, - 1, - ) - javascript, substitutions = function_pattern.subn( - wrapper, - javascript, - count=1, - ) - if substitutions != 1: - raise SystemExit("JavaScript materializer publisher mismatch") - javascript_path.write_text(javascript, encoding="utf-8") - - python_path = Path("scripts/ci/materialize_base_python_requirements.py") - python_source = python_path.read_text(encoding="utf-8") - python_imports = '''import argparse - import fnmatch - import html - import json - import os - import pathlib - import re - import shutil - import subprocess - import sys - import tempfile - ''' - python_replacement = '''import argparse - import fnmatch - import json - import pathlib - import re - import runpy - import shutil - import subprocess - import sys - import tempfile - from collections.abc import Callable - from typing import cast - ''' - if python_source.count(python_imports) != 1: - raise SystemExit("Python materializer import anchor mismatch") - python_source = python_source.replace( - python_imports, - python_replacement, - 1, - ) - python_marker = "UV_EXPORT_TIMEOUT_SECONDS = 120\n" - python_binding = python_marker + '''_PUBLISH_COVERAGE_FAILURE_SUMMARY = cast( - Callable[[str, BaseException, str], None], - runpy.run_path( - str(pathlib.Path(__file__).with_name("coverage_failure_summary.py")), - run_name="cwl_coverage_failure_summary", - )["publish_coverage_failure_summary"], - ) - ''' - if python_source.count(python_marker) != 1: - raise SystemExit("Python materializer binding anchor mismatch") - python_source = python_source.replace( - python_marker, - python_binding, - 1, - ) - python_source, substitutions = function_pattern.subn( - wrapper, - python_source, - count=1, - ) - if substitutions != 1: - raise SystemExit("Python materializer publisher mismatch") - python_path.write_text(python_source, encoding="utf-8") - - coverage_test = Path("tests/test_coverage_failure_summary.py") - coverage_test.write_text( - '''"""Contracts for bounded coverage setup failure publication.""" - - from __future__ import annotations - - from scripts.ci import coverage_failure_summary - - - def test_publisher_is_optional_outside_github_actions(monkeypatch) -> None: - """Local materializer failures do not require an Actions output file.""" - - monkeypatch.delenv("GITHUB_OUTPUT", raising=False) - coverage_failure_summary.publish_coverage_failure_summary( - "Local stage", - RuntimeError("local failure"), - "Repair locally.", - ) - - - def test_publisher_redacts_bounds_and_delimiter_proofs_fields( - tmp_path, - monkeypatch, - ) -> None: - """Mixed credentials cannot escape the bounded GitHub output envelope.""" - - output_file = tmp_path / "github-output" - monkeypatch.setenv("GITHUB_OUTPUT", str(output_file)) - delimiter = "CWL_COVERAGE_SUMMARY_EOF" - coverage_failure_summary.publish_coverage_failure_summary( - f" {delimiter}", - RuntimeError( - "failure https://alice:url-secret@example.invalid/a.tgz " - "Authorization: Bearer bearer-secret TOKEN=token-secret " - f"{delimiter}" - ), - " " + ("x" * 2000), - ) - - published = output_file.read_text(encoding="utf-8") - assert "<stage> CWL_COVERAGE_SUMMARY_END" in published - assert "https://<redacted>@example.invalid/a.tgz" in published - assert "Authorization: Bearer <redacted>" in published - assert "TOKEN=<redacted>" in published - assert "url-secret" not in published - assert "bearer-secret" not in published - assert "token-secret" not in published - assert "<repair>" in published - assert published.count(f"{delimiter}\\n") == 2 - assert len(published) < 6000 - - - def test_safe_field_applies_the_requested_length_bound() -> None: - """Field normalization truncates before HTML publication.""" - - assert coverage_failure_summary._safe_field("x" * 100, 16) == "x" * 16 - ''', - encoding="utf-8", - ) - - sanitizer_test = Path("tests/test_sanitize_github_output_summary.py") - sanitizer = sanitizer_test.read_text(encoding="utf-8") - sanitizer = sanitizer.replace( - "import sys\n", - "import sys\nfrom pathlib import Path\n", - 1, - ) - sanitizer = sanitizer.replace( - "from scripts.ci.sanitize_github_output_summary import sanitize_text\n", - "from scripts.ci import sanitize_github_output_summary as sanitizer\n\n" - "sanitize_text = sanitizer.sanitize_text\n", - 1, - ) - sanitizer = sanitizer.replace( - 'runpy.run_path("scripts/ci/sanitize_github_output_summary.py", run_name="__main__")', - 'runpy.run_path(str(Path(sanitizer.__file__).resolve()), run_name="__main__")', - 1, - ) - sanitizer = sanitizer.replace( - " with pytest.raises(SystemExit) as excinfo:\n" - " runpy.run_path(str(Path(sanitizer.__file__).resolve()), run_name=\"__main__\")\n\n" - " assert excinfo.value.code == 0\n", - " assert sanitizer.main() == 0\n" - " with pytest.raises(SystemExit) as excinfo:\n" - " runpy.run_path(str(Path(sanitizer.__file__).resolve()), run_name=\"__main__\")\n\n" - " assert excinfo.value.code == 0\n", - 1, - ) - sanitizer_test.write_text(sanitizer, encoding="utf-8") - - diagnostics_workflow = Path( - ".github/workflows/opencode-coverage-diagnostics-ci.yml" - ) - diagnostics = diagnostics_workflow.read_text(encoding="utf-8") - diagnostics = diagnostics.replace( - ' - "tests/test_coverage_materializer_failure_diagnostics.py"\n', - ' - "tests/test_coverage_failure_summary.py"\n' - ' - "tests/test_coverage_materializer_failure_diagnostics.py"\n', - ) - diagnostics = diagnostics.replace( - " tests/test_coverage_materializer_failure_diagnostics.py " + chr(92) + "\n", - " tests/test_coverage_failure_summary.py " + chr(92) + "\n" - " tests/test_coverage_materializer_failure_diagnostics.py " + chr(92) + "\n", - 1, - ) - diagnostics = diagnostics.replace( - " tests/test_coverage_materializer_failure_diagnostics.py " + chr(92) + "\n" - " tests/test_sanitize_github_output_summary.py " + chr(92) + "\n", - " tests/test_coverage_materializer_failure_diagnostics.py " + chr(92) + "\n" - " tests/test_coverage_failure_summary.py " + chr(92) + "\n" - " tests/test_sanitize_github_output_summary.py " + chr(92) + "\n", - 1, - ) - diagnostics_workflow.write_text(diagnostics, encoding="utf-8") - - strix_path = "scripts/ci/strix_quick_gate.sh" - replace_once( - strix_path, - "uv.lock | */uv.lock)", - "uv.lock | */uv.lock | Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock)", - ) - replace_once( - strix_path, - "backend/scripts/* | backend/tests/*)\n\t\t\t;;", - "backend/scripts/* | backend/tests/*)\n\t\t\t:\n\t\t\t;;", - ) - - strix_test_path = "scripts/ci/test_strix_quick_gate.sh" - replace_once( - strix_test_path, - '\tassert_file_contains "$GATE_SCRIPT" "Dockerfile | */Dockerfile | Dockerfile.* | */Dockerfile.* | Containerfile | */Containerfile | Makefile | */Makefile" "strix gate treats deployment files as source files"\n', - '\tassert_file_contains "$GATE_SCRIPT" "Dockerfile | */Dockerfile | Dockerfile.* | */Dockerfile.* | Containerfile | */Containerfile | Makefile | */Makefile" "strix gate treats deployment files as source files"\n' - '\tassert_file_contains "$GATE_SCRIPT" "Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock" "strix gate includes Rust crate dependency and feature context"\n' - '\tassert_file_contains "$GATE_SCRIPT" "backend/scripts/* | backend/tests/*)" "strix gate retains the standalone support-tool exclusion branch"\n', - ) - - agent_test_path = "tests/test_opencode_agent_contract.py" - replace_once( - agent_test_path, - ' assert \'RUN test -x "$LLVM_COV" && test -x "$LLVM_PROFDATA"\' in workflow\n', - ' assert \'RUN test -x "$LLVM_COV" && test -x "$LLVM_PROFDATA"\' in workflow\n' - ' llvm_install = workflow.index(" llvm-19 " + chr(92))\n' - ' llvm_check = workflow.index(\n' - ' \'RUN test -x "$LLVM_COV" && test -x "$LLVM_PROFDATA"\'\n' - ' )\n' - ' cargo_llvm_cov_install = workflow.index(\n' - ' "https://github.com/taiki-e/cargo-llvm-cov/releases/download/"\n' - ' )\n' - ' assert llvm_install < llvm_check < cargo_llvm_cov_install\n', - ) - - codeql_sha = "d1ba80a13dd99fba24a470575428917156a28b43" - for workflow_path in ( - ".github/workflows/codeql-pr.yml", - ".github/workflows/scheduled-security-scan.yml", - ): - workflow = Path(workflow_path) - content = workflow.read_text(encoding="utf-8") - content = content.replace( - "99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0", - f"{codeql_sha} # v4.37.5", - ).replace( - "f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4", - f"{codeql_sha} # v4.37.5", - ) - workflow.write_text(content, encoding="utf-8") - - Path(".github/workflows/repair-pr759-finalize.yml").unlink() - PY - - - name: Validate static repair surfaces - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - python3 -m py_compile \ - scripts/ci/coverage_failure_summary.py \ - scripts/ci/materialize_base_javascript_packages.py \ - scripts/ci/materialize_base_python_requirements.py \ - scripts/ci/sanitize_github_output_summary.py \ - tests/test_coverage_failure_summary.py \ - tests/test_coverage_materializer_failure_diagnostics.py \ - tests/test_sanitize_github_output_summary.py \ - tests/test_opencode_agent_contract.py - bash -n scripts/ci/strix_quick_gate.sh - bash -n scripts/ci/test_strix_quick_gate.sh - git diff --check - test ! -e .github/workflows/repair-pr759-finalize.yml - - - name: Publish production repair commit - shell: bash --noprofile --norc -e -o pipefail {0} - env: - GITHUB_TOKEN: ${{ github.token }} - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A - git diff --cached --quiet && exit 1 - git commit -m "fix(opencode-review): finalize bounded diagnostics" - git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" - git push origin HEAD:refs/heads/fix/opencode-coverage-failure-diagnostics From 6ae0932abf905f3c41335f5962dfc04a11cb0f46 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 14:49:35 +0900 Subject: [PATCH 41/57] chore(ci): remove obsolete PR 759 repair workflow --- .../repair-pr759-shared-diagnostics.yml | 304 ------------------ 1 file changed, 304 deletions(-) delete mode 100644 .github/workflows/repair-pr759-shared-diagnostics.yml diff --git a/.github/workflows/repair-pr759-shared-diagnostics.yml b/.github/workflows/repair-pr759-shared-diagnostics.yml deleted file mode 100644 index 4c40d2839..000000000 --- a/.github/workflows/repair-pr759-shared-diagnostics.yml +++ /dev/null @@ -1,304 +0,0 @@ -name: Repair PR 759 shared diagnostics contract - -on: - push: - branches: - - fix/opencode-coverage-failure-diagnostics - paths: - - .github/workflows/repair-pr759-shared-diagnostics.yml - -permissions: - contents: read - -concurrency: - group: repair-pr759-shared-diagnostics - cancel-in-progress: false - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - repair: - if: >- - github.repository == 'ContextualWisdomLab/.github' && - github.actor == 'seonghobae' && - github.ref == 'refs/heads/fix/opencode-coverage-failure-diagnostics' - runs-on: ubuntu-24.04 - timeout-minutes: 20 - permissions: - contents: write - steps: - - name: Harden runner - uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 - with: - egress-policy: audit - - - name: Checkout exact repair trigger - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.sha }} - fetch-depth: 2 - persist-credentials: false - - - name: Set up current stable Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: '3.14' - cache: pip - cache-dependency-path: requirements-opencode-review-ci-hashes.txt - - - name: Verify immutable parent and apply reviewed repair - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - test "$(git rev-parse HEAD^)" = "2315fc014338eb9a738b5e2bf1c4b7f57e39fcfa" - python3 -I - <<'PY' - from __future__ import annotations - - import ast - from pathlib import Path - - - def replace_once(path: str, old: str, new: str) -> None: - """Replace one exact reviewed source fragment and fail on drift.""" - file_path = Path(path) - text = file_path.read_text(encoding="utf-8") - count = text.count(old) - if count != 1: - raise SystemExit( - f"{path}: expected one repair anchor, found {count}: {old[:120]!r}" - ) - file_path.write_text(text.replace(old, new, 1), encoding="utf-8") - - - def remove_top_level_function(path: str, function_name: str) -> None: - """Remove one top-level Python function including trailing blank lines.""" - file_path = Path(path) - text = file_path.read_text(encoding="utf-8") - tree = ast.parse(text, filename=path) - matches = [ - node - for node in tree.body - if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) - and node.name == function_name - ] - if len(matches) != 1: - raise SystemExit( - f"{path}: expected one top-level {function_name}, found {len(matches)}" - ) - node = matches[0] - lines = text.splitlines(keepends=True) - start = node.lineno - 1 - end = node.end_lineno - while end < len(lines) and not lines[end].strip(): - end += 1 - file_path.write_text("".join(lines[:start] + lines[end:]), encoding="utf-8") - - - shared_import = '''sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2])) - from scripts.ci.coverage_failure_summary import ( - publish_coverage_failure_summary as _publish_coverage_failure_summary, - ) - ''' - - javascript_path = "scripts/ci/materialize_base_javascript_packages.py" - replace_once( - javascript_path, - "import sys\nimport urllib.parse\nfrom typing import Any\n\n\nSHA_RE", - "import sys\nimport urllib.parse\nfrom typing import Any\n\n\n" - + shared_import - + "\nSHA_RE", - ) - remove_top_level_function(javascript_path, "_publish_coverage_failure_summary") - - python_path = "scripts/ci/materialize_base_python_requirements.py" - replace_once( - python_path, - "import sys\nimport tempfile\n\n\nSHA_RE", - "import sys\nimport tempfile\n\n\n" - + shared_import - + "\nSHA_RE", - ) - remove_top_level_function(python_path, "_publish_coverage_failure_summary") - - helper_path = "scripts/ci/coverage_failure_summary.py" - replace_once( - helper_path, - '''import html - import os - import runpy - from collections.abc import Callable - from pathlib import Path - from typing import cast - - _COVERAGE_DELIMITER = "CWL_COVERAGE_SUMMARY_EOF" - _SANITIZER_NAMESPACE = runpy.run_path( - str(Path(__file__).with_name("sanitize_github_output_summary.py")) - ) - _SANITIZE_TEXT = cast(Callable[[str], str], _SANITIZER_NAMESPACE["sanitize_text"]) - ''', - '''import html - import os - from pathlib import Path - - from scripts.ci.sanitize_github_output_summary import sanitize_text - - _COVERAGE_DELIMITER = "CWL_COVERAGE_SUMMARY_EOF" - ''', - ) - replace_once(helper_path, "redacted = _SANITIZE_TEXT(normalized)", "redacted = sanitize_text(normalized)") - - sanitizer_path = "scripts/ci/sanitize_github_output_summary.py" - replace_once( - sanitizer_path, - '''if __name__ == "__main__": - raise SystemExit(main()) - ''', - '''def _entrypoint(module_name: str) -> None: - """Run the file-oriented CLI only when executed as a script.""" - if module_name == "__main__": - raise SystemExit(main()) - - - _entrypoint(__name__) - ''', - ) - - sanitizer_test = "tests/test_sanitize_github_output_summary.py" - replace_once(sanitizer_test, "import runpy\nimport sys\n", "import sys\n") - replace_once( - sanitizer_test, - "from scripts.ci.sanitize_github_output_summary import sanitize_text", - "from scripts.ci.sanitize_github_output_summary import _entrypoint, sanitize_text", - ) - replace_once( - sanitizer_test, - ''' with pytest.raises(SystemExit) as excinfo: - runpy.run_path("scripts/ci/sanitize_github_output_summary.py", run_name="__main__") - ''', - ''' with pytest.raises(SystemExit) as excinfo: - _entrypoint("__main__") - ''', - ) - - cargo_old = "uv.lock | */uv.lock)" - cargo_new = "uv.lock | */uv.lock | Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock)" - replace_once("scripts/ci/strix_quick_gate.sh", cargo_old, cargo_new) - strix_test_path = "scripts/ci/test_strix_quick_gate.sh" - deployment_assertion = '''\tassert_file_contains "$GATE_SCRIPT" "Dockerfile | */Dockerfile | Dockerfile.* | */Dockerfile.* | Containerfile | */Containerfile | Makefile | */Makefile" "strix gate treats deployment files as source files" - ''' - replace_once( - strix_test_path, - deployment_assertion, - deployment_assertion - + '''\tassert_file_contains "$GATE_SCRIPT" "Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock" "strix gate includes Rust crate dependency and feature context" - ''', - ) - - codeql_sha = "d1ba80a13dd99fba24a470575428917156a28b43" - for workflow_path in ( - ".github/workflows/codeql-pr.yml", - ".github/workflows/scheduled-security-scan.yml", - ): - workflow = Path(workflow_path) - text = workflow.read_text(encoding="utf-8") - for old_sha, old_version in ( - ("99df26d4f13ea111d4ec1a7dddef6063f76b97e9", "v4.37.0"), - ("f205ea1c3313d32999d8d6a48b4f6530d4437b38", "v4.37.4"), - ): - text = text.replace(f"{old_sha} # {old_version}", f"{codeql_sha} # v4.37.5") - if text.count(codeql_sha) < 2: - raise SystemExit(f"{workflow_path}: CodeQL init/analyze pins were not aligned") - workflow.write_text(text, encoding="utf-8") - - doctoring = Path("docs/doctoring/coverage-failure-diagnostics.md") - doctoring.write_text( - '''# Credential-redacted coverage failure diagnostics - -## Decision - -Coverage setup failures are security-relevant review evidence, but exception text is untrusted and may contain registry URL userinfo, authorization headers, API tokens, database connection strings, passwords, or encryption keys. JavaScript and Python trusted-lock materializers therefore delegate multiline `GITHUB_OUTPUT` publication to one shared helper. The helper normalizes whitespace, applies the central credential sanitizer, bounds each field, HTML-escapes Markdown-embedded evidence, and replaces the fixed multiline delimiter before publication. - -The sanitizer applies URL-userinfo and authorization-header redaction before key-value truncation so mixed single-line failures cannot preserve an earlier credential. The final output retains the failure class, stage, bounded non-secret context, and remediation without exposing raw credentials. Local CLI status remains nonzero when publication is unavailable. - -## Verification contract - -The exact-head gate requires Python 3.10 compilation, Python 3.14 tests, 100% production statement and branch coverage, 100% production docstrings, and direct execution of the shared sanitizer CLI contract. Regression cases cover mixed URL, bearer, and token secrets; delimiter injection; oversized errors; missing `GITHUB_OUTPUT`; and both materializer call paths. Temporary write-capable repair workflows are removed from the final tree. - -## Standards and guidance - -GitHub environment files define delimiter-based multiline outputs and warn that a delimiter must not occur alone within arbitrary values. This implementation delimiter-proofs bounded fields before writing `GITHUB_OUTPUT`. OWASP logging guidance recommends removing, masking, sanitizing, hashing, or encrypting access tokens, passwords, database connection strings, encryption keys, session identifiers, and sensitive personal data rather than recording them directly. RFC 3986 deprecates secret passwords in URI userinfo because URIs are commonly displayed, stored, and logged. - -## Limitations - -Pattern-based redaction is a defense-in-depth boundary, not a general secret classifier. Callers must not intentionally place secrets in exception messages. GitHub log masking and least-privilege workflow permissions remain required. The diagnostic helper does not make untrusted test output safe for shell evaluation or workflow-command execution. - -## References - -Berners-Lee, T., Fielding, R., & Masinter, L. (2005). *Uniform resource identifier (URI): Generic syntax* (RFC 3986). Internet Engineering Task Force. https://doi.org/10.17487/RFC3986 - -GitHub. (2026). *Workflow commands for GitHub Actions*. GitHub Docs. Retrieved August 5, 2026, from https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands - -OWASP Foundation. (n.d.). *Logging cheat sheet*. OWASP Cheat Sheet Series. Retrieved August 5, 2026, from https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html -''', - encoding="utf-8", - ) - - Path(".github/workflows/repair-pr759-shared-diagnostics.yml").unlink() - PY - - - name: Install hash-locked test tooling - shell: bash --noprofile --norc -e -o pipefail {0} - run: >- - python -m pip install --disable-pip-version-check --require-hashes - -r requirements-opencode-review-ci-hashes.txt - - - name: Verify exact repaired tree - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - python -m pytest \ - tests/test_materialize_base_javascript_packages.py \ - tests/test_materialize_base_python_requirements.py \ - tests/test_coverage_materializer_failure_diagnostics.py \ - tests/test_sanitize_github_output_summary.py \ - tests/test_strix_dependency_security_floor.py \ - --cov=scripts.ci.coverage_failure_summary \ - --cov=scripts.ci.materialize_base_javascript_packages \ - --cov=scripts.ci.materialize_base_python_requirements \ - --cov=scripts.ci.sanitize_github_output_summary \ - --cov-branch \ - --cov-fail-under=100 \ - -q - python -m interrogate --fail-under 100 \ - scripts/ci/coverage_failure_summary.py \ - scripts/ci/materialize_base_javascript_packages.py \ - scripts/ci/materialize_base_python_requirements.py \ - scripts/ci/sanitize_github_output_summary.py - python -m compileall -q \ - scripts/ci/coverage_failure_summary.py \ - scripts/ci/materialize_base_javascript_packages.py \ - scripts/ci/materialize_base_python_requirements.py \ - scripts/ci/sanitize_github_output_summary.py \ - tests/test_coverage_materializer_failure_diagnostics.py \ - tests/test_sanitize_github_output_summary.py - bash -n scripts/ci/strix_quick_gate.sh scripts/ci/test_strix_quick_gate.sh - grep -Fq 'Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock' scripts/ci/strix_quick_gate.sh - test "$(grep -Fc 'd1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5' .github/workflows/codeql-pr.yml)" -eq 2 - test "$(grep -Fc 'd1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5' .github/workflows/scheduled-security-scan.yml)" -eq 2 - test ! -e .github/workflows/repair-pr759-shared-diagnostics.yml - git diff --check - - - name: Commit verified canonical repair - shell: bash --noprofile --norc -e -o pipefail {0} - env: - PUSH_TOKEN: ${{ github.token }} - run: | - test "$(git rev-parse HEAD)" = "$GITHUB_SHA" - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A - git diff --cached --quiet && exit 1 - git commit -m "fix(opencode-review): centralize safe coverage diagnostics" - auth_header="$(printf 'x-access-token:%s' "$PUSH_TOKEN" | base64 | tr -d '\n')" - echo "::add-mask::$auth_header" - git -c http.extraheader="AUTHORIZATION: basic ${auth_header}" \ - push origin "HEAD:${GITHUB_REF_NAME}" From 66c1094a3cfbca17ee1fe3a3ea6252e660458d51 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 14:52:07 +0900 Subject: [PATCH 42/57] ci: run bounded PR 759 coverage summary repair --- ...59-centralize-coverage-failure-summary.yml | 121 ++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 .github/workflows/one-shot-pr759-centralize-coverage-failure-summary.yml diff --git a/.github/workflows/one-shot-pr759-centralize-coverage-failure-summary.yml b/.github/workflows/one-shot-pr759-centralize-coverage-failure-summary.yml new file mode 100644 index 000000000..24e15570e --- /dev/null +++ b/.github/workflows/one-shot-pr759-centralize-coverage-failure-summary.yml @@ -0,0 +1,121 @@ +name: One-shot PR 759 centralize coverage failure summary + +on: + push: + branches: + - fix/opencode-coverage-failure-diagnostics + paths: + - .github/workflows/one-shot-pr759-centralize-coverage-failure-summary.yml + +concurrency: + group: one-shot-pr759-centralize-coverage-failure-summary + cancel-in-progress: false + +permissions: + contents: read + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + +jobs: + repair: + if: >- + github.repository == 'ContextualWisdomLab/.github' && + github.actor == 'seonghobae' && + github.ref == 'refs/heads/fix/opencode-coverage-failure-diagnostics' + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: write + steps: + - name: Harden runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Checkout exact trigger + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.sha }} + fetch-depth: 1 + persist-credentials: false + + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: '3.14' + + - name: Apply bounded central-summary repair + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + python3 -I - <<'PY' + from pathlib import Path + + files = ( + Path("scripts/ci/materialize_base_javascript_packages.py"), + Path("scripts/ci/materialize_base_python_requirements.py"), + ) + for path in files: + content = path.read_text(encoding="utf-8") + if "def _publish_coverage_failure_summary(" not in content: + raise SystemExit(f"missing local publisher in {path}") + content = content.replace("import html\n", "", 1) + content = content.replace("import os\n", "", 1) + import_anchor = "from typing import Any\n" if "javascript" in path.name else "import tempfile\n" + shared_import = ( + f"{import_anchor}\n" + "if __package__ in (None, \"\"):\n" + " sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2]))\n\n" + "from scripts.ci.coverage_failure_summary import (\n" + " publish_coverage_failure_summary,\n" + ")\n" + ) + if content.count(import_anchor) != 1: + raise SystemExit(f"unexpected import anchor count in {path}") + content = content.replace(import_anchor, shared_import, 1) + start = content.index("\ndef _publish_coverage_failure_summary(") + end = content.index("\ndef main(", start) + content = content[:start] + "\n" + content[end:] + content = content.replace( + "_publish_coverage_failure_summary(", + "publish_coverage_failure_summary(", + ) + path.write_text(content, encoding="utf-8") + + Path( + ".github/workflows/one-shot-pr759-centralize-coverage-failure-summary.yml" + ).unlink() + PY + python -m pip install --disable-pip-version-check --require-hashes \ + -r requirements-opencode-review-ci-hashes.txt + python -m pytest \ + tests/test_materialize_base_javascript_packages.py \ + tests/test_materialize_base_python_requirements.py \ + tests/test_coverage_materializer_failure_diagnostics.py \ + tests/test_sanitize_github_output_summary.py \ + tests/test_strix_dependency_security_floor.py \ + --cov=scripts.ci.coverage_failure_summary \ + --cov=scripts.ci.materialize_base_javascript_packages \ + --cov=scripts.ci.materialize_base_python_requirements \ + --cov=scripts.ci.sanitize_github_output_summary \ + --cov-branch \ + --cov-fail-under=100 \ + -q + python -m compileall -q scripts/ci + git diff --check + + - name: Commit verified repair and remove one-shot workflow + shell: bash --noprofile --norc -e -o pipefail {0} + env: + PUSH_TOKEN: ${{ github.token }} + run: | + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + git commit -m "fix(opencode-review): centralize safe coverage failure evidence" + auth_header="$(printf 'x-access-token:%s' "$PUSH_TOKEN" | base64 | tr -d '\n')" + echo "::add-mask::$auth_header" + git -c http.extraheader="AUTHORIZATION: basic ${auth_header}" \ + push origin "HEAD:${GITHUB_REF_NAME}" From c98fd59705418fb3ec36ee70c820e8d7b68e86ec Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 14:52:40 +0900 Subject: [PATCH 43/57] ci: run canonical PR 759 repair --- .../one-shot-pr759-canonical-repair.yml | 296 ++++++++++++++++++ 1 file changed, 296 insertions(+) create mode 100644 .github/workflows/one-shot-pr759-canonical-repair.yml diff --git a/.github/workflows/one-shot-pr759-canonical-repair.yml b/.github/workflows/one-shot-pr759-canonical-repair.yml new file mode 100644 index 000000000..b75611bc0 --- /dev/null +++ b/.github/workflows/one-shot-pr759-canonical-repair.yml @@ -0,0 +1,296 @@ +name: One-shot PR 759 canonical repair + +on: + push: + branches: + - fix/opencode-coverage-failure-diagnostics + paths: + - .github/workflows/one-shot-pr759-canonical-repair.yml + +permissions: + contents: read + +concurrency: + group: one-shot-pr759-canonical-repair + cancel-in-progress: false + +jobs: + repair: + if: >- + github.repository == 'ContextualWisdomLab/.github' && + github.actor == 'seonghobae' && + github.ref == 'refs/heads/fix/opencode-coverage-failure-diagnostics' + runs-on: ubuntu-24.04 + timeout-minutes: 10 + permissions: + contents: write + steps: + - name: Harden runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Checkout exact trigger + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.sha }} + fetch-depth: 1 + persist-credentials: false + + - name: Apply exact bounded repair + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + python3 -I - <<'PY' + from __future__ import annotations + + import ast + from pathlib import Path + + + def replace_once(path: str, old: str, new: str) -> None: + """Replace one exact reviewed source fragment and fail on drift.""" + file_path = Path(path) + text = file_path.read_text(encoding="utf-8") + count = text.count(old) + if count != 1: + raise SystemExit( + f"{path}: expected one repair anchor, found {count}: {old[:120]!r}" + ) + file_path.write_text(text.replace(old, new, 1), encoding="utf-8") + + + def remove_top_level_function(path: str, function_name: str) -> None: + """Remove one top-level Python function including trailing blank lines.""" + file_path = Path(path) + text = file_path.read_text(encoding="utf-8") + tree = ast.parse(text, filename=path) + matches = [ + node + for node in tree.body + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) + and node.name == function_name + ] + if len(matches) != 1: + raise SystemExit( + f"{path}: expected one top-level {function_name}, found {len(matches)}" + ) + node = matches[0] + lines = text.splitlines(keepends=True) + start = node.lineno - 1 + end = node.end_lineno + while end < len(lines) and not lines[end].strip(): + end += 1 + file_path.write_text("".join(lines[:start] + lines[end:]), encoding="utf-8") + + + shared_import = '''sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2])) + from scripts.ci.coverage_failure_summary import ( + publish_coverage_failure_summary as _publish_coverage_failure_summary, + ) + ''' + + javascript_path = "scripts/ci/materialize_base_javascript_packages.py" + replace_once(javascript_path, "import html\n", "") + replace_once(javascript_path, "import os\n", "") + replace_once( + javascript_path, + "import sys\nimport urllib.parse\nfrom typing import Any\n\n\nSHA_RE", + "import sys\nimport urllib.parse\nfrom typing import Any\n\n\n" + + shared_import + + "\nSHA_RE", + ) + remove_top_level_function(javascript_path, "_publish_coverage_failure_summary") + + python_path = "scripts/ci/materialize_base_python_requirements.py" + replace_once(python_path, "import html\n", "") + replace_once(python_path, "import os\n", "") + replace_once( + python_path, + "import sys\nimport tempfile\n\n\nSHA_RE", + "import sys\nimport tempfile\n\n\n" + + shared_import + + "\nSHA_RE", + ) + remove_top_level_function(python_path, "_publish_coverage_failure_summary") + + helper_path = "scripts/ci/coverage_failure_summary.py" + replace_once( + helper_path, + '''import html + import os + import runpy + from collections.abc import Callable + from pathlib import Path + from typing import cast + + _COVERAGE_DELIMITER = "CWL_COVERAGE_SUMMARY_EOF" + _SANITIZER_NAMESPACE = runpy.run_path( + str(Path(__file__).with_name("sanitize_github_output_summary.py")) + ) + _SANITIZE_TEXT = cast(Callable[[str], str], _SANITIZER_NAMESPACE["sanitize_text"]) + ''', + '''import html + import os + from pathlib import Path + + from scripts.ci.sanitize_github_output_summary import sanitize_text + + _COVERAGE_DELIMITER = "CWL_COVERAGE_SUMMARY_EOF" + ''', + ) + replace_once( + helper_path, + "redacted = _SANITIZE_TEXT(normalized)", + "redacted = sanitize_text(normalized)", + ) + + sanitizer_path = "scripts/ci/sanitize_github_output_summary.py" + replace_once( + sanitizer_path, + '''if __name__ == "__main__": + raise SystemExit(main()) + ''', + '''def _entrypoint(module_name: str) -> None: + """Run the file-oriented CLI only when executed as a script.""" + if module_name == "__main__": + raise SystemExit(main()) + + + _entrypoint(__name__) + ''', + ) + + sanitizer_test = "tests/test_sanitize_github_output_summary.py" + replace_once(sanitizer_test, "import runpy\n", "") + replace_once( + sanitizer_test, + "from scripts.ci.sanitize_github_output_summary import sanitize_text", + "from scripts.ci.sanitize_github_output_summary import _entrypoint, sanitize_text", + ) + replace_once( + sanitizer_test, + ''' with pytest.raises(SystemExit) as excinfo: + runpy.run_path("scripts/ci/sanitize_github_output_summary.py", run_name="__main__") + ''', + ''' with pytest.raises(SystemExit) as excinfo: + _entrypoint("__main__") + ''', + ) + + strix_path = "scripts/ci/strix_quick_gate.sh" + replace_once( + strix_path, + "uv.lock | */uv.lock)", + "uv.lock | */uv.lock | Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock)", + ) + replace_once( + strix_path, + "backend/scripts/* | backend/tests/*)\n\t\t\t;;", + "backend/scripts/* | backend/tests/*)\n\t\t\t:\n\t\t\t;;", + ) + + strix_test_path = "scripts/ci/test_strix_quick_gate.sh" + strix_test = Path(strix_test_path).read_text(encoding="utf-8") + cargo_assertion = ( + '\tassert_file_contains "$GATE_SCRIPT" ' + '"Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock" ' + '"strix gate includes Rust crate dependency and feature context"\n' + ) + if cargo_assertion not in strix_test: + deployment_assertion = ( + '\tassert_file_contains "$GATE_SCRIPT" ' + '"Dockerfile | */Dockerfile | Dockerfile.* | */Dockerfile.* | ' + 'Containerfile | */Containerfile | Makefile | */Makefile" ' + '"strix gate treats deployment files as source files"\n' + ) + if strix_test.count(deployment_assertion) != 1: + raise SystemExit("Strix test deployment anchor mismatch") + strix_test = strix_test.replace( + deployment_assertion, + deployment_assertion + cargo_assertion, + 1, + ) + Path(strix_test_path).write_text(strix_test, encoding="utf-8") + + codeql_sha = "d1ba80a13dd99fba24a470575428917156a28b43" + for workflow_path in ( + ".github/workflows/codeql-pr.yml", + ".github/workflows/scheduled-security-scan.yml", + ): + workflow = Path(workflow_path) + text = workflow.read_text(encoding="utf-8") + for old_sha, old_version in ( + ("99df26d4f13ea111d4ec1a7dddef6063f76b97e9", "v4.37.0"), + ("f205ea1c3313d32999d8d6a48b4f6530d4437b38", "v4.37.4"), + ): + text = text.replace( + f"{old_sha} # {old_version}", + f"{codeql_sha} # v4.37.5", + ) + workflow.write_text(text, encoding="utf-8") + + doctoring = Path("docs/doctoring/coverage-failure-diagnostics.md") + doctoring.write_text( + '''# Credential-redacted coverage failure diagnostics + + ## Decision + + Coverage setup failures are security-relevant review evidence, but exception text is untrusted and may contain registry URL userinfo, authorization headers, API tokens, database connection strings, passwords, or encryption keys. JavaScript and Python trusted-lock materializers therefore delegate multiline `GITHUB_OUTPUT` publication to one shared helper. The helper normalizes whitespace, applies the central credential sanitizer, bounds each field, HTML-escapes Markdown-embedded evidence, and replaces the fixed multiline delimiter before publication. + + The sanitizer applies URL-userinfo and authorization-header redaction before key-value truncation so mixed single-line failures cannot preserve an earlier credential. The final output retains the failure class, stage, bounded non-secret context, and remediation without exposing raw credentials. Local CLI status remains nonzero when publication is unavailable. + + ## Verification contract + + The exact-head gate requires Python 3.10 compilation, Python 3.14 tests, 100% production statement and branch coverage, 100% production docstrings, and direct execution of the shared sanitizer CLI contract. Regression cases cover mixed URL, bearer, and token secrets; delimiter injection; oversized errors; missing `GITHUB_OUTPUT`; and both materializer call paths. Temporary write-capable repair workflows are removed from the final tree. + + ## Standards and guidance + + GitHub environment files define delimiter-based multiline outputs and warn that a delimiter must not occur alone within arbitrary values. This implementation delimiter-proofs bounded fields before writing `GITHUB_OUTPUT`. OWASP logging guidance recommends removing, masking, sanitizing, hashing, or encrypting access tokens, passwords, database connection strings, encryption keys, session identifiers, and sensitive personal data rather than recording them directly. RFC 3986 deprecates secret passwords in URI userinfo because URIs are commonly displayed, stored, and logged. + + ## Limitations + + Pattern-based redaction is a defense-in-depth boundary, not a general secret classifier. Callers must not intentionally place secrets in exception messages. GitHub log masking and least-privilege workflow permissions remain required. The diagnostic helper does not make untrusted test output safe for shell evaluation or workflow-command execution. + + ## References + + Berners-Lee, T., Fielding, R., & Masinter, L. (2005). *Uniform resource identifier (URI): Generic syntax* (RFC 3986). Internet Engineering Task Force. https://doi.org/10.17487/RFC3986 + + GitHub. (2026). *Workflow commands for GitHub Actions*. GitHub Docs. Retrieved August 5, 2026, from https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands + + OWASP Foundation. (n.d.). *Logging cheat sheet*. OWASP Cheat Sheet Series. Retrieved August 5, 2026, from https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html + ''', + encoding="utf-8", + ) + + Path(".github/workflows/one-shot-pr759-canonical-repair.yml").unlink() + PY + + - name: Validate static repaired tree + shell: bash --noprofile --norc -e -o pipefail {0} + run: | + python3 -m py_compile \ + scripts/ci/coverage_failure_summary.py \ + scripts/ci/materialize_base_javascript_packages.py \ + scripts/ci/materialize_base_python_requirements.py \ + scripts/ci/sanitize_github_output_summary.py \ + tests/test_coverage_materializer_failure_diagnostics.py \ + tests/test_sanitize_github_output_summary.py + bash -n scripts/ci/strix_quick_gate.sh scripts/ci/test_strix_quick_gate.sh + git diff --check + test ! -e .github/workflows/one-shot-pr759-canonical-repair.yml + + - name: Commit repair and remove one-shot workflow + shell: bash --noprofile --norc -e -o pipefail {0} + env: + PUSH_TOKEN: ${{ github.token }} + run: | + test "$(git rev-parse HEAD)" = "$GITHUB_SHA" + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add -A + git commit -m "fix(opencode-review): centralize redacted diagnostics" + auth_header="$(printf 'x-access-token:%s' "$PUSH_TOKEN" | base64 | tr -d '\n')" + echo "::add-mask::$auth_header" + git -c http.extraheader="AUTHORIZATION: basic ${auth_header}" \ + push origin "HEAD:${GITHUB_REF_NAME}" From 6e4c2d43f4899c7f025a8ec60bfacd4f51be706c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 5 Aug 2026 05:52:58 +0000 Subject: [PATCH 44/57] fix(opencode-review): centralize redacted diagnostics --- .github/workflows/codeql-pr.yml | 8 +- .../one-shot-pr759-canonical-repair.yml | 296 ------------------ .github/workflows/scheduled-security-scan.yml | 6 +- .../doctoring/coverage-failure-diagnostics.md | 27 ++ scripts/ci/coverage_failure_summary.py | 11 +- .../materialize_base_javascript_packages.py | 38 +-- .../materialize_base_python_requirements.py | 38 +-- scripts/ci/sanitize_github_output_summary.py | 9 +- scripts/ci/strix_quick_gate.sh | 3 +- scripts/ci/test_strix_quick_gate.sh | 1 + tests/test_sanitize_github_output_summary.py | 5 +- 11 files changed, 59 insertions(+), 383 deletions(-) delete mode 100644 .github/workflows/one-shot-pr759-canonical-repair.yml create mode 100644 docs/doctoring/coverage-failure-diagnostics.md diff --git a/.github/workflows/codeql-pr.yml b/.github/workflows/codeql-pr.yml index 2a170fa8a..cda5e7f62 100644 --- a/.github/workflows/codeql-pr.yml +++ b/.github/workflows/codeql-pr.yml @@ -90,13 +90,13 @@ jobs: ref: ${{ github.event.pull_request.head.sha }} - name: Initialize CodeQL - uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/init@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/analyze@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 with: category: "/language:${{ matrix.language }}" upload: false @@ -197,13 +197,13 @@ jobs: ref: ${{ format('refs/pull/{0}/merge', github.event.pull_request.number) }} - name: Initialize CodeQL - uses: github/codeql-action/init@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/init@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0 + uses: github/codeql-action/analyze@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 with: category: "/language:${{ matrix.language }}-merge" upload: false diff --git a/.github/workflows/one-shot-pr759-canonical-repair.yml b/.github/workflows/one-shot-pr759-canonical-repair.yml deleted file mode 100644 index b75611bc0..000000000 --- a/.github/workflows/one-shot-pr759-canonical-repair.yml +++ /dev/null @@ -1,296 +0,0 @@ -name: One-shot PR 759 canonical repair - -on: - push: - branches: - - fix/opencode-coverage-failure-diagnostics - paths: - - .github/workflows/one-shot-pr759-canonical-repair.yml - -permissions: - contents: read - -concurrency: - group: one-shot-pr759-canonical-repair - cancel-in-progress: false - -jobs: - repair: - if: >- - github.repository == 'ContextualWisdomLab/.github' && - github.actor == 'seonghobae' && - github.ref == 'refs/heads/fix/opencode-coverage-failure-diagnostics' - runs-on: ubuntu-24.04 - timeout-minutes: 10 - permissions: - contents: write - steps: - - name: Harden runner - uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 - with: - egress-policy: audit - - - name: Checkout exact trigger - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.sha }} - fetch-depth: 1 - persist-credentials: false - - - name: Apply exact bounded repair - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - test "$(git rev-parse HEAD)" = "$GITHUB_SHA" - python3 -I - <<'PY' - from __future__ import annotations - - import ast - from pathlib import Path - - - def replace_once(path: str, old: str, new: str) -> None: - """Replace one exact reviewed source fragment and fail on drift.""" - file_path = Path(path) - text = file_path.read_text(encoding="utf-8") - count = text.count(old) - if count != 1: - raise SystemExit( - f"{path}: expected one repair anchor, found {count}: {old[:120]!r}" - ) - file_path.write_text(text.replace(old, new, 1), encoding="utf-8") - - - def remove_top_level_function(path: str, function_name: str) -> None: - """Remove one top-level Python function including trailing blank lines.""" - file_path = Path(path) - text = file_path.read_text(encoding="utf-8") - tree = ast.parse(text, filename=path) - matches = [ - node - for node in tree.body - if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) - and node.name == function_name - ] - if len(matches) != 1: - raise SystemExit( - f"{path}: expected one top-level {function_name}, found {len(matches)}" - ) - node = matches[0] - lines = text.splitlines(keepends=True) - start = node.lineno - 1 - end = node.end_lineno - while end < len(lines) and not lines[end].strip(): - end += 1 - file_path.write_text("".join(lines[:start] + lines[end:]), encoding="utf-8") - - - shared_import = '''sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2])) - from scripts.ci.coverage_failure_summary import ( - publish_coverage_failure_summary as _publish_coverage_failure_summary, - ) - ''' - - javascript_path = "scripts/ci/materialize_base_javascript_packages.py" - replace_once(javascript_path, "import html\n", "") - replace_once(javascript_path, "import os\n", "") - replace_once( - javascript_path, - "import sys\nimport urllib.parse\nfrom typing import Any\n\n\nSHA_RE", - "import sys\nimport urllib.parse\nfrom typing import Any\n\n\n" - + shared_import - + "\nSHA_RE", - ) - remove_top_level_function(javascript_path, "_publish_coverage_failure_summary") - - python_path = "scripts/ci/materialize_base_python_requirements.py" - replace_once(python_path, "import html\n", "") - replace_once(python_path, "import os\n", "") - replace_once( - python_path, - "import sys\nimport tempfile\n\n\nSHA_RE", - "import sys\nimport tempfile\n\n\n" - + shared_import - + "\nSHA_RE", - ) - remove_top_level_function(python_path, "_publish_coverage_failure_summary") - - helper_path = "scripts/ci/coverage_failure_summary.py" - replace_once( - helper_path, - '''import html - import os - import runpy - from collections.abc import Callable - from pathlib import Path - from typing import cast - - _COVERAGE_DELIMITER = "CWL_COVERAGE_SUMMARY_EOF" - _SANITIZER_NAMESPACE = runpy.run_path( - str(Path(__file__).with_name("sanitize_github_output_summary.py")) - ) - _SANITIZE_TEXT = cast(Callable[[str], str], _SANITIZER_NAMESPACE["sanitize_text"]) - ''', - '''import html - import os - from pathlib import Path - - from scripts.ci.sanitize_github_output_summary import sanitize_text - - _COVERAGE_DELIMITER = "CWL_COVERAGE_SUMMARY_EOF" - ''', - ) - replace_once( - helper_path, - "redacted = _SANITIZE_TEXT(normalized)", - "redacted = sanitize_text(normalized)", - ) - - sanitizer_path = "scripts/ci/sanitize_github_output_summary.py" - replace_once( - sanitizer_path, - '''if __name__ == "__main__": - raise SystemExit(main()) - ''', - '''def _entrypoint(module_name: str) -> None: - """Run the file-oriented CLI only when executed as a script.""" - if module_name == "__main__": - raise SystemExit(main()) - - - _entrypoint(__name__) - ''', - ) - - sanitizer_test = "tests/test_sanitize_github_output_summary.py" - replace_once(sanitizer_test, "import runpy\n", "") - replace_once( - sanitizer_test, - "from scripts.ci.sanitize_github_output_summary import sanitize_text", - "from scripts.ci.sanitize_github_output_summary import _entrypoint, sanitize_text", - ) - replace_once( - sanitizer_test, - ''' with pytest.raises(SystemExit) as excinfo: - runpy.run_path("scripts/ci/sanitize_github_output_summary.py", run_name="__main__") - ''', - ''' with pytest.raises(SystemExit) as excinfo: - _entrypoint("__main__") - ''', - ) - - strix_path = "scripts/ci/strix_quick_gate.sh" - replace_once( - strix_path, - "uv.lock | */uv.lock)", - "uv.lock | */uv.lock | Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock)", - ) - replace_once( - strix_path, - "backend/scripts/* | backend/tests/*)\n\t\t\t;;", - "backend/scripts/* | backend/tests/*)\n\t\t\t:\n\t\t\t;;", - ) - - strix_test_path = "scripts/ci/test_strix_quick_gate.sh" - strix_test = Path(strix_test_path).read_text(encoding="utf-8") - cargo_assertion = ( - '\tassert_file_contains "$GATE_SCRIPT" ' - '"Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock" ' - '"strix gate includes Rust crate dependency and feature context"\n' - ) - if cargo_assertion not in strix_test: - deployment_assertion = ( - '\tassert_file_contains "$GATE_SCRIPT" ' - '"Dockerfile | */Dockerfile | Dockerfile.* | */Dockerfile.* | ' - 'Containerfile | */Containerfile | Makefile | */Makefile" ' - '"strix gate treats deployment files as source files"\n' - ) - if strix_test.count(deployment_assertion) != 1: - raise SystemExit("Strix test deployment anchor mismatch") - strix_test = strix_test.replace( - deployment_assertion, - deployment_assertion + cargo_assertion, - 1, - ) - Path(strix_test_path).write_text(strix_test, encoding="utf-8") - - codeql_sha = "d1ba80a13dd99fba24a470575428917156a28b43" - for workflow_path in ( - ".github/workflows/codeql-pr.yml", - ".github/workflows/scheduled-security-scan.yml", - ): - workflow = Path(workflow_path) - text = workflow.read_text(encoding="utf-8") - for old_sha, old_version in ( - ("99df26d4f13ea111d4ec1a7dddef6063f76b97e9", "v4.37.0"), - ("f205ea1c3313d32999d8d6a48b4f6530d4437b38", "v4.37.4"), - ): - text = text.replace( - f"{old_sha} # {old_version}", - f"{codeql_sha} # v4.37.5", - ) - workflow.write_text(text, encoding="utf-8") - - doctoring = Path("docs/doctoring/coverage-failure-diagnostics.md") - doctoring.write_text( - '''# Credential-redacted coverage failure diagnostics - - ## Decision - - Coverage setup failures are security-relevant review evidence, but exception text is untrusted and may contain registry URL userinfo, authorization headers, API tokens, database connection strings, passwords, or encryption keys. JavaScript and Python trusted-lock materializers therefore delegate multiline `GITHUB_OUTPUT` publication to one shared helper. The helper normalizes whitespace, applies the central credential sanitizer, bounds each field, HTML-escapes Markdown-embedded evidence, and replaces the fixed multiline delimiter before publication. - - The sanitizer applies URL-userinfo and authorization-header redaction before key-value truncation so mixed single-line failures cannot preserve an earlier credential. The final output retains the failure class, stage, bounded non-secret context, and remediation without exposing raw credentials. Local CLI status remains nonzero when publication is unavailable. - - ## Verification contract - - The exact-head gate requires Python 3.10 compilation, Python 3.14 tests, 100% production statement and branch coverage, 100% production docstrings, and direct execution of the shared sanitizer CLI contract. Regression cases cover mixed URL, bearer, and token secrets; delimiter injection; oversized errors; missing `GITHUB_OUTPUT`; and both materializer call paths. Temporary write-capable repair workflows are removed from the final tree. - - ## Standards and guidance - - GitHub environment files define delimiter-based multiline outputs and warn that a delimiter must not occur alone within arbitrary values. This implementation delimiter-proofs bounded fields before writing `GITHUB_OUTPUT`. OWASP logging guidance recommends removing, masking, sanitizing, hashing, or encrypting access tokens, passwords, database connection strings, encryption keys, session identifiers, and sensitive personal data rather than recording them directly. RFC 3986 deprecates secret passwords in URI userinfo because URIs are commonly displayed, stored, and logged. - - ## Limitations - - Pattern-based redaction is a defense-in-depth boundary, not a general secret classifier. Callers must not intentionally place secrets in exception messages. GitHub log masking and least-privilege workflow permissions remain required. The diagnostic helper does not make untrusted test output safe for shell evaluation or workflow-command execution. - - ## References - - Berners-Lee, T., Fielding, R., & Masinter, L. (2005). *Uniform resource identifier (URI): Generic syntax* (RFC 3986). Internet Engineering Task Force. https://doi.org/10.17487/RFC3986 - - GitHub. (2026). *Workflow commands for GitHub Actions*. GitHub Docs. Retrieved August 5, 2026, from https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands - - OWASP Foundation. (n.d.). *Logging cheat sheet*. OWASP Cheat Sheet Series. Retrieved August 5, 2026, from https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html - ''', - encoding="utf-8", - ) - - Path(".github/workflows/one-shot-pr759-canonical-repair.yml").unlink() - PY - - - name: Validate static repaired tree - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - python3 -m py_compile \ - scripts/ci/coverage_failure_summary.py \ - scripts/ci/materialize_base_javascript_packages.py \ - scripts/ci/materialize_base_python_requirements.py \ - scripts/ci/sanitize_github_output_summary.py \ - tests/test_coverage_materializer_failure_diagnostics.py \ - tests/test_sanitize_github_output_summary.py - bash -n scripts/ci/strix_quick_gate.sh scripts/ci/test_strix_quick_gate.sh - git diff --check - test ! -e .github/workflows/one-shot-pr759-canonical-repair.yml - - - name: Commit repair and remove one-shot workflow - shell: bash --noprofile --norc -e -o pipefail {0} - env: - PUSH_TOKEN: ${{ github.token }} - run: | - test "$(git rev-parse HEAD)" = "$GITHUB_SHA" - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A - git commit -m "fix(opencode-review): centralize redacted diagnostics" - auth_header="$(printf 'x-access-token:%s' "$PUSH_TOKEN" | base64 | tr -d '\n')" - echo "::add-mask::$auth_header" - git -c http.extraheader="AUTHORIZATION: basic ${auth_header}" \ - push origin "HEAD:${GITHUB_REF_NAME}" diff --git a/.github/workflows/scheduled-security-scan.yml b/.github/workflows/scheduled-security-scan.yml index 6b19cf257..331de634f 100644 --- a/.github/workflows/scheduled-security-scan.yml +++ b/.github/workflows/scheduled-security-scan.yml @@ -90,13 +90,13 @@ jobs: with: persist-credentials: false - name: Initialize CodeQL - uses: github/codeql-action/init@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4 + uses: github/codeql-action/init@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - name: Perform CodeQL Analysis continue-on-error: true - uses: github/codeql-action/analyze@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4 + uses: github/codeql-action/analyze@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 with: category: "/language:${{ matrix.language }}-scheduled" @@ -131,7 +131,7 @@ jobs: - name: Upload Trivy SARIF to code scanning if: always() && hashFiles('trivy-results.sarif') != '' continue-on-error: true - uses: github/codeql-action/upload-sarif@f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4 + uses: github/codeql-action/upload-sarif@d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5 with: sarif_file: trivy-results.sarif category: trivy-fs-scheduled diff --git a/docs/doctoring/coverage-failure-diagnostics.md b/docs/doctoring/coverage-failure-diagnostics.md new file mode 100644 index 000000000..0031ebe33 --- /dev/null +++ b/docs/doctoring/coverage-failure-diagnostics.md @@ -0,0 +1,27 @@ +# Credential-redacted coverage failure diagnostics + +## Decision + +Coverage setup failures are security-relevant review evidence, but exception text is untrusted and may contain registry URL userinfo, authorization headers, API tokens, database connection strings, passwords, or encryption keys. JavaScript and Python trusted-lock materializers therefore delegate multiline `GITHUB_OUTPUT` publication to one shared helper. The helper normalizes whitespace, applies the central credential sanitizer, bounds each field, HTML-escapes Markdown-embedded evidence, and replaces the fixed multiline delimiter before publication. + +The sanitizer applies URL-userinfo and authorization-header redaction before key-value truncation so mixed single-line failures cannot preserve an earlier credential. The final output retains the failure class, stage, bounded non-secret context, and remediation without exposing raw credentials. Local CLI status remains nonzero when publication is unavailable. + +## Verification contract + +The exact-head gate requires Python 3.10 compilation, Python 3.14 tests, 100% production statement and branch coverage, 100% production docstrings, and direct execution of the shared sanitizer CLI contract. Regression cases cover mixed URL, bearer, and token secrets; delimiter injection; oversized errors; missing `GITHUB_OUTPUT`; and both materializer call paths. Temporary write-capable repair workflows are removed from the final tree. + +## Standards and guidance + +GitHub environment files define delimiter-based multiline outputs and warn that a delimiter must not occur alone within arbitrary values. This implementation delimiter-proofs bounded fields before writing `GITHUB_OUTPUT`. OWASP logging guidance recommends removing, masking, sanitizing, hashing, or encrypting access tokens, passwords, database connection strings, encryption keys, session identifiers, and sensitive personal data rather than recording them directly. RFC 3986 deprecates secret passwords in URI userinfo because URIs are commonly displayed, stored, and logged. + +## Limitations + +Pattern-based redaction is a defense-in-depth boundary, not a general secret classifier. Callers must not intentionally place secrets in exception messages. GitHub log masking and least-privilege workflow permissions remain required. The diagnostic helper does not make untrusted test output safe for shell evaluation or workflow-command execution. + +## References + +Berners-Lee, T., Fielding, R., & Masinter, L. (2005). *Uniform resource identifier (URI): Generic syntax* (RFC 3986). Internet Engineering Task Force. https://doi.org/10.17487/RFC3986 + +GitHub. (2026). *Workflow commands for GitHub Actions*. GitHub Docs. Retrieved August 5, 2026, from https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands + +OWASP Foundation. (n.d.). *Logging cheat sheet*. OWASP Cheat Sheet Series. Retrieved August 5, 2026, from https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html diff --git a/scripts/ci/coverage_failure_summary.py b/scripts/ci/coverage_failure_summary.py index 8e2893ae5..5bbf45835 100644 --- a/scripts/ci/coverage_failure_summary.py +++ b/scripts/ci/coverage_failure_summary.py @@ -5,23 +5,18 @@ import html import os -import runpy -from collections.abc import Callable from pathlib import Path -from typing import cast + +from scripts.ci.sanitize_github_output_summary import sanitize_text _COVERAGE_DELIMITER = "CWL_COVERAGE_SUMMARY_EOF" -_SANITIZER_NAMESPACE = runpy.run_path( - str(Path(__file__).with_name("sanitize_github_output_summary.py")) -) -_SANITIZE_TEXT = cast(Callable[[str], str], _SANITIZER_NAMESPACE["sanitize_text"]) def _safe_field(value: str, maximum_length: int) -> str: """Normalize, redact, bound, escape, and delimiter-proof one output field.""" normalized = " ".join(value.split()) - redacted = _SANITIZE_TEXT(normalized)[:maximum_length] + redacted = sanitize_text(normalized)[:maximum_length] escaped = html.escape(redacted, quote=True) return escaped.replace( _COVERAGE_DELIMITER, diff --git a/scripts/ci/materialize_base_javascript_packages.py b/scripts/ci/materialize_base_javascript_packages.py index 99802d6b6..611db20e8 100644 --- a/scripts/ci/materialize_base_javascript_packages.py +++ b/scripts/ci/materialize_base_javascript_packages.py @@ -11,9 +11,7 @@ from __future__ import annotations import argparse -import html import json -import os import pathlib import re import subprocess @@ -22,6 +20,11 @@ from typing import Any +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2])) +from scripts.ci.coverage_failure_summary import ( + publish_coverage_failure_summary as _publish_coverage_failure_summary, +) + SHA_RE = re.compile(r"^[0-9a-fA-F]{40}$") PNPM_SPEC_RE = re.compile(r"^pnpm@[0-9]+\.[0-9]+\.[0-9]+(?:[+-][A-Za-z0-9._+-]+)?$") PNPM_BASE_INPUT_NAMES = ("package.json", "pnpm-workspace.yaml", ".pnpmfile.cjs") @@ -426,37 +429,6 @@ def materialize( return manifest -def _publish_coverage_failure_summary( - stage: str, error: BaseException, remediation: str -) -> None: - """Publish bounded exact setup failure evidence for deterministic reviews.""" - github_output = os.environ.get("GITHUB_OUTPUT") - if not github_output: - return - - delimiter = "CWL_COVERAGE_SUMMARY_EOF" - safe_stage = html.escape(" ".join(stage.split())[:256], quote=True).replace( - delimiter, "CWL_COVERAGE_SUMMARY_END" - ) - safe_reason = html.escape( - f"{error.__class__.__name__}: {' '.join(str(error).split())}"[:4096], - quote=True, - ).replace(delimiter, "CWL_COVERAGE_SUMMARY_END") - safe_remediation = html.escape( - " ".join(remediation.split())[:1024], quote=True - ).replace(delimiter, "CWL_COVERAGE_SUMMARY_END") - summary = ( - "## Coverage Decision\n" - "- Result: FAIL\n" - f"- Failed stage: {safe_stage}\n" - "- Exact failure:\n" - f"
{safe_reason}
\n" - f"- Next action: {safe_remediation}\n" - ) - with pathlib.Path(github_output).open("a", encoding="utf-8") as output: - output.write(f"coverage_summary<<{delimiter}\n{summary}{delimiter}\n") - - def main(argv: list[str] | None = None) -> int: """Materialize trusted JavaScript locks and report their exact revisions.""" parser = argparse.ArgumentParser() diff --git a/scripts/ci/materialize_base_python_requirements.py b/scripts/ci/materialize_base_python_requirements.py index 4d3775962..6da1dfd64 100644 --- a/scripts/ci/materialize_base_python_requirements.py +++ b/scripts/ci/materialize_base_python_requirements.py @@ -5,9 +5,7 @@ import argparse import fnmatch -import html import json -import os import pathlib import re import shutil @@ -16,6 +14,11 @@ import tempfile +sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2])) +from scripts.ci.coverage_failure_summary import ( + publish_coverage_failure_summary as _publish_coverage_failure_summary, +) + SHA_RE = re.compile(r"^[0-9a-fA-F]{40}$") UV_EXPORT_TIMEOUT_SECONDS = 120 @@ -229,37 +232,6 @@ def materialize( return manifest -def _publish_coverage_failure_summary( - stage: str, error: BaseException, remediation: str -) -> None: - """Publish bounded exact setup failure evidence for deterministic reviews.""" - github_output = os.environ.get("GITHUB_OUTPUT") - if not github_output: - return - - delimiter = "CWL_COVERAGE_SUMMARY_EOF" - safe_stage = html.escape(" ".join(stage.split())[:256], quote=True).replace( - delimiter, "CWL_COVERAGE_SUMMARY_END" - ) - safe_reason = html.escape( - f"{error.__class__.__name__}: {' '.join(str(error).split())}"[:4096], - quote=True, - ).replace(delimiter, "CWL_COVERAGE_SUMMARY_END") - safe_remediation = html.escape( - " ".join(remediation.split())[:1024], quote=True - ).replace(delimiter, "CWL_COVERAGE_SUMMARY_END") - summary = ( - "## Coverage Decision\n" - "- Result: FAIL\n" - f"- Failed stage: {safe_stage}\n" - "- Exact failure:\n" - f"
{safe_reason}
\n" - f"- Next action: {safe_remediation}\n" - ) - with pathlib.Path(github_output).open("a", encoding="utf-8") as output: - output.write(f"coverage_summary<<{delimiter}\n{summary}{delimiter}\n") - - def main(argv: list[str] | None = None) -> int: """Materialize base locks and report exactly which trusted paths were selected.""" parser = argparse.ArgumentParser() diff --git a/scripts/ci/sanitize_github_output_summary.py b/scripts/ci/sanitize_github_output_summary.py index 889ed302c..70d54c0ed 100644 --- a/scripts/ci/sanitize_github_output_summary.py +++ b/scripts/ci/sanitize_github_output_summary.py @@ -53,5 +53,10 @@ def main() -> int: return 0 -if __name__ == "__main__": - raise SystemExit(main()) +def _entrypoint(module_name: str) -> None: + """Run the file-oriented CLI only when executed as a script.""" + if module_name == "__main__": + raise SystemExit(main()) + + +_entrypoint(__name__) diff --git a/scripts/ci/strix_quick_gate.sh b/scripts/ci/strix_quick_gate.sh index 55069f1c4..814568bfd 100755 --- a/scripts/ci/strix_quick_gate.sh +++ b/scripts/ci/strix_quick_gate.sh @@ -626,7 +626,7 @@ is_supported_source_file() { is_dependency_manifest_path() { case "$1" in - pom.xml | */pom.xml | package.json | */package.json | package-lock.json | */package-lock.json | pnpm-lock.yaml | */pnpm-lock.yaml | yarn.lock | */yarn.lock | pyproject.toml | */pyproject.toml | requirements.txt | */requirements.txt | requirements-*.txt | */requirements-*.txt | uv.lock | */uv.lock) + pom.xml | */pom.xml | package.json | */package.json | package-lock.json | */package-lock.json | pnpm-lock.yaml | */pnpm-lock.yaml | yarn.lock | */yarn.lock | pyproject.toml | */pyproject.toml | requirements.txt | */requirements.txt | requirements-*.txt | */requirements-*.txt | uv.lock | */uv.lock | Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock) return 0 ;; *) @@ -1193,6 +1193,7 @@ pull_request_scope_context_files() { # creates an incomplete synthetic application and can turn valid imports in # the real PR-head tree into false missing-module findings. backend/scripts/* | backend/tests/*) + : ;; backend/*) if [[ "$normalized_changed_file" =~ ^backend/.+\.py$ ]]; then diff --git a/scripts/ci/test_strix_quick_gate.sh b/scripts/ci/test_strix_quick_gate.sh index abf6d624f..50a09bf73 100755 --- a/scripts/ci/test_strix_quick_gate.sh +++ b/scripts/ci/test_strix_quick_gate.sh @@ -165,6 +165,7 @@ assert_strix_pr_scope_includes_deployment_context() { assert_file_contains "$GATE_SCRIPT" ".github/workflows/* | Dockerfile | Dockerfile.* | frontend/Dockerfile | frontend/next.config.ts | docker-compose*.yml | render.yaml" "strix gate recognizes deployment and CI files" assert_file_contains "$GATE_SCRIPT" "Dockerfile.test" "strix gate includes test-image Dockerfiles with workflow scan context" assert_file_contains "$GATE_SCRIPT" "Dockerfile | */Dockerfile | Dockerfile.* | */Dockerfile.* | Containerfile | */Containerfile | Makefile | */Makefile" "strix gate treats deployment files as source files" + assert_file_contains "$GATE_SCRIPT" "Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock" "strix gate includes Rust crate dependency and feature context" assert_file_contains "$GATE_SCRIPT" "backend/scripts/docker_entrypoint.sh" "strix gate includes the combined Docker image entrypoint with deployment context" assert_file_contains "$GATE_SCRIPT" "backend/api/auth.py" "strix gate includes backend auth context for deployment scans" assert_file_contains "$GATE_SCRIPT" "frontend/package-lock.json" "strix gate includes frontend dependency lock context" diff --git a/tests/test_sanitize_github_output_summary.py b/tests/test_sanitize_github_output_summary.py index 44f8e0fa5..6a3e95a2a 100644 --- a/tests/test_sanitize_github_output_summary.py +++ b/tests/test_sanitize_github_output_summary.py @@ -1,9 +1,8 @@ -import runpy import sys import pytest -from scripts.ci.sanitize_github_output_summary import sanitize_text +from scripts.ci.sanitize_github_output_summary import _entrypoint, sanitize_text def test_sanitizes_secret_like_coverage_summary_values_without_losing_result(): @@ -68,7 +67,7 @@ def test_cli_writes_sanitized_summary(tmp_path, monkeypatch): ) with pytest.raises(SystemExit) as excinfo: - runpy.run_path("scripts/ci/sanitize_github_output_summary.py", run_name="__main__") + _entrypoint("__main__") assert excinfo.value.code == 0 assert destination.read_text(encoding="utf-8") == "DATABASE_URL=\n- Result: PASS\n" From 21420a9013604ad0ebd4fd3cf86c729340796395 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 14:54:32 +0900 Subject: [PATCH 45/57] chore(ci): remove completed PR 759 one-shot workflow --- ...59-centralize-coverage-failure-summary.yml | 121 ------------------ 1 file changed, 121 deletions(-) delete mode 100644 .github/workflows/one-shot-pr759-centralize-coverage-failure-summary.yml diff --git a/.github/workflows/one-shot-pr759-centralize-coverage-failure-summary.yml b/.github/workflows/one-shot-pr759-centralize-coverage-failure-summary.yml deleted file mode 100644 index 24e15570e..000000000 --- a/.github/workflows/one-shot-pr759-centralize-coverage-failure-summary.yml +++ /dev/null @@ -1,121 +0,0 @@ -name: One-shot PR 759 centralize coverage failure summary - -on: - push: - branches: - - fix/opencode-coverage-failure-diagnostics - paths: - - .github/workflows/one-shot-pr759-centralize-coverage-failure-summary.yml - -concurrency: - group: one-shot-pr759-centralize-coverage-failure-summary - cancel-in-progress: false - -permissions: - contents: read - -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true - -jobs: - repair: - if: >- - github.repository == 'ContextualWisdomLab/.github' && - github.actor == 'seonghobae' && - github.ref == 'refs/heads/fix/opencode-coverage-failure-diagnostics' - runs-on: ubuntu-latest - timeout-minutes: 15 - permissions: - contents: write - steps: - - name: Harden runner - uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 - with: - egress-policy: audit - - - name: Checkout exact trigger - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - ref: ${{ github.sha }} - fetch-depth: 1 - persist-credentials: false - - - name: Set up Python - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 - with: - python-version: '3.14' - - - name: Apply bounded central-summary repair - shell: bash --noprofile --norc -e -o pipefail {0} - run: | - test "$(git rev-parse HEAD)" = "$GITHUB_SHA" - python3 -I - <<'PY' - from pathlib import Path - - files = ( - Path("scripts/ci/materialize_base_javascript_packages.py"), - Path("scripts/ci/materialize_base_python_requirements.py"), - ) - for path in files: - content = path.read_text(encoding="utf-8") - if "def _publish_coverage_failure_summary(" not in content: - raise SystemExit(f"missing local publisher in {path}") - content = content.replace("import html\n", "", 1) - content = content.replace("import os\n", "", 1) - import_anchor = "from typing import Any\n" if "javascript" in path.name else "import tempfile\n" - shared_import = ( - f"{import_anchor}\n" - "if __package__ in (None, \"\"):\n" - " sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2]))\n\n" - "from scripts.ci.coverage_failure_summary import (\n" - " publish_coverage_failure_summary,\n" - ")\n" - ) - if content.count(import_anchor) != 1: - raise SystemExit(f"unexpected import anchor count in {path}") - content = content.replace(import_anchor, shared_import, 1) - start = content.index("\ndef _publish_coverage_failure_summary(") - end = content.index("\ndef main(", start) - content = content[:start] + "\n" + content[end:] - content = content.replace( - "_publish_coverage_failure_summary(", - "publish_coverage_failure_summary(", - ) - path.write_text(content, encoding="utf-8") - - Path( - ".github/workflows/one-shot-pr759-centralize-coverage-failure-summary.yml" - ).unlink() - PY - python -m pip install --disable-pip-version-check --require-hashes \ - -r requirements-opencode-review-ci-hashes.txt - python -m pytest \ - tests/test_materialize_base_javascript_packages.py \ - tests/test_materialize_base_python_requirements.py \ - tests/test_coverage_materializer_failure_diagnostics.py \ - tests/test_sanitize_github_output_summary.py \ - tests/test_strix_dependency_security_floor.py \ - --cov=scripts.ci.coverage_failure_summary \ - --cov=scripts.ci.materialize_base_javascript_packages \ - --cov=scripts.ci.materialize_base_python_requirements \ - --cov=scripts.ci.sanitize_github_output_summary \ - --cov-branch \ - --cov-fail-under=100 \ - -q - python -m compileall -q scripts/ci - git diff --check - - - name: Commit verified repair and remove one-shot workflow - shell: bash --noprofile --norc -e -o pipefail {0} - env: - PUSH_TOKEN: ${{ github.token }} - run: | - test "$(git rev-parse HEAD)" = "$GITHUB_SHA" - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add -A - git commit -m "fix(opencode-review): centralize safe coverage failure evidence" - auth_header="$(printf 'x-access-token:%s' "$PUSH_TOKEN" | base64 | tr -d '\n')" - echo "::add-mask::$auth_header" - git -c http.extraheader="AUTHORIZATION: basic ${auth_header}" \ - push origin "HEAD:${GITHUB_REF_NAME}" From 206ab44f0b04a3852dd6f35f20b6acf6004e7b6e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 14:57:35 +0900 Subject: [PATCH 46/57] test(opencode-review): stage canonical diagnostics repair --- scripts/ci/apply_pr759_shared_diagnostics.py | 209 +++++++++++++++++++ 1 file changed, 209 insertions(+) create mode 100644 scripts/ci/apply_pr759_shared_diagnostics.py diff --git a/scripts/ci/apply_pr759_shared_diagnostics.py b/scripts/ci/apply_pr759_shared_diagnostics.py new file mode 100644 index 000000000..14f0ccbfd --- /dev/null +++ b/scripts/ci/apply_pr759_shared_diagnostics.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 +"""Apply the exact reviewed PR 759 diagnostics repair, then remove this helper.""" + +from __future__ import annotations + +import ast +from pathlib import Path + + +def replace_once(path: str, old: str, new: str) -> None: + """Replace one exact source fragment and fail when the branch has drifted.""" + file_path = Path(path) + text = file_path.read_text(encoding="utf-8") + count = text.count(old) + if count != 1: + raise SystemExit( + f"{path}: expected one repair anchor, found {count}: {old[:120]!r}" + ) + file_path.write_text(text.replace(old, new, 1), encoding="utf-8") + + +def remove_top_level_function(path: str, function_name: str) -> None: + """Remove one top-level Python function and its trailing blank lines.""" + file_path = Path(path) + text = file_path.read_text(encoding="utf-8") + tree = ast.parse(text, filename=path) + matches = [ + node + for node in tree.body + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) + and node.name == function_name + ] + if len(matches) != 1: + raise SystemExit( + f"{path}: expected one top-level {function_name}, found {len(matches)}" + ) + node = matches[0] + lines = text.splitlines(keepends=True) + start = node.lineno - 1 + end = node.end_lineno + while end < len(lines) and not lines[end].strip(): + end += 1 + file_path.write_text("".join(lines[:start] + lines[end:]), encoding="utf-8") + + +def align_codeql_pins(path: str) -> None: + """Align CodeQL init and analyze to the reviewed immutable v4.37.5 commit.""" + file_path = Path(path) + text = file_path.read_text(encoding="utf-8") + target = "d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5" + for old in ( + "99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0", + "f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4", + ): + text = text.replace(old, target) + if text.count(target) != 2: + raise SystemExit(f"{path}: CodeQL init/analyze pins are not exactly aligned") + file_path.write_text(text, encoding="utf-8") + + +def main() -> int: + """Apply production, test, standards, and cleanup changes atomically.""" + shared_import = '''sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2])) +from scripts.ci.coverage_failure_summary import ( + publish_coverage_failure_summary as _publish_coverage_failure_summary, +) +''' + + javascript_path = "scripts/ci/materialize_base_javascript_packages.py" + replace_once( + javascript_path, + "import sys\nimport urllib.parse\nfrom typing import Any\n\n\nSHA_RE", + "import sys\nimport urllib.parse\nfrom typing import Any\n\n\n" + + shared_import + + "\nSHA_RE", + ) + remove_top_level_function(javascript_path, "_publish_coverage_failure_summary") + + python_path = "scripts/ci/materialize_base_python_requirements.py" + replace_once( + python_path, + "import sys\nimport tempfile\n\n\nSHA_RE", + "import sys\nimport tempfile\n\n\n" + + shared_import + + "\nSHA_RE", + ) + remove_top_level_function(python_path, "_publish_coverage_failure_summary") + + helper_path = "scripts/ci/coverage_failure_summary.py" + replace_once( + helper_path, + '''import html +import os +import runpy +from collections.abc import Callable +from pathlib import Path +from typing import cast + +_COVERAGE_DELIMITER = "CWL_COVERAGE_SUMMARY_EOF" +_SANITIZER_NAMESPACE = runpy.run_path( + str(Path(__file__).with_name("sanitize_github_output_summary.py")) +) +_SANITIZE_TEXT = cast(Callable[[str], str], _SANITIZER_NAMESPACE["sanitize_text"]) +''', + '''import html +import os +from pathlib import Path + +from scripts.ci.sanitize_github_output_summary import sanitize_text + +_COVERAGE_DELIMITER = "CWL_COVERAGE_SUMMARY_EOF" +''', + ) + replace_once( + helper_path, + "redacted = _SANITIZE_TEXT(normalized)", + "redacted = sanitize_text(normalized)", + ) + + sanitizer_path = "scripts/ci/sanitize_github_output_summary.py" + replace_once( + sanitizer_path, + '''if __name__ == "__main__": + raise SystemExit(main()) +''', + '''def _entrypoint(module_name: str) -> None: + """Run the file-oriented CLI only when executed as a script.""" + if module_name == "__main__": + raise SystemExit(main()) + + +_entrypoint(__name__) +''', + ) + + sanitizer_test = "tests/test_sanitize_github_output_summary.py" + replace_once(sanitizer_test, "import runpy\nimport sys\n", "import sys\n") + replace_once( + sanitizer_test, + "from scripts.ci.sanitize_github_output_summary import sanitize_text", + "from scripts.ci.sanitize_github_output_summary import _entrypoint, sanitize_text", + ) + replace_once( + sanitizer_test, + ''' with pytest.raises(SystemExit) as excinfo: + runpy.run_path("scripts/ci/sanitize_github_output_summary.py", run_name="__main__") +''', + ''' with pytest.raises(SystemExit) as excinfo: + _entrypoint("__main__") +''', + ) + + replace_once( + "scripts/ci/strix_quick_gate.sh", + "uv.lock | */uv.lock)", + "uv.lock | */uv.lock | Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock)", + ) + strix_test = "scripts/ci/test_strix_quick_gate.sh" + assertion = '\tassert_file_contains "$GATE_SCRIPT" "Dockerfile | */Dockerfile | Dockerfile.* | */Dockerfile.* | Containerfile | */Containerfile | Makefile | */Makefile" "strix gate treats deployment files as source files"\n' + replace_once( + strix_test, + assertion, + assertion + + '\tassert_file_contains "$GATE_SCRIPT" "Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock" "strix gate includes Rust crate dependency and feature context"\n', + ) + + align_codeql_pins(".github/workflows/codeql-pr.yml") + align_codeql_pins(".github/workflows/scheduled-security-scan.yml") + + Path("docs/doctoring/coverage-failure-diagnostics.md").write_text( + '''# Credential-redacted coverage failure diagnostics + +## Decision + +Coverage setup failures are review evidence, but exception text is untrusted and may contain registry URL userinfo, authorization headers, API tokens, database connection strings, passwords, or encryption keys. JavaScript and Python trusted-lock materializers therefore delegate multiline `GITHUB_OUTPUT` publication to one shared helper. The helper normalizes whitespace, applies the central credential sanitizer, bounds each field, HTML-escapes Markdown evidence, and replaces the fixed multiline delimiter before publication. + +The sanitizer applies URL-userinfo and authorization-header redaction before key-value truncation so mixed single-line failures cannot preserve an earlier credential. The result retains the failure class, stage, bounded non-secret context, and remediation without exposing raw credentials. Local CLI status remains nonzero when publication is unavailable. + +## Verification contract + +The exact-head gate requires Python 3.10 compilation, Python 3.14 tests, 100% production statement and branch coverage, 100% production docstrings, and direct execution of the sanitizer entrypoint contract. Regression cases cover mixed URL, bearer, and token secrets; delimiter injection; oversized errors; missing `GITHUB_OUTPUT`; and both materializer paths. Temporary write-capable repair automation is absent from the final tree. + +## Standards and guidance + +GitHub environment files define delimiter-based multiline outputs and warn that a delimiter must not occur alone within arbitrary values. OWASP logging guidance recommends removing, masking, sanitizing, hashing, or encrypting access tokens, passwords, database connection strings, encryption keys, session identifiers, and sensitive personal data rather than recording them directly. RFC 3986 deprecates secret passwords in URI userinfo because URIs are commonly displayed, stored, and logged. + +## Limitations + +Pattern-based redaction is defense in depth, not a general secret classifier. Callers must not intentionally place secrets in exception messages. GitHub log masking and least-privilege workflow permissions remain required. The helper does not make untrusted output safe for shell evaluation or workflow-command execution. + +## References + +Berners-Lee, T., Fielding, R., & Masinter, L. (2005). *Uniform resource identifier (URI): Generic syntax* (RFC 3986). Internet Engineering Task Force. https://doi.org/10.17487/RFC3986 + +GitHub. (2026). *Workflow commands for GitHub Actions*. GitHub Docs. Retrieved August 5, 2026, from https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands + +OWASP Foundation. (n.d.). *Logging cheat sheet*. OWASP Cheat Sheet Series. Retrieved August 5, 2026, from https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html +''', + encoding="utf-8", + ) + + Path(".github/workflows/repair-pr759-shared-diagnostics.yml").unlink(missing_ok=True) + Path(".github/workflows/canonicalize-opencode-diagnostics.yml").unlink() + Path(__file__).unlink() + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From c60811f6e81c91108ec0d4d297dafd121b7a6419 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 14:59:26 +0900 Subject: [PATCH 47/57] chore(ci): remove completed PR 759 patch helper --- scripts/ci/apply_pr759_shared_diagnostics.py | 209 ------------------- 1 file changed, 209 deletions(-) delete mode 100644 scripts/ci/apply_pr759_shared_diagnostics.py diff --git a/scripts/ci/apply_pr759_shared_diagnostics.py b/scripts/ci/apply_pr759_shared_diagnostics.py deleted file mode 100644 index 14f0ccbfd..000000000 --- a/scripts/ci/apply_pr759_shared_diagnostics.py +++ /dev/null @@ -1,209 +0,0 @@ -#!/usr/bin/env python3 -"""Apply the exact reviewed PR 759 diagnostics repair, then remove this helper.""" - -from __future__ import annotations - -import ast -from pathlib import Path - - -def replace_once(path: str, old: str, new: str) -> None: - """Replace one exact source fragment and fail when the branch has drifted.""" - file_path = Path(path) - text = file_path.read_text(encoding="utf-8") - count = text.count(old) - if count != 1: - raise SystemExit( - f"{path}: expected one repair anchor, found {count}: {old[:120]!r}" - ) - file_path.write_text(text.replace(old, new, 1), encoding="utf-8") - - -def remove_top_level_function(path: str, function_name: str) -> None: - """Remove one top-level Python function and its trailing blank lines.""" - file_path = Path(path) - text = file_path.read_text(encoding="utf-8") - tree = ast.parse(text, filename=path) - matches = [ - node - for node in tree.body - if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) - and node.name == function_name - ] - if len(matches) != 1: - raise SystemExit( - f"{path}: expected one top-level {function_name}, found {len(matches)}" - ) - node = matches[0] - lines = text.splitlines(keepends=True) - start = node.lineno - 1 - end = node.end_lineno - while end < len(lines) and not lines[end].strip(): - end += 1 - file_path.write_text("".join(lines[:start] + lines[end:]), encoding="utf-8") - - -def align_codeql_pins(path: str) -> None: - """Align CodeQL init and analyze to the reviewed immutable v4.37.5 commit.""" - file_path = Path(path) - text = file_path.read_text(encoding="utf-8") - target = "d1ba80a13dd99fba24a470575428917156a28b43 # v4.37.5" - for old in ( - "99df26d4f13ea111d4ec1a7dddef6063f76b97e9 # v4.37.0", - "f205ea1c3313d32999d8d6a48b4f6530d4437b38 # v4.37.4", - ): - text = text.replace(old, target) - if text.count(target) != 2: - raise SystemExit(f"{path}: CodeQL init/analyze pins are not exactly aligned") - file_path.write_text(text, encoding="utf-8") - - -def main() -> int: - """Apply production, test, standards, and cleanup changes atomically.""" - shared_import = '''sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[2])) -from scripts.ci.coverage_failure_summary import ( - publish_coverage_failure_summary as _publish_coverage_failure_summary, -) -''' - - javascript_path = "scripts/ci/materialize_base_javascript_packages.py" - replace_once( - javascript_path, - "import sys\nimport urllib.parse\nfrom typing import Any\n\n\nSHA_RE", - "import sys\nimport urllib.parse\nfrom typing import Any\n\n\n" - + shared_import - + "\nSHA_RE", - ) - remove_top_level_function(javascript_path, "_publish_coverage_failure_summary") - - python_path = "scripts/ci/materialize_base_python_requirements.py" - replace_once( - python_path, - "import sys\nimport tempfile\n\n\nSHA_RE", - "import sys\nimport tempfile\n\n\n" - + shared_import - + "\nSHA_RE", - ) - remove_top_level_function(python_path, "_publish_coverage_failure_summary") - - helper_path = "scripts/ci/coverage_failure_summary.py" - replace_once( - helper_path, - '''import html -import os -import runpy -from collections.abc import Callable -from pathlib import Path -from typing import cast - -_COVERAGE_DELIMITER = "CWL_COVERAGE_SUMMARY_EOF" -_SANITIZER_NAMESPACE = runpy.run_path( - str(Path(__file__).with_name("sanitize_github_output_summary.py")) -) -_SANITIZE_TEXT = cast(Callable[[str], str], _SANITIZER_NAMESPACE["sanitize_text"]) -''', - '''import html -import os -from pathlib import Path - -from scripts.ci.sanitize_github_output_summary import sanitize_text - -_COVERAGE_DELIMITER = "CWL_COVERAGE_SUMMARY_EOF" -''', - ) - replace_once( - helper_path, - "redacted = _SANITIZE_TEXT(normalized)", - "redacted = sanitize_text(normalized)", - ) - - sanitizer_path = "scripts/ci/sanitize_github_output_summary.py" - replace_once( - sanitizer_path, - '''if __name__ == "__main__": - raise SystemExit(main()) -''', - '''def _entrypoint(module_name: str) -> None: - """Run the file-oriented CLI only when executed as a script.""" - if module_name == "__main__": - raise SystemExit(main()) - - -_entrypoint(__name__) -''', - ) - - sanitizer_test = "tests/test_sanitize_github_output_summary.py" - replace_once(sanitizer_test, "import runpy\nimport sys\n", "import sys\n") - replace_once( - sanitizer_test, - "from scripts.ci.sanitize_github_output_summary import sanitize_text", - "from scripts.ci.sanitize_github_output_summary import _entrypoint, sanitize_text", - ) - replace_once( - sanitizer_test, - ''' with pytest.raises(SystemExit) as excinfo: - runpy.run_path("scripts/ci/sanitize_github_output_summary.py", run_name="__main__") -''', - ''' with pytest.raises(SystemExit) as excinfo: - _entrypoint("__main__") -''', - ) - - replace_once( - "scripts/ci/strix_quick_gate.sh", - "uv.lock | */uv.lock)", - "uv.lock | */uv.lock | Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock)", - ) - strix_test = "scripts/ci/test_strix_quick_gate.sh" - assertion = '\tassert_file_contains "$GATE_SCRIPT" "Dockerfile | */Dockerfile | Dockerfile.* | */Dockerfile.* | Containerfile | */Containerfile | Makefile | */Makefile" "strix gate treats deployment files as source files"\n' - replace_once( - strix_test, - assertion, - assertion - + '\tassert_file_contains "$GATE_SCRIPT" "Cargo.toml | */Cargo.toml | Cargo.lock | */Cargo.lock" "strix gate includes Rust crate dependency and feature context"\n', - ) - - align_codeql_pins(".github/workflows/codeql-pr.yml") - align_codeql_pins(".github/workflows/scheduled-security-scan.yml") - - Path("docs/doctoring/coverage-failure-diagnostics.md").write_text( - '''# Credential-redacted coverage failure diagnostics - -## Decision - -Coverage setup failures are review evidence, but exception text is untrusted and may contain registry URL userinfo, authorization headers, API tokens, database connection strings, passwords, or encryption keys. JavaScript and Python trusted-lock materializers therefore delegate multiline `GITHUB_OUTPUT` publication to one shared helper. The helper normalizes whitespace, applies the central credential sanitizer, bounds each field, HTML-escapes Markdown evidence, and replaces the fixed multiline delimiter before publication. - -The sanitizer applies URL-userinfo and authorization-header redaction before key-value truncation so mixed single-line failures cannot preserve an earlier credential. The result retains the failure class, stage, bounded non-secret context, and remediation without exposing raw credentials. Local CLI status remains nonzero when publication is unavailable. - -## Verification contract - -The exact-head gate requires Python 3.10 compilation, Python 3.14 tests, 100% production statement and branch coverage, 100% production docstrings, and direct execution of the sanitizer entrypoint contract. Regression cases cover mixed URL, bearer, and token secrets; delimiter injection; oversized errors; missing `GITHUB_OUTPUT`; and both materializer paths. Temporary write-capable repair automation is absent from the final tree. - -## Standards and guidance - -GitHub environment files define delimiter-based multiline outputs and warn that a delimiter must not occur alone within arbitrary values. OWASP logging guidance recommends removing, masking, sanitizing, hashing, or encrypting access tokens, passwords, database connection strings, encryption keys, session identifiers, and sensitive personal data rather than recording them directly. RFC 3986 deprecates secret passwords in URI userinfo because URIs are commonly displayed, stored, and logged. - -## Limitations - -Pattern-based redaction is defense in depth, not a general secret classifier. Callers must not intentionally place secrets in exception messages. GitHub log masking and least-privilege workflow permissions remain required. The helper does not make untrusted output safe for shell evaluation or workflow-command execution. - -## References - -Berners-Lee, T., Fielding, R., & Masinter, L. (2005). *Uniform resource identifier (URI): Generic syntax* (RFC 3986). Internet Engineering Task Force. https://doi.org/10.17487/RFC3986 - -GitHub. (2026). *Workflow commands for GitHub Actions*. GitHub Docs. Retrieved August 5, 2026, from https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands - -OWASP Foundation. (n.d.). *Logging cheat sheet*. OWASP Cheat Sheet Series. Retrieved August 5, 2026, from https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html -''', - encoding="utf-8", - ) - - Path(".github/workflows/repair-pr759-shared-diagnostics.yml").unlink(missing_ok=True) - Path(".github/workflows/canonicalize-opencode-diagnostics.yml").unlink() - Path(__file__).unlink() - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) From 61202975479c036169de2837ccdb47f3587ed16e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 16:17:12 +0900 Subject: [PATCH 48/57] test(coverage): expose username-only URL userinfo leak --- tests/test_sanitize_github_output_summary.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/test_sanitize_github_output_summary.py b/tests/test_sanitize_github_output_summary.py index 6a3e95a2a..973b3879c 100644 --- a/tests/test_sanitize_github_output_summary.py +++ b/tests/test_sanitize_github_output_summary.py @@ -35,6 +35,14 @@ def test_sanitizes_url_credentials_without_secret_key_prefix(): assert sanitized == "postgresql://@db:5432/app\n" +def test_sanitizes_url_userinfo_without_password(): + """A username-only URL authority cannot leak through coverage evidence.""" + sanitized = sanitize_text("https://alice@example.invalid/artifact\n") + + assert sanitized == "https://@example.invalid/artifact\n" + assert "alice" not in sanitized + + def test_sanitizes_mixed_credentials_before_truncating_at_secret_key(): """Mixed URL, Authorization, and key-value secrets are all removed.""" source = ( From b5a85970048b26630d5f81de7174d3b2b81e1811 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 16:18:00 +0900 Subject: [PATCH 49/57] fix(coverage): redact username-only URL userinfo --- scripts/ci/sanitize_github_output_summary.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/ci/sanitize_github_output_summary.py b/scripts/ci/sanitize_github_output_summary.py index 70d54c0ed..5cf762a72 100644 --- a/scripts/ci/sanitize_github_output_summary.py +++ b/scripts/ci/sanitize_github_output_summary.py @@ -15,7 +15,7 @@ r"API[_-]?KEY|PRIVATE[_-]?KEY|ACCESS[_-]?KEY|ENCRYPTION[_-]?KEY" r")[A-Z0-9_.-]*\b)(?P\s*[:=]\s*)" ) -URL_CREDENTIAL_RE = re.compile(r"(?i)\b([a-z][a-z0-9+.-]*://)([^/\s:@]+):([^@\s/]+)@") +URL_CREDENTIAL_RE = re.compile(r"(?i)\b([a-z][a-z0-9+.-]*://)([^/\s@]+)@") AUTH_HEADER_RE = re.compile(r"(?i)\b(Authorization\s*[:=]\s*)(Bearer|Basic)\s+[^\s,;]+") From e1d6e01591541103921f53fbe8df0a7566556ad9 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 17:05:55 +0900 Subject: [PATCH 50/57] fix(strix): freeze source-directory scan boundary --- scripts/ci/strix_model_utils.sh | 78 +++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/scripts/ci/strix_model_utils.sh b/scripts/ci/strix_model_utils.sh index 9f20eae67..2f60a5e4e 100755 --- a/scripts/ci/strix_model_utils.sh +++ b/scripts/ci/strix_model_utils.sh @@ -12,6 +12,84 @@ trim_whitespace() { printf '%s\n' "$value" } +sanitize_strix_source_dirs() { + local raw_source_dirs + raw_source_dirs="$(trim_whitespace "${1-}")" + if [ -z "$raw_source_dirs" ]; then + echo "ERROR: STRIX_SOURCE_DIRS must contain at least one safe direct directory name." >&2 + return 2 + fi + + python3 -I -S - "$raw_source_dirs" <<'PY' +from __future__ import annotations + +import sys +import unicodedata + +raw_source_dirs = sys.argv[1] +if len(raw_source_dirs.encode("utf-8")) > 8192 or any( + character in "\x00\r\n\t" for character in raw_source_dirs +): + print( + "ERROR: STRIX_SOURCE_DIRS must be a bounded space-separated directory list.", + file=sys.stderr, + ) + raise SystemExit(2) +entries = raw_source_dirs.split(" ") +entries = [entry for entry in entries if entry] +if not entries or len(entries) > 32: + print( + "ERROR: STRIX_SOURCE_DIRS must contain between 1 and 32 safe direct directory names.", + file=sys.stderr, + ) + raise SystemExit(2) + +allowed_ascii = frozenset("_.@+[]-") +normalized: list[str] = [] +seen: set[str] = set() +for entry in entries: + if entry == ".": + pass + elif ( + len(entry.encode("utf-8")) > 255 + or entry.startswith("-") + or "/" in entry + or "\\" in entry + or not all( + (character.isascii() and (character.isalnum() or character in allowed_ascii)) + or ( + not character.isascii() + and unicodedata.category(character)[0] in {"L", "M", "N"} + ) + for character in entry + ) + ): + print( + "ERROR: STRIX_SOURCE_DIRS accepts only '.' or safe direct directory names.", + file=sys.stderr, + ) + raise SystemExit(2) + if entry not in seen: + seen.add(entry) + normalized.append(entry) + +print(" ".join(normalized)) +PY +} + +# STRIX_SOURCE_DIRS is later split by the gate before joining each token to the +# already-canonical scan root. Freeze a lexical direct-child allowlist at source +# time so absolute paths, parent traversal, nested symlink chains, shell glob expansion, +# and option-like path ambiguity can never reach that join. +STRIX_SOURCE_DIRS_SANITIZED="$( + sanitize_strix_source_dirs "${STRIX_SOURCE_DIRS-.}" +)" || { + status=$? + return "$status" 2>/dev/null || exit "$status" +} +readonly STRIX_SOURCE_DIRS="$STRIX_SOURCE_DIRS_SANITIZED" +unset STRIX_SOURCE_DIRS_SANITIZED + sanitize_provider_name() { local provider provider="$(trim_whitespace "${1-}")" From c9ecbe1cdea201d7292960981241151c7a2e29f1 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 17:06:29 +0900 Subject: [PATCH 51/57] test(strix): reject source-directory traversal inputs --- tests/test_strix_model_utils_source_dirs.py | 93 +++++++++++++++++++++ 1 file changed, 93 insertions(+) create mode 100644 tests/test_strix_model_utils_source_dirs.py diff --git a/tests/test_strix_model_utils_source_dirs.py b/tests/test_strix_model_utils_source_dirs.py new file mode 100644 index 000000000..5b68bbd5f --- /dev/null +++ b/tests/test_strix_model_utils_source_dirs.py @@ -0,0 +1,93 @@ +"""Regression tests for Strix source-directory input boundaries.""" + +from __future__ import annotations + +import os +import subprocess +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +HELPER = ROOT / "scripts" / "ci" / "strix_model_utils.sh" + + +def run_source(raw_value: str) -> subprocess.CompletedProcess[str]: + """Source the helper with one caller-controlled directory-list value.""" + + environment = os.environ.copy() + environment["STRIX_SOURCE_DIRS"] = raw_value + return subprocess.run( + [ + "bash", + "-c", + f'source "{HELPER}" || exit $?; printf "%s" "$STRIX_SOURCE_DIRS"', + ], + cwd=ROOT, + env=environment, + text=True, + capture_output=True, + check=False, + timeout=10, + ) + + +def test_direct_source_directories_are_normalized_and_readonly() -> None: + """Keep direct safe names, Unicode names, and first-occurrence order.""" + + completed = run_source(". src 데이터 backend src 데이터") + assert completed.returncode == 0, completed.stderr + assert completed.stdout == ". src 데이터 backend" + + reassignment = subprocess.run( + [ + "bash", + "-c", + ( + f'STRIX_SOURCE_DIRS="."; source "{HELPER}"; ' + 'STRIX_SOURCE_DIRS="../etc"' + ), + ], + cwd=ROOT, + text=True, + capture_output=True, + check=False, + timeout=10, + ) + assert reassignment.returncode != 0 + assert "readonly" in reassignment.stderr.lower() + + +def test_traversal_absolute_nested_glob_and_empty_values_fail_closed() -> None: + """Reject every path shape that can escape or broaden the scan root.""" + + unsafe_values = ( + "../etc", + "/etc", + "src/../etc", + "src/api", + "*", + "-rf", + " ", + "src\nbackend", + ) + for raw_value in unsafe_values: + completed = run_source(raw_value) + assert completed.returncode == 2, ( + raw_value, + completed.stdout, + completed.stderr, + ) + assert "STRIX_SOURCE_DIRS" in completed.stderr + + +def test_unsafe_punctuation_and_oversized_lists_fail_closed() -> None: + """Bound metacharacters, encoded size, and list cardinality.""" + + for raw_value in ("src;echo", "src$HOME", "src\\api", "src?"): + completed = run_source(raw_value) + assert completed.returncode == 2, raw_value + + oversized_entry = "a" * 256 + assert run_source(oversized_entry).returncode == 2 + + oversized_list = " ".join(f"dir{index}" for index in range(33)) + assert run_source(oversized_list).returncode == 2 From c500762c58e176f1ac5c5816a6db040b0395719e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 17:07:13 +0900 Subject: [PATCH 52/57] docs(strix): record source-directory trust boundary --- .../strix-source-directory-boundary.md | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 docs/doctoring/strix-source-directory-boundary.md diff --git a/docs/doctoring/strix-source-directory-boundary.md b/docs/doctoring/strix-source-directory-boundary.md new file mode 100644 index 000000000..738f05a4e --- /dev/null +++ b/docs/doctoring/strix-source-directory-boundary.md @@ -0,0 +1,48 @@ +# Strix source-directory boundary + +## Decision + +`STRIX_SOURCE_DIRS` is a scanner input boundary, not an arbitrary filesystem path list. The central Strix gate now accepts only `.` or direct child directory names whose characters are drawn from a known-good Unicode-aware allowlist. The normalized value is deduplicated, bounded to 32 entries and 8,192 input bytes, and frozen as a read-only shell variable before any path join occurs. + +Nested paths are intentionally not accepted. The gate already resolves and validates `STRIX_TARGET_PATH`; callers that need a nested scan root must select that root through the target-path contract and use `STRIX_SOURCE_DIRS=.`. This keeps one canonical trust boundary instead of composing two independently mutable path fragments. + +## Threat model + +Before this change, each whitespace-delimited `STRIX_SOURCE_DIRS` token was appended to the canonical target root. A caller-controlled absolute path could discard the intended root, while `..` components or nested symlink chains could resolve outside it. The subsequent recursive search could then read unrelated runner files and allow their content to influence a published Strix report. + +The protected boundary rejects: + +- absolute paths; +- `/` and `\\` separators; +- parent traversal and nested path components; +- shell glob and metacharacter input; +- option-like names beginning with `-`; +- control characters, tabs, and line breaks; +- overlong components, overlong lists, and excessive entry counts. + +Safe direct names remain internationalized: Unicode letters, combining marks, and numbers are accepted. The final candidate must still be a real non-symlink directory under the already-canonical scan target before recursive search begins. + +## Verification + +`tests/test_strix_model_utils_source_dirs.py` provides executable regressions for: + +- deterministic deduplication and order preservation; +- Korean direct-directory names; +- read-only post-validation state; +- relative and absolute traversal; +- nested paths and both path separators; +- glob, punctuation, option-like, control-character, size, and cardinality limits. + +The test was first executed against the prior helper and failed for traversal, absolute, nested, glob, punctuation, and duplicate inputs. It passes after the source-boundary contract is installed. The helper is also parsed with `bash -n`, and the Python regression module is compiled before publication. + +## Security properties and limits + +The change follows an accept-known-good strategy instead of attempting to remove dangerous substrings. It also avoids returning the rejected value in error messages. This prevents the common failure mode where filtering one traversal representation leaves another representation or where diagnostics disclose useful filesystem details. + +This control does not make arbitrary scanner output trustworthy. Strix findings remain untrusted data, provider failures remain fail-closed, PR-head materialization remains bounded to validated Git objects, and privileged workflow publication continues to require exact-head checks and repository protection. + +## References + +MITRE. (2026, April 30). *CWE-22: Improper limitation of a pathname to a restricted directory ('path traversal')* (Version 4.20). Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/22.html + +OWASP Foundation. (n.d.). *Path traversal*. Retrieved August 5, 2026, from https://owasp.org/www-community/attacks/Path_Traversal From 76ac1cdff667d904a64249af9e75c9b84087f57f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 17:16:50 +0900 Subject: [PATCH 53/57] test(strix): reproduce standalone parent traversal --- tests/test_strix_model_utils_source_dirs.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_strix_model_utils_source_dirs.py b/tests/test_strix_model_utils_source_dirs.py index 5b68bbd5f..0267a0817 100644 --- a/tests/test_strix_model_utils_source_dirs.py +++ b/tests/test_strix_model_utils_source_dirs.py @@ -60,6 +60,7 @@ def test_traversal_absolute_nested_glob_and_empty_values_fail_closed() -> None: """Reject every path shape that can escape or broaden the scan root.""" unsafe_values = ( + "..", "../etc", "/etc", "src/../etc", From 4d076f636b6de5043e8501e93c06ed0a8c896eb3 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 17:19:01 +0900 Subject: [PATCH 54/57] fix(strix): reject standalone parent traversal --- scripts/ci/strix_model_utils.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/ci/strix_model_utils.sh b/scripts/ci/strix_model_utils.sh index 2f60a5e4e..80541c7c2 100755 --- a/scripts/ci/strix_model_utils.sh +++ b/scripts/ci/strix_model_utils.sh @@ -51,7 +51,8 @@ for entry in entries: if entry == ".": pass elif ( - len(entry.encode("utf-8")) > 255 + entry == ".." + or len(entry.encode("utf-8")) > 255 or entry.startswith("-") or "/" in entry or "\\" in entry From 71a695de38c4de402f5de5c06bbceb152155ed62 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 18:08:44 +0900 Subject: [PATCH 55/57] test(automation): require hourly NVIDIA NIM review repair Add failing contracts for the one-hour scheduler cadence, immutable called-workflow source, NVIDIA_NIM_API_KEY-only OpenCode repair transport, write-credential stripping, fail-closed secret handling, and unchanged independent reviewer workflow. --- ...t_pr_review_autofix_nvidia_nim_contract.py | 151 ++++++++++++++++++ tests/test_pr_review_fix_hourly_contract.py | 45 ++++++ ...test_pr_review_fix_scheduler_source_pin.py | 77 +++++++++ 3 files changed, 273 insertions(+) create mode 100644 tests/test_pr_review_autofix_nvidia_nim_contract.py create mode 100644 tests/test_pr_review_fix_hourly_contract.py create mode 100644 tests/test_pr_review_fix_scheduler_source_pin.py diff --git a/tests/test_pr_review_autofix_nvidia_nim_contract.py b/tests/test_pr_review_autofix_nvidia_nim_contract.py new file mode 100644 index 000000000..362b90c17 --- /dev/null +++ b/tests/test_pr_review_autofix_nvidia_nim_contract.py @@ -0,0 +1,151 @@ +"""Contract tests for the scheduled OpenCode review-autofix trust boundary.""" + +from pathlib import Path +import subprocess + + +AUTOFIX_WORKFLOW = Path(".github/workflows/pr-review-autofix.yml") +FIX_SCHEDULER_WORKFLOW = Path(".github/workflows/pr-review-fix-scheduler.yml") +REVIEW_DISPATCH_WORKFLOW = Path(".github/workflows/opencode-review-dispatch.yml") +REVIEW_DISPATCH_BLOB_SHA = "41748bcecf9870a8bea10085d2682133082015ab" + + +def _workflow_text(path: Path) -> str: + """Read one central workflow as UTF-8 text for static trust-boundary checks.""" + return path.read_text(encoding="utf-8") + + +def test_review_fix_scheduler_runs_once_each_hour() -> None: + """Keep the actionable-review repair loop on the approved hourly cadence.""" + scheduler = _workflow_text(FIX_SCHEDULER_WORKFLOW) + assert 'cron: "23 * * * *"' in scheduler + assert 'cron: "23 */2 * * *"' not in scheduler + + +def test_scheduled_autofix_uses_only_nvidia_nim() -> None: + """Require the write-capable OpenCode autofix agent to use NVIDIA NIM only.""" + workflow = _workflow_text(AUTOFIX_WORKFLOW) + required_fragments = ( + '"model": "nvidia-nim/mistralai/mistral-nemotron"', + '"small_model": "nvidia-nim/nvidia/nemotron-3-nano-30b-a3b"', + '"enabled_providers": ["nvidia-nim"]', + '"nvidia-nim": {', + '"npm": "@ai-sdk/openai-compatible"', + '"baseURL": "https://integrate.api.nvidia.com/v1"', + '"apiKey": "{env:NVIDIA_API_KEY}"', + 'NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}', + 'MODEL: nvidia-nim/mistralai/mistral-nemotron', + ) + for fragment in required_fragments: + assert fragment in workflow, fragment + forbidden_fragments = ( + 'STRIX_GITHUB_MODELS_TOKEN:', + 'MODEL: github-models/', + 'USE_GITHUB_TOKEN:', + '"enabled_providers": ["github-models"]', + '"apiKey": "{env:STRIX_GITHUB_MODELS_TOKEN}"', + '"baseURL": "https://models.github.ai/inference"', + 'COPILOT_GITHUB_TOKEN', + ) + for fragment in forbidden_fragments: + assert fragment not in workflow, fragment + + +def test_trusted_autofix_source_is_bound_to_dispatch_sha() -> None: + """Prevent a moving default branch from replacing trusted autofix scripts.""" + workflow = _workflow_text(AUTOFIX_WORKFLOW) + checkout_start = workflow.index(" - name: Checkout trusted autofix source") + checkout_end = workflow.index( + " - name: Exchange OpenCode app token", checkout_start + ) + checkout = workflow[checkout_start:checkout_end] + assert "ref: ${{ github.sha }}" in checkout + assert "ref: main" not in checkout + assert "fetch-depth: 1" in checkout + assert "persist-credentials: false" in checkout + + +def test_opencode_agent_denies_non_file_interactions() -> None: + """Keep unattended repair bounded to local file inspection and edits.""" + workflow = _workflow_text(AUTOFIX_WORKFLOW) + for permission_name in ( + "bash", + "task", + "skill", + "question", + "webfetch", + "websearch", + "lsp", + "external_directory", + "doom_loop", + ): + assert workflow.count(f'"{permission_name}": "deny"') == 2 + + +def test_nvidia_nim_secret_is_scoped_to_agent_execution_steps() -> None: + """Prevent the NVIDIA credential from leaking beyond the two OpenCode runs.""" + workflow = _workflow_text(AUTOFIX_WORKFLOW) + binding = 'NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }}' + ordinary_start = workflow.index(" - name: Run OpenCode review autofix") + ordinary_end = workflow.index(" - name: Validate changed files", ordinary_start) + conflict_start = workflow.index( + " - name: Merge base branch and resolve conflicts with OpenCode" + ) + assert workflow.count(binding) == 2 + assert binding in workflow[ordinary_start:ordinary_end] + assert binding in workflow[conflict_start:] + assert binding not in workflow[:ordinary_start] + assert binding not in workflow[ordinary_end:conflict_start] + + +def test_model_subprocesses_receive_no_github_or_oidc_write_credentials() -> None: + """Strip GitHub write and OIDC credentials from both OpenCode processes.""" + workflow = _workflow_text(AUTOFIX_WORKFLOW) + ordinary_start = workflow.index(" - name: Run OpenCode review autofix") + ordinary_end = workflow.index(" - name: Validate changed files", ordinary_start) + ordinary = workflow[ordinary_start:ordinary_end] + conflict_start = workflow.index( + " - name: Merge base branch and resolve conflicts with OpenCode" + ) + conflict = workflow[conflict_start:] + sanitized_invocation = ( + "env -u GITHUB_TOKEN -u GH_TOKEN " + "-u ACTIONS_ID_TOKEN_REQUEST_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_URL" + ) + assert "GITHUB_TOKEN:" not in ordinary + assert "GH_TOKEN:" not in ordinary + assert sanitized_invocation in ordinary + assert sanitized_invocation in conflict + assert workflow.count(sanitized_invocation) == 2 + + +def test_missing_nvidia_nim_secret_fails_closed_before_model_execution() -> None: + """Reject an empty model credential instead of falling back to another provider.""" + workflow = _workflow_text(AUTOFIX_WORKFLOW) + guard = ( + 'if [ -z "${NVIDIA_API_KEY:-}" ]; then\n' + ' echo "::error::NVIDIA_NIM_API_KEY is required for scheduled ' + 'OpenCode autofix."\n' + " exit 1\n" + " fi" + ) + ordinary_start = workflow.index(" - name: Run OpenCode review autofix") + ordinary_end = workflow.index(" - name: Validate changed files", ordinary_start) + conflict_start = workflow.index( + " - name: Merge base branch and resolve conflicts with OpenCode" + ) + assert workflow.count(guard) == 2 + assert guard in workflow[ordinary_start:ordinary_end] + assert guard in workflow[conflict_start:] + + +def test_independent_review_agent_key_system_is_unchanged() -> None: + """Pin the existing read-only reviewer workflow byte-for-byte.""" + result = subprocess.run( + ["git", "hash-object", str(REVIEW_DISPATCH_WORKFLOW)], + check=True, + capture_output=True, + text=True, + ) + assert result.stdout.strip() == REVIEW_DISPATCH_BLOB_SHA + assert "pr-review-autofix" not in _workflow_text(REVIEW_DISPATCH_WORKFLOW) diff --git a/tests/test_pr_review_fix_hourly_contract.py b/tests/test_pr_review_fix_hourly_contract.py new file mode 100644 index 000000000..f55408fc8 --- /dev/null +++ b/tests/test_pr_review_fix_hourly_contract.py @@ -0,0 +1,45 @@ +"""Static contract for the central hourly PR review-fix scheduler.""" + +from __future__ import annotations + +from pathlib import Path + + +_WORKFLOW = Path(".github/workflows/pr-review-fix-scheduler.yml") + + +def _workflow_text() -> str: + """Return the canonical scheduler workflow text.""" + return _WORKFLOW.read_text(encoding="utf-8") + + +def test_review_fix_scheduler_runs_once_each_hour() -> None: + """The bounded repair dispatcher uses the requested hourly heartbeat.""" + text = _workflow_text() + + assert 'cron: "23 * * * *"' in text + assert 'cron: "23 */2 * * *"' not in text + + +def test_review_fix_scheduler_retries_same_head_after_one_hour() -> None: + """A blocked head can be retried on the next hourly cycle, not a day later.""" + text = _workflow_text() + + retry_block = text.split("retry_hours:", maxsplit=1)[1].split( + "autofix_workflow:", maxsplit=1 + )[0] + assert 'default: "1"' in retry_block + assert "inputs.retry_hours || '1'" in text + assert "inputs.retry_hours || '24'" not in text + + +def test_review_fix_scheduler_remains_bounded_and_single_flight() -> None: + """Higher cadence never expands mutation volume or parallel execution.""" + text = _workflow_text() + + dispatch_block = text.split("max_dispatches:", maxsplit=1)[1].split( + "target_repository:", maxsplit=1 + )[0] + assert 'default: "1"' in dispatch_block + assert "cancel-in-progress: true" in text + assert "MAX_DISPATCHES" in text diff --git a/tests/test_pr_review_fix_scheduler_source_pin.py b/tests/test_pr_review_fix_scheduler_source_pin.py new file mode 100644 index 000000000..bb5a6bbc5 --- /dev/null +++ b/tests/test_pr_review_fix_scheduler_source_pin.py @@ -0,0 +1,77 @@ +"""Supply-chain contract for the reusable PR-review autofix scheduler.""" + +from __future__ import annotations + +from pathlib import Path + + +_REPO_ROOT = Path(__file__).resolve().parents[1] +_WORKFLOW = _REPO_ROOT / ".github" / "workflows" / "pr-review-fix-scheduler.yml" + + +def _workflow_text() -> str: + """Read the reusable scheduler workflow as UTF-8 text.""" + return _WORKFLOW.read_text(encoding="utf-8") + + +def test_reusable_scheduler_validates_called_workflow_identity_before_checkout() -> None: + """Missing workflow identity must fail before checkout can use defaults.""" + workflow = _workflow_text() + guard = workflow.index("Resolve immutable called-workflow source") + checkout = workflow.index("Checkout immutable called-workflow source") + + assert guard < checkout + assert "WORKFLOW_REPOSITORY: ${{ job.workflow_repository }}" in workflow + assert "WORKFLOW_SHA: ${{ job.workflow_sha }}" in workflow + assert "WORKFLOW_REF: ${{ job.workflow_ref }}" in workflow + assert "WORKFLOW_FILE_PATH: ${{ job.workflow_file_path }}" in workflow + assert 'expected_repository="ContextualWisdomLab/.github"' in workflow + assert 'expected_file=".github/workflows/pr-review-fix-scheduler.yml"' in workflow + assert '[[ "$WORKFLOW_SHA" =~ ^[0-9a-f]{40}$ ]]' in workflow + assert "repository: ${{ steps.trusted_source.outputs.repository }}" in workflow + assert "ref: ${{ steps.trusted_source.outputs.sha }}" in workflow + + +def test_reusable_scheduler_verifies_checked_out_called_workflow_sha() -> None: + """The checked-out commit must equal the validated called-workflow SHA.""" + workflow = _workflow_text() + verification = workflow.index("Verify immutable called-workflow checkout") + self_test = workflow.index("Self-test fix scheduler contract") + + assert verification < self_test + assert 'actual_sha="$(git rev-parse HEAD)"' in workflow + assert '[ "$actual_sha" != "$EXPECTED_SHA" ]' in workflow + assert '[ ! -f "$EXPECTED_FILE" ] || [ -L "$EXPECTED_FILE" ]' in workflow + + +def test_reusable_scheduler_source_is_not_caller_input_controlled() -> None: + """No caller-supplied ref or ordinary caller GitHub SHA selects trusted code.""" + workflow = _workflow_text() + assert "inputs.canonical_ref" not in workflow + assert "github.event.client_payload.canonical_ref" not in workflow + assert "ref: ${{ env.CANONICAL_REF }}" not in workflow + assert "ref: ${{ github.sha }}" not in workflow + assert "ref: ${{ github.workflow_sha }}" not in workflow + + +def test_deprecated_canonical_ref_input_is_accepted_but_never_consumed() -> None: + """Existing callers can upgrade pins without controlling privileged source.""" + workflow = _workflow_text() + declaration = workflow.split("canonical_ref:", 1)[1].split( + "repository_dispatch:", 1 + )[0] + + assert "Deprecated compatibility input" in declaration + assert "ignored" in declaration + assert 'default: ""' in declaration + assert workflow.count("canonical_ref") == 1 + + +def test_reusable_scheduler_retains_least_privilege_and_bounded_dispatch() -> None: + """Source pinning does not broaden token scope or queue fan-out.""" + workflow = _workflow_text() + assert "contents: write" not in workflow + assert "pull-requests: write" not in workflow + assert "MAX_DISPATCHES:" in workflow + assert "RETRY_HOURS:" in workflow + assert "cancel-in-progress: true" in workflow From 71d1fc599538715a71c5eb2423e97cc7d566f01f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 18:10:56 +0900 Subject: [PATCH 56/57] ci(automation): execute the hourly NIM repair contract Add a permanent read-only exact-head workflow that runs the hourly cadence, immutable scheduler source, NVIDIA_NIM_API_KEY, credential-stripping, fail-closed, and independent-review separation contracts. The current baseline is expected to fail before implementation. --- .../hourly-nvidia-nim-review-repair.yml | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 .github/workflows/hourly-nvidia-nim-review-repair.yml diff --git a/.github/workflows/hourly-nvidia-nim-review-repair.yml b/.github/workflows/hourly-nvidia-nim-review-repair.yml new file mode 100644 index 000000000..1490f8bba --- /dev/null +++ b/.github/workflows/hourly-nvidia-nim-review-repair.yml @@ -0,0 +1,66 @@ +name: Hourly NVIDIA NIM Review Repair + +on: + pull_request: + paths: + - .github/workflows/pr-review-fix-scheduler.yml + - .github/workflows/pr-review-autofix.yml + - .github/workflows/hourly-nvidia-nim-review-repair.yml + - tests/test_pr_review_fix_hourly_contract.py + - tests/test_pr_review_fix_scheduler_source_pin.py + - tests/test_pr_review_autofix_nvidia_nim_contract.py + - docs/automation/hourly-review-repair.md + - docs/doctoring/hourly-nvidia-nim-autofix.md + push: + paths: + - .github/workflows/pr-review-fix-scheduler.yml + - .github/workflows/pr-review-autofix.yml + - .github/workflows/hourly-nvidia-nim-review-repair.yml + - tests/test_pr_review_fix_hourly_contract.py + - tests/test_pr_review_fix_scheduler_source_pin.py + - tests/test_pr_review_autofix_nvidia_nim_contract.py + - docs/automation/hourly-review-repair.md + - docs/doctoring/hourly-nvidia-nim-autofix.md + +permissions: + contents: read + +concurrency: + group: hourly-nvidia-nim-review-repair-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + contract: + name: Hourly cadence, immutable source, and NIM credential boundary + runs-on: ubuntu-24.04 + timeout-minutes: 20 + steps: + - name: Harden runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + - name: Checkout exact source revision + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ github.event.pull_request.head.sha || github.sha }} + persist-credentials: false + - name: Set up Python + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 + with: + python-version: "3.12" + - name: Install hash-locked test tooling + run: >- + python -m pip install --disable-pip-version-check --require-hashes + -r requirements-opencode-review-ci-hashes.txt + - name: Verify hourly scheduler and NVIDIA NIM autofix contracts + run: | + set -euo pipefail + python -m pytest -q \ + tests/test_pr_review_fix_hourly_contract.py \ + tests/test_pr_review_fix_scheduler_source_pin.py \ + tests/test_pr_review_autofix_nvidia_nim_contract.py + python -m compileall -q \ + tests/test_pr_review_fix_hourly_contract.py \ + tests/test_pr_review_fix_scheduler_source_pin.py \ + tests/test_pr_review_autofix_nvidia_nim_contract.py + git diff --check From 1d70f782f81612a69dfacff9a39aa192c16c6b2f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Wed, 5 Aug 2026 18:15:46 +0900 Subject: [PATCH 57/57] fix(automation): run hourly review repair through NVIDIA NIM Set the central scheduler to an hourly one-dispatch loop with a one-hour retry floor, bind reusable and dispatch worker source to immutable workflow SHAs, move write-capable OpenCode repair to NVIDIA_NIM_API_KEY only, strip GitHub/OIDC credentials from model subprocesses, and retain the independent reviewer workflow unchanged. --- .github/workflows/pr-review-autofix.yml | 74 +++--- .github/workflows/pr-review-fix-scheduler.yml | 90 ++++++- CHANGELOG.md | 20 ++ docs/automation/hourly-review-repair.md | 72 ++++++ docs/doctoring/hourly-nvidia-nim-autofix.md | 240 ++++++++++++++++++ 5 files changed, 452 insertions(+), 44 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 docs/automation/hourly-review-repair.md create mode 100644 docs/doctoring/hourly-nvidia-nim-autofix.md diff --git a/.github/workflows/pr-review-autofix.yml b/.github/workflows/pr-review-autofix.yml index e5475be1b..cc0611eef 100644 --- a/.github/workflows/pr-review-autofix.yml +++ b/.github/workflows/pr-review-autofix.yml @@ -42,6 +42,7 @@ jobs: uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: repository: ContextualWisdomLab/.github + ref: ${{ github.sha }} fetch-depth: 1 persist-credentials: false path: trusted-autofix-source @@ -231,9 +232,9 @@ jobs: EOF jq -n --arg workspace "$TARGET_WORKSPACE" '{ "$schema": "https://opencode.ai/config.json", - "model": "github-models/openai/gpt-5", - "small_model": "github-models/deepseek/deepseek-v3-0324", - "enabled_providers": ["github-models"], + "model": "nvidia-nim/mistralai/mistral-nemotron", + "small_model": "nvidia-nim/nvidia/nemotron-3-nano-30b-a3b", + "enabled_providers": ["nvidia-nim"], "permission": { "edit": "allow", "bash": "deny", @@ -242,10 +243,13 @@ jobs: "glob": "allow", "list": "allow", "task": "deny", + "skill": "deny", + "question": "deny", "webfetch": "deny", "websearch": "deny", "lsp": "deny", - "external_directory": "deny" + "external_directory": "deny", + "doom_loop": "deny" }, "agent": { "ci-autofix": { @@ -261,45 +265,40 @@ jobs: "glob": "allow", "list": "allow", "task": "deny", + "skill": "deny", + "question": "deny", "webfetch": "deny", "websearch": "deny", "lsp": "deny", - "external_directory": "deny" + "external_directory": "deny", + "doom_loop": "deny" } } }, "provider": { - "github-models": { + "nvidia-nim": { "npm": "@ai-sdk/openai-compatible", - "name": "GitHub Models", + "name": "NVIDIA NIM", "options": { - "baseURL": "https://models.github.ai/inference", - "apiKey": "{env:STRIX_GITHUB_MODELS_TOKEN}" + "baseURL": "https://integrate.api.nvidia.com/v1", + "apiKey": "{env:NVIDIA_API_KEY}" }, "models": { - "openai/gpt-5": { - "name": "OpenAI GPT-5", + "mistralai/mistral-nemotron": { + "name": "Mistral Nemotron", "tool_call": true, - "reasoning": true, - "options": { - "reasoningEffort": "high" - }, - "variants": { - "high": { - "reasoningEffort": "high" - } - }, "limit": { - "context": 200000, - "output": 100000 + "context": 128000, + "output": 4096 } }, - "deepseek/deepseek-v3-0324": { - "name": "DeepSeek V3 0324", + "nvidia/nemotron-3-nano-30b-a3b": { + "name": "Nemotron 3 Nano 30B A3B", "tool_call": true, + "reasoning": true, "limit": { "context": 128000, - "output": 4096 + "output": 32768 } } } @@ -310,16 +309,18 @@ jobs: - name: Run OpenCode review autofix if: env.RESOLVE_CONFLICT != 'true' env: - STRIX_GITHUB_MODELS_TOKEN: ${{ secrets.STRIX_GITHUB_MODELS_TOKEN || github.token }} - GITHUB_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token || github.token }} - MODEL: github-models/openai/gpt-5 - USE_GITHUB_TOKEN: "true" + NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} + MODEL: nvidia-nim/mistralai/mistral-nemotron SHARE: "false" NPM_CONFIG_IGNORE_SCRIPTS: "true" NO_COLOR: "1" OPENCODE_AUTOFIX_WORKDIR: ${{ runner.temp }}/opencode-autofix-project run: | set -euo pipefail + if [ -z "${NVIDIA_API_KEY:-}" ]; then + echo "::error::NVIDIA_NIM_API_KEY is required for scheduled OpenCode autofix." + exit 1 + fi prompt_file="${RUNNER_TEMP}/opencode-autofix-prompt.md" allowed_paths_context="$( awk ' @@ -374,7 +375,8 @@ jobs: } trap restore_workspace_config EXIT cd "$TARGET_WORKSPACE" - timeout 18000 opencode run "$(cat "$prompt_file")" \ + env -u GITHUB_TOKEN -u GH_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_URL \ + timeout 18000 opencode run "$(cat "$prompt_file")" \ --pure \ --agent ci-autofix \ --model "$MODEL" \ @@ -446,17 +448,20 @@ jobs: - name: Merge base branch and resolve conflicts with OpenCode if: env.RESOLVE_CONFLICT == 'true' env: - STRIX_GITHUB_MODELS_TOKEN: ${{ secrets.STRIX_GITHUB_MODELS_TOKEN || github.token }} + NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} GITHUB_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token || github.token }} GH_TOKEN: ${{ secrets.PR_REVIEW_MERGE_TOKEN || secrets.OPENCODE_APPROVE_TOKEN || steps.target_app_token.outputs.token || github.token }} - MODEL: github-models/openai/gpt-5 - USE_GITHUB_TOKEN: "true" + MODEL: nvidia-nim/mistralai/mistral-nemotron SHARE: "false" NPM_CONFIG_IGNORE_SCRIPTS: "true" NO_COLOR: "1" OPENCODE_AUTOFIX_WORKDIR: ${{ runner.temp }}/opencode-autofix-project run: | set -euo pipefail + if [ -z "${NVIDIA_API_KEY:-}" ]; then + echo "::error::NVIDIA_NIM_API_KEY is required for scheduled OpenCode autofix." + exit 1 + fi cd "$TARGET_WORKSPACE" # Merge the base branch into the detached head. A clean merge stays @@ -516,7 +521,8 @@ jobs: fi } trap restore_workspace_config EXIT - timeout 18000 opencode run "$(cat "$prompt_file")" \ + env -u GITHUB_TOKEN -u GH_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_URL \ + timeout 18000 opencode run "$(cat "$prompt_file")" \ --pure \ --agent ci-autofix \ --model "$MODEL" \ diff --git a/.github/workflows/pr-review-fix-scheduler.yml b/.github/workflows/pr-review-fix-scheduler.yml index cc7875bc8..7bc09c378 100644 --- a/.github/workflows/pr-review-fix-scheduler.yml +++ b/.github/workflows/pr-review-fix-scheduler.yml @@ -26,7 +26,7 @@ on: retry_hours: description: Minimum hours before redispatching autofix for the same head required: false - default: "24" + default: "1" type: string autofix_workflow: description: Autofix workflow file to dispatch @@ -44,14 +44,16 @@ on: default: "" type: string canonical_ref: - description: Ref of ContextualWisdomLab/.github to use for scheduler code + description: Deprecated compatibility input; accepted and ignored because privileged source is bound to the called workflow SHA required: false - default: "main" + default: "" type: string repository_dispatch: types: [pr-review-fix-scheduler] schedule: - - cron: "23 */2 * * *" + # Run away from minute zero, where scheduled GitHub Actions are more likely + # to be delayed, while preserving a bounded one-dispatch-per-run repair loop. + - cron: "23 * * * *" concurrency: group: central-pr-review-fix-scheduler-${{ github.event.client_payload.target_repository || inputs.target_repository || vars.PR_REVIEW_FIX_TARGET_REPOSITORY || github.repository }} @@ -80,19 +82,87 @@ jobs: DRY_RUN: ${{ github.event.client_payload.dry_run == true || github.event.client_payload.dry_run == 'true' || inputs.dry_run == true }} MAX_PRS: ${{ github.event.client_payload.max_prs || inputs.max_prs || '50' }} MAX_DISPATCHES: ${{ github.event.client_payload.max_dispatches || inputs.max_dispatches || '1' }} - RETRY_HOURS: ${{ github.event.client_payload.retry_hours || inputs.retry_hours || '24' }} + RETRY_HOURS: ${{ github.event.client_payload.retry_hours || inputs.retry_hours || '1' }} AUTOFIX_WORKFLOW: pr-review-autofix.yml AUTOFIX_REPOSITORY: ContextualWisdomLab/.github - CANONICAL_REF: main steps: - - name: Checkout canonical scheduler - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + - name: Resolve immutable called-workflow source + id: trusted_source + env: + WORKFLOW_REPOSITORY: ${{ job.workflow_repository }} + WORKFLOW_SHA: ${{ job.workflow_sha }} + WORKFLOW_REF: ${{ job.workflow_ref }} + WORKFLOW_FILE_PATH: ${{ job.workflow_file_path }} + run: | + set -euo pipefail + expected_repository="ContextualWisdomLab/.github" + expected_file=".github/workflows/pr-review-fix-scheduler.yml" + + if [ "$WORKFLOW_REPOSITORY" != "$expected_repository" ]; then + printf '::error::Called workflow repository resolved to %s, expected %s.\n' \ + "${WORKFLOW_REPOSITORY:-}" "$expected_repository" + exit 1 + fi + if ! [[ "$WORKFLOW_SHA" =~ ^[0-9a-f]{40}$ ]]; then + printf '::error::Called workflow SHA is missing or malformed: %s.\n' \ + "${WORKFLOW_SHA:-}" + exit 1 + fi + if [ "$WORKFLOW_FILE_PATH" != "$expected_file" ]; then + printf '::error::Called workflow file resolved to %s, expected %s.\n' \ + "${WORKFLOW_FILE_PATH:-}" "$expected_file" + exit 1 + fi + expected_ref_prefix="${WORKFLOW_REPOSITORY}/${WORKFLOW_FILE_PATH}@" + case "$WORKFLOW_REF" in + "$expected_ref_prefix"*) ;; + *) + printf '::error::Called workflow ref is missing or inconsistent: %s.\n' \ + "${WORKFLOW_REF:-}" + exit 1 + ;; + esac + + { + printf 'repository=%s\n' "$WORKFLOW_REPOSITORY" + printf 'sha=%s\n' "$WORKFLOW_SHA" + printf 'workflow_ref=%s\n' "$WORKFLOW_REF" + printf 'workflow_file_path=%s\n' "$WORKFLOW_FILE_PATH" + } >>"$GITHUB_OUTPUT" + printf 'Resolved immutable called-workflow source repository=%s file=%s sha=%s ref=%s.\n' \ + "$WORKFLOW_REPOSITORY" "$WORKFLOW_FILE_PATH" "$WORKFLOW_SHA" "$WORKFLOW_REF" + + - name: Checkout immutable called-workflow source + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: - repository: ContextualWisdomLab/.github - ref: ${{ env.CANONICAL_REF }} + # GitHub documents job.workflow_repository and job.workflow_sha as + # the called workflow identity. The preceding step validates every + # field before checkout so an absent property cannot select defaults. + repository: ${{ steps.trusted_source.outputs.repository }} + ref: ${{ steps.trusted_source.outputs.sha }} fetch-depth: 1 persist-credentials: false + - name: Verify immutable called-workflow checkout + env: + EXPECTED_SHA: ${{ steps.trusted_source.outputs.sha }} + EXPECTED_FILE: ${{ steps.trusted_source.outputs.workflow_file_path }} + run: | + set -euo pipefail + actual_sha="$(git rev-parse HEAD)" + if [ "$actual_sha" != "$EXPECTED_SHA" ]; then + printf '::error::Checked-out scheduler SHA %s does not match called-workflow SHA %s.\n' \ + "$actual_sha" "$EXPECTED_SHA" + exit 1 + fi + if [ ! -f "$EXPECTED_FILE" ] || [ -L "$EXPECTED_FILE" ]; then + printf '::error::Called workflow source file is missing or symlinked: %s.\n' \ + "$EXPECTED_FILE" + exit 1 + fi + printf 'Verified immutable scheduler checkout at %s (%s).\n' \ + "$actual_sha" "$EXPECTED_FILE" + - name: Self-test fix scheduler contract run: python3 scripts/ci/pr_review_fix_scheduler.py --self-test diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..d405e9f68 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,20 @@ +# Changelog + +All notable changes to the ContextualWisdomLab central GitHub control plane are documented in this file. + +The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versioned releases follow [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Security + +- Move the write-capable scheduled OpenCode PR repair worker from GitHub Models to NVIDIA NIM using only the organization secret `NVIDIA_NIM_API_KEY`, while leaving the independent read-only reviewer workflow and its credential system byte-for-byte unchanged. +- Strip GitHub and OIDC credentials from both OpenCode model subprocesses, pin trusted autofix source to the exact repository-dispatch workflow SHA, deny non-file agent interactions, and fail closed when the NVIDIA credential is absent. + +### Changed + +- Run the bounded central PR review-repair scheduler at minute 23 of every hour, use a one-hour same-head retry floor, retain a one-dispatch budget and repository-scoped single flight, and bind reusable scheduler implementation to `job.workflow_repository` and `job.workflow_sha` rather than mutable branches or caller-controlled refs. + +### Documentation + +- Add operator guidance and APA 7 doctoring for the hourly scheduler, immutable workflow source, NVIDIA provider and credential boundary, OpenCode sandbox, modular CWL MSA ownership, verification, and rollback requirements. diff --git a/docs/automation/hourly-review-repair.md b/docs/automation/hourly-review-repair.md new file mode 100644 index 000000000..1924dba5b --- /dev/null +++ b/docs/automation/hourly-review-repair.md @@ -0,0 +1,72 @@ +# Hourly PR review-repair scheduler + +The central `PR Review Fix Scheduler` provides a bounded organization-wide +review → fix → revalidate → merge support loop. It runs at minute 23 of every +hour and may dispatch at most one existing autofix workflow per run. Merge +eligibility remains owned by the separate merge scheduler, branch protection, +required checks, independent review, and unresolved-thread policy. + +## Execution and compatibility contract + +- The scheduled heartbeat is `23 * * * *`. +- The default same-head retry floor is one hour. +- `max_dispatches` remains one by default. +- Repository-scoped concurrency and `cancel-in-progress: true` prevent two + superseded scheduler runs from mutating the same repository concurrently. +- `canonical_ref` remains an accepted deprecated input only so callers pinned to + older workflow interfaces can upgrade without a coordinated breaking change. + It is never read and cannot choose executable scheduler code. + +## Immutable reusable-workflow source + +GitHub associates the ordinary `github` context in a reusable workflow with the +caller. Consequently, a called privileged workflow must not use caller-derived +`github.sha`, a caller payload, or a mutable branch such as `main` to select its +co-located implementation. + +The checkout step instead uses: + +```yaml +repository: ${{ job.workflow_repository }} +ref: ${{ job.workflow_sha }} +``` + +`job.workflow_repository` identifies the repository that contains the called +workflow and `job.workflow_sha` identifies its immutable resolved commit. This +keeps the scheduler implementation aligned with the exact workflow revision +selected by the caller's `uses: ...@` reference. Checkout credentials are +not persisted. + +## Security and MSA boundary + +The scheduler can inspect review state and dispatch the already-reviewed bounded +autofix workflow. It cannot approve its own changes, lower branch protection, +convert queued checks to success, publish releases, or bypass independent +review. Product repositories remain independently operable and consume the +central policy as a reusable module rather than copying privileged automation. + +CWL repositories and naruon retain their own product tests, authorization, +release, deployment, data-governance, and runtime responsibilities. The central +workflow owns only organization-level queue inspection and bounded repair +dispatch. + +## Verification + +Dependency-free static tests pin the hourly cron, one-hour retry default, +one-dispatch budget, single-flight concurrency, immutable called-workflow +checkout, ignored compatibility input, and least-privilege token boundary. The +exact PR head must also pass all central security, coverage, workflow-contract, +and independent-review gates before merge. + +## References (APA 7th edition) + +GitHub. (n.d.). *Contexts reference: Job context*. GitHub Docs. Retrieved August +4, 2026, from +https://docs.github.com/en/actions/reference/workflows-and-actions/contexts#job-context + +GitHub. (n.d.). *Reusing workflow configurations*. GitHub Docs. Retrieved August +4, 2026, from +https://docs.github.com/en/actions/reference/workflows-and-actions/reusing-workflow-configurations + +GitHub. (n.d.). *Reusing workflows*. GitHub Docs. Retrieved August 4, 2026, from +https://docs.github.com/en/actions/how-tos/reuse-automations/reuse-workflows diff --git a/docs/doctoring/hourly-nvidia-nim-autofix.md b/docs/doctoring/hourly-nvidia-nim-autofix.md new file mode 100644 index 000000000..5806c766a --- /dev/null +++ b/docs/doctoring/hourly-nvidia-nim-autofix.md @@ -0,0 +1,240 @@ +# Hourly NVIDIA NIM Review-Autofix Boundary + +## Decision + +The write-capable scheduled pull-request autofix agent uses OpenCode with the +NVIDIA NIM API and the organization Actions secret `NVIDIA_NIM_API_KEY`. The +independent read-only review agent remains unchanged and continues to use its +existing credential and model-pool contract. + +This separation is intentional. Review and repair have different privileges: +the review path publishes a verdict, while the autofix path may modify and push +a same-repository pull-request branch. Sharing or silently replacing the review +credential would couple two independent controls and weaken incident +containment. + +## Central MSA ownership + +`ContextualWisdomLab/.github` owns the scheduler, dispatch authorization, +model-provider configuration, credential binding, immutable worker source, and +fail-closed repair contract. Leaf repositories receive the behavior through the +central reusable workflow and do not copy provider credentials or scheduler +implementation. + +The central scheduler established by the baseline repair runs once per hour, +dispatches at most one repair per invocation, and binds its scheduler +implementation to the immutable called-workflow source. The NVIDIA migration +changes only the model transport used by the write-capable autofix worker and +hardens that worker's own default-branch source checkout. + +## Immutable repository-dispatch worker source + +`PR Review Autofix` is a default-branch-only `repository_dispatch` workflow. +GitHub defines `GITHUB_SHA` for `repository_dispatch` as the last commit on the +default branch and runs only a workflow file present on that branch. The +workflow therefore checks out its co-located context builder and policy source +at the exact workflow-run commit: + +```yaml +repository: ContextualWisdomLab/.github +ref: ${{ github.sha }} +fetch-depth: 1 +persist-credentials: false +``` + +Without the explicit `ref`, `actions/checkout` would resolve the repository's +moving default branch at checkout time. A later default-branch push could then +replace trusted scripts after GitHub had already selected the workflow run, +creating a time-of-check/time-of-use gap around a job that receives OIDC and +branch-write capability. The explicit SHA keeps the executed helper source +aligned with the workflow revision selected for the dispatch. + +The client payload remains untrusted metadata. It can identify the intended +target PR only after the workflow re-reads live PR state and verifies exact base +and head refs and SHAs. + +## Provider contract + +The pinned OpenCode runtime is configured with one enabled provider, +`nvidia-nim`, using the OpenAI-compatible adapter and NVIDIA hosted endpoint: + +```text +https://integrate.api.nvidia.com/v1 +``` + +The primary repair model is `mistralai/mistral-nemotron`; the small model used +for bounded helper work is `nvidia/nemotron-3-nano-30b-a3b`. NVIDIA documents +both identifiers. Mistral-Nemotron supports tool calling for agentic workflows. +Nemotron 3 Nano is used as a lower-active-parameter reasoning helper, not as a +fallback provider. + +Only the `nvidia-nim` provider is enabled. GitHub Models configuration, model +identifiers, base URLs, and model-auth fallbacks are absent from the scheduled +autofix execution path. + +## Credential boundary + +The organization secret is bound as: + +```yaml +NVIDIA_API_KEY: ${{ secrets.NVIDIA_NIM_API_KEY }} +``` + +It is present only on the two steps that execute OpenCode: ordinary +review-feedback repair and merge-conflict repair. Earlier metadata collection, +checkout, context preparation, validation, commit, and push steps do not receive +the NVIDIA credential. + +The workflow passes the key through an environment variable and OpenCode +substitutes `{env:NVIDIA_API_KEY}` into provider configuration. The key is never +written to repository files, command arguments, generated prompts, or logs. A +missing secret is a fatal configuration error; the workflow does not fall back +to `GITHUB_TOKEN`, a GitHub Models token, or another provider. + +The ordinary repair step no longer binds a GitHub write token at step scope. The +conflict-repair shell retains GitHub credentials because the same shell must +re-read the live PR and push a verified merge result after model execution. In +both paths, the OpenCode child process is launched through: + +```text +env -u GITHUB_TOKEN -u GH_TOKEN \ + -u ACTIONS_ID_TOKEN_REQUEST_TOKEN -u ACTIONS_ID_TOKEN_REQUEST_URL +``` + +Consequently, model-controlled file operations receive the NVIDIA model +credential and non-secret execution controls, but cannot call GitHub APIs or +mint an OIDC token. GitHub credentials remain available only to reviewed shell +logic before or after the child process. This reduces the consequence of prompt +injection without removing the worker's independently validated branch-update +capability. + +GitHub documents that a missing secret expression resolves to an empty string +and recommends delivering secrets through inputs or environment variables rather +than embedding them in command lines. The explicit preflight prevents an +ambiguous unauthenticated provider request and preserves fail-closed behavior. + +## OpenCode repair sandbox + +OpenCode permissions are permissive unless explicitly restricted. The workflow +therefore denies every non-file interaction that is unnecessary for a bounded +review repair in both the global permission map and the named `ci-autofix` +agent: + +- `bash` +- `task` +- `skill` +- `question` +- `webfetch` +- `websearch` +- `lsp` +- `external_directory` +- `doom_loop` + +The agent may read, search, list, and edit only the validated same-repository PR +worktree. It receives an authoritative file allowlist derived from current +file-scoped actionable review context. The workflow rejects any changed path +outside that allowlist, syntax-checks changed Python, validates workflow files +when `actionlint` is available, rechecks the live head before push, and refuses +to publish unresolved merge markers. + +Explicitly denying `skill`, `question`, and `doom_loop` matters for unattended +execution. OpenCode exposes these as independent permissions; omitted +permissions are not implicitly denied. The worker must not load a broader skill, +pause for interactive approval, or repeat an identical tool action beyond the +bounded workflow contract. + +## GitHub write boundary + +The model transport change does not expand GitHub permissions. GitHub repository +credentials and the NVIDIA model credential remain separate. The existing +short-lived GitHub App/OIDC exchange and branch-write token chain are not used +for model authentication. Conversely, `NVIDIA_NIM_API_KEY` is not used for +GitHub reads or writes. + +Before editing, the workflow validates repository syntax, numeric PR identity, +forty-character base and head SHAs, same-repository branch ownership, open PR +state, and exact live base/head metadata. Before pushing, it re-reads the live +head and fails if the branch moved. The scheduler and worker cannot approve +their own changes, lower branch protection, convert queued checks into success, +or publish a release. + +## Independent review-agent boundary + +`.github/workflows/opencode-review-dispatch.yml` is not modified by this +migration. The regression contract pins that workflow's Git blob SHA +byte-for-byte rather than inferring independence from provider-name strings. +This allows the existing reviewer to retain its own evolving, separately +reviewed model-pool and credential design while proving that this autofix change +did not alter it. + +This is not cosmetic separation: review produces the verdict that gates merge, +whereas autofix proposes branch changes. Keeping their credentials, workflow +sources, and change histories independent limits the blast radius of either +path. + +## Verification contract + +Automated tests must prove all of the following: + +1. The repair scheduler retains the approved hourly cron expression. +2. The OpenCode configuration enables only `nvidia-nim`. +3. Primary and small model identifiers match NVIDIA's published identifiers. +4. The provider uses the OpenAI-compatible package, NVIDIA base URL, and + environment substitution. +5. Exactly two OpenCode execution steps receive `NVIDIA_API_KEY` from + `secrets.NVIDIA_NIM_API_KEY`. +6. GitHub Models credentials, providers, model identifiers, base URLs, and + `USE_GITHUB_TOKEN` model-auth fallback are absent from the autofix workflow. +7. The trusted autofix checkout is pinned to `${{ github.sha }}`, does not use + mutable `main`, and does not persist credentials. +8. Both OpenCode permission maps explicitly deny every non-file interaction + listed in the sandbox section. +9. Both OpenCode subprocesses explicitly remove GitHub and OIDC credentials; + the ordinary model step has no step-level GitHub token binding. +10. The independent review workflow retains its exact reviewed Git blob SHA and + contains no coupling to the autofix event. +11. A missing NVIDIA secret fails before either model process executes. +12. The exact current head passes complete workflow, Python, security, + CodeRabbit, independent-review, unresolved-thread, and branch-protection + gates before merge. + +## Scheduling and activation + +The NVIDIA worker does not create a second scheduler. It is consumed by the +hourly central review-fix scheduler established in the stacked baseline PR. The +hourly production loop becomes active only after both the baseline and this +migration are merged into the protected default branch. Draft or feature-branch +workflow files are not represented as active organization automation. + +## Rollback + +Rollback is a normal revert of the NVIDIA transport commit. A rollback must not +reintroduce an implicit GitHub-token model-auth fallback, GitHub or OIDC +credentials inside the model child process, a mutable trusted source checkout, +permissive unattended-agent tools, or any change to the independent review-agent +credential system. If NVIDIA NIM is unavailable, scheduled autofix must fail +closed while review, checks, and manual maintenance remain available. + +## References + +GitHub, Inc. (n.d.-a). *Events that trigger workflows*. GitHub Docs. Retrieved +August 4, 2026, from +https://docs.github.com/en/enterprise-cloud@latest/actions/reference/workflows-and-actions/events-that-trigger-workflows + +GitHub, Inc. (n.d.-b). *Secrets reference*. GitHub Docs. Retrieved August 4, +2026, from https://docs.github.com/en/actions/reference/security/secrets + +NVIDIA Corporation. (n.d.-a). *LLM APIs*. NVIDIA API Catalog. Retrieved August +4, 2026, from https://docs.api.nvidia.com/nim/reference/llm-apis + +NVIDIA Corporation. (n.d.-b). *Mistralai / mistral-nemotron*. NVIDIA API +Catalog. Retrieved August 4, 2026, from +https://docs.api.nvidia.com/nim/reference/mistralai-mistral-nemotron + +NVIDIA Corporation. (n.d.-c). *NVIDIA / nemotron-3-nano-30b-a3b*. NVIDIA API +Catalog. Retrieved August 4, 2026, from +https://docs.api.nvidia.com/nim/re/reference/nvidia-nemotron-3-nano-30b-a3b + +OpenCode. (2026a). *Permissions*. https://opencode.ai/docs/permissions + +OpenCode. (2026b, July 28). *Providers*. https://opencode.ai/docs/providers