Skip to content

release: prepare v0.4.0-rc.5 metadata and an offline, AST-based RC preflight (replaces #264) - #265

Draft
ErenAri wants to merge 11 commits into
mainfrom
claude/rc5-preflight-toolchain
Draft

ErenAri wants to merge 11 commits into
mainfrom
claude/rc5-preflight-toolchain

Conversation

@ErenAri

@ErenAri ErenAri commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

Draft rc.5 preparation PR. Merge only on an exact-head operator decision. It replaces #264: this branch is #264's exact head (115a3745) plus six fix commits. #264 is not rewritten or closed; the maintainer chooses which draft to keep. Nothing here approves rc.5 publication or qualification. The CHANGELOG date stays TBD until the final update before tagging.

What it carries

From #264, unchanged:

  • release.yaml release_version: 0.4.0-rc.5;
  • the CHANGELOG ## [0.4.0-rc.5] - TBD section;
  • the read-only scripts/rc-preflight.sh inventory and its test;
  • the YAML-AST pin parser internal/tools/rcpreflight;
  • a CI step.

The six fix commits:

Commit Change
135889f The parser builds offline with a cached go.mod toolchain. #264 forced GOTOOLCHAIN=local, so every post-publication preflight aborted with an older Go on PATH; #264's own test failed 29 checks there. Usage and IO errors now exit 2 instead of printing PENDING.
334ce96 Review 1: pins under a literally-false if no longer count. The build cannot download a toolchain even with a caller GOPRIVATE/GONOPROXY (GONOPROXY/GONOSUMDB pinned to a non-matching pattern). The canary's CANDIDATE_* env is read from the YAML tree (env mode). Merge keys are rejected, and the commit is matched exactly.
a6b0918 Review 2: every constant if other than true counts as never running. Skips propagate through needs. The case-variant check covers only workflow, job and step keys. Diagnostics show the value found.
0f8f3b2 Review 3: matches GitHub's semantics. An empty or YAML-null if is no condition, and true is matched case-insensitively. Only always() and !cancelled() resume after a skipped need, and a quoted status function does not count. needs job IDs compare case-insensitively. Unknown, self or cyclic needs and job IDs differing only by case are rejected.
3eb880c Canary/campaign and documentation analysis. The designated canary's crons must each fire inside a timed window the plan job accepts, [T0+20h, T0+31h) and [T0+68h, T0+79h): the helper gains a schedule mode with a small cron matcher. campaign-candidate.json must record this release's SHA256SUMS and candidate-evidence digests. The rc.5 CHANGELOG notes are corrected against the code.
e1cbd97 Review 4. A schedule entry with any key besides cron (GitHub's timezone, for one) is refused, because only cron is evaluated, in UTC. The cron fields are stricter: names only in their own field, digits only, steps bounded by the field 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, as in cron. A non-object campaign-candidate.json aborts the report instead of truncating it. CHANGELOG wording fixed.

Validation at e1cbd97

  • scripts/rc-preflight_test.sh: 45/45 with the workstation's default Go 1.22 on PATH, and 45/45 with GOTOOLCHAIN=go1.26.9. The checks added in 3eb880c (stale campaign digest, stale crons) fail against 0f8f3b2.
  • Helper unit tests pass with -race. The new cases of each fix commit fail against its parent, except two of e1cbd97's eight, which also pass on 3eb880c (the review's R5-N2). Logs are in the task record.
  • All 47 combined local checks pass in the author's environment: golangci-lint built with Go 1.26.9, race tests, coverage, release consistency, canary pins, docs, actionlint, freeze, and every CI shell test. Reviewers' local golangci-lint could not run, being built with an older Go; CI's golangci-lint is green.
  • Historical corpus test (independent test specialist, at a6b0918): the parser agreed with independently derived designations on every revision of consumer-canary.yml (9) and release-candidate-canary.yml (7) on main, plus 4 branch-tip versions. That is 348 runs, 0 disagreements.
  • Live read-only preflight:
    • for rc.4 with --commit 222c3f38…, it exits 1. The three designations are VERIFIED from the YAML tree, now including rc.4's real campaign digests and its two crons inside both windows, and the three rc.4 canary runs are VERIFIED. The newer assets and policy file are MISSING, as expected for rc.4.
    • for rc.5, it exits 0, with everything after the metadata PENDING.
  • Reviews: independent exact-head reviews of 135889f, 334ce96, a6b0918, 3eb880c and e1cbd97. The last two found no blocker, and review 5 (e1cbd97) found no should-fix and judged release: prepare v0.4.0-rc.5 metadata and an offline, AST-based RC preflight (replaces #264) #265 ready for the merge decision. Their findings are closed above, except the limitations below.

Known limitations

  • Cron spacing: a cron with a tab, a leading space or cron: | block style is accepted, although GitHub's published cron validator rejects it (review 5, R5-N1). The designation should use single-spaced, double-quoted UTC crons with no timezone key.
  • Both day fields restricted: the checker assumes either one may match (Vixie cron). The canary's M H D Mon * shape is unaffected.
  • Not evaluated: conditions that call functions (fromJSON('false'), always() && false) or read a context are left to the runner; only constants are judged.
  • Env rebinding the static check cannot see:
    • $GITHUB_ENV writes;
    • jobs.<id>.container.env;
    • case-variant env names (Linux env is case-sensitive; this was not verified on a runner).
  • Schedules: only cron is evaluated, in UTC; an entry with timezone or any other key is refused. The rc.5 designation should write UTC crons with no timezone.
  • Delay margin: the cron check asks only that a cron fires inside each window. How much schedule-delay margin to leave is the operator's choice when designating. The precedent offsets of +24h15m and +72h15m absorb 6h45m of delay.
  • Elsewhere in the preflight (accepted earlier):
    • the plan job is found by its display name;
    • releases are read from one page of 100;
    • the shell test skips itself once release.yaml is no longer a prerelease.
  • Separate draft PR release: let the stable PR and tag build pass the stable canary-pin and doc-pin checks #266: the stable track's canary-pin self-reference (DEF-1) is fixed there, not here.

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>
@coderabbitai

coderabbitai Bot commented Oct 18, 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.

- 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>
…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>
…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>
@ErenAri ErenAri changed the title release: rc.5 preparation with offline-buildable preflight pin parser (replacement draft for #264; do not merge) release: prepare v0.4.0-rc.5 metadata and an offline, AST-based RC preflight (replaces #264) Oct 18, 2026
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>

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