# Public Release Runbook

Last updated: 2026-07-06

Canonical runbook for shipping OpenDevBrowser public releases (npm package + GitHub release artifacts) from this repository.

## Current source-backed release reference

As of the 2026-07-06 release prep, the checked-in release version is `0.0.40` across `package.json`, root `package-lock.json`, `package-lock.json#packages[""]`, `extension/manifest.json`, and `extension/package.json`. The current version-scoped release ledger is `docs/RELEASE_0.0.40_EVIDENCE.md`; `docs/RELEASE_0.0.39_EVIDENCE.md` and older ledgers are historical.

Use `npm run version:check` as the authoritative local parity check for those five version fields before tagging or publishing.

## Scope

This runbook covers:
- release prep on a branch
- tagged release execution through `.github/workflows/release-public.yml`
- post-release validation and rollback controls

It does not cover website hosting deploys. Website deploy cutover is handled in `docs/CUTOVER_CHECKLIST.md` plus the private-repo deployment docs.

## Required inputs

- Release version: `X.Y.Z`
- Release tag: `vX.Y.Z`
- Public repo branch with merged release changes
- npm publish token (`NPM_TOKEN`) configured in GitHub Actions secrets

## Required CI configuration

- Workflow: `.github/workflows/release-public.yml`
- Trigger modes:
  - Tag push: pushing `vX.Y.Z` runs the publish-enabled release path with `publish_npm=true`, `publish_github_release=true`, `draft_release=false`, and `run_release_live_gates=false`.
  - `workflow_dispatch`: manual dry runs or controlled publishes use the inputs below.
- Workflow dispatch inputs:
  - `release_ref`
  - `release_tag`
  - `publish_npm`
  - `publish_github_release`
  - `draft_release`
  - `run_release_live_gates`

## Preflight checklist

- ### Local preflight policy

- [ ] `package.json` version is updated to target semver.
- [ ] Extension version metadata is synced (`npm run extension:sync` updates `extension/manifest.json` and `extension/package.json`).
- [ ] Local policy gates pass:
  - `npm run version:check`
  - `npm run test:release-gate` (grouped contract coverage only; rerun only failed group with `npm run test:release-gate:g<N>`)
  - `node scripts/audit-zombie-files.mjs`
  - `node scripts/docs-drift-check.mjs`
  - `node scripts/chrome-store-compliance-check.mjs`
  - `./skills/opendevbrowser-best-practices/scripts/validate-skill-assets.sh`
  - `./skills/opendevbrowser-motion-design/scripts/validate-skill-assets.sh`
  - `npx opendevbrowser --help`
  - `npx opendevbrowser help`
  - Treat the two help commands above as release-facing wording proof for `screencast / browser replay`, `desktop observation`, and `computer use / browser-scoped computer use`.
  - `npm run lint`
  - `npm run typecheck`
  - `npm run test`
  - `npm run build`
  - `npm run extension:build`
  - `node scripts/provider-direct-runs.mjs --release-gate --out artifacts/release/vX.Y.Z/provider-direct-runs.json`
  - `node scripts/live-regression-direct.mjs --release-gate --out artifacts/release/vX.Y.Z/live-regression-direct.json`
  - Treat the two direct-run commands above as the optional strict live release-proof lane. Keep them distinct from grouped contract checks such as `npm run test:release-gate`.
  - `npm run extension:pack`
  - `npm pack`
- [ ] After npm publish, `node scripts/registry-consumer-smoke.mjs --version X.Y.Z --output artifacts/release/vX.Y.Z/registry-consumer-smoke.json` succeeds from a fresh temp workspace install and captures the resolved consumer dependency graph.
- [ ] First-time global install dry run passes (`docs/FIRST_RUN_ONBOARDING.md`) with daemon + extension + mode validation evidence captured.
- [ ] Release branch is merged to `main`.

### CI workflow gates executed by `.github/workflows/release-public.yml`

- [ ] `npm ci`
- [ ] `npm run version:check`
- [ ] `node scripts/audit-zombie-files.mjs`
- [ ] `node scripts/docs-drift-check.mjs`
- [ ] `node scripts/chrome-store-compliance-check.mjs`
- [ ] `./skills/opendevbrowser-best-practices/scripts/validate-skill-assets.sh`
- [ ] `./skills/opendevbrowser-motion-design/scripts/validate-skill-assets.sh`
- [ ] `npm run lint`
- [ ] `npm run typecheck`
- [ ] `npm run test`
- [ ] `npm run build`
- [ ] `npx opendevbrowser --help`
- [ ] `npx opendevbrowser help`
- [ ] `npm run extension:build`
- [ ] When `publish_npm=true`, run `node scripts/registry-consumer-smoke.mjs --version X.Y.Z --output artifacts/release/vX.Y.Z/registry-consumer-smoke.json` after `npm publish`
- [ ] Optional strict live gates run only when `run_release_live_gates=true`:
  - `node scripts/provider-direct-runs.mjs --release-gate --out artifacts/release/vX.Y.Z/provider-direct-runs.json`
  - `node scripts/live-regression-direct.mjs --release-gate --out artifacts/release/vX.Y.Z/live-regression-direct.json`
- [ ] `npm run extension:pack`
- [ ] GitHub release artifact packaging computes `opendevbrowser-extension.zip.sha256` before publish.

## Release execution

### Local npm-token release path

Use this lane when publishing npm from a locally authenticated workstation instead of the GitHub Actions `NPM_TOKEN` secret.

1. Merge the release prep branch to `main`, then update the local checkout:

```bash
git checkout main
git pull --ff-only
```

2. Verify the target package is still absent from npm:

```bash
npm view opendevbrowser@X.Y.Z version
```

The command should fail before first publish. If it already returns `X.Y.Z`, stop and verify whether this is a recovery publish or a duplicate release.

3. Publish npm locally without printing credentials:

```bash
npm publish --access public
```

4. Run the registry consumer smoke against the published package:

```bash
node scripts/registry-consumer-smoke.mjs --version X.Y.Z --output artifacts/release/vX.Y.Z/registry-consumer-smoke.json
```

5. Create the GitHub release with extension artifacts by dispatching `.github/workflows/release-public.yml` instead of pushing a release tag directly:

```bash
gh workflow run release-public.yml \
  -f release_ref=main \
  -f release_tag=vX.Y.Z \
  -F publish_npm=false \
  -F publish_github_release=true \
  -F draft_release=false \
  -F run_release_live_gates=false
```

This avoids the tag-push default path, which sets `publish_npm=true` and would attempt a duplicate npm publish.

### Standard tag-driven release (recommended)

1. Create and push tag from merged commit:

```bash
git checkout main
git pull --ff-only
git tag vX.Y.Z
git push origin vX.Y.Z
```

2. GitHub Actions automatically runs `Public Release` with publish enabled.
3. Workflow gates:
- resolves publish defaults from the trigger mode and validates `release_tag` format (`vX.Y.Z`)
- validates `package.json` version matches the tag
- runs `npm run version:check`
- runs `node scripts/audit-zombie-files.mjs`
- runs `node scripts/docs-drift-check.mjs`
- runs `node scripts/chrome-store-compliance-check.mjs`
- runs `./skills/opendevbrowser-best-practices/scripts/validate-skill-assets.sh`
- runs `./skills/opendevbrowser-motion-design/scripts/validate-skill-assets.sh`
- runs `npm run lint`
- runs `npm run typecheck`
- runs `npm run test`
- runs `npm run build`
- runs `npx opendevbrowser --help` and `npx opendevbrowser help` as release-facing wording proof for `screencast / browser replay`, `desktop observation`, and `computer use / browser-scoped computer use`
- runs `npm run extension:build`
- optionally runs strict live gates when `run_release_live_gates=true`
  - this input executes only `node scripts/provider-direct-runs.mjs --release-gate ...` and `node scripts/live-regression-direct.mjs --release-gate ...`, not grouped wrappers or matrix scripts
- runs `npm run extension:pack`
- computes checksum (`opendevbrowser-extension.zip.sha256`)
- publishes npm package when enabled
- runs `node scripts/registry-consumer-smoke.mjs --version X.Y.Z --output artifacts/release/vX.Y.Z/registry-consumer-smoke.json` after npm publish when enabled
- publishes GitHub release and uploads extension artifacts when enabled

### Manual dry-run release

Use workflow dispatch when validating release flow without publishing npm:

- `publish_npm=false`
- `publish_github_release=true`
- `draft_release=true`

This path validates build/package/release artifact generation before production tag release.

## Post-release validation

Run and record:

```bash
npm view opendevbrowser version
node scripts/registry-consumer-smoke.mjs --version X.Y.Z --output artifacts/release/vX.Y.Z/registry-consumer-smoke.json
```

Confirm all of the following:
- npm version equals tag version (`X.Y.Z`)
- registry-consumer smoke confirms `--help`, `help`, `version`, packaged assets, and captures the resolved dependency graph from a fresh npm install
- GitHub release `vX.Y.Z` exists
- release assets include:
  - `opendevbrowser-extension.zip`
  - `opendevbrowser-extension.zip.sha256`
- checksum file matches uploaded zip

## Failure handling

### Tag/version mismatch

- Symptom: workflow fails at version validation.
- Action: fix `package.json` and manifest sync, retag with corrected version.

### npm publish failure

- Symptom: release job fails on `npm publish`.
- Action:
  - verify `NPM_TOKEN`
  - verify package version is not already published
  - rerun using workflow dispatch once corrected
  - if npm is already live and only the GitHub release is missing, rerun the same tag with `publish_npm=false`, `publish_github_release=true`, and `draft_release=false`

### GitHub release artifact failure

- Symptom: tag exists but release/assets missing.
- Action:
  - rerun workflow dispatch for the same tag with `publish_github_release=true`
  - ensure artifact packaging step is green

## Rollback controls

If release is bad after publish:

1. Deprecate problematic npm version:

```bash
npm deprecate opendevbrowser@X.Y.Z "deprecated: use <fixed-version>"
```

2. Prepare and publish corrective release `X.Y.Z+1` (or next semver).
3. Update release notes with incident summary and migration guidance.

## Evidence to retain

- Active version-scoped release evidence ledger path, currently `docs/RELEASE_0.0.40_EVIDENCE.md` for package version `0.0.40`
- Release workflow run URL
- npm published version output (or explicit publish deferral for manual dry runs)
- `artifacts/release/vX.Y.Z/registry-consumer-smoke.json`
- GitHub release URL
- checksum artifact
- If `run_release_live_gates=true`, retain `artifacts/release/vX.Y.Z/provider-direct-runs.json` and `artifacts/release/vX.Y.Z/live-regression-direct.json`; otherwise record explicit deferral in the active version-scoped release evidence ledger.
- rollback/deprecation record (if incident occurred)
