From 0a8c1d5846c0fe15e8a2dd5c41900b85b84bcadb Mon Sep 17 00:00:00 2001 From: J-Dog Date: Fri, 14 Aug 2026 12:31:31 -0700 Subject: [PATCH 1/2] release: adopt the platform version stream at 0.9.0 First release of the XChain Platform release train. Every train component now shares one platform version, so "XChain 0.9.0" names an exact, reproducible set of software. The number moves backward for most components, which is deliberate: the platform stream starts at 0.9.0 for the testnet series and 1.0.0 is reserved for mainnet. The changelog carries a marker line above the old entries, because a higher legacy number is an older release and that will otherwise read as a regression. --- CHANGELOG.md | 17 +++++++++++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e84578e..9daf936 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.9.0] - 2026-08-14 + +First release of the XChain Platform release train. Every component in the train +now shares one platform version, so "XChain 0.9.0" names an exact, reproducible +set of software rather than a rough era. + +### Changed +- Adopted the platform version stream. This component moves from `0.0.26` to + `0.9.0`. **The number is lower but the release is newer**: the platform stream + starts at 0.9.0 for the testnet series, and 1.0.0 is reserved for mainnet. + + + ## [0.0.26] - 2026-08-13 ### Fixed diff --git a/package-lock.json b/package-lock.json index 7b662c0..40d1067 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "xchain-node", - "version": "0.0.25", + "version": "0.9.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "xchain-node", - "version": "0.0.25", + "version": "0.9.0", "license": "AGPL-3.0-or-later", "dependencies": { "axios": "^1.18.1", diff --git a/package.json b/package.json index e1d631b..7b056c5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xchain-node", - "version": "0.0.26", + "version": "0.9.0", "description": "xchain-node allows users to install, configure and run XChain platform nodes.", "license": "AGPL-3.0-or-later", "repository": { From b64c3a52a9b7a4cfc999483fc5e5e86b0c7676fc Mon Sep 17 00:00:00 2001 From: J-Dog Date: Fri, 14 Aug 2026 14:22:59 -0700 Subject: [PATCH 2/2] release: pin the 0.9.0 component set Written from the actual tagged merge commits of the eight sibling repos, which is why this repo cuts last: the manifest records the commit each tag points at, and those commits do not exist until the merges do. The shipped-manifest test changes shape with it. Asserting the component set is empty was correct right up to this cut and is now wrong; what has to hold from here is that every pin is usable, so it checks a full 40-hex commit and a tag matching the train, and that the carrier does not pin itself. --- src/release-manifest.json | 60 +++++++++++++++--------- test/unit/ReleaseManifestService.test.js | 46 ++++++++++++++---- 2 files changed, 76 insertions(+), 30 deletions(-) diff --git a/src/release-manifest.json b/src/release-manifest.json index abcb1a9..bbfeb26 100644 --- a/src/release-manifest.json +++ b/src/release-manifest.json @@ -1,25 +1,43 @@ { "_comment": [ - "Release manifest: the pinned component set for one platform release train.", - "Written at release-ceremony step 6 (claude/specs/living/release-management.md", - "section 6) AFTER every sibling repo has merged and tagged, because the pins", - "record each tag's master merge-commit SHA and those commits do not exist", - "until the merges do.", - "", - "xchain-node is the only dense-tagged train member: checking out", - "xchain-node vX.Y.Z self-contains the full pinned set, so `install vX.Y.Z`", - "reproduces an exact release and `install` with no ref resolves the latest", - "xchain-node release and installs that release's manifest.", - "", - "Sparse lockstep: a component appears here at EVERY train, pinned to its", - "highest tag <= this train's version. A component that did not change this", - "train keeps its previous tag; that is the resolution rule, not a gap.", - "", - "`components` is EMPTY until the first train (0.9.0) is cut. While it is", - "empty every install resolves by branch exactly as it did before this file", - "existed, so nothing here changes behaviour pre-adoption." + "Pinned component set for XChain Platform v0.9.0.", + "Written at ceremony step 6 from the ACTUAL tagged master merge commits.", + "xchain-node is the carrier and is not listed: checking out its tag IS this manifest." ], - "platform_version": null, - "released": null, - "components": {} + "platform_version": "0.9.0", + "released": "2026-08-14", + "components": { + "xchain-vm": { + "tag": "v0.9.0", + "commit": "d29fba335d98cd156c5ee927a89eccdda1aaaf01" + }, + "xchain-decoder": { + "tag": "v0.9.0", + "commit": "622ce5ab616f605f990692771a6682cb730fb2f4" + }, + "xchain-indexer": { + "tag": "v0.9.0", + "commit": "ea53a7f99255fa20f8bacb2aee5cbc9f0b3ad1dd" + }, + "xchain-hub": { + "tag": "v0.9.0", + "commit": "3a9e1b5fd3ba115f12826ea88a0868a0d10f5b5a" + }, + "xchain-sync": { + "tag": "v0.9.0", + "commit": "dc75649f3469a8fa32c4a1ea7749d05da4a319f7" + }, + "xchain-encoder": { + "tag": "v0.9.0", + "commit": "ea785f6da4281ebc45912f7dea1e9e062a4e8f44" + }, + "xchain-utxo-tracker": { + "tag": "v0.9.0", + "commit": "21fdfd80660bd86d09d91402e23b873f0cee8574" + }, + "xchain-explorer": { + "tag": "v0.9.0", + "commit": "ffc73bf4d33716e9932a87d2405b5d42e69092ba" + } + } } diff --git a/test/unit/ReleaseManifestService.test.js b/test/unit/ReleaseManifestService.test.js index 43eab40..4438b1d 100644 --- a/test/unit/ReleaseManifestService.test.js +++ b/test/unit/ReleaseManifestService.test.js @@ -275,15 +275,43 @@ describe('ReleaseManifestService', () => { }) describe('the shipped manifest', () => { - it('is valid JSON with an empty component set until the first train', () => { - // Guards the pre-adoption promise: while `components` is empty every - // install resolves by branch exactly as it did before these rails existed. - const shipped = require('../../src/release-manifest.json') - const real = proxyquire('../../src/services/ReleaseManifestService', { - '../GitHubDownloader': { githubApiHeaders: () => ({}), githubRateLimitError: () => null } - }) - expect(real.manifestHasPins(shipped)).to.equal(false) - expect(real.getComponentPin(shipped, 'xchain-vm')).to.equal(null) + // Was "components is empty until the first train". That was the correct + // invariant right up to the moment 0.9.0 was cut, and it is now the wrong + // one: the shipped manifest carries real pins. What has to hold from here + // on is that every pin is USABLE, because a malformed one is the failure + // this whole file exists to prevent. + const shipped = require('../../src/release-manifest.json') + const real = proxyquire('../../src/services/ReleaseManifestService', { + '../GitHubDownloader': { githubApiHeaders: () => ({}), githubRateLimitError: () => null } + }) + + it('declares a platform version and a release date', () => { + expect(shipped.platform_version).to.match(/^\d+\.\d+\.\d+$/) + expect(shipped.released).to.match(/^\d{4}-\d{2}-\d{2}$/) + }) + + it('pins every component to a tag and a full 40-hex commit', () => { + expect(real.manifestHasPins(shipped)).to.equal(true) + for (const name of Object.keys(shipped.components)) { + const pin = real.getComponentPin(shipped, name) + expect(pin, `${name} must resolve`).to.not.equal(null) + expect(pin.commit, `${name} commit`).to.match(/^[0-9a-f]{40}$/) + expect(pin.tag, `${name} tag`).to.match(/^v\d+\.\d+\.\d+$/) + } + }) + + it('pins every component at the platform version it ships in', () => { + // A component pinned at some other tag would mean the manifest and the + // release number disagree about what "XChain 0.9.0" contains. + for (const [name, pin] of Object.entries(shipped.components)) { + expect(pin.tag, `${name} tag matches the train`).to.equal('v' + shipped.platform_version) + } + }) + + it('does not pin xchain-node itself', () => { + // node is the carrier: checking out its tag IS this manifest, so listing + // it would be a self-reference that can never be written before the fact. + expect(shipped.components['xchain-node']).to.equal(undefined) }) }) })