Skip to content

ci: verify the two win32 better-sqlite3 prebuilds actually load - #357

Merged
KnockOutEZ merged 6 commits into
studio-handofffrom
slice-q6-win32-prebuilds
Aug 18, 2026
Merged

ci: verify the two win32 better-sqlite3 prebuilds actually load#357
KnockOutEZ merged 6 commits into
studio-handofffrom
slice-q6-win32-prebuilds

Conversation

@KnockOutEZ

@KnockOutEZ KnockOutEZ commented Aug 18, 2026

Copy link
Copy Markdown
Owner

Closes the last two rows of the better-sqlite3 12.9.0 Node-22 (ABI 127) prebuild matrix. That matrix is 10 targets, not 8 — linux-arm and the three linuxmusl-* are the ones a count-from-memory misses.

Eight were already verified by loading the published release asset and driving it through require -> open DB -> FTS5 index -> MATCH: darwin-arm64, darwin-x64, linux-x64, linux-arm64, linux-arm, linuxmusl-x64, linuxmusl-arm64, linuxmusl-arm. The two win32 targets were not — the build machine had no Windows kernel, so only the binaries' PE shape was confirmed, and the previous agent declined to synthesise a pass.

Both win32 targets are now verified on real Windows kernels. The matrix is 10/10.

What this adds

scripts/verify-better-sqlite3-prebuild.mjs — downloads the published asset for a target, unpacks the JS wrapper straight from the registry tarball the lockfile resolves to (checked against the lockfile's integrity hash), loads the asset via nativeBinding, then builds an FTS5 index and asserts a MATCH returns exactly the one matching row and zero rows for a term that matches none. Loading is not the bar: a require() that returns an object only proves a file resolved, not that the extension's SQLite carries FTS5.

No npm, so no install lifecycle exists — the --ignore-scripts guarantee is structural rather than a flag: neither prebuild-install nor node-gyp can supply a binding and let the probe verify itself. The pinned version, tarball URL and integrity hash all come from package-lock.json, never a literal, so a pin bump cannot leave the probe verifying the previous release.

.github/workflows/ci.yml — two jobs, on windows-latest and on the GitHub-hosted windows-11-arm runner (free and GA for public repositories since 2025-08-07; this repository is public). The arm64 leg was confirmed to allocate: image windows-11-arm64, Node 22.23.2/arm64.

Results on head 3d97e144

job result
better-sqlite3 prebuild loads (win32-x64) success — run 32144112512
better-sqlite3 prebuild loads (win32-arm64) success — run 32144112512

What the positive steps printed:

win32-x64   asset better-sqlite3-v12.9.0-node-v127-win32-x64.tar.gz (1038208 bytes)
            sha256 8c041ef57dd1bb55b0032306594310625b7a7a374bc48956e0858645f56919c4
            sqlite 3.53.0   fts5 MATCH ["prebuild-under-test"]

win32-arm64 asset better-sqlite3-v12.9.0-node-v127-win32-arm64.tar.gz (905718 bytes)
            sha256 36302069784833dac5e62ff6413f01631a8bbad2f80ad438b9d914028d8c537d
            sqlite 3.53.0   fts5 MATCH ["prebuild-under-test"]

Negative controls

Five per leg, each of which must be rejected. --expect-fail inverts the exit code over the load only: the asset must still download and extract, so a control cannot "pass" by 404ing on a mistyped target.

control rejection observed on Windows
wrong arch (the other win32 target) ... better_sqlite3.node is not a valid Win32 application.
wrong platform (linux-x64 ELF) ... is not a valid Win32 application.
wrong libc (linuxmusl-x64) ... is not a valid Win32 application.
wrong ABI (Node 18, v115) The module '...' — NODE_MODULE_VERSION mismatch
binding absent Cannot find module '...'

The controls were shown to go red

Commit 2dcda2b6 handed the wrong-arch control each runner's own target, so the binding loads. Both legs failed on that step, exactly as intended — run 32143923092:

NEGATIVE CONTROL FAILED — win32-x64 / ABI 127 LOADED and ran FTS5 on win32-x64.
A control that cannot fail proves nothing about the positive runs beside it.

3d97e144 reverts that mutation and is byte-identical to 192fedcf.

Constraints held

Two CI-only portability bugs found and fixed on the way

  • spawnSync npm.cmd EINVAL — the CVE-2024-27980 hardening, the same trap tests/e2e/init-command.e2e.test.ts hit. Fixed by removing npm from the probe rather than reaching for shell: true.
  • tar (child): Cannot connect to C: resolve failed — under shell: bash a Windows runner resolves tar to GNU tar from Git for Windows, which reads an absolute C:\... argument as a host:path remote spec. Fixed by naming the tarball relative to cwd, a form bsdtar in System32 accepts too.

… serves FTS5

A require() that returns an object only proves a file resolved. The probe
downloads the release asset for a given target, installs the JS wrapper with
--ignore-scripts so no locally built binding can stand in for it, then builds an
FTS5 index and asserts a MATCH returns exactly the one row that matches and
nothing for a term that matches none.

--expect-fail inverts the exit code over the load only: the asset must still
download and extract, so a control cannot pass by 404ing on a mistyped target.
The version comes from package-lock.json, never a literal, so a pin bump cannot
leave the probe verifying the previous release.
…nels

Eight of the ten Node-22 (ABI 127) prebuild targets for the pinned 12.9.0 were
verified from the published release asset. win32-x64 and win32-arm64 were not:
the build machine had no Windows kernel, so only the binaries' PE shape was
confirmed. This runs the same probe on windows-latest and on the GitHub-hosted
windows-11-arm runner, which is free and generally available for public repos.

Five negative controls per leg — wrong arch, wrong platform, wrong libc, wrong
ABI, absent binding — each of which must be rejected, because a probe that
cannot fail says nothing about the binding it loaded. No continue-on-error: a
job that reports success regardless of its steps is the vacuous green this phase
removes. No npm ci either, which keeps the job clear of the v13 install-script
problem and stops prebuild-install from supplying the binding under test.
@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3d810b39-3917-4773-8c30-6888bbfc35ac

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Both win32 legs failed identically on the first CI run with 'spawnSync npm.cmd
EINVAL' — the CVE-2024-27980 hardening, which refuses to spawn a .cmd without
shell: true. Same trap the init e2e test hit.

Rather than reach for shell: true, the probe now downloads the tarball the
lockfile resolves to, verifies it against the lockfile's integrity hash, and
untars it. No install lifecycle exists at all, so the earlier --ignore-scripts
guarantee is now structural: there is no script to ignore. better-sqlite3's
wrapper requires 'bindings' lazily and only when nativeBinding is null, and this
probe always passes a path, so the unpacked wrapper needs no dependency tree.

Confirms windows-11-arm resolved and allocated: the arm64 leg ran on image
windows-11-arm64 with Node 22.23.2/arm64 and reached the same npm failure.
…s it

Both win32 legs failed with 'tar (child): Cannot connect to C: resolve failed'.
Under shell: bash a Windows runner resolves tar to GNU tar from Git for Windows,
which reads an absolute C:\... argument as a host:path remote spec. Passing the
name relative to cwd removes the colon, and bsdtar in System32 accepts the same
form, so one call works on every runner.
A control asserted to hold is worth nothing until it has been seen to break. This
hands the wrong-arch control each runner's OWN target, so the binding loads and
serves an FTS5 MATCH and the step must fail. Reverted in the next commit; this
exists so the green beside it has a measured failure mode.
@KnockOutEZ
KnockOutEZ merged commit f901293 into studio-handoff Aug 18, 2026
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant