fix(ci): libuv learns MidnightBSD; lws alloc.c gets Haiku's missing d… #10
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |