ci: run the spawn-edge acceptance per leg before publish (#211) - #226
Conversation
The argv[0] drop in the driver's spawn plan (the tebako#691 failure class, fixed in tebako v2.8.23) reached a published runtime because no per-leg check exercised payload-to-payload spawn before publish. The boot smoke only proves the runtime boots; it never spawns a second payload through the driver's spawn surface. This wires a minimal spawn-edge acceptance gate into _build-platform.yml: - checks out the harness from tamatebako/ruby (ci/spawn-edge), riding the pre-existing inputs.harness_ref like the windows dogfood; - runs it per ruby line x platform, after the boot smoke and before the leg-complete marker — a red gate publishes nothing; - three arms: POSIX legs run run.sh under bash, musl legs run it inside alpine:3.21 like the musl boot smoke, windows runs run-msys.sh under msys2; - each arm resolves the leg's pin-verified tfs CLI from .build/downloads/tfs, fail-closed if absent. The harness presses a provider payload (exposes spawn-edge-echo) and a consumer payload (requires it, array-spawns it via system() and IO.popen), stages a scratch store, and boots the consumer against the freshly built runtime package. Any argv-handling regression in the spawn plan turns the child's boot into a named error and fails the leg. The macos/linux-gnu/linux-musl trigger workflows gain the harness_ref workflow_dispatch input (default main), mirroring build-windows.yml, so probe rounds can pin a harness branch. spec/build_workflow_spec.rb locks the ordering structurally: gate arms after every boot-smoke step and the harness checkout, before the leg-complete marker and the package upload, each fed RUBY_VERSION.
The first gate round failed on every POSIX leg with the probe exiting
127: the workflow handed the harness a RELATIVE RUNTIME_PKG_DIR
(runtime-packages), and the harness cd's into its scratch dir for the
proof run — the boot then could not exec the leg's runtime exe
("run.sh: line 146: runtime-packages/tebako-runtime-…: No such file or
directory"). The harness contract is fine (it documents the dir, not
its spelling); the wiring owed it an absolute path.
- POSIX + windows arms: resolve RUNTIME_PKG_DIR to an absolute path
(cd + pwd; declare/assign split so set -e still catches a missing
dir) before invoking the harness. The msys pwd yields the /d/…
spelling run-msys.sh's find/cp need.
- musl arm: pass -e RUNTIME_PKG_DIR=/mnt/w/runtime-packages — the
in-container absolute spelling of the workspace mount.
spec/build_workflow_spec.rb now asserts each gate arm absolutizes the
package dir, so the relative-spelling regression fails the suite.
Verified locally against the published v0.16.32 macos-arm64 package in
a CI-mimicking layout: the relative spelling reproduces the CI failure
verbatim (line 146, exit 127), and the absolutize line turns the same
run green (SPAWN-EDGE-ACCEPTANCE-OK 4.0.7 (macos-arm64)).
|
First gate round failed on every POSIX leg (e.g. job 109784285613, macos arm64 Ruby 4.0.7): the probe exited 127 with Root cause was on the wiring side, not the harness: the arms handed the harness a relative Fix in
Local verification against the published v0.16.32 macos-arm64 package in a CI-mimicking layout: the relative spelling reproduces the CI failure verbatim (same line, exit 127); with the absolutize line the same run is green ( |
) The second gate round failed on every linux-gnu and linux-musl leg: - linux-gnu died SILENTLY 12ms in: the leg builds in a Docker container with a container-local prefix, so the pin-verified tfs CLI that packs the env image lives at /root/.build/downloads/tfs INSIDE the container and the host workspace has no .build/downloads/tfs. The gate's find then exited 1 and, under the step's pipefail, the assignment died before the ::error:: guard could fire. - linux-musl hit the named guard (no pin-verified tfs CLI) — same root cause, surfaced because the in-alpine sh runs without pipefail. Fixes: 1. The container build step's trailing copy chain (which already copies the smoke headers/tools out to /mnt/w) now also copies /root/.build/downloads/tfs/. out to /mnt/w/.build/downloads/tfs/, preserving the <sha>/tfs-* layout the gate's find expects. Fail-closed: a miss fails the leg right there — the gate has nothing to press with otherwise. 2. The tfs resolution in all three gate arms carries || true INSIDE the command substitution (find … | head -1 || true), so a missing cache dir reaches the named ::error:: guard instead of dying silently at the assignment under pipefail. spec/build_workflow_spec.rb now asserts both: every gate arm's find is || true-guarded, and the container build step copies the tfs cache host-visibly. Local verification: shell-level repro under bash -e -o pipefail — the old spelling exits 1 with zero output (the CI signature), the new spelling reaches the named ::error:: and still picks the first match when the dir exists; yaml parse, full rspec suite (295/0), rubocop and actionlint clean (only the 4 pre-existing findings).
|
Second gate round: macOS and windows legs now pass the gate; linux-gnu died silently 12ms in and linux-musl hit the named guard ( Root cause: container legs build with a container-local prefix ( Fix in
Local verification: shell-level repro under Also confirmed from this round's logs: the windows arm passed end-to-end (its |
What
Wires the spawn-edge acceptance gate into
_build-platform.yml— the factory half of #211. The argv[0] drop in the driver's spawn plan (the tebako#691 failure class, fixed in tebako v2.8.23) reached a published runtime because no per-leg check exercised payload-to-payload spawn before publish: the boot smoke proves the runtime boots, but never spawns a second payload through the driver's spawn surface. This gate closes that hole with two tiny payloads and one boot — no metanorma compile.Companion harness PR: tamatebako/ruby#126 (the fixtures + runners under
ci/spawn-edge/).How CI exercises it
Per ruby line × platform, in
_build-platform.yml, after the boot smoke and before the leg-complete marker — a red gate publishes nothing:inputs.harness_ref(defaultmain), same as the windows dogfood.Spawn-edge acceptance (POSIX legs)—run.shunder bash;Spawn-edge acceptance (musl legs, in alpine)— same script insidealpine:3.21, mirroring the musl boot smoke;Spawn-edge acceptance (windows)—run-msys.shunder msys2.tfsCLI from.build/downloads/tfs(fail-closed if absent) and runs the harness against the leg's freshly built runtime package (RUNTIME_PKG_DIR=runtime-packages,RUBY_VERSIONfrom the matrix,TEBAKO_VERSIONfrom the chain gate).The harness presses a provider payload (exposes
spawn-edge-echo) and a consumer payload (requires it, array-spawns it viasystem()andIO.popen), stages a scratch store, and boots the consumer against the fresh runtime. Any argv-handling regression in the spawn plan turns the child's boot into a named error and fails the leg with the child's diagnostics printed.The macos/linux-gnu/linux-musl trigger workflows gain the
harness_refworkflow_dispatch input (defaultmain), mirroring build-windows.yml, so probe rounds can pin a harness branch.spec/build_workflow_spec.rblocks the ordering structurally: gate arms after every boot-smoke step and the harness checkout, before the leg-complete marker and the package upload, each fedRUBY_VERSION.Evidence
Harness verified locally against published packages on macos-arm64 (details in tamatebako/ruby#126):
SPAWN-EDGE-ACCEPTANCE-OK, exit 0;Repo suite:
bundle exec rspec— 295 examples, 0 failures (38 pre-existing platform-gated pendings);bundle exec rubocopclean on the touched spec;actionlinton the four workflow files reports only the pre-existing findings also present on main.Sequencing
tamatebako/ruby#126 should land first: the default
harness_refismain, and until the harness is on main the gate's checkout fails closed by design. The interim end-to-end proof is a probe dispatch of any build workflow withharness_ref: ci/spawn-edge-acceptance; the windows and musl arms are exercised by the PR's own legs under that pin.