# Release Runbook

No local command or `CI=true` environment variable grants release authority.
The Engineer authorizes each release after every gate below is evidenced.

## Current hard stops

Do not publish while any of these remains unresolved:

- account-side token/session revocation and sensitive-history disposition;
- project license plus bundled code/data/logo provenance and redistribution
  rights recorded in
  [`docs/data-and-asset-provenance.md`](docs/data-and-asset-provenance.md);
- live cross-engine/browser-Worker evidence against the immutable release
  candidate;
- exact native target manifest and multi-platform release evidence;
- accurate public repository/support metadata;
- protected release environment and npm trusted-publisher identity.

## One-time external setup

1. Configure a GitHub-hosted workflow as the npm trusted publisher, restricted
   to the exact repository, workflow filename, environment, and `npm publish`
   action.
2. Require approval on the release environment and protect release tags/checks
   on a hosting plan that supports those controls.
3. Restrict GitHub Actions to approved SHA-pinned actions; enable vulnerability
   alerts, dependency review, secret scanning, and automated security updates
   where available.
4. Decide commit/tag signing identities and publish verification keys.

npm trusted publishing can authenticate a private GitHub repository, but npm
does not currently generate provenance for a public package built from a
private repository. Resolve that limitation by publishing reviewed source or
attaching an independently verifiable GitHub artifact attestation; never claim
provenance that the registry did not emit.

## Prepare

1. Start from a clean, reviewed commit whose identity is attributable.
2. Select npm `11.12.1` and assert that exact version before the first project
   install or script. The workflows pin Node, Bun, Deno, Rust, and every action
   separately; a runner default is not release evidence.
3. Confirm tag, root package version, generated package version, Cargo version,
   and dated changelog release heading are identical. The metadata verifier
   permits an untagged branch for a local/CI rehearsal, but an actual release
   must pass `--require-tag` on exactly `v<version>` or `<version>`.
4. Confirm all P0/P1 enterprise findings are closed or carry an explicit dated
   Engineer risk acceptance.
5. Run frozen installs on fresh GitHub-hosted runners; never reuse a developer
   `node_modules`, Rust target tree, or old `lib/` directory.
6. Build every declared native target in its own clean target job and verify
   the exact manifest/interface/platform/checksum before aggregation.

## Verify and seal

Run the full JS, Rust, package, runtime, real-browser, security, license,
critical-contract, and workspace gates. Build the canonical package once, then:

1. create one `.tgz` from `lib/` and retain the complete `npm pack --json`
   record;
2. install that exact tarball into a fresh consumer with lifecycle scripts
   disabled, freeze the installed `node_modules/zynor`, and exercise only that
   installed tree;
3. generate deterministic JS and Rust CycloneDX SBOMs bound to the candidate
   SHA-256 and source commit, plus third-party notices;
4. record SHA-256, size, file inventory, toolchains, source commit, and native
   artifact hashes in `release-evidence.json`;
5. attest/sign that immutable tarball and attach the same artifacts to the
   release record.

Rebuilding between verification and publication invalidates the evidence.

Ordinary pull-request CI uses deterministic DNS fixtures. Public Google,
Cloudflare, and Quad9 availability is therefore not a prerequisite for an
unrelated change to pass. The manually dispatched release-readiness workflow
is different: after building `lib/` once, it creates and checksums one tarball,
extracts that immutable candidate, and must run live Node/Bun/Deno, Cloudflare
Workerd, Chromium, Firefox, and WebKit checks against those exact packaged
bytes. Browser coverage includes page and dedicated-Worker origins. Treat a
scheduled live job as early warning only. When a live gate fails, preserve the
provider/runtime diagnostic and determine whether the cause is external
reachability or a Zynor regression; never waive or silently retry a red release
gate into green.

The package-runtime matrix covers both ESM and CommonJS for the main and native
entries on Node and Bun. Deno covers both entries through ESM. Cloudflare
Workers exercise the complete main entry (including the provider named
`native`) and reject `zynor/native`; browser pages and dedicated Workers expose
the DoH-capable main entry and reject `zynor/native`. The authoritative browser
release gate has zero retries. Scheduled live diagnostics may retry once only
to retain a second trace; their result never substitutes for the release gate.

The release run writes separate candidate-bound JSON evidence for the runtime
matrix (`runtime-evidence.json`), Workerd (`workerd-evidence.json`), and
Playwright (`browser-evidence.json`). The browser record is all-and-only nine
named tests in each of Chromium, Firefox, and WebKit: exactly 27 passes, no
skip, retry, flaky result, duplicate, or extra. The critical-contract verifier
checks all three records against the frozen tarball SHA-256 and clean source
commit before gate status is finalized.

Release evidence upload runs even after a failed gate and contains only the
candidate metadata, structured runtime records, checksums, normalized SBOMs,
and bounded sanitized diagnostics. Raw child output is not uploaded. It is
diagnostic evidence, not permission to publish. `CI=true ./deploy.sh` is also
a local CI rehearsal only: it does not create immutable-candidate live
evidence.

## Publish

Only the protected trusted-publishing workflow may publish the verified `.tgz`.
Do not run `npm publish` from the repository root or mutable `lib/` directory.
The publishing job receives `id-token: write` only in the release job and no
long-lived npm token.

## Post-release

- Verify npm version, integrity, exports, declarations, provenance/attestation,
  SBOM, and a clean consumer install.
- Run pinned Node/native and browser/Worker smoke checks against the published
  package.
- Record the registry timestamp, tarball digest, release URL, and checks in the
  release log without recording credentials.
- Monitor provider/runtime failures and security reports through the declared
  observation window.

## Rollback, deprecate, or yank

npm packages are immutable. For a non-exploited defect, deprecate the bad
version with a precise upgrade message and publish a reviewed patch. For an
active security incident, coordinate advisory timing, revoke compromised
credentials, restrict affected infrastructure, deprecate/yank only when
ecosystem impact is understood, and preserve forensic evidence privately.
Never overwrite tags or silently rebuild an existing version.
