From 1057f9827c358f91ae5aee501672cdb000e21677 Mon Sep 17 00:00:00 2001 From: J-Dog Date: Fri, 14 Aug 2026 15:31:55 -0700 Subject: [PATCH 1/7] ci: pick up the reusable workflow's declared-sibling checkout --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 583ff54..02c903c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ on: jobs: ci: - uses: XChain-Platform/.github/.github/workflows/ci-reusable.yml@e2d578827928e79ec71c9b6afc4595025dc025fe # pin: XChain-Platform/.github @ master 2026-08-13; bump deliberately + uses: XChain-Platform/.github/.github/workflows/ci-reusable.yml@ece256eede852d0cb0da5f8d3bec02051085c4de # pin: XChain-Platform/.github @ master 2026-08-14; bump deliberately # Override the Node version for a repo if ever needed: # with: # node-version: "20" From 3fa246b3e7278f2c59cae5380915c4d9a70914cb Mon Sep 17 00:00:00 2001 From: J-Dog Date: Fri, 14 Aug 2026 15:37:01 -0700 Subject: [PATCH 2/7] ci: pick up the reusable workflow's sibling dependency install --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 02c903c..1c17e7b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ on: jobs: ci: - uses: XChain-Platform/.github/.github/workflows/ci-reusable.yml@ece256eede852d0cb0da5f8d3bec02051085c4de # pin: XChain-Platform/.github @ master 2026-08-14; bump deliberately + uses: XChain-Platform/.github/.github/workflows/ci-reusable.yml@6f4d39ae85787fc31e90a31588d87610a2c33103 # pin: XChain-Platform/.github @ master 2026-08-14; bump deliberately # Override the Node version for a repo if ever needed: # with: # node-version: "20" From c0cebaa4bef4dc5cc482bcd2cbaab54063c75a98 Mon Sep 17 00:00:00 2001 From: J-Dog Date: Fri, 14 Aug 2026 15:42:55 -0700 Subject: [PATCH 3/7] ci: give the coverage ratchet the same siblings as the gate The ratchet re-runs the unit suite, so without the siblings the tests that read xchain-documentation skip and measured coverage lands under a floor that was set with them running. The job had never actually run: it needs the gate, and the gate was red for the missing sibling this same roster now supplies. --- .github/workflows/ci.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c17e7b..4beb60a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -78,6 +78,25 @@ jobs: steps: - uses: actions/checkout@v4 + # The ratchet re-runs the unit suite, so it needs the same siblings the gate + # gets, or the tests that read xchain-documentation skip and the measured + # coverage lands under a floor that was set with them running. Same roster + # and same layout as the shared workflow uses. + - name: Check out declared sibling repositories + env: + SIBLINGS_REF: ${{ github.ref == 'refs/heads/master' && 'master' || 'develop' }} + run: | + set -euo pipefail + repos=$(sed 's/#.*//' .ci-siblings | tr -d '\r' | awk 'NF') + cd "$GITHUB_WORKSPACE/.." + for repo in $repos; do + rm -rf "$repo" + url="https://github.com/${{ github.repository_owner }}/$repo.git" + git clone --quiet --depth 1 --branch "$SIBLINGS_REF" "$url" "$repo" 2>/dev/null \ + || git clone --quiet --depth 1 "$url" "$repo" + echo "sibling $repo @ $(git -C "$repo" rev-parse --abbrev-ref HEAD) $(git -C "$repo" rev-parse --short HEAD)" + done + - name: Use Node.js 22 uses: actions/setup-node@v4 with: From 73c19cae10edf5da57d04721a643f116387babe3 Mon Sep 17 00:00:00 2001 From: J-Dog Date: Fri, 14 Aug 2026 19:54:27 -0700 Subject: [PATCH 4/7] ci: gate release tags on the pinned platform signing key --- .github/workflows/verify-tag.yml | 104 ++++++++++++++++++ tools/release/release-signing-fingerprint.txt | 1 + tools/release/release-signing-key.asc | 53 +++++++++ 3 files changed, 158 insertions(+) create mode 100644 .github/workflows/verify-tag.yml create mode 100644 tools/release/release-signing-fingerprint.txt create mode 100644 tools/release/release-signing-key.asc diff --git a/.github/workflows/verify-tag.yml b/.github/workflows/verify-tag.yml new file mode 100644 index 0000000..db99919 --- /dev/null +++ b/.github/workflows/verify-tag.yml @@ -0,0 +1,104 @@ +# Train tag gate: every vX.Y.Z tag in this repo must be signed by the XChain +# Platform release key, and must name the version the commit actually carries. +# +# WHY THIS EXISTS. The release-manifest chain starts at the tag: the tag +# signature proves who cut the release, SHA256SUMS.asc proves the asset set is +# theirs, the manifest pins every component, and clone verification proves the +# installed tree is that commit. An unsigned train tag is not a style lapse, it +# is the root of that chain missing, and it cannot be fixed after the fact: +# re-signing means deleting and re-pushing the tag, which branch protection +# refuses and which breaks the sparse-tag invariant. A tag cut unsigned stays +# unsigned, so this gate has to exist before a train is cut, not after. +# +# THIS FILE IS A TWIN. It is byte-identical in every train repo (nothing in it +# is repo-specific) and a platform-side test enforces that. Edit it in one place +# and re-copy; a per-repo edit is how nine gates stop being one gate. +# +# NOT the wallet's keys. The wallet signs its tags with K14 and its release +# manifests with K1, and confusing the three is a named hazard. This gate pins +# the PLATFORM key by fingerprint, from a file in this repo. +name: Verify tag + +on: + push: + tags: + - 'v*' + +# Never cancel a tag verification in flight: a cancelled run reads as "nothing +# went wrong" and this is the one check that must have said yes out loud. +concurrency: + group: verify-tag-${{ github.ref }} + cancel-in-progress: false + +permissions: + contents: read + +jobs: + verify-tag: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # actions/checkout recreates the TRIGGERING tag as a lightweight ref + # pointing straight at the commit, which discards the annotated tag object + # and with it the signature. Every check below would then fail as "cannot + # verify a non-tag object of type commit": not because the tag is + # unsigned, but because the runner no longer has a tag to verify. Re-fetch + # by force so what gets verified is the object the maintainer signed. + # (Learned the hard way in xchain-wallet's first release run.) + - name: Restore the annotated tag object (checkout flattens it) + run: git fetch --force origin "refs/tags/${GITHUB_REF_NAME}:refs/tags/${GITHUB_REF_NAME}" + + - name: Tag must be signed by the XChain Platform release key + run: | + set -euo pipefail + KEY="tools/release/release-signing-key.asc" + FPR_FILE="tools/release/release-signing-fingerprint.txt" + + EXPECTED="$(tr -d ' \n' < "$FPR_FILE" || true)" + + # A real fingerprint or nothing: a placeholder, an empty file or any + # other malformed value is refused rather than read as "unpinned, so + # allow". A gate that defaults to allow when unconfigured is not a gate. + if ! printf '%s' "$EXPECTED" | grep -qiE '^[0-9A-F]{40}$'; then + echo "::error::the release key is not pinned" + echo " $FPR_FILE reads '${EXPECTED}', not a 40-hex fingerprint." + exit 1 + fi + + gpg --batch --import "$KEY" + # Trust the pinned key ultimately so verification fails on the + # SIGNATURE rather than on the web of trust. + echo "${EXPECTED}:6:" | gpg --batch --import-ownertrust + + if ! git verify-tag --raw "${GITHUB_REF_NAME}" 2>verify.txt; then + echo "::error::tag ${GITHUB_REF_NAME} is not signed by a key we trust" + sed 's/^/ /' verify.txt + exit 1 + fi + + # `git verify-tag` succeeding is not the verdict: it passes for ANY + # key in the keyring. Bind it to the pinned fingerprint explicitly. + if ! grep -q "VALIDSIG ${EXPECTED}" verify.txt; then + echo "::error::tag ${GITHUB_REF_NAME} is signed, but not by the pinned release key" + echo " expected fingerprint: ${EXPECTED}" + sed 's/^/ /' verify.txt + exit 1 + fi + + echo "tag ${GITHUB_REF_NAME} verified against ${EXPECTED}" + + - name: Tag must match the committed version + run: | + set -euo pipefail + TAG="${GITHUB_REF_NAME}" + VERSION="v$(node -p "require('./package.json').version")" + if [ "$TAG" != "$VERSION" ]; then + echo "::error::tag $TAG does not match package.json version $VERSION" + echo " A train tag is cut on the master merge commit that carries the" + echo " version bump; a mismatch means the tag was cut from the wrong SHA." + exit 1 + fi + echo "$TAG matches package.json at $(git rev-parse HEAD)" diff --git a/tools/release/release-signing-fingerprint.txt b/tools/release/release-signing-fingerprint.txt new file mode 100644 index 0000000..475ee41 --- /dev/null +++ b/tools/release/release-signing-fingerprint.txt @@ -0,0 +1 @@ +1DA7C4896F56EA22CF491EDF4361611A82F90B70 diff --git a/tools/release/release-signing-key.asc b/tools/release/release-signing-key.asc new file mode 100644 index 0000000..8c98bab --- /dev/null +++ b/tools/release/release-signing-key.asc @@ -0,0 +1,53 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +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 +=rTEm +-----END PGP PUBLIC KEY BLOCK----- From 7341653444d35fc5cdd0511f606c77335ca78b2d Mon Sep 17 00:00:00 2001 From: J-Dog Date: Fri, 14 Aug 2026 19:55:29 -0700 Subject: [PATCH 5/7] ci: check out declared siblings for the coverage job and re-baseline the ratchet --- .github/workflows/ci.yml | 5 +++ bin/coverage-thresholds.json | 8 ++-- package.json | 2 +- test/unit/coverage-thresholds-sync.test.js | 43 ++++++++++++++++++++++ 4 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 test/unit/coverage-thresholds-sync.test.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4beb60a..8ec2b24 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,6 +95,11 @@ jobs: git clone --quiet --depth 1 --branch "$SIBLINGS_REF" "$url" "$repo" 2>/dev/null \ || git clone --quiet --depth 1 "$url" "$repo" echo "sibling $repo @ $(git -C "$repo" rev-parse --abbrev-ref HEAD) $(git -C "$repo" rev-parse --short HEAD)" + if [ -f "$repo/package.json" ]; then + ( cd "$repo" && npm ci --omit=dev --ignore-scripts --no-audit --no-fund >/dev/null 2>&1 ) \ + || ( cd "$repo" && npm install --omit=dev --ignore-scripts --no-audit --no-fund >/dev/null 2>&1 ) \ + || echo "sibling $repo: dependency install failed" + fi done - name: Use Node.js 22 diff --git a/bin/coverage-thresholds.json b/bin/coverage-thresholds.json index 6634063..ec6b8c2 100644 --- a/bin/coverage-thresholds.json +++ b/bin/coverage-thresholds.json @@ -1,7 +1,7 @@ { - "comment": "Coverage floors for the CI coverage job (regression floors, ~1-1.5 points below measured on 2026-08-12, not tier targets; raise as coverage climbs). Mirrored into the coverage:check npm script in package.json: keep both in sync. Re-baselined 2026-08-12: the 2026-07-23 floors (lines/statements 91, branches 87.5, functions 89) had drifted above measured and failed the job. Functions in particular fell to 67.48, so that floor is a holding line, not a target.", - "lines": 86, - "statements": 86, - "branches": 85, + "comment": "Coverage floors for the CI coverage job (regression floors, ~1-1.5 points below measured, not tier targets; raise as coverage climbs). Mirrored into the coverage:check npm script in package.json and guarded by test/unit/coverage-thresholds-sync.test.js. Re-measured 2026-08-14 WITH the declared siblings checked out, which is what the coverage job now does: 87.77 lines/statements, 86.67 branches, 67.48 functions over 587 unit tests; the functions floor stays at its existing 66, already inside the band, and remains a holding line rather than a target.", + "lines": 86.2, + "statements": 86.2, + "branches": 85.1, "functions": 66 } diff --git a/package.json b/package.json index acaf374..f716a51 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "test": "npx mocha --timeout 10000 'test/unit/**/*.test.js' --recursive --exit", "test:regtest": "mocha --timeout 0 --require ./test/prepareRegtest.test.js", "coverage": "c8 --reporter=text --reporter=html --include 'src/**/*.js' mocha --timeout 10000 'test/unit/**/*.test.js' --exit", - "coverage:check": "c8 --check-coverage --lines 86 --statements 86 --branches 85 --functions 66 --reporter=text-summary --include 'src/**/*.js' mocha --timeout 10000 'test/unit/**/*.test.js' --exit", + "coverage:check": "c8 --check-coverage --lines 86.2 --statements 86.2 --branches 85.1 --functions 66 --reporter=text-summary --include 'src/**/*.js' mocha --timeout 10000 'test/unit/**/*.test.js' --exit", "ci": "npx mocha --timeout 10000 'test/unit/**/*.test.js' --recursive --exit && npm run ci:security && npm run ci:regression && npm run ci:e2e && npm run ci:conformance", "ci:security": "npx mocha --timeout 30000 'test/security/**/*.test.js' --recursive --exit", "ci:regression": "npx mocha --timeout 30000 'test/regression/**/*.test.js' --recursive --exit", diff --git a/test/unit/coverage-thresholds-sync.test.js b/test/unit/coverage-thresholds-sync.test.js new file mode 100644 index 0000000..9fab91c --- /dev/null +++ b/test/unit/coverage-thresholds-sync.test.js @@ -0,0 +1,43 @@ +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); + +// The coverage ratchet keeps its floors in two places: bin/coverage-thresholds.json, +// which is what a human reads, and the c8 flags inside the coverage:check npm script, +// which is what CI obeys. Every one of those files says "keep both in sync" and +// nothing enforced it, so a floor could describe a ratchet the job was not running. +// The failure mode is not hypothetical: xchain-dashboard's ci.yml called a +// coverage:check script that did not exist in that repo at all, a job that could only +// ever exit 1, and the missing-script case is asserted here for that reason. +describe('coverage ratchet floors', () => { + const repoRoot = path.join(__dirname, '..', '..'); + const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')); + const declared = JSON.parse( + fs.readFileSync(path.join(repoRoot, 'bin', 'coverage-thresholds.json'), 'utf8'), + ); + + it('ships the coverage:check script the CI coverage job invokes', () => { + assert.equal( + typeof (pkg.scripts || {})['coverage:check'], + 'string', + 'ci.yml runs `npm run coverage:check`; without the script the job can only exit 1', + ); + }); + + it('enforces every declared floor, at the declared value', () => { + const script = pkg.scripts['coverage:check']; + for (const metric of ['lines', 'statements', 'branches', 'functions']) { + const flag = script.match(new RegExp('--' + metric + '\\s+([0-9.]+)')); + assert.ok(flag, `coverage:check does not enforce --${metric}, so that floor is decorative`); + assert.equal( + Number(flag[1]), + declared[metric], + `${metric} floor drifted: thresholds.json says ${declared[metric]}, coverage:check enforces ${flag[1]}`, + ); + } + }); + + it('fails the job on a shortfall rather than only reporting it', () => { + assert.match(pkg.scripts['coverage:check'], /--check-coverage/); + }); +}); From c737940cfa664236710279fdc83d488e1a7dda6c Mon Sep 17 00:00:00 2001 From: J-Dog Date: Sat, 15 Aug 2026 09:46:27 -0700 Subject: [PATCH 6/7] fix(deps): take the js-yaml and brace-expansion security patches Both overrides sat one patch short of the fix: js-yaml 4.3.0 carries the quadratic !!omap CPU consumption advisory and brace-expansion 5.0.8 the unbounded-array DoS. --- package-lock.json | 12 ++++++------ package.json | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4e6b1bd..4fa9cb8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2705,9 +2705,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", - "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dev": true, "license": "MIT", "dependencies": { @@ -4825,9 +4825,9 @@ "dev": true }, "node_modules/js-yaml": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", - "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", "dev": true, "funding": [ { diff --git a/package.json b/package.json index f716a51..dc06d99 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "mutate:quick": "npx stryker run test/mutation/stryker.conf.quick.json" }, "overrides": { - "brace-expansion": "^5.0.8", + "brace-expansion": "^5.0.9", "minimatch": "^10.2.5", "ajv": "^8.18.0", "elliptic": "^6.6.1", @@ -75,7 +75,7 @@ "ip-address": "^10.3.1", "tough-cookie": "^4.1.3", "form-data": "^4.0.6", - "js-yaml": "^4.3.0", + "js-yaml": "^4.3.1", "shell-quote": "^1.9.0" }, "engines": { From 08ebd52202ad2bd742659f742dc9516517aca643 Mon Sep 17 00:00:00 2001 From: J-Dog Date: Sat, 15 Aug 2026 12:48:53 -0700 Subject: [PATCH 7/7] chore(github): add the pull request template with the CLA checkbox The contributor agreement says the template carries an agreement checkbox; no template existed in any repo, so the sentence described something that was not there. --- .github/pull_request_template.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..6241305 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,17 @@ + + +## What this changes + + + +## How it was verified + + + +## Contributor License Agreement + +- [ ] I have read and agree to the [Contributor License Agreement](https://docs.xchain.io/legal/cla). + +The CLA Assistant bot checks this automatically and records your signature +against your GitHub account, once, covering all XChain Platform repositories. +Its `license/cla` check is the record that counts; this box is a reminder.