ci(ga): GA evidence producer for VER-001 + SUPPLY-001

Merged
YA
2mo ago
+704-0

Why

E1-HANDSHAKE.md P2 row 3 (Instinct external GA validator epic, control repo
wave-av/claude-workstation) needs a GA-evidence producer for this repo. wave-av/sdks is the
only repo in the org that emits GA evidence today (registry clean-room acceptance). This PR
gives wave-av/sdk-python its own producer, on the same pattern, for the two criteria this repo
owns from the canonical gate spec: VER-001 (version/release truth agrees from source through
deployment) and SUPPLY-001 (release artifacts carry verifiable provenance from approved CI).

What each check verifies, and what stays unknown

VER-001 (scripts/ga/check_ver_001.py) compares five independent sources: HEAD's
pyproject.toml version, PyPI's published info.version, the newest v* tag on GitHub, that
tag's GitHub Release (if any), and the published wheel's METADATA Version (downloaded fresh
from PyPI, sha256-verified against the index's declared digest — never read from this checkout).
pass only when all agree. A release PR whose pyproject.toml is legitimately ahead of what's
published is unknown ("unreleased source"), never fail — that branch is explicit in the code
and is exactly what this PR's own real run hits (see receipts below: this repo's HEAD is
2.1.0, PyPI still serves 2.0.0, and the v2.1.0 tag has no GitHub Release object yet).

SUPPLY-001 (scripts/ga/check_supply_001.py) queries the public PyPI Integrity API
(/integrity/<pkg>/<version>/<file>/provenance) for every published artifact (wheel + sdist) and
requires the attestation to claim github.com/wave-av/sdk-python as its source repository. This
producer machine-verifies the provenance clause only. SBOM attachment and critical-vuln
resolution are explicitly out of scope and are never assumed: a fully-verified provenance still
yields unknown (never pass) with failing_checks: ["SBOM attachment not verified", ...].
Absent or mismatched provenance is fail. Real run: PyPI serves no attestations for wave-sdk
today ({"message": "No provenance available..."} from the Integrity API for both the 2.0.0
wheel and sdist), so this PR's own local run reports SUPPLY-001 as fail — an honest, real
finding, not a placeholder.

Receipts

Schema-valid on the real run:

$ python3 scripts/ga/ga_evidence.py --out-dir ga-out
UNKNOWN VER-001: wheel-digest-matches-index=True; wheel-metadata-matches-pypi-version=True; newest-tag-exists=True; newest-tag-has-github-release=None; head-version-matches-published=None; newest-tag-matches-published=None
FAIL SUPPLY-001: provenance-present:wave_sdk-2.0.0-py3-none-any.whl=False; provenance-present:wave_sdk-2.0.0.tar.gz=False
evidence fingerprint: 0857e434bbb6e60cccd0ba2472d4252999fae5c4f2370b1d5b1d2690977cacc1
$ node governance/bin/ga-gate.mjs validate ga-out/wave-av__sdk-python.ga-evidence.json
ok    ga-out/wave-av__sdk-python.ga-evidence.json

A gate that cannot fail is not a gate — deliberately-broken input flips VER-001 to FAIL, exit 1:

$ GA_EXPECT_VERSION=9.9.9 scripts/ga/check-VER-001.sh
FAIL VER-001: expected-version-matches-published=False; wheel-digest-matches-index=True; ...
$ echo $?
1

(GA_EXPECT_VERSION pins the exact version a caller expects PyPI to now serve — the same lever
workflow_dispatch.inputs.expect_version exposes in the workflow, e.g. for a release job
verifying its own publish. Setting it wrong is the honest way to prove this check can actually
fail; the resulting document still validates against the schema.)

CI wiring

.github/workflows/ga-evidence.yml runs on pull_request, workflow_dispatch, and daily at
09:43 UTC (offset from a round hour to dodge shared registry rate-limit windows). Every action is
SHA-pinned (pins copied from wave-av/sdks's registry-cleanroom.yml). permissions: contents: read only. The ga-evidence-sdk-python artifact (ga-out/, retention 90 days,
if-no-files-found: warn) reaches claude-workstation via actions/upload-artifact today — the
cross-repo PR that lands ga-out/wave-av__sdk-python.ga-evidence.json into
governance/ga-gate/evidence/incoming/ in claude-workstation is a separate, credential-gated
step this PR does not attempt (this repo has no write access to that repo's default branch, and
shouldn't). Mirrors wave-av/sdks's fail-loud posture: no || true, no continue-on-error; a
final Enforce step turns a non-zero producer exit into a red job on every trigger, including
pull_request.

ga-out/ is gitignored — this repo had no .gitignore before this PR, so one was added scoped
to the producer's own output plus standard Python build artifacts.

Not done / out of scope here

  • No cross-repo PR into claude-workstation's evidence intake directory (credential-gated,
    separate step per the brief).
  • SBOM and vuln-resolution clauses of SUPPLY-001 are not machine-verified; always reported as
    unverified, never silently assumed passing.
  • Not merging this PR — opened for review per the epic's validator-lane process.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


[!NOTE]
Cursor Bugbot is generating a summary for commit 37d9ab7c193396b6b31dafee28635e3e8e6c3989. Configure here.

Addendum — 2431f38

The Enforce step previously failed this PR's job on ANY non-zero producer exit code, including exit 1, which means "a live GA criterion currently fails against the public PyPI/GitHub registries" — not a defect in this diff. Verified live on 2026-09-05: SUPPLY-001 fails today, so this PR's job was red from live-registry state unrelated to this branch's changes.

This commit passes EVENT: ${{ github.event_name }} alongside the existing exit-code output into the Enforce step's env: block. On pull_request, exit 1 now emits a ::warning and exits 0, keeping the job green while still surfacing the failing criterion in the log, job summary, and uploaded ga-evidence-sdk-python artifact. Exit 2 (the producer could not run at all) still fails the job on every trigger, and exit 1 on schedule/workflow_dispatch/push still fails the job too — those cases indicate the gate itself is untrustworthy, not just that a live criterion is red. The header comment and the Enforce step's inline comment were both updated to state this contract.