diff --git a/RELEASE_WORKFLOW.md b/RELEASE_WORKFLOW.md
index 5dbd95f..98391e7 100644
--- a/RELEASE_WORKFLOW.md
+++ b/RELEASE_WORKFLOW.md
@@ -13,6 +13,44 @@ identities are pinned in `.github/release-signers.json` and documented in `.gith
This proves release provenance before `npm run upgrade` consumes upstream product code. It does not prove the code is
safe; maintainers still review changes, run checks, and protect GitHub accounts.
+## Signing Standard
+
+Use SSH signing backed by 1Password for day-to-day commits. Use Sigstore/gitsign for release tags and other
+provenance-sensitive actions.
+
+This keeps normal development fast while making published release boundaries auditable through an OIDC identity and the
+Sigstore transparency log.
+
+
+Daily commit signing
+
+Day-to-day commits should use SSH signing through 1Password:
+
+```sh
+git config --global gpg.format ssh
+git config --global gpg.ssh.program "/Applications/1Password.app/Contents/MacOS/op-ssh-sign"
+git config --global commit.gpgsign true
+```
+
+Use the SSH signing key registered with GitHub for the maintainer's `code@Dicaire.com` or maintainer-approved identity.
+
+
+
+
+Release tag signing
+
+Release tags must use Sigstore/gitsign:
+
+```sh
+git -c gpg.format=x509 -c gpg.x509.program=gitsign tag -s vX.Y.Z -m "vX.Y.Z"
+git -c gpg.format=x509 -c gpg.x509.program=gitsign verify-tag -v vX.Y.Z
+```
+
+Prefer per-command `-c gpg.format=x509 -c gpg.x509.program=gitsign` for release tag creation. This avoids changing the
+global commit-signing configuration used for SSH/1Password daily commits.
+
+
+
## Daily Development
Do not work directly on `main`.
@@ -21,11 +59,12 @@ Do not work directly on `main`.
2. Create a branch: `git switch -c work/descriptive-name`.
3. Make the change.
4. Run the relevant check: `npm run check` for broad changes, or a focused script when the change is narrow.
-5. Commit with a Conventional Commits message: `git commit -m "type: summary"`.
-6. Push the branch: `git push origin HEAD`.
-7. Open a pull request.
-8. Require review and `ci:check` before merge.
-9. Merge using the repository's documented merge strategy.
+5. Stage the intended changes: `git add .`.
+6. Commit with a Conventional Commits message and SSH/1Password signing enabled: `git commit -m "type: summary"`.
+7. Push the branch: `git push origin HEAD`.
+8. Open a pull request: `gh pr create --fill`.
+9. Require review and `ci:check` before merge.
+10. Merge using the repository's documented merge strategy.
Use squash merge when the pull request is the release unit. In that case, the pull request title must be a valid
Conventional Commit because it becomes the commit that release-please reads on `main`.
@@ -58,6 +97,79 @@ git diff --name-status HEAD...origin/main
+
+Pull request CLI commands
+
+Open a pull request from the current branch:
+
+```sh
+gh pr create --fill
+```
+
+Watch pull request checks:
+
+```sh
+gh pr checks --watch
+```
+
+Approve a pull request authored by someone else or a bot:
+
+```sh
+gh pr review PR_NUMBER --approve
+```
+
+Merge after checks and required review pass:
+
+```sh
+gh pr merge PR_NUMBER --merge --delete-branch
+```
+
+For maintainer-authored pull requests during stabilization, bypass only when checks have passed and the change does not
+require second human review:
+
+```sh
+gh pr merge PR_NUMBER --merge --admin --delete-branch
+```
+
+
+
+
+Choosing the relevant check
+
+- For broad product, Worker, policy, or generated-output changes: `npm run check`.
+- For maintenance script changes, such as `scripts/doctor.mjs`, `scripts/install.mjs`, `scripts/upgrade.mjs`, or shared
+ script libraries: run the focused test that covers the changed path, then `npm run check` before merge.
+- For doctor output changes: `npm run doctor -- --json`.
+- For opt-in upstream nudge changes: `npm run doctor -- --json --check-upstream`.
+- For manual upstream release checks:
+ `node scripts/check-upstream-release.mjs --json --current-version 0.0.0 --repo vanityURLs/code`.
+
+Network-backed upstream checks must stay non-fatal.
+
+
+
+## Dependabot Maintenance
+
+Dependabot pull requests are authored by `dependabot[bot]`, so maintainers may approve them. Review that the diff only
+updates the expected dependency or GitHub Action, wait for checks, then merge.
+
+When several Dependabot pull requests touch workflow files, merge them as a maintenance batch. If a Dependabot branch
+conflicts after `main` moves, ask Dependabot to rebase instead of resolving the bot branch manually:
+
+```sh
+gh pr comment PR_NUMBER --body "@dependabot rebase"
+```
+
+If Dependabot reports missing labels, create the labels instead of editing every pull request:
+
+```sh
+gh label create dependencies --repo vanityURLs/code --color 0366d6 --description "Dependency updates"
+gh label create github-actions --repo vanityURLs/code --color 5319e7 --description "GitHub Actions dependency updates"
+```
+
+Because `chore` is mapped to patch releases, merged Dependabot chores will usually produce a release-please patch pull
+request.
+
## Release-Please
Release-please runs after commits land on `main`.
@@ -71,17 +183,27 @@ When release-please opens or updates the release pull request:
2. Review `CHANGELOG.md` for operator-facing clarity.
3. Confirm `.release-please-manifest.json`, `package.json`, and `package-lock.json` are consistent.
4. Confirm no user-visible change was hidden behind an inappropriate commit type.
-5. Merge the release pull request only when the release should be published.
+5. Approve and merge the release pull request only when the release should be published.
+
+The release pull request is authored by `github-actions[bot]`, so a maintainer may approve it even when that maintainer
+authored the underlying feature or fix commits. GitHub does not allow an author to satisfy review requirements on their
+own pull request, but the release-please pull request has the bot as author.
+
+CLI flow:
+
+```sh
+gh pr view RELEASE_PR_NUMBER --repo vanityURLs/code --web
+gh pr checks RELEASE_PR_NUMBER --repo vanityURLs/code --watch
+gh pr review RELEASE_PR_NUMBER --repo vanityURLs/code --approve
+gh pr merge RELEASE_PR_NUMBER --repo vanityURLs/code --merge --delete-branch
+```
Release preparation checklist
- Confirm the worktree only contains intended release changes: `git status --short`.
- Confirm the release signer is listed in `.github/release-signers.json`.
-- Confirm Git tag signing uses Sigstore/gitsign:
- - `git config --get gpg.format` returns `x509`.
- - `git config --get gpg.x509.program` returns `gitsign`.
- - `git config --get tag.gpgsign` returns `true`.
+- Confirm the release tag will be created with Sigstore/gitsign, not the daily SSH/1Password commit signer.
- Review runtime registry schema changes with `docs/adr/`.
- Run `npm run clean`.
- Run `npm run check`.
@@ -118,12 +240,11 @@ GitHub's `Watch -> Releases` and security notification workflows receive the str
## Signed Release Tag
-Configure gitsign before creating release tags:
+Release tags use gitsign even when daily commits use SSH/1Password signing. Create the tag with per-command gitsign
+configuration so global commit signing remains unchanged:
```sh
-git config --global gpg.format x509
-git config --global gpg.x509.program gitsign
-git config --global tag.gpgsign true
+git -c gpg.format=x509 -c gpg.x509.program=gitsign tag -s vX.Y.Z -m "vX.Y.Z"
```
After the release pull request has merged:
@@ -132,18 +253,33 @@ After the release pull request has merged:
git switch main
git pull --rebase
npm run check
-git tag -s vX.Y.Z -m "vX.Y.Z"
-gitsign verify --certificate-identity code@Dicaire.com --certificate-oidc-issuer https://github.com/login/oauth vX.Y.Z
+git -c gpg.format=x509 -c gpg.x509.program=gitsign tag -s vX.Y.Z -m "vX.Y.Z"
+git -c gpg.format=x509 -c gpg.x509.program=gitsign verify-tag -v vX.Y.Z
git push origin vX.Y.Z
+awk '/^## /{if(seen) exit; seen=1} seen {print}' CHANGELOG.md > /tmp/vanityurls-vX.Y.Z-release-notes.md
+gh release create vX.Y.Z --repo vanityURLs/code --title "vX.Y.Z" --notes-file /tmp/vanityurls-vX.Y.Z-release-notes.md --latest
+gh pr edit RELEASE_PR_NUMBER --repo vanityURLs/code --remove-label "autorelease: pending" --add-label "autorelease: tagged"
+gh release list --repo vanityURLs/code --limit 5
```
-Use the signer identity that matches the maintainer creating the tag. For Felix:
+`gitsign verify` verifies commits, not annotated release tags. For release tags, use `git verify-tag -v` with the same
+per-command gitsign configuration used to create the tag.
-```sh
-gitsign verify --certificate-identity felix@felixleger.com --certificate-oidc-issuer https://github.com/login/oauth vX.Y.Z
-```
+Confirm the verification output shows the expected signer identity and issuer, such as:
+
+- `Good signature from [code@Dicaire.com](https://github.com/login/oauth)`
+- `Validated Git signature: true`
+- `Validated Rekor entry: true`
+
+Use the signer identity that matches the maintainer creating the tag. For Felix, the expected identity is
+`felix@felixleger.com` with issuer `https://github.com/login/oauth`.
+
+If a release tag was accidentally created with SSH signing and already pushed, do not move or recreate the tag without a
+deliberate maintainer decision. Publish that release as transitional, then use gitsign for the next release tag.
-Do not push an unsigned release tag. Do not move or recreate a release tag.
+Do not push an unsigned release tag. Do not move or recreate a release tag. Publish the GitHub Release after the tag is
+pushed so operators using `Watch -> Releases` are notified. Mark the release-please pull request as
+`autorelease: tagged` after publishing because this repository uses `skip-github-release: true`.
Release tag checklist
@@ -151,15 +287,49 @@ Do not push an unsigned release tag. Do not move or recreate a release tag.
- Merge the release-please release pull request.
- Pull the clean release commit locally: `git switch main` then `git pull --rebase`.
- Run the release confidence check: `npm run check`.
-- Create the signed release tag: `git tag -s vX.Y.Z -m "vX.Y.Z"`.
-- Verify the tag with the signing identity:
- `gitsign verify --certificate-identity code@Dicaire.com --certificate-oidc-issuer https://github.com/login/oauth vX.Y.Z`.
+- Create the signed release tag with gitsign:
+ `git -c gpg.format=x509 -c gpg.x509.program=gitsign tag -s vX.Y.Z -m "vX.Y.Z"`.
+- Verify the tag: `git -c gpg.format=x509 -c gpg.x509.program=gitsign verify-tag -v vX.Y.Z`.
+- Confirm the verification output shows the expected signer identity and `https://github.com/login/oauth` issuer.
- Push the tag only after verification: `git push origin vX.Y.Z`.
+- Prepare release notes from the latest changelog section:
+ `awk '/^## /{if(seen) exit; seen=1} seen {print}' CHANGELOG.md > /tmp/vanityurls-vX.Y.Z-release-notes.md`.
+- Publish the GitHub Release from the pushed tag:
+ `gh release create vX.Y.Z --repo vanityURLs/code --title "vX.Y.Z" --notes-file /tmp/vanityurls-vX.Y.Z-release-notes.md --latest`.
+- Mark the release-please pull request as tagged:
+ `gh pr edit RELEASE_PR_NUMBER --repo vanityURLs/code --remove-label "autorelease: pending" --add-label "autorelease: tagged"`.
+- Confirm GitHub shows the latest release: `gh release list --repo vanityURLs/code --limit 5`.
- Confirm GitHub release tag rules protect `refs/tags/v*` from deletion, updates, and force-pushes, including
administrator bypass.
+
+Repairing a missing release tag
+
+If release-please reports `There are untagged, merged release PRs outstanding`, the manifest may point to a version
+whose tag was never pushed. Create the missing signed tag on the release PR merge commit, not on the current `HEAD`:
+
+```sh
+git switch main
+git pull --rebase
+git -c gpg.format=x509 -c gpg.x509.program=gitsign tag -s vX.Y.Z RELEASE_MERGE_COMMIT -m "vX.Y.Z"
+git -c gpg.format=x509 -c gpg.x509.program=gitsign verify-tag -v vX.Y.Z
+git push origin vX.Y.Z
+awk '/^## /{if(seen) exit; seen=1} seen {print}' CHANGELOG.md > /tmp/vanityurls-vX.Y.Z-release-notes.md
+gh release create vX.Y.Z --repo vanityURLs/code --title "vX.Y.Z" --notes-file /tmp/vanityurls-vX.Y.Z-release-notes.md
+gh pr edit RELEASE_PR_NUMBER --repo vanityURLs/code --remove-label "autorelease: pending" --add-label "autorelease: tagged"
+```
+
+Rerun release-please after the repair. It should find the tag and stop scanning back into older repository history.
+
+```sh
+gh run list --repo vanityURLs/code --workflow release-please.yml --limit 5
+gh run rerun RUN_ID --repo vanityURLs/code
+```
+
+
+
## Tag Protection
Before enforcing this workflow, configure GitHub tag rules for `refs/tags/v*`: