Merge PR #26: final RC status and ref-integrity closure #114
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Consumer Pin Verification | ||
| # Verify independent consumers pin OVK to an exact 40-hex candidate SHA (WP-14). | ||
| # Historical tags like v1.2.1 cannot evidence this candidate. uses: ./ is forbidden. | ||
| on: | ||
| workflow_dispatch: | ||
| inputs: | ||
| ovk_candidate_sha: | ||
| description: Exact 40-hex OVK candidate SHA consumers must pin | ||
| required: true | ||
| type: string | ||
| fastapi_ref: | ||
| description: Ref for ovk-consumer-fastapi-terraform | ||
| required: true | ||
| default: main | ||
| express_ref: | ||
| description: Ref for ovk-consumer-express-actions | ||
| required: true | ||
| default: main | ||
| permissions: | ||
| contents: read | ||
| jobs: | ||
| verify-consumer-pins: | ||
| runs-on: ubuntu-latest | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| include: | ||
| - repo: fraware/ovk-consumer-fastapi-terraform | ||
| ref: ${{ inputs.fastapi_ref }} | ||
| - repo: fraware/ovk-consumer-express-actions | ||
| ref: ${{ inputs.express_ref }} | ||
| steps: | ||
| - name: Validate candidate SHA shape | ||
| env: | ||
| OVK_SHA: ${{ inputs.ovk_candidate_sha }} | ||
| run: | | ||
| python - <<'PY' | ||
| import os, re, sys | ||
| sha = os.environ["OVK_SHA"].strip().lower() | ||
| if not re.fullmatch(r"[0-9a-f]{40}", sha): | ||
| sys.exit(f"ovk_candidate_sha must be exact 40-hex, got {sha!r}") | ||
| print(f"candidate sha ok: {sha}") | ||
| PY | ||
| - name: Checkout consumer | ||
| uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0 | ||
| with: | ||
| repository: ${{ matrix.repo }} | ||
| ref: ${{ matrix.ref }} | ||
| path: consumer | ||
| - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 | ||
| with: | ||
| python-version: "3.12" | ||
| - name: Assert immutable Action SHA pin (no uses: ./) | ||
| env: | ||
| OVK_SHA: ${{ inputs.ovk_candidate_sha }} | ||
| working-directory: consumer | ||
| run: | | ||
| set -euo pipefail | ||
| echo "Checking ${{ matrix.repo }}@${{ matrix.ref }} for OVK pin ${OVK_SHA}" | ||
| python - <<'PY' | ||
| import os, re, sys | ||
| from pathlib import Path | ||
| sha = os.environ["OVK_SHA"].strip().lower() | ||
| workflows = list(Path(".github/workflows").glob("*.yml")) + list(Path(".github/workflows").glob("*.yaml")) | ||
| if not workflows: | ||
| sys.exit("no workflow files") | ||
| failures = [] | ||
| pin = f"fraware/open-verification-kernel@{sha}" | ||
| texts = [] | ||
| for path in workflows: | ||
| text = path.read_text(encoding="utf-8") | ||
| texts.append(text) | ||
| for line in text.splitlines(): | ||
| m = re.match(r"\s*uses:\s*(\S+)", line) | ||
| if not m: | ||
| continue | ||
| ref = m.group(1).strip().strip("'\"") | ||
| if re.match(r"^\./", ref): | ||
| failures.append(f"{path.name}: forbidden uses: ./") | ||
| if "open-verification-kernel@" in ref: | ||
| if ref.endswith(("@main", "@master", "@HEAD")): | ||
| failures.append(f"{path.name}: mutable pin {ref}") | ||
| if re.search(r"@v?\d+\.\d+", ref) and sha not in ref: | ||
| failures.append(f"{path.name}: tag pin cannot evidence candidate SHA: {ref}") | ||
| if not ref.lower().endswith("@" + sha): | ||
| failures.append(f"{path.name}: expected exact SHA pin {pin}, got {ref}") | ||
| joined = "\n".join(texts) | ||
| if pin not in joined.lower() and f"open-verification-kernel@{sha}" not in joined.lower(): | ||
| if f"@{sha}" not in joined.lower() or "open-verification-kernel@" not in joined.lower(): | ||
| failures.append(f"missing required exact pin .../open-verification-kernel@{sha}") | ||
| if failures: | ||
| print("FAIL") | ||
| for item in failures: | ||
| print(item) | ||
| raise SystemExit(1) | ||
| print(f"Pin verification OK for candidate SHA {sha}") | ||
| PY | ||
| if [ -f scripts/assert_ovk_pin.py ]; then | ||
| python scripts/assert_ovk_pin.py --candidate-sha "$OVK_SHA" | ||
| fi | ||
| - name: Capture ledger-ready consumer evidence | ||
| env: | ||
| OVK_SHA: ${{ inputs.ovk_candidate_sha }} | ||
| CONSUMER_REPO: ${{ matrix.repo }} | ||
| CONSUMER_REF: ${{ matrix.ref }} | ||
| working-directory: consumer | ||
| run: | | ||
| set -euo pipefail | ||
| CONSUMER_SOURCE_SHA="$(git rev-parse HEAD)" | ||
| export CONSUMER_SOURCE_SHA | ||
| mkdir -p ../.verification | ||
| python - <<'PY' | ||
| import hashlib, json, os, pathlib, re | ||
| sha = os.environ["OVK_SHA"].strip().lower() | ||
| repo = os.environ["CONSUMER_REPO"] | ||
| ref = os.environ["CONSUMER_REF"] | ||
| consumer_source_sha = os.environ["CONSUMER_SOURCE_SHA"].strip().lower() | ||
| if not re.fullmatch(r"[0-9a-f]{40}", consumer_source_sha): | ||
| raise SystemExit(f"consumer_source_sha must be 40-hex, got {consumer_source_sha!r}") | ||
| workflows = list(pathlib.Path(".github/workflows").glob("*.yml")) + list( | ||
| pathlib.Path(".github/workflows").glob("*.yaml") | ||
| ) | ||
| scenario_digests = [] | ||
| for path in workflows: | ||
| scenario_digests.append( | ||
| {"path": path.as_posix(), "sha256": hashlib.sha256(path.read_bytes()).hexdigest()} | ||
| ) | ||
| payload = { | ||
| "schema_version": "ovk.consumer_pin_evidence.v1", | ||
| "consumer_repository": repo, | ||
| "consumer_ref": ref, | ||
| "consumer_source_sha": consumer_source_sha, | ||
| "ovk_candidate_sha": sha, | ||
| "pin": f"fraware/open-verification-kernel@{sha}", | ||
| "workflow_digests": scenario_digests, | ||
| "note": "Candidate-bound consumer pin evidence; consumer_source_sha records the exact checkout.", | ||
| } | ||
| out = pathlib.Path("../.verification") / f"consumer-pin-{repo.replace('/', '_')}.json" | ||
| out.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8") | ||
| print(out) | ||
| PY | ||
| - name: Upload consumer pin evidence | ||
| uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 | ||
| with: | ||
| name: consumer-pin-${{ strategy.job-index }} | ||
| path: .verification/consumer-pin-*.json | ||
| if-no-files-found: error | ||