Release workflow
Use this at the end of every Vivary update that changes behavior, packaging, public docs, install commands, release status, or package versions.
The rule: merging to dev is not a release, and development slices do not publish
early. A coordinated train publishes only after every held item on it is complete and
separately approved. At that final gate, core and all dependent packages publish
together, and the website copy updates with them.
The Vivary Governed Context train published and was verified on 2026-08-15. A train name is a planning and release label, not a suite version, and it is never evidence that a given artifact reached a registry. Exact current source and registry versions live in the root release status; maturity lives in MIGRATION-STATUS.md.
1. Name the train and decide the release scope
Section titled “1. Name the train and decide the release scope”Versions are independent per package — there is no single “Vivary X.Y.Z”. Work out which packages actually changed, then bump only those:
| What changed | Package to bump | Also update |
|---|---|---|
packages/tropo/tropo.py or its tests |
vivary-tropo |
README release line, COMMANDS if CLI changed |
packages/ozone/ozone.py |
vivary-ozone |
same |
packages/exo/exo.py |
vivary-exo |
same |
packages/create-vivary/create_vivary.py |
create-vivary (PyPI) and @vivary/create (npm) — always in lockstep |
same |
packages/create-vivary/create_vivary_assets/ or legacy full-workspace fixtures |
No user-facing package bump by themselves; these are repository-only compatibility archives and must remain excluded from wheels and source distributions | parity/packaging proof |
packages/strato/ templates or skills |
vivary-strato when its runtime/package surface changes; do not copy these assets into thin init/adopt output |
same |
packages/strato/strato.py, its tests, or CLI contract |
vivary-strato — bump the version and publish it in the next coordinated train alongside core and the other role packages |
ARCHITECTURE seam section, COMMANDS, README surface row |
packages/memory-cognee/vivary_cognee.py |
vivary-memory-cognee |
same |
packages/mcp/vivary_mcp.py or its tests |
vivary-mcp — keep it optional and off by default; preserve the exact reviewed MCP SDK pin |
MCP guide, package README, Tropo floor |
packages/core/ modules or tests |
vivary-core — bump the version and publish it in the next coordinated train, always before every dependent role package |
ARCHITECTURE seam section, README surface row |
dependency floors in packages/vivary/pyproject.toml |
vivary (meta) — bump its floors and patch version when component minimums move |
README table |
docs/, site/, root README only |
no package bump — site redeploys from dev via Vercel automatically |
keep docs/site sync (step 3) |
| repo CI / stats / tests only | no bump, no site work | — |
Bump rules (semver-ish, pre-1.0):
- new user-visible command, subcommand, or flag → minor (0.3.0 → 0.4.0);
- bug fix, hardening, docs-in-package, or template tweak → patch;
create-vivaryPyPI and@vivary/createnpm versions are always identical (packages/create-vivary/pyproject.toml+packages/create-vivary/npm/package.json);- a role package that first imports
vivary_coreadds its dependency floor in the same commit, as defined by the architecture’s dependency direction, and takes a patch bump at minimum; - never re-release an existing version number; registries are immutable.
Train and version lifecycle
Section titled “Train and version lifecycle”- Planned — name the train in the approved plan and top changelog entry. Do not assign a suite semver.
- Staged — set each changed package’s independent next version, dependency floors,
and source status. Keep
create-vivaryand@vivary/createidentical. The README registry table still shows the old published versions. - Publishing — after the train-level approval, publish one exact artifact at a time in dependency order. If publication is partial, name each artifact that reached its registry and keep the train itself incomplete.
- Registry-complete — every planned artifact is visible at its exact version, but the train is not yet verified.
- Verified — cache-resistant install and CLI smokes pass for every artifact; only then update the root registry table and change the same changelog entry to “Published and verified.”
The historical independent versions remain valid history. A source change after one of its versions has published requires a new package version; it never reuses the published number or forces unrelated packages to match it. This is the selected versioning policy for #149.
2. Set release truth first
Section titled “2. Set release truth first”Update every surface that names versions or the command set, in the repo, before publishing:
packages/<pkg>/pyproject.toml— updateversion = "..."and any dependency floors. When the package exposes a module__version__constant, update that too; its parity test must match the manifest.vivary-corehas no module__version__;packages/core/pyproject.tomlis its sole in-repo version declaration, and step 6 verifies the installed distribution version.packages/create-vivary/npm/package.json— lockstep version;- root
README.md— the train name/state, registry table, development-source line, create-only lockstep statement, and “Current command surface” list; CHANGELOG.md— new entry at the top, matching the existing format: package names + versions + date, what changed, and a Verification section listing only the exact smoke commands actually run. Before publishing, the entry says “Publishing remains a manual human gate.” After publishing, change that same entry to “Published and verified” with exact versions;docs/MIGRATION-STATUS.mdonly when a surface changes classification, anddocs/DECISIONS.mdonly when a durable decision changes;- package
README.mds whose status lines name versions; docs/COMMANDS.mdfor CLI changes; the homepage FAQ /docs/PORTFOLIO.mdif they name versions or surfaces (grep for the old version string);site/src/pages/index.astroif the homepage names versions, commands, or package surfaces —grep -nE "0\.[0-9]+\.[0-9]+|tropo |create-vivary" site/src/pages/index.astroand read what it claims;AGENTS.mdonly when the public agent contract itself changes.
Before publication, old registry versions remain in the published-status table and published install examples. Check that each source version appears only in its manifest, runtime owner, development-status copy, generated mirror, and changelog entry. After publication, search the same surfaces for the replaced version and keep it only where the changelog or compatibility history owns it:
grep -rn "<old-version>" README.md docs/ packages/ site/src/pages/ --include="*.md" --include="*.toml" --include="*.json" --include="*.astro"3. Keep docs and site in sync
Section titled “3. Keep docs and site in sync”Source docs live in docs/ plus root CHANGELOG.md; the site mirrors them.
cd sitenpm run sync-docsnpm run buildgit diff --exit-code -- src/content/docs public/llms.txt public/llms-full.txtCommit the regenerated site/src/content/docs/* with the source docs — CI’s
site build and the graph review gate both expect them to match. (sync-docs
is dependency-free; plain node scripts/sync-docs.mjs works without
npm install.) The live site redeploys from dev on merge via Vercel — there
is no separate site publish step, but the copy only updates if you committed it.
Live npm advisory gate
Section titled “Live npm advisory gate”The site CI job runs npm audit --audit-level=high from site/ immediately after
npm ci. HIGH and CRITICAL advisories block the job. Lower-severity findings remain
visible without turning every advisory-database change into a release blocker. This
is the selected threshold for #232:
it catches release-threatening dependency defects while limiting unrelated CI churn.
The audit reads live registry data, so a site-scoped PR can turn red without changing the lockfile. When that happens, a maintainer:
- checks
npm config get offline, then reruns the audit fromsite/with online advisory lookup (npm audit --offline=false --audit-level=high) and records the advisory and affected dependency path; - distinguishes a registry transport failure from a vulnerability result, retrying the former without claiming the dependency state is green;
- opens a bounded dependency-remediation slice and reviews the resulting lockfile;
- reruns the audit, site behavior tests, build, and link check; and
- keeps the gate blocking until the reviewed remediation is green.
Do not add continue-on-error, skip the audit, weaken the threshold, or use a forced
dependency rewrite to make an unrelated PR green. The historical red/green control is
recorded in CHANGELOG.md; the CI workflow contract and its tests prevent the command,
working directory, job boundary, or install-before-audit ordering from drifting.
4. Make local CLI truth explicit before command smokes
Section titled “4. Make local CLI truth explicit before command smokes”Build, smoke, tag, and publish only from a dedicated clean checkout/worktree at the approved release commit. A clean release worktree prevents ignored build debris, untracked files, and an unrelated developer diff from entering the artifact or changing the command under test. Record the resulting HEAD in the release evidence.
git worktree add --detach ../vivary-release-governed-context <approved-commit>cd ../vivary-release-governed-contextgit rev-parse HEADgit status --porcelain=v1 --untracked-files=allgit diff --quietgit diff --cached --quietThe status command must print nothing and both diff commands must exit 0. Verify that
the release tag or workflow input resolves to that same HEAD before any publish gate.
Do not publish from a dirty primary checkout and do not clean or reset it to make it
look releasable; user work may be present there. Worktree cleanup is a separate
destructive action and needs its own approval.
If the change adds or changes CLI behavior that is not published yet, refresh the local CLIs from the current checkout before testing bare commands:
powershell -ExecutionPolicy Bypass -File scripts/install-local-clis.ps1The script uninstalls existing Vivary uv tools, then installs this checkout. This prevents stale global tools from silently testing an older package.
Pre-PR hardening and review SOP
Section titled “Pre-PR hardening and review SOP”Before opening a PR for an optional provider, installer, filesystem, release, or agent-execution change, run a hostile review pass and turn repeat failures into tests or docs before pushing:
- Direct CLI execution: run changed Python entry files directly, not only through
imports or mocks. A unit suite can miss script-only failures such as missing
importlib.util. - Real optional dependency smoke: when a feature wraps an optional package, create an ignored disposable environment, install the real dependency there, run package presence/dry-run/blocking smokes, and delete the disposable environment after verification. Do not commit proof sandboxes.
- Packaged bridge smoke: when source code imports an optional sibling package
through the installed CLI path, add a CI smoke that installs the local packages and
exercises the bridge. Use
--no-depsonly when the smoke is intentionally proving package/import boundaries without provider runtime or network calls. - Inspect both package formats: inventory every wheel and source distribution. A clean wheel is insufficient if the sdist still carries retired templates, skills, starter records, credentials, or local proof artifacts.
- No silent provider side effects: optional providers must be explicit about network, API-key, telemetry, dotenv, cache, log, and state-directory behavior. Default to closed gates and workspace-scoped paths; add an explicit opt-in flag for any third-party telemetry.
- Path and link abuse: test symlinks, junctions, hard links, absolute paths, nested roots, malformed config, stale manifests, repeated runs, and out-of-root targets. Fail closed with a Vivary error, not a raw Python traceback.
- Cross-platform orientation loop: for scaffold, adopt, Doctor, map, or retrieval
changes, run
python packages/create-vivary/tests/orientation_proof.py --receipt orientation-proof.json. The disposable runner uses both Python and npm entry points, proves dry-run before bounded apply, re-runs adopt after apply to prove idempotence, preserves divergent Git state, and emits a sanitized aggregate receipt. CI runs the same proof on Ubuntu and Windows and retains each receipt whenever the process writes one, including fixture and preflight failures. A timed-out or cancelled process is killed before that write, so it has no receipt to upload. The real transport proof requiresnode,uv(foruvx), andgitonPATH; a missing prerequisite fails closed and writes a preflight receipt. - Security scan shape: scan diffs for shell execution, encoded payloads, inline PowerShell blobs, download-and-execute patterns, secret literals, and broad filesystem deletion. Keep long prompts/instructions in reviewed files and pass paths, not giant inline command strings.
- Docs and site truth: update source docs, package READMEs, changelog, and synced
site docs in the same change. Run
cd site && npm run sync-docs && npm run build. - PR evidence: include exact local commands, real-package smokes, known deferred limits, and any reviewer-found issues in the PR body. If review found a real bug, fix it before push or add the fix as a follow-up commit before merge.
5. Build and publish (human gate, one package at a time)
Section titled “5. Build and publish (human gate, one package at a time)”Publishing is deliberate. Each publish below is its own explicit gate.
PyPI, per changed package:
cd packages/<pkg>rm -rf distuv build # or: python -m builduv publish # or: twine upload dist/* (PyPI token)npm, only when create-vivary changed (lockstep with the PyPI publish), uses
Trusted Publishing from GitHub Actions OIDC instead of a stored npm automation
token. Configure the trusted publisher once on npmjs.com for @vivary/create:
- Provider: GitHub Actions
- Organization / repository:
vivary-dev/vivary - Workflow filename:
npm-trusted-publish.yml(filename only; the file lives in.github/workflows/) - Environment:
npm-publish - Allowed action:
npm publish
Equivalent npm CLI setup, when using npm 11.15.0+ with a maintainer account that has package write access and account-level 2FA:
npm trust github @vivary/create --repo vivary-dev/vivary --file npm-trusted-publish.yml --env npm-publish --allow-publishKeep the GitHub npm-publish environment protected with required reviewers.
The workflow is manually dispatched, checks out the explicit release tag, verifies
the tag/version match, verifies PyPI/npm create-vivary version lockstep, runs
the create-vivary release checks, builds the npm tarball, and verifies its package
identity plus exact MIT license payload. Leave publish=false for the verification-only
gate; rerun with publish=true only after the npm publish gate is approved.
Order when multiple packages ship: dependencies first. Publish vivary-core before
every package that depends on it. Publish vivary-tropo next; then the eligible
vivary-strato, vivary-ozone, vivary-exo, vivary-memory-cognee, vivary-mcp, and
create-vivary artifacts after their declared floors exist. Publish the vivary
meta-package only after all five of its component floors are available. Publish
create-vivary on PyPI before the same-version @vivary/create npm launcher, because
the launcher installs the PyPI distribution at run time. Optional memory and MCP do not
become meta-package dependencies merely because they ride the same train. The
package dependency map owns the edges.
6. Verify from the public registries after publish
Section titled “6. Verify from the public registries after publish”Check the package pages and run cache-resistant install smokes for every changed package:
uv run --isolated --no-project --no-cache --index-url https://pypi.org/simple \ --with vivary-core==<ver> python -c \ "from importlib.metadata import version; import vivary_core; assert version('vivary-core') == '<ver>'"uv run --isolated --no-project --no-cache --index-url https://pypi.org/simple \ --with vivary==<vivary-ver> python -c \ "from importlib.metadata import version; import vivary_core; assert version('vivary') == '<vivary-ver>'; assert version('vivary-core') == '<core-ver>'"uvx --no-cache --index-url https://pypi.org/simple --from vivary-tropo==<ver> tropo --versionuvx --no-cache --index-url https://pypi.org/simple --from vivary-strato==<ver> strato --versionuvx --no-cache --index-url https://pypi.org/simple --from vivary-ozone==<ver> ozone --versionuvx --no-cache --index-url https://pypi.org/simple --from vivary-exo==<ver> exo --versionuvx --no-cache --index-url https://pypi.org/simple --from vivary-memory-cognee==<ver> vivary-cognee --versionuvx --no-cache --index-url https://pypi.org/simple --from vivary-mcp==<ver> vivary-mcp --versionuvx --no-cache --index-url https://pypi.org/simple --from create-vivary==<ver> create-vivary --versionSmoke the front door’s routed verbs from the registry as well. The first line of
uvx --no-cache --index-url https://pypi.org/simple --from vivary==<ver> vivary check --help must be usage: vivary check, and the installed parity checker must pass
against a venv built from the registry:
python -m venv /tmp/vivary-registry/tmp/vivary-registry/bin/pip install --index-url https://pypi.org/simple vivary==<ver>python scripts/check_installed_route_parity.py /tmp/vivary-registry/binpython scripts/check_installed_route_parity.py --characterize /tmp/vivary-registry/binIf npm changed, verify both the registry version and the launcher path:
npm view @vivary/create versionnpx --yes @vivary/create@<ver> capabilities --preset coding --jsonAlso confirm the wheel stays clean: pip download <pkg>==<ver> --no-deps -d tmp/
and list it — wheels must contain the module and nothing else (no tests; the
py-modules allowlists in each pyproject.toml enforce this — don’t loosen them).
Then flip the changelog entry to “Published and verified” (step 2) and commit.
7. GitHub release
Section titled “7. GitHub release”Create or update the GitHub release (human gate) only after the train is verified.
Title it with the train name and the exact independently versioned package set, for
example Vivary Governed Context — vivary-core <ver>, vivary-tropo <ver>, …; do not present
one package’s version as a suite version. Use the changelog entry as the body. If the
release needs a repository tag, select and approve that tag in the release plan rather
than inventing an unowned suite semver.
8. Announce the release
Section titled “8. Announce the release”Create public release content only after registry and live-site verification pass. Do not keep prewritten release posts or a scheduled publisher in the public repo.
Every release gets a coordinated public announcement on Facebook, LinkedIn, X/Twitter, Bluesky, and Instagram — each post with a generated image sized for the platform, conveying what the update means for users (not a changelog dump).
- Draft everything in
.release/private/(ignored local storage — social drafts never enter the public repo): one file per release, containing per-platform copy plus an image prompt per platform. - Tailor, don’t broadcast: X/Bluesky short and concrete (what you can now do, one command example); LinkedIn value-framed for practitioners; Facebook conversational; Instagram image-first with a tight caption.
- Image prompts specify aspect ratio per platform (X/Facebook/LinkedIn 16:9 or 1.91:1 link-card, Instagram 1:1, Bluesky 16:9), and keep the brand language: the layered-vivarium metaphor and the site’s emerald-on-dark palette.
- Generate the images from the prompts, review them against the copy, and attach per post.
- Posting is a human gate, per item, per platform — no batch approval. Post only after the registry verification (step 6) has passed, so the announcement never points at an unpublished version.
9. After the release
Section titled “9. After the release”- Confirm the live site (https://vivary.vercel.app/) shows the new versions and
command surface — it deploys from
dev, so this is a read-check, not a step. - Stats: the daily
track-statsworkflow picks up the new versions. Every stats PR receives exact-head CI, while only warning-free, non-stale output may request auto-merge or supersede an older proposal. Stale output stays open asblockedfor inspection. Confirmstats/history.csvand the README chart advanced before treating public signals as current. - Keep private agent communications outside the repo: handoffs, launch/social
drafts, and private release packets stay in ignored local storage such as
.release/private/— never in the public repo. - If the update revealed a repeatable release lesson, save it in these docs. Do not rely on chat history as the only copy.
These remain explicit human gates, one item at a time:
git push- opening a PR
- merging a PR
- PyPI publish
- npm publish
- GitHub release creation or update
- posting launch/social copy
Inside the work, be fast. At the edges, leave proof.
