page:docs-versioning
Versioning reference
train every package is exact-pinned lockstep; majors mark breaking trains.
Versioning
**Posture (spec 40 §2.2, WP-PKG-02): semver encodes the release train; within a train every package is exact-pinned lockstep; majors mark breaking trains.**
This page is the one place that states how versions are chosen in this repository. There is exactly one mechanism — scripts/release-version.cjs, the single resolver — and nothing else assigns, bumps or infers a version.
1. The lockstep train
scripts/lib/publishable-packages.cjs, never hand-maintained) plus the root manifest, the private workspace members in the private scope and the plugin manifests (plugins/*/plugin.json, versions.json) carry **one** version: the train version. Today that is 7.0.0.
publishable inventory or the workspace set — is pinned to that exact version. *, latest, workspace:*, ^/~ and comparator ranges are forbidden on internal edges (external dependencies keep whatever ranges they need).
resolves to @fabricstack/orchestrator-sdk@7.0.0, which resolves to @fabricstack/harness-sdk@7.0.0, and so on. Mixed trains cannot be assembled by accident, which is the property that makes a private product with a handful of whole-train consumers (the platform itself, the Form 1 archives, enterprise installs via dist-tags) supportable.
- All 43 publishable
@fabricstack/*packages (inventory derived at run time by - Every **internal dependency** — any dependency whose name is in the
- A consumer therefore installs a whole train:
@fabricstack/orchestrator@7.0.0
2. The single resolver
scripts/release-version.cjs (library: scripts/lib/release-version.cjs, tests: scripts/__tests__/release-version.test.mjs, scripts/__tests__/verify-manifests.test.mjs) is the only place the released version is decided (FIX-001, docs/release-incident-2026-08-13.md):
| Command | Role |
|---|---|
resolve --branch <b> --sha <short-sha> | Produces THE release identity once: main releases the root version X.Y.Z; staging / develop release X.Y.(Z+1)-<branch>.<sha> on their own channels. Any other branch is a hard error. |
verify --branch --sha --version | Validates an explicitly passed version against the resolver instead of re-reading manifests. |
sync-workspace-versions.mjs --version <v> | Writes the resolved version into every synchronized manifest and every internal dependency pin (publication workspace only). |
verify-manifests --version <v> | Proves the tree IS at <v>: every synchronized version, every internal pin, and — since WP-PKG-02 — that no internal dependency carries a wildcard, dist-tag, workspace: protocol or range. Failures are reported as <manifest>:<line>. Runs on every PR (ci.yml, publish-isolation job) and before every publication. |
assert-channel-tags, ensure-tag, tag-source, from-tag | Channel promotion and tag provenance; read-only against the registry except npm view. |
The tree is committed **at** the train version. Nobody edits a version field by hand and nobody adds a per-package bump: a train moves when the resolver says so, and verify-manifests refuses a tree that disagrees with itself.
3. Breaking changes: D10 at train level
Intent decision D10 (docs/specs/platform-gaps/00-intent.md): *break freely, major bump; no compatibility shims; in-repo consumers migrated in the same effort.* Under the lockstep posture that rule is expressed **once per train**, not per package:
train **major**. Minor and patch trains carry only additive or corrective changes across the whole set.
train broke something somewhere" and runs the migration for the train — there is no per-package matrix to reconcile.
change that breaks an API migrates every in-repo consumer, and the exact pins guarantee no stale package can observe the old surface.
- A train that contains any breaking change to any published surface bumps the
- Because every package shares the number, a consumer reads the major as "this
- The compatibility obligation is satisfied inside the repository: the same
How 7.0.0 was chosen
The public train (a5c-ai/babysitter, @a5c-ai/*) is at lockstep 6.0.x. The RN-1 cutover (spec 15) renames every package into the private @fabricstack/* scope, retires duplicate bins, and (spec 40 §2.1) lands exports maps that make deep imports of dist/ unreachable. Each of those is a breaking change for every consumer, so by the rule above the first private train is a **major** bump over the last public one: 6.0.x → 7.0.0. It is 7.0.0 rather than any other number because the private train continues the public lineage (a consumer migrating from @a5c-ai/babysitter-sdk@6.0.x to @fabricstack/orchestrator-sdk@7.0.0 sees the expected ordering) while making it impossible to confuse the two trains. Subsequent breaking trains bump to 8.0.0, 9.0.0, … ; non-breaking trains move the minor or patch.
4. Changesets: removed, and why
The repository once carried the changesets tool (.changeset/config.json, a fixed group for the adapters packages, and one stranded changeset file). Research for spec 40 (docs/specs/platform-gaps/research/packaging-state.md §1.1) found it referenced by **no workflow**: contributors were occasionally told to "add a changeset", but nothing ever consumed the files. Spec input 3 of that research required *wire-or-remove*. WP-PKG-02 removed it:
agent-mux-era config/README copies).
contributor-facing instructions in packages/adapters/** (PR template, the historical CONTRIBUTING, the integrate-harness process and skill) now say the opposite: do not bump versions.
and one exact-pin rule enforced by verify-manifests.
.changeset/is deleted (config, the stranded changeset, and the- No workflow, script or root/package manifest references the tool; the
- The repository therefore carries **one** versioning mechanism, the resolver,
Alternative considered and rejected: per-package semver via changesets
Wiring changesets (each PR declares a per-package bump; a "Version Packages" PR aggregates them; internal dependencies float on ^ ranges so packages can release independently) was the other coherent half of wire-or-remove. It was rejected because:
1. **Nobody resolves against per-package signals.** Consumers of a private product take whole trains via dist-tags; 43 independently versioned manifests would decorate the registry with information no consumer uses. 2. **Independent versions require floating internal ranges.** Per-package releases only work if @fabricstack/orchestrator-sdk may depend on ^7.0.0 of @fabricstack/harness-sdk; that reintroduces the mixed-train installs and the "which version is actually running" ambiguity the incident-hardened lockstep scheme exists to prevent (docs/release-incident-2026-08-13.md). 3. **Two mechanisms, one truth.** Changesets would compute versions in a second place, exactly the "several places each infer the released version" failure mode FIX-001 closed. The single resolver is never bypassed. 4. **D10 is simpler at train level.** One major per breaking train is a clearer contract for a whole-train consumer than a per-package matrix of majors that all ship together anyway.
The rejected alternative remains a valid design for a public, ecosystem-facing package set; it is the wrong fit for a private platform consumed as a unit.
5. Rules for contributors
exactly (copy the root package.json version); npm run test:verify-manifests and node scripts/release-version.cjs verify-manifests --version <train> must stay green.
the next train is cut as a major; migrate every in-repo consumer in the same PR.
publication, channel promotion) are described in docs/release-pipeline.md; the operator procedures — normal train, dry-run rehearsal, half-published-train recovery from the immutable tag, dist-tag repair, public-namespace checks — are in docs/RELEASE-RUNBOOK.md. Both consume the resolver's output and never re-derive it.
- Never edit a
versionfield or an internal dependency pin by hand. - When adding an internal dependency, pin it to the current train version
- A change that breaks a published surface is fine (D10) — note it in the PR so
- Release mechanics (tags
fabric/<branch>/v<version>, Artifact Registry