Package lifecycle
This guide covers how to develop, test, release, and run package versions in a group. Each device chooses which version to run; the group shares version records and defaults, but dev work does not automatically switch everyone else.
For the underlying design rationale and remaining roadmap items, see Package lifecycle design.
Overview
Packages move through three promotion levels:
dev ──▶ beta ──▶ stable
| Phase | How it is created | Syncs to the group? | Auto-activates on other devices? |
|---|---|---|---|
| dev | Automatically when code is loaded from disk | Yes (version records sync) | Never — unless a device explicitly opts in |
| beta | Promote in the package Versions UI | Yes | Only on devices following stable,beta or * |
| stable | Promote in the package Versions UI | Yes | On devices using the default follow policy (stable) |
The platform assigns versionTag (dev / beta / stable). Do not set it in an isolated manifest,
definePackage(), or contract definitions.
Metadata vs bundle bytes: PackageVersions (and related Files records) sync as group data. As soon as a version record arrives, this device also downloads that version’s package, routes, and UI bundles into the local chunk cache so later activation or UI load does not wait on a peer. A downloaded version is not automatically activated. The device waits until the current sync has applied every page and saved its watermark, then evaluates the final package state once using its pin, follow, and device-override rules. An incompatible bundle can fail to load without blocking metadata sync or preventing a newer version from arriving. If a peer cannot provide a bundle yet, metadata sync still succeeds and the missing chunks retry when another peer becomes available.
Group defaults vs this device
Two layers work together:
| Layer | Where it lives | Purpose |
|---|---|---|
| Group settings | IPackage record (synced) | activePackageVersionId, versionFollowRange, followVersionTags for devices that follow the group |
| Device preferences | groupDeviceVar per package (packagePrefs_${packageId}) | Local active version, pin state, and optional per-device follow overrides |
By default, a device follows the group settings. Turning on Override on this device in Package Info makes the auto-update range and release channel local to that device.
Activation scope depends on the version and override state:
- Activating a non-dev version with device override off updates the group active version.
- Activating a non-dev version with device override on updates only this device.
- Activating a dev version updates only this device, even when override is off.
- Promoting the version this device is already running to a tag the group follows (default
stable) advances the group active version under the same admin / override / not-pinned rules as Activate. - Automatic non-dev upgrades advance the group active version only when the device is following group settings and the current user is a group Admin or higher. Otherwise the upgrade remains local to the device.
Day-to-day development
- Edit your package on disk (local path in Package settings).
- Reload or restart the app (or use your usual dev workflow). The installer creates or updates a dev package version from the bundle on disk.
- Dev versions sync to the group as
PackageVersionsrecords, and other devices download those version files immediately, but they do not auto-switch to dev. - Open Packages → your package → Versions to see versions, hashes, and tags. Use Activate on a dev version to run it on this device without changing the group active version.
App icons in the apps list come from appNavs on the active PackageVersion (copied from
the isolated manifest or legacy package definition when bundles are installed). Startup sync
always persists appNavs onto new versions, including when the group is currently on a stable
build.
Routes and UI bundles reload when you change the active version on this device (no full page refresh required).
Releasing to the group
- In Versions, use Promote on a dev version: dev → beta or dev → stable (or beta → stable).
- Promotion updates the version’s
versionTag, refreshespackageAuthorSignaturewhen a package signing key is available (and clears a stale signature if not), and appends to the version’s signedhistoryaudit trail. Editing the semver on a version row does the same for the signature. - If you promote the version this device is already running, and the new tag matches the group’s follow policy (default:
stable;betaonly when the group follows beta), the groupactivePackageVersionIdadvances automatically (admin, device follow override off, group not pinned) — same rules as Activate. Otherwise use Activate on the promoted beta/stable release; devices following the group pick it up on their next resolve/sync.
Today: promotion and activation are done in the package Versions UI.
Planned: promote-package-version and set-active-package-version tools for CI and AI assistants (same behavior as the UI, single code path). See Package lifecycle design.
Signed publish artifacts (local)
Use the system tool publish-package to build distributable files on disk after you have a signing key and built bundles:
peers tools run publish-package '{"name":"Groceries","versionTag":"beta"}'
This writes (by default under <packageLocalPath>/dist/publish/):
- A signed
.peers-pkg.tar.gzcontaining the three bundles plus metadata - A
latest-<tag>.jsonpointer with version, hashes, and author signature
Verification expectations before you trust an artifact:
- Bundle file hashes in the pointer/payload match the files inside the tarball
packageAuthorSignatureverifies againstPackages.publishPublicKey- Semver in the pointer is what you intend to publish (remote auto-activation requires a newer semver than the installed follow candidate)
Upload / hosting is a separate step. publish-package does not push to S3 or your updateUrl. Point Packages.updateUrl at a host only after you have uploaded the tarball and pointer yourself (or via your release tooling).
Permissions
| Action | Typical role |
|---|---|
| Create / update dev versions (disk reload) | Writer |
| Promote to beta or stable | Admin |
| Activate beta/stable for the group | Admin |
| Activate dev or use device override | Local device choice |
| Pin a device | Local device choice |
Personal space (no group) bypasses group role checks for your own packages.
Package Info Settings
On the package Info tab, Auto-update range and Following edit group settings by default. Enable Override on this device to make those controls local to the current device.
- Update URL — base URL where admin devices check for new signed releases (
<updateUrl>/latest-<tag>.json). Edit on the Info tab and save with Save Changes. See Package lifecycle design for publishing and upload details. - Auto-update range - pinned (no auto-updates), patch, minor, or latest.
- Following -
stableorstable,beta. - Override on this device - when enabled, auto-upgrades and manual beta/stable activations affect only this device.
Pinned devices keep their active version even when new stable or beta versions sync in.
Multi-device testing
To test with other devices before a stable release:
- Promote your build to beta in Versions.
- On each tester device, set Follow version tags to
stable,beta(or*) if you want automatic beta upgrades, or Activate the beta version manually. - Dev versions remain available for manual activation on any device but never auto-activate elsewhere.
Contracts and stable releases
Contract maturity is tied to package promotion. New contract shapes can evolve freely in dev builds. The platform is designed to finalize contracts (remove dev tag, freeze shape) when a package version is promoted to stable.
If you need to extend a frozen contract after stable, increment the contract version and use alsoImplements so providers remain compatible with older consumers. See Package contracts.
Related
- Getting started — isolated package structure and build basics
- Package contracts — versioned interfaces, validation,
alsoImplements - Package lifecycle design — design doc, shipped vs planned work
- Variables (pvars) —
groupDeviceVarand other persistent variable scopes