From 37f24d21c661b4f1ec71e0290136c9da4a039f54 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 13 Oct 2026 22:42:25 +0900 Subject: [PATCH 1/4] ci: pin TEPP reusable workflow callers --- .github/workflows/ci.yml | 230 +----------------- .github/workflows/docs-quality.yml | 18 +- .../quality/test_reusable_supplier_callers.py | 134 ++++++++++ 3 files changed, 144 insertions(+), 238 deletions(-) create mode 100644 tests/quality/test_reusable_supplier_callers.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 18bc8defa..e10ad41b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,229 +38,9 @@ concurrency: group: tepp-rust-foundation-${{ github.repository }}-${{ github.event.pull_request.number || github.run_id }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} -env: - CARGO_TERM_COLOR: always - RUSTFLAGS: -Dwarnings - RUSTDOCFLAGS: -Dwarnings - CARGO_NEXTEST_VERSION: "0.9.140" - CARGO_DENY_VERSION: "0.19.7" - CARGO_LLVM_COV_VERSION: "0.8.6" - jobs: - repository-contracts: - name: Repository contracts and Python branch coverage - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Checkout exact head - uses: actions/checkout@631c942040754b6e095e929c1677c07e10ed4f87 - with: - persist-credentials: false - - name: Install pinned Python quality dependency - run: python3 -m pip install --disable-pip-version-check --no-deps --require-hashes -r requirements-quality.txt - - name: Exercise repository tooling with branch coverage - run: python3 -m coverage run --branch -m unittest discover -s tests/quality -p 'test_*.py' - - name: Enforce repository tooling coverage - run: python3 -m coverage report --fail-under=100 --show-missing - - name: Validate workspace contract - run: python3 scripts/check_workspace_contract.py - - name: Validate Rust documentation contract - run: python3 scripts/check_docstrings.py - - name: Generate and validate release SBOM/provenance evidence - run: | - python3 scripts/release_evidence.py generate \ - --repository-root . \ - --output-directory "${RUNNER_TEMP}/tepp-release-evidence" \ - --git-commit "${GITHUB_SHA}" - python3 scripts/release_evidence.py validate \ - --evidence-directory "${RUNNER_TEMP}/tepp-release-evidence" \ - --repository-root . \ - --expected-git-commit "${GITHUB_SHA}" - - rust-quality: - name: Format, lint, test, rustdoc, and dependency policy - runs-on: ubuntu-latest - timeout-minutes: 30 - steps: - - name: Checkout exact head - uses: actions/checkout@631c942040754b6e095e929c1677c07e10ed4f87 - with: - persist-credentials: false - - name: Install pinned Rust toolchain - run: rustup toolchain install 1.98.0 --profile minimal --component clippy --component rustfmt - - name: Restore pinned Rust quality tools - id: rust-tools-cache - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 - with: - path: | - ~/.cargo/bin/cargo-nextest - ~/.cargo/bin/cargo-deny - key: ${{ runner.os }}-${{ runner.arch }}-tepp-rust-tools-nextest-${{ env.CARGO_NEXTEST_VERSION }}-deny-${{ env.CARGO_DENY_VERSION }} - - name: Install pinned Rust quality tools - if: steps.rust-tools-cache.outputs.cache-hit != 'true' - run: | - cargo install cargo-nextest --locked --version "$CARGO_NEXTEST_VERSION" - cargo install cargo-deny --locked --version "$CARGO_DENY_VERSION" - - name: Verify pinned Rust quality tool versions - run: | - cargo nextest --version | grep -F "$CARGO_NEXTEST_VERSION" - cargo deny --version | grep -F "$CARGO_DENY_VERSION" - - name: Check formatting - run: cargo fmt --all -- --check - - name: Run Clippy - run: cargo clippy --workspace --all-targets --all-features -- -D warnings - - name: Run test suite without retries - run: cargo nextest run --workspace --all-features - - name: Run doctests separately - run: cargo test --doc --workspace --all-features - - name: Build warning-free documentation - run: cargo doc --workspace --all-features --no-deps - - name: Enforce dependency, license, advisory, and source policy - run: cargo deny check - - production-coverage: - name: Production line and branch coverage - runs-on: ubuntu-latest - timeout-minutes: 45 - steps: - - name: Checkout exact head - uses: actions/checkout@631c942040754b6e095e929c1677c07e10ed4f87 - with: - persist-credentials: false - - name: Install pinned Rust toolchain with LLVM tools - run: rustup toolchain install 1.98.0 --profile minimal --component llvm-tools-preview - - name: Install pinned nightly with LLVM tools - run: rustup toolchain install nightly-2026-08-21 --profile minimal --component llvm-tools-preview - - name: Restore pinned cargo-llvm-cov - id: llvm-cov-cache - uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 - with: - path: ~/.cargo/bin/cargo-llvm-cov - key: ${{ runner.os }}-${{ runner.arch }}-tepp-cargo-llvm-cov-${{ env.CARGO_LLVM_COV_VERSION }} - - name: Install cargo-llvm-cov - if: steps.llvm-cov-cache.outputs.cache-hit != 'true' - run: cargo install cargo-llvm-cov --locked --version "$CARGO_LLVM_COV_VERSION" - - name: Verify pinned cargo-llvm-cov version - run: cargo llvm-cov --version | grep -F "$CARGO_LLVM_COV_VERSION" - - name: Generate exact line coverage with region diagnostics - id: line-report - # mlx_native_receipt is excluded exactly like sqlx_live.rs: it is a - # macOS/MLX-only device receipt probe whose success path cannot - # execute on the Linux coverage runner (see ADR 0025). Its macOS - # behaviour is covered by the crate's cfg(macos) tests. - run: cargo llvm-cov --workspace --all-features --json --output-path coverage.json --ignore-filename-regex 'sqlx_live\.rs|mlx_native_receipt' - - name: Export exact authored line coverage - run: cargo llvm-cov report --lcov --output-path coverage.lcov --ignore-filename-regex 'sqlx_live\.rs|mlx_native_receipt' - - name: Enforce complete authored line coverage - run: python3 scripts/check_coverage.py coverage.lcov --kind lines --format lcov - - name: Show exact missing line diagnostics - if: ${{ failure() && steps.line-report.outcome == 'success' }} - run: | - LLVM_COV_FLAGS="--show-line-counts-or-regions" cargo llvm-cov report --text --show-missing-lines --show-instantiations - test -f coverage.lcov || cargo llvm-cov report --lcov --output-path coverage.lcov --ignore-filename-regex 'sqlx_live\.rs|mlx_native_receipt' - python3 - <<'PY' - import json - from pathlib import Path - - report = json.loads(Path("coverage.json").read_text(encoding="utf-8")) - for data in report.get("data", []): - for file_record in data.get("files", []): - filename = file_record.get("filename", "") - lines = file_record.get("summary", {}).get("lines", {}) - covered = int(lines.get("covered", 0)) - count = int(lines.get("count", 0)) - if covered < count: - print(f"INCOMPLETE_FILE {filename} {covered}/{count}") - for segment in file_record.get("segments", []): - if len(segment) < 6: - continue - line_number, column_number, execution_count = segment[:3] - has_count, is_region_entry, is_gap_region = segment[3:6] - if ( - has_count - and is_region_entry - and not is_gap_region - and int(execution_count) == 0 - ): - print( - "UNCOVERED_REGION " - f"{filename}:{line_number}:{column_number}" - ) - for function_record in data.get("functions", []): - if int(function_record.get("count", 0)) != 0: - continue - filenames = function_record.get("filenames", []) - filename = filenames[0] if filenames else "" - print( - "UNCOVERED_FUNCTION " - f"{filename} {function_record.get('name', '')}" - ) - - current_source = None - for raw_line in Path("coverage.lcov").read_text(encoding="utf-8").splitlines(): - if raw_line.startswith("SF:"): - current_source = raw_line[3:] - elif raw_line.startswith("DA:") and current_source is not None: - line_number, count, *_ = raw_line[3:].split(",") - if int(count) == 0: - print(f"UNCOVERED_LINE {current_source}:{line_number}") - PY - - name: Upload exact line coverage diagnostics - if: ${{ failure() && steps.line-report.outcome == 'success' }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: line-coverage-diagnostics - path: | - coverage.json - coverage.lcov - if-no-files-found: error - retention-days: 1 - - name: Generate exact branch coverage - id: branch-report - if: ${{ !cancelled() }} - run: cargo +nightly-2026-08-21 llvm-cov --branch --workspace --all-features --json --output-path coverage-branches.json --ignore-filename-regex 'sqlx_live\.rs|mlx_native_receipt' - - name: Enforce complete branch coverage - if: ${{ !cancelled() && steps.branch-report.outcome == 'success' }} - run: python3 scripts/check_coverage.py coverage-branches.json --kind branches - - name: Show exact missing branch diagnostics - if: ${{ failure() && steps.branch-report.outcome == 'success' }} - run: cargo +nightly-2026-08-21 llvm-cov report --branch --text --show-missing-lines - - name: Upload exact branch coverage diagnostics - if: ${{ failure() && steps.branch-report.outcome == 'success' }} - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 - with: - name: branch-coverage-diagnostics - path: coverage-branches.json - if-no-files-found: error - retention-days: 1 - - live-postgres: - name: Live PostgreSQL integration - runs-on: ubuntu-latest - timeout-minutes: 20 - services: - postgres: - image: postgres:16.9-alpine - env: - POSTGRES_USER: tepp - POSTGRES_PASSWORD: tepp_ci - POSTGRES_DB: tepp - ports: - - 5432:5432 - options: >- - --health-cmd "pg_isready -U tepp -d tepp" - --health-interval 5s - --health-timeout 5s - --health-retries 10 - env: - TEPP_LIVE_POSTGRES: "1" - DATABASE_URL: postgres://tepp:tepp_ci@localhost:5432/tepp - steps: - - name: Checkout exact head - uses: actions/checkout@631c942040754b6e095e929c1677c07e10ed4f87 - with: - persist-credentials: false - - name: Install pinned Rust toolchain - run: rustup toolchain install 1.98.0 --profile minimal - - name: Run live-sqlx PostgreSQL integration - run: cargo test -p persistence_postgres --features live-sqlx --test live_postgres -- --nocapture + foundation: + name: TEPP Rust Foundation supplier + uses: ContextualWisdomLab/.github/.github/workflows/tepp-rust-foundation-ci.yml@9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de + permissions: + contents: read diff --git a/.github/workflows/docs-quality.yml b/.github/workflows/docs-quality.yml index cf03a3ef3..7d9cc3ef4 100644 --- a/.github/workflows/docs-quality.yml +++ b/.github/workflows/docs-quality.yml @@ -32,16 +32,8 @@ concurrency: cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: - validate: - name: Validate planning and governance contracts - runs-on: ubuntu-latest - timeout-minutes: 10 - steps: - - name: Checkout exact head - uses: actions/checkout@631c942040754b6e095e929c1677c07e10ed4f87 - with: - persist-credentials: false - - name: Validate documentation - run: python3 scripts/validate_documentation.py - - name: Reject whitespace errors - run: git diff --check HEAD^ HEAD || git diff --check + documentation: + name: TEPP Documentation supplier + uses: ContextualWisdomLab/.github/.github/workflows/tepp-documentation-quality.yml@9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de + permissions: + contents: read diff --git a/tests/quality/test_reusable_supplier_callers.py b/tests/quality/test_reusable_supplier_callers.py new file mode 100644 index 000000000..e6a9888ed --- /dev/null +++ b/tests/quality/test_reusable_supplier_callers.py @@ -0,0 +1,134 @@ +"""Contract tests for the pinned TEPP reusable-workflow callers.""" + +from pathlib import Path +import re +import unittest + + +ROOT = Path(__file__).parents[2] +EXPECTED_SHA = "9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de" +EXPECTED_CALLERS = { + ".github/workflows/ci.yml": { + "job": "foundation", + "supplier": "tepp-rust-foundation-ci.yml", + "name": "TEPP Rust Foundation supplier", + "gate_ids": ( + "repository-contracts", + "rust-quality", + "production-coverage", + "live-postgres", + ), + "concurrency": ( + "tepp-rust-foundation-${{ github.repository }}-" + "${{ github.event.pull_request.number || github.run_id }}" + ), + }, + ".github/workflows/docs-quality.yml": { + "job": "documentation", + "supplier": "tepp-documentation-quality.yml", + "name": "TEPP Documentation supplier", + "gate_ids": ("validate",), + "concurrency": ( + "docs-quality-${{ github.repository }}-" + "${{ github.event.pull_request.number || github.run_id }}" + ), + }, +} + + +def _job_block(source: str, job_id: str) -> str: + """Return one top-level job block from the deliberately small caller YAML.""" + match = re.search( + rf"(?ms)^ {re.escape(job_id)}:\n(?P.*?)(?=^ [A-Za-z0-9_-]+:|\Z)", + source, + ) + assert match, f"missing caller job {job_id}" + return match.group("body") + + +def test_callers_pin_one_supplier_without_nested_execution() -> None: + """Each consumer job delegates once and cannot duplicate supplier steps.""" + seen_suppliers = [] + for relative, expected in EXPECTED_CALLERS.items(): + source = (ROOT / relative).read_text(encoding="utf-8") + block = _job_block(source, expected["job"]) + expected_use = ( + " uses: ContextualWisdomLab/.github/.github/workflows/" + f"{expected['supplier']}@{EXPECTED_SHA}" + ) + assert block.count(expected_use) == 1 + assert "runs-on:" not in block + assert "steps:" not in block + assert "secrets:" not in block + assert "with:" not in block + assert block.count("uses:") == 1 + assert f"name: {expected['name']}" in block + seen_suppliers.append(expected["supplier"]) + + assert len(seen_suppliers) == 2 + assert len(set(seen_suppliers)) == 2 + assert sum(len(item["gate_ids"]) for item in EXPECTED_CALLERS.values()) == 5 + + +def test_five_supplier_gates_are_coordinated_by_two_callers() -> None: + """The foundation and documentation suppliers account for five unique gates.""" + gate_ids = [ + gate_id + for expected in EXPECTED_CALLERS.values() + for gate_id in expected["gate_ids"] + ] + assert len(gate_ids) == 5 + assert len(set(gate_ids)) == 5 + assert set(gate_ids) == { + "repository-contracts", + "rust-quality", + "production-coverage", + "live-postgres", + "validate", + } + + +def test_original_trigger_concurrency_and_permissions_remain() -> None: + """Thin callers retain the original event, coalescing, and read-only contract.""" + for relative, expected in EXPECTED_CALLERS.items(): + source = (ROOT / relative).read_text(encoding="utf-8") + assert "on:\n pull_request:" in source + assert " push:\n branches:\n - main" in source + assert " workflow_dispatch:\n" in source + assert "permissions:\n contents: read" in source + assert f" group: {expected['concurrency']}" in source + assert " cancel-in-progress: ${{ github.event_name == 'pull_request' }}" in source + assert "secrets: inherit" not in source + + +def test_all_caller_references_are_immutable_and_unique() -> None: + """No floating ref or duplicate central invocation is admitted.""" + references = [] + for relative in EXPECTED_CALLERS: + source = (ROOT / relative).read_text(encoding="utf-8") + references.extend( + re.findall( + r"uses: ContextualWisdomLab/.github/.github/workflows/[^\s]+", + source, + ) + ) + assert len(references) == 2 + assert all(f"@{EXPECTED_SHA}" in reference for reference in references) + assert all("@main" not in reference for reference in references) + assert len(set(references)) == 2 + + +class ReusableSupplierCallerTests(unittest.TestCase): + """Run the caller contracts under the supplier's unittest gate.""" + + def test_callers_pin_one_supplier_without_nested_execution(self) -> None: + test_callers_pin_one_supplier_without_nested_execution() + + def test_five_supplier_gates_are_coordinated_by_two_callers(self) -> None: + test_five_supplier_gates_are_coordinated_by_two_callers() + + def test_original_trigger_concurrency_and_permissions_remain(self) -> None: + test_original_trigger_concurrency_and_permissions_remain() + + def test_all_caller_references_are_immutable_and_unique(self) -> None: + test_all_caller_references_are_immutable_and_unique() From 417ff6f1830ea8370b331595290d9cba0ac01636 Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 13 Oct 2026 23:04:18 +0900 Subject: [PATCH 2/4] test: validate pinned central CI supplier offline --- .coveragerc | 1 + CHANGELOG.md | 10 + README.md | 26 ++ scripts/check_workspace_contract.py | 9 + scripts/effective_ci_source.py | 59 +++++ .../.github.tree | Bin 0 -> 191 bytes .../commit.object | 29 +++ .../root.tree | Bin 0 -> 1987 bytes .../tepp-rust-foundation-ci.yml | 243 ++++++++++++++++++ .../workflows.tree | Bin 0 -> 2919 bytes tests/quality/test_ci_coverage_diagnostics.py | 10 +- tests/quality/test_effective_ci_source.py | 143 +++++++++++ tests/quality/test_workflow_queue_hygiene.py | 4 +- 13 files changed, 529 insertions(+), 5 deletions(-) create mode 100644 scripts/effective_ci_source.py create mode 100644 tests/fixtures/central-ci/9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de/.github.tree create mode 100644 tests/fixtures/central-ci/9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de/commit.object create mode 100644 tests/fixtures/central-ci/9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de/root.tree create mode 100644 tests/fixtures/central-ci/9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de/tepp-rust-foundation-ci.yml create mode 100644 tests/fixtures/central-ci/9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de/workflows.tree create mode 100644 tests/quality/test_effective_ci_source.py diff --git a/.coveragerc b/.coveragerc index 8f5ce471f..4d0faf081 100644 --- a/.coveragerc +++ b/.coveragerc @@ -3,6 +3,7 @@ branch = True relative_files = True source = scripts.check_workspace_contract + scripts.effective_ci_source scripts.check_docstrings scripts.check_coverage scripts.prepare_agent_pr_message diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b495291a..69d908bf6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -38,6 +38,16 @@ All notable changes to TEPP are documented here. The format follows Keep a Chang ## [Unreleased] +### Fixed + +- Resolve the effective Rust CI contract from the reviewed immutable central + supplier offline. Authenticate retained commit/tree/blob evidence and the + caller/workflow SHA-256 digests; preserve coverage diagnostics, pinned tools, + live PostgreSQL, and shared isolated-runner checks without duplicating jobs. + Include resolver corruption and gate-weakening controls in the unchanged + 100% Python statement/branch coverage gate. The separate inherited hourly + schedule contract is not waived. + - Removed the repository-local hourly PR-maintenance caller now covered by the central required scheduler, retired stale workflow registrations, narrowed documentation triggers, keyed PR concurrency by fixed workflow name, repository, and pull-request number without cancelling non-PR runs, and combined line/branch coverage on one sequential runner while preserving both 100% gates and diagnostics. - `event_core` adds bounded Allen interval-consistency classification, atomic path-consistency closure, contradiction/resource refusals, and an explicit dependency-error fallback without claiming unrestricted global satisfiability. diff --git a/README.md b/README.md index e0f6b3dd2..248cc9166 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,32 @@ an unstable compiler capability. A zero denominator is reported explicitly for any crate whose lane still ships no executable behavior; it must never conceal uncovered production behavior. +## Centralized CI contract source + +`.github/workflows/ci.yml` dispatches the reviewed central workflow +`ContextualWisdomLab/.github/.github/workflows/tepp-rust-foundation-ci.yml` +at `9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de`. Executable jobs remain owned by +that supplier; frozen files under `tests/fixtures/central-ci/` are evidence only, +not workflows installed in `.github/workflows/`. + +`scripts/effective_ci_source.py` resolves the caller and supplier offline for the +workspace, coverage diagnostics, and shared-runner contract checks. It verifies +the exact reviewed caller SHA-256, the original Git commit object and each tree +on the supplier path, the workflow blob identity, and its independent SHA-256. +Missing evidence, changed pins, disabled dispatch, extra caller jobs, or modified +supplier bytes fail closed. It needs neither Git metadata nor network access. +The original coverage, reproducible toolchain, PostgreSQL service/test, and +runner-isolation contracts remain enforced; tooling coverage includes the +resolver at the unchanged 100% statement/branch threshold. + +To update the supplier, review the new immutable central commit and executable +body, export its original commit/tree/blob objects, and update the reviewed pin +and digest constants together with positive and negative contract controls. +Never hand-author supplier fixtures, substitute a moving branch, or resolve from +live CI during static tests. These local source checks do not prove hosted jobs +ran successfully. The inherited hourly schedule contract is separate and is not +waived by this resolver. + ## Normative documents - `AGENTS.md` diff --git a/scripts/check_workspace_contract.py b/scripts/check_workspace_contract.py index f7aad6c82..a0bd5bc28 100644 --- a/scripts/check_workspace_contract.py +++ b/scripts/check_workspace_contract.py @@ -12,6 +12,11 @@ from pathlib import Path from typing import Any, Iterable, Mapping, Sequence +if __package__: + from .effective_ci_source import effective_ci_source +else: # pragma: no cover - direct CLI entry point + from effective_ci_source import effective_ci_source + EXPECTED_CRATES: tuple[str, ...] = ( "evidence_core", "semantic_core", @@ -234,6 +239,10 @@ def _validate_ci_contract(root: Path) -> list[str]: return [".github/workflows/ci.yml is missing"] ci_text = ci_path.read_text(encoding="utf-8") + try: + ci_text = effective_ci_source(root) + except (OSError, ValueError) as error: + errors.append(f"CI supplier source validation failed: {error}") for snippet in REQUIRED_CI_SNIPPETS: if snippet not in ci_text: errors.append(f"CI workflow is missing required command: {snippet}") diff --git a/scripts/effective_ci_source.py b/scripts/effective_ci_source.py new file mode 100644 index 000000000..9b1bb9bfd --- /dev/null +++ b/scripts/effective_ci_source.py @@ -0,0 +1,59 @@ +"""Resolve reviewed CI supplier bytes offline with immutable Git-object custody. + +Fixtures are non-executable evidence, not alternative GitHub Actions jobs. A pin +or caller change requires a reviewed fixture refresh, never a network fallback. +""" + +from __future__ import annotations + +import hashlib +from pathlib import Path + +SUPPLIER_PIN = "9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de" +CALLER_SHA256 = "4cdbacc6c531bba1fcb0632e9a4ea88ec9814820bb4e8c45447d57bc046201e3" +SUPPLIER_SHA256 = "0baf1384a6f66b357bba6cf74b384ecae447767f512eb2c90bb50849c74a7160" +FIXTURE_PATH = Path("tests/fixtures/central-ci") / SUPPLIER_PIN + + +def _verify_object(kind: str, data: bytes, expected: str) -> None: + """Require the original Git object identity, including type and byte size.""" + header = f"{kind} {len(data)}\0".encode("ascii") + if hashlib.sha1(header + data).hexdigest() != expected: + raise ValueError(f"CI supplier {kind} Git object mismatch") + + +def _tree_child(data: bytes, mode: str, name: str) -> str: + """Read an exact child from a previously authenticated raw Git tree.""" + marker = f"{mode} {name}\0".encode("ascii") + start = data.index(marker) + len(marker) + return data[start:start + 20].hex() + + +def effective_ci_source(root: Path) -> str: + """Return verified caller plus pinned supplier for static contract checks. + + Exact caller bytes bind dispatch, read-only permissions, concurrency, pin, + and the absence of competing local jobs. Every raw Git object is checked + from the caller's reviewed commit through the original tree path to blob; + SHA-256 additionally binds the workflow bytes. Missing or changed evidence + fails closed. No Git installation, repository metadata, or network is needed. + """ + caller = (root / ".github/workflows/ci.yml").read_bytes() + if hashlib.sha256(caller).hexdigest() != CALLER_SHA256: + raise ValueError("CI caller differs from reviewed immutable supplier contract") + fixture = root / FIXTURE_PATH + commit = (fixture / "commit.object").read_bytes() + _verify_object("commit", commit, SUPPLIER_PIN) + expected_tree = commit.split(b"\n", 1)[0].split(b" ", 1)[1].decode("ascii") + for filename, child in (("root.tree", ".github"), (".github.tree", "workflows")): + tree = (fixture / filename).read_bytes() + _verify_object("tree", tree, expected_tree) + expected_tree = _tree_child(tree, "40000", child) + tree = (fixture / "workflows.tree").read_bytes() + _verify_object("tree", tree, expected_tree) + expected_blob = _tree_child(tree, "100644", "tepp-rust-foundation-ci.yml") + supplier = (fixture / "tepp-rust-foundation-ci.yml").read_bytes() + _verify_object("blob", supplier, expected_blob) + if hashlib.sha256(supplier).hexdigest() != SUPPLIER_SHA256: + raise ValueError("CI supplier SHA-256 mismatch") + return caller.decode("utf-8") + "\n" + supplier.decode("utf-8") diff --git a/tests/fixtures/central-ci/9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de/.github.tree b/tests/fixtures/central-ci/9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de/.github.tree new file mode 100644 index 0000000000000000000000000000000000000000..5abadb48ac87a9a46138525d57ce89a2486743c7 GIT binary patch literal 191 zcmXpsFfcPQQE>KmarF=Pa}5e+Xs(Ra@`~zurI-J_dgZl?2mfC8K5Gb3m6%+TnV*-F znOCA$nV6fy;QjbTOyylICcpW&jRkLAQrb{nuV!KZ1PTz9#SCl{-rlMgUD9IjP#?eM zJ)5AusH!(aMNw&9UTTqUW^Q77YB59CN3U 1791897957 +0900 +committer GitHub 1791897957 +0900 +gpgsig -----BEGIN PGP SIGNATURE----- + + wsFcBAABCAAQBQJqzjFlCRC1aQ7uu5UhlAAAqvUQAIfR+9SpUHwEO2LPMrNMDMz2 + 1t4NhE8/PYR1hD3kJ+aD9at4U3/kK1VpkNVr/Xdx7WFuraVokPRYNkriWb0h9PA8 + RzvltffN6G0YxI+LGGUwATL7jMrtV1WHpUMYpxxmOzGw6ORvgEfZvKkXknCs7ajk + uHZj1VBMs0LIUSqNLvResOy+Z01QIrMa8+RU7LqoOAnhigtXpKIq2lXa4NSmPNVs + UwaF/nltlPGubAko00PKWcTtCyshUDG5ZPqnHodkAyPeQMmYgc4fC6aIbXqI5UUp + iQSNhGAzgMnCIRwyJ7nnPyixuMbXEhTGtr7N7V/2adw9q//P7cy0DX8nhw9wspLj + SHZbDtufesE4/anHA/nG/kIEQgPh8RpJYnhxsIL00XJGd6fVF8I0MV9qio+SHsHX + +Xmgmq3IhqJfUvUDthsGN2Y9RbORT1hQjexf4AeMm1yp3ntYpq970nGggdvswvPl + p/42ME+8FxN3E7txCXmiSTCWcH9Oh9T5dcFalrsQJ1Ed9frgtHA2bAAWOZaeDfsk + M1bx7MFxGTyhCahFqdZ9ouaqLdD5olbts7PelqaTA7LlRxZ9qIrP2fEZ/WEAdIG1 + sFcCpEVhSQIb1iNPPhpfkUzeKsqvVRiA5ABrSxX+9XJQkpAnsx9+59NHtudrrkAn + MBKC64VrwSQj6DSlsVOe + =D1OS + -----END PGP SIGNATURE----- + + +ci(tepp): centralize reusable foundation and documentation workloads (#2635) + +* ci(tepp): add reviewed reusable foundation and documentation suppliers + +* fix(tepp): fail closed on committed documentation whitespace + +* fix(ci): validate existing scoped K-CSAP runner list without weakening isolation \ No newline at end of file diff --git a/tests/fixtures/central-ci/9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de/root.tree b/tests/fixtures/central-ci/9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de/root.tree new file mode 100644 index 0000000000000000000000000000000000000000..58b83f813af9f7e6f3316bec3dd91a7df035ec59 GIT binary patch literal 1987 zcma)+c~BE)7>C!RI+RN*aynKJR2(*fAQX#Y8Y2l}VnRR=C4|ieHYB@YcLPB=Mx_W< zYAFm_3yKvL%OL_ff{1{iK`kOGR47#uJSr%n5y7h=S*F%9*8O*%-?8uae(UC}edf@7 zl`@zBu9pidpdIdY>6u%W7&~s9@0}Y5yXgT)rX)xhmnb)Fk|87vI2TXRIQbOp0Wz#A z2EMTzw1aArC^P~gBbDL69tcQIZ23Vmpw5_>p3rv0E6oOUadvihbDKlcB1i;^;V|&_ zw0Z+8hu!;Pc*VT6p{dU^+Lp$?N6BDO6hR{~xeSPOz;4E)wINN%ounF5BW}CnSHmBY zl5_Z}m)oQId3Lb_j30HIXC zI02KuiysqCOy>1ZPbMPMF3n?gSS9)Irt;`MOpd^xCKm$+_%UANG;4<>UR$zRlv%CC zF?#73#AmSB0wzNcz-Nx4cNVz{z*qZvgN$}&6S*my35z%N`b-9k&hcUTaeZiF;PJ}# z?Q78r4~rvsm-MHD`ARjK>Z?x5$-xSA8yiK#&_GN|jRfQ$BT@M>6#E;Ej za{QSCTrjWIKp$|8Z$>-2H45m;U%NC59p67#I(v*8b z@7~jIe>%0IbE8#sC#B#s=_}Xh%H6EF;;ETnDP4TBLCs6Kn*HqDmOu*fXEFl#Y(em- zSH4?Yr?F{Gdt#dS?Cj%=_5F6{q#fcFp_I~9Bvy#EV*3OCz zLgVTf!5EH-VM+kw?@4;_g#C<6x3}eN^KTz>vM;WdS5nIWVWZVQki{9beY@WYRGnZHVUZX?VS*4!5EZG! zVS=WJ19BI<4tDgq9BU#^?l-y9(wgF(Tdc379i#-2!GL|DgY{+0^YF_KRdHwPqnXB% z;T$ST5vPTuFhuIFW%F74DN#n;%7zp5cPb66xleW_HBlrEM=KE=mcuAXfZ-xkjF6!A z%pyfZq_zjCB7u;GtNl}@tXaX+GaA?D-c@xKSz#X)>r^1?G8k#$+Z$1yKa|K0Y)oD+Fw>2NWz4+$9w55zPT1TQ-^$)33|$8IWKC9s#4El<#K8KW7vB|(+j%Tn*LSJQ;rcoa zOB{HrJ!SU7lid43&EficM?g5DAPIo1G7!Iu8(HRYdsSpW>T+2LJJm~1=(ZQQ;#!e$ UllNH}y0_*a7W-}yAMXD0ZyMGcX#fBK literal 0 HcmV?d00001 diff --git a/tests/fixtures/central-ci/9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de/tepp-rust-foundation-ci.yml b/tests/fixtures/central-ci/9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de/tepp-rust-foundation-ci.yml new file mode 100644 index 000000000..190eaf8fc --- /dev/null +++ b/tests/fixtures/central-ci/9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de/tepp-rust-foundation-ci.yml @@ -0,0 +1,243 @@ +name: TEPP Rust Foundation CI + +on: + workflow_call: + +permissions: + contents: read + +env: + CARGO_TERM_COLOR: always + RUSTFLAGS: -Dwarnings + RUSTDOCFLAGS: -Dwarnings + CARGO_NEXTEST_VERSION: "0.9.140" + CARGO_DENY_VERSION: "0.19.7" + CARGO_LLVM_COV_VERSION: "0.8.6" + + +jobs: + repository-contracts: + name: Repository contracts and Python branch coverage + runs-on: + group: CWL CI isolated + labels: [self-hosted, linux, x64, cwlab-ci-isolated] + timeout-minutes: 10 + steps: + - name: Checkout exact head + uses: actions/checkout@631c942040754b6e095e929c1677c07e10ed4f87 + with: + persist-credentials: false + - name: Install pinned Python quality dependency + run: python3 -m pip install --disable-pip-version-check --no-deps --require-hashes -r requirements-quality.txt + - name: Exercise repository tooling with branch coverage + run: python3 -m coverage run --branch -m unittest discover -s tests/quality -p 'test_*.py' + - name: Enforce repository tooling coverage + run: python3 -m coverage report --fail-under=100 --show-missing + - name: Validate workspace contract + run: python3 scripts/check_workspace_contract.py + - name: Validate Rust documentation contract + run: python3 scripts/check_docstrings.py + - name: Generate and validate release SBOM/provenance evidence + run: | + python3 scripts/release_evidence.py generate \ + --repository-root . \ + --output-directory "${RUNNER_TEMP}/tepp-release-evidence" \ + --git-commit "${GITHUB_SHA}" + python3 scripts/release_evidence.py validate \ + --evidence-directory "${RUNNER_TEMP}/tepp-release-evidence" \ + --repository-root . \ + --expected-git-commit "${GITHUB_SHA}" + + rust-quality: + name: Format, lint, test, rustdoc, and dependency policy + runs-on: + group: CWL CI isolated + labels: [self-hosted, linux, x64, cwlab-ci-isolated] + timeout-minutes: 30 + steps: + - name: Checkout exact head + uses: actions/checkout@631c942040754b6e095e929c1677c07e10ed4f87 + with: + persist-credentials: false + - name: Install pinned Rust toolchain + run: rustup toolchain install 1.98.0 --profile minimal --component clippy --component rustfmt + - name: Restore pinned Rust quality tools + id: rust-tools-cache + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: | + ~/.cargo/bin/cargo-nextest + ~/.cargo/bin/cargo-deny + key: ${{ runner.os }}-${{ runner.arch }}-tepp-rust-tools-nextest-${{ env.CARGO_NEXTEST_VERSION }}-deny-${{ env.CARGO_DENY_VERSION }} + - name: Install pinned Rust quality tools + if: steps.rust-tools-cache.outputs.cache-hit != 'true' + run: | + cargo install cargo-nextest --locked --version "$CARGO_NEXTEST_VERSION" + cargo install cargo-deny --locked --version "$CARGO_DENY_VERSION" + - name: Verify pinned Rust quality tool versions + run: | + cargo nextest --version | grep -F "$CARGO_NEXTEST_VERSION" + cargo deny --version | grep -F "$CARGO_DENY_VERSION" + - name: Check formatting + run: cargo fmt --all -- --check + - name: Run Clippy + run: cargo clippy --workspace --all-targets --all-features -- -D warnings + - name: Run test suite without retries + run: cargo nextest run --workspace --all-features + - name: Run doctests separately + run: cargo test --doc --workspace --all-features + - name: Build warning-free documentation + run: cargo doc --workspace --all-features --no-deps + - name: Enforce dependency, license, advisory, and source policy + run: cargo deny check + + production-coverage: + name: Production line and branch coverage + runs-on: + group: CWL CI isolated + labels: [self-hosted, linux, x64, cwlab-ci-isolated] + timeout-minutes: 45 + steps: + - name: Checkout exact head + uses: actions/checkout@631c942040754b6e095e929c1677c07e10ed4f87 + with: + persist-credentials: false + - name: Install pinned Rust toolchain with LLVM tools + run: rustup toolchain install 1.98.0 --profile minimal --component llvm-tools-preview + - name: Install pinned nightly with LLVM tools + run: rustup toolchain install nightly-2026-08-21 --profile minimal --component llvm-tools-preview + - name: Restore pinned cargo-llvm-cov + id: llvm-cov-cache + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ~/.cargo/bin/cargo-llvm-cov + key: ${{ runner.os }}-${{ runner.arch }}-tepp-cargo-llvm-cov-${{ env.CARGO_LLVM_COV_VERSION }} + - name: Install cargo-llvm-cov + if: steps.llvm-cov-cache.outputs.cache-hit != 'true' + run: cargo install cargo-llvm-cov --locked --version "$CARGO_LLVM_COV_VERSION" + - name: Verify pinned cargo-llvm-cov version + run: cargo llvm-cov --version | grep -F "$CARGO_LLVM_COV_VERSION" + - name: Generate exact line coverage with region diagnostics + id: line-report + # mlx_native_receipt is excluded exactly like sqlx_live.rs: it is a + # macOS/MLX-only device receipt probe whose success path cannot + # execute on the Linux coverage runner (see ADR 0025). Its macOS + # behaviour is covered by the crate's cfg(macos) tests. + run: cargo llvm-cov --workspace --all-features --json --output-path coverage.json --ignore-filename-regex 'sqlx_live\.rs|mlx_native_receipt' + - name: Export exact authored line coverage + run: cargo llvm-cov report --lcov --output-path coverage.lcov --ignore-filename-regex 'sqlx_live\.rs|mlx_native_receipt' + - name: Enforce complete authored line coverage + run: python3 scripts/check_coverage.py coverage.lcov --kind lines --format lcov + - name: Show exact missing line diagnostics + if: ${{ failure() && steps.line-report.outcome == 'success' }} + run: | + LLVM_COV_FLAGS="--show-line-counts-or-regions" cargo llvm-cov report --text --show-missing-lines --show-instantiations + test -f coverage.lcov || cargo llvm-cov report --lcov --output-path coverage.lcov --ignore-filename-regex 'sqlx_live\.rs|mlx_native_receipt' + python3 - <<'PY' + import json + from pathlib import Path + + report = json.loads(Path("coverage.json").read_text(encoding="utf-8")) + for data in report.get("data", []): + for file_record in data.get("files", []): + filename = file_record.get("filename", "") + lines = file_record.get("summary", {}).get("lines", {}) + covered = int(lines.get("covered", 0)) + count = int(lines.get("count", 0)) + if covered < count: + print(f"INCOMPLETE_FILE {filename} {covered}/{count}") + for segment in file_record.get("segments", []): + if len(segment) < 6: + continue + line_number, column_number, execution_count = segment[:3] + has_count, is_region_entry, is_gap_region = segment[3:6] + if ( + has_count + and is_region_entry + and not is_gap_region + and int(execution_count) == 0 + ): + print( + "UNCOVERED_REGION " + f"{filename}:{line_number}:{column_number}" + ) + for function_record in data.get("functions", []): + if int(function_record.get("count", 0)) != 0: + continue + filenames = function_record.get("filenames", []) + filename = filenames[0] if filenames else "" + print( + "UNCOVERED_FUNCTION " + f"{filename} {function_record.get('name', '')}" + ) + + current_source = None + for raw_line in Path("coverage.lcov").read_text(encoding="utf-8").splitlines(): + if raw_line.startswith("SF:"): + current_source = raw_line[3:] + elif raw_line.startswith("DA:") and current_source is not None: + line_number, count, *_ = raw_line[3:].split(",") + if int(count) == 0: + print(f"UNCOVERED_LINE {current_source}:{line_number}") + PY + - name: Upload exact line coverage diagnostics + if: ${{ failure() && steps.line-report.outcome == 'success' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: line-coverage-diagnostics + path: | + coverage.json + coverage.lcov + if-no-files-found: error + retention-days: 1 + - name: Generate exact branch coverage + id: branch-report + if: ${{ !cancelled() }} + run: cargo +nightly-2026-08-21 llvm-cov --branch --workspace --all-features --json --output-path coverage-branches.json --ignore-filename-regex 'sqlx_live\.rs|mlx_native_receipt' + - name: Enforce complete branch coverage + if: ${{ !cancelled() && steps.branch-report.outcome == 'success' }} + run: python3 scripts/check_coverage.py coverage-branches.json --kind branches + - name: Show exact missing branch diagnostics + if: ${{ failure() && steps.branch-report.outcome == 'success' }} + run: cargo +nightly-2026-08-21 llvm-cov report --branch --text --show-missing-lines + - name: Upload exact branch coverage diagnostics + if: ${{ failure() && steps.branch-report.outcome == 'success' }} + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: branch-coverage-diagnostics + path: coverage-branches.json + if-no-files-found: error + retention-days: 1 + + live-postgres: + name: Live PostgreSQL integration + runs-on: + group: CWL CI isolated + labels: [self-hosted, linux, x64, cwlab-ci-isolated] + timeout-minutes: 20 + services: + postgres: + image: postgres:16.9-alpine + env: + POSTGRES_USER: tepp + POSTGRES_PASSWORD: tepp_ci + POSTGRES_DB: tepp + ports: + - 5432:5432 + options: >- + --health-cmd "pg_isready -U tepp -d tepp" + --health-interval 5s + --health-timeout 5s + --health-retries 10 + env: + TEPP_LIVE_POSTGRES: "1" + DATABASE_URL: postgres://tepp:tepp_ci@localhost:5432/tepp + steps: + - name: Checkout exact head + uses: actions/checkout@631c942040754b6e095e929c1677c07e10ed4f87 + with: + persist-credentials: false + - name: Install pinned Rust toolchain + run: rustup toolchain install 1.98.0 --profile minimal + - name: Run live-sqlx PostgreSQL integration + run: cargo test -p persistence_postgres --features live-sqlx --test live_postgres -- --nocapture diff --git a/tests/fixtures/central-ci/9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de/workflows.tree b/tests/fixtures/central-ci/9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de/workflows.tree new file mode 100644 index 0000000000000000000000000000000000000000..e3c77e7007f5e6fc50cc975aad2eeb4ade7109f6 GIT binary patch literal 2919 zcmaKudt4OP700nqP}Ua|BM4FiA{rQhur8X^(1Jh^ANT+p9)@A&?(VR&GwaMOuowje zm7s{OC=j9`s0Es6Kr4Y1Q6Az)t5C3os$jtf)qo=Sh{i|n&d%~M+y1kk{o{P+-1EEV z{Lc0E@{)+fPAJ9^G{qtt)j$mpIY3EHzCfcSP2SX*Ybr&h8g7*`q~e2y8l~CSMcx7< z0~CiS;SWR+iUvv)!3kD{a+sd2XA1J{${HjgFOYNJcbO+Yc1q4S%F-%8VKffLu9 zcp~+GaGu#^|9$#aP0#z0ik!O#_bRNFXO1YwF$_o`KqA7ZVbMzPZps4W9vAI7tJCD0 zpE@@7|BM#?Bl{}@uEq%t!Jt?SNCp8RYF_apwxEY|L#3g@Ry4KVw>}`&tUvW(7>yeKuRVbxFa3Dq1 zB&TZ;9N`!>%ONt91ANMf+y-syQk#>#I<9LbE#gg7^kA}b?=oK`a= z)RIADy80-T!t{4NA)~iz%_jq)U0wJ8Q<^`wChy@#M~(0*bsR~sazv$3=})?_CirT5 z?ofLFujj4^tR%I*AO9uI7zg8FW2q3C8f9?)#4RnSN{VTzEMxQZcIV=$v-=ADcN!rX zKmwEn2#3meY~6*p%1(QaS22G#k53$8EOuVoX$<2poaG1%PAAL3VHJAkZBSUtH7aVT zTz>Oq{!mkGgJNm9kX6veUGP!?kd z74(948%FT0+)LYMEDBhXY~s3E8gYMaU1mzST%Z*Q#zIY41woQJujc)Zud--gR9FNa z^JYcd>Qhx0Ttn+qk2?qui6$Z_fxy;;Lnq{DJzsEgZJHSTw%;%3`MpUQ!&q6e5U%&} zF_U)rknFu2tGe=}En>&lFDidnml;1{JA1(FoLOmK zonP{L&!p?9A)Jw+6p_r2Gt^wE1Pn&#EEpOXK>?OUNSaphUwP>`X#19s_9eP~cxcn4 zJ(oV26TU_Gib{q621j^99w)rpYR?_Z`?lSf_VW_c=?%V@RwO+=W?&cuRdY1V6bBoU z&k&+@4w*Ypkv8o5>2maT^tAa7w!Wj$f{HpwN+j_>;JlJSn{J}_h<|mrwRUl@g0V5I zP&ktU0E3~K%K;9zioOHG1AeHlb_iej(qf=Cx@brJo!l(?7kQu((5}XViHU>m1CNe$ zdVtgZ(y~EG(lO^G1ITJHIjsa7Ltregr0&5WG*r9Sl8mN+rJHYF_+84pIg4&oU)V8h z;53{Zz7JS{sTsJQ`LZaB&YATIrz4jgp3~BU_Z?W}oV;ELXP|Lo3Z0R{ii9qj;&T0) z52ko;@>%8gWLN7S^0kfM8uqGT+{bJQl7MR#Isgk#nl!qdvd=c&^8L!9+@&VEHPpdM zvS&k7tsy5+(JaByj0QeuI23Lr6l#mn6h@Hxi+gQTW98Ws;r<}+uX$k(|F(`0#Ts0M z)pt)KN@K3&+fwfR?vEiFbc8deSc7C|YL)UbqpeLCTPE?74pmR{{j8J52g zWyeP3TsIH^V*sokz9jgMOY&};L>%7zxH;x&o0c8yf8cYtV>Fb%|Iy%}D_tKhaG(<> z{HWO7=2aTB*sIyb5Ujg8{)_J5*xzt_j5eQcexNW2xW6pExmpO~fJ%knG^W-)C-n0@ z))F%Vp3H6(MIsM#4*}P< zs_5;fOGgsw5P&1<1VjlRE)290BKemO&&&;nDkm2_ZT3&Kf8Mv~;hd^Sw~PM;_4Ilm literal 0 HcmV?d00001 diff --git a/tests/quality/test_ci_coverage_diagnostics.py b/tests/quality/test_ci_coverage_diagnostics.py index c731887e0..e76c5cc4a 100644 --- a/tests/quality/test_ci_coverage_diagnostics.py +++ b/tests/quality/test_ci_coverage_diagnostics.py @@ -6,6 +6,8 @@ import unittest from pathlib import Path +from scripts.effective_ci_source import effective_ci_source + REPOSITORY_ROOT = Path(__file__).resolve().parents[2] CI_WORKFLOW = REPOSITORY_ROOT / ".github" / "workflows" / "ci.yml" @@ -28,7 +30,7 @@ class CoverageDiagnosticsContractTests(unittest.TestCase): def test_line_and_branch_failures_print_exact_missing_locations(self) -> None: """Both LLVM coverage lanes retain same-run missing-location reports.""" - workflow = CI_WORKFLOW.read_text(encoding="utf-8") + workflow = effective_ci_source(REPOSITORY_ROOT) self.assertIn("id: line-report", workflow) self.assertIn("cargo llvm-cov report --text --show-missing-lines", workflow) @@ -56,7 +58,7 @@ def test_line_and_branch_failures_print_exact_missing_locations(self) -> None: def test_line_gate_uses_lcov_authored_lines_and_keeps_region_evidence(self) -> None: """Authored lines gate on LCOV while full JSON retains hidden-region evidence.""" - workflow = CI_WORKFLOW.read_text(encoding="utf-8") + workflow = effective_ci_source(REPOSITORY_ROOT) self.assertIn( "cargo llvm-cov --workspace --all-features --json --output-path coverage.json", @@ -84,7 +86,7 @@ def test_line_gate_uses_lcov_authored_lines_and_keeps_region_evidence(self) -> N def test_live_postgres_job_is_gated_and_service_backed(self) -> None: """Live SQLx evidence requires a Postgres service and explicit env gate.""" - workflow = CI_WORKFLOW.read_text(encoding="utf-8") + workflow = effective_ci_source(REPOSITORY_ROOT) self.assertIn("live-postgres:", workflow) self.assertIn("name: Live PostgreSQL integration", workflow) @@ -103,7 +105,7 @@ def test_rust_toolchains_are_reproducible_and_reviewably_updated(self) -> None: manifest = tomllib.loads(RUST_TOOLCHAIN.read_text(encoding="utf-8")) self.assertEqual(manifest["toolchain"]["channel"], "1.98.0") - workflow = CI_WORKFLOW.read_text(encoding="utf-8") + workflow = effective_ci_source(REPOSITORY_ROOT) self.assertEqual(workflow.count("nightly-2026-08-21"), 3) self.assertNotIn("nightly-2026-08-01", workflow) diff --git a/tests/quality/test_effective_ci_source.py b/tests/quality/test_effective_ci_source.py new file mode 100644 index 000000000..5273aea60 --- /dev/null +++ b/tests/quality/test_effective_ci_source.py @@ -0,0 +1,143 @@ +"""Offline source custody and substantive regression controls for centralized CI.""" + +from __future__ import annotations + +import shutil +import tempfile +import unittest +from pathlib import Path +from unittest import mock + +from scripts import effective_ci_source as source +from scripts import check_workspace_contract as workspace +import test_ci_coverage_diagnostics as diagnostics +import test_workflow_queue_hygiene as queue + +ROOT = Path(__file__).resolve().parents[2] + + +class EffectiveCiSourceTests(unittest.TestCase): + """Keep source identity, inherited gates, and runner isolation fail closed.""" + + def copy_evidence(self, root: Path) -> Path: + """Create an offline checkout containing only caller and frozen evidence.""" + (root / ".github/workflows").mkdir(parents=True) + shutil.copyfile(ROOT / ".github/workflows/ci.yml", root / ".github/workflows/ci.yml") + fixture = root / source.FIXTURE_PATH + shutil.copytree(ROOT / source.FIXTURE_PATH, fixture) + return fixture + + def test_offline_source_is_complete_and_isolated(self) -> None: + """The original Git objects resolve without Git metadata or network access.""" + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + self.copy_evidence(root) + text = source.effective_ci_source(root) + self.assertIn(f"@{source.SUPPLIER_PIN}", text) + self.assertEqual(text.count(" group: CWL CI isolated"), 4) + self.assertEqual(text.count(" labels: [self-hosted, linux, x64, cwlab-ci-isolated]"), 4) + self.assertIn("coverage report --fail-under=100", text) + self.assertIn("coverage-branches.json --kind branches", text) + self.assertIn('CARGO_NEXTEST_VERSION: "0.9.140"', text) + self.assertIn('CARGO_DENY_VERSION: "0.19.7"', text) + self.assertIn('CARGO_LLVM_COV_VERSION: "0.8.6"', text) + + def test_every_evidence_object_rejects_corruption_and_absence(self) -> None: + """No missing or tampered commit, intermediate tree, or workflow is accepted.""" + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + fixture = self.copy_evidence(root) + for filename in ("commit.object", "root.tree", ".github.tree", "workflows.tree", "tepp-rust-foundation-ci.yml"): + with self.subTest(filename=filename): + path = fixture / filename + original = path.read_bytes() + path.write_bytes(original + b"corrupt") + with self.assertRaisesRegex(ValueError, "Git object mismatch"): + source.effective_ci_source(root) + path.unlink() + with self.assertRaises(OSError): + source.effective_ci_source(root) + path.write_bytes(original) + self.assertEqual(source.effective_ci_source(root), source.effective_ci_source(ROOT)) + + def test_caller_identity_rejects_pin_dispatch_and_job_drift(self) -> None: + """Valid-looking replacement pins, disabled calls, and extra jobs fail closed.""" + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + self.copy_evidence(root) + path = root / ".github/workflows/ci.yml" + original = path.read_text(encoding="utf-8") + variants = ( + original.replace(source.SUPPLIER_PIN, "a" * 40), + original.replace(" foundation:", " foundation:\n if: false"), + original.replace("contents: read", "contents: write"), + original.replace("cancel-in-progress:", "removed-concurrency:"), + original + "\n competing-job:\n runs-on: ubuntu-latest\n", + ) + for text in variants: + path.write_text(text, encoding="utf-8") + with self.assertRaisesRegex(ValueError, "caller differs"): + source.effective_ci_source(root) + self.assertTrue(any("supplier source validation failed" in error for error in workspace._validate_ci_contract(root))) + + def test_supplier_weakenings_are_rejected_by_custody(self) -> None: + """Coverage, compiler, tool, PostgreSQL and isolation mutations reject offline.""" + changes = ( + ("--fail-under=100", "--fail-under=99"), + ("coverage.lcov --kind lines --format lcov", "coverage.lcov --kind removed --format lcov"), + ("coverage-branches.json --kind branches", "coverage-branches.json --kind removed"), + ("nightly-2026-08-21", "nightly"), + ("rustup toolchain install 1.98.0", "rustup toolchain install stable"), + ('CARGO_LLVM_COV_VERSION: "0.8.6"', 'CARGO_LLVM_COV_VERSION: "latest"'), + ("postgres:16.9-alpine", "postgres:latest"), + ('TEPP_LIVE_POSTGRES: "1"', 'TEPP_LIVE_POSTGRES: "0"'), + ("--features live-sqlx --test live_postgres", "--features live-sqlx --test removed"), + ("CWL CI isolated", "CWL CI control"), + ("cwlab-ci-isolated", "privileged"), + ("x64,", ""), + ) + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + fixture = self.copy_evidence(root) + path = fixture / "tepp-rust-foundation-ci.yml" + original = path.read_text(encoding="utf-8") + for before, after in changes: + with self.subTest(mutation=before): + self.assertIn(before, original) + path.write_text(original.replace(before, after), encoding="utf-8") + with self.assertRaisesRegex(ValueError, "blob Git object mismatch"): + source.effective_ci_source(root) + path.write_text(original, encoding="utf-8") + self.assertEqual(workspace._validate_ci_contract(root), ["rust-toolchain.toml is missing", "deny.toml is missing"]) + + def test_original_assertions_reject_weakened_effective_bodies(self) -> None: + """Exercise inherited checks themselves, independently of custody rejection.""" + original = source.effective_ci_source(ROOT) + controls = ( + (diagnostics, diagnostics.CoverageDiagnosticsContractTests, "test_line_and_branch_failures_print_exact_missing_locations", "id: line-report", "id: removed"), + (diagnostics, diagnostics.CoverageDiagnosticsContractTests, "test_line_and_branch_failures_print_exact_missing_locations", "id: branch-report", "id: removed"), + (diagnostics, diagnostics.CoverageDiagnosticsContractTests, "test_line_gate_uses_lcov_authored_lines_and_keeps_region_evidence", "coverage.lcov --kind lines --format lcov", "coverage.lcov --kind removed"), + (diagnostics, diagnostics.CoverageDiagnosticsContractTests, "test_live_postgres_job_is_gated_and_service_backed", "postgres:16.9-alpine", "postgres:latest"), + (diagnostics, diagnostics.CoverageDiagnosticsContractTests, "test_live_postgres_job_is_gated_and_service_backed", 'TEPP_LIVE_POSTGRES: "1"', 'TEPP_LIVE_POSTGRES: "0"'), + (diagnostics, diagnostics.CoverageDiagnosticsContractTests, "test_rust_toolchains_are_reproducible_and_reviewably_updated", "nightly-2026-08-21", "nightly"), + (queue, queue.WorkflowQueueHygieneTests, "test_rust_ci_reuses_one_runner_for_both_coverage_reports", " production-coverage:", " line-coverage:"), + ) + for module, case, method, before, after in controls: + with self.subTest(method=method, mutation=before): + self.assertIn(before, original) + with mock.patch.object(module, "effective_ci_source", return_value=original.replace(before, after)): + with self.assertRaises(AssertionError): + getattr(case(method), method)() + for command in workspace.REQUIRED_CI_SNIPPETS: + with self.subTest(command=command): + self.assertIn(command, original) + with mock.patch.object(workspace, "effective_ci_source", return_value=original.replace(command, "REMOVED")): + self.assertIn(f"CI workflow is missing required command: {command}", workspace._validate_ci_contract(ROOT)) + + def test_independent_sha256_and_tree_path_checks(self) -> None: + """SHA-256 is independently enforced and tree lookup requires exact names.""" + with mock.patch.object(source, "SUPPLIER_SHA256", "0" * 64): + with self.assertRaisesRegex(ValueError, "SHA-256 mismatch"): + source.effective_ci_source(ROOT) + with self.assertRaises(ValueError): + source._tree_child(b"", "40000", ".github") diff --git a/tests/quality/test_workflow_queue_hygiene.py b/tests/quality/test_workflow_queue_hygiene.py index 431687af5..644859171 100644 --- a/tests/quality/test_workflow_queue_hygiene.py +++ b/tests/quality/test_workflow_queue_hygiene.py @@ -3,6 +3,8 @@ from pathlib import Path import unittest +from scripts.effective_ci_source import effective_ci_source + WORKFLOW_DIRECTORY = Path(".github/workflows") @@ -47,7 +49,7 @@ def test_documentation_workflow_does_not_duplicate_crate_ci(self) -> None: def test_rust_ci_reuses_one_runner_for_both_coverage_reports(self) -> None: """Coverage keeps both gates while avoiding a second runner boot.""" - workflow = (WORKFLOW_DIRECTORY / "ci.yml").read_text(encoding="utf-8") + workflow = effective_ci_source(Path(__file__).resolve().parents[2]) self.assertIn(" production-coverage:\n", workflow) self.assertNotIn(" line-coverage:\n", workflow) self.assertNotIn(" branch-coverage:\n", workflow) From 93b6fb326987c7af10ec4ccf99e9dd45c44be51f Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 13 Oct 2026 23:10:50 +0900 Subject: [PATCH 3/4] test(ci): preserve signed Git commit evidence as binary bytes --- .gitattributes | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..fd0f26ca4 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +# This raw signed Git object is immutable binary evidence, not editable text. +# Its exact bytes (including signature whitespace) are authenticated by Git OID. +tests/fixtures/central-ci/9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de/commit.object binary From c478f31bb37336c6957e1f3867c831aa8746466e Mon Sep 17 00:00:00 2001 From: Seongho Bae Date: Tue, 13 Oct 2026 23:50:58 +0900 Subject: [PATCH 4/4] test: bind reusable callers to actual supplier jobs --- scripts/effective_ci_source.py | 25 +++++-- .../tepp-documentation-quality.yml | 26 +++++++ tests/fixtures/central-ci/README.md | 24 +++++++ tests/quality/test_effective_ci_source.py | 72 ++++++++++++++++++- .../quality/test_reusable_supplier_callers.py | 15 ++-- 5 files changed, 150 insertions(+), 12 deletions(-) create mode 100644 tests/fixtures/central-ci/9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de/tepp-documentation-quality.yml create mode 100644 tests/fixtures/central-ci/README.md diff --git a/scripts/effective_ci_source.py b/scripts/effective_ci_source.py index 9b1bb9bfd..c024c882d 100644 --- a/scripts/effective_ci_source.py +++ b/scripts/effective_ci_source.py @@ -12,6 +12,8 @@ SUPPLIER_PIN = "9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de" CALLER_SHA256 = "4cdbacc6c531bba1fcb0632e9a4ea88ec9814820bb4e8c45447d57bc046201e3" SUPPLIER_SHA256 = "0baf1384a6f66b357bba6cf74b384ecae447767f512eb2c90bb50849c74a7160" +DOCUMENTATION_CALLER_SHA256 = "99ed24d9ce64a4232dcb29e05ddcdedac659c37f5c8b4230386f57435b3bd1f2" +DOCUMENTATION_SUPPLIER_SHA256 = "76d5f64f05fbf953c8c96b6bf8129880a904498b1bbce355567108975bfb00aa" FIXTURE_PATH = Path("tests/fixtures/central-ci") / SUPPLIER_PIN @@ -29,7 +31,7 @@ def _tree_child(data: bytes, mode: str, name: str) -> str: return data[start:start + 20].hex() -def effective_ci_source(root: Path) -> str: +def effective_ci_source(root: Path, supplier_name: str = "tepp-rust-foundation-ci.yml") -> str: """Return verified caller plus pinned supplier for static contract checks. Exact caller bytes bind dispatch, read-only permissions, concurrency, pin, @@ -37,9 +39,20 @@ def effective_ci_source(root: Path) -> str: from the caller's reviewed commit through the original tree path to blob; SHA-256 additionally binds the workflow bytes. Missing or changed evidence fails closed. No Git installation, repository metadata, or network is needed. + Only the foundation (default) and documentation supplier names are allowed; + each is bound to its own exact caller and digest at the same reviewed pin. """ - caller = (root / ".github/workflows/ci.yml").read_bytes() - if hashlib.sha256(caller).hexdigest() != CALLER_SHA256: + contracts = { + "tepp-rust-foundation-ci.yml": ("ci.yml", CALLER_SHA256, SUPPLIER_SHA256), + "tepp-documentation-quality.yml": ( + "docs-quality.yml", DOCUMENTATION_CALLER_SHA256, DOCUMENTATION_SUPPLIER_SHA256, + ), + } + if supplier_name not in contracts: + raise ValueError("CI supplier name is not allowlisted") + caller_name, caller_sha256, supplier_sha256 = contracts[supplier_name] + caller = (root / ".github/workflows" / caller_name).read_bytes() + if hashlib.sha256(caller).hexdigest() != caller_sha256: raise ValueError("CI caller differs from reviewed immutable supplier contract") fixture = root / FIXTURE_PATH commit = (fixture / "commit.object").read_bytes() @@ -51,9 +64,9 @@ def effective_ci_source(root: Path) -> str: expected_tree = _tree_child(tree, "40000", child) tree = (fixture / "workflows.tree").read_bytes() _verify_object("tree", tree, expected_tree) - expected_blob = _tree_child(tree, "100644", "tepp-rust-foundation-ci.yml") - supplier = (fixture / "tepp-rust-foundation-ci.yml").read_bytes() + expected_blob = _tree_child(tree, "100644", supplier_name) + supplier = (fixture / supplier_name).read_bytes() _verify_object("blob", supplier, expected_blob) - if hashlib.sha256(supplier).hexdigest() != SUPPLIER_SHA256: + if hashlib.sha256(supplier).hexdigest() != supplier_sha256: raise ValueError("CI supplier SHA-256 mismatch") return caller.decode("utf-8") + "\n" + supplier.decode("utf-8") diff --git a/tests/fixtures/central-ci/9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de/tepp-documentation-quality.yml b/tests/fixtures/central-ci/9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de/tepp-documentation-quality.yml new file mode 100644 index 000000000..8a0ad78cd --- /dev/null +++ b/tests/fixtures/central-ci/9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de/tepp-documentation-quality.yml @@ -0,0 +1,26 @@ +name: TEPP Documentation Quality + +on: + workflow_call: + +permissions: + contents: read + + +jobs: + validate: + name: Validate planning and governance contracts + runs-on: + group: CWL CI isolated + labels: [self-hosted, linux, x64, cwlab-ci-isolated] + timeout-minutes: 10 + steps: + - name: Checkout exact head + uses: actions/checkout@631c942040754b6e095e929c1677c07e10ed4f87 + with: + persist-credentials: false + fetch-depth: 2 + - name: Validate documentation + run: python3 scripts/validate_documentation.py + - name: Reject whitespace errors + run: git diff --check HEAD^ HEAD diff --git a/tests/fixtures/central-ci/README.md b/tests/fixtures/central-ci/README.md new file mode 100644 index 000000000..026f11b39 --- /dev/null +++ b/tests/fixtures/central-ci/README.md @@ -0,0 +1,24 @@ +# Authenticated central CI evidence + +These non-executable fixtures preserve raw Git objects from central commit +`9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de`. Both consumer callers use that +same immutable pin. `scripts/effective_ci_source.py` authenticates the commit, +root tree, `.github` tree, workflow tree, and the selected workflow blob before +checking its independent SHA-256 digest and its exact consumer caller digest. +Only the Rust foundation and documentation workflow names are admitted. There +is no Git metadata requirement or network fallback. + +The documentation workflow is the original 649-byte blob +`8a0ad78cd8687b2b8b0f1437e230b04e95e4aea3`, SHA-256 +`76d5f64f05fbf953c8c96b6bf8129880a904498b1bbce355567108975bfb00aa`. +It reuses the existing commit and three authenticated trees, without modifying +their bytes. The signed `commit.object` retains its exact binary attribute. + +Caller tests extract the actual top-level job IDs from each authenticated +supplier body: `repository-contracts`, `rust-quality`, `production-coverage`, +`live-postgres`, and `validate`. Controls alter or omit each actual ID and +exercise both blob-custody rejection and the independent ID assertion. +Documentation controls also reject absent/corrupt objects, caller pin and +permission drift, disabled or additional caller jobs, digest drift, and +non-allowlisted supplier names. These are static source-custody checks, not +execution of the five GitHub Actions jobs or proof of hosted acceptance. diff --git a/tests/quality/test_effective_ci_source.py b/tests/quality/test_effective_ci_source.py index 5273aea60..065b50ea9 100644 --- a/tests/quality/test_effective_ci_source.py +++ b/tests/quality/test_effective_ci_source.py @@ -12,6 +12,7 @@ from scripts import check_workspace_contract as workspace import test_ci_coverage_diagnostics as diagnostics import test_workflow_queue_hygiene as queue +import test_reusable_supplier_callers as callers ROOT = Path(__file__).resolve().parents[2] @@ -22,7 +23,8 @@ class EffectiveCiSourceTests(unittest.TestCase): def copy_evidence(self, root: Path) -> Path: """Create an offline checkout containing only caller and frozen evidence.""" (root / ".github/workflows").mkdir(parents=True) - shutil.copyfile(ROOT / ".github/workflows/ci.yml", root / ".github/workflows/ci.yml") + for caller_name in ("ci.yml", "docs-quality.yml"): + shutil.copyfile(ROOT / ".github/workflows" / caller_name, root / ".github/workflows" / caller_name) fixture = root / source.FIXTURE_PATH shutil.copytree(ROOT / source.FIXTURE_PATH, fixture) return fixture @@ -134,6 +136,74 @@ def test_original_assertions_reject_weakened_effective_bodies(self) -> None: with mock.patch.object(workspace, "effective_ci_source", return_value=original.replace(command, "REMOVED")): self.assertIn(f"CI workflow is missing required command: {command}", workspace._validate_ci_contract(ROOT)) + def test_documentation_evidence_and_caller_controls(self) -> None: + """Authenticate documentation through the shared commit and reject drift.""" + name = "tepp-documentation-quality.yml" + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + fixture = self.copy_evidence(root) + self.assertEqual(source.effective_ci_source(root, name), source.effective_ci_source(ROOT, name)) + for filename in ("commit.object", "root.tree", ".github.tree", "workflows.tree", name): + with self.subTest(filename=filename): + path = fixture / filename + original = path.read_bytes() + path.write_bytes(original + b"corrupt") + with self.assertRaisesRegex(ValueError, "Git object mismatch"): + source.effective_ci_source(root, name) + path.unlink() + with self.assertRaises(OSError): + source.effective_ci_source(root, name) + path.write_bytes(original) + path = root / ".github/workflows/docs-quality.yml" + original = path.read_text(encoding="utf-8") + for text in ( + original.replace(source.SUPPLIER_PIN, "a" * 40), + original.replace(" documentation:", " documentation:\n if: false"), + original.replace("contents: read", "contents: write"), + original + "\n extra-job:\n runs-on: ubuntu-latest\n", + ): + with self.subTest(caller=text): + path.write_text(text, encoding="utf-8") + with self.assertRaisesRegex(ValueError, "caller differs"): + source.effective_ci_source(root, name) + path.write_text(original, encoding="utf-8") + with mock.patch.object(source, "DOCUMENTATION_SUPPLIER_SHA256", "0" * 64): + with self.assertRaisesRegex(ValueError, "SHA-256 mismatch"): + source.effective_ci_source(root, name) + for name in ("../tepp-documentation-quality.yml", "other.yml", "", "tepp-rust-foundation-ci.yaml"): + with self.subTest(name=name): + with self.assertRaisesRegex(ValueError, "not allowlisted"): + source.effective_ci_source(ROOT, name) + + def test_actual_five_job_ids_reject_alteration_and_omission(self) -> None: + """Each real gate is checked by custody and independently by the ID oracle.""" + verified = { + expected["supplier"]: source.effective_ci_source(ROOT, expected["supplier"]) + for expected in callers.EXPECTED_CALLERS.values() + } + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + fixture = self.copy_evidence(root) + for expected in callers.EXPECTED_CALLERS.values(): + name = expected["supplier"] + path = fixture / name + original = path.read_text(encoding="utf-8") + for gate_id in expected["gate_ids"]: + marker = f" {gate_id}:\n" + self.assertEqual(original.count(marker), 1) + for replacement in (" altered-job:\n", ""): + with self.subTest(supplier=name, gate=gate_id, replacement=replacement): + path.write_text(original.replace(marker, replacement), encoding="utf-8") + with self.assertRaisesRegex(ValueError, "blob Git object mismatch"): + source.effective_ci_source(root, name) + altered = dict(verified) + altered[name] = verified[name].replace(marker, replacement) + with mock.patch.object(callers, "effective_ci_source", side_effect=lambda root, supplier: altered[supplier]): + with self.assertRaises(AssertionError): + callers.test_five_supplier_gates_are_coordinated_by_two_callers() + path.write_text(original, encoding="utf-8") + callers.test_five_supplier_gates_are_coordinated_by_two_callers() + def test_independent_sha256_and_tree_path_checks(self) -> None: """SHA-256 is independently enforced and tree lookup requires exact names.""" with mock.patch.object(source, "SUPPLIER_SHA256", "0" * 64): diff --git a/tests/quality/test_reusable_supplier_callers.py b/tests/quality/test_reusable_supplier_callers.py index e6a9888ed..ae4642efe 100644 --- a/tests/quality/test_reusable_supplier_callers.py +++ b/tests/quality/test_reusable_supplier_callers.py @@ -4,6 +4,8 @@ import re import unittest +from scripts.effective_ci_source import effective_ci_source + ROOT = Path(__file__).parents[2] EXPECTED_SHA = "9dab3c2a607ddef69b5ff8cb0f761e90f6bc28de" @@ -72,11 +74,14 @@ def test_callers_pin_one_supplier_without_nested_execution() -> None: def test_five_supplier_gates_are_coordinated_by_two_callers() -> None: """The foundation and documentation suppliers account for five unique gates.""" - gate_ids = [ - gate_id - for expected in EXPECTED_CALLERS.values() - for gate_id in expected["gate_ids"] - ] + gate_ids = [] + for expected in EXPECTED_CALLERS.values(): + verified = effective_ci_source(ROOT, expected["supplier"]) + supplier_jobs = verified.rsplit("\njobs:\n", 1)[1] + actual_ids = re.findall(r"^ ([A-Za-z0-9_-]+):$", supplier_jobs, re.MULTILINE) + assert set(actual_ids) == set(expected["gate_ids"]) + assert len(actual_ids) == len(expected["gate_ids"]) + gate_ids.extend(actual_ids) assert len(gate_ids) == 5 assert len(set(gate_ids)) == 5 assert set(gate_ids) == {