Skip to content

fix(ci): libuv learns MidnightBSD; lws alloc.c gets Haiku's missing d… #10

fix(ci): libuv learns MidnightBSD; lws alloc.c gets Haiku's missing d…

fix(ci): libuv learns MidnightBSD; lws alloc.c gets Haiku's missing d… #10

Workflow file for this run

name: release
# THE release workflow (unified 2026-07-10, user call — formerly two files:
# the SEA-only release.yml and this matrix): tjs builder legs (T1 native,
# T1.5 Alpine/musl x8, T2 VM guests — fuse and smoke run INSIDE the guest; a
# BSD binary has no binfmt escape on a Linux host) PLUS the transitional
# Node-SEA legs (the -node-tagged assets; Windows's only path today). ONE
# release job gates on BOTH families: every hard leg must succeed or nothing
# publishes; soft legs (continue-on-error) contribute artifacts only when
# green. One SHA256SUMS covers every asset; every asset is SLSA-attested.
# Publishes ONLY clode builders (canon: quaude is derived work, never leaves
# a runner). glibc Linux legs are CI-smoke only (Decision 3) — the published
# Linux artifacts are the musl-static ones. Phase 3 adds the T3 own-qemu
# legs (netbsd aarch64/sparc/i386) through ./.github/actions/guest, whose
# YAML shape is fixed here from day one.
#
# Triggers: a version tag, or manual dispatch (default: artifacts only, no
# release — the dry-run mode; 'draft' mints a draft prerelease to inspect).
on:
push:
tags: ['v*']
workflow_dispatch:
inputs:
release:
description: "release handling: skip = artifacts only (dry-run), draft = draft prerelease"
type: choice
options: [skip, draft]
default: skip
permissions:
contents: read
jobs:
leg:
strategy:
fail-fast: false
matrix:
include:
# ---- T1 native runners
# Naming (user decision 2026-07-10, closing the plan's "-tjs
# disposition" item): tjs builders own the CANONICAL name
# clode-<ver>-<platform>; the transitional Node-SEA binaries carry
# the -node engine tag instead (release.yml).
- leg: darwin-arm64 # PUBLISHED
os: macos-14
publish: true
- leg: linux-x64-glibc # CI smoke only (Decision 3)
os: ubuntu-latest
publish: false
- leg: linux-arm64-glibc # CI smoke only
os: ubuntu-24.04-arm
publish: false
# ---- T1.5 Alpine musl-static (the published Linux artifacts)
- leg: linux-x64-musl # PUBLISHED
os: ubuntu-latest
guest-platform: alpine
guest-arch: x86_64
static: true
publish: true
- leg: linux-arm64-musl # PUBLISHED (alpine container on the arm runner)
os: ubuntu-24.04-arm
guest-platform: alpine
guest-arch: aarch64
static: true
publish: true
- leg: linux-s390x-musl # PUBLISHED; 64-bit BE oracle arch (qemu-user)
os: ubuntu-latest
guest-platform: alpine
guest-arch: s390x
static: true
wasm: off # WAMR's MAP_32BIT is x86/ARM-only; undefined on s390x
publish: true
smoke: version # PONG-class smoke lives in the be-oracle job
timeout: 300
soft-fail: true # slow qemu-user BE leg — non-blocking (plan T1.5)
# ---- Phase-2 legs (plan Q2 PHASE-2 PICKUP). New legs start
# soft-fail and EARN hard status by smoking green (house rule) —
# dispatches #5-#13 hardened everything except the slow qemu-user
# TCG class (non-blocking by design, like s390x) and dragonflybsd
# (one environmental VM hang on record; hardens next cycle).
# publish:true only materializes an artifact when the leg is green.
- leg: darwin-x64 # PUBLISHED (deferred from phase 1)
os: macos-15-intel
publish: true
# ---- T1.5 extra musl arches. x86 execs natively on the x64 kernel
# (full smoke); the rest are qemu-user with version-smoke like s390x.
# wasm off: MAP_32BIT is x86_64/aarch64-only in musl headers — and
# 32-bit WAMR is worthless to us anyway.
- leg: linux-x86-musl # PUBLISHED; 32-bit LE
os: ubuntu-latest
guest-platform: alpine
guest-arch: x86
static: true
wasm: off
publish: true
- leg: linux-armv7-musl # PUBLISHED; qemu-user (Cobalt runners lack aarch32 EL0)
os: ubuntu-latest
guest-platform: alpine
guest-arch: armv7
static: true
wasm: off
publish: true
smoke: version
timeout: 300
soft-fail: true
- leg: linux-ppc64le-musl # PUBLISHED; qemu-user
os: ubuntu-latest
guest-platform: alpine
guest-arch: ppc64le
static: true
wasm: off
publish: true
smoke: version
timeout: 300
soft-fail: true
- leg: linux-riscv64-musl # PUBLISHED; qemu-user
os: ubuntu-latest
guest-platform: alpine
guest-arch: riscv64
static: true
wasm: off
publish: true
smoke: version
timeout: 300
soft-fail: true
- leg: linux-loongarch64-musl # PUBLISHED; qemu-user (alpine >= 3.21)
os: ubuntu-latest
guest-platform: alpine
guest-arch: loongarch64
static: true
wasm: off
publish: true
smoke: version
timeout: 300
soft-fail: true
# ---- T2 VM legs: fuse + smoke run INSIDE the guest (exec=guest —
# BSD/illumos binaries have no binfmt escape on a Linux host).
# Config: wasm off (WAMR "linux"-platform mremap wall on every
# non-Linux POSIX), mimalloc off (NetBSD compile regression; start
# uniform, re-enable per-platform as legs prove), ffi off (spares a
# guest libffi dep; nothing shipped imports tjs:ffi). Engine config
# (quickjs + wurl + libuv) identical to the pinned oracle build.
- leg: netbsd-amd64 # PUBLISHED; cross-platform-actions, KVM
os: ubuntu-latest
guest-platform: netbsd
guest-version: "10.1"
guest-packages: cmake gmake nodejs git-base bash
wasm: off
mimalloc: off
ffi: off
publish: true
- leg: freebsd-amd64 # PUBLISHED; cross-platform-actions, KVM
os: ubuntu-latest
guest-platform: freebsd
guest-version: "14.4"
guest-packages: cmake gmake node git bash
wasm: off
mimalloc: off
ffi: off
publish: true
- leg: openbsd-amd64 # PUBLISHED; cross-platform-actions, KVM
os: ubuntu-latest
guest-platform: openbsd
guest-version: "7.9"
guest-packages: cmake gmake node git bash
wasm: off
mimalloc: off
ffi: off
publish: true
- leg: dragonflybsd-amd64 # PUBLISHED; cross-platform-actions, KVM
os: ubuntu-latest
guest-platform: dragonflybsd
guest-version: "6.4.2"
guest-packages: cmake gmake node git bash
wasm: off
mimalloc: off
ffi: off
publish: true
soft-fail: true
- leg: omnios-amd64 # PUBLISHED; cross-platform-actions, KVM (illumos rung)
os: ubuntu-latest
guest-platform: omnios
guest-version: r151058
guest-packages: developer/gcc14 developer/build/gnu-make ooce/developer/cmake ooce/runtime/node-22 developer/versioning/git shell/bash
wasm: off
mimalloc: off
ffi: off
publish: true
- leg: solaris-amd64 # PUBLISHED; vmactions (SunOS rung)
os: ubuntu-latest
guest-platform: solaris
guest-version: 11.4-gcc # CBE image with gcc/g++ preinstalled
guest-packages: developer/build/cmake developer/build/gnu-make developer/versioning/git runtime/nodejs shell/bash
wasm: off
mimalloc: off
ffi: off
publish: true
timeout: 120 # vmactions boot is slower than cpa
# ---- Sweep 2 (2026-07-10): the remaining easy adds on proven
# machinery. BSD arm64 = cpa's other architecture (TCG on GitHub
# runners — no /dev/kvm — hence the long timeouts); MidnightBSD +
# Haiku = cpa's remaining x86-64 catalog; OpenIndiana = the third
# illumos flavor via vmactions (the __sun fixups transfer). All
# soft-fail until they earn hard status.
- leg: netbsd-arm64 # PUBLISHED; cpa, TCG
os: ubuntu-latest
guest-platform: netbsd
guest-arch: arm64
guest-version: "10.1"
guest-packages: cmake gmake nodejs git-base bash
wasm: off
mimalloc: off
ffi: off
publish: true
timeout: 300
soft-fail: true
- leg: freebsd-arm64 # PUBLISHED; cpa, TCG
os: ubuntu-latest
guest-platform: freebsd
guest-arch: arm64
guest-version: "14.4"
guest-packages: cmake gmake node git bash
wasm: off
mimalloc: off
ffi: off
publish: true
timeout: 300
soft-fail: true
- leg: openbsd-arm64 # PUBLISHED; cpa, TCG
os: ubuntu-latest
guest-platform: openbsd
guest-arch: arm64
guest-version: "7.9"
guest-packages: cmake gmake node git bash
wasm: off
mimalloc: off
ffi: off
publish: true
timeout: 300
soft-fail: true
- leg: midnightbsd-amd64 # PUBLISHED; cpa, KVM (mport packages)
os: ubuntu-latest
guest-platform: midnightbsd
guest-version: "4.0.4"
# no git: the 4.0.4 mport tree's git dep chain is broken
# (p5-Digest-HMAC wants perl >= 5.40.3, image ships 5.38.5 —
# dispatch #14); every cmake git usage is if(GIT_EXECUTABLE)-
# guarded, so the build does not need it.
guest-packages: cmake gmake node bash
wasm: off
mimalloc: off
ffi: off
publish: true
soft-fail: true
- leg: haiku-x64 # PUBLISHED; cpa, KVM (a genuinely new OS rung)
os: ubuntu-latest
guest-platform: haiku
guest-version: r1beta5
# HaikuPorts ships exactly ONE node: nodejs20 (user-verified,
# 2026-07-10) — named explicitly; v20 clears the build floor
# (lowered to 20 for OpenIndiana the same day). cmd:X provides-
# syntax for the rest ("nodejs" alone: Name not found, #14).
guest-packages: cmd:cmake cmd:gcc nodejs20 cmd:git cmd:make
wasm: off
mimalloc: off
ffi: off
publish: true
soft-fail: true
- leg: openindiana-amd64 # PUBLISHED; vmactions (3rd illumos flavor)
os: ubuntu-latest
guest-platform: openindiana
guest-version: 202604-build # build-essential image
guest-packages: developer/build/cmake developer/build/gnu-make developer/versioning/git shell/bash runtime/nodejs
wasm: off
mimalloc: off
ffi: off
publish: true
timeout: 120
soft-fail: true
runs-on: ${{ matrix.os }}
timeout-minutes: ${{ matrix.timeout || 90 }}
continue-on-error: ${{ matrix.soft-fail || false }}
permissions:
contents: read
id-token: write # actions/attest-build-provenance
attestations: write
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
with:
node-version-file: .tool-versions
- uses: ./.github/actions/build-leg
with:
leg: ${{ matrix.leg }}
guest-platform: ${{ matrix.guest-platform || 'native' }}
guest-arch: ${{ matrix.guest-arch || 'x86_64' }}
guest-version: ${{ matrix.guest-version || '3.22' }}
guest-packages: ${{ matrix.guest-packages || '' }}
static: ${{ matrix.static && 'true' || 'false' }}
wasm: ${{ matrix.wasm || 'on' }}
mimalloc: ${{ matrix.mimalloc || 'on' }}
ffi: ${{ matrix.ffi || 'on' }}
smoke: ${{ matrix.smoke || 'full' }}
attest: ${{ matrix.publish && 'true' || 'false' }}
publish: ${{ matrix.publish && 'true' || 'false' }}
# ---- Transitional Node-SEA legs (the -node engine tag; formerly their
# own workflow). Retired per platform as tjs legs prove out — Windows has
# no tjs leg yet (sync-spawn is posix_spawn; port is Q3+ work).
sea:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest # linux-glibc2.28-x64
- os: ubuntu-24.04-arm # linux-glibc2.28-arm64
- os: macos-14 # macos-14-arm64
- os: windows-latest # windows-x64
- os: windows-11-arm # windows-arm64
runs-on: ${{ matrix.os }}
permissions:
contents: read
id-token: write # actions/attest-build-provenance
attestations: write
defaults:
run:
shell: bash # windows runners default to pwsh; force Git Bash
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
with:
node-version-file: .tool-versions
- name: Assert Node matches the .tool-versions pin
run: |
set -euo pipefail
want=$(sed -n 's/^nodejs //p' .tool-versions)
have=$(node -v); have=${have#v}
[ "$have" = "$want" ] || { echo "node skew: .tool-versions=$want but runtime=$have" >&2; exit 1; }
echo "node $have matches .tool-versions pin"
- name: Build the SEA binary
run: node scripts/build-sea.mjs
- name: Offline SEA smoke (build + --clode-version/--clode-help)
run: CLODE_SEA=1 node --test --test-concurrency=1 test/sea-build.test.cjs test/sea-smoke.test.cjs
- name: Name the binary
id: name
run: |
set -euo pipefail
if [ "$GITHUB_REF_TYPE" = tag ]; then V="${GITHUB_REF_NAME#v}"; else V="$(cat VERSION)"; fi
BASE=$(node -e "console.log(require('./scripts/platform-tag.cjs').platformTag())")
BASE=${BASE%-node*}
# seaBin returns an OS-NATIVE path; normalize to forward slashes
# so bash `cp` accepts it on the Git Bash runner.
BIN=$(node -e "const p=require('path');console.log(require('./scripts/platform-tag.cjs').seaBin('.').split(p.sep).join('/'))")
EXT=""; case "$BIN" in *.exe) EXT=".exe" ;; esac
# -node = the engine tag: tjs builders own the bare
# clode-<ver>-<platform> names.
ASSET="clode-$V-$BASE-node$EXT"
cp "$BIN" "$ASSET"
echo "asset=$ASSET" >> "$GITHUB_OUTPUT"
- name: Attest build provenance
uses: actions/attest-build-provenance@v4
with:
subject-path: ${{ steps.name.outputs.asset }}
- uses: actions/upload-artifact@v7
with:
name: ${{ steps.name.outputs.asset }}
path: ${{ steps.name.outputs.asset }}
if-no-files-found: error
# 64-bit big-endian oracle (plan T1.5): the node-shim suite against the
# s390x-musl tjs under qemu-user binfmt. Provider-gated suites self-skip
# (no provider here); the engine/url grading and shim behavior rows run.
# Non-blocking by design — a BE regression is a finding, not a release gate,
# until the leg earns its way in (house rule: note it, don't grind).
be-oracle:
needs: leg
if: ${{ !cancelled() }} # run even when an unrelated leg failed; missing artifact fails loudly
runs-on: ubuntu-latest
continue-on-error: true
timeout-minutes: 180
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
with:
node-version-file: .tool-versions
- uses: docker/setup-qemu-action@v4 # fix-binary binfmt: host node spawns s390x tjs
- uses: actions/download-artifact@v8
with:
name: tjs-linux-s390x-musl
path: ${{ runner.temp }}/tjs-s390x
- name: Install the runtime dep closure
run: npm ci
- name: BE oracle — engine + endianness subset (node-shim + url golden)
env:
CLODE_TJS: ${{ runner.temp }}/tjs-s390x/tjs
run: |
set -euo pipefail
chmod +x "$CLODE_TJS"
"$CLODE_TJS" eval 'console.log("s390x tjs alive:", tjs.version)'
# THE PRIZE: prove the ENGINE is big-endian-correct. First BE matrix
# run (dispatch #4, run 29065977866) was 124/140 — every
# endianness-sensitive test GREEN (url characterization = the crown
# BE test, sha256/hmac KAT, Buffer view semantics, zlib consts). The
# oracle runs the pure engine/language + BE-sensitive node-shim tests
# and the url golden, and must be GREEN when the engine is BE-clean.
#
# EXCLUDED — not engine, not endianness (all 12 dispatch-#4 reds fell
# in exactly these four files; each is an environmental or Linux-
# portability matter, filed as its own workstream in BACKLOG.md, NOT
# a BE bug):
# node-shim-tty PTY/TTY: no controlling terminal under
# qemu-user headless emulation (harness).
# node-shim-child-process nested child spawn under qemu-user
# binfmt (cwd -> status:null) + the musl
# addchdir limit (#if __APPLE__||__GLIBC__).
# node-shim-core os.constants.signals: the shim's signal
# table is hardcoded DARWIN values (SIGBUS 10
# vs Linux 7) — a real Linux-portability shim
# bug, first surfaced here; separate TDD pass.
# node-shim-bunshim bun:ffi suffix hardcodes the macOS 'dylib'
# (Linux .so) — platform-aware fix pending.
# Non-blocking (continue-on-error): a NEW data-correctness red stands
# out against this green baseline. House rule: note it, don't grind.
EXCLUDE='node-shim-tty|node-shim-child-process|node-shim-core|node-shim-bunshim'
FILES=$(ls test/node-shim-*.test.cjs | grep -vE "$EXCLUDE")
node --test --test-concurrency=1 $FILES test/url-characterization.test.cjs
# ONE gate for the whole release: every HARD leg in BOTH families must
# succeed (needs:), or nothing publishes. Soft legs (continue-on-error)
# never block; their artifacts ship only when green.
release:
needs: [leg, sea]
if: github.ref_type == 'tag' || inputs.release == 'draft'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v7
- uses: actions/download-artifact@v8
with:
pattern: clode-* # published builders + SEA binaries (smoke-* and tjs-* stay CI artifacts)
path: dist
merge-multiple: true
- name: Attach builders + shas to the release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
cd dist
chmod +x clode-*
sha256sum clode-* > SHA256SUMS
cat SHA256SUMS
cd ..
if [ "$GITHUB_REF_TYPE" = tag ]; then
TAG="$GITHUB_REF_NAME"
# The SEA release workflow may already have created this release —
# attach, don't duplicate; create only if we got here first.
gh release view "$TAG" --repo "$GITHUB_REPOSITORY" >/dev/null 2>&1 \
|| gh release create "$TAG" --repo "$GITHUB_REPOSITORY" \
--target "$GITHUB_SHA" --title "$TAG" \
--notes "Automated release (tjs matrix)."
gh release upload "$TAG" --repo "$GITHUB_REPOSITORY" --clobber dist/*
else
V="$(cat VERSION)"; TAG="v$V-matrix-test.$GITHUB_RUN_NUMBER"
echo "Draft prerelease $TAG with: $(ls dist)"
gh release create "$TAG" --draft --prerelease \
--repo "$GITHUB_REPOSITORY" \
--target "$GITHUB_SHA" --title "$TAG" \
--notes "Matrix dry-run. Assets: $(ls dist | tr '\n' ' ')" \
dist/*
fi