---
title: "Releasing"
description: "Cut a Chickpea release: the source gates, attended acceptance, and publishing a tag that never moves."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.chickpea.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Releasing

A **release** is a SemVer tag such as `v0.1.0` plus a matching GitHub release, cut from one exact commit. Merging to `main` changes source and deploying changes one running deployment; neither publishes a release, and Chickpea publishes no npm package. This page covers the three gates (source, attended acceptance, publish), the export allowlist, the model catalog's immutable revisions, and what release notes must contain.

## Prerequisites

- **A clean checkout of the exact candidate commit**, with no private environment files. Never `.env`, `.dev.vars`, SQLite databases, credential keyrings, setup links, or live transcripts.
- **Node 24.20.0.** `.nvmrc` pins the single release-verification baseline. Only Node 24.x is supported, minimum 24.20.0. Update the pin for later patch/security releases and verify it once, without a second runtime sweep.
- **An approved disposable deployment and Slack workspace** for acceptance. Not a production workspace, and not another task's environment.
- **Repository settings in place.** Force pushes and deletions blocked on `main`, GitHub Actions disabled with no required workflow checks, Dependabot alerts and security updates on, private vulnerability reporting on.
- **Explicit authorization** to tag and publish that exact commit.

## Run the source gates

Start from the clean checkout. Run the checks serially, once on the pinned Node 24 baseline, because tests inspect generated artifacts and a concurrent build makes them read a partially written artifact.

```sh
nvm install && nvm use
npm ci
npm audit --omit=dev
npm run verify:regression -- --mode release
```

The release command restores artifacts and runs authoring, Admin, local workerd,
lockfile, and committed-source export checks. The export installs, builds, and
runs the full root/CLI suite plus offline turn, durability, and provider checks.
Those checks run once inside the export; the outer command does not repeat them.
Use `--record <private-run.json>` with the existing verification notebook to keep
logs and checkpoint evidence. Missing evidence, stale configuration, unresolved
failures, and the final merge-result check remain release gates.

- **Commit first.** `verify:oss-export` reads committed HEAD. Release mode refuses dirty source or private environment files; an earlier commit's pass does not validate new changes.

- **`verify:cf-smoke` takes several minutes.** It builds both Cloudflare profiles and runs the core profile in local workerd, and it treats `.wrangler-state` in that directory as disposable.
- **No hosted run counts.** There is no GitHub workflow that runs or deploys a release. Record the local results with the change or the release evidence.

## What `verify:oss-export` checks

The verifier reads the tracked file list at HEAD, extracts that archive into a temporary directory it prints as `SCRATCH=<path>`, and then:

- **Scans every tracked file for denied terms.** Private project and company names, local home-directory paths, and, in the live-verification sources, Slack tokens, provider API keys, and Slack coordinate IDs.
- **Blocks unlisted documents.** A tracked path under `docs/` that is not on the `allowedPublicDocs` list fails with `OSS export contains forbidden public-source paths:`.
- **Blocks unlisted binaries.** Image and media extensions fail unless the path is on the `allowedBinaryFiles` list with a matching SHA-256 digest.
- **Blocks local agent and tool state.** Tracked paths under `evidence/`, `screenshots/`, `transcripts/`, `tmp/`, and the agent-state dot directories never ship, apart from one allowed skill file.
- **Requires the license files and public package metadata.** `LICENSE` and `NOTICE` must exist, and `package.json` must stay `private` with an `Apache-2.0` license, a description, and a repository.
- **Rebuilds the archive from nothing.** `npm ci`, `npm run build`, `npm run test:ci`, the offline Flue turn, `verify:durability`, `verify:providers`, and a deployment dry run, all inside the scratch directory.

A pass prints `OSS export verification passed` and deletes the scratch directory. Set `KEEP_EXPORT_SCRATCH=1` to keep it for inspection.

Adding a public document to the release means adding its path to the allowlist in `scripts/verify-oss-export.mjs` in the same change. A new runtime image goes on the binary allowlist with its digest, and into `src/assets/public-assets.ts` so Node serves it.

## Record the compressed upload size

The deployment dry run at the end of `verify:oss-export` is the measurement. To take it by hand, run `npm run deploy -- --dry-run`; the deploy wrapper uploads nothing on a dry run and passes wrangler's output through untouched.

- Record the compressed upload size from that dry run, not the size of the repository or `node_modules`.
- Keep headroom below the advertised plan's Worker limit. The current bundle exceeds the Free plan's 3 MiB limit, which is why Chickpea advertises Workers Paid.
- A dynamic import still contributes its uploaded chunk to that limit.
- Do not advertise Free compatibility unless the release artifact fits those limits and the relevant runtime checks pass.

## Rebuild the model catalog when its source changed

`catalog/current.json` is generated from `catalog/source.json`. Do not edit it by hand.

- `npm run catalog:build` with no arguments only checks. It prints `Catalog revision <n> is deterministic and immutable.`, or fails with `catalog/current.json is not the deterministic output of catalog/source.json.`
- `npm run catalog:build -- --write` writes `catalog/current.json` and `catalog/releases/revision-<n>.json`. The revision file is written exclusively: rewriting a published revision with different bytes fails with `Immutable release already exists with different bytes:`.
- Bump `revision` in `catalog/source.json` when entries change. A published revision keeps its bytes forever.
- The source gates above do not include this check. Run `npm run verify:model-catalog` yourself when the catalog changed; it rebuilds the catalog and runs the catalog tests.

## Run attended acceptance

Fake-backend tests and local workerd give deterministic coverage. They do not establish real Slack and Admin acceptance, so this gate is a live run against a disposable environment. Keep identities, bindings, secrets, and evidence outside the public repository.

1. Choose the approved disposable deployment and Slack workspace before you install anything.
2. Install the exact candidate from a fresh public source checkout with no local private files.
3. Exercise each advertised target and Slack lane. Name any lane you did not test instead of claiming general acceptance.
4. Complete Slack installation, first-Owner sign-in, provider and model selection, and a real DM or mention that receives a reply from the candidate deployment.
5. Confirm a second user cannot reach Owner-only configuration or another Agent's personal connection. Check an approved connector flow if the release advertises one.
6. Restart or redeploy, then confirm sign-in, existing state, and Slack delivery still work. Check retry and deduplication behavior without claiming exactly-once delivery.
7. On Cloudflare, verify an approved schedule fires and stops when paused. Test the coding sandbox if this release advertises the optional profile.
8. For a second or later release, test the documented upgrade from the prior supported version and the stated recovery procedure. A fresh install does not replace upgrade testing.
9. Remove only the fixtures and resources this run created. Record the source commit, the target and version, observed outcomes, limitations, and cleanup.

## Write the release notes

The notes are the operator's contract for that version. They must cover:

- **Prerequisites and installation links**, so an operator can install the tagged source the documented way.
- **Changes and known limitations** in this version.
- **Supported upgrade origins.** State the supported starting versions and the required operator actions. There is no implied upgrade path from an unlisted experimental schema.
- **Migrations**, and whether a downgrade is safe. If the notes do not declare a downgrade safe, operators use the tested recovery procedure instead.
- **Recovery limits.** Reverting code does not undo a schema migration or recover deleted data.
- **Experimental status.** For a 0.x release, say so, and decide whether GitHub's prerelease flag applies.

## Verify

Confirm all of this before you create the tag.

- `package.json`, the lockfile, and the tag carry the same version. The tag is `v0.1.0`, not `v0.01`.
- The candidate commit is on `main`, and the local checks were rerun or confirmed on that final commit.
- Attended acceptance ran against this commit, with untested lanes named rather than implied.
- The release notes cover every item above.
- You hold explicit authorization to tag and publish this exact commit.

> **A published tag never moves**
>
> Do not retag, force-push, or delete a published version. If a release is wrong, issue a new version for the correction.

## Publish

1. Land the release preparation through a GitHub pull request or a maintainer-authorized local merge into root `main`, verified before pushing.
2. Create the version tag and the GitHub release together, on the authorized commit.
3. Download the release's source archive and confirm it installs by the documented path.
4. Publish no secrets and no private acceptance evidence.

## Next steps

- [Contribute](/contribute): the development checkout and the source layout behind these gates.
- [Upgrades and compatibility](/operate/upgrades): the upgrade and recovery procedures your release notes commit operators to.
- [Security model](/security/security-model): the guarantees a release must not weaken.

Source: https://docs.chickpea.co/contribute/releasing/index.mdx
