Files
gstack/scripts/build-cso.sh
T
4dfd83bf8c v1.91.60.0 fix(windows): setup degrades instead of dying on a CSO failure, detects Smart App Control, Consolas terminal font (#3079)
* Fix spaced-out terminal text on Windows: put a Latin monospace before the CJK fallbacks

On Windows none of the Mac/Linux monospace fonts in the stack are
installed, so the first font that resolves is Malgun Gothic (the Windows
Korean UI font). xterm.js sizes its character cell from that font's
advance width, and Malgun Gothic advances Latin glyphs in a full-width
CJK cell — doubling every cell and rendering the whole terminal (and the
sidepanel mono UI) as spaced-out text.

Insert Consolas (present on every Windows install) and the Cascadia
faces ahead of the CJK fonts in both the xterm fontFamily and the
--font-mono CSS var. The CJK fonts stay last for actual CJK coverage.
No bundled font, no user install needed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GKj8Nr8T4GvryTcWBJRMyC

* fix(windows): every compiled binary answers --version before any side effect

browse --version started a server and exited 1, pdf printed usage and
exited 1, gstack-global-discover rejected the flag, and find-browse ran
discovery. A launch probe for Smart App Control (#2595) needs one
side-effect-free command that exits 0 on a healthy build, so browse,
find-browse, make-pdf and gstack-global-discover now answer --version
first (design already did).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(extension): pin Consolas ahead of the CJK fallbacks in both mono stacks (#2287)

Regression test for @tomfluff's fix: the xterm fontFamily and --font-mono
must list Consolas before Noto Sans Mono CJK KR and Malgun Gothic, or
Windows renders the sidebar terminal double-width.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(cso): record each CSO build's outcome and stop arming the parent trap across the native publisher exec (#3071)

scripts/build-cso.sh now writes bin/.gstack-cso-build-result: result,
failed stage (build or publish), reason, this revision, the revision of
the generation still installed, whether its launcher is usable, and the
diagnostic log. An 'interrupted' entry is written before each stage so a
killed run is visible. A failed publish that restores the old generation
records it as still installed.

Before exec'ing the native publisher, the parent re-checks the locker,
clears its EXIT trap and enables execfail: the locked child's own trap
owns the stage. On Git Bash an exec of a native program may not replace
the shell, so the parent's trap could otherwise delete the stage under
the running child (the vanishing files in #3071; root cause unproven).
If the exec itself fails the parent cleans the stage and records it.

The Windows build prefers PowerShell 7 (pwsh) over Windows PowerShell
5.1, which crashed outright on the reporter's machine.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(setup): an optional CSO build or publish failure no longer aborts setup (#3071)

CSO was optional at probe time but fatal at build time: scripts/build.sh
ran build-cso.sh under set -e before the browse server bundle and the
completion stamp, and setup exited when the CSO artifact set was
incomplete. A failed CSO step now prints 'CSO unavailable: the <stage>
step failed' with its log (bin/.gstack-cso-build.log) and retry, and the
build and setup finish. GSTACK_STRICT_BUILD=1 keeps it fatal; the free-tests,
make-pdf-gate and windows-setup-e2e builds set it, so regressions still
fail CI.

setup's summary reads the build-result record instead of the pre-build
availability flag: probe failures keep their prerequisite instruction;
build and publish failures name the stage, the diagnostic and
'bun run build:cso && ./setup', and a restored earlier helper is
reported as 'previous CSO kept: <revision>'. setup records probe
failures in the same file.

The Windows prerequisite probe prefers pwsh, and a probe that fails for
any reason other than a missing toolchain or a compile error keeps its
exit status and first output line (windows-toolchain-probe) instead of
being labeled windows-msvc-toolchain. Reported and diagnosed by
@dviolante.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* feat(windows): detect Smart App Control-blocked binaries in setup and gstack-doctor (#2595, #2124)

Smart App Control refuses the five unsigned binaries gstack compiles
locally, and Git Bash reports it as 'Permission denied', so setup's
[ -x ] gate passed and skills failed later. bin/gstack-launch-probe.sh
runs each binary's side-effect-free --version and classifies it:
native, blocked (spawn error, exit 126/127, or an application-control
message), broken (launched, exited non-zero) or missing. It shows the
raw first output line, since the exact Windows text is unverified.

On Windows, setup names each blocked binary and the skills that need it,
cites #2595, says which shell ran setup and whether WSL is installed,
gives today's workarounds (WSL, or turning Smart App Control off), how to
undo, and the new troubleshooting section. gstack-doctor's browse row now
says 'blocked at launch' instead of passing, a binaries row covers the
other four, and a cso row reads the build-result record.

windows-setup-e2e builds with GSTACK_STRICT_BUILD=1, checks all five
binaries answer --version, and adds two forced CSO failures: fatal under
strict mode, and a completed setup that reports the failure and a doctor
cso warning under GSTACK_STRICT_BUILD=0.

No shim or launcher: it could not be called from PowerShell and would
load the caller's project .env/bunfig. Signing is the follow-up in
TODOS.md. Diagnosis credit: @knetics9000 (#2595, #2596), @smartjelic-sys
(#2595), @pstilwell90 (#2124), @cko32002 (#2265), @salluexez (#2127).

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* docs: Windows Smart App Control known issue, optional CSO failures, doctor rows

README's Windows section says /cso failures no longer stop setup
(GSTACK_STRICT_BUILD=1 keeps them fatal), documents Smart App Control as
a known issue with today's workarounds (WSL, or turning it off) and the
Consolas fix. troubleshooting.md gains a Windows section for blocked
binaries and an entry for CSO build/publish failures. TODOS.md replaces
the Node CLI lane item with Windows code signing (needs a signing
account), the #3071 root-cause follow-up and SAC text verification.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* fix(setup): never write the CSO build record into a source checkout that needed no build

Setup wrote bin/.gstack-cso-build-result for every probe-stage outcome,
so a run that built nothing still changed the source checkout's bin/
(caught by setup-codex-scope's unchanged-source checks). A missing
prerequisite now comes from the run's own probe, as before, and setup
only removes a stale record alongside the removed helper.
scripts/build-cso.sh remains the only writer. The doctor's cso row
reports 'native helper not built' with no record.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* test(extension): move the Consolas font-stack check into sidebar-ux (#2287)

The standalone file read extension sources without importing product
code, which the test-of-test ratchet rejects; sidebar-ux already owns
the sidepanel.css structural checks.

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

* v1.91.43.0 fix(windows): setup degrades instead of dying on a CSO failure, detects Smart App Control, Consolas terminal font

Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>

---------

Co-authored-by: Yotam <yotsurfing@gmail.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: capy-ai[bot] <230910855+capy-ai[bot]@users.noreply.github.com>
2026-10-08 15:43:32 +00:00

437 lines
22 KiB
Bash

#!/usr/bin/env bash
set -eu
CSO_BUILD_ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
cd "$CSO_BUILD_ROOT"
umask 077
BUN_CMD="${BUN_CMD:-bun}"
CSO_CC="${CSO_CC:-cc}"
CSO_EXE=""
case "$(uname -s)" in
MINGW*|MSYS*|CYGWIN*|Windows_NT) CSO_EXE=".exe" ;;
esac
CSO_FINAL_CORE="$CSO_BUILD_ROOT/bin/gstack-cso-core$CSO_EXE"
CSO_FINAL_LAUNCHER="$CSO_BUILD_ROOT/bin/gstack-cso-launcher$CSO_EXE"
CSO_FINAL_WATCHDOG="$CSO_BUILD_ROOT/bin/gstack-cso-watchdog"
CSO_FINAL_GENERATION="$CSO_BUILD_ROOT/bin/.gstack-cso-generation"
# Build-result record read by setup's summary and gstack-doctor (#3071).
CSO_RESULT_FILE="$CSO_BUILD_ROOT/bin/.gstack-cso-build-result"
CSO_RESULT_RECORDING=0
CSO_RESULT_STAGE=build
CSO_REVISION=""
CSO_PREVIOUS_INSTALLED=""
CSO_STAGE=""
CSO_PUBLISHING=0
CSO_COMMITTED=0
CSO_OLD_COHERENT=0
CSO_OLD_LAUNCHER_MOVED=0
CSO_OLD_CORE_MOVED=0
CSO_OLD_WATCHDOG_MOVED=0
CSO_OLD_GENERATION_MOVED=0
CSO_NEW_LAUNCHER_INSTALLED=0
CSO_NEW_CORE_INSTALLED=0
CSO_NEW_WATCHDOG_INSTALLED=0
CSO_NEW_GENERATION_INSTALLED=0
CSO_EVALUATION_UNIT=""
cso_checkpoint() {
[ "${GSTACK_CSO_BUILD_TESTING:-0}" = 1 ] || return 0
if [ "${GSTACK_CSO_BUILD_TEST_FAIL_AFTER:-}" = "$1" ]; then
echo "Injected CSO build failure after $1" >&2
exit 86
fi
if [ "${GSTACK_CSO_BUILD_TEST_KILL_AFTER:-}" = "$1" ]; then
kill -KILL "$$"
fi
if [ "${GSTACK_CSO_BUILD_TEST_REMOVE_LOCKER_AFTER:-}" = "$1" ]; then
rm -f "$CSO_STAGE_LOCKER"
fi
}
cso_record_field() { sed -n "s/^$1=//p" "$CSO_RESULT_FILE" 2>/dev/null | head -1; }
# One record per run: result (ok/failed), stage (build/publish), reason, this
# checkout's revision, the revision of the CSO generation still installed, and
# whether that launcher is usable. A SIGKILL leaves the last "interrupted" entry.
cso_write_result() {
launcher=no
if cso_valid_artifact "$CSO_FINAL_LAUNCHER" && cso_valid_artifact "$CSO_FINAL_CORE"; then launcher=yes; fi
installed=""
if [ "$1" = ok ]; then installed=$CSO_REVISION
elif [ "$launcher" = yes ]; then installed=${CSO_PREVIOUS_INSTALLED:-an earlier build}; fi
# Written in place: publication's signal tests count on mv moving artifacts only.
printf 'result=%s\nstage=%s\nreason=%s\nrevision=%s\ninstalled=%s\nlauncher=%s\ndiagnostic=%s\n' \
"$1" "$2" "$3" "$CSO_REVISION" "$installed" "$launcher" "${GSTACK_CSO_BUILD_LOG:-the scripts/build-cso.sh output}" > "$CSO_RESULT_FILE" || true
}
cso_restore_previous() {
rollback_ok=1
# A public launcher is removed before any supporting artifact is restored.
if [ "$CSO_NEW_LAUNCHER_INSTALLED" -eq 1 ]; then rm -f "$CSO_FINAL_LAUNCHER" || rollback_ok=0; fi
if [ "$CSO_NEW_CORE_INSTALLED" -eq 1 ]; then rm -f "$CSO_FINAL_CORE" || rollback_ok=0; fi
if [ -z "$CSO_EXE" ] && [ "$CSO_NEW_WATCHDOG_INSTALLED" -eq 1 ]; then rm -f "$CSO_FINAL_WATCHDOG" || rollback_ok=0; fi
if [ "$CSO_NEW_GENERATION_INSTALLED" -eq 1 ];then rm -f "$CSO_FINAL_GENERATION"||rollback_ok=0;fi
if [ "$CSO_OLD_CORE_MOVED" -eq 1 ]; then
if [ "${GSTACK_CSO_BUILD_TESTING:-0}" = 1 ] && [ "${GSTACK_CSO_BUILD_TEST_FAIL_RESTORE:-}" = core ];then rollback_ok=0
elif cso_present "$CSO_STAGE/previous/core";then cso_move "$CSO_STAGE/previous/core" "$CSO_FINAL_CORE" || rollback_ok=0
elif ! cso_valid_artifact "$CSO_FINAL_CORE";then rollback_ok=0;fi
fi
if [ -z "$CSO_EXE" ] && [ "$CSO_OLD_WATCHDOG_MOVED" -eq 1 ]; then
if cso_present "$CSO_STAGE/previous/watchdog";then cso_move "$CSO_STAGE/previous/watchdog" "$CSO_FINAL_WATCHDOG" || rollback_ok=0
elif ! cso_valid_artifact "$CSO_FINAL_WATCHDOG";then rollback_ok=0;fi
fi
if [ "$CSO_OLD_GENERATION_MOVED" -eq 1 ];then
if cso_present "$CSO_STAGE/previous/generation";then cso_move "$CSO_STAGE/previous/generation" "$CSO_FINAL_GENERATION"||rollback_ok=0
elif ! cso_valid_generation "$CSO_FINAL_GENERATION";then rollback_ok=0;fi
fi
# Restore the old launcher last, and only beside the complete old support set.
if [ "$CSO_OLD_LAUNCHER_MOVED" -eq 1 ]; then
if [ "$rollback_ok" -eq 1 ] && [ "$CSO_OLD_COHERENT" -eq 1 ]; then
if cso_present "$CSO_STAGE/previous/launcher";then cso_move "$CSO_STAGE/previous/launcher" "$CSO_FINAL_LAUNCHER" || rollback_ok=0
elif ! cso_valid_artifact "$CSO_FINAL_LAUNCHER";then rollback_ok=0;fi
else
rollback_ok=0
fi
fi
if [ "$rollback_ok" -ne 1 ]; then
rm -f "$CSO_FINAL_LAUNCHER"
: > "$CSO_STAGE/.retain-recovery"
echo "CSO build rollback was incomplete; the public launcher is withheld and recovery files remain in $CSO_STAGE" >&2
return 1
fi
return 0
}
cso_cleanup() {
status=$1
trap - EXIT
trap '' HUP INT TERM
set +e
cleanup_stage=1
if [ "$CSO_PUBLISHING" -eq 1 ] && [ "$CSO_COMMITTED" -ne 1 ]; then
if ! cso_restore_previous; then status=1; cleanup_stage=0; fi
fi
if [ "$cleanup_stage" -eq 1 ] && [ -n "$CSO_STAGE" ] && [ ! -f "$CSO_STAGE/.retain-recovery" ]; then rm -rf "$CSO_STAGE"; fi
if [ -n "$CSO_EVALUATION_UNIT" ]; then rm -rf "$CSO_EVALUATION_UNIT"; fi
if [ "$CSO_RESULT_RECORDING" -eq 1 ]; then
if [ "$status" -eq 0 ] && [ "$CSO_COMMITTED" -eq 1 ]; then cso_write_result ok publish committed
else cso_write_result failed "$CSO_RESULT_STAGE" "exit $status"; fi
fi
exit "$status"
}
trap 'cso_cleanup $?' EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM
cso_valid_artifact() { [ -f "$1" ] && [ ! -L "$1" ] && [ -x "$1" ]; }
cso_present() { [ -e "$1" ] || [ -L "$1" ]; }
cso_valid_generation() {
[ -f "$1" ]&&[ ! -L "$1" ]||return 1
generation_size="$(wc -c < "$1" 2>/dev/null||true)"
generation_size="${generation_size//[[:space:]]/}"
[ "$generation_size" = 65 ]||return 1
generation="$(cat "$1" 2>/dev/null)";[ "${#generation}" -eq 64 ]||return 1
case "$generation" in *[!a-f0-9]*) return 1;;esac
}
cso_move() {
move_attempts=0
while ! mv "$1" "$2";do
move_attempts=$((move_attempts+1))
if [ -z "$CSO_EXE" ] || [ "$move_attempts" -ge 50 ];then return 1;fi
sleep .02
done
}
cso_sign_macos_artifact() {
artifact=$1
hardened=$2
if [ "$(uname -m)" = arm64 ]; then
sig_end="$(otool -l "$artifact" 2>/dev/null | awk '/LC_CODE_SIGNATURE/{f=1} f&&/dataoff/{o=$2} f&&/datasize/{print o+$2; exit}')"
file_size="$(stat -f%z "$artifact" 2>/dev/null || true)"
if [ -n "$sig_end" ] && [ -n "$file_size" ] && [ "$sig_end" -gt 0 ] 2>/dev/null && [ "$sig_end" -lt "$file_size" ] 2>/dev/null; then
truncated="$CSO_STAGE/.codesign-truncated"
head -c "$sig_end" "$artifact" > "$truncated"
cat "$truncated" > "$artifact"
rm -f "$truncated"
chmod +x "$artifact"
fi
fi
codesign --remove-signature "$artifact" 2>/dev/null || true
if [ "$hardened" -eq 1 ]; then codesign --force --sign - --options runtime "$artifact"
else codesign --force --sign - "$artifact"; fi
codesign --verify --strict "$artifact"
if [ "$hardened" -eq 1 ]; then codesign -d --verbose=4 "$artifact" 2>&1 | grep -q 'runtime'; fi
}
cso_sha256() {
if [ -x /usr/bin/sha256sum ];then digest="$(/usr/bin/sha256sum "$1")";digest=${digest%% *}
elif [ -x /usr/bin/shasum ];then digest="$(/usr/bin/shasum -a 256 "$1")";digest=${digest%% *}
else echo 'CSO build requires sha256sum or shasum to bind the native launcher to its core.' >&2;return 1;fi
case "$digest" in *[!a-f0-9]*|'') echo 'CSO build received an invalid core SHA-256 digest.' >&2;return 1;;esac
[ "${#digest}" -eq 64 ] || { echo 'CSO build received an invalid core SHA-256 digest.' >&2;return 1; }
printf '%s\n' "$digest"
}
# Bun's compile-time switches stop configuration discovery in the audited
# working directory before the helper can apply its own policy.
cso_compile_bun() {
"$BUN_CMD" build --compile \
--no-compile-autoload-dotenv \
--no-compile-autoload-bunfig \
--no-compile-autoload-tsconfig \
--no-compile-autoload-package-json \
"$1" --outfile "$2"
chmod +x "$2"
}
cso_compile_watchdog() {
"$CSO_CC" -std=c11 -D_POSIX_C_SOURCE=200809L -O2 -Wall -Wextra \
lib/cso/watchdog.c -o "$1"
chmod +x "$1"
}
cso_compile_posix_launcher() {
CSO_LAUNCHER_FLAGS=""
case "$(uname -s)" in
Linux) CSO_LAUNCHER_FLAGS="-static" ;;
# Local ad-hoc signatures do not distinguish the launcher from another
# ad-hoc dylib. This section makes dyld prune DYLD_* before constructors.
Darwin) CSO_LAUNCHER_FLAGS="-Wl,-sectcreate,__RESTRICT,__restrict,/dev/null" ;;
esac
# shellcheck disable=SC2086 -- the optional platform flags are fixed literals.
"$CSO_CC" -std=c11 -D_POSIX_C_SOURCE=200809L -O2 -Wall -Wextra $CSO_LAUNCHER_FLAGS \
"-DGSTACK_CSO_CORE_SHA256=\"$2\"" lib/cso/launcher.c -o "$1"
}
# An evaluation candidate embeds a cso-eval-* runtime catalog in a private
# five-artifact producer unit outside this checkout. It never touches bin/, and
# its .gstack-cso-evaluation manifest marks it evaluation-only for setup.
cso_build_evaluation_candidate() {
[ "$#" -eq 4 ] && [ "$1" = --evaluation-candidate ] && [ "$3" = --output ] || {
echo 'Usage: build-cso.sh --evaluation-candidate <cso-eval-catalog.json> --output <new-directory>' >&2;return 64; }
[ -z "$CSO_EXE" ] || { echo 'CSO evaluation candidates are built only on Linux and macOS.' >&2;return 69; }
command -v "$CSO_CC" >/dev/null 2>&1 || { echo 'CSO build requires a C compiler (cc/clang/gcc) for the trusted launcher and watchdog.' >&2;return 1; }
candidate_catalog=$2
[ -f "$candidate_catalog" ] && [ ! -L "$candidate_catalog" ] || { echo 'CSO evaluation catalog must be one regular file.' >&2;return 66; }
candidate_parent="$(cd "$(dirname "$4")" 2>/dev/null && pwd -P)" || { echo 'CSO evaluation output parent must be an existing directory.' >&2;return 73; }
candidate_output="$candidate_parent/$(basename "$4")"
if cso_present "$candidate_output";then echo 'CSO evaluation output must be a new directory.' >&2;return 73;fi
case "$candidate_output/" in "$CSO_BUILD_ROOT"/*) echo 'CSO evaluation output must be outside the gstack checkout, where setup and distribution never look.' >&2;return 73;;esac
candidate_revision="$(grep -o '"revision": *"cso-eval-[0-9]\{1,\}-[a-f0-9]\{12\}"' "$candidate_catalog" | head -n 1 | sed 's/.*"\(cso-eval-[^"]*\)"$/\1/' || true)"
[ -n "$candidate_revision" ] || { echo 'CSO evaluation candidates require a cso-eval-<runId>-<sha12> catalog revision.' >&2;return 65; }
CSO_STAGE="$(mktemp -d "${TMPDIR:-/tmp}/gstack-cso-evaluation.XXXXXX")"
CSO_EVALUATION_UNIT="$(mktemp -d "$candidate_parent/.gstack-cso-evaluation.XXXXXX")"
source_root="$CSO_STAGE/source"
mkdir -m 700 "$source_root" "$source_root/test"
cp -R lib scripts "$source_root/"
cp -R test/helpers "$source_root/test/"
cp "$candidate_catalog" "$source_root/lib/cso/runtime-catalog.json"
(cd "$source_root" && "$BUN_CMD" scripts/cso-runtime-promotion.ts check-evaluation lib/cso/runtime-catalog.json)
(cd "$source_root" && cso_compile_bun lib/cso/cli.ts "$CSO_EVALUATION_UNIT/gstack-cso-core")
(cd "$source_root" && cso_compile_bun scripts/cso-eval-producer.ts "$CSO_EVALUATION_UNIT/cso-eval-producer")
cso_compile_watchdog "$CSO_EVALUATION_UNIT/gstack-cso-watchdog"
if [ "$(uname -s)" = Darwin ]; then
cso_sign_macos_artifact "$CSO_EVALUATION_UNIT/gstack-cso-core" 0
cso_sign_macos_artifact "$CSO_EVALUATION_UNIT/gstack-cso-watchdog" 0
fi
candidate_core_sha256="$(cso_sha256 "$CSO_EVALUATION_UNIT/gstack-cso-core")"
cso_compile_posix_launcher "$CSO_EVALUATION_UNIT/gstack-cso-launcher" "$candidate_core_sha256"
chmod +x "$CSO_EVALUATION_UNIT/gstack-cso-launcher"
if [ "$(uname -s)" = Darwin ];then cso_sign_macos_artifact "$CSO_EVALUATION_UNIT/gstack-cso-launcher" 1;fi
printf '%s\n' "$candidate_core_sha256" > "$CSO_EVALUATION_UNIT/.gstack-cso-generation"
printf '{"schemaVersion":1,"evaluationOnly":true,"runtimeCatalogRevision":"%s","runtimeCatalogSha256":"%s","coreSha256":"%s"}\n' \
"$candidate_revision" "$(cso_sha256 "$source_root/lib/cso/runtime-catalog.json")" "$candidate_core_sha256" \
> "$CSO_EVALUATION_UNIT/.gstack-cso-evaluation"
for artifact in gstack-cso-core gstack-cso-launcher gstack-cso-watchdog cso-eval-producer;do
cso_valid_artifact "$CSO_EVALUATION_UNIT/$artifact" || { echo "Evaluation $artifact is not one executable regular file." >&2;return 1; }
done
cso_valid_generation "$CSO_EVALUATION_UNIT/.gstack-cso-generation" || { echo 'Evaluation generation manifest is invalid.' >&2;return 1; }
[ "$(cso_sha256 "$CSO_EVALUATION_UNIT/gstack-cso-core")" = "$candidate_core_sha256" ] || { echo 'Evaluation core changed after launcher binding.' >&2;return 1; }
"$CSO_EVALUATION_UNIT/gstack-cso-launcher" --version >/dev/null
chmod 0555 "$CSO_EVALUATION_UNIT"/gstack-cso-core "$CSO_EVALUATION_UNIT"/gstack-cso-launcher "$CSO_EVALUATION_UNIT"/gstack-cso-watchdog "$CSO_EVALUATION_UNIT"/cso-eval-producer
chmod 0444 "$CSO_EVALUATION_UNIT/.gstack-cso-generation" "$CSO_EVALUATION_UNIT/.gstack-cso-evaluation"
chmod 0755 "$CSO_EVALUATION_UNIT"
mv "$CSO_EVALUATION_UNIT" "$candidate_output"
CSO_EVALUATION_UNIT=""
printf '{"output":"%s","revision":"%s","evaluationOnly":true}\n' "$candidate_output" "$candidate_revision"
}
cso_publish_locked() {
[ "${GSTACK_CSO_PUBLISH_LOCKED:-}" = 1 ] || { echo 'CSO publication requires the native publisher lock.' >&2;return 73; }
[ "$#" -eq 1 ] && [ -d "$1" ] && [ ! -L "$1" ] || { echo 'CSO publication stage is invalid.' >&2;return 69; }
CSO_STAGE="$(cd "$1" && pwd -P)"
case "$CSO_STAGE" in "$CSO_BUILD_ROOT"/bin/.gstack-cso-stage.*) ;; *) echo 'CSO publication stage escaped the trusted bin directory.' >&2;return 69;;esac
CSO_STAGE_CORE="$CSO_STAGE/gstack-cso-core$CSO_EXE"
CSO_STAGE_LAUNCHER="$CSO_STAGE/gstack-cso-launcher$CSO_EXE"
CSO_STAGE_WATCHDOG="$CSO_STAGE/gstack-cso-watchdog"
CSO_STAGE_GENERATION="$CSO_STAGE/.gstack-cso-generation"
cso_valid_artifact "$CSO_STAGE_CORE" || { echo 'Staged CSO core is not one executable regular file.' >&2;return 1; }
cso_valid_artifact "$CSO_STAGE_LAUNCHER" || { echo 'Staged CSO launcher is not one executable regular file.' >&2;return 1; }
if [ -z "$CSO_EXE" ];then cso_valid_artifact "$CSO_STAGE_WATCHDOG" || { echo 'Staged CSO watchdog is not one executable regular file.' >&2;return 1; };fi
cso_valid_generation "$CSO_STAGE_GENERATION"||{ echo 'Staged CSO generation manifest is invalid.' >&2;return 1; }
old_present=0
for artifact in "$CSO_FINAL_LAUNCHER" "$CSO_FINAL_CORE";do
if cso_present "$artifact";then cso_valid_artifact "$artifact" || { echo 'Existing CSO artifacts contain an unsafe file.' >&2;return 1; };old_present=$((old_present+1));fi
done
if [ -z "$CSO_EXE" ]&&cso_present "$CSO_FINAL_WATCHDOG";then cso_valid_artifact "$CSO_FINAL_WATCHDOG" || { echo 'Existing CSO artifacts contain an unsafe file.' >&2;return 1; };old_present=$((old_present+1));fi
if cso_present "$CSO_FINAL_GENERATION";then cso_valid_generation "$CSO_FINAL_GENERATION"||{ echo 'Existing CSO generation manifest is unsafe.' >&2;return 1; };old_present=$((old_present+1));fi
expected=3;[ -n "$CSO_EXE" ]||expected=4
if [ "$old_present" -eq "$expected" ];then CSO_OLD_COHERENT=1;fi
CSO_PUBLISHING=1
# Ignored signals remain ignored in mv children. SIGKILL is handled by the
# launcher-first withdrawal and launcher-last commit order.
trap '' HUP INT TERM
if cso_present "$CSO_FINAL_LAUNCHER";then
CSO_OLD_LAUNCHER_MOVED=1
cso_move "$CSO_FINAL_LAUNCHER" "$CSO_STAGE/previous/launcher"
cso_checkpoint withdraw-launcher
fi
if cso_present "$CSO_FINAL_CORE";then CSO_OLD_CORE_MOVED=1;cso_move "$CSO_FINAL_CORE" "$CSO_STAGE/previous/core";fi
if [ -z "$CSO_EXE" ]&&cso_present "$CSO_FINAL_WATCHDOG";then CSO_OLD_WATCHDOG_MOVED=1;cso_move "$CSO_FINAL_WATCHDOG" "$CSO_STAGE/previous/watchdog";fi
if cso_present "$CSO_FINAL_GENERATION";then CSO_OLD_GENERATION_MOVED=1;cso_move "$CSO_FINAL_GENERATION" "$CSO_STAGE/previous/generation";fi
CSO_NEW_CORE_INSTALLED=1
cso_move "$CSO_STAGE_CORE" "$CSO_FINAL_CORE"
cso_checkpoint publish-core
if [ -z "$CSO_EXE" ];then
CSO_NEW_WATCHDOG_INSTALLED=1
cso_move "$CSO_STAGE_WATCHDOG" "$CSO_FINAL_WATCHDOG"
cso_checkpoint publish-watchdog
fi
CSO_NEW_GENERATION_INSTALLED=1
cso_move "$CSO_STAGE_GENERATION" "$CSO_FINAL_GENERATION"
cso_checkpoint before-publish-launcher
CSO_NEW_LAUNCHER_INSTALLED=1
cso_move "$CSO_STAGE_LAUNCHER" "$CSO_FINAL_LAUNCHER"
cso_checkpoint publish-launcher
CSO_COMMITTED=1
}
cso_start_result() {
CSO_RESULT_RECORDING=1
CSO_RESULT_STAGE=$1
CSO_REVISION="$(head -1 "$CSO_BUILD_ROOT/VERSION" 2>/dev/null | tr -cd '0-9A-Za-z.+-')"
revision_sha="$(git -C "$CSO_BUILD_ROOT" rev-parse --short=12 HEAD 2>/dev/null | tr -cd '0-9a-f')"
CSO_REVISION="${CSO_REVISION:-unknown}${revision_sha:+ ($revision_sha)}"
CSO_PREVIOUS_INSTALLED="$(cso_record_field installed)"
}
if [ "${1:-}" = __publish_locked ];then
shift
cso_start_result publish
cso_publish_locked "$@"
exit 0
fi
if [ "${1:-}" = --evaluation-candidate ];then
cso_build_evaluation_candidate "$@"
exit 0
fi
[ "$#" -eq 0 ] || { echo 'Usage: build-cso.sh [--evaluation-candidate <cso-eval-catalog.json> --output <new-directory>]' >&2;exit 64; }
cso_start_result build
cso_write_result failed build interrupted
# Distribution builds embed only the committed catalog, never an evaluation one.
if [ -f lib/cso/runtime-catalog.json ] && grep -q '"revision": *"cso-eval-' lib/cso/runtime-catalog.json;then
echo 'lib/cso/runtime-catalog.json is an evaluation-only catalog; distribution builds refuse it. Restore the committed catalog.' >&2
exit 65
fi
# This entrypoint can be run directly. Once it may publish a CSO generation,
# the previous whole-build completion proof no longer describes all outputs.
rm -f "$CSO_BUILD_ROOT/browse/dist/.build-complete"
if ! command -v "$CSO_CC" >/dev/null 2>&1 && [ -z "$CSO_EXE" ]; then
echo 'CSO build requires a C compiler (cc/clang/gcc) for the trusted launcher and watchdog.' >&2
exit 1
fi
if [ "$(uname -s)" = Darwin ] && ! command -v codesign >/dev/null 2>&1; then
echo 'CSO build requires macOS codesign so staged artifacts can be verified before publication.' >&2
exit 1
fi
CSO_STAGE="$(mktemp -d "$CSO_BUILD_ROOT/bin/.gstack-cso-stage.XXXXXX")"
CSO_STAGE_CORE="$CSO_STAGE/gstack-cso-core$CSO_EXE"
CSO_STAGE_LAUNCHER="$CSO_STAGE/gstack-cso-launcher$CSO_EXE"
CSO_STAGE_WATCHDOG="$CSO_STAGE/gstack-cso-watchdog"
CSO_STAGE_GENERATION="$CSO_STAGE/.gstack-cso-generation"
CSO_STAGE_LOCKER="$CSO_STAGE/gstack-cso-publish-lock$CSO_EXE"
mkdir -m 700 "$CSO_STAGE/previous"
if [ -n "$CSO_EXE" ];then : > "$CSO_STAGE/.gstack-cso-generation.lock";fi
# These are compile-time switches. Bun otherwise discovers configuration in
# the audited working directory before the helper can apply its own policy.
cso_compile_bun lib/cso/cli.ts "$CSO_STAGE_CORE"
cso_checkpoint core
# POSIX process groups are required by the detached watchdog. Windows keeps
# comprehensive execution unavailable instead of substituting a weaker timer.
if [ -z "$CSO_EXE" ]; then
cso_compile_watchdog "$CSO_STAGE_WATCHDOG"
cso_checkpoint watchdog
fi
if [ "$(uname -s)" = Darwin ]; then
cso_sign_macos_artifact "$CSO_STAGE_CORE" 0
cso_sign_macos_artifact "$CSO_STAGE_WATCHDOG" 0
fi
CSO_CORE_SHA256="$(cso_sha256 "$CSO_STAGE_CORE")"
printf '%s\n' "$CSO_CORE_SHA256" > "$CSO_STAGE_GENERATION"
if [ -n "$CSO_EXE" ];then
# PowerShell 7 first: Windows PowerShell 5.1 can crash outright (#3071).
CSO_POWERSHELL="$(command -v pwsh 2>/dev/null || command -v powershell.exe 2>/dev/null || true)"
if [ -z "$CSO_POWERSHELL" ]||! command -v cygpath >/dev/null 2>&1;then
echo 'CSO Windows build requires Git Bash and PowerShell (pwsh or Windows PowerShell) with MSVC Build Tools.' >&2;exit 1
fi
CSO_WINDOWS_GIT="$(type -P git 2>/dev/null || true)"
[ -n "$CSO_WINDOWS_GIT" ] && [ -f "$CSO_WINDOWS_GIT" ] || { echo 'CSO Windows build requires the Git for Windows git.exe selected by Git Bash.' >&2;exit 1; }
"$CSO_POWERSHELL" -NoProfile -NonInteractive -ExecutionPolicy Bypass \
-File "$(cygpath -w "$CSO_BUILD_ROOT/scripts/build-cso-windows.ps1")" \
-RepoRoot "$(cygpath -w "$CSO_BUILD_ROOT")" \
-OutputPath "$(cygpath -w "$CSO_STAGE_LAUNCHER")" \
-LockOutputPath "$(cygpath -w "$CSO_STAGE_LOCKER")" \
-CoreSha256 "$CSO_CORE_SHA256" \
-GitExePath "$(cygpath -aw "$CSO_WINDOWS_GIT")"
else
cso_compile_posix_launcher "$CSO_STAGE_LAUNCHER" "$CSO_CORE_SHA256"
"$CSO_CC" -std=c11 -D_POSIX_C_SOURCE=200809L -O2 -Wall -Wextra lib/cso/publish-lock.c -o "$CSO_STAGE_LOCKER"
fi
chmod +x "$CSO_STAGE_LAUNCHER" "$CSO_STAGE_LOCKER"
cso_checkpoint launcher
cso_checkpoint publish-lock
if [ "$(uname -s)" = Darwin ];then cso_sign_macos_artifact "$CSO_STAGE_LAUNCHER" 1;fi
cso_valid_artifact "$CSO_STAGE_CORE" || { echo 'Staged CSO core is not one executable regular file.' >&2; exit 1; }
cso_valid_artifact "$CSO_STAGE_LAUNCHER" || { echo 'Staged CSO launcher is not one executable regular file.' >&2; exit 1; }
cso_valid_artifact "$CSO_STAGE_LOCKER" || { echo 'Staged CSO publisher lock is not one executable regular file.' >&2; exit 1; }
if [ -z "$CSO_EXE" ]; then cso_valid_artifact "$CSO_STAGE_WATCHDOG" || { echo 'Staged CSO watchdog is not one executable regular file.' >&2; exit 1; }; fi
cso_valid_generation "$CSO_STAGE_GENERATION"||{ echo 'Staged CSO generation manifest is invalid.' >&2;exit 1; }
[ "$(cso_sha256 "$CSO_STAGE_CORE")" = "$CSO_CORE_SHA256" ] || { echo 'Staged CSO core changed after launcher binding.' >&2;exit 1; }
"$CSO_STAGE_LAUNCHER" --version >/dev/null
cso_checkpoint validated
if [ -n "$CSO_EXE" ];then
[ -f /usr/bin/bash.exe ] || { echo 'CSO publication requires the Git Bash executable.' >&2;exit 1; }
CSO_PUBLISH_SHELL="$(cygpath -aw /usr/bin/bash.exe)"
else
case "${BASH:-}" in /*) CSO_PUBLISH_SHELL=$BASH;; *) echo 'CSO publication requires an absolute Bash executable.' >&2;exit 1;;esac
fi
cso_valid_artifact "$CSO_STAGE_LOCKER" || { echo 'Staged CSO publisher lock is not one executable regular file.' >&2; exit 1; }
CSO_RESULT_STAGE=publish
cso_write_result failed publish interrupted
cso_checkpoint before-exec
# From here the locked child owns the stage: its own EXIT trap restores and
# cleans it. Git Bash's exec of a native program may not replace this shell,
# so this trap must not be armed under the running child (#3071). If the exec
# itself fails, clean up here.
set +e
shopt -s execfail
trap - EXIT
exec "$CSO_STAGE_LOCKER" "$CSO_BUILD_ROOT/bin" "$CSO_PUBLISH_SHELL" "$CSO_BUILD_ROOT/scripts/build-cso.sh" __publish_locked "$CSO_STAGE"
cso_exec_status=$?
echo "CSO publication could not start $CSO_STAGE_LOCKER (exit $cso_exec_status)." >&2
cso_cleanup "$cso_exec_status"