Ship one reusable daily-release workflow for every fleet package
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 / incident | Legacy evidence | Port status and behavior | Reusable evidence |
|---|---|---|---|
| Negative Bun manifest cache made retries vacuous; 14 days of tags without Releases | pm-ops:836 | Missing → 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 pause | pm-web:871; pm-ops:852 | Ported: 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 acceptance | pm-web:886; pm-changelog:876 | Ported: 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 git | pm-web:774; pm-changelog:764 | Ported: 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 attempt | pm-slack:696 | Missing 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 loop | pm-web:715 | Ported: 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 insufficient | pm-ops:662 | Ported 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 provenance | pm-memory:713 | Missing → 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 artifact | pm-beads:685 | Missing → 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 advancing | pm-ops:610 | Ported: 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 active | pm-web:82 | Ported: 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 OIDC | pm-web:650 | Ported: 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 -eu | pm-web:241; pm-web:251; pm-web:270 | Ported: 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 permission | pm-web:281; pm-web:287; pm-web:297 | Ported: 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 step | pm-web:263 | Ported: 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 leases | pm-web:171; pm-rust:267 | Ported: 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-scoped | pm-web:171 | Ported: 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 transient | pm-web:436; pm-web:525 | Ported: guarded reads, UNKNOWN state, retry missing SHA and refused PUT | .github/workflows/daily-release.yml:761 |
| Unresolved bot conversations can block protected merge forever | pm-rust:267 | Ported 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 run | pm-web:531 | Ported: 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 head | pm-memory:553 | Missing → 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 state | pm-web:586; pm-rust:490 | Ported 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 output | pm-web:326; pm-web:620; pm-gantt-chart:293 | Ported: 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 CI | pm-context:327; pm-changelog:350 | Ported 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 again | pm-changelog:106 | Missing → 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 rejection | pm-changelog:141; pm-rust:133; pm-rust:514 | Ported normalization/suffix preservation; legacy creation of another same-day -N release intentionally replaced by the tested single-daily-release cap | daily-release.ts:39; daily-release.ts:73 |
| Tagged unchanged head must recover a missing Release; a concurrent recovery may already have created it | pm-memory:66; pm-memory:905 | Ported: 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 red | pm-ops:914; pm-ops:939 | Missing 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 nonblocking | pm-gantt-chart:877; pm-brief:868 | Ported: 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 toolchain | pm-web:32; pm-rust:52 | Ported: 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:checkandbun run release:check, each under isolated HOME,GIT_CONFIG_GLOBAL=/dev/nullandGIT_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
decideDailyReleasemakes 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 passesnode --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 --progressthen 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.