Skip to content

Build & test hardening: config.sh everywhere, generated wasm exports, server_common, C/TS unit suites, sccache-dist CI#1

Merged
xdqi merged 76 commits into
mainfrom
build-and-test-hardening
Jun 11, 2026
Merged

Build & test hardening: config.sh everywhere, generated wasm exports, server_common, C/TS unit suites, sccache-dist CI#1
xdqi merged 76 commits into
mainfrom
build-and-test-hardening

Conversation

@xdqi

@xdqi xdqi commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Summary

Five phases of build & test hardening:

  1. config.sh / build.config.toml everywhere — all native + wasm build scripts (build_anyfs, build_qemu, build_lkl_wasm, build_boot_wasm, build_libblkid_*, gen_lkl_config_wasm) read paths from build.config.toml; lint gate covers all native scripts; wasm_sysroot key added with materialized wasm-ld default.
  2. Generated wasm exports — export list generated from anyfs_ts.c with a drift gate that matches all TS symbol references (not just inline ccall); build_anyfs_wasm rewritten on config.sh + generated exports, stale browser_wasm script dropped.
  3. server_common — shared daemon skeleton (signals/boot/shares/shutdown) factored out of ksmbd + nfsd servers; doctor learns a wasm-sysroot manifest check.
  4. C/TS unit suites — meson C unit suite (path DSL, share helpers), node:test core suite (format, session-base, dispatch matrix), vitest react provider+hooks suite, node wasm smokes ported to the async _p call pattern; test-core relocated to ts integration and ported to the session API; CDP scripts demoted to diagnostics after a Playwright parity audit.
  5. sccache-dist CI — linux.yml + mingw64.yml gain a best-effort sccache-dist compile farm (2 Tailscale workers + coordinator in the build job); new ts.yml unit-test workflow; shellcheck gate.

Key fixes along the way:

  • LKL-wasm bracket-fixer silent skip — the SECTIONS{} bracket-rewrite tools were not vendored, so the build silently skipped the fixup and produced a broken lkl.o; tools are now vendored and the build fails loudly when missing.
  • test-core session-API port — integration suite was calling the removed pre-session C API; ported to session* + async _p out-pointer wasm pattern.
  • Stale native smokes — addon smoke tests called async N-API entry points without awaiting; now awaited.

CI notes

Farm steps are PR-skipped by design (command -v sccache fallback contract): this PR's checks validate the local-compile fallback path, unit suites, and gates. Farm validation runs via workflow_dispatch on this branch.

🤖 Generated with Claude Code

xdqi and others added 30 commits May 31, 2026 22:07
Native QEMU child adopts a parent-created socketpair fd as its NBD
protocol channel (no TCP listen), with format drivers auto-layered atop
NBD. Approach A reuses QEMU's existing fd:N SocketAddress under the
no-monitor path, so zero QEMU source changes are needed; only anyfs-side
lspart + qemu_backend gain an nbd-fd branch. Windows falls back to
127.0.0.1 loopback. Three gated verification stages (fd passing, Linux
chain, wine scouting).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Record design intent for the production NBD server: fully async fs +
http data sources, exploiting QEMU NBD client's 16 concurrent in-flight
requests (cookie-matched, out-of-order replies) so a keep-alive http
upstream stays saturated. Note keep-alive/connection-pooling as a shared
future improvement URLFS should adopt too. PoC server stays synchronous
one-at-a-time; interface left Promise-compatible.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
HTTP/1.1 keep-alive is connection reuse, not mux — a single H1
connection is serial (pipelining deprecated). Concurrency for the 16
in-flight NBD reads comes from pool size N on H1, or true single-conn
multiplexing on H2 (undici negotiates H2 for CDN/object-storage URLs).
Local fs just runs the reads concurrently. URLFS shares the connection-
reuse/mux gap.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
9 tasks: qemu_backend nbd-fd/nbd-port branch, lspart flags, socketpair
N-API addon, hand-written read-only NBD server, qcow2 test fixture,
qemu-img protocol cross-check, then three gated stages (inherited-fd
open / full Linux chain parity + byte verify / wine loopback scouting).
TDD, exact commands, frequent commits.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
nbd-fd:N builds a server.type=fd QDict so QEMU's NBD client adopts an
inherited socket fd; nbd-port:P is the Windows 127.0.0.1 loopback
fallback. Filename to blk_new_open is NULL in both cases (server comes
from options). Zero QEMU source changes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Synthesizes an nbd-fd:N (or nbd-port:P) pseudo-path and feeds it through
the existing anyfs_session_open path unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Minimal N-API addon returning a connected AF_UNIX socket pair with
CLOEXEC cleared, for inheriting one end into the QEMU child. Seed of the
Node-layer native transport abstraction.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
newstyle fixed handshake + NBD_OPT_GO export info + READ/FLUSH/DISC
command loop. PoC: synchronous fs.readSync, one request at a time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ext4.img is all-zeros and mke2fs/sgdisk are not installed, so Task 5 now
synthesizes a deterministic raw image (i&0xff + ASCII marker) and converts
to qcow2. Task 8 byte-verify now reads the marker back THROUGH the
qcow2-over-NBD chain via qemu-io instead of a side control file. Tasks 7/8/9
use the correct build-*/src/lspart/anyfs-lspart(.exe) binary path; Stage-1
pass criterion is plain-vs-NBD table parity (no filesystem assumption).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Synthesizes an 8 MiB raw image with a deterministic byte pattern (i&0xff)
plus an ASCII marker at 1 MiB, then converts to qcow2 via qemu-img. The
marker is the Stage-2 byte-verification anchor read back through the
qcow2-over-NBD chain. (ext4.img is all-zeros and no fs tooling is
installed, so we generate our own deterministic content.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The hand-written server used 0x3e889045565a9700 which qemu-img rejected
with 'Unexpected option reply magic'. Correct value per QEMU
nbd/nbd-internal.h NBD_REP_MAGIC. Caught by the Task-6 qemu-img
cross-check gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s-check

serveOnFd creates a socketpair via the addon and runs the server on one
end; serveOnUnixSocket validates protocol correctness against the real
qemu-img NBD client. Cross-check confirmed qcow2 detected over NBD.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two corrections found during Task 6 gate: (1) OPT_REPLY_MAGIC was wrong
(0x3e889045565a9700 → 0x0003e889045565a9 per QEMU NBD_REP_MAGIC); (2)
Stage-2 byte verify must use async execFile, not execFileSync, because
the in-process NBD server shares the event loop and execFileSync would
deadlock it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lspart opens nbd-fd:3 over an inherited socketpair end; asserts exit 0,
non-zero socketpair reads, and table parity with the plain-file open.

Fix qemu_backend.c: call module_call_init(MODULE_INIT_QOM) before
bdrv_init() so QIOChannelSocket and other QOM types are registered.
QEMU binaries do this explicitly; the embedded block layer must too.
Without this, blk_new_open for NBD fails with "unknown type
'qio-channel-socket'" even though libio.a is statically linked in.

Also adjust the test script: the fixture has no partition table, so
dataRows == 0 for both plain-file and NBD; the parity check is the
correct signal — a standalone dataRows(out)==0 guard was redundant.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stage 1 confirmed: lspart opened qcow2 over an inherited socketpair fd
(27 reads traversed it, capacity detected, table parity). Documented the
MODULE_INIT_QOM init requirement found during Stage 1 — needed for the
qio-channel-socket QOM type; anyfs-glue fix only, zero QEMU source change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… verify

Asserts nbd lspart's table equals plain-file lspart and non-zero
inherited-fd reads, then verifies the deterministic marker reads back
byte-for-byte through the qcow2-over-NBD chain via qemu-io.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Records Stage 1/2 Linux PASS and the Task-6 protocol cross-check PASS,
plus the Windows/wine loopback probe outcome (go/no-go; stale mingw
binary predates the --nbd-port + MODULE_INIT_QOM changes). Stage 3 does
not gate the Linux PoC conclusion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Final-review cleanups: transport.c cleared CLOEXEC on both socketpair
ends but only the child end (fds[1]) is inherited — clear it there only
so the child no longer holds a stray copy of the parent's server end.
test-stage2 now closes the imageFd returned by serveOnUnixSocket. Both
Linux stages still PASS (27 reads, marker verified through chain).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
With a freshly-rebuilt QEMU-enabled mingw64 lspart, wine's QEMU NBD client
connected to the host Node NBD server over 127.0.0.1, opened the qcow2 over
NBD (capacity detected, 49 reads). Records the three gotchas that blocked it:
WINEPATH DLL search, stale binary, and the enable_qemu=False build-dir config
(--components=server silently disables QEMU; needs --components=core,server).
Both transports now validated: Linux inherited-fd + Windows loopback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Standalone, optionally-privileged Node process exposing a file / physical
disk / http URL as a read-only NBD endpoint for the QEMU engine to open
over an inherited fd or 127.0.0.1 loopback. Async multi-in-flight server
(out-of-order replies keyed by handle), DataSource abstraction (FileSource
/ BlockDeviceSource via drivelist / HttpSource via undici keep-alive),
thin CLI. Explicitly must-not-break the web blob/URLFS/curl paths; Electron
data-source convergence is future, not this scope.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
8 tasks: scaffold package → DataSource+FileSource → async multi-in-flight
NbdServer (out-of-order by handle) → endpoint+qemu-img integration →
HttpSource (global fetch keep-alive, connection-reuse asserted) →
BlockDeviceSource (Linux v1, drivelist/sys-block size) → CLI → web-regression
guard. Reuses the PoC fixture; no new undici dep; touches nothing outside
the new package.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
tsup/ESM package mirroring @anyfs/core's layout; empty index builds clean.
Adjusted package.json main/types/bin/exports paths to dist/src/ and dist/bin/
to match tsup output when entry points span two source directories.
optionalDependencies block omitted (drivelist loaded at runtime via import().catch()).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Async fs.promises pread backend with a partial-read loop; factory
dynamic-imports each backend. Unit test verifies exact-byte reads.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Ports the PoC wire format to TS; transmission loop dispatches reads
without awaiting (cap 16 in-flight), replies drained through a serialized
FIFO writer, paired by handle. Unit test proves out-of-order completion
still pairs each reply with its own handle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
serveOnFd / serveOnLoopback bind NbdServer to an inherited fd or a
127.0.0.1 listener. Integration test opens the PoC fixture qcow2 through
the proxy with real qemu-img/qemu-io, verifying format detection + the
marker byte-for-byte (FileSource end-to-end).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nging

A non-zero --port already in use previously hung the listen Promise
forever (no error handler). Attach server.once('error', reject) so bind
failures surface. Reachable once the CLI exposes --port.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Range-backed source over Node's global fetch (undici's default pool
reuses connections — no new dep). Integration test serves the fixture
qcow2 over a local Range server through the proxy and asserts qcow2
detection + that range reads share connections (keep-alive), not one
per read.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Raw block device source; size via optional drivelist with /sys/block
fallback, reads via plain pread (Linux kernel handles non-aligned).
Non-Linux throws a clear 'v1 Linux-only' error. Unit test uses a loop
device when privileged, skips otherwise.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replaces the placeholder CLI: wires a DataSource to an fd/loopback
endpoint, reports the bound port on stdout, shuts down on stdin EOF /
SIGTERM (lifecycle binding). Smoke test spawns the CLI and confirms
qemu-img opens the fixture qcow2 through it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Package test now runs both suites. Verified @anyfs/core still builds and
no files outside ts/packages/nbd-proxy (plus lockfile) were touched — the
web blob/URLFS/WORKERFS/curl-proxy paths are untouched per the non-goals
constraint.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
xdqi and others added 21 commits June 10, 2026 20:10
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pt install

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sing

The May-31 liblkl.a was built with the absolute-bracket fixer silently
skipped: FIXER defaulted to ${LKLFTPD_SRC}/wasm_fix_absolute_brackets.py
but LKLFTPD_SRC was never set anywhere, so the default expanded to
/wasm_fix_absolute_brackets.py and the `[[ -f ]]` guard quietly skipped
both post-processing passes. The resulting kernel boots into
`RuntimeError: table index is out of bounds` in trace_event_init when it
iterates __start_ftrace_events..__stop_ftrace_events (70 bracket symbols
were still WASM_SYM_ABSOLUTE in the shipped lkl.o; verified by running
the fixer against it). CONFIG_TRACING cannot be config'd away — `config
LKL` in arch/lkl/Kconfig force-selects it — and the __initcallN brackets
are equally load-bearing, so the rewrite is unconditional.

- vendor wasm_fix_absolute_brackets.py + wasm_prefix_kernel_symbols.py
  (project-internal tooling, no license headers) into
  scripts/lkl-wasm-tools/ and default FIXER/PREFIXER there
- hard-error instead of silently skipping when either tool is missing;
  FIXER/PREFIXER env overrides still honored
- set -o pipefail so a fixer failure can't hide behind `| tail`

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ymlinked images

With the rebuilt (bracket-fixed) liblkl.a from the May-31 lkl618-wasm
kernel merge, the old sync-ccall smoke now trips Emscripten's "running
asynchronously" assertion at anyfs_ts_session_open: the disk-add path
parks in a wait that defers to the event loop, so the sync proxied call
can't complete. This was always outside the documented contract — every
entry point that can touch the QEMU block layer must go through the `_p`
out-pointer variant with ccall({async:true}) because the fiber rewind
path discards export return values. Production (src/worker.ts) already
does exactly that; the smoke test now mirrors it, including the
init_async / session_enter_async dedicated-pthread paths when exported.

Also realpathSync the image before mounting: single.img and big.img are
symlinks pointing outside the disks dir, and Emscripten's VFS resolves
NODEFS symlink targets inside the wasm namespace where they don't exist.

All three variants (single/multi/big) pass against the rebuilt bundle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add --cc=CMD / --cc CMD option to build_lkl.sh (passed as CC= to both
make invocations) and build_qemu.sh (passed as --cc= to configure for
linux-amd64 only; switching compilers requires --reconfigure).
Groundwork for sccache-dist CI integration.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add .github/workflows/ts.yml for pure-TS CI: @anyfs/core node:test,
@anyfs/react vitest, @anyfs/nbd-proxy (unit + integration). Excludes
@anyfs/native (needs prebuilt liblkl) and format:check (17 files
out-of-spec across concurrent-agent branches).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Audited every assertion of the legacy CDP UI suite (tests/test-cdp.mjs,
6 target x source combos) against the Playwright E2E suite (ts/tests/e2e).
Full parity confirmed (Playwright is strictly stronger almost everywhere);
the one genuine gap -- the "Open URL..." dialog UI open flow, which the
drivers' openUrl() bridge hook bypassed -- is ported into
flows/url-load.spec.ts (green on web; skipped on electron, same renderer
DOM). The parity matrix with per-assertion justifications lives in
tests/diagnostics/README.md.

The CDP suite is DEMOTED, not removed: moved to tests/diagnostics/ and
kept runnable as manual diagnostics (notably the only way to exercise the
electron-native backend until FINDING F9's utilityProcess split lands).
Import paths fixed for the move: test-cdp.mjs now imports ../common.mjs;
common.mjs and the boot/prewarm debug scripts import
./diagnostics/common-cdp.mjs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d_lkl help

Move FIXER/PREFIXER existence check in build_lkl_wasm.sh to the early preflight
section (alongside .config/preseed/JOEL_WASM_LD guards) so a missing post-
processing tool fails before any kernel compile, preventing a half-built
liblkl.a that crashes at boot. Introduce REPO_ROOT/TOOLS_DIR there so the
variables are available at the new location; collapse the now-redundant later
block to a single comment.

Replace the fixed `sed -n '2,18p'` range in build_lkl.sh --help with the same
length-agnostic awk form used by build_qemu.sh, so the full header (including
the recently-added --cc option line) is always printed.

Add vendored-from provenance comment to wasm_fix_absolute_brackets.py and
wasm_prefix_kernel_symbols.py.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…r tests/diagnostics

The renames themselves (tests/test-core.mjs -> ts/tests/integration/,
tests/{common,test-atomics,test-async-boot,test-prewarm-direct,
test-prewarm-e2e,test-direct-module,test-worker-debug}.mjs ->
tests/diagnostics/) were accidentally swept into 5625d8b as pure R100
moves; this commit carries the accompanying content fixes:

- test-core.mjs: ROOT now resolves ../../.. (repo root) so the native
  addon, wasm bundle, and tests/images fixtures still resolve; usage
  header points at `pnpm run test:integration`.
- ts/package.json: add `test:integration` script.
- common.mjs lives with its only importers (test-cdp.mjs, formerly
  '../common.mjs', and test-worker-debug.mjs) in tests/diagnostics/;
  its ROOT and common-cdp import adjusted for the new depth.
- Debug scripts: './diagnostics/common-cdp.mjs' -> './common-cdp.mjs',
  '../ts/examples/*' -> '../../ts/examples/*'.
- diagnostics README: fill in the reserved "Debug scripts" section with
  one line per script stating the regression it guards.

Note: test-core currently fails on all combos for a pre-existing
reason unrelated to the move (verified identical at the pre-move
path): it still calls the old init/diskOpen/anyfs_ts_init API while
the native addon and wasm bundle now export the renamed session API
(kernelInit/sessionOpen/anyfs_ts_kernel_init/...). Porting the test
to the session API is follow-up work.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…attern)

The 2026-05-29 session-API rename was never applied to this test:
native combos failed with `m.init is not a function`, wasm combos with
`Cannot call unknown function anyfs_ts_init`.

- native: init/diskOpen/diskListJson/diskEnter/diskClose ->
  kernelInit/sessionOpen/sessionListJson/sessionEnter/sessionClose;
  sessionListJson/readdirJson are AsyncWorker-based and now awaited.
- wasm: anyfs_ts_init/disk_open/... -> the async `_p` out-pointer
  pattern from packages/core/test/smoke.node.mjs (callP with sentinel
  -0x7fffffff, boot via anyfs_ts_init_async + is_boot_complete polling,
  enter via session_enter_async/enter_is_complete/enter_result_p).
- mountWhole is gone from the session API: sessionEnter(h, 0, flags)
  mounts the whole disk (raw filesystem), part >= 1 selects a top-level
  partition by `index` (was `num`).
- explicit process.exit on success: lingering LKL/QEMU host threads keep
  the event loop alive after kernelHalt (known teardown behavior).

All 4 combos (native+file, native+url, wasm+file, wasm+url) pass:
17 pass, 0 fail. Note: tests/images/ext4.img (git-ignored, generated by
tests/setup.sh) was a truncate-only all-zeros artifact from an
interrupted setup run and had to be regenerated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
sessionListJson/sessionMetaJson/readdirJson/lstatJson/statJson/
fileOpen/pread/fileClose are AsyncWorker-based and return Promises;
smoke.mjs, smoke-url.mjs and native-session.test.mjs still called them
synchronously and JSON.parse'd "[object Promise]". Await them, and
adopt pread's new (fd, n, off) -> { rc, data } shape.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…he legacy hand-built sysroot)

scripts/build_wasm_sysroot.sh rebuilds all 17 static libs of
scripts/lib/wasm_sysroot.manifest from pinned upstream sources
(zlib 1.3.1, bzip2 1.0.8, zstd 1.5.7, libffi 3.5.2, glib 2.88.0 with
pcre2 10.46 via its subproject fallback + in-tree girepository,
util-linux 2.40.4 blkid+uuid, and the hand-written res_query stub
that is libresolv.a). Provenance was reverse-engineered from the
build trees/logs the hand-built sysroot preserved; the two
non-obvious bits are shipped explicitly:

  - scripts/lib/glib-2.88.0-emscripten-fd-query-path.patch — the
    uncommitted emscripten branch for g_unix_fd_query_path() the
    original glib tree carried (hard #error otherwise).
  - a post-setup config.h edit dropping HAVE_POSIX_SPAWN /
    HAVE_PTHREAD_GETNAME_NP (emscripten declares but does not define
    them; verified as the only non-path delta vs the oracle config.h).

scripts/lib/emscripten-cross.meson is the @sysRoot@-templated meson
cross file (replica of the oracle's cross-wasm32.meson). Acceptance
verified: clean run into a fresh sysroot diffs empty against the
manifest, and the node wasm bundle built against it passes the
@anyfs/core suite (16 unit + 3 smoke). Both lint gates extended.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sent

First real-Actions runs surfaced two issues:

- ts.yml: `pnpm install` failed with ENOENT on the sibling
  drivelist-anyfs checkout (electron-demo's file: dependency), and
  would next have failed on @anyfs/native's node-gyp script (needs the
  LKL tree). Exclude both from the CI install — electron-demo is the
  only dependent of @anyfs/native and neither is exercised by the unit
  suites. Verified locally in a simulated bare-runner layout (install,
  package build, core + react suites green).

- linux.yml / mingw64.yml: with TS_OAUTH_SECRET unset, the worker jobs
  failed ("config: oauth-secret is required") and marked every non-PR
  run red even though the build job stayed green; the half-initialized
  coordinator also left sccache on PATH, silently switching the build
  to local-sccache mode. Gate all farm steps on the secret being
  present (step-level `if` — the secrets context is not available at
  job level), so missing-secret runs skip the farm exactly like PRs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…he glib config.h sed

build_libblkid_wasm.sh is gutted to a thin exec-delegate to
build_wasm_sysroot.sh --only=blkid, which carries the mandatory
-O3 -pthread flags and the util-linux 2.40.4 version gate the old
helper lacked.  UL_SRC/SYSROOT env vars are forwarded unchanged
(same names in both scripts); BLD_DIR is superseded by WORK.

build_wasm_sysroot.sh: add pre/post grep assertions around the
HAVE_POSIX_SPAWN / HAVE_PTHREAD_GETNAME_NP sed deletion so a
silent no-op from a future glib bump fails loudly with a clear
remediation message instead of producing a broken sysroot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…visioning)

Completes the three-role provisioning model for the wasm sysroot
(recipe = build_wasm_sysroot.sh, release tarball = wasm-sysroot.yml
workflow, installer = fetch_wasm_sysroot.sh + manifest check):

- scripts/fetch_wasm_sysroot.sh: downloads the wasm-sysroot-linux.tar.xz
  release asset (WASM_SYSROOT_TAG, default wasm-sysroot-r1) into
  <repo>/.toolchain/wasm-sysroot/, with manifest-completeness as both
  the short-circuit and the post-extract validation; clean 404 error.
- scripts/lib/config.sh: auto-prefer the fetched .toolchain sysroot when
  paths.wasm_sysroot is unset/empty (explicit config still wins).
- .gitignore: ignore .toolchain/.
- both lint gates cover the new script.
- docs/wasm-sysroot.md: provisioning model, per-target table,
  version-bump procedure, the two excavated glib hacks, local rebuild.

(.github/workflows/wasm-sysroot.yml itself landed alongside the recipe
commit during concurrent work; it is already in the branch.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
First real-Actions dispatch showed that with TS_OAUTH_SECRET unset the
worker jobs fail ("config: oauth-secret is required") and mark every
non-PR run red even though the build job stays green; the
half-initialized coordinator also leaves sccache on PATH, silently
switching the build to local-sccache mode.

Gate all farm steps on the secret being present. The secrets context
is not allowed in `if` expressions (workflow parse rejects it), so its
presence is laundered through job-level env (FARM_SECRET_SET).
Missing-secret runs now skip the farm exactly like PRs do.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Companion to the fetch/recipe commits (referenced by 737bec3 as
already in the branch — restore it after a concurrent-history fixup
briefly dropped it).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@xdqi xdqi force-pushed the build-and-test-hardening branch from 073098d to bd2b066 Compare June 10, 2026 15:57
xdqi and others added 7 commits June 11, 2026 00:01
The integration test opens scripts/poc-nbd/fixtures/test.qcow2 through
the proxy with real qemu-img and refuses to run without it (fixture
dir is gitignored). Generate the deterministic 8 MiB image on the
runner — qemu-utils is already installed for the same tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The OAuth client owns tag:ci, not the action-default tag:ci-sccache
(workers failed to join the tailnet on the first real dispatch). v0.0.5
also ships the s3-feature engine.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
scripts/fetch_wasm_ld.sh downloads the prebuilt patched wasm-ld (release
wasm-ld-18.1.2-anyfs-r1 of xdqi/llvm-wasm, built with zig cc for
glibc>=2.11) into .toolchain/wasm-ld/, validates the 'LLD 18' version
string, and short-circuits when already present. config.sh prefers the
fetched binary when toolchains.wasm_ld is unset (build.user.toml still
wins; deps/llvm-wasm source build stays the last-resort default).
doctor.sh and build_lkl_wasm.sh hints now point at the fetch script
first; both lint gates cover the new script.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The msys.kosaka.moe repo's pkgconf package is now per-target
(msys-cross-{mingw64,mingw32,ucrt64,...}-pkgconf 2.5.1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…olchain precedence in config comments

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@xdqi xdqi merged commit 3ed5fbd into main Jun 11, 2026
5 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