release · diff

git:20260929.d8a8acc to git:20260930.406f8da

12 added, 5 removed. Audit A to A.

---
name: release
description: Operate FSL's GitLab Flow-inspired lifecycle from short-lived branches through main and production to an exact vX.Y.Z release tag. Use when adopting the branch flow, integrating post-merge quality improvements, promoting a release, cutting a release, or handling a production hotfix.
disable-model-invocation: true
argument-hint: "[adopt|integrate|promote|cut|hotfix] [version]"
---
# Operate the release flow
Treat CI-green pull requests as integration evidence and release promotion as a
separate, stronger decision:
```text
short-lived branch -> main -> production -> vX.Y.Z
^ ^
integration release readiness
```
Keep `main` as the default branch. Do not create `develop` or `pre-production`.
Use a temporary `release/vX.Y` branch only when `main` must advance while an
exact candidate is stabilized.
Read `docs/design/RELEASE.md` completely before `promote` or `cut`; it is the
authoritative command-level procedure. Keep it and this lifecycle contract
aligned in the same pull request.
## Non-negotiable rules
- Merge product and quality changes into `main` through pull requests.
- Accept changes into `production` only through a release-promotion pull request.
- Never tag `main`; tag the exact `production` commit approved for release.
- Never selectively promote commits from `main`. Promote its complete tree.
- Never force-move or reuse a published tag.
- Publish only the tag-driven GitHub Release. Do not publish the frozen Python
compatibility reference to PyPI or the Rust crates to crates.io.
- Release native binaries for macOS arm64, Linux x64, Linux arm64, and Windows
x64 only. Intel macOS (`macos-x64`) is not supported.
- Populate the GitHub Release body from the matching non-empty changelog section.
- Fix defects upstream in `main` first, then promote downstream. Use the hotfix
exception only when unreleased `main` cannot safely ship.
- Stop before a push, production merge, tag, release publication, or branch
protection change unless the user explicitly authorized that exact action.
## Route the invocation
Interpret `$ARGUMENTS` as one of these operations. If the operation or version
is missing, inspect the repository state and ask only for the decision that
cannot be derived safely.
| Operation | Outcome |
|---|---|
| `adopt` | Bootstrap and protect `production` |
| `integrate` | Merge a feature, fix, or quality improvement into `main` |
| `promote` | Prove the current release candidate and propose `main -> production` |
| `cut X.Y.Z` | Prepare, promote, tag, and observe version `X.Y.Z` |
| `hotfix X.Y.Z` | Patch the released line without pulling unreleased `main` into production |
## Adopt the flow
1. Fetch remote branches and tags. Require a clean worktree.
2. Identify the latest published `vX.Y.Z` tag and verify its release exists.
3. Create `production` at that tag, not at a newer unreleased `main` commit.
4. If that released baseline predates `.github/workflows/production-policy.yml`,
bootstrap it before requiring the check: create `release/vX.Y` from
`production`, add the exact workflow already reviewed on `main`, open a pull
request to `production`, and manually verify both the allowed source ref and
byte-for-byte workflow match. Merge that one bootstrap pull request only
after explicit approval. A `pull_request_target` workflow absent from the
base branch cannot validate its own first installation.
5. Protect `main` and `production` from direct pushes and force pushes.
6. Require normal CI and review on `main` pull requests.
7. Add a required policy status check for pull requests whose base is
`production`. Make it reject every head except `main`, `release/vX.Y`, or
`hotfix/vX.Y.Z`; branch protection alone cannot restrict the source branch.
8. Require the release gate and explicit approval on `production` promotion
pull requests. Treat adoption as incomplete until the source-policy check is
required by the `production` ruleset.
9. Keep tag creation as the publication trigger in `.github/workflows/release.yml`.
If no release has ever been published, require the user to identify the initial
production baseline. Do not guess it from branch age.
## Integrate and improve on main
1. Start from current `main` on a short-lived branch named for the change, such
as `feat/...`, `fix/...`, `quality/...`, or `refactor/...`.
2. Apply the narrowest relevant checks before the repository-required gate.
3. Open a pull request to `main` with the contract, risk, and replayable evidence.
4. Merge when the integration gate passes. Do not claim that this makes the
commit release-ready.
5. Continue audits, formalization, tests, refactoring, and defect fixes through
new pull requests to `main` until the complete `main` tree meets the release
gate.
Keep `main` releasable as a whole. Leave incomplete behavior unmerged unless it
is isolated by an already-required explicit configuration boundary. Do not add
a feature-flag system solely to accommodate this flow.
## Promote main to production
1. Verify `production` is an ancestor of, or can be merged cleanly with, `main`.
2. Freeze the candidate by recording the exact `main` SHA. Follow the canonical
pinned-head and pre-/post-merge tree-identity procedure in
`docs/design/RELEASE.md` §2, steps 3–4: create the candidate branch `release/vX.Y`
at that SHA — the name the required `production source policy` check accepts —
use it as the promotion pull request head, and perform both identity checks.
Do not open a moving `main -> production` pull request. If unrelated changes
land, rerun the gate or replace the candidate.
3. Confirm the release commit preparation is already on `main`:
- bump `rust/Cargo.toml`'s workspace version and regenerate `rust/Cargo.lock`
with Cargo; do not bump the frozen `pyproject.toml` package for a native
GitHub Release;
- ran `tools/aggregate_changelog.sh release --version X.Y.Z --date YYYY-MM-DD`,
moving `[Unreleased]` entries and aggregating every `changelog.d/`
fragment under `## [X.Y.Z] - YYYY-MM-DD`, leaving `[Unreleased]` and
`changelog.d/` both empty;
- update comparison links and confirm the matching changelog section is a
non-empty Release body.
4. Run `./tools/check-native-integration.sh` and dispatch the release workflow's
manual artifact build. Verify the completed run's `head_sha` equals the
recorded candidate SHA; discard and rerun evidence produced from a moving
branch after it advances. Confirm all four native targets, the Agent Skill
bundle, the VS Code extension, and both Kernel bundles pass.
`workflow_dispatch` is non-publishing and must never attach Release assets, even for a tag ref. Add
focused formal, mutation, platform, or compatibility evidence when the
changed contract requires it.
5. Open the promotion pull request from the pinned candidate branch to
`production`. State the candidate SHA, version, included changes, known
residual risk, exact gates, and artifact evidence.
6. Complete the canonical checks in `docs/design/RELEASE.md` §2, step 4 before and
after merge. Record the new production merge SHA; it is distinct from the
gated candidate SHA unless promotion was a true fast-forward.
Do not merge newer `main` work into an open promotion implicitly. Close or update
the promotion and rerun its evidence against the new SHA.
## Cut the release
After the promotion is approved and merged:
1. Verify the requested version is valid SemVer and absent from local and remote
tags and releases.
2. Verify `production` HEAD has the prepared Rust workspace version, lockfile,
changelog, and non-empty version section.
3. Rerun `./tools/check-native-integration.sh` on exact `production` HEAD. Dispatch
the manual release artifact workflow from `production`, verify its `head_sha`
equals that HEAD, and require every job to pass. Never reuse evidence from the
pre-merge candidate for a distinct production merge commit.
4. Regenerate a temporary notes file from the exact production HEAD's matching
changelog section, show it to the user, and stop if it is empty; never reuse
notes derived from the pre-promotion candidate.
5. Create annotated tag `vX.Y.Z` at the gated `production` HEAD.
6. Before pushing, state the tag, commit SHA, exact notes, and that the push
- uploads a draft, verifies its remote inventory, then makes the notes, native
- binaries, VS Code extension, and Kernel contract bundles public.
+ uploads a draft, verifies its remote inventory, signs and uploads the
+ packslip bundles, verifies the inventory again, then makes the notes, native
+ binaries, VS Code extension, Kernel contract bundles, and packslip bundles
+ public.
7. Push the tag only after one explicit confirmation for that complete
publication and version.
8. Observe every release job. If a job fails transiently, inspect it and rerun
only the failed jobs; do not retag.
9. Verify the Release body is non-empty and matches the changelog section.
10. Verify exactly the four supported native `fslc`/`fslc-lsp` asset pairs and
- checksums, the Agent Skill bundle and checksum, the VS Code extension, and
- both Kernel bundles. Reject any
+ checksums, the Agent Skill bundle and checksum, the VS Code extension,
+ both Kernel bundles, and both packslip bundles. Reject any
`macos-x64` asset. Download one supported binary, verify its checksum, and
require `fslc --version` to equal the tag; the workflow performs the same
- tag/version assertion on every native runner.
+ tag/version assertion on every native runner. Then, in a throwaway
+ directory, install the release with
+ `mise use "packslip:github.com/ymm-oss/fsl/fslc@X.Y.Z"` and
+ `mise use "packslip:github.com/ymm-oss/fsl/fslc-lsp@X.Y.Z"`, require
+ `mise skills ls` to name every directory under `skills/`, and require
+ `mise which fslc-lsp` to resolve.
11. If publication has begun and a defect is found, fix it upstream, promote it,
and cut a new patch version. Never rewrite the published release.
## Classify a failed gate before retrying
This is the sole classification rule for the release procedure. Apply it before
calling a job failure transient or rerunning it; `docs/design/RELEASE.md` retains the
command and publication contract.
1. Record the exact commit, the binary that ran, and the run ID/status before
interpreting a failure. A run still in progress is not evidence from the
completed run; do not mix their observations.
2. For a mutation lane, read the outcome classes before its exit code.
`outcomes.json` supplies the `CaughtMutant`, `MissedMutant`, `Timeout`, and
`Unviable` counts and per-phase durations; read `missed.txt` and
`timeout.txt` separately. A surviving mutant is a coverage defect, whereas
a timeout is a budget/performance observation.
3. Compare a local failure with the same commit's completed CI run for the same
lane, and record both observed values. Do not conclude that a local failure
is a code defect before making that comparison.
4. Classify the evidence and act on the matching branch:
- A missing release-procedure or release-bump-path requirement is a real
procedure defect. The v4.4.1 candidate exposed this when #906's
`rust/fslc/tests/goldens/induction_cli_contract.json` golden recorded the
version block; #914 added its regeneration step and the
`is_release_bump_path` entry in `tools/aggregate_changelog.sh`.
- A local timeout that does not reproduce in that CI lane is an environment
difference. The v4.4.1 local mutation run had 14 `Timeout`, 0
`MissedMutant`, 775 s mutant builds against the 600 s build timeout, and a
322 s baseline build; CI on the same commit had 55 tested, 47 caught, 8
unviable, and 0 timeouts. Record both sets of numbers. Never widen a
timeout or threshold to make a local gate pass: record the environment
difference instead.
- A `cancelled` job is not a failing job, but decide *why* it was cancelled
before rerunning: GitHub reports a job killed by its own
`timeout-minutes` as `cancelled`, not as a timeout. Compute the job's
elapsed time from its `startedAt` and `completedAt` and compare it with
that job's configured `timeout-minutes`.
- Elapsed well under the budget, and the aggregate's only non-success
input is the cancellation: transient. Rerun only failed jobs. On
`d9e9710`, one `native Z3 4.16 (windows-latest)` job was cancelled
while the other 15 jobs succeeded; `gh run rerun --failed` succeeded.
- Elapsed at the budget: this is a timeout wearing a cancellation label.
Rerunning without establishing why the job exceeded its budget repeats
it; `.github/workflows/ci.yml`'s `rust-native-z3` comment records six
consecutive scheduled `windows-latest` timeouts from 2026-08-07 through
2026-08-11, with cache absence as an observed material correlate. Establish the cause first, then rerun
at most once when the cause is known to have changed. On `ca2d5e7d`,
that job ran 06:45:55Z to 07:46:59Z — 61 minutes against
`.github/workflows/ci.yml`'s `timeout-minutes: 60` — with a cache key
(`v0-rust-rust-native-z3-Windows_NT-x64-368f6b88-...`) that had not
existed before the run, so it built cold. It saved that cache as it
exited, which is what made a single warm rerun worth attempting.
Never widen the budget to make the lane pass.
- A reproducing failure, a surviving non-equivalent mutant, or other failed
job is a defect until evidence establishes a different branch. Fix it
upstream and repeat the applicable release evidence; do not retag to
retry a different commit.
## Stabilize while main advances
Create `release/vX.Y` from a recorded `main` SHA only when candidate validation
must continue while unrelated work lands on `main`.
- Put every generally applicable fix in `main` first, then backport it to the
release branch with traceable pull requests.
- Accept only release preparation and candidate fixes on the release branch.
- Promote `release/vX.Y -> production`, tag the production merge, then delete
the temporary branch only after its release metadata is reconciled to `main`.
- After release, open a metadata reconciliation pull request to `main`. Carry
forward the released version and comparison links, remove only the entries
shipped by the release branch, and preserve newer `[Unreleased]` entries and
`changelog.d/` fragments from advancing `main`. Do not merge the older
release tree wholesale over `main`.
- Do not introduce permanent `pre-production`; the temporary branch represents
the stabilized candidate, not an environment.
## Handle an urgent hotfix
Prefer fixing `main` and running the normal promotion. If `main` contains work
that must not ship:
1. Branch `hotfix/vX.Y.Z` from `production`.
2. Make only the minimal patch and release metadata change.
3. Open coordinated pull requests to `production` and `main`; ensure the fix is
present on `main` before closing the hotfix task.
4. Run the release gate against the hotfix SHA, merge to `production`, and tag a
new patch version using the normal cut procedure.
Record why normal upstream-first promotion was unsafe. Do not use a hotfix to
ship ordinary feature work.
## Preserve component boundaries
This branch flow does not justify a new runtime component or a branch-aware code
path. Apply these design constraints when a change affects architecture:
- Keep branch names out of product configuration, binaries, schemas, and runtime
behavior. Git controls promotion; components implement one tested contract.
- Treat all artifacts built from one tag as one atomic release unit. For FSL this
includes `fslc`, `fslc-lsp`, the Agent Skills, the VS Code extension, and
published Kernel bundles.
Build jobs upload private workflow artifacts; one final job uploads the complete
unit to a draft, verifies the remote inventory, and only then makes it public.
- Verify cross-component contracts at the promotion SHA. A component cannot be
declared ready while another artifact from the same tag still depends on an
incompatible contract.
- Use versioned interfaces and an explicit compatibility policy only when
components are intentionally deployed or supported independently. Do not use
a branch per component as a substitute for interface versioning.
- Keep incomplete work outside `main` when it cannot preserve the release unit's
existing observable behavior. Prefer deletion or consolidation before adding
configuration or rollout machinery.
If a component needs a different release cadence, stop and propose a separate
release-unit contract before changing this flow.
## Report evidence
At completion, report:
- source and target branches with exact SHAs;
- integration and release gates actually observed;
- promotion pull request and release URLs;
- released tag and whether it equals `production` HEAD;
- non-empty release notes, supported asset inventory, checksum, and version
smoke-test evidence;
- temporary release/hotfix branches still requiring cleanup;
- any component-contract risk deferred from the release.