test(dirty-content-fingerprint): add startup-log-unchanged consistency check

Adds src/tools/test-dirty-fingerprint-startup-log-unchanged.sh, reusing
task 6.1's already-approved isolated-git-worktree technique. Builds
scheduler and apiserver for real on a genuinely dirty tree, starts both
daemons, and proves --version/-version show a real fingerprint while
their startup logs show the unchanged, fingerprint-free original format.

Apiserver's injected fingerprint reuses scheduler's own extracted value
(same dirty tree) rather than an independent recomputation -- verified
independently by review as legitimate, not tautological: apiserver has no
fingerprint-computation code of its own (only ever receives an externally
injected value via -ldflags), and the test still exercises apiserver's
own real composeVersionDisplay()/logger.Info code on a genuinely running
binary, with a round-trip check that would catch a real strip-and-append
bug.

Verified independently by review with a fresh full run plus a separately-
authored 4-case discrimination probe outside the script's own demo block.
No leftover processes, worktrees, or build dirs after either run.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Rongsong Shen 2026-08-21 10:17:39 +00:00
parent a7d64c8225
commit a47854184a
No known key found for this signature in database
GPG key ID: 5BCDAAE0123E9FE2
2 changed files with 640 additions and 3 deletions

View file

@ -101,9 +101,10 @@
- _Requirements: 1.1, 1.2, 1.3, 1.4, 2.1_
- _Depends: 2.1, 3.2_
- [ ] 6.2 Startup-log-unchanged consistency test
- For at least one C daemon (scheduler) and apiserver, on a dirty tree, capture the startup log's version line and separately run `--version`/`-version`; assert the startup log line is byte-identical to the original `build-time-versioning` feature's already-shipped format (no fingerprint present), while `--version`/`-version` does show the fingerprint
- Observable: a passing test proves the fingerprint never leaks into the startup log for either artifact, even while the tree is dirty
- [x] 6.2 Startup-log-unchanged consistency test
- New script `src/tools/test-dirty-fingerprint-startup-log-unchanged.sh` reuses task 6.1's already-approved isolated-git-worktree technique; builds scheduler and apiserver for real on a genuinely dirty tree, starts both daemons, captures `--version`/`-version` (expect fingerprint) and each startup log (expect old fingerprint-free format)
- Apiserver's injected fingerprint value is reused from scheduler's own extracted value (same dirty tree, same content) rather than independently recomputed -- independently assessed by review as legitimate, not tautological: apiserver has no independent fingerprint-computation code of its own in production (only ever receives an externally-injected value via `-ldflags`), the script still exercises apiserver's own real, unmocked `composeVersionDisplay()`/`logger.Info` code paths on a genuinely running compiled binary, and a round-trip `api_fp == sched_fp` check would catch a real strip-and-append bug
- Observable confirmed live (independently re-verified by review, fresh full run + a separately-authored 4-case discrimination probe outside the script's own demo block): both daemons' `--version`/`-version` show real fingerprints while their startup logs show the unchanged, fingerprint-free old format; no leftover processes, worktrees, or build dirs after either run
- _Requirements: 3.1, 3.2_
- _Depends: 3.2, 4.4_

View file

@ -0,0 +1,636 @@
#!/usr/bin/env bash
# test-dirty-fingerprint-startup-log-unchanged.sh — dirty-content-fingerprint
# task 6.2 (.kiro/specs/dirty-content-fingerprint/tasks.md, Requirements
# 3.1, 3.2)
#
# MOTIVATING GAP this guards against: this feature's whole design hinges on
# a deliberate divergence between two previously-identical call sites --
# per-binary `--version`/`-version` output now composes a content-derived
# fingerprint (task 3.1-4.4), but the startup INFO log line each daemon
# already emits (shipped by the earlier, separate `build-time-versioning`
# feature) must stay completely untouched, fingerprint-free, even while the
# tree is dirty and even while that same running binary's `--version` output
# clearly does show a fingerprint. Task 6.1's sibling script
# (test-dirty-fingerprint-distinguishability.sh) already proves the
# `--version`-side behaviour end-to-end; task 6.2 is the complementary proof
# that the fingerprint never leaks into the OTHER call site -- a regression
# here (e.g. a future edit accidentally threading dirty_fingerprint into the
# startup-log composition call) would not be caught by 6.1 at all, since 6.1
# never inspects a startup log.
#
# Components checked (one C daemon, one Go binary, matching task 6.2's own
# text and mirroring test-version-startup-consistency.sh's precedent):
# - xce_scheduler (C daemon, task 3.2's `xce_compose_version_ex()` path)
# - xce_apiserver (Go binary, task 4.4's `composeVersionDisplay()` path)
#
# ISOLATION FROM AMBIENT SESSION NOISE / DIRTY-TREE CONSTRUCTION: same
# rationale and same technique as task 6.1's sibling script -- this repo is
# routinely under continuous, unrelated concurrent editing in this
# environment, so a controlled, genuinely-dirty tree cannot be reliably
# constructed by editing the ambient shared checkout. This script creates
# its own throwaway, fully-isolated `git worktree` at HEAD (detached),
# registers a `trap cleanup EXIT` BEFORE creating it (safe no-op if creation
# never happened), and makes its one deliberate edit only inside that
# isolated worktree -- the real repo's own working tree is never touched
# (verified explicitly at the end, same as task 6.1's script).
#
# Build path notes (mirrors test-version-startup-consistency.sh, task 6.1's
# sibling for build-time-versioning):
# - xce_scheduler is built via the real `xce-build.sh scheduler debug`
# entry point, INSIDE the isolated worktree, so its own real
# dirty_fingerprint computation (task 2.1) runs against genuinely
# dirty, controlled content.
# - xce_apiserver's two documented real build paths (build-server /
# go_build) are both pre-existing-broken/unusable in this sandbox
# (bug-1597 / goproxy.cn hang, see test-version-startup-consistency.sh's
# header for the full analysis, unchanged and independently
# reconfirmed here) -- this script uses the same documented workaround:
# a direct `go build -ldflags "-X main.GitCommit=... -X
# main.BuildTime=... -X main.DirtyFingerprint=..."` invocation. The
# DirtyFingerprint value injected is the REAL value extracted from the
# scheduler binary's own `--version` output captured moments earlier in
# this same run (same isolated worktree, same dirty content, same
# dirty_fingerprint algorithm task 2.1 already computed once) rather
# than a second, independently-reimplemented hash computation here --
# this is both simpler (no algorithm duplication risk) and MORE
# faithful to what a real `xce-build.sh apiserver` invocation would
# inject (the exact same computed value, reused for every component in
# one build session) than recomputing it a second time would be.
# GitCommit is composed the same way xce-build.sh's own commit_id
# computation would for a dirty tree: "<short-sha>-dirty".
#
# Usage:
# bash src/tools/test-dirty-fingerprint-startup-log-unchanged.sh [--keep-worktree]
#
# Exit code is the sole pass/fail signal (0 = pass, non-zero = a real
# fingerprint leak into a startup log, an unexpected startup-log format
# change, or a demonstration inconsistency). Two real builds (scheduler +
# apiserver's C lib dependency) plus a direct go build plus two real daemon
# startups happen -- budget several minutes of wall-clock time.
#
# Why this file lives here: same convention as test-build-version-
# regression.sh / test-version-startup-consistency.sh /
# test-dirty-fingerprint-distinguishability.sh -- a standalone,
# CLI-runnable, real-build-and-real-process regression check for a
# cross-cutting concern, deliberately NOT a meson test() (real builds +
# real daemon startups are too expensive for every `meson test` run).
#
# Revalidation triggers:
# - SCHED_VERSION_PREFIX below must match the literal prefix passed to
# xce_compose_version()/xce_compose_version_ex() in scheduler/src/main.c
# (tasks 3.2/task 3.1's XCE_DIRTY_SUFFIX).
# - APISERVER_VERSION_PREFIX below must match the literal
# "xce-apiserver " format string in
# src/frontend/apiserver/cmd/main.go's -version branch (task 4.4).
# - APISERVER_LOG_ANCHOR below must match the literal
# "********** Initialize Logger done!" log message in the same file.
# - XCE_DIRTY_SUFFIX below ("-dirty") must match the literal suffix text
# in src/lib/utils.c's XCE_DIRTY_SUFFIX and apiserver/cmd/main.go's
# composeVersionDisplay()'s strings.TrimSuffix() call.
set -euo pipefail
# ── paths ───────────────────────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
WORKTREE="$(cd "$SCRIPT_DIR/../.." && pwd)"
SCHED_VERSION_PREFIX="xce v0.0.0.1-"
APISERVER_VERSION_PREFIX="xce-apiserver "
APISERVER_LOG_ANCHOR="Initialize Logger done!"
XCE_DIRTY_SUFFIX="-dirty"
BUILD_DIR_SCHED="startuplog.sched"
BUILD_DIR_APILIB="startuplog.apilib"
README_REL="README.md"
MARKER="<!-- xce-dirty-fingerprint-test: task 6.2 startup-log-unchanged marker -->"
KEEP_WORKTREE=0
for arg in "$@"; do
case "$arg" in
--keep-worktree) KEEP_WORKTREE=1 ;;
*)
echo "Unknown argument: $arg" >&2
echo "Usage: $0 [--keep-worktree]" >&2
exit 2
;;
esac
done
# ── colour / pass-fail helpers (matches test-build-version-regression.sh /
# test-version-startup-consistency.sh / test-dirty-fingerprint-
# distinguishability.sh) ─────────────────────────────────────────────────────
green() { printf '\033[32m%s\033[0m\n' "$*"; }
red() { printf '\033[31m%s\033[0m\n' "$*"; }
yellow() { printf '\033[33m%s\033[0m\n' "$*"; }
trap 'red "FATAL: command failed (line $LINENO). See output above."' ERR
ISO_DIR=""
SCHED_PID=""
API_PID=""
SCHED_BIN=""
APISERVER_BIN=""
cleanup() {
# Kill any daemons this run started FIRST, before the leftover-process
# scan and before the isolated worktree (and its binaries) is removed.
if [ -n "$SCHED_PID" ]; then
kill -9 "$SCHED_PID" 2>/dev/null || true
wait "$SCHED_PID" 2>/dev/null || true
SCHED_PID=""
fi
if [ -n "$API_PID" ]; then
kill -9 "$API_PID" 2>/dev/null || true
wait "$API_PID" 2>/dev/null || true
API_PID=""
fi
# Leftover-process scan: resolve each /proc/<pid>/exe symlink and
# compare against the REAL built binary paths, rather than grepping
# /proc/<pid>/cmdline text or using `pgrep -f`/`pkill -f` -- this
# sandbox's Bash tool wraps every invocation as a shell command whose
# own argv can contain this very script's literal text, which makes a
# cmdline-text-based match self-match the invoking wrapper (see
# cerebrum.md bug-1576). /proc/<pid>/exe resolution has no such hazard.
if [ -n "$SCHED_BIN" ] || [ -n "$APISERVER_BIN" ]; then
for pid_dir in /proc/[0-9]*; do
[ -d "$pid_dir" ] || continue
pid="${pid_dir#/proc/}"
exe_path="$(readlink -f "$pid_dir/exe" 2>/dev/null || true)"
[ -n "$exe_path" ] || continue
if [ "$exe_path" = "$SCHED_BIN" ] || [ "$exe_path" = "$APISERVER_BIN" ]; then
red "LEFTOVER PROCESS after cleanup: pid=$pid exe=$exe_path -- force-killing."
kill -9 "$pid" 2>/dev/null || true
fi
done
fi
if [ "$KEEP_WORKTREE" = 1 ]; then
if [ -n "$ISO_DIR" ] && [ -d "$ISO_DIR" ]; then
yellow "--keep-worktree given: leaving isolated worktree in place at $ISO_DIR"
fi
return 0
fi
if [ -n "$ISO_DIR" ] && [ -d "$ISO_DIR" ]; then
git -C "$WORKTREE" worktree remove --force "$ISO_DIR" 2>/dev/null || {
rm -rf "$ISO_DIR"
git -C "$WORKTREE" worktree prune 2>/dev/null || true
}
fi
}
trap cleanup EXIT
# ── field extraction (verbatim, proven helpers from
# test-version-startup-consistency.sh) ──────────────────────────────────────
#
# extract_composed_fields TEXT PREFIX
# TEXT is either a raw --version string, or a larger line of text (e.g. a
# full log line) that CONTAINS "<PREFIX><commit> built <build_time>"
# somewhere in it. Prints "<commit>\t<build_time>" on stdout; returns 1 if
# PREFIX is not found at all.
extract_composed_fields() {
local text="$1" prefix="$2" rest commit build_time
rest="${text##*"$prefix"}"
if [ "$rest" = "$text" ]; then
return 1
fi
rest="$(printf '%s' "$rest" | tr -d '\r\n')"
commit="${rest%% built *}"
build_time="${rest#* built }"
printf '%s\t%s\n' "$commit" "$build_time"
}
# extract_zap_fields LOGLINE
# apiserver's startup log line carries the two values as structured zap
# JSON fields ("gitCommit": "...", "buildTime": "...") rather than a
# composed string. Prints "<commit>\t<build_time>"; returns 1 if either
# field is absent. Deliberately POSIX BRE `sed` -- see
# test-version-startup-consistency.sh's identical helper for why (this
# script's real invocation environment resolves plain `grep`/`sed` to
# BusyBox with no -P support).
extract_zap_fields() {
local logline="$1" commit build_time
commit="$(printf '%s' "$logline" | sed -n 's/.*"gitCommit":[[:space:]]*"\([^"]*\)".*/\1/p')"
build_time="$(printf '%s' "$logline" | sed -n 's/.*"buildTime":[[:space:]]*"\([^"]*\)".*/\1/p')"
if [ -z "$commit" ] || [ -z "$build_time" ]; then
return 1
fi
printf '%s\t%s\n' "$commit" "$build_time"
}
# ── the two validating functions: one confirms --version DOES show a real
# fingerprint (not the old "-dirty" format), the other confirms the startup
# log does NOT (byte-identical commit field to the original shipped format,
# and the fingerprint string absent anywhere in the line). Both the real
# per-binary checks below AND demonstrate_detection_capability() (the
# discriminating-power proof) exercise these exact functions. ─────────────
# assert_version_shows_fingerprint LABEL RAW COMPONENT(sched|apiserver) BASE_COMMIT
# On success: prints PASS, sets FP_OUT to the extracted fingerprint suffix,
# returns 0. On failure: prints FAIL with a clear reason, resets FP_OUT to
# "", returns 1.
FP_OUT=""
assert_version_shows_fingerprint() {
local label="$1" raw="$2" component="$3" base_commit="$4"
local prefix fields commit_field
FP_OUT=""
case "$component" in
sched) prefix="$SCHED_VERSION_PREFIX" ;;
apiserver) prefix="$APISERVER_VERSION_PREFIX" ;;
*)
red "FAIL [$label]: unknown component '$component'"
return 1
;;
esac
if ! fields="$(extract_composed_fields "$raw" "$prefix")"; then
red "FAIL [$label]: --version/-version output did not contain the expected prefix '$prefix'. Raw: [$raw]"
return 1
fi
commit_field="${fields%%$'\t'*}"
if [ "$commit_field" = "${base_commit}${XCE_DIRTY_SUFFIX}" ]; then
red "FAIL [$label]: --version/-version shows the OLD '$XCE_DIRTY_SUFFIX' format ('$commit_field') -- expected a real content-derived fingerprint suffix on this deliberately dirty tree. Raw: [$raw]"
return 1
fi
if [ "$commit_field" = "$base_commit" ]; then
red "FAIL [$label]: --version/-version shows the bare commit id with no suffix at all ('$commit_field') -- expected a fingerprint suffix on this deliberately dirty tree. Raw: [$raw]"
return 1
fi
case "$commit_field" in
"${base_commit}-"*)
FP_OUT="${commit_field#"${base_commit}-"}"
;;
*)
red "FAIL [$label]: commit field '$commit_field' does not match expected base commit '$base_commit' with a fingerprint suffix. Raw: [$raw]"
return 1
;;
esac
green "PASS [$label]: --version/-version shows a real fingerprint suffix (not '$XCE_DIRTY_SUFFIX') -- commit_field='$commit_field' fingerprint='$FP_OUT'"
return 0
}
# assert_startup_log_unchanged LABEL LOG_LINE COMPONENT(sched|apiserver) BASE_COMMIT FINGERPRINT
# The exact comparison Requirement 3.2 requires: the startup log's commit
# field must be byte-identical to the original build-time-versioning
# feature's already-shipped dirty-tree format ("<base_commit>-dirty"), and
# the fingerprint string must not appear ANYWHERE in the log line (not just
# absent from the commit field -- a leak via some other field would be just
# as real a regression).
assert_startup_log_unchanged() {
local label="$1" log_line="$2" component="$3" base_commit="$4" fingerprint="$5"
local fields commit_field expected_commit="${base_commit}${XCE_DIRTY_SUFFIX}"
local ok=1
case "$component" in
sched)
if ! fields="$(extract_composed_fields "$log_line" "$SCHED_VERSION_PREFIX")"; then
red "FAIL [$label]: scheduler startup log line did not contain the expected prefix '$SCHED_VERSION_PREFIX'. Line: [$log_line]"
return 1
fi
;;
apiserver)
if ! fields="$(extract_zap_fields "$log_line")"; then
red "FAIL [$label]: apiserver startup log line did not contain both gitCommit/buildTime zap fields. Line: [$log_line]"
return 1
fi
;;
*)
red "FAIL [$label]: unknown component '$component'"
return 1
;;
esac
commit_field="${fields%%$'\t'*}"
if [ "$commit_field" != "$expected_commit" ]; then
red "FAIL [$label]: startup log commit field is '$commit_field' -- expected it byte-identical to the original build-time-versioning feature's already-shipped format '$expected_commit' (unchanged, no fingerprint). Line: [$log_line]"
ok=0
fi
if [ -n "$fingerprint" ]; then
case "$log_line" in
*"$fingerprint"*)
red "FAIL [$label]: startup log line contains the fingerprint '$fingerprint' -- it must NEVER appear in the startup log (Requirement 3.2). Line: [$log_line]"
ok=0
;;
esac
fi
if [ "$ok" = 1 ]; then
green "PASS [$label]: startup log unchanged from original format (commit field='$expected_commit'), fingerprint '$fingerprint' does not leak into it."
return 0
fi
return 1
}
# Proves assert_startup_log_unchanged() -- the EXACT function the real
# checks below use -- would catch every required leak/regression mode:
# fingerprint leaking straight into the commit field, fingerprint leaking
# via some OTHER field on the same line while the commit field itself looks
# fine, and (for completeness) a commit field that silently drifted from
# the original shipped format even without the fingerprint being involved
# at all. Also proves genuinely well-formed, non-leaking lines still PASS.
demonstrate_detection_capability() {
local base="$1" real_fp="$2"
yellow "=== DEMONSTRATION: proving assert_startup_log_unchanged() catches every required leak/regression mode ==="
yellow "--- (a) scheduler: fingerprint leaks straight into the commit field (synthetic) ---"
local bad_sched_commit="2026-08-21 12:00:00 INFO xce v0.0.0.1-${base}-${real_fp} built 2026-08-21T12:00:00Z scheduler starting, election candidates=3"
if assert_startup_log_unchanged "DEMO-sched-commit-leak" "$bad_sched_commit" "sched" "$base" "$real_fp"; then
red "DEMONSTRATION FAILED: incorrectly PASSED a scheduler startup log line whose commit field carries the fingerprint."
exit 1
fi
green "Confirmed: a fingerprint leaking straight into the scheduler startup log's commit field is correctly rejected."
yellow "--- (b) apiserver: fingerprint leaks straight into the gitCommit field (synthetic) ---"
local bad_api_commit="{\"level\":\"INFO\",\"message\":\"********** Initialize Logger done!\",\"gitCommit\": \"${base}-${real_fp}\",\"buildTime\": \"2026-08-21T12:00:00Z\"}"
if assert_startup_log_unchanged "DEMO-api-commit-leak" "$bad_api_commit" "apiserver" "$base" "$real_fp"; then
red "DEMONSTRATION FAILED: incorrectly PASSED an apiserver startup log line whose gitCommit field carries the fingerprint."
exit 1
fi
green "Confirmed: a fingerprint leaking straight into the apiserver startup log's gitCommit field is correctly rejected."
yellow "--- (c) apiserver: gitCommit field itself is unchanged ('-dirty'), but the fingerprint leaks via a DIFFERENT field on the same line (synthetic) ---"
local bad_api_elsewhere="{\"level\":\"INFO\",\"message\":\"********** Initialize Logger done!\",\"gitCommit\": \"${base}-dirty\",\"buildTime\": \"2026-08-21T12:00:00Z\",\"extra\":\"${real_fp}\"}"
if assert_startup_log_unchanged "DEMO-api-elsewhere-leak" "$bad_api_elsewhere" "apiserver" "$base" "$real_fp"; then
red "DEMONSTRATION FAILED: incorrectly PASSED a log line where the fingerprint leaked via an unrelated field, even though gitCommit itself looked fine."
exit 1
fi
green "Confirmed: a fingerprint leaking anywhere in the line (not just the commit field) is correctly rejected."
yellow "--- (d) scheduler: commit field silently drifted from the original shipped format, independent of any fingerprint leak ---"
local bad_sched_drift="2026-08-21 12:00:00 INFO xce v0.0.0.1-${base} built 2026-08-21T12:00:00Z scheduler starting, election candidates=3"
if assert_startup_log_unchanged "DEMO-sched-format-drift" "$bad_sched_drift" "sched" "$base" "$real_fp"; then
red "DEMONSTRATION FAILED: incorrectly PASSED a scheduler startup log line whose commit field is missing the original '-dirty' suffix entirely."
exit 1
fi
green "Confirmed: a commit field that drifted from the original shipped format is correctly rejected, even with no fingerprint present at all."
yellow "--- (e) sanity: genuinely unchanged, non-leaking log lines must still PASS ---"
local good_sched="2026-08-21 12:00:00 INFO xce v0.0.0.1-${base}-dirty built 2026-08-21T12:00:00Z scheduler starting, election candidates=3"
if ! assert_startup_log_unchanged "DEMO-sanity-sched" "$good_sched" "sched" "$base" "$real_fp"; then
red "DEMONSTRATION FAILED: incorrectly FAILED a genuinely unchanged scheduler startup log line."
exit 1
fi
local good_api="{\"level\":\"INFO\",\"message\":\"********** Initialize Logger done!\",\"gitCommit\": \"${base}-dirty\",\"buildTime\": \"2026-08-21T12:00:00Z\"}"
if ! assert_startup_log_unchanged "DEMO-sanity-api" "$good_api" "apiserver" "$base" "$real_fp"; then
red "DEMONSTRATION FAILED: incorrectly FAILED a genuinely unchanged apiserver startup log line."
exit 1
fi
green "Confirmed: genuinely unchanged, non-leaking startup log lines pass for both components."
green "=== DEMONSTRATION complete: a fingerprint leak (into the commit field, or via any other field on the line) and a silent commit-field format drift are ALL rejected; genuinely unchanged log lines are correctly accepted ==="
}
overall_pass=1
# ══════════════════════════════════════════════════════════════════════════
# Set up the isolated worktree and make it genuinely dirty
# ══════════════════════════════════════════════════════════════════════════
ISO_DIR="$(mktemp -u "${TMPDIR:-/tmp}/xce-startuplog-fp-worktree.XXXXXX")"
yellow "=== Creating isolated git worktree at HEAD (detached), immune to ambient concurrent edits in $WORKTREE: $ISO_DIR ==="
git -C "$WORKTREE" worktree add --detach "$ISO_DIR" HEAD >/dev/null
ISO_SRC="$ISO_DIR/src"
if [ ! -f "$ISO_SRC/tools/xce-build.sh" ]; then
red "FATAL: isolated worktree does not contain $ISO_SRC/tools/xce-build.sh -- unexpected checkout layout."
exit 1
fi
untracked_at_start="$(git -C "$ISO_DIR" status --porcelain)"
if [ -n "$untracked_at_start" ]; then
red "FATAL: freshly-created isolated worktree is not clean at creation time -- this should be impossible for a fresh 'git worktree add'. Status:"
printf '%s\n' "$untracked_at_start" >&2
exit 1
fi
base_commit="$(git -C "$ISO_DIR" rev-parse --short HEAD)"
yellow "Isolated worktree base commit (clean, by construction): $base_commit"
readme_path="$ISO_DIR/$README_REL"
if [ ! -f "$readme_path" ]; then
red "FATAL: expected $readme_path to exist -- pick a different safe edit target if this repo's layout changed."
exit 1
fi
printf '\n%s\n' "$MARKER" >> "$readme_path"
dirty_status="$(git -C "$ISO_DIR" status --porcelain)"
if [ -z "$dirty_status" ]; then
red "FATAL: isolated worktree is still clean after the deliberate edit -- cannot construct the dirty-tree scenario this test requires."
exit 1
fi
yellow "Isolated worktree is now genuinely dirty (by construction, one controlled edit to $README_REL):"
printf '%s\n' "$dirty_status"
# ══════════════════════════════════════════════════════════════════════════
# xce_scheduler (C daemon): build, capture --version, start, capture startup
# log line
# ══════════════════════════════════════════════════════════════════════════
yellow "=== [scheduler] nix develop $ISO_SRC --command bash $ISO_SRC/tools/xce-build.sh scheduler debug $BUILD_DIR_SCHED ==="
nix develop "$ISO_SRC" --command bash "$ISO_SRC/tools/xce-build.sh" scheduler debug "$BUILD_DIR_SCHED"
SCHED_BIN="$ISO_SRC/$BUILD_DIR_SCHED/scheduler/xce_scheduler"
if [ ! -x "$SCHED_BIN" ]; then
red "FATAL: expected binary not found/executable at $SCHED_BIN after a successful-looking build."
exit 1
fi
sched_version_raw="$("$SCHED_BIN" --version 2>&1)"
yellow "[scheduler] --version raw output: [$sched_version_raw]"
sched_fp=""
if assert_version_shows_fingerprint "scheduler-version" "$sched_version_raw" "sched" "$base_commit"; then
sched_fp="$FP_OUT"
else
overall_pass=0
fi
sched_log="$ISO_DIR/scheduler.log"
rm -f "$sched_log"
"$SCHED_BIN" --logfile "$sched_log" >"$ISO_DIR/scheduler.stdout" 2>&1 &
SCHED_PID=$!
sched_log_line=""
waited=0
while [ "$waited" -lt 20 ]; do
if [ -f "$sched_log" ] && grep -qF -- "$SCHED_VERSION_PREFIX" "$sched_log" 2>/dev/null; then
sched_log_line="$(grep -F -- "$SCHED_VERSION_PREFIX" "$sched_log" | tail -1)"
break
fi
sleep 0.5
waited=$((waited + 1))
done
kill -9 "$SCHED_PID" 2>/dev/null || true
wait "$SCHED_PID" 2>/dev/null || true
SCHED_PID=""
if [ -z "$sched_log_line" ]; then
red "FAIL [scheduler-startup-log]: startup log never produced a line containing '$SCHED_VERSION_PREFIX' within 10s. Log contents:"
cat "$sched_log" 2>/dev/null >&2 || true
overall_pass=0
else
yellow "[scheduler] startup log line: [$sched_log_line]"
if [ -n "$sched_fp" ]; then
if ! assert_startup_log_unchanged "scheduler-startup-log" "$sched_log_line" "sched" "$base_commit" "$sched_fp"; then
overall_pass=0
fi
else
yellow "[scheduler] skipping startup-log-unchanged assertion: no real fingerprint was captured from --version above."
overall_pass=0
fi
fi
# ══════════════════════════════════════════════════════════════════════════
# xce_apiserver (Go binary): build lib dep, build via direct go build
# reusing the scheduler's real fingerprint, capture -version, start, capture
# startup log line
# ══════════════════════════════════════════════════════════════════════════
yellow "=== [apiserver] building lib dependency: nix develop $ISO_SRC --command bash $ISO_SRC/tools/xce-build.sh lib debug $BUILD_DIR_APILIB ==="
nix develop "$ISO_SRC" --command bash "$ISO_SRC/tools/xce-build.sh" lib debug "$BUILD_DIR_APILIB"
APILIB_DIR="$ISO_SRC/$BUILD_DIR_APILIB"
if [ ! -f "$APILIB_DIR/lib/libxce.a" ]; then
red "FATAL: expected $APILIB_DIR/lib/libxce.a not found after 'lib' build."
exit 1
fi
API_COMMIT_ID="${base_commit}${XCE_DIRTY_SUFFIX}"
API_BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
API_DIRTY_FINGERPRINT="$sched_fp"
if [ -z "$API_DIRTY_FINGERPRINT" ]; then
red "FATAL: no real fingerprint available from the scheduler build above to inject into the apiserver build -- cannot construct this test's required scenario (a real, non-degraded fingerprint)."
exit 1
fi
yellow "=== [apiserver] go build (direct invocation, see header comment re: bug-1597 / go_build's goproxy.cn hang in this sandbox) -- commit_id=$API_COMMIT_ID build_time=$API_BUILD_TIME dirty_fingerprint=$API_DIRTY_FINGERPRINT ==="
APISERVER_BIN="$APILIB_DIR/frontend/apiserver/xce_apiserver"
mkdir -p "$(dirname "$APISERVER_BIN")"
nix develop "$ISO_SRC" --command bash -c "
set -e
cd '$ISO_SRC/frontend/apiserver'
CGO_CFLAGS=\"-g \$(pkg-config --cflags librtti c-capnproto openssl tinylog) -I$ISO_SRC/include -I$APILIB_DIR/lib\"
CGO_LDFLAGS=\"-g \$(pkg-config --libs librtti c-capnproto openssl tinylog) -lm -lrt -L$APILIB_DIR/lib -lxce\"
GOPROXY=off CGO_ENABLED=1 CGO_CFLAGS=\"\$CGO_CFLAGS\" CGO_LDFLAGS=\"\$CGO_LDFLAGS\" \
go build -mod=readonly -gcflags 'all=-N -l' -ldflags \"-X main.GitCommit=$API_COMMIT_ID -X main.BuildTime=$API_BUILD_TIME -X main.DirtyFingerprint=$API_DIRTY_FINGERPRINT\" \
-o $APISERVER_BIN ./cmd/main.go
"
if [ ! -x "$APISERVER_BIN" ]; then
red "FATAL: expected binary not found/executable at $APISERVER_BIN after a successful-looking build."
exit 1
fi
api_version_raw="$("$APISERVER_BIN" -version 2>&1)"
yellow "[apiserver] -version raw output: [$api_version_raw]"
api_fp=""
if assert_version_shows_fingerprint "apiserver-version" "$api_version_raw" "apiserver" "$base_commit"; then
api_fp="$FP_OUT"
else
overall_pass=0
fi
if [ -n "$api_fp" ] && [ "$api_fp" != "$sched_fp" ]; then
red "FAIL [apiserver-version]: injected fingerprint '$sched_fp' but -version reports '$api_fp' -- the injection did not round-trip correctly."
overall_pass=0
fi
# Minimal topdir so apiserver reaches its startup INFO log line without a
# real controller cluster -- identical technique to
# test-version-startup-consistency.sh (deploy-ha.sh's own lightweight
# stub-mode setup): config/{auth.yaml,rbac.yaml} copied verbatim,
# app.yaml with its trailing `backend:` section stripped so main.go boots
# in permanent stub mode, and a freshly-generated logger.yaml pointing at
# this run's own ISO_DIR.
APISERVER_SRC_CONFIG="$ISO_SRC/frontend/apiserver/config"
API_TOPDIR="$ISO_DIR/apiserver_topdir"
mkdir -p "$API_TOPDIR/config"
cp "$APISERVER_SRC_CONFIG/auth.yaml" "$API_TOPDIR/config/auth.yaml"
cp "$APISERVER_SRC_CONFIG/rbac.yaml" "$API_TOPDIR/config/rbac.yaml"
sed '/^backend:/,$d' "$APISERVER_SRC_CONFIG/app.yaml" > "$API_TOPDIR/config/app.yaml"
cat > "$API_TOPDIR/config/logger.yaml" << EOF
logging:
level: "debug"
encoding: console
outputPath: "$ISO_DIR/apiserver.log"
file:
maxSize: 100
maxBackups: 10
maxAge: 14
compress: true
encoderConfig:
messageKey: "message"
levelKey: "level"
timeKey: "timestamp"
nameKey: "logger"
callerKey: "caller"
functionKey: "func"
stacktraceKey: "stacktrace"
timeEncoder: "ISO8601"
levelEncoder: "capital"
EOF
api_log="$ISO_DIR/apiserver.log"
rm -f "$api_log"
"$APISERVER_BIN" -d "$API_TOPDIR" >"$ISO_DIR/apiserver.stdout" 2>&1 &
API_PID=$!
api_log_line=""
waited=0
while [ "$waited" -lt 20 ]; do
if [ -f "$api_log" ] && grep -qF -- "$APISERVER_LOG_ANCHOR" "$api_log" 2>/dev/null; then
api_log_line="$(grep -F -- "$APISERVER_LOG_ANCHOR" "$api_log" | head -1)"
break
fi
sleep 0.5
waited=$((waited + 1))
done
kill -9 "$API_PID" 2>/dev/null || true
wait "$API_PID" 2>/dev/null || true
API_PID=""
if [ -z "$api_log_line" ]; then
red "FAIL [apiserver-startup-log]: startup log never produced a line containing '$APISERVER_LOG_ANCHOR' within 10s. Log contents:"
cat "$api_log" 2>/dev/null >&2 || true
cat "$ISO_DIR/apiserver.stdout" 2>/dev/null >&2 || true
overall_pass=0
else
yellow "[apiserver] startup log line: [$api_log_line]"
if [ -n "$api_fp" ]; then
if ! assert_startup_log_unchanged "apiserver-startup-log" "$api_log_line" "apiserver" "$base_commit" "$api_fp"; then
overall_pass=0
fi
else
yellow "[apiserver] skipping startup-log-unchanged assertion: no real fingerprint was captured from -version above."
overall_pass=0
fi
fi
# ── discrimination demonstration ────────────────────────────────────────────
demo_fp="${sched_fp:-$api_fp}"
if [ -n "$demo_fp" ]; then
demonstrate_detection_capability "$base_commit" "$demo_fp"
else
red "FATAL: neither binary produced a usable real fingerprint to demonstrate the comparison logic against."
exit 1
fi
# ── constraint check: the SHARED working tree's own README.md must be
# completely untouched by this run (all edits happened only inside the
# throwaway isolated worktree) ──────────────────────────────────────────────
shared_readme_diff="$(git -C "$WORKTREE" status --porcelain -- "$README_REL")"
if [ -n "$shared_readme_diff" ]; then
red "FAIL: the SHARED working tree's own $README_REL was modified by this run -- it must never be touched (all edits must land only in the isolated worktree). git status: [$shared_readme_diff]"
overall_pass=0
else
green "PASS: shared working tree's own $README_REL is untouched (git status --porcelain empty)."
fi
if [ "$overall_pass" = 1 ]; then
green "=== ALL CHECKS PASS: xce_scheduler and xce_apiserver both show a real content-derived fingerprint in --version/-version on a dirty tree, while their startup log stays byte-identical to the original build-time-versioning format with no fingerprint leak ==="
exit 0
else
red "=== FAILURE DETECTED: see FAIL line(s) above ==="
exit 1
fi