Skip to content

DRAFT landing: v0.4.0-rc.5 preparation (#265–#269 + release-provenance binding + 3 integration commits) - #270

Draft
ErenAri wants to merge 44 commits into
mainfrom
claude/v040-rc5-landing
Draft

ErenAri wants to merge 44 commits into
mainfrom
claude/v040-rc5-landing

Conversation

@ErenAri

@ErenAri ErenAri commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

DRAFT landing candidate for v0.4.0-rc.5. Merge only on an exact-head operator decision. Nothing here approves a tag, a release, a promotion, a designation or qualification. rc.4 stays NO-GO; its evidence never counts for rc.5.

Hosted CI cannot run. Every GitHub Actions job for this repository currently fails before starting: "The job was not started because your account is locked due to a billing issue" (first seen 2026-10-19T18:45Z). Any red check runs on this PR are that lock, not code results. All validation below is LOCAL, run on the maintainer's workstation. Branch protection requires 9 hosted contexts that cannot turn green while the lock lasts.

What this branch is

main 402d7c90b6c0 plus six --no-ff merges of unchanged source heads, then three attributed integration-only commits.

Order Source Exact head What it brings
1 #269 claude/installer-gh-capability 69cebeae6791d3c8f016cdb0a10ed3b68bce0778 Live installer (scripts/install.sh on main). It names a missing gh capability. Missing or invalid gh authentication (class 2) and an unreachable GitHub API (class 3) are now reported apart from a refused verification (class 4); every class still refuses to install. The probe is retried, bounded, on a 5xx or no response. gh-path installs are bound to the release's attested asset names (R-B)
2 #267 claude/docs-tag-bound-verification 2269647bdb10954a603ea62e2b8e41fa0be91815 Manual verification docs bound to the exact tag and signer; gh ≥ 2.68.0 stated
3 #268 claude/readiness-falco-earliest-target fdae550d4d24c920f19e7f6f3a26290bcdc184d1 Readiness: Falco dated from its first target; drill, incident and now canary runs looked up in attested mode. Canary checks: run, attempt, evidence job, release published_at, and the plan job's stated upper bound for timed canaries
4 #266 claude/stable-canary-pin-bootstrap 4cb4516bda42cf4877397518c5f780ded8dfe54a DEF-1–4: without it the v0.4.0 stable PR and tag build cannot pass required CI. Runbook
5 claude/release-provenance-binding (no separate PR) 5794ee48babe3dc1e41b6850605ec8c253b3485b Three fixes. promote-release.yml verifies the exact signer identity release-artifacts.yml@refs/tags/<tag> and cmp's the signature and SBOM files against the run artifact. verify-release-assets.sh and its callers, including the Action, bind attestations to the exact tag ref and commit (ported from the v0.3.8 maintenance line). The stable gate requires a completed rollback drill of this candidate
6 #265 claude/rc5-preflight-toolchain e1cbd9748e2878033c60bf2cc1e405890d1a4dce release.yaml 0.4.0-rc.5, the rc.5 CHANGELOG section, the offline AST-based RC preflight
I3 integration 4a1466d — The stable gate refuses readiness records that lack the canary run lookups (#268 fields × provenance gate)
I2 integration 94f0333 — Docs: the Action's tag-bound prebuilt verification and its warned source-build fallbacks (action.yml prebuilt, docs/verifying-releases.md); a re-run of a successful canary can void it (docs/production-release-process.md)
I1 integration 0826cde — The rc.5 CHANGELOG section reconciled with everything above. The date stays TBD

Not included, on purpose:

Source-to-union proof (LOCAL)

Validation at this head: all LOCAL, none hosted

  • CI replay: a replay of every run: step of ci.yml's test and build jobs ran 43 steps with 0 CI failures. Skipped: go mod download (vendored, offline). It covered actionlint, all shell regressions (installer 110 cases, release-workflow gates 66), go vet, go test -race, the coverage floor and the toolchain proof. The extra non-CI shellcheck reports SC2034 at scripts/check-canary-pins_test.sh:223; main has the same warning.

  • rc.5 tag build's "Release quality gates": the commands, replayed locally, all pass. That includes govulncheck: "No vulnerabilities found".

  • Real gh 2.68.0 and 2.102.0 (official, checksum-verified binaries; real v0.3.7 and rc.4 release bytes from a checksum-verified local mirror):

    • Installer: real v0.3.7 and rc.4 install. A tampered asset list is refused. Missing auth gives class 2 and a dead proxy gives class 3, not "verification FAILED".
    • verify-release-assets.sh: accepts rc.4 for refs/tags/v0.4.0-rc.4 with its commit; refuses it for refs/tags/v0.4.0-rc.5 or the wrong commit.
    • The Action's prebuilt step: binds tag pins and SHA pins; another release's genuine bytes are a hard failure.
  • Cross-component checks:

    • Release metadata and pins: release.yaml rc.5, CHANGELOG, make check-release-consistency and the canary and doc pins pass. The candidate pin stays on rc.4 by the bootstrap rule until the rc.5 tag exists; a simulated local tag build passes.
    • RC preflight: a read-only live run for rc.5 shows metadata VERIFIED and everything else PENDING.
    • Readiness and the stable gate: fresh attested-stub readiness output is accepted by the extracted stable gate, while bypass and pre-lookup outputs are refused.
  • Independent exact-head review of 0826cde: GO, no BLOCKER, no SHOULD-FIX, 5 NITs:

    • a trailing newline is accepted in the I3 timestamp regexp;
    • a ci.yml comment is not updated;
    • "(exit 2)" in the CHANGELOG holds only after the earlier checks pass;
    • the pre-existing SC2034;
    • one wrapped line.

    Every source head also had its own independent review, with no open BLOCKER or SHOULD-FIX.

  • Local equivalents of the 9 required contexts, run on this exact head (2026-10-19, local only, on the maintainer's host; hosted Actions are locked): all 9 PASS, and the non-required "Action diff mode (end-to-end)" job passes too. The jobs come from this commit's own workflow files, run step by step through a local job runner, against a local refs/pull/270/merge equivalent.

    Context Fidelity Notes
    Test (-race), Build (Go side), govulncheck, Generated support docs in sync, Experimental surface remains frozen same commands On main, the same harness matched the hosted jobs step for step
    golangci-lint same commands v2.12.2, with a cold cache
    Analyze (Go), CodeQL emulated CodeQL 2.27.2 replays codeql-action v4.37.4's CLI sequence. No new alerts versus main's full analysis; main's counts matched the hosted run
    Compatibility Gate (hosted runner) partial A real KVM VM booted a 5.15.0-186-generic guest; the validator digest is e0e7fcf8. There is no apt step, and it ran on the host kernel

    Planted failures (a data race, an unchecked error, a path-injection sink, a broken VM check, a deleted gate workflow) were each reported FAIL or NOT-RUN. This is local evidence, not hosted CI; GitHub's branch protection does not see it.

Never claimed here: hosted CI; tag-only signing or attestation; a VM or image qualification; any real-release consumer run at this head.

What merging changes immediately

  • The live installer and docs served from main switch to this version.
  • release.yaml reads 0.4.0-rc.5. It can be reverted, and nothing is published.
  • Nothing is signed, attested, tagged or released by a push to main. While the billing lock lasts, no workflow runs at all.

Decisions this PR does not make

Before the v0.4.0-rc.5 tag (the tag's copies of these workflows execute, and tags are immutable):

Before T0+20h: the timed-canary re-run question (tighten, keep as written, or relax).

The full ordered list is in the operator's decision packet.

Set release_version to 0.4.0-rc.5 (stable_version 0.3.7 and the
prerelease channel unchanged) and turn the Unreleased CHANGELOG section
into the 0.4.0-rc.5 section, dated when the tag is cut. Add the merged
changes it did not yet describe (installer verification bound to the
requested tag; pinned validator build and stable-must-ship-RC-validator;
per-report release gate; candidate-bound readiness evidence; CI token and
gate hardening), migration notes and known limitations.

The consumer-canary candidate pin stays on the published v0.4.0-rc.4
under check-canary-pins.sh's bootstrap rule until the rc.5 tag exists;
it must be repinned in the designation change after verification.

Signed-off-by: ErenAri <erenari27@gmail.com>
scripts/rc-preflight.sh reports each prerequisite of the RC sequence as
VERIFIED, MISSING or PENDING with its evidence: metadata, recorded
distribution decisions, tag and approved commit, tag build, staged
assets, SHA256SUMS membership, validator identity against the lock,
optional assets against the tagged policy, publication, designation on
main, canary runs in their 20-30h/68-78h windows, scheduled campaigns,
rollback drill, and the operator-held notes.

It only issues GETs (plus downloading the SHA256SUMS text), never
dispatches, writes or executes anything, aborts on a lookup failure
instead of reporting it as pending, and never turns elapsed time into
VERIFIED. Candidate binding stays with production-readiness-report.sh.
scripts/rc-preflight_test.sh covers it in CI.

Signed-off-by: ErenAri <erenari27@gmail.com>
CHANGELOG 0.4.0-rc.5: drop the readiness-sounding opening, scope the
validator byte-identity rule to the recorded tree, state the readiness
bindings as enforced (Falco validator binding reported only; an earlier
candidate's incident note accepted), and date the "all undecided" note.

rc-preflight.sh:
- abort (exit 3) on any non-JSON API body, undecodable file contents or
  failed asset download instead of reporting a status;
- a designated release-candidate canary must name the candidate's tag,
  commit, image digest (from the attested evidence), publication time
  and rcN artifact/report names, with no other candidate's names left;
- canary and campaign runs count only from the time the designation
  landed on main (PR merge time), and a canary run only if a job after
  its plan job succeeded;
- a rollback drill run stays PENDING: its candidate is not visible here.
Tests cover each case.

Signed-off-by: ErenAri <erenari27@gmail.com>
- A designated release-candidate canary must also use exactly the
  candidate commit in its published-action job; a pin left on another
  candidate keeps the designation PENDING.
- A missing consumer-canary candidate marker is reported instead of
  ending the script silently; unparsable candidate evidence aborts with
  exit 3.
- Canary windows accept a run started up to an hour before the window
  when its plan job accepted it (proven by an executed later job).
- CHANGELOG known-limitations wording names the stable-blocking
  decisions. Tests cover the new cases.

Signed-off-by: ErenAri <erenari27@gmail.com>
…chain

rc-preflight.sh built its workflow-pin parser with GOTOOLCHAIN=local and
GOSUMDB=off, so it worked only where Go at go.mod's version (1.26.9) is
first on PATH. With an older Go on PATH (go 1.22 on the operator
workstation) every post-publication run aborted (exit 3), and
rc-preflight_test.sh failed 31 checks outside CI's setup-go image.

Keep the build offline (GOPROXY=off, GOWORK=off, -mod=vendor) but leave
GOTOOLCHAIN as the caller set it and keep checksum verification, so Go
can switch to a go.mod toolchain already in the module cache. Verified
with go 1.22 on PATH: the parser builds with go1.26.9 and the build makes
no network connections.

The helper now exits 2, with nothing on stdout, on a usage error or an
unreadable file instead of printing PENDING, so the shell aborts rather
than reporting a pin status it never judged.

Tests: TestRunExitCodes; rc-preflight_test.sh checks the build
environment and that an unusable Go aborts the report.

Signed-off-by: ErenAri <erenari27@gmail.com>
- A candidate pin on a step or job whose if is literally false (false,
  ${{ false }}, 0, '', null) never runs, so it is PENDING, not VERIFIED.
- The build env pins GONOPROXY and GONOSUMDB to a pattern nothing matches,
  so a caller GOPRIVATE or GONOPROXY cannot make Go download an unverified
  toolchain despite GOPROXY=off.
- The canary's CANDIDATE_* bindings are read from the workflow-level env
  node by the helper (env mode), and a job or step env may not rebind them;
  run text can no longer stand in for them.
- Merge keys and case variants of jobs/steps/uses/if/env are rejected.
- The commit in a pin must be spelled exactly; the owner/repo match is
  ASCII case-insensitive only.
- Correct the fetch comment and the build comment, which overstated that
  no network is ever contacted.

Signed-off-by: ErenAri <erenari27@gmail.com>
- Any if that reads no context and calls no function is a constant; only
  the literal true runs. This covers every falsy spelling GitHub accepts
  (-0, 0.0, 0e0, 0x0, NaN, '', null, ...) instead of a fixed list.
- A job that needs a job that can never run is skipped unless its if calls
  always(), failure() or cancelled(); the skip is carried through chains
  of needs. A needs value that is not a name or a list of names is
  rejected.
- Case variants of workflow keywords (now including needs) are rejected
  only in the workflow, job and step mappings; env names, inputs and
  matrix keys are user data.
- The canary designation detail shows the value the parser found instead
  of the expected one, and the build abort names GOSUMDB=off as a cause.

Signed-off-by: ErenAri <erenari27@gmail.com>
…heck

check-canary-pins.sh always ran check_pin stable "v<stable_version>": the
stable pin had to carry that exact version comment and equal the commit the
tag resolves to. With no bootstrap for the stable pin (DEF-1), the stable
release PR (stable_version = release_version = X.Y.Z, release_channel
stable) fails required CI because vX.Y.Z is not a tag yet, and the vX.Y.Z
tag build fails too, because the pin would have to name the tagged commit
itself, which that commit cannot contain. docs/production-release-process.md
already says to repin the stable step once the new stable tag exists; the
check made that sequence impossible to follow.

Mirror the existing candidate bootstrap for the stable pin. With
release_channel stable, while vX.Y.Z positively does not exist, or resolves
to the commit under check (the tag build), the stable pin may instead name
the most recent published stable release before X.Y.Z: the newest vA.B.C,
A.B.C < X.Y.Z, listed by the GitHub releases API as non-draft and
non-prerelease, whose tag resolves to the pinned full SHA. As for the
candidate, a missing, mutable or short pin fails, and every lookup failure
fails closed: an unreachable or malformed tag answer, a pinned tag that
does not resolve, a failed or partial API listing, or a malformed line.
Once vX.Y.Z exists at any other commit the rule is strict exactly as
before, so the first commit after the tag must carry the repin. The
prerelease channel's stable pin stays strict, and the candidate rules are
unchanged.

check-canary-pins_test.sh: section 27 no longer asserts that the stable pin
never bootstraps; it now tags the new stable at another commit and asserts
the previous stable pin is still refused there. Sections 29-38 cover the
stable PR, the tag build (also with the v0.4.0 draft staged and after its
publication), the strict commit after the tag and its repin, pins on the
new or a later version, an older-than-latest stable, an rc, a draft, a
release marked prerelease, a tag without a release, a mismatched SHA, a
missing tag, mutable and short refs, a missing pin, each lookup failure,
and the unchanged prerelease channel. The gh stub also answers the paginated
release listing, for the exact projection the script requests. The new
sections are red against the unmodified script and green here. The
release process doc spells out the stable PR and tag build phase.

Signed-off-by: ErenAri <erenari27@gmail.com>
…rser

- An if that is empty or YAML null is no condition (GitHub runs it as
  success()), and true is matched case-insensitively, so such pins count.
- Only always() and !cancelled() let a job run after a skipped need, and
  a status function inside a quoted string no longer counts.
- Job IDs in needs compare case-insensitively; job IDs that differ only by
  case, needs on an unknown job, on the job itself or in a cycle are
  rejected, as GitHub rejects them.
- The CHANGELOG states that conditions calling functions are left to the
  runner; doc comments no longer contain '' (gofmt turned it into a curly
  quote).

Signed-off-by: ErenAri <erenari27@gmail.com>
The Stable Release runbook omitted two gates the stable tag build enforces
(every blocks_stable distribution decision recorded; the stable validator
byte-identical to the graduated RC's), and a v* tag cannot be reused after
a refused build. It also had the operator record the promotion marker after
checking the readiness report, while the report reads that record as an
input; the record now comes first and the promotion dispatch stays a
separate decision.

Signed-off-by: ErenAri <erenari27@gmail.com>
…flight

- The designated release-candidate canary must have a schedule cron that
  fires inside each timed window the plan job accepts, [T0+20h, T0+31h)
  and [T0+68h, T0+79h); the previous candidate's dated crons left in place
  would otherwise pass every other designation check while no timed canary
  can ever count. The helper gains a schedule mode with a small cron
  matcher (values, ranges, steps, lists, JAN-DEC/SUN-SAT names, the
  day-of-month/day-of-week OR rule); anything else is refused.
- campaign-candidate.json on main must also record this release's
  SHA256SUMS and candidate-evidence digests, which the scheduled campaigns
  bind.
- CHANGELOG: correct the rc.5 notes against the code: the Action's
  dot-directory rule is unchanged (now documented), only path and option
  inputs became literal (command is still run by the guest shell), the
  relink kit carries only the validator's own sources, the registry-ref
  rule needs a slash after the host, name:tag refs are affected too and the
  Action never pulled implicit short refs, the incident note's rejection is
  operator-asserted, the installer hardening is already live on main, and
  BPFCOMPAT_DOC_DIR/sudo behaviour.

Signed-off-by: ErenAri <erenari27@gmail.com>
The manual verification commands were weaker than the installer they sit
next to. cosign verify-blob accepted any tag's signature through an
'@refs/tags/v' regexp, gh attestation verify had no --source-ref, and the
README's container check used an unanchored identity regexp that any
workflow SAN containing 'github.com/Kernel-Guard/bpfcompat' satisfies.

- Blob signatures use the exact identity
  release-artifacts.yml@refs/tags/$VER, as scripts/install.sh does.
- gh attestation verify pins --signer-workflow and
  --source-ref "refs/tags/$VER" (README, verifying-releases, SBOM check).
- The README container check uses the exact signer of the pinned 0.3.7
  image (ghcr-image.yml@refs/tags/v0.3.7) and says that 0.3.5-0.3.7
  images were signed by ghcr-image.yml, while 0.3.8-rc.2, 0.4.0-rc.4 and
  later release-workflow images are signed by release-artifacts.yml.

README and quickstart pin @v0.3.7, so behaviour that ships only from
v0.4.0-rc.4 (promotion evidence, SBOM signature, Action attestation
verification) or v0.4.0-rc.5 (license bundle, relink kit, build record,
Action validator pinning, command-mode kernel-release INFRA_ERROR, missing
artifact paths never pulled) now carries that version.

Also: the arm64 CLI in the asset table; BPFCOMPAT_DOC_DIR and
/usr/libexec/bpfcompat in the installer notes; image tag example 0.3.7;
"the validator's own C sources" for the relink kit; the pinned validator
build, stable==RC validator and distribution-decision gates in the
production support boundary.

Signed-off-by: ErenAri <erenari27@gmail.com>
The artifact input description now says that a workspace path whose
first directory contains '.' or ':' (for example .output/) must be
prefixed with ./, or it is treated as a remote reference. The binary
input description says that an executable at that path in the workspace
is used instead of the verified prebuilt CLI. Both behaviours are
unchanged and already documented in docs/compatibility-contract.md;
only description strings change.

Signed-off-by: ErenAri <erenari27@gmail.com>
DEF-2, found by the independent review of DEF-1's fix: the release-
consistency gate requires the stable PR's docs to name vX.Y.Z, while
scripts/check-doc-pins.sh refused any tag-form pin whose tag does not exist,
so the stable PR could not pass required CI even with the canary-pin fix.

While release.yaml says release_channel stable, a tag-form doc pin on
exactly v<stable_version> is accepted when the remote positively has no
such tag (git ls-remote --exit-code returns 2). An unreachable remote,
another version, a SHA pin or the prerelease channel still fail. The check
gains its first regression test, run in CI.

The runbook also states the stable order and that no other stable release
may be published between the stable PR's CI and the tag build, because the
stable pin may only name the newest published stable below X.Y.Z.

Signed-off-by: ErenAri <erenari27@gmail.com>
production-readiness-report.sh required the Falco report's run.started_at
to postdate the candidate's generated_at. The runner writes run.started_at
when it assembles the report, after every target ran (runner.go:308-311);
in each preserved real report it is minutes later than the first target's
start. A run that began before the candidate was built could therefore be
accepted as starting after it, contradicting the written rule that the
Falco run must start after the candidate was built.

The run now starts at the earliest of run.started_at and every target's
started_at, and a report with a target that records no start time is
refused. The manifest still names the report's run.started_at, unchanged.

Signed-off-by: ErenAri <erenari27@gmail.com>
…led verification

The gh path probed only `gh attestation --help`, but its tag-bound check
also needs `gh attestation verify --signer-workflow` (gh 2.51.0) and
`--source-ref` (gh 2.68.0), with gh's errors discarded. So a host with
an older gh and no cosign got either "requires cosign or GitHub CLI (gh)"
(gh < 2.49, although gh is installed) or "GitHub attestation verification
FAILED" (gh 2.49-2.67), which reads like a tampered release.

When cosign is absent and gh is present, ask gh's own help whether it has
the attestation command and both flags (capability, not version parsing)
and, if not, refuse naming what it lacks and the remedy (gh >= 2.68.0 or
cosign). Still fail-closed; cosign is still preferred whenever present; a
real verification failure still says FAILED; the cosign identity, the
--source-ref value, checksum, license-bundle and sudo logic are unchanged.

The tag guard matched line by line, so a BPFCOMPAT_VERSION carrying a
valid tag on one line of a multi-line value passed it and was stopped only
by curl. Reject any character a tag cannot hold before the regex, so the
whole value is checked.

install_test.sh: the gh shim can play gh < 2.49 / < 2.51 / < 2.68; new
cases for each (and an unparsable version), for cosign still winning over
an old gh, and for newline-bearing tags.

Signed-off-by: ErenAri <erenari27@gmail.com>
…began

Review follow-up for the tag-bound verification docs.

- The gh attestation commands now need --source-ref, so the docs state
  the minimum: gh 2.68.0 (--signer-workflow arrived in 2.51.0,
  --source-ref in 2.68.0). An older gh stops with "unknown flag"
  without verifying anything; use cosign or upgrade. gh 2.97.0 and
  2.102.0 fixed published advisories in how these values are matched,
  so 2.102.0 or later is recommended.
- Promotion evidence, the SBOM signature and the Action's attestation
  check are published from v0.4.0-rc.2 and v0.3.8-rc.1 (not v0.3.7),
  not from v0.4.0-rc.4 as the qualifiers said.
- The README lists every release-artifacts.yml-signed image
  (0.3.8-rc.1, 0.3.8-rc.2, 0.4.0-rc.2, 0.4.0-rc.3, 0.4.0-rc.4) and notes
  that the 0.3 and latest aliases carry the 0.3.7 signer while they
  point at 0.3.7.
- BPFCOMPAT_DOC_DIR: the bundle goes to <BPFCOMPAT_DOC_DIR>/<tag>/.
- The validator==RC and distribution-decision gates are stable-only.
- Checksum checks use --ignore-missing where only some assets are
  downloaded; only bpfcompat-linux-amd64 carries an SBOM attestation.
- action.yml: the artifact description also names a localhost first
  directory, as is_remote_artifact does.

Signed-off-by: ErenAri <erenari27@gmail.com>
Fourth exact-head review findings:
- A schedule entry with any key besides cron (GitHub's timezone, for one)
  is refused: only cron is evaluated, in UTC, so a timezone would move the
  real firing time outside the window while the check said VERIFIED.
- Cron fields are stricter: names only in their own field, digits only (no
  +N), steps bounded by the field's range (a huge step crashed the helper),
  the documented a/n form, day of week 0-6, and a day field starting with *
  is unrestricted for the day-of-month/day-of-week rule, as in cron.
- A campaign-candidate.json that is JSON but not an object aborts the
  report (exit 3) instead of ending it with a truncated jq error.
- CHANGELOG: an empty if is no condition; the incident note's run is named,
  not checked.

Signed-off-by: ErenAri <erenari27@gmail.com>
Review of the previous commit: the runner writes no started_at for a
target it never runs, such as an optional profile the default runner cannot
boot (status unsupported; fail in v0.3.7), and the shipped expanded matrix
has three. Refusing every target without started_at would refuse such a
legitimate report. Now every required or passing target must record
started_at, and the earliest recorded start is the run's start.

The runbook describes the rule, and the test gains a positive case shaped
like a real report (targets start before run.started_at) with an unbooted
optional profile.

Signed-off-by: ErenAri <erenari27@gmail.com>
Written policy requires the rollback evidence to be the note
rollback-drill.yml writes and the incident note to link the deliberately
rejected promote-release run. The readiness report checked only that each
was a well-formed run ID, so (verified by reproduction) a run ID that does
not exist, or a successful ci.yml run, was accepted with attestations
verified.

With attestation verification on, the drill run must now be a successful
rollback-drill.yml run on main and the incident run a completed
promote-release.yml run that did not succeed; an unreadable run fails.
Which rejection qualifies stays the operator's choice; a successful or
unrelated run never does. The test-only bypass is unchanged.

Signed-off-by: ErenAri <erenari27@gmail.com>
… stable window

DEF-3, found by the independent review of f61c291's simulated stable PR:
the docs/integrations templates are pinned by commit SHA (their README
makes that policy), so they cannot name vX.Y.Z before its tag exists, and
check-doc-pins.sh's one-version rule then refused the stable PR ("docs pin
more than one bpfcompat version").

During the stable window -- the stable PR while vX.Y.Z is absent, and the
vX.Y.Z tag build itself -- exactly one other documented version may sit
beside v<stable_version>: the newest earlier stable release tag on the
remote. Every later commit is strict, so the templates are repinned with the
consumer canary once the tag exists (now stated in the runbook).

check-release-consistency_test.sh no longer hard-codes 0.3.7 as the stable
channel's version; it reads stable_version from release.yaml, so the stable
PR does not have to edit the test.

Signed-off-by: ErenAri <erenari27@gmail.com>
…ll is made

Review follow-ups for the gh capability probe:

- A gh that cannot run at all (every command fails, e.g. an unreadable
  ~/.config/gh/config.yml) was told it "lacks the 'gh attestation'
  command" and to upgrade. When `gh attestation --help` fails, the
  installer now also runs `gh --help`; if that fails too it refuses with
  "installed gh could not run 'gh --help' (<gh's first error line>); fix
  gh or install cosign". Still fail-closed; nothing else changes.
- Comment: `gh attestation` exists, hidden, from gh 2.47.0 (public from
  2.49.0). Behaviour is unchanged: on 2.47/2.48 the flag probe already
  names --signer-workflow and --source-ref as missing.

install_test.sh: removing or neutralising one gh verify call used to go
unnoticed (only the whole gh block and the bundle call were guarded).
The gh stub now records every verify call; the gh-path install cases
assert that exactly the installed assets (CLI, validator, and the license
bundle when listed) were verified, each with the release workflow and
--source-ref refs/tags/<tag>, and the cosign cases that gh verified
nothing. New cases swap only the CLI, or only the validator, for bytes
attested by another tag's run (SHA256SUMS regenerated to match), so each
call must refuse on its own. Also: a gh that cannot run, with and
without cosign, and a root `--help` in the stub as real gh has.

Mutation check (scratch copies of install.sh): replacing a verify call
with `true`, turning its `die` into a warning, or dropping its
--source-ref, for each of the three calls, fails this suite (9/9); the
previous suite caught 2/9.

Signed-off-by: ErenAri <erenari27@gmail.com>
…and time

Review of e033fe6: the drill lookup accepted a fork's pull-request run from
a branch named main, any old rollback-drill run, and a note whose Completed
time was unrelated to the run (2099). The drill run must now be a
dispatched run of this repository that started after the candidate was
built, with the note's Completed time inside it; the incident run must be
a dispatched promote-release run of this repository. The runbook says the
lookups need the network and an authenticated gh.

Signed-off-by: ErenAri <erenari27@gmail.com>
Review of 35d6ebe: the Action falls back to a source build when gh cannot
verify attestations only in the v0.4.0 release candidates; the v0.3.8
candidates' Action fails the job instead. Also name the error an older gh
prints before 2.49.0 (unknown command "attestation").

Signed-off-by: ErenAri <erenari27@gmail.com>
gh 2.47.0 and 2.48.0 already run the hidden attestation command, so they
fail with unknown flag, like every gh before 2.68.0.

Signed-off-by: ErenAri <erenari27@gmail.com>
Review of 2273204 (full stable-sequence simulation):
- DEF-4: check-release-consistency_test.sh still asserted that the current
  tree is on the prerelease channel, so the stable PR's required CI, the
  push to main and the v0.4.0 tag build's quality gates would fail. It now
  reads release_channel from release.yaml.
- The stable window's second documented version is now the release the
  consumer canary's '# bpfcompat-pin: stable' step names, which
  check-canary-pins.sh verifies is the newest published stable release, so
  the two checks cannot disagree (a stable tag without a published release
  no longer qualifies), and no remote tag listing is involved.
- Only commit-SHA pins count toward that second version; a tag-form pin on
  the previous release is refused as before.
- The ci.yml step comment and the runbook describe the window precisely;
  docs name an unreleased version until promotion, not only until the tag.

Signed-off-by: ErenAri <erenari27@gmail.com>
…s does

Review of b9149e7: on a canary line with two version comments
("# v0.3.7 # v0.3.6"), check-canary-pins.sh read the first and
check-doc-pins.sh the last, so the stable window could admit templates on
a different release than the verified canary pin. Both now read the
first, and a test pins it.

Signed-off-by: ErenAri <erenari27@gmail.com>
…fication

On the gh path every `gh attestation verify` failure was reported as
"GitHub attestation verification FAILED for <asset>", with gh's output
sent to /dev/null. A capable gh that is not logged in, holds a rejected
token, or cannot reach the GitHub API got the same tampering-like words
as a release whose attestation does not match (review S2).

The installer now keeps four outcomes apart, and every one still
refuses (exit 1, nothing installed, temp dir removed, no checksum-only
fallback, no retry):

1. gh lacks the attestation command or flags: the existing help probe,
   "installed gh <ver> lacks ...", unchanged.
2. No usable credentials: "GitHub authentication needed for attestation
   verification: gh is not logged in (<gh's first line>)", or "GitHub
   rejected gh's credentials (HTTP 401)".
3. GitHub API out of reach: "GitHub API unavailable for attestation
   verification: no HTTP response | HTTP 5xx | HTTP 429, rate limited |
   HTTP 403, rate limited (<gh's first line>)".
4. Anything else, including no attestation for the digest (404), another
   signer workflow or source ref, or a digest mismatch: the existing
   "GitHub attestation verification FAILED for <asset>", unchanged.

Classes 2 and 3 rest only on positive evidence from one read-only
request made before the first verify call: `gh api --method GET
--include repos/<repo>/attestations/sha256:<CLI digest>?per_page=1`,
the endpoint that call queries, through the same gh, host, token and
proxy. gh exits 4 only from its root auth check (internal/ghcmd/cmd.go,
pkg/cmd/root/root.go at v2.68.0 and v2.102.0), which also guards
`attestation verify` (only --bundle lifts it). With --include the first
stdout line is the status line gh builds from the response, and a
transport failure exits 1 with empty stdout. A 403 counts only with
GitHub's X-Ratelimit-Remaining: 0 or Retry-After header. Any other
answer leaves the decision to the verify calls, unchanged (same assets,
--repo, --signer-workflow, --source-ref refs/tags/<tag>), whose failures
other than exit code 4 always read "verification FAILED".

gh's verify text is never parsed: with stdout not a terminal it prints
only its final error, worded differently by 2.68 and 2.102 for the same
401 or 404, and on 2.102 a dead proxy fails before the API fetch with a
Sigstore message. Probing before verifying, not after a failure, means
a refusal can never be relabelled as an auth or network problem when
the verify itself spends a nearly exhausted rate limit or a token
expires in between. Cost: one extra GET on the gh path (about 240 KB for
v0.3.7's CLI, next to about 22 MB of downloads). The cosign path is
unchanged and still runs no gh command.

gh's words are quoted as one line with control characters removed, also
in the "could not run 'gh --help'" refusal (review R2-N2), and the
gh_err and gh_ver captures tolerate an imported pipefail (R2-N1).

install_test.sh: the gh stub answers the probe from the attestation DB
and can play a gh that is not logged in (exit 4), holds a rejected token
(401), gets no response, a 503, a 429, a rate-limited 403 (also with
coloured headers) or a plain 403, a verify that exits 4, and a refusing
verify whose stderr claims a network or auth error. 35 new cases check
the class named, the absence of the other classes' wording, nothing
installed, the installer's temp dir removed, no control bytes, exactly
one probe made before any verify call, the cosign path untouched in
every mode, and imported pipefail. The 45 existing cases pass unchanged;
against f8809a9's install.sh the new suite fails 24 cases. All 34
mutants of the change are caught (a refusal reworded as network or
auth, a die removed on the auth or network path, a 404 taken for an
outage, the probe ignored or removed, S1 regressions on the new calls).

Real gh 2.68.0 and 2.102.0 (official tarballs) with the real v0.3.7
assets: no token and an invalid token give class 2, a dead HTTPS proxy
class 3, a modified CLI or rc.4's assets served as v0.3.7 class 4, and
the real release installs. f8809a9 said "FAILED" for the first three.

Signed-off-by: ErenAri <erenari27@gmail.com>
…date

A tag's own copies of these checks are what run for that release (the
release build, its promotion, and the Action pinned to it), so they must
be in the commit that is tagged.

- promote-release: compare SHA256SUMS.sig, SHA256SUMS.crt and the SBOM
  json/sig/crt in the draft with the candidate run's copies, and verify
  the cosign signatures (both blobs and the image) with the exact
  release-artifacts.yml@refs/tags/<tag> identity instead of any v* tag.
  The identity lines are the ones 4a51550 ships on the v0.3.8 line
  (v0.3.8-rc.1, v0.3.8-rc.2). The evidence attestation and
  verify-release-assets.sh are bound to the tag ref and expected commit.
- verify-release-assets.sh: require BPFCOMPAT_EXPECTED_SOURCE_SHA and
  BPFCOMPAT_EXPECTED_SOURCE_REF and pass them as --source-digest and
  --source-ref (as in 4a51550). The tag build's two gates pass the run's
  commit and ref; maintenance-evidence already did.
- action.yml: before verifying prebuilt assets, check that gh offers
  --signer-workflow, --source-ref and --source-digest, and resolve the
  pinned commit (the SHA pin itself, or git ls-remote of the tag, peeled).
  If either is unavailable, take the existing warned source build; never
  an unbound prebuilt install and never a job failure.
- release-artifacts stable gate: require a completed rollback drill of the
  release's own candidate (drill alias and image digest), next to the
  incident term it already checks.
- tests: new release-workflow-gates_test.sh (stable gate cases and the
  promotion steps' run bodies against stub cosign/gh/docker, plus caller
  wiring), bound-identity cases in verify-release-assets_test.sh, and
  tag/SHA-pin, ls-remote failure, gh capability and real-verifier cases in
  the Action tests.

Signed-off-by: ErenAri <erenari27@gmail.com>
The RC canary installs through the gh path, where one transient 5xx or
dropped connection at the read-only API probe ended the install, although
gh's own verify retries a 5xx. The probe now makes up to three attempts,
3 s apart (6 s of waiting at most), for a 5xx or no HTTP response only,
and the last attempt decides. A 429 or rate-limited 403 is not retried:
GitHub asks the client to wait longer than an installer should, and
asking again sooner spends more of the limit. 401, 404 and 200 are
answers. Every outcome still refuses or verifies as before.

Text quoted from gh is reduced to printable ASCII by an allow-list (octal,
because busybox tr has no [:print:] class), so C1 controls such as a raw
or UTF-8 encoded CSI no longer pass.

Tests: retry sequences (5xx then 200; 502, 503, 200; no response then
200; 5xx then 401, 429 or 404; no response then a rate-limited 403 or a
refusal) with exact attempt counts and waits; C1 bytes in both quoting
places; and the defensive branches no case pinned: a probe exit other
than 1 or 4 with no output, exit 1 with output that is no status line,
HTTP/1.1 status lines, and a verify exit other than 0, 1 or 4.

Signed-off-by: ErenAri <erenari27@gmail.com>
The promotion cases only used signatures from v0.4.0-rc.4 and v9.9.9-rogue,
which no regexp derived from the v0.4.0-rc.5 identity matches. A regexp of
the identity, even start-anchored, therefore passed every case, although it
accepts a signature from a newly created tag such as v0.4.0-rc.50-rogue.

- release-workflow-gates_test.sh: sign SHA256SUMS, the SBOM and the image,
  one site at a time, by four identities that are close to but not exactly
  this tag's release run: a tag extending the release tag, a tag differing
  only at a dot, the release tag in another repository, and another
  workflow on the release tag. For the blobs the run artifact and the draft
  agree, so the identity check is the only barrier. Each must be refused
  for the identity reason, and every identity must be used at all three
  sites.
- verify-release-assets_test.sh: a tag ref under another ref and a tag ref
  with a trailing path are usage errors, which pins both anchors of the
  ref check.

No workflow or script changes; the exact --certificate-identity already
refuses every new case.

Signed-off-by: ErenAri <erenari27@gmail.com>
Every curl in the prebuilt step has --connect-timeout and --max-time, but
the git ls-remote that resolves a tag pin to its commit had no bound: a
stalled HTTPS connection would hold the consumer's job until the job
timeout instead of taking the warned source build.

Run it under timeout 60, the bound of the tags API call in the same step.
An expired limit is an ls-remote failure, which already takes the warned
source build; verification and the prebuilt install are unchanged.

git's low-speed limit (GIT_HTTP_LOW_SPEED_*) was not used: it aborts a
stalled transfer but not a stalled TLS handshake, which libcurl only ends
at its 300 s connect timeout.

The Action test records, for every ls-remote, the prompt setting and the
command line of the process that ran git, and requires timeout(1) with a
limit of at most 300 s, the step's longest curl bound.

Signed-off-by: ErenAri <erenari27@gmail.com>
On the gh path SHA256SUMS is unsigned, and gh attestation verify matches
an artifact by digest only. So a party able to change release assets
could delete the validator or license-bundle line (a CLI-only install
with a false "not published" note), or serve one genuine asset of the
tag under another asset's name, and the install still passed.

The CLI's verify call now also prints the subjects of its verified
provenance (--format json --jq, as "<sha256>  <name>" lines), which name
every asset the tag's release run attested. The installer refuses, as a
failed verification naming the mismatch, when SHA256SUMS and those
subjects disagree on whether the validator or the license bundle exists,
or when a file to be installed is not attested under its own name. That
call's stderr is still kept for the auth classification. The cosign path
is unchanged.

Tests: the gh stub prints the subjects of every statement that holds the
verified digest. Refused: the validator line deleted (v0.3.7 and rc.5
shapes), the bundle line deleted, the arm64 CLI served as the amd64 CLI,
the CLI served as the validator, an arm64 validator line added on an
aarch64 host, and bytes from another statement of the run served as the
validator or as the CLI; each also refused by cosign. Installed: a CLI-only
install, an aarch64 host and a re-run release with two statements.

Signed-off-by: ErenAri <erenari27@gmail.com>
Written policy says a canary is a run of the temporary
release-candidate-canary workflow, the timed ones from its dated schedule,
and that its plan job admits T+24h only 20-30 and T+72h only 68-78 whole
hours after publication. Readiness checked only fields the canary evidence
reports about itself and made no run lookup. A ci.yml, branch, fork,
dispatched, failed or nonexistent run was accepted, and a partial re-run of
a failed in-window run, which keeps the plan job's earlier decision, could
write evidence long after the window closed (F6).

With attestation verification on, readiness now reads publication (T0)
from the candidate release, which must be published, not a draft, and not
before generated_at. For each canary it reads the run, its attempts and the
chosen attempt's jobs:
- the run is a successful release-candidate-canary.yml run of this
  repository from workflow_dispatch (manual) or schedule (timed, on main);
- the evidence's completed_at falls inside a successful attempt, in which
  the "Assemble RC canary evidence" job itself ran and succeeded;
- that attempt started at or after T0, and a timed attempt started before
  its window closed (T0+31h, T0+79h); a first attempt, which ran its own
  plan job, also completed after the window opened (T0+20h, T0+68h).

The attempt, its start, the run's branch and T0 are recorded in the JSON
and Markdown. No written rule names the manual dispatch's branch, so it is
recorded, not enforced. A re-run started inside the window is accepted, as
the windows are written; whether re-runs should count stays an open
operator decision. The bypass mode is unchanged.

The test's gh stub answers the new lookups in this same commit, because
the test runs in ci.yml and in the release quality gates. The runbook no
longer says the plan job alone bounds re-runs and describes the new checks.

Signed-off-by: ErenAri <erenari27@gmail.com>
…0-rc.5 landing branch

Source head: 69cebea. Merged unchanged; no conflict resolution.

Signed-off-by: ErenAri <erenari27@gmail.com>
…0.4.0-rc.5 landing branch

Source head: 2269647. Merged unchanged; no conflict resolution.

Signed-off-by: ErenAri <erenari27@gmail.com>
…he v0.4.0-rc.5 landing branch

Source head: fdae550. Merged unchanged; no conflict resolution.

Signed-off-by: ErenAri <erenari27@gmail.com>
…0.4.0-rc.5 landing branch

Source head: 4cb4516. Merged unchanged; no conflict resolution.

Signed-off-by: ErenAri <erenari27@gmail.com>
…0.4.0-rc.5 landing branch

Source head: 5794ee4. Merged unchanged; no conflict resolution.

Signed-off-by: ErenAri <erenari27@gmail.com>
…0-rc.5 landing branch

Source head: e1cbd97. Merged unchanged; no conflict resolution.

Signed-off-by: ErenAri <erenari27@gmail.com>
… run lookups

Integration-only commit on the v0.4.0-rc.5 landing branch; not part of any source PR.

Reconciles #268 (fdae550) with claude/release-provenance-binding (5794ee4).
#268's production-readiness-report.sh looks up the candidate release and
every canary run, attempt and evidence job in attested mode, and records
candidate.published_at and each canary's workflow_run_attempt,
attempt_started_at and head_branch. The provenance branch's stable gate in
release-artifacts.yml ("Stage production readiness evidence") read only
`.canaries | length == 3`, so a readiness record generated by a checkout
from before those lookups (main 402d7c9, #268 7f7a4ef) still passed the
v0.4.0 tag build.

The gate now also requires every canary's workflow_run_attempt to be a
positive integer and its attempt_started_at a UTC timestamp, and
candidate.published_at a UTC timestamp. The attested report writes these
only after the lookups succeed (production-readiness-report.sh:390-391,
:492-504, :600-602, :858 at fdae550); the attestation bypass writes null
and older reports omit the keys. head_branch is not required: the manual
canary's branch is recorded, not enforced. No other gate term changes.

scripts/release-workflow-gates_test.sh: the fixture is regenerated as the
unedited attested output of this branch's production-readiness-report.sh
for production-readiness-report_test.sh's input. 14 new refusals: no
lookups at all, the bypass's nulls with attestations_verified true, and
each field absent, null or malformed on one or every canary. 2 new
acceptances: an in-window re-run recorded as attempt 2, and a manual
canary dispatched on a branch. Against the unchanged gate exactly the 14
refusals fail; with the new terms the test passes, and
production-readiness-report_test.sh still extracts the gate and accepts
its freshly generated attested output.

Signed-off-by: ErenAri <erenari27@gmail.com>
…e-run voiding

Integration-only commit on the v0.4.0-rc.5 landing branch; not part of any source PR.

Reconciles claude/release-provenance-binding (5794ee4) with #267
(2269647) and #268 (fdae550): wording each owner deferred to the union.

- action.yml `prebuilt` description (#267 owns the input descriptions,
  the provenance branch the prebuilt step): the attestations are bound to
  the pinned release's tag and commit, and gh older than 2.68.0, a tag pin
  that cannot be resolved to its commit, or a resolution taking more than
  60 s gives the warned source build (action.yml:223-230, :241, :251-259
  at 5794ee4). Provenance review N3.
- docs/verifying-releases.md (#267's Action section): from v0.4.0-rc.5
  the Action passes --source-ref refs/tags/<tag> and --source-digest
  <commit>, with the same fallbacks; the v0.4.0-rc.2 to rc.4 Actions do
  not bind the tag. T3 remedy item 5. docs/compatibility-contract.md is
  unchanged: with the provenance branch its rc.5 sentence ("other bytes
  fail closed") is accurate as written.
- docs/production-release-process.md (#268's Canaries bullet): GitHub
  reports a run's conclusion from its latest attempt, so a failed re-run
  of a successful canary voids it under the readiness run check
  (production-readiness-report.sh:482 at fdae550). Readiness review N1.

make check-docs-drift, scripts/check-doc-pins.sh and
scripts/check-doc-pins_test.sh pass.

Signed-off-by: ErenAri <erenari27@gmail.com>
…ranch

Integration-only commit on the v0.4.0-rc.5 landing branch; not part of any source PR.

Reconciles #265's rc.5 section (e1cbd97; #265 owns CHANGELOG.md) with
what the other sources bring into this branch: #266 (4cb4516), #267
(2269647), #268 (fdae550), #269 (69cebea),
claude/release-provenance-binding (5794ee4) and this branch's stable-gate
commit 4a1466d. The heading and its TBD date are unchanged.

- Corrects CHANGELOG.md:110-112 at e1cbd97 ("asserted by the operator,
  not checked"): with attestation verification on, #268 reads the
  incident run and requires a completed, dispatched promote-release.yml
  run of this repository that did not succeed
  (production-readiness-report.sh:685-688 at fdae550). Decision-packet
  critique M3.
- Security: the installer's gh path checks SHA256SUMS against the CLI's
  attested subjects (#269); the Action and verify-release-assets.sh bind
  attestations to the tag ref and commit, promotion checks the signature
  files and the exact signer identity, and the stable gate requires the
  rollback drill and the canary run lookups (provenance branch, 4a1466d).
- Fixed: the installer's gh failure classes, probe retry and whole-value
  tag check (#269); the tag-bound manual verification commands (#267).
- Changed: #268's Falco start and drill, incident and canary run
  lookups; #266's stable-window consistency checks and runbook order.
- Migration notes: the Action's prebuilt path needs gh 2.68.0 or later
  (and git and timeout for a tag pin) or takes the warned source build;
  verify-release-assets.sh callers must set its two variables; a
  readiness record from an earlier report must be regenerated.

The wording starts from the writers' proposed lines (installer round 2,
provenance round 1, readiness R1) and was checked against the code on
this branch.

Signed-off-by: ErenAri <erenari27@gmail.com>
@coderabbitai

coderabbitai Bot commented Oct 19, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant