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": { 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) }) }) })