diff --git a/AGENTS.md b/AGENTS.md index 85abfd3f..e1dfc920 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -29,12 +29,29 @@ Push normal development branches to `swarm` and open PRs against `EffortlessMetrics/shiplog` clone in place for new normal work; clone `shiplog-swarm` side-by-side when setting up a fresh agent or machine. -Promotion from swarm back to source uses a regular merge-commit PR into -`EffortlessMetrics/shiplog/main`, not a squash merge. A promotion PR should -include the swarm head SHA, included swarm PRs, and proof that -`Shiplog Rust Small Result` passed. Use +Promotion from swarm back to source uses `cargo xtask promote` and a regular +merge-commit PR into `EffortlessMetrics/shiplog/main`, never a squash merge. +Do not push raw `swarm/main` as a source promotion branch or rebuild the +per-path overlay manually. Run the current exact-head dry-run twice, inspect the +machine path decisions and included receipts, execute the idempotent command, +and verify the landed checkpoint with `--verify-only`. A fail-closed result is +a repair queue; do not bypass it with an older source ref or historical mode. +Use [`plans/shiplog-swarm/promotion-runbook.md`](plans/shiplog-swarm/promotion-runbook.md) -for the exact promotion sequence. +for the complete promotion and closeout sequence. + +Shared release-candidate preparation also belongs in swarm. Finish the release +scope, version and lockfile, changelog freeze, README/guides, release decision, +readiness ledger, and handoff skeleton on `shiplog-swarm/main`, then promote the +exact proven candidate. Source work begins only after that checkpoint and is +limited to release execution, tagging, publication, and explicitly authorized +source-owned writer configuration. Use +[`docs/release/release-preparation.md`](docs/release/release-preparation.md) for +the current preparation, preflight, tag, staged-artifact, publication, +rollback, and closeout procedure. Versioned readiness/decision files and root +`RELEASE_HANDOFF_*` files are historical receipts, not standing command +references. A product or shared-documentation defect found on source returns to +swarm and is promoted again. ## Code Review Standards diff --git a/docs/README.md b/docs/README.md index f55e5925..970be8d0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,15 +10,28 @@ history. - `docs/proposals/` owns problem framing, user value, alternatives, and success criteria. - `docs/specs/` owns required behavior, non-goals, acceptance, and proof obligations. - `docs/adr/` owns durable architecture decisions and consequences. -- `plans/` owns PR-sized sequencing, proof commands, and rollback notes. +- `plans/` owns PR-sized sequencing, proof commands, rollback notes, and the + source/swarm promotion transaction. - `.codex/goals/active.toml` owns current Codex execution state. - `docs/status/SUPPORT_TIERS.md` owns product-claim-to-proof mapping. - `policy/*.toml` owns enforceable ledgers and exceptions. -- `docs/governance/` owns human guides to enforced repository-governance boundaries (e.g. [automation authority](governance/automation-authority.md)). -- `docs/handoffs/` owns closeout outcomes and residual work. +- `docs/governance/` owns human guides to enforced repository-governance + boundaries, including [automation authority](governance/automation-authority.md). +- `docs/release/release-preparation.md` owns the living release-preparation, + staging, publication, and closeout procedure. +- versioned files under `docs/release/` and root `RELEASE_HANDOFF_*` files own + historical release decisions and execution evidence, not current commands. +- `docs/handoffs/` owns initiative closeout outcomes and residual work. ## Linking rule Every material initiative should be linked across the stack: `roadmap -> proposal -> spec -> ADR (if needed) -> plan -> active goal -> issue/PR -> proof and policy receipts`. + +A release adds a separate bounded evidence chain: + +`merged swarm candidate -> exact source promotion -> release decision -> readiness ledger -> immutable tag proof -> public-state handoff`. + +Start a new release from [`docs/release/README.md`](release/README.md). Do not +copy standing instructions from a historical versioned release receipt. diff --git a/docs/release/README.md b/docs/release/README.md new file mode 100644 index 00000000..f99bcbc9 --- /dev/null +++ b/docs/release/README.md @@ -0,0 +1,66 @@ +# Shiplog Release Documentation + +This directory separates the **current release procedure** from historical +release evidence. + +## Current procedure + +Use [`release-preparation.md`](release-preparation.md) for every new release. +It owns the current order of operations, stop conditions, proof commands, +publication boundary, rollback rules, and copyable checklists. + +Promotion from the development trunk into the public source repository is a +separate prerequisite for source release execution. Use +[`plans/shiplog-swarm/promotion-runbook.md`](../../plans/shiplog-swarm/promotion-runbook.md) +for that exact-source/exact-swarm transaction. + +## Artifact roles + +| Surface | Role | +| --- | --- | +| `release-preparation.md` | Living preparation, staging, publication, and closeout procedure. | +| `templates/` | Starting points for a new versioned readiness ledger and execution handoff. | +| `-release-decision.md` | Version-specific scope and release/no-release decision. | +| `-readiness.md` | Version-specific preflight and observed release evidence. | +| `../../RELEASE_HANDOFF_.md` | Version-specific execution and public-state handoff. | +| `../../CHANGELOG.md` | User-visible release contents and version history. | + +Versioned files and root `RELEASE_HANDOFF_*` files are historical receipts. +They may contain commands, workflow names, tools, versions, or repository state +that were correct for that release but are no longer current. **Do not copy a +historical command block into a new release.** Start from the living procedure +and current templates, then record the commands and evidence actually used. + +## Authority boundary + +- `EffortlessMetrics/shiplog-swarm` owns normal product, documentation, test, + CI, policy, and shared release-candidate preparation: scope, version, + changelog freeze, README/guides, release decision, readiness ledger, and + handoff skeleton. +- `EffortlessMetrics/shiplog` owns the public source lineage, release tags, + crates.io publication, GitHub Releases, signing, package-channel publication, + and security-sensitive release credentials. +- A complete shared release candidate must be promoted from a proven exact + swarm head into source through a regular merge-commit checkpoint before + source release execution begins. +- Source release preparation is narrow and source-owned: validate the promoted + candidate and adjust only explicitly authorized release-writer configuration + when necessary. A product or shared-documentation defect goes back to swarm. + +## Quick route + +1. On `shiplog-swarm`, finish the release scope, shared docs, version/lockfile, + changelog freeze, release decision, readiness ledger, and handoff skeleton. +2. Prove the exact release candidate on `shiplog-swarm/main`. +3. Promote that exact green swarm head with `cargo xtask promote`. +4. Verify and close out the regular-merge source checkpoint. +5. On current `shiplog/main`, run source preflight and merge only narrowly + required source-owned release-writer changes. +6. Push one immutable annotated tag from the exact proven source commit. +7. Require the source Release workflow to stage and validate one exact + four-platform candidate set while it remains non-current. +8. Publish crates.io and make the GitHub release public only after the exact tag + workflow and terminal candidate-ready aggregate are green. +9. Verify public assets and package channels, finalize the observed release + evidence, and back-port any source-only release documentation/workflow + changes to swarm before normal promotion resumes. diff --git a/docs/release/release-preparation.md b/docs/release/release-preparation.md new file mode 100644 index 00000000..8dbd6372 --- /dev/null +++ b/docs/release/release-preparation.md @@ -0,0 +1,564 @@ +# Shiplog Release Preparation and Execution + +**Status:** active living procedure +**Authority:** `shiplog-swarm` prepares and proves the complete shared +candidate; `shiplog` owns source execution, tags, publication, and public +release state. + +This is the canonical procedure for a new Shiplog release. Versioned readiness, +decision, and handoff files are evidence for one completed release; they are not +standing instructions. + +## Operating invariants + +1. Normal product, shared documentation, tests, CI, policy, package metadata, + version changes, changelog changes, and release-candidate preparation land in + `EffortlessMetrics/shiplog-swarm`. +2. `EffortlessMetrics/shiplog` remains the public source and release authority. + It owns release tags, crates.io publication, GitHub Releases, signing, + package-channel publication, and security-sensitive release credentials. +3. Source receives the complete proven candidate through a regular merge-commit + promotion PR. Never squash a source promotion. +4. Source release work begins only after the exact shared candidate has been + promoted. It is limited to validation, tagging, publication, and narrowly + authorized source-owned writer configuration. +5. A product, version, package, changelog, README, guide, test, or shared CI + defect found on source returns to swarm, is proved there, and is promoted + again. Source is not a second release-preparation lane. +6. The release tag is immutable. Never force-move or reuse a failed tag; fix the + defect through swarm and cut the next patch version. +7. One release attempt has one exact staged candidate set. Every platform test, + checksum, readiness receipt, and publication decision must identify the same + tag, source commit, artifact manifest, and artifact digests. +8. A skipped or unavailable optional check is not claimed as executed proof. +9. Release claims come from merged behavior and observed artifacts, not commit + subjects, PR-body assertions, historical command blocks, or a local rebuild. + +## Phase 0 — Define the candidate on swarm + +Choose the release version and theme from the user-visible change set, not from +control-plane activity alone. + +Before candidate freeze: + +- identify the user-facing reason for the release; +- decide the intended semantic-version increment; +- list included and explicitly deferred user-visible changes; +- identify installer, package-channel, signing, or platform-trust impact; +- create `docs/release/X.Y.Z-release-decision.md` from + [`templates/release-decision.md`](templates/release-decision.md); +- create `docs/release/X.Y.Z-readiness.md` from + [`templates/release-readiness.md`](templates/release-readiness.md); and +- create root `RELEASE_HANDOFF_X.Y.Z.md` from + [`templates/release-handoff.md`](templates/release-handoff.md). + +Keep these files in `shiplog-swarm`. Their pre-publication form records intended +scope, required proof, and placeholders for observed source/tag/public state. + +Do not bump versions merely to make a candidate feel real. Finish product scope, +README/guides, changelog curation, and the release decision first. Make the +version/changelog freeze late in the final shared release-preparation PR. + +## Phase 1 — Finish and freeze the shared candidate on swarm + +Release preparation starts on current `shiplog-swarm/main`. + +### Content and documentation review + +- Curate `CHANGELOG.md` from merged behavior. Include user-visible capability, + meaningful compatibility changes, and important fixes; omit internal churn. +- Update README installation, first-use, recurring-use, and command examples + affected by the release. +- Update guides, configuration reference, support tiers, claim boundaries, and + package-channel instructions before freezing release notes. +- Verify every changelog and release-note statement against the actual command + path. Narrow any unproved security, platform, signing, or availability claim. +- Remove stale future-tense wording from current docs without rewriting + historical release receipts. +- Confirm the release still has one coherent user-facing theme after deferrals. + +### Final shared release-preparation PR + +After scope and documentation are stable, use one focused swarm PR to: + +- set the workspace/package version to `X.Y.Z`; +- align workspace package entries in `Cargo.lock`; +- freeze the curated changelog section as `X.Y.Z` with the intended release + date and create a fresh empty `[Unreleased]` section; +- update the documentation-only `publish.release` marker when appropriate, + without misrepresenting it as an executable gate; +- finalize the release decision and pre-publication readiness state; +- prepare the handoff skeleton with exact fields still marked pending; and +- make any last shared README, installer, guide, test, or verification changes. + +Do not make these shared changes directly in `EffortlessMetrics/shiplog`. + +### Queue and repository state + +Both repositories may contain deliberately deferred work, but the candidate +must not be ambiguous. + +```bash +git fetch origin --prune +git fetch swarm --prune + +gh pr list --repo EffortlessMetrics/shiplog-swarm --state open --limit 50 +gh issue list --repo EffortlessMetrics/shiplog-swarm --state open --limit 50 +gh pr list --repo EffortlessMetrics/shiplog --state open --limit 50 +gh issue list --repo EffortlessMetrics/shiplog --state open --limit 50 +``` + +For every open item, record one of: + +- included before candidate freeze; +- explicitly deferred and non-blocking; +- superseded/duplicate and closed; or +- release blocker. + +Do not promote while a required-check failure, source mutation incident, +release-blocking PR, or source/shared candidate mismatch has an unexplained +disposition. + +### Swarm proof + +Run the contributor and repository-contract proof from a clean tracked checkout. +Known protected agent workspace artifacts may remain only when the contract +report classifies them explicitly; tracked edits and unknown untracked paths +remain blocking. + +```bash +cargo xtask ci-small +cargo xtask check-policy-ledgers +cargo xtask check-doc-artifacts +cargo xtask check-goals +cargo xtask check-support-tiers +cargo xtask check-workflows --mode blocking-allowlist +cargo xtask check-file-policy --mode blocking-allowlist +cargo xtask check-automation-authority --repository-role swarm +cargo xtask promotion-state --check +cargo xtask repo-contract-report + +bash scripts/package-version-audit.sh +bash scripts/package-boundary-audit.sh +bash scripts/package-proof.sh +bash scripts/publish-dry-run.sh + +git diff --check +``` + +On Windows, run the Bash package scripts through WSL or Git Bash. Hosted Linux +CI remains the authority for their normal execution. + +Confirm independently: + +- every publishable workspace package reports `X.Y.Z`; +- `policy/publish-allowlist.toml` contains only intended public packages; +- `cargo package --list` contains no private, generated, or unintended files; +- `cargo publish --dry-run --locked` succeeds for the allowlisted order; +- the exact candidate head has a successful routed + `Shiplog Rust Small Result`; and +- version, changelog, README/guides, decision, readiness, and handoff all belong + to that same exact swarm commit. + +The required aggregate is the authority; optional smoke lanes are supplementary +evidence. + +## Phase 2 — Promote the exact release candidate + +Follow +[`plans/shiplog-swarm/promotion-runbook.md`](../../plans/shiplog-swarm/promotion-runbook.md). +The canonical entrypoint is `cargo xtask promote`; do not hand-build a raw +`swarm/main` source branch. + +### Read-only planning + +Resolve the current exact head after fetching both remotes: + +```bash +git fetch origin --prune +git fetch swarm --prune +swarm_head="$(git rev-parse swarm/main)" +mkdir -p target/source-of-truth + +cargo xtask promote --swarm-sha "$swarm_head" --dry-run \ + > target/source-of-truth/promote-plan-1.json +cargo xtask promote --swarm-sha "$swarm_head" --dry-run \ + > target/source-of-truth/promote-plan-2.json +cmp target/source-of-truth/promote-plan-1.json \ + target/source-of-truth/promote-plan-2.json +``` + +PowerShell equivalent: + +```powershell +git fetch origin --prune +git fetch swarm --prune +$swarmHead = (git rev-parse swarm/main).Trim() +New-Item -ItemType Directory -Force target/source-of-truth | Out-Null + +cargo xtask promote --swarm-sha $swarmHead --dry-run | + Set-Content -NoNewline target/source-of-truth/promote-plan-1.json +cargo xtask promote --swarm-sha $swarmHead --dry-run | + Set-Content -NoNewline target/source-of-truth/promote-plan-2.json + +if ((Get-FileHash target/source-of-truth/promote-plan-1.json).Hash -ne + (Get-FileHash target/source-of-truth/promote-plan-2.json).Hash) { + throw "promotion dry-runs are not deterministic" +} +``` + +Review the structured plan, not only the exit code. Confirm: + +- `source_head`, `swarm_head`, and merge base are the expected exact commits; +- the routed workflow and terminal aggregate belong to that exact swarm SHA; +- the included merged swarm PR receipt list is complete; +- every differing path has an exact source/swarm tree entry, effect, and basis; +- every source-retained path has current bounded source authority; +- every exceptional take-swarm path has current bounded transition authority; +- the deterministic overlay SHA and source branch/PR action are explicit; and +- neither dry-run created a branch, PR, worktree residue, durable object, or + source mutation. + +A non-zero dry-run is a repair queue. Do not bypass it with an old +`--source-ref`, a raw branch push, a guessed receipt, or `--allow-historical`. +Historical mode is for explicit historical diagnosis; it is not permission to +prepare a PR against a different current source head. + +### Prepare, prove, and merge the source checkpoint + +After the exact dry-run is deterministic and fully reviewed: + +```bash +cargo xtask promote --swarm-sha "$swarm_head" +``` + +A real execution must re-resolve live state, require source merge control, +create/update/reuse one deterministic source-local promotion branch and PR, +preserve current source-owned release/governance paths through the exact +overlay, emit the machine receipt, and stop before merge/tag/publication. + +Run the command again against unchanged state. It must reuse the same branch, +PR, overlay, title, body, and receipt identity rather than create duplicates. + +Review the actual source PR diff and exact-head source CI. Its body must record +the exact swarm head, included swarm PRs, swarm proof, source proof, regular +merge instruction, rollback, and the release-authority claim boundary. + +Merge with a regular merge commit, never squash: + +```bash +gh pr merge --repo EffortlessMetrics/shiplog --merge --delete-branch +``` + +Then verify the landed transaction: + +```bash +git fetch origin --prune +git fetch swarm --prune +cargo xtask promote --swarm-sha "$swarm_head" --verify-only +cargo xtask repo-contract-report +``` + +Require the exact two-parent checkpoint, matching overlay identities, exact +source post-merge routed CI, no unexplained source product commits, only current +approved source governance, and a closed-out bounded promotion state. + +The required closeout records one landed transaction. It is distinct from +opening repeated receipt-only PRs merely to chase a moving pending range. + +## Phase 3 — Validate source release execution + +After promotion, current `shiplog/main` should already contain the version, +changelog freeze, shared docs, decision, readiness ledger, handoff skeleton, +package state, and candidate behavior. + +Do **not** create a source release-prep PR merely to repeat those changes. + +A focused source PR is allowed only when the release requires a narrowly +source-owned change, such as: + +- release-writer permissions or source-only workflow projection; +- source-specific signing/notarization configuration; +- source-only package-publication credentials or protected environment wiring; +- source-only release notes that cannot exist on the verification-only swarm + workflow; or +- an explicitly authorized release-governance correction. + +It must not change the shared product, version, lockfile, changelog, README, +guides, tests, or ordinary CI. If one of those needs correction, stop, fix it on +swarm, promote a new exact candidate, and restart source validation. + +### Source preflight + +Run from current source main, or from the narrow source-owned release-execution +PR when one is required: + +```bash +cargo xtask ci-small +cargo xtask check-policy-ledgers +cargo xtask check-doc-artifacts +cargo xtask check-goals +cargo xtask check-support-tiers +cargo xtask check-workflows --mode blocking-allowlist +cargo xtask check-file-policy --mode blocking-allowlist +cargo xtask check-automation-authority --repository-role source +cargo xtask promotion-state --check +cargo xtask repo-contract-report + +bash scripts/package-version-audit.sh +bash scripts/package-boundary-audit.sh +bash scripts/package-proof.sh +bash scripts/publish-dry-run.sh +bash scripts/check-release-hold.sh vX.Y.Z + +git diff --check +``` + +Confirm: + +- source product/package/version/changelog state matches the exact promoted + candidate; +- source automation authority is `source`, not `swarm`; +- release workflow write permission is narrowly job-scoped; +- package proof and allowlisted publish dry-run succeed; +- the release-hold guard accepts the exact intended tag; and +- exact source PR and post-merge checks are green when a source-owned PR was + required. + +Do not tag from an unmerged PR head. The release source commit is the exact +promoted source main or the exact merged result of the narrow source-owned +execution PR. + +## Phase 4 — Stage the exact tagged candidates + +The swarm candidate contract is implemented and proven by the staged-candidate +workflow on #391. The source-owned counterpart still needs to project that +contract into the source release writer; this procedure does not claim an +exact-tag run or public release until that source work and live evidence exist. + +Tag only the exact proven source commit recorded in the readiness ledger. Do +not tag whichever source checkout happens to be current: + +```bash +approved_sha="<40-character readiness SHA>" +tag="vX.Y.Z" + +if [[ ! "$approved_sha" =~ ^[0-9a-f]{40}$ ]]; then + echo "approved readiness SHA must be a full 40-character commit SHA" >&2 + exit 1 +fi + +git fetch origin "$approved_sha" + +test -z "$(git status --porcelain --untracked-files=no)" + +git switch --detach "$approved_sha" + +test "$(git rev-parse HEAD)" = "$approved_sha" + +if git rev-parse --quiet --verify "refs/tags/$tag" >/dev/null; then + echo "tag already exists: $tag" >&2 + exit 1 +fi + +git tag -a "$tag" "$approved_sha" \ + -m "Shiplog $tag" + +test "$(git rev-list -n 1 "$tag")" = "$approved_sha" +git push origin "$tag" +``` + +Stop before tagging when the tracked checkout is dirty or the approved SHA is +unavailable. If source has advanced after readiness, do not silently adopt the +new head; return to swarm and produce a new approved candidate. If the tag +already exists, do not move or reuse it. If the immutable tag or candidate +fails, repair through swarm and cut the next version. The tag push invokes the +source `Release` workflow. A manual dispatch is only for an existing explicit +semver tag and requires the owner-approval input; it does not replace the tag +or authorize an untagged build. + +The workflow must keep the candidate non-current while it proves one immutable +staged set. A draft GitHub release, retained workflow-artifact bundle, or other +source-authorized equivalent may be used, but every later lane must consume the +same candidate manifest and artifact digests. + +Require successful completion of: + +- `Release Preflight`; +- package proof and allowlisted publish dry-run; +- Linux x86-64 binary build; +- macOS Intel binary build; +- macOS Apple Silicon binary build; +- Windows x86-64 binary build; +- staged candidate manifest, checksums, and artifact digests; +- draft GitHub Release creation and asset upload when that is the staging + mechanism; +- release validation; +- first-use acceptance on all four supported targets; +- release-mode integration tests; +- deliberate checksum and executable/first-use negative controls; and +- one terminal `Release Candidate Ready` aggregate depending on every required + candidate job. + +Every job, artifact, manifest, and draft release must point at the same immutable +tag SHA. No acceptance lane may use a workspace binary, `cargo run`, or a local +rebuild as the binary under test. + +If any exact-tag gate fails: + +- keep the candidate non-current and any GitHub release draft; +- do not publish crates.io; +- do not make the GitHub release public; +- do not move or reuse the tag; and +- fix the defect through swarm and cut the next patch version. + +## Phase 5 — Publish + +Only after all staged-artifact lanes and `Release Candidate Ready` are green, +publish the crate from a detached checkout of that same immutable tag: + +```bash +git fetch origin --prune --tags +git switch --detach vX.Y.Z +test "$(git rev-parse HEAD)" = "$(git rev-list -n 1 vX.Y.Z)" + +cargo publish -p shiplog --locked +gh release edit vX.Y.Z --draft=false --latest +``` + +Do not publish from a moving `main`, an unmerged branch, or a local rebuild that +is not the exact tag exercised by the source Release workflow. + +Verify public state: + +- crates.io shows the intended version and package metadata; +- `cargo install shiplog --version X.Y.Z --locked` installs successfully; +- the GitHub release is public, non-prerelease unless explicitly intended, and + marked latest when appropriate; +- all four binaries and `SHA256SUMS.txt` are present; +- downloaded binaries report `shiplog X.Y.Z`; +- versionless installer paths resolve the public assets; and +- Homebrew and Scoop updates use the final public asset hashes and pass native + package validation. + +Claim signing, notarization, SmartScreen reputation, or other platform-trust +proof only when the corresponding lane exists and passed. Checksums alone are +not a signing claim. + +## Phase 6 — Record observed release evidence on swarm + +After public verification, complete the versioned readiness ledger and handoff +on a focused swarm release-closeout PR. Replace placeholders with observed: + +- exact promoted swarm and source commits; +- exact tag and source Release workflow run; +- staged candidate manifest and artifact digests; +- successful platform jobs and terminal candidate-ready aggregate; +- public crates.io version; +- public GitHub Release and asset list; +- first-use acceptance results; +- installer and package-channel results; +- known limitations and deferred work; and +- rollback/yank decision, if any. + +Set the release decision/readiness status to shipped only after public-state +verification. The new `[Unreleased]` section was already opened during candidate +freeze and remains ready for subsequent work. + +Merge and prove this closeout on swarm. Promote it to source as a coherent +release-evidence checkpoint, either immediately when public documentation must +be current or with the next substantive promotion batch. Do not edit the shared +handoff only on source and leave swarm unaware of the public result. + +Any source-only release-workflow or execution change must also be reconciled +back into the swarm verification projection before normal promotion resumes. + +## Abort and rollback rules + +### Before the tag + +Fix the candidate in swarm, rerun proof, and promote again. Abandon or rebuild +any narrow source-owned execution PR from the new source main. + +### After the tag but before crates.io publication + +Keep the candidate non-current and any GitHub release draft. Do not force-move +the tag. Fix through swarm and cut the next patch tag. + +### After crates.io publication + +Published crate bytes cannot be replaced. Use a new patch release. Yank only +when the published version should no longer be selected automatically, and +record the reason in the release handoff. + +### Incorrect source promotion + +Revert the regular merge commit in source and pause release work until the +divergence is understood. Never rewrite `shiplog/main` history. + +## Copyable release checklist + +### Shared candidate on swarm + +- [ ] Release version and user-facing theme are decided. +- [ ] Included and deferred scope is explicit. +- [ ] README, guides, config docs, support tiers, and examples are current. +- [ ] Workspace/package version and `Cargo.lock` are aligned. +- [ ] Changelog `X.Y.Z` section is frozen and a new `[Unreleased]` exists. +- [ ] Release decision, readiness ledger, and handoff skeleton are prepared. +- [ ] Open PRs/issues are included, deferred, closed, or blocking explicitly. +- [ ] `cargo xtask ci-small` passes. +- [ ] Policy, docs, goals, support-tier, workflow, file-policy, and authority + checks pass. +- [ ] Package version/boundary audits, package proof, and publish dry-run pass. +- [ ] `promotion-state --check` and `repo-contract-report` pass. +- [ ] Exact swarm `Shiplog Rust Small Result` is green. + +### Promotion + +- [ ] Two current-head dry-runs are byte-identical. +- [ ] Exact source/swarm/merge-base and required CI evidence are correct. +- [ ] Included swarm PR receipts are complete. +- [ ] Every path decision has exact entries and a current basis. +- [ ] Real `cargo xtask promote` creates or reuses one compatible source PR. +- [ ] Source promotion PR exact-head CI is green. +- [ ] Source promotion is regular-merged, never squashed. +- [ ] `--verify-only` and source post-merge `repo-contract-report` pass. +- [ ] Promotion manifest/current view and consumptive decisions are closed out. + +### Source execution preflight + +- [ ] Source shared candidate state exactly matches the promoted swarm candidate. +- [ ] No shared product/version/changelog/docs correction is being made on source. +- [ ] Any source PR is limited to source-owned release execution/governance. +- [ ] Source-role automation and release authority remain correct. +- [ ] Package version/boundary audits, package proof, and publish dry-run pass. +- [ ] Release-hold guard passes for the exact tag. +- [ ] Source PR and post-merge required checks are green when applicable. + +### Tag and staged candidates + +- [ ] Annotated semver tag points at the exact proven source commit. +- [ ] Tag has not been moved or reused. +- [ ] Source Release workflow targets the exact tag SHA. +- [ ] Four platform builds pass. +- [ ] One staged candidate manifest binds the tag, source commit, assets, and + digests. +- [ ] Checksums and any draft release assets are complete. +- [ ] Release validation consumes the staged set and passes. +- [ ] Four-platform first-use acceptance consumes the same staged set and passes. +- [ ] Deliberate checksum and executable/first-use failures remain red. +- [ ] Release-mode integration tests pass. +- [ ] Terminal `Release Candidate Ready` is green. + +### Publication and closeout + +- [ ] crates.io publication ran from a detached checkout of the exact tag. +- [ ] GitHub release became public only after exact-tag candidate proof. +- [ ] Public assets, checksums, installers, and `--version` are verified. +- [ ] Homebrew and Scoop use final public hashes and pass native validation. +- [ ] Swarm readiness/handoff records observed run IDs and public state. +- [ ] Deferred work and limitations are explicit. +- [ ] Source-only execution/workflow changes are reconciled back to swarm. +- [ ] Observed release evidence is promoted to source on a coherent checkpoint. +- [ ] New `[Unreleased]` work can proceed without unresolved release drift. diff --git a/docs/release/templates/release-decision.md b/docs/release/templates/release-decision.md new file mode 100644 index 00000000..b585ec2f --- /dev/null +++ b/docs/release/templates/release-decision.md @@ -0,0 +1,53 @@ +# shiplog X.Y.Z — Release Decision + +**Release target:** `vX.Y.Z` +**Theme:** `` +**Status:** proposed | ready to stage | shipped | cancelled + +## Decision + +State the actual release/no-release decision and why this version earns a +public release. Do not use control-plane completion alone as the justification. + +## Included boundary + +- `` +- `` +- `` + +## Explicitly deferred + +- `` +- `` + +## Compatibility and migration + +Describe configuration, schema, CLI, artifact, package, or workflow +compatibility. State `none` when there is no user action. + +## Safety and proof decision + +Record the release-specific proof boundary: + +- required source/swarm CI; +- fail-closed behavior retained or changed; +- security or privacy claims actually exercised; +- supported platform artifact matrix; +- signing/notarization status, when applicable; and +- checks intentionally advisory or unavailable. + +## Distribution decision + +Record which public channels are in scope: + +- crates.io; +- GitHub Release assets; +- versionless installers; +- Homebrew; +- Scoop; and +- any channel explicitly deferred. + +## Claim boundary + +State what this decision does **not** authorize or prove. A ready decision does +not itself tag, publish, sign, upload, or make a release public. diff --git a/docs/release/templates/release-handoff.md b/docs/release/templates/release-handoff.md new file mode 100644 index 00000000..835be135 --- /dev/null +++ b/docs/release/templates/release-handoff.md @@ -0,0 +1,91 @@ +# shiplog X.Y.Z — Release Execution Handoff + +**Release target:** `vX.Y.Z` +**Status:** preparing | staged | published | closed +**Release decision:** [`docs/release/X.Y.Z-release-decision.md`](docs/release/X.Y.Z-release-decision.md) +**Readiness ledger:** [`docs/release/X.Y.Z-readiness.md`](docs/release/X.Y.Z-readiness.md) + +Copy this template to root `RELEASE_HANDOFF_X.Y.Z.md`. The links above are +written for that destination. + +This handoff records the execution boundary and observed public state. Replace +all placeholders with evidence; do not mark it closed from intended commands. + +## Exact release identities + +| Identity | Value | +| --- | --- | +| Swarm candidate SHA | `` | +| Source promotion PR / merge | `#` / `` | +| Source release-prep PR / merge | `#` / `` | +| Release source commit | `` | +| Annotated tag | `vX.Y.Z` | +| Source Release workflow | `` | +| Staged candidate manifest | `` | +| Terminal candidate-ready aggregate | `` | + +## Published contents + +Summarize the release in user language and link to the changelog. Keep internal +control-plane receipts in the readiness ledger rather than turning them into the +release story. + +## Artifact receipt + +| Artifact | Observed result | +| --- | --- | +| `shiplog-x86_64-unknown-linux-gnu` | `` | +| `shiplog-x86_64-apple-darwin` | `` | +| `shiplog-aarch64-apple-darwin` | `` | +| `shiplog-x86_64-pc-windows-msvc.exe` | `` | +| `SHA256SUMS.txt` | `` | + +## Validation receipt + +- Release preflight: `` +- Release validation: `` +- Four-platform first-use acceptance against one staged set: `` +- Release-mode integration tests: `` +- Terminal `Release Candidate Ready`: `` +- crates.io install and `--version`: `` +- GitHub release public-state verification: `` +- Versionless installers: `` +- Homebrew: `` +- Scoop: `` +- Signing/notarization: `` + +## Execution record + +Record only commands actually executed, with the exact commit/tag context. +Typical final boundary: + +```text +git tag -a vX.Y.Z -m "shiplog vX.Y.Z" +git push origin vX.Y.Z +# After exact-tag staged-candidate proof: +git checkout --detach vX.Y.Z +cargo publish -p shiplog --locked +gh release edit vX.Y.Z --draft=false --latest +``` + +Record that `cargo publish` ran from the exact detached tag checkout proven by +the source Release workflow, not from a later moving `main`. + +## Exceptions, rollback, or yank + +Record any failed attempt, replacement patch version, yank, draft deletion, or +public limitation. Release tags are immutable and published crate bytes are not +replaced. + +## Residual work + +- `` +- `` +- `` + +## Claim boundary + +State exactly what the completed release proves and what remains unverified. +Checksums are not a signing claim; skipped optional checks are not executed +proof; a public GitHub release does not by itself prove crates.io or package +channels. diff --git a/docs/release/templates/release-readiness.md b/docs/release/templates/release-readiness.md new file mode 100644 index 00000000..904c8849 --- /dev/null +++ b/docs/release/templates/release-readiness.md @@ -0,0 +1,78 @@ +# shiplog X.Y.Z — Release Readiness + +**Release target:** `vX.Y.Z` +**Theme:** `` +**Status:** preparing | ready to stage | staged | shipped | blocked + +## Candidate identities + +| Identity | Value | +| --- | --- | +| Swarm candidate SHA | `` | +| Source promotion PR | `EffortlessMetrics/shiplog#` | +| Source promotion merge | `` | +| Source release-prep PR | `EffortlessMetrics/shiplog#` | +| Release source commit | `` | +| Release tag | `vX.Y.Z` | + +## Included release state + +Summarize the user-visible release contents. Link to the release decision and +`CHANGELOG.md`; do not duplicate every internal PR. + +## Preflight evidence + +| Gate | Result | Evidence | +| --- | --- | --- | +| Swarm `Shiplog Rust Small Result` | pending | `` | +| `cargo xtask ci-small` | pending | `` | +| Repository contract report | pending | `` | +| Deterministic promotion dry-run | pending | `` | +| Source promotion exact-head CI | pending | `` | +| Promotion `--verify-only` | pending | `` | +| Source post-merge CI | pending | `` | +| Package version audit | pending | `` | +| Package boundary audit | pending | `` | +| Package proof | pending | `` | +| Allowlisted publish dry-run | pending | `` | +| Release-hold guard | pending | `` | + +## Exact-tag artifact evidence + +| Gate | Result | Evidence | +| --- | --- | --- | +| Release Preflight | pending | `` | +| Linux x86-64 build | pending | `` | +| macOS Intel build | pending | `` | +| macOS Apple Silicon build | pending | `` | +| Windows x86-64 build | pending | `` | +| Staged candidate manifest and artifact digests | pending | `` | +| Checksums and draft assets | pending | `` | +| Release Validation | pending | `` | +| Linux first-use acceptance | pending | `` | +| macOS Intel first-use acceptance | pending | `` | +| macOS Apple Silicon first-use acceptance | pending | `` | +| Windows first-use acceptance | pending | `` | +| Release-mode integration tests | pending | `` | +| Terminal `Release Candidate Ready` aggregate | pending | `` | + +## Publication gates + +- [ ] Exact immutable tag SHA is confirmed. +- [ ] All applicable exact-tag jobs consume one staged candidate set and are green. +- [ ] Terminal `Release Candidate Ready` is green for that exact tag and manifest. +- [ ] GitHub release remains draft before publication approval. +- [ ] crates.io package version and metadata are final. +- [ ] Public asset names and checksums are final. +- [ ] Homebrew and Scoop update inputs are final. +- [ ] Known limitations and deferred work are explicit. + +## Blockers and exceptions + +List every unresolved item. A skipped or unavailable check must be stated as +not verified rather than silently converted to a pass. + +## Release readiness decision + +`READY` or `BLOCKED`, with the exact evidence boundary and owner approval still +required for tag push/publication. diff --git a/plans/shiplog-swarm/promotion-runbook.md b/plans/shiplog-swarm/promotion-runbook.md index c20a2c67..63c5990c 100644 --- a/plans/shiplog-swarm/promotion-runbook.md +++ b/plans/shiplog-swarm/promotion-runbook.md @@ -1,249 +1,325 @@ # Shiplog Swarm Promotion Runbook -This runbook promotes proven `shiplog-swarm/main` work into +This runbook promotes one proven, exact `shiplog-swarm/main` head into `EffortlessMetrics/shiplog/main` without moving release authority. -Normal development still happens in `EffortlessMetrics/shiplog-swarm`. -`EffortlessMetrics/shiplog` remains the release/public source surface. +Normal development remains authoritative in `EffortlessMetrics/shiplog-swarm`. +`EffortlessMetrics/shiplog` remains the public source and release surface. +Promotion is a regular merge-commit checkpoint, not a second development lane. -## When To Promote +The canonical entrypoint is **`cargo xtask promote`**. Do not prepare a source +promotion by pushing raw `swarm/main`, hand-building an overlay, or manually +reconstructing the promotion PR body. -Promote after one or more green swarm PRs when the source/release repo should -checkpoint the current development state. +## When to promote -Promote before release preflight, release docs refreshes, source-only release -work, or any handoff that expects `shiplog/main` to include current swarm work. +Promote a coherent batch of green swarm work when source must checkpoint the +current development state, especially before: -Do not promote while either repo has an unexplained failing required check or -an ambiguous open release-blocking PR. +- release preparation; +- source-owned release workflow or publication work; +- a public-source handoff that expects current product behavior; or +- a bounded source-governance transaction that depends on the current tree. + +Do not promote merely to keep a copied pending list cosmetically current. Batch +coherent proven work, then record the completed transaction during closeout. ## Preconditions -- `EffortlessMetrics/shiplog-swarm` open PR queue is empty or explicitly - deferred. -- `EffortlessMetrics/shiplog` open PR queue is empty or explicitly deferred. -- `shiplog-swarm/main` has green `Shiplog Rust Small Result`. -- `shiplog/main` and `shiplog-swarm/main` share history. -- The promotion branch contains only the intended `origin/main..swarm/main` - range. -- `shiplog/main` legacy branch protection or its active repository ruleset +Before planning a promotion: + +- open PRs and issues in both repositories are included, explicitly deferred, + closed as superseded/duplicate, or identified as blockers; +- the exact `shiplog-swarm/main` head has a successful + `Shiplog Rust Small Result` aggregate; +- `cargo xtask promotion-state --check` passes; +- `cargo xtask repo-contract-report` reports no unexplained source product + commits or unknown source/swarm drift; +- source and swarm share the expected promotion ancestry; +- active transition and source-authority decisions are exact-target-bound, + reviewable, and unconsumed; +- the source open-PR queue contains no incompatible promotion PR; and +- source `main` legacy branch protection or its active repository ruleset requires the exact `reject-routine-bot-pr` Source Automation Guard check. - This is the merge control that prevents routine Dependabot and Factory Droid - product PRs from landing directly on source. -## Prepare The Promotion Branch +A required-check failure, stale decision, unexplained source commit, incomplete +receipt range, or ambiguous path resolution is a stop condition. Treat the +failure as evidence to repair, not friction to route around. + +## Maintainer checkout -Run from a checkout that has: +Run from a clean tracked checkout with both repositories available: ```text origin = git@github.com:EffortlessMetrics/shiplog.git swarm = git@github.com:EffortlessMetrics/shiplog-swarm.git ``` -```powershell +```bash git fetch origin --prune git fetch swarm --prune +git status --short --branch git merge-base origin/main swarm/main git log --oneline origin/main..swarm/main git diff --stat origin/main..swarm/main -$swarmSha = (git rev-parse --short swarm/main).Trim() -$branch = "promote/swarm-$(Get-Date -Format yyyyMMdd)-$swarmSha" - -git push origin "swarm/main:refs/heads/$branch" +cargo xtask promotion-state --check +cargo xtask repo-contract-report ``` -Stop if `git merge-base` prints nothing, if the log contains unintended work, -or if the diff is broader than the swarm PRs being promoted. +Stop if the merge base is missing, the source head is not the current source +main you intend to target, the log contains unintended work, or the diff does +not match the reviewed swarm batch and bounded source-governance decisions. -`cargo xtask promote --dry-run` remains read-only. A real promotion execution -fails closed unless source `main` legacy branch protection or its active -repository ruleset requires `reject-routine-bot-pr`; it refuses before overlay, -branch, receipt, or source PR mutation when that check is absent or the -protection response is unavailable. +## Plan the exact promotion -## Open The Source PR +Resolve the exact current swarm head after fetching both remotes: -```powershell -gh pr create --repo EffortlessMetrics/shiplog --base main --head $branch --title "merge(swarm): promote shiplog-swarm through $swarmSha" --body-file +```bash +swarm_head="$(git rev-parse swarm/main)" ``` -The PR body must include: +Run two read-only plans and require deterministic output: -```text -swarm head SHA -included swarm PRs -`## Swarm proof` section with Shiplog Rust Small Result evidence -`## Source proof` section with Shiplog Rust Small Result evidence -explicit note to merge with a regular merge commit, not squash -claim boundary: no release authority movement -``` +```bash +mkdir -p target/source-of-truth -`repo-contract-report` validates the exact `Swarm proof` and `Source proof` -section labels, and each section must mention `Shiplog Rust Small Result`. +cargo xtask promote --swarm-sha "$swarm_head" --dry-run \ + > target/source-of-truth/promote-plan-1.json +cargo xtask promote --swarm-sha "$swarm_head" --dry-run \ + > target/source-of-truth/promote-plan-2.json -## Merge +cmp target/source-of-truth/promote-plan-1.json \ + target/source-of-truth/promote-plan-2.json +``` -Only merge after source PR checks are green. +PowerShell equivalent: ```powershell -gh pr merge --repo EffortlessMetrics/shiplog --merge --delete-branch +$swarmHead = (git rev-parse swarm/main).Trim() +New-Item -ItemType Directory -Force target/source-of-truth | Out-Null + +cargo xtask promote --swarm-sha $swarmHead --dry-run | + Set-Content -NoNewline target/source-of-truth/promote-plan-1.json +cargo xtask promote --swarm-sha $swarmHead --dry-run | + Set-Content -NoNewline target/source-of-truth/promote-plan-2.json + +if ((Get-FileHash target/source-of-truth/promote-plan-1.json).Hash -ne + (Get-FileHash target/source-of-truth/promote-plan-2.json).Hash) { + throw "promotion dry-runs are not deterministic" +} ``` -Do not use `--squash` for source promotion PRs. Swarm work is already squashed -at the normal development boundary; the source merge commit is the checkpoint. +Review the structured plan itself. Confirm: -## Post-Merge Verification +- `source_head`, `swarm_head`, and merge base are the expected full SHAs; +- the routed workflow and terminal `Shiplog Rust Small Result` belong to the + exact swarm SHA; +- every included merged swarm PR appears in the receipt list; +- every differing path records the exact source and swarm tree entries; +- each path effect is backed by the current policy, transition evidence, + explicit discard-source decision, or bounded source-authority decision that + actually entitles it; +- the overlay tree, resolution-plan identity, branch identity, and source PR + action are deterministic; and +- neither dry-run created a branch, PR, worktree residue, durable object, or + source mutation. -After merge, verify source `main`: +A non-zero dry-run is the exact repair queue. Do **not** make it pass by: -```powershell -gh run list --repo EffortlessMetrics/shiplog --branch main --limit 12 --json databaseId,workflowName,status,conclusion,headSha,createdAt,displayTitle +- supplying an older `--source-ref` while targeting current source `main`; +- using `--allow-historical` for an ordinary current promotion; +- pushing raw `swarm/main` to source; +- adding a path to source-only policy merely because the two repos differ; +- guessing a receipt from commit subjects or matching file names; or +- weakening exact tree-entry or ancestry checks. -cargo xtask repo-contract-report +Historical mode is for explicit historical diagnosis. It is not authority to +prepare a real PR against a different current source head. -gh pr list --repo EffortlessMetrics/shiplog --state open --limit 50 -gh pr list --repo EffortlessMetrics/shiplog-swarm --state open --limit 50 -gh api repos/EffortlessMetrics/shiplog-swarm/branches/main/protection/required_status_checks --jq '{strict: .strict, contexts: .contexts, checks: .checks}' -``` +## Prepare or update the source promotion PR -Expected: +After the exact plan is deterministic and reviewed: -```text -source post-merge EM CI Routed Shiplog Rust: success -source post-merge CI: success -repo-contract-report git topology: tree-aligned -repo-contract-report source ahead classification: promotion-merge-only -repo-contract-report source other commits: 0 -shiplog open PR queue: empty or explicitly deferred -shiplog-swarm open PR queue: empty or explicitly deferred -shiplog-swarm required checks: Shiplog Rust Small Result only +```bash +cargo xtask promote --swarm-sha "$swarm_head" ``` -## Source-Only Changes - -Avoid source-only product, docs, or CI changes after cutover. If emergency -release work lands directly in `EffortlessMetrics/shiplog`, back-sync that -change into `EffortlessMetrics/shiplog-swarm` before more normal development -lands there. - -Routine dependency, workflow-update, security-remediation, and documentation -automation must propose changes in `shiplog-swarm`. Source security automation -may fail a check, retain an artifact, or link a remediation handoff to swarm, -but it must not create a product branch or pull request in `shiplog`. - -For an emergency hotfix, create and prove the fix in swarm first. If explicit -release authority requires an immediate source hotfix, pause normal promotion, -land the authorized source change, back-port the exact fix into swarm, and -re-establish tree alignment before promotion resumes. Emergency authorization -does not become standing permission for source-side product automation. - -## Transition Evidence And Resolution - -An active transition receipt records two separate questions for each path: - -1. What happened historically? `disposition = "missing_in_swarm"` records that - the source PR changed the path and no swarm chain carries that change. It - remains blocking evidence by itself. -2. What is this bounded promotion allowed to do? An exceptional source-side - decision may explicitly set `resolution = "discard_source"`. - -`discard_source` is valid only with a non-empty `decision_receipt`, its full -`decision_merge_sha` reachable from the current swarm target, a human-readable -`reason`, exact `source_tree_entry` and `swarm_tree_entry` bindings for the -immutable evidence targets, and differing source/swarm entries. The recorded -`source_target` and `swarm_target` values are evidence commits, not frozen -promotion refs: each must be an ancestor of the current promotion target, and -the recorded complete tree entries must still match at both the evidence -targets and the current targets. It selects the swarm tree entry for that path -for this promotion only; it does not create permanent source authority or -replace `source-only-paths.toml`. Unrelated tip advancement is therefore safe, -but a later change to the governed path fails closed and requires a new -reviewed decision. - -For dependency-only lockfile transitions, use -`disposition = "dependency_equivalent"`. This is limited to `Cargo.lock` and -requires one swarm PR whose parsed package `name`/`version` from/to transitions -match the source PR exactly. It is narrower than whole-patch `equivalent` and -does not assert that unrelated lockfile resolutions or final blobs are equal; -those differences still require their own evidence or an explicit bounded -resolution. - -Example shape: - -```toml -[[transition.path]] -path = "docs/xtask.md" -disposition = "missing_in_swarm" -resolution = "discard_source" -decision_receipt = "EffortlessMetrics/shiplog-swarm#242" -decision_merge_sha = "" -reason = "Reviewed swarm state supersedes the source transition copy." -source_tree_entry = { mode = "100644", object_type = "blob", oid = "" } -swarm_tree_entry = { mode = "100644", object_type = "blob", oid = "" } -``` +A real execution must re-resolve live source and swarm state and then: + +- verify source merge control before any mutation; +- create or fast-forward one deterministic source-local promotion branch; +- create, update, or reuse one compatible source promotion PR; +- refuse a non-fast-forward branch or incompatible/duplicate PR; +- build the exact overlay from the same per-path decisions accepted by the + planner; +- preserve source-owned release/governance paths only through current bounded + authority; +- retain an always-present deterministic checkpoint commit, including when the + final tree would otherwise be unchanged; +- emit the machine-readable promotion receipt; and +- stop before merge, tag, crates.io publication, GitHub Release publication, + signing, or package-channel mutation. + +Run the command again against unchanged state. It should reuse the same branch, +PR, overlay, title, body, and receipt identity rather than create duplicates. -The promotion planner and overlay must consume this same per-path decision. -Decision receipts are bounded by the transition entry's `consumed_by` -promotion and must never be inferred from matching trees, commit subjects, or -source-only policy. - -### Keeping source for a source-only path - -The source-only policy remains blocking when swarm changes one of its paths. -When review confirms that the source copy must win for one bounded promotion, -record a separate top-level `[[source_authority]]` decision in -`promotion-state.toml`. The planner records this as a `kept-source` resolution -basis; it is not a change to `source-only-paths.toml` and does not grant -permanent source authority. - -Example shape: - -```toml -[[source_authority]] -path = ".github/workflows/release.yml" -source_target = "" -swarm_target = "" -decision_receipt = "EffortlessMetrics/shiplog-swarm#319" -decision_merge_sha = "" -reason = "The canonical source repository retains release-writer authority." -source_tree_entry = { mode = "100644", object_type = "blob", oid = "" } -swarm_tree_entry = { mode = "100644", object_type = "blob", oid = "" } +Inspect the actual source PR diff and body. It must include: + +- the exact swarm head SHA; +- the complete included swarm PR receipt list; +- a `## Swarm proof` section naming `Shiplog Rust Small Result` evidence; +- a `## Source proof` section naming the source PR proof boundary; +- an explicit instruction to use a regular merge commit, never squash; and +- a claim boundary stating that promotion does not move release authority. + +The PR body is not independent proof. Review the exact source diff, exact-head +source CI, overlay trailers, and machine receipt. + +## Merge the source checkpoint + +Only merge after the exact source PR head is green and the actual diff matches +the reviewed plan. + +```bash +gh pr merge --repo EffortlessMetrics/shiplog --merge --delete-branch ``` -An active decision must name the exact immutable evidence `source_target` and -`swarm_target`, the merged decision receipt and full `decision_merge_sha`, a -human-readable `reason`, and complete `source_tree_entry` / -`swarm_tree_entry` values. The promotion verifies that the path is still -policy-listed, each evidence target is an ancestor of the current promotion -target, the entries still differ and match at both target pairs, and the -decision merge is reachable from the current swarm target. After the promotion -consumes it, set `consumed_by`; the historical record remains visible but -grants no further authority. +Do not use `--squash` or `--rebase`. Swarm PRs are already squash-merged at the +normal development boundary; the source merge commit is the durable ancestry +checkpoint. -The same decision must appear in the structured path plan and determine the -overlay effect. Missing, stale, unmerged, unreachable, or target-mismatched -decisions fail closed. A later swarm change requires a new reviewed decision. +## Verify the landed promotion -Verify the role boundary explicitly rather than inferring it from remote names: +After the regular merge lands: -```powershell +```bash +git fetch origin --prune +git fetch swarm --prune + +cargo xtask promote --swarm-sha "$swarm_head" --verify-only +cargo xtask repo-contract-report + +gh run list --repo EffortlessMetrics/shiplog --branch main --limit 12 \ + --json databaseId,workflowName,status,conclusion,headSha,createdAt,displayTitle +``` + +Require evidence that: + +- the exact swarm head landed through an accepted two-parent checkpoint shape; +- the overlay has exactly the expected source parent and carries matching + `Shiplog-Source-Head`, `Shiplog-Swarm-Head`, and resolution-plan identities; +- the complete overlay tree matches the selected source/swarm tree entries, + including file mode, object type, object ID, and absence; +- source post-merge routed CI succeeded at the exact landed source result; +- the source-ahead classification contains the promotion checkpoint and only + approved source governance; +- unexplained source product commits are zero; and +- source/swarm open queues are empty or explicitly deferred. + +`--verify-only` proves the Git topology transaction. Source post-merge CI, +queue state, source-governance drift, and current contract alignment remain +`repo-contract-report` responsibilities. + +## Close out the bounded promotion state + +The completed transaction must be recorded in a substantive closeout change on +swarm: + +- update `latest_promotion` with the source promotion PR, regular-merge SHA, + promoted swarm head, included swarm PRs, source-governance receipts, and + source post-merge proof; +- consume transition and source-authority decisions at the landed checkpoint; +- establish the new pending range from actual merged swarm work after the + promoted head; +- regenerate `plans/shiplog-swarm/current-promotion.md` with + `cargo xtask promotion-state`; and +- run `promotion-state --check`, `repo-contract-report`, and the relevant + transition tests. + +This required closeout records the landed transaction. It is distinct from the +anti-pattern of opening repeated receipt-only PRs merely to chase every moving +swarm head before a promotion exists. + +After closeout, a new current-head dry-run must recognize the just-landed +checkpoint and either produce a valid next plan or a precise fail-closed repair +queue. + +## Transition evidence and per-path resolution + +Promotion permission and overlay content come from one exact per-path plan. +The following rules are non-negotiable: + +- Only a current bounded source-authority decision may select source for a + source-only policy path that swarm also changed. +- A historical `missing_in_swarm` fact grants no authority by itself. +- Exceptional abandonment of a source change requires explicit + `resolution = "discard_source"`, a merged and reachable decision receipt, + a human-readable reason, exact immutable evidence targets, and complete + source/swarm tree entries. +- `equivalent`, `tree_equivalent`, `dependency_equivalent`, + `superseded_in_swarm`, `converged_at_target`, and self-referential cases keep + their distinct evidence meanings; do not collapse them into tree similarity. +- Current target entries and ancestry must still satisfy the recorded evidence. + A later change to the governed path fails closed and requires a new decision. +- `consumed_by` retires authority while preserving history. +- No authority is inferred from commit subjects, matching paths, branch names, + PR-body prose, or an old receipt that happened to mention the path. + +The durable human decision ledger is +[`transition-decisions.md`](transition-decisions.md); the exact active machine +bindings live in [`promotion-state.toml`](promotion-state.toml). Keep those two +roles separate: reviewed rationale first, exact merged receipt binding second. + +## Source-only and emergency source changes + +Routine dependency, workflow-update, security-remediation, documentation, and +product automation must propose changes in swarm. Source automation may verify, +fail a check, retain an artifact, comment, or link a remediation handoff; it +must not originate routine product branches or PRs. + +For an emergency source hotfix: + +1. pause ordinary promotion; +2. obtain explicit source release authority; +3. land the smallest source fix; +4. back-port the exact fix into swarm immediately; +5. record exact transition evidence and resolution where required; and +6. re-establish a passing current-head promotion plan before normal work + resumes. + +Emergency authorization does not become standing source-side product authority. + +Verify role policy directly: + +```bash cargo xtask check-automation-authority --repository-role swarm -# Run with `--repository-role source` in the canonical source checkout. +# Run with --repository-role source in the canonical source checkout. ``` +## Release handoff + +Once the exact candidate promotion and closeout are green, continue with the +living +[`docs/release/release-preparation.md`](../../docs/release/release-preparation.md) +procedure. Release decision, version bump, changelog freeze, tag, crates.io, +GitHub Release publication, signing, and package-channel work remain source +release-authority operations. + ## Rollback -If a promotion merge is wrong, revert the merge commit in -`EffortlessMetrics/shiplog` and pause further promotions until the divergence is -understood. +If a promotion merge is wrong, revert the regular merge commit in +`EffortlessMetrics/shiplog` and pause further promotion and release work until +the divergence is understood. -Do not rewrite `shiplog/main` history. Do not force-push source promotion -branches after review has started. +Do not rewrite `shiplog/main` history. Do not force-push a source promotion +branch after review has started. Do not move release tags to compensate for an +incorrect promotion. -## Claim Boundary +## Claim boundary -Promotion keeps `shiplog/main` current with proven swarm work. It does not move -tags, crates.io publish, GitHub Releases, signing, release branches, release -workflows, or security-sensitive token operations to `shiplog-swarm`. +Promotion keeps `shiplog/main` current with one exact, proven swarm transaction. +It does not tag, publish to crates.io, create or publish a GitHub Release, sign +artifacts, update package channels, or move security-sensitive release +credentials to `shiplog-swarm`.