page:docs-specs-platform-gaps-phase1-pkg-02-versioning
WP-PKG-02 record: lockstep kept, changesets removed, wildcard internal deps eliminated reference
Executed 2026-09-29 on branch phase1/rename-train of a5c-ai/fabric (base 3ed7ac0a3, the WP-RN-06 head plus two CI-triage commits; draft PR 4 open). Spec: docs/specs/platform-gaps/40-packaging.md §2.2 (decision) and "WP-PKG-02" (objective, affected paths, acceptance criteria, test requirements); spec 15 §4.2 / §4.3 G2(d); intent 00-intent.md D10 (break freely, major bump). Builds on rn-05-rename.md (every internal edge already renamed and exact-pinned 7.0.0, G2(d) unconditional) and rn-06-bins.md.
WP-PKG-02 record: lockstep kept, changesets removed, wildcard internal deps eliminated
Executed 2026-09-29 on branch phase1/rename-train of a5c-ai/fabric (base 3ed7ac0a3, the WP-RN-06 head plus two CI-triage commits; draft PR #4 open). Spec: docs/specs/platform-gaps/40-packaging.md §2.2 (decision) and "WP-PKG-02" (objective, affected paths, acceptance criteria, test requirements); spec 15 §4.2 / §4.3 G2(d); intent 00-intent.md D10 (break freely, major bump). Builds on rn-05-rename.md (every internal edge already renamed and exact-pinned 7.0.0, G2(d) unconditional) and rn-06-bins.md.
The public repository a5c-ai/babysitter was never written to: gh api repos/a5c-ai/babysitter --jq .pushed_at → 2026-09-16T19:59:50Z at start and end (only GET calls were issued); git remote -v → upstream no_push (push). Nothing was published to any registry, no tag was created (tag count 3, unchanged), no PR was opened.
1. What was removed
| Path | Content | Why it was live |
|---|---|---|
.changeset/config.json | changesets config, fixed group for the five adapters packages | the spec's named file (§2.2, affected paths) |
.changeset/observer-dashboard-replacement.md | one stranded pending changeset | the spec's named file |
.changeset/agent-mux.config.json, .changeset/README.agent-mux.md | a second config + README carried in by the agent-mux merge, instructing npx changeset and describing a release.yml that does not exist here | same dead machinery; the WP deletes the directory, and the orchestrator gate is ! test -d .changeset |
Live references rewritten (each told a contributor or an agent to add a changeset):
| Path | Before | After |
|---|---|---|
packages/adapters/meta/github/PULL_REQUEST_TEMPLATE.md | "## Changeset — Ran npm run changeset" checkbox | "## Versioning — no version bump; lockstep train (docs/VERSIONING.md)" |
packages/adapters/meta/legacy-root/CONTRIBUTING.md | "## Release flow (changesets)" with the preferred npm run changeset flow | "## Release flow (historical)" — superseded note pointing at docs/VERSIONING.md; the manual steps kept as history only |
packages/adapters/processes/integrate-harness.js | task title "Docs + changeset", task text "and a changeset entry", instruction npm run changeset — minor | title "Docs", instruction "Do not bump any package version: packages ride the lockstep release train" |
packages/adapters/skills/integrate-harness/SKILL.md step 11 | "**Changeset** — npm run changeset, pick minor" | "**Versioning** — nothing to do … do not bump any version field" |
packages/adapters/todos.md:54 | historical "[x]" note naming the changesets scripts | same note plus "(Historical: removed in WP-PKG-02 — docs/VERSIONING.md)" |
Proof — git grep -n -i changeset -- . ':!package-lock.json' after the change returns only: marketplace blueprint content that *describes the changesets tool to blueprint users* (blueprints/a5c/marketplace/blueprints/{changesets,changelog-enforcer,autorelease}/*.md, .claude/skills/assimilate-popular-workflows/SKILL.md:258), Atlas catalogue data (packages/atlas/graph/** — the library:changesets node, Ecto changesets, an extensions list), library reference material (library/specializations/**/README.md, claudekit-code-review.js changeSet variable), an archived plan (docs/adapters/archive/...), the specs that record the decision, and the three historical notes listed above. None is a mechanism, workflow, script or manifest of this repository. .github/, scripts/ and the root package.json contain no occurrence at all (case-sensitive grep, the orchestrator gate). package-lock.json has 0 @changesets entries (it never did).
2. Wildcard / range internal edges
Scan (scratch script over all 223 tracked package.json outside node_modules/dist, internal = 43-entry publishable inventory ∪ every @fabricstack/*-named manifest, 68 names): **1** non-exact internal edge in the whole tree.
| Manifest | Edge | Fix |
|---|---|---|
library/specializations/meta/harnesses/pi/examples/package.json:11 | "@fabricstack/orchestrator-sdk": "^0.0.180" (the RN-05 name sweep renamed the package but left the pre-rename range) | pinned to 7.0.0 |
packages/atlas/webui/package.json:16 (the spec's example, "@a5c-ai/atlas": "*") | already "@fabricstack/catalog": "7.0.0" — pinned by WP-RN-05's manifest pass | no change needed; it is now covered by the new rule and by the AC2 fixture |
The example manifest is not a workspace member, so package-lock.json is untouched (md5 ab51e0832bee9404974780a1329a8898, identical to the RN-05 value; npm run guard:packages "lockfile sync" green). No workspace:*, latest or ~ internal edge exists anywhere in the tree.
3. The new rule (`verify-manifests`)
scripts/lib/release-version.cjs collectVersionDivergences (single resolver, FIX-001) now:
- computes the **internal name set** = synchronized private-scope manifest names ∪ the publishable inventory (
listPublishablePackages) ∪ every root-workspace member name (resolveWorkspaceDirs), derived at run time (FIX-005 discipline); the inventory/workspace sources are optional so the rule still works inside a fixture root or an extracted publication tarball, and can only grow the set; - classifies every internal dependency specifier with
classifyUnpinnedSpecifier: exactX.Y.Z[-prerelease][+build]→ pin; otherwisewildcard(*,x, empty),dist-tag "<name>"(latest,staging, …),workspace: protocol,caret range,tilde range,range(comparators,||, hyphen,7.x,7),non-registry specifier(file:,npm:,git,github:,owner/repo); - emits a divergence
{ rule: 'unpinned-internal-dependency', kind, manifestPath, line, field, subject, actual, expected }for each, **before and instead of** the plain version mismatch, so the message names the actual problem; - attaches a 1-based
line(manifestLineOf, located in the raw manifest text inside the dependency field's object) to every manifest-level divergence, and the CLI prints each entry as- <manifest>:<line> <field> <name>: <actual> (expected …).
External dependencies (zod, @a5c-ai/compendium, …) are untouched; the legacy-scope rule G2(d) still fires first and unchanged.
Fixture transcript ("@fabricstack/catalog": "*" plus a ^7.0.0 devDependency, real scripts/ copied into a temp root):
exit 1
2 manifest value(s) diverge from release version 7.0.0. Publishing from this workspace would publish or promote a different version (docs/release-incident-2026-08-13.md). Run `node scripts/sync-workspace-versions.mjs --version 7.0.0` first; 2 of them are internal dependencies that are not exact pins (wildcard, dist-tag, workspace: protocol or semver range) — the checked-in tree must pin every internal edge to the one train version (docs/VERSIONING.md):
- packages/consumer/package.json:8 dependencies @fabricstack/catalog: "*" (expected exact pin 7.0.0; wildcard is not allowed on an internal dependency (lockstep train, docs/VERSIONING.md, spec 40 §2.2))
- packages/consumer/package.json:12 devDependencies @fabricstack/harness-sdk: "^7.0.0" (expected exact pin 7.0.0; caret range is not allowed on an internal dependency (lockstep train, docs/VERSIONING.md, spec 40 §2.2))Real tree: node scripts/release-version.cjs verify-manifests --version 7.0.0 → All synchronized manifests are at release version 7.0.0. (exit 0).
Fixture tests — `scripts/__tests__/verify-manifests.test.mjs`, `npm run test:verify-manifests` (8/8)
1. AC2 literal: "@fabricstack/catalog": "*" → exit 1, packages/consumer/package.json:<line> dependencies @fabricstack/catalog: "*" (expected exact pin 7.0.0; wildcard is not allowed …); the external zod/typescript ranges in the same manifest are not reported. 2. ^7.0.0 → exit 1 with caret range at file:line. 3. Every shape (*, latest, workspace:*, ^, ~, >=7.0.0 <8.0.0, 7.x, ||, file:) trips exactly one unpinned-internal-dependency divergence with its own kind and the line read back from disk. 4. A private workspace member outside the private scope (fixture-private-tool) is internal by workspace-set membership: "*" on it trips; zod is not internal. 5. A wrong exact pin (6.0.3) is still the classic mismatch (no rule), and sync-workspace-versions.mjs repairs it to a passing tree. 6. Classifier unit cases (exact with channel prerelease/build → pin; ''/x → wildcard; 7 → range; staging → dist-tag; npm: → non-registry; undefined → wildcard). 7. An exact-pinned lockstep fixture passes. 8. The real tree passes at the root version, and collectVersionDivergences over it is [].
The file matches scripts/__tests__/*.test.mjs, so it also runs under npm run test:release-tooling in ci.yml (Release tooling tests) and release-artifact-verifier.yml. In addition, ci.yml's dependency-free publish-isolation job gained the step "Lockstep manifests and exact internal pins (G2(d), WP-PKG-02)": node scripts/release-version.cjs verify-manifests --version "$(node -p "require('./package.json').version")" followed by npm run test:verify-manifests — the rule now bites on every PR against the checked-in tree, not only in the publication workspace (spec 15 §4.3 G2 "required check on every PR", item (d)).
4. `docs/VERSIONING.md`
New one-page posture document: §1 lockstep train (43 publishables + root + private-scope workspaces + plugin manifests at one version; internal = inventory ∪ workspace set; exact pins only; whole-train consumption), §2 the single resolver and its subcommands as the ONLY mechanism, §3 D10 at train level (majors mark breaking trains; the compatibility obligation is met in-repo) and **how 7.0.0 was chosen** (rename + bin retirement + exports maps are breaking for every consumer → major over the public 6.0.x lineage; next breaking train 8.0.0), §4 changesets removal (dead machinery, no workflow consumer, spec input 3 wire-or-remove) and the **rejected alternative** (per-package semver via changesets) with four reasons (no consumer resolves per-package signals; independent releases require floating internal ranges; a second version-computing mechanism re-opens FIX-001; D10 is simpler per train), §5 contributor rules. docs/release-pipeline.md links to it from its FIX-001 section. markdownlint-cli2, docs-style-check.cjs and docs-link-check.cjs all pass on the new/edited docs.
5. Gates (all run from `C:/work/fabric` on the final tree)
Orchestrator gate verbatim — ! test -d .changeset && ! grep -rqn "changeset" .github scripts package.json && npm run test:verify-manifests && node scripts/release-version.cjs verify-manifests --version 7.0.0 && echo PKG02-PASS — tail:
# tests 8
# pass 8
# fail 0
All synchronized manifests are at release version 7.0.0.
PKG02-PASS| Check | Result |
|---|---|
npm run test:release-tooling before | 149 tests, 112 pass, 37 fail (inherited FIX-010/FIX-011 workflow-shape + npm-stub-on-PATH set; names saved) |
npm run test:release-tooling after | 157 tests, 119 pass, 38 fail = the 37 inherited names + 8 new WP-PKG-02 tests passing + **one flake**: hooks-atlas-ownership.test.mjs "a hooks leaf resolves Atlas through its OWN dependency in a non-hoisted clean consumer" failed only in the parallel full run — its packed verifier rebuilt @fabricstack/catalog while another test file was regenerating the same library JSON (validate-library-bridge.mjs: "Unterminated string in JSON at position 21532219"). Rerun of that file alone: the test passes (5/6; the 1 failure, "the hooks CLI installs, imports and runs from its packed tarball", is in the inherited list). Failing-name set after, minus that flake, equals the set before: 0 new failing names. No test deleted or skipped. |
npm run test:publish-guards | 17/17 (incl. G2(d) over the real tree with the new rule active) |
node scripts/ci/publish-isolation-guard.cjs --self-test | self-test passed; repository scan clean (101 files, 0 findings, 8 legacy-train notices) |
node scripts/ci/no-double-rename.cjs --self-test | 10/10; repository scan 59 manifests, 0 findings |
node scripts/ci/public-push-guard.cjs | scanned 313 files, 0 findings |
npm run guard:packages | passed (lockfile sync, native bindings, metadata, arch boundaries) |
npm run verify:metadata | passed; publishable inventory 43 |
node scripts/release-version.cjs resolve --branch main --sha 3ed7ac0a308f --print releaseVersion | 7.0.0 |
node scripts/release-version.cjs resolve --branch staging --sha 3ed7ac0a308f | 7.0.1-staging.3ed7ac0a308f / dist-tag staging / tag babysitter/staging/v7.0.1-staging.3ed7ac0a308f — the FIX-001 staging shape (patch+1 prerelease of the root 7.0.0); the tag prefix is still babysitter (WP-RN-07 retargets it to fabric/) |
5a. Review round 1: the lockfile changed once, for a dependency-declaration fix
The md5 in §2 (ab51e0832bee9404974780a1329a8898) describes the tree up to the review. WP-RN-05 review finding M6 (console-tui's bin crashed on every install: packages/genty/tui declared react ^18.3.1 while the workspace root resolves React 19, and --legacy-peer-deps hoisted ink 5's react-reconciler@0.29 next to react@19) was fixed as a declaration change — @fabricstack/console-tui, @fabricstack/console-tui-plugins and @fabricstack/harness-cli now declare ink ^6.0.0 (requires React 19) and react ^19.2.4, matching the root — plus jsdom ^30.1.1 as a devDependency of @fabricstack/console-ui (its SessionDetailScreen test declares @vitest-environment jsdom; the package was never installed, so the "Realtime flow tests" CI step failed identically on staging — oracle run 36638300466). package-lock.json was regenerated with npm install --legacy-peer-deps from the HEAD lockfile (never deleted; the two stale nested react@18.3.1 nodes npm leaves behind as invalid were pruned and the install re-run): **4 transitive version changes** (jsdom's @csstools/css-* ×3, tough-cookie), 77 added nodes (the ink 6 / react-reconciler 0.33 / jsdom 30 closures), 25 removed (the ink 5 closure and the nested React 18 copies); every internal pin is still 7.0.0 (verify-manifests green). An npm dedupe attempt that re-resolved 274 unrelated packages was discarded.
6. Not done / deferred, and honesty notes
sync-workspace-versions.mjsstill rewrites any internal range to the release version inside the publication workspace (that is its job). The new rule guards the **checked-in** tree: PR CI and the pre-publicationverify-manifestsboth refuse a wildcard before the sync could paper over it.- The
blueprints/**changesets content is product material teaching the tool to blueprint users; it is not this repository's versioning and was intentionally left. resolve --branch stagingprints the staging prerelease, not7.0.0;--branch mainprints7.0.0. Both reported verbatim above.- Environment: vitest/rolldown and lightningcss bindings are linux-only in this checkout (as in RN-05/RN-06), so no vitest suite ran locally; nothing in this WP touches a vitest-tested package source.
npm ciwas not run (per instruction). - The pre-commit hook (
.githooks/pre-commit,validate-library-js-syntax.cjs --staged) ran unchanged on the commit.