II.
Page JSON
Structured · livepage:docs-specs-platform-gaps-phase1-pkg-02-versioning
WP-PKG-02 record: lockstep kept, changesets removed, wildcard internal deps eliminated json
Inspect the normalized record payload exactly as the atlas UI reads it.
{
"id": "page:docs-specs-platform-gaps-phase1-pkg-02-versioning",
"_kind": "Page",
"_file": "wiki/docs/specs/platform-gaps/phase1/pkg-02-versioning.md",
"_cluster": "wiki",
"attributes": {
"nodeKind": "Page",
"sourcePath": "docs/specs/platform-gaps/phase1/pkg-02-versioning.md",
"sourceKind": "repo-docs",
"title": "WP-PKG-02 record: lockstep kept, changesets removed, wildcard internal deps eliminated",
"displayName": "WP-PKG-02 record: lockstep kept, changesets removed, wildcard internal deps eliminated",
"slug": "docs/specs/platform-gaps/phase1/pkg-02-versioning",
"articlePath": "wiki/docs/specs/platform-gaps/phase1/pkg-02-versioning.md",
"article": "\n# WP-PKG-02 record: lockstep kept, changesets removed, wildcard internal deps eliminated\n\nExecuted 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`.\n\nThe 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.\n\n## 1. What was removed\n\n| Path | Content | Why it was live |\n|---|---|---|\n| `.changeset/config.json` | changesets config, `fixed` group for the five adapters packages | the spec's named file (§2.2, affected paths) |\n| `.changeset/observer-dashboard-replacement.md` | one stranded pending changeset | the spec's named file |\n| `.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` |\n\nLive references rewritten (each told a contributor or an agent to add a changeset):\n\n| Path | Before | After |\n|---|---|---|\n| `packages/adapters/meta/github/PULL_REQUEST_TEMPLATE.md` | \"## Changeset — Ran `npm run changeset`\" checkbox | \"## Versioning — no version bump; lockstep train (`docs/VERSIONING.md`)\" |\n| `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 |\n| `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\" |\n| `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\" |\n| `packages/adapters/todos.md:54` | historical \"[x]\" note naming the changesets scripts | same note plus \"(Historical: removed in WP-PKG-02 — docs/VERSIONING.md)\" |\n\nProof — `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).\n\n## 2. Wildcard / range internal edges\n\nScan (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.\n\n| Manifest | Edge | Fix |\n|---|---|---|\n| `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` |\n| `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 |\n\nThe 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.\n\n## 3. The new rule (`verify-manifests`)\n\n`scripts/lib/release-version.cjs` `collectVersionDivergences` (single resolver, FIX-001) now:\n\n- 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;\n- classifies every internal dependency specifier with `classifyUnpinnedSpecifier`: exact `X.Y.Z[-prerelease][+build]` → pin; otherwise `wildcard` (`*`, `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`);\n- 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;\n- 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 …)`.\n\nExternal dependencies (`zod`, `@a5c-ai/compendium`, …) are untouched; the legacy-scope rule G2(d) still fires first and unchanged.\n\nFixture transcript (`\"@fabricstack/catalog\": \"*\"` plus a `^7.0.0` devDependency, real `scripts/` copied into a temp root):\n\n```text\nexit 1\n2 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):\n - 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))\n - 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))\n```\n\nReal tree: `node scripts/release-version.cjs verify-manifests --version 7.0.0` → `All synchronized manifests are at release version 7.0.0.` (exit 0).\n\n### Fixture tests — `scripts/__tests__/verify-manifests.test.mjs`, `npm run test:verify-manifests` (8/8)\n\n1. 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.\n2. `^7.0.0` → exit 1 with `caret range` at file:line.\n3. 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.\n4. A private workspace member outside the private scope (`fixture-private-tool`) is internal by workspace-set membership: `\"*\"` on it trips; `zod` is not internal.\n5. 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.\n6. Classifier unit cases (exact with channel prerelease/build → pin; `''`/`x` → wildcard; `7` → range; `staging` → dist-tag; `npm:` → non-registry; `undefined` → wildcard).\n7. An exact-pinned lockstep fixture passes.\n8. The real tree passes at the root version, and `collectVersionDivergences` over it is `[]`.\n\nThe 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)).\n\n## 4. `docs/VERSIONING.md`\n\nNew 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.\n\n## 5. Gates (all run from `C:/work/fabric` on the final tree)\n\nOrchestrator 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:\n\n```text\n# tests 8\n# pass 8\n# fail 0\nAll synchronized manifests are at release version 7.0.0.\nPKG02-PASS\n```\n\n| Check | Result |\n|---|---|\n| `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) |\n| `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. |\n| `npm run test:publish-guards` | 17/17 (incl. G2(d) over the real tree with the new rule active) |\n| `node scripts/ci/publish-isolation-guard.cjs --self-test` | self-test passed; repository scan clean (101 files, 0 findings, 8 legacy-train notices) |\n| `node scripts/ci/no-double-rename.cjs --self-test` | 10/10; repository scan 59 manifests, 0 findings |\n| `node scripts/ci/public-push-guard.cjs` | scanned 313 files, 0 findings |\n| `npm run guard:packages` | passed (lockfile sync, native bindings, metadata, arch boundaries) |\n| `npm run verify:metadata` | passed; publishable inventory 43 |\n| `node scripts/release-version.cjs resolve --branch main --sha 3ed7ac0a308f --print releaseVersion` | `7.0.0` |\n| `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/`) |\n\n## 5a. Review round 1: the lockfile changed once, for a dependency-declaration fix\n\nThe 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.\n\n## 6. Not done / deferred, and honesty notes\n\n- `sync-workspace-versions.mjs` still 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-publication `verify-manifests` both refuse a wildcard before the sync could paper over it.\n- The `blueprints/**` changesets content is product material teaching the tool to blueprint users; it is not this repository's versioning and was intentionally left.\n- `resolve --branch staging` prints the staging prerelease, not `7.0.0`; `--branch main` prints `7.0.0`. Both reported verbatim above.\n- 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 ci` was not run (per instruction).\n- The pre-commit hook (`.githooks/pre-commit`, `validate-library-js-syntax.cjs --staged`) ran unchanged on the commit.\n",
"documents": []
},
"outgoingEdges": [],
"incomingEdges": [
{
"from": "page:docs",
"to": "page:docs-specs-platform-gaps-phase1-pkg-02-versioning",
"kind": "contains_page"
}
]
}