Skip to content

release: breaking/* labels become enforced version policy; tear guard; cut notes - #223

Merged
lannbot merged 3 commits into
mainfrom
release-version-guard
Aug 22, 2026
Merged

release: breaking/* labels become enforced version policy; tear guard; cut notes#223
lannbot merged 3 commits into
mainfrom
release-version-guard

Conversation

@lannbot

@lannbot lannbot commented Aug 22, 2026

Copy link
Copy Markdown
Collaborator

Implements the label-tracked breaking-change policy discussed for the upcoming cut, plus the guards that make it enforced rather than remembered.

Policy. Breaking changes are declared by PR label — breaking/{runtime,translator,wasi,ct-runner,protocol} (already created), one per package whose published surface breaks; no label = caret-compatible. Labels are read live everywhere they're consulted, so retroactive label fixes on merged PRs are supported and feed enforcement at cut time.

Guards (tools/version-guard/check.ts, 30 unit tests over injected effects):

  • pr — first step of gha::core, no-op outside PR runs: lockstep agreement, monotonicity vs the last cut, label ↔ minor-bump agreement both directions, protocol-tear early warning. Advisory (label edits don't re-trigger CI; a re-run picks up fixes).
  • publish — release.yml, both modes, before deno publish: the authoritative tear guard. If protocol's manifest version is already on JSR, in-tree protocol must be byte-identical to the published files (bidirectional, via the _meta.json checksums). This is the embedder: structured-clone-safe boundary forms (A20, #131) #219 tear (repaired by protocol: 0.2.1 — republish the A20 exports burned out of 0.2.0 #221) made loudly red at the only point that doesn't race publish-on-green-main. Exact identity by design; softer canonicalizations parked in version-guard: softer canonicalizations for the protocol tear guard (parked alternatives) #222.
  • cut — release=true only: re-reads the window's PR labels as of cut time, requires the matching minor bumps, and renders Breaking/Changes release-notes sections from PR titles (direct pushes listed by commit) — cut releases stop having boilerplate-only notes.

Runbook. AGENTS.md gains the labeling policy and a six-step cutting-a-release runbook; step 1 is the human sanity pass over the window's titles/diffs vs labels, because a missing breaking label is the one failure mode the mechanical checks are blind to.

Live smokes: publish PASSes against the published protocol@0.2.1 and reproduces the exact #219 failure shape when pointed at 0.2.0; cut against the real v0.3.1..HEAD window finds #216's labels and renders the fragment. This PR's own CI exercises pr mode for real (no labels, no bumps — all checks should pass).

No breaking labels: nothing here touches any published package surface.

…; cut notes

Breaking changes are now declared by PR label — breaking/{runtime,
translator,wasi,ct-runner,protocol}, one per package whose published
surface the PR breaks; no label means caret-compatible. Labels are
mutable and read LIVE everywhere they are consulted, so retroactively
correcting a merged PR's labels is a supported workflow: the cut re-reads
the whole window.

tools/version-guard/check.ts enforces the policy at three points:

- pr (gha::core, first step; no-op without PR_NUMBER): lockstep
  agreement, monotonicity against the last cut, breaking-label <->
  minor-bump agreement in both directions, and the protocol-tear early
  warning. Advisory by design — label edits do not re-trigger CI.
- publish (release.yml, both modes, before deno publish): the
  authoritative tear guard. When protocol's manifest version is already
  on JSR, the in-tree protocol must be byte-identical to the published
  files (both directions, via the version _meta.json checksums) —
  otherwise this run would skip protocol as already-published and ship
  every dependent against the registry's older copy. That is the #219
  tear (published protocol@0.2.0 was missing the A20 exports
  runtime@0.4.0-pre.* imported; repaired by #221), and it lands here
  rather than at PR time because PR checks race publish-on-green-main:
  #219's last CI run predated the prerelease that burned 0.2.0. Exact
  identity is deliberate; softer canonicalizations are parked in #222.
- cut (release=true): the window's PRs since the last cut tag, with
  their labels as they exist AT CUT TIME, become the minor-bump
  requirement — and the notes fragment (Breaking / Changes, PR titles,
  direct commits) that replaces the boilerplate-only release notes.

AGENTS.md gains the labeling policy and a six-step cutting-a-release
runbook; the sanity pass over the window's titles and diffs is first,
because a MISSING breaking label is the one failure mode every
mechanical check here is blind to.

30 unit tests over injected effects (no network); live smokes: publish
mode PASSes against the published protocol@0.2.1 and reproduces the
exact #219 failure shape against 0.2.0 (--protocol-version override);
cut mode against the real v0.3.1..HEAD window finds #216's labels and
renders the fragment.
@lannbot
lannbot enabled auto-merge August 22, 2026 21:24
@lann
lann disabled auto-merge August 22, 2026 21:26
…side

Every green main still gets its pre-<shorthash> GitHub release with the
full artifact set (translator shim wasm, embedder bundle, SHA256SUMS) —
the cut guard's green-pipeline proof keeps working — but nothing is
published to JSR or npm between cuts anymore:

- release.yml: the JSR publish and the npm dispatch are release-mode
  only; the prerelease version-stamping loop is gone (with it, the
  g-prefix leading-zero machinery); prerelease notes say what a
  prerelease now is — artifacts for a green commit. The protocol
  identity guard stays in BOTH modes: authoritative at the cut, pure
  early detection on the prerelease path (red = the next cut would
  tear).
- ci.yml: the release job's permissions trim to contents:write — the
  prerelease path needs neither JSR OIDC nor the npm dispatch; cuts
  enter release.yml via workflow_dispatch under its own block.
- npm-publish.yml: pre-* tags are refused rather than computed, which
  gives the release:published backstop teeth — a hand-made GitHub
  prerelease can start the workflow but cannot publish. All surviving
  versions are bare cuts, so the loop publishes --tag latest
  unconditionally; the npm pre dist-tag is retired and frozen wherever
  it last pointed.
- version-guard: logic untouched (30 tests green); comments rewritten
  off the publish-on-green-main race framing — publishes now happen
  only at explicit cuts, and publish mode stays authoritative there
  because PR-time verdicts still miss post-run label edits, direct
  pushes, and their own staleness. The tear check's merged-PR-replay
  note corrected: a merged protocol bump now stays unpublished until
  the cut, so replays pass.
- README §Consuming, docs/consumers.md, AGENTS.md: tracking main
  between cuts = GitHub release assets or git refs; the -pre.g*
  versions on JSR and npm's pre dist-tag are frozen leftovers of the
  earlier flow.

A side benefit worth naming: version burning at every green main was
the mechanism behind the #219 tear (a 'pending' version published out
from under a concurrent branch); with cut-only publishing that entire
race class is gone.
@lannbot
lannbot enabled auto-merge August 22, 2026 21:34
…ired

The #223 trim to contents:write failed the whole CI workflow at startup:
a reusable-workflow call is validated against the CALLED workflow's
permissions block, and the caller must grant everything it requests —
if-skipped steps included. The tokens are inert on the prerelease path
(the spending steps are if: inputs.release, which workflow_call cannot
set); the comment now records the rule so the trim is not re-attempted.
@lannbot
lannbot merged commit fc7e2db into main Aug 22, 2026
4 checks passed
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.

2 participants