From e23c21852a113cfd195c39bb7565f0b7a14c89e2 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 51daf85..efdd640 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,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 # Cross-repo drift guards: this repo vendors the canonical coin registry from # xchain-hub, and per-repo CI cannot see sibling repos, so a drifted vendored From 722410c1a2f7a5aed7a467f9e101f571fc169590 Mon Sep 17 00:00:00 2001 From: J-Dog Date: Fri, 14 Aug 2026 15:36:59 -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 efdd640..cf2cc37 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,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 # Cross-repo drift guards: this repo vendors the canonical coin registry from # xchain-hub, and per-repo CI cannot see sibling repos, so a drifted vendored From 3640c28cc2d9be0d3e6c58e61cf3a6ce2030ef89 Mon Sep 17 00:00:00 2001 From: J-Dog Date: Fri, 14 Aug 2026 16:13:52 -0700 Subject: [PATCH 3/7] Collapse per-table schema verification logs into a two-line summary --- src/db.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/db.js b/src/db.js index cc5884d..2be564b 100644 --- a/src/db.js +++ b/src/db.js @@ -184,6 +184,11 @@ class Database { try { await db.release(); } catch(_){} return false; } + // One summary line instead of a per-table pair; error paths below still + // name the table, so a failure stays attributable. + console.log('Verifying database and tables...'); + let checked = 0; + let created = 0; try { for (file of files){ // indexOf returns -1 when '.sql' is absent (e.g. the migrations/ subdirectory). @@ -192,7 +197,7 @@ class Database { var isSql = file.indexOf('.sql'); if(isSql !== -1){ let table = file.substring(0, file.indexOf('.sql')); - console.log('Verifying ' + table + ' table exists...'); + checked++; try { if(existing.has(table)){ // Existing table: reconcile column drift against the SQL @@ -209,6 +214,7 @@ class Database { } else { await this.createTable(file, db); existing.add(table); + created++; } } catch(e){ console.log('Error verifying table ' + table + ': ' + e.code); @@ -224,6 +230,7 @@ class Database { // one connection per created table plus this one and exhausts the pool. try { await db.release(); } catch(_){} } + console.log('Database and tables verified (' + checked + ' tables, ' + created + ' created).'); return true; } @@ -1089,7 +1096,6 @@ class Database { // latent fresh-install-only bug. let queries = this.splitSqlStatements(data); let query = null; - console.log('Creating ' + table + ' table and indexes...'); try { for(query of queries){ query = query.trim(); From 8fda0dfb92ee0017b626f063379363549b7abdf7 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 3e7e784b656d5cf8ad79322db4d315ab61b3af0f 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 | 24 ++++++++++++ bin/coverage-thresholds.json | 10 ++--- package.json | 2 +- test/unit/coverage-thresholds-sync.test.js | 43 ++++++++++++++++++++++ 4 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 test/unit/coverage-thresholds-sync.test.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf2cc37..e18fe14 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -102,6 +102,30 @@ jobs: steps: - uses: actions/checkout@v4 + # The ratchet re-runs the unit suite, so it needs the same siblings the gate + # gets. Without them the guards that read a sibling skip silently, so the + # ratchet measures a suite the gate never ran: 1248 of 1289 unit tests ran without them. + # Same roster (.ci-siblings) and same layout 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)" + 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 uses: actions/setup-node@v4 with: diff --git a/bin/coverage-thresholds.json b/bin/coverage-thresholds.json index 1bb7127..23eda2f 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.", - "lines": 86, - "statements": 86, - "branches": 84, - "functions": 75 + "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: 89.30 lines/statements, 86.97 branches, 78.75 functions over 1289 unit tests (1248 without siblings).", + "lines": 87.8, + "statements": 87.8, + "branches": 85.4, + "functions": 77.2 } diff --git a/package.json b/package.json index 0322edb..5d80d90 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,7 @@ "migrate": "node ./src/migrate.js", "test": "mocha --timeout 5000 --require ./test/unit/setup.js 'test/unit/**/*.test.js' --exit", "coverage": "c8 --reporter=text --reporter=html --include 'src/**/*.js' mocha --timeout 5000 --require ./test/unit/setup.js 'test/unit/**/*.test.js' --exit", - "coverage:check": "c8 --check-coverage --lines 86 --statements 86 --branches 84 --functions 75 --reporter=text-summary --include 'src/**/*.js' mocha --timeout 5000 --require ./test/unit/setup.js 'test/unit/**/*.test.js' --exit", + "coverage:check": "c8 --check-coverage --lines 87.8 --statements 87.8 --branches 85.4 --functions 77.2 --reporter=text-summary --include 'src/**/*.js' mocha --timeout 5000 --require ./test/unit/setup.js 'test/unit/**/*.test.js' --exit", "test:smoke": "mocha --timeout 5000 --require ./test/unit/setup.js 'test/smoke/**/*.smoke.js'", "test:unit": "mocha --timeout 5000 --require ./test/unit/setup.js 'test/unit/**/*.test.js'", "ci": "npm run ci:unit && npm run ci:security && npm run ci:smoke && npm run ci:regression && npm run ci:chaos && npm run ci:fuzz", 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 59d3f35fd3df4fecedc239a9a94b0aab955defa9 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 63b3ba5..3ffd92a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1697,9 +1697,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": { @@ -3599,9 +3599,9 @@ "license": "MIT" }, "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 5d80d90..8ab384e 100644 --- a/package.json +++ b/package.json @@ -85,7 +85,7 @@ "sinon": "^21.0.3" }, "overrides": { - "brace-expansion": "^5.0.8", + "brace-expansion": "^5.0.9", "minimatch": "^10.2.5", "form-data": "^4.0.6", "qs": "^6.15.2", @@ -102,7 +102,7 @@ "@uphold/request-logger": { "uuid": "^3.4.0" }, - "js-yaml": "^4.3.0" + "js-yaml": "^4.3.1" }, "engines": { "node": ">=22.0.0" From 1d5d0df55d90d65d8a7e4b562a31a73682ecaeaf 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.