Ship one reusable daily-release workflow for every fleet package

Merged
UN
3d ago
+13371-40

Fleet packages maintain separate release workflows, allowing incident fixes to disappear when they are consolidated. This PR supplies one reusable workflow, tested TypeScript decisions, a SHA-pinned caller contract, package hooks and migration guidance. Read-only preparation hands off the checked artifact; publication binds its bytes, merged main, trigger, release tag and npm provenance.

Release tags and new branches now share the existing npmVersionToTag formatter. A continuation reading npm version 2026.10.5 now keeps the first run's v2026.10.05; a legacy untagged 2026.8.9 resumes as v2026.08.09, and 2026.8.9-2 retains its suffix as v2026.08.09-2. Reviewed package versions use the same formatter. New branches derive from the canonical tag; verified existing PR branches retain their identities. Suffixed branches remain eligible and sort numerically for refresh. PR create/edit titles and bodies, staged release commits, pushed tags and GitHub Release titles all consume that planned tag.

The reusable Bun verifier fixes a propagation failure: previously all 21 retries shared Bun's negative manifest cache, so a package appearing later could never be observed. Each attempt now exports a fresh BUN_INSTALL_CACHE_DIR under RUNNER_TEMP and clears the configured cache before bun add. The contract test requires both operations inside the loop and before installation, ignoring comments and setup outside the loop.

The audit also restored six other missing safeguards: CI-only approval filtering, equality with the prepared tarball, registry-read failure diagnostics, stopping repeat publication after success, exact untagged-version resumption across midnight, and completeness auditing after release/recovery even on no-change days. Actual GitHub Release dates retain the daily cap when resuming an older version. The optional audit-script hook preserves package-owned repair opt-in and connects failures to the isolated alert job. Its explicit status check follows GitHub's documented !cancelled() guidance.

The prior recovery suffix, bounded canonical-tag lookup, exact tag diagnostics, offline deferral, trusted review selectors, conditional CLI-pin validation and adjacent-publisher checks remain covered. No dependency pin or gate threshold changed. The remote PR head was pulled first; it already contained origin/main.

Legacy safeguard audit, 2026-10-20: all 28 original package copies plus the now-available pm-memory copy, 29 total. All audited workflow files were clean at capture. Immutable snapshot receipts identify every source commit, workflow SHA-256 and selected step job/name/line/body hash: 449 step occurrences, 138 distinct executable bodies. Comments about incidents, caches, propagation, retries, races and stale state are grouped by safeguard. Step guards, ordering and permissions were compared alongside shell bodies. Older weaker behavior is identified where superseded by a newer fix.

Documented legacy fix / incidentLegacy evidencePort status and behaviorReusable evidence
Negative Bun manifest cache made retries vacuous; 14 days of tags without Releasespm-ops:836Missing → ported: fresh runner-temp cache and cache clear inside every attempt.github/workflows/daily-release.yml:1347
Bun visibility needs the full registry window, including a final attempt after the last pausepm-web:871; pm-ops:852Ported: 21 attempts, twenty 30-second pauses (600 seconds); replaces the older 675-second backoff schedule with the common ten-minute window.github/workflows/daily-release.yml:1341
Bun failure must stay red without preventing GitHub Release creation; older npm-view fallback hid failed acceptancepm-web:886; pm-changelog:876Ported: separate read-only verification job, Release depends on publication/tag only, explicit failure gate; obsolete npm fallback superseded.github/workflows/daily-release.yml:1305; .github/workflows/daily-release.yml:1117
Registry publish error can mean accepted bytes; stale reads and short windows left npm ahead of gitpm-web:774; pm-changelog:764Ported: three publish attempts, online registry reads, twenty 30-second polls and one final read; never publish without provenance.github/workflows/daily-release.yml:1016; .github/workflows/daily-release.yml:1058
Successful publish can precede visible attestations, including on the last attemptpm-slack:696Missing break-on-success → ported: retain polling without repeating successful publication while metadata propagates.github/workflows/daily-release.yml:1045
Attestation probe must return status, never exit the calling retry looppm-web:715Ported: both visibility functions return status; only the bounded caller decides success/failure.github/workflows/daily-release.yml:994
Reconcile an existing coordinate only after verifying its artifact; bare version existence is insufficientpm-ops:662Ported and strengthened: SLSA subject digest and commit must match registry metadata and verified main.github/workflows/daily-release.yml:1013; daily-release.ts:128
Published bytes must equal the verified handoff, even if another artifact has provenancepm-memory:713Missing → ported: compare registry integrity with SHA-512 of the prepared tarball before accepting provenance.github/workflows/daily-release.yml:993
Registry metadata read failure is not proof of an unattested artifactpm-beads:685Missing → ported: retain a distinct read-failure status and diagnose it after exhausted polling.github/workflows/daily-release.yml:1019
Expired stored npm token silently stopped publishing while main kept advancingpm-ops:610Ported: OIDC only, no token fallback, preflight before privileged metadata mutation.github/workflows/daily-release.yml:640; .github/workflows/daily-release.yml:966
Effective npm version matters; cached shims or failed upgrades can leave npm 10 activepm-web:82Ported: effective version checked after upgrade and immediately before publish (minimum 11.5.1).github/workflows/daily-release.yml:263; .github/workflows/daily-release.yml:987
setup-node empty auth lines and credentials in either npm config suppress OIDCpm-web:650Ported: remove every legacy auth form from user and global config before publication.github/workflows/daily-release.yml:974
Unset OIDC variables and curl failures must yield actionable diagnoses under set -eupm-web:241; pm-web:251; pm-web:270Ported: defensive variable reads and guarded curl calls.github/workflows/daily-release.yml:649
Exchange accepts 200 or 201; rate limits/outages do not imply an identity refusal; exchange does not prove publish action permissionpm-web:281; pm-web:287; pm-web:297Ported: all 2xx accepted; 000/429/5xx classified separately; publish-action guidance preserved.github/workflows/daily-release.yml:674
Short-lived exchange response must not leak or survive the steppm-web:263Ported: random response file, never echoed, EXIT cleanup.github/workflows/daily-release.yml:665
Publishing then pushing protected main failed GH006, leaving npm ahead of git; branch updates need exact leasespm-web:171; pm-rust:267Ported: protected metadata PR before publication, base check, leased writes/deletion, tag-only push after publish.github/workflows/daily-release.yml:693; .github/workflows/daily-release.yml:1085
Computed mergeStateStatus can remain BLOCKED after checks pass; required-check API is administration-scopedpm-web:171Ported: merge endpoint is the authority, 30-minute deadline, fail fast only DIRTY/BEHIND.github/workflows/daily-release.yml:756
PR-read 5xx/rate limits and successful merge responses without SHA are transientpm-web:436; pm-web:525Ported: guarded reads, UNKNOWN state, retry missing SHA and refused PUT.github/workflows/daily-release.yml:761
Unresolved bot conversations can block protected merge foreverpm-rust:267Ported with stricter selectors: only complete nonempty trusted-Bot-only threads qualify.github/workflows/daily-release.yml:799
action_required is invisible in statusCheckRollup; surface and best-effort approve the parked CI runpm-web:531Ported: prepared-head query, run URLs, best-effort approval, fresh approval state each attempt.github/workflows/daily-release.yml:836
Approval must apply only to CI, never another waiting workflow sharing the headpm-memory:553Missing → ported: CI-name filter restored inside the retry loop.github/workflows/daily-release.yml:839
Main can move after release preparation or merge; publish only byte-for-byte checked statepm-web:586; pm-rust:490Ported and strengthened: checked bundle/tree/hash, main/merged/trigger identity, continuation at merged commit.github/workflows/daily-release.yml:595; .github/workflows/daily-release.yml:953
tsc leaves orphaned output; a self-diff cannot establish a clean dist rebuild; copied packages need tracked outputpm-web:326; pm-web:620; pm-gantt-chart:293Ported: clean build in read-only prepare, npm/Bun gates, explicit tracked dist staging and ignored/untracked output checks before pack.github/workflows/daily-release.yml:434; .github/workflows/daily-release.yml:498
Generate/check/notes drift and clock-dated changelog output broke later CIpm-context:327; pm-changelog:350Ported through package-owned generate/check/notes scripts; Rust retains its pinned just recipes.github/workflows/daily-release.yml:438
Failed run may have merged an untagged version before midnight; resume that coordinate instead of bumping againpm-changelog:106Missing → ported: read current daily npm metadata, normalize complete tag coordinates and retain untagged version/suffix across rollover; actual publication dates retain the daily cap.github/workflows/daily-release.yml:361; daily-release.ts:70
Padded/unpadded date tags collide in npm; same-day suffix policy and Cargo leading-zero rejectionpm-changelog:141; pm-rust:133; pm-rust:514Ported normalization/suffix preservation; legacy creation of another same-day -N release intentionally replaced by the tested single-daily-release capdaily-release.ts:39; daily-release.ts:73
Tagged unchanged head must recover a missing Release; a concurrent recovery may already have created itpm-memory:66; pm-memory:905Ported: recovery tag from planner, separate notes/handoff and restricted recovery job; recheck before create, verify provenance first.github/workflows/daily-release.yml:1280
Completeness gate before creation blocked repair; no-change days must still audit; missing GH_TOKEN kept it permanently redpm-ops:914; pm-ops:939Missing unconditional scheduling → ported: optional audit-script job after release/recovery even on no-change/failure runs, GH_TOKEN set; explicit repair remains package-owned opt-in.github/workflows/daily-release.yml:1144
Scheduled release failures went unnoticed; alert must be isolated, deduplicated and nonblockingpm-gantt-chart:877; pm-brief:868Ported: separate issues-write alert hook covers prepare/release/recover/Bun/audit failures; keep existing tested package alert script during migration.github/workflows/daily-release.yml:1175
Postgres gate must not skip; Rust requires the whole release-check toolchainpm-web:32; pm-rust:52Ported: optional synthetic database service and Rust clippy/rustfmt, llvm-tools, just, cargo-llvm-cov and cargo-audit.github/workflows/daily-release.yml:133; .github/workflows/daily-release.yml:283

Revert proof, with final regression tests unchanged: ten independent mutations each fail with 0 passed / 1 failed. All implementations were restored; the final focused suite passes 41/41.

  • node --test --test-name-pattern='the reusable daily-release bun verification' test/release-workflow.test.ts: remove only the cache export; remove only the cache clear; move both outside the loop (three separate failures).
  • node --test --test-name-pattern='release approval retries' test/daily-release-workflow.test.ts: remove only the CI filter.
  • node --test --test-name-pattern='publish retries bind' test/daily-release-workflow.test.ts: remove only local artifact equality; remove only break-on-success; revert only the distinct registry-read status (three separate failures). The different-bytes fixture has valid provenance for the same commit, so its rejection specifically proves the local artifact check.
  • node --test --test-name-pattern='completeness audit follows' test/daily-release-workflow.test.ts: add the old should_release guard.
  • node --test --test-name-pattern='untagged main metadata' test/daily-release.test.ts: restore the previous planner implementation.
  • node --test --test-name-pattern='actual publication dates' test/daily-release-workflow.test.ts: restore package-policy-only date reads in a scratch copy; restored workflow passes 1/1, reverted workflow fails 0/1.

Validation at unchanged thresholds:

  • npm run release:check and bun run release:check, each under isolated HOME, GIT_CONFIG_GLOBAL=/dev/null and GIT_CONFIG_NOSYSTEM=1: 1,387 tests each; 1,385 passed, zero failures/cancellations/TODOs, two existing opt-in fleet skips.
  • Both gates: 100% statements/branches/functions/lines across all 30 measured source files; zero duplication across 28,457 lines and 83 sources; all 255 declarations documented; zero production audit vulnerabilities; 77-file dry pack. Changelog/date, publish-attestation and lifecycle checks pass. Dependency pins and thresholds are unchanged.
  • Canonical-name revert proof: reverting only decideDailyRelease makes all five new regressions fail (0 passed, 5 failed, zero skips): single-digit month/day, resumed numeric suffix, legacy untagged coordinate, byte-identical first-run/resume tags, and reviewed package coordinate. Command: node --test --test-name-pattern="canonical release names" test/daily-release.test.ts. Independently reverting ownership or numeric suffix ordering also causes one regression failure each. Restoring all functions passes node --test test/daily-release.test.ts: 17/17, zero skips.
  • Repo-pinned pm test ops-reusable-daily-release --run --progress: eleven linked commands passed initially; one canonical-tag check hit its 120-second wrapper timeout. pm test ops-reusable-daily-release --run --only-index 6 --timeout 300 --progress then passed in 134 seconds. All twelve linked commands have passing results. The existing disabled result-persistence policy is unchanged; durable evidence is recorded in item comments.
  • Regenerated and checked the changelog after PM writes. Added-content privacy and append-only PM history checks pass.

Implementation, generated dist, regressions, documentation and PM evidence are committed together at 03bce69393ab4688d8c2a5670f80a0f959ab0f3d. PM: ops-reusable-daily-release, left in progress and unclaimed for orchestrator verification. The immutable legacy safeguard audit remains available alongside the implementation.

The latest fetched CodeRabbit review has no Outside diff range findings. Review threads remain untouched. The two fleet tests remain opt-in; live publication and consumer migration are separate evidence. No PR merge, release, tag or consumer change was performed.