Launch Runbook¶
How kew reaches its two registries in one motion: what is automatic, the
one-time manual steps, and the single-flag launch.
Linear epic-driver launch gate¶
The Linear driver is a supported dual-rail surface, but a launch claim requires both the automated suite and a recorded live acceptance on a disposable Linear project. Use a throwaway SFD project and non-sensitive issue text only. The project must contain these roles:
| Role | Required setup | Expected ownership/outcome |
|---|---|---|
| Armed root | unstarted/Todo, kew and kew:ready |
The only epic item dispatched on the first tick |
| Unarmed/backlog root | Backlog or unarmed Todo | Remains queued and is never dispatched |
| Parent/container | Has one or more Linear children | Metadata only; never dispatched |
| Dependent | native blocks edge from the root |
Waits, then enters the frontier after the root is Done |
| Delegated non-epic | outside the project, assigned to the kew seat | Claimed only by kew delegate watch |
Configure the project with tracker.type = "linear", its Linear team key, and
the GitHub repository that hosts the dispatch branches and PRs. Export
LINEAR_API_KEY only in the operator environment. Do not place the value in
configuration, shell transcripts, issue content, or this record. OAuth #837 is
not part of this gate.
The acceptance operator should run the following from the repository root, substituting only sanitized project/item/PR identifiers in the report:
uv run kew epic graph <throwaway-project>
uv run kew epic validate <throwaway-project>
uv run kew epic status <throwaway-project>
uv run kew epic advance <throwaway-project>
uv run kew epic status <throwaway-project>
# After the armed root's GitHub PR is human-approved and CI-green, merge it
# externally on GitHub, then reconcile:
uv run kew epic advance <throwaway-project>
uv run kew epic status <throwaway-project>
uv run kew delegate watch --dry-run
Record the first tick's dispatched set, the dependent's waiting status, the
parent's absence from the dispatch set, the delegation dry-run candidate, and
the final Linear state. A positive final result requires the externally merged
GitHub PR to reconcile the corresponding Linear item to Done. The driver never
merges without GitHub's verified human approval and green required CI. A
Linear 502/503/504 or reset is classified linear-unavailable after bounded
retry and exits 2 for a re-run; a 4xx or GraphQL error fails fast. See the
dated acceptance record (docs/audit/2026-08-04-linear-epic-driver-uat.md in
the repository — internal audit packs are not published) for the sanitized
evidence format.
Distribution names: kew-cli on PyPI (pip install kew-cli; the
installed command is kew — the PyPI name kew is an unrelated project) and
create-kew on npm (npx create-kew, the recommended onboarding path).
The release pipeline lives in .github/workflows/release.yml; a single publish
gate — the repository variable PUBLISH_RELEASE — governs both the PyPI and
npm publishes, so they flip together.
One-time setup (now, alongside the packaging PR)¶
1. Create the GitHub environments¶
gh api --method PUT repos/thossullivan/kew/environments/pypi
gh api --method PUT repos/thossullivan/kew/environments/testpypi
gh api --method PUT repos/thossullivan/kew/environments/npm
2. Register the pending publishers (web UI — the only manual steps)¶
On pypi.org: log in → Account settings → Publishing (https://pypi.org/manage/account/publishing/) → "Add a new pending publisher" → GitHub tab:
| Field | Value |
|---|---|
| PyPI Project Name | kew-cli |
| Owner | thossullivan |
| Repository name | kew |
| Workflow name | release.yml |
| Environment name | pypi |
On test.pypi.org: same form at https://test.pypi.org/manage/account/publishing/:
| Field | Value |
|---|---|
| PyPI Project Name | kew-cli |
| Owner | thossullivan |
| Repository name | kew |
| Workflow name | release.yml |
| Environment name | testpypi |
On npmjs.com: no access token is used — trusted publishing is OIDC. Log in →
the create-kew package's Settings → Trusted Publishing (Account → Publishing
before the package exists) → add a GitHub Actions publisher:
| Field | Value |
|---|---|
| Package name | create-kew |
| Organization/User | thossullivan |
| Repository | kew |
| Workflow filename | release.yml |
| Environment | npm |
3. Claim the names (stub publish — PyPI + npm in one dispatch)¶
A single dispatch claims both names and exercises both OIDC auth paths
(the only real pre-launch test of the npm auth round-trip — the standing
npm publish --dry-run cannot check auth):
Verify PyPI: https://pypi.org/project/kew-cli/ exists and shows only
0.0.1.dev0, and a plain install still fails for the public (pip ignores
dev releases without --pre):
Verify npm:
The create-kew@0.0.1 stub is a functional-but-old wizard (npm has no
dev-release equivalent to hide it). Optionally deprecate it so it never
resolves as @latest at launch — needs an interactive npm login, so it is not
run in CI:
Standing rehearsal (automatic)¶
Every release on main publishes kew-cli to TestPyPI (skip-existing: true
absorbs workflow re-runs) and attaches the sdist + wheel to the GitHub release,
and runs npm publish --dry-run for create-kew (proving the tarball packs;
it does not check npm auth — that is what the §3 stub claim is for).
Spot-check monthly, and once more just before launch:
pipx install --index-url https://test.pypi.org/simple/ \
--pip-args="--extra-index-url https://pypi.org/simple/" kew-cli
kew --version
pipx uninstall kew-cli
Launch day¶
One flag flips both registries:
Then produce a release. Re-running an old workflow run will not publish —
semantic-release finds no new commits and releases nothing. Either merge any
pending fix:/feat: PR, or:
That single release run publishes kew-cli to PyPI, then (ordered after it)
create-kew to npm at the same version.
Verify:
- Both the
publish-pypiandpublish-npmjobs ran (not skipped) in the release workflow. - https://pypi.org/project/kew-cli/ renders the landing page and https://www.npmjs.com/package/create-kew shows the new version; all links and badges resolve.
- On a clean machine:
pipx install kew-cli && kew --version && kew --help, andnpx create-kew@latestscaffolds cleanly.
Rollback¶
- Stop future publishes:
gh variable set PUBLISH_RELEASE --body false(TestPyPI + npm dry-run rehearsals continue; nothing already published is affected). - Bad PyPI release: yank it, never delete it. There is no official CLI for
yanking — use the web UI: https://pypi.org/manage/project/kew-cli/releases/
→ the release → Options → Yank. Yanked versions remain installable for
users who pin
kew-cli==X.Y.Zexactly, but resolvers skip them otherwise. - Bad npm release:
npm deprecate create-kew@X.Y.Z "reason"(warns on install but keeps it resolvable).npm unpublish create-kew@X.Y.Zonly within 72h and only if nothing depends on it — never rely on unpublish for a version users may already have pinned.