Files
codebase-memory-mcp/.github/workflows/_test.yml
T
Martin Vogel d6c8d1dd0a fix(msan): split the known-red block by cause; fix the RSS one properly
The x86-64 leg runs this lane without exclusions on purpose, to settle
which limits are architectural. It has now run, and it disproves part of
what the previous block asserted. That block claimed all seven excluded
suites "abort with stack-overflow". Five do. Two do not, and lumping
them together hid two different problems behind one rationale.

  (A) stack-overflow, five suites: grammar_regression grammar_labels
      pipeline lang_contract grammar_probe_e. Confirmed on BOTH arm64 and
      x86-64 (CI logged 5), so it is not the aarch64 artifact an earlier
      note claimed. The recursion guards bound DEPTH while the resource
      exhausted is BYTES; that follow-up stands unchanged.

  (B) cli: no overflow at all. On x86-64 it runs to completion, 253
      passed / 5 failed, every failure in the install or activation path,
      with "agent_config agent=OpenClaw op=mcp_install" above them. Green
      on every other venue. MSan reported zero use-of-uninitialized-value
      in it, so the exclusion costs no uninit coverage. Recorded as
      undiagnosed rather than guessed at: the local lane is arm64 where
      these suites hit (A) before reaching this code, so there is no
      faithful venue to iterate in and each attempt is a ~30min round
      trip. That is a follow-up with an owner, not a dismissal.

  (C) incremental: an RSS BUDGET failure, 3054MB against a 2304MB limit
      -- not an overflow either. MSan maps shadow (and origin) memory for
      every allocation, so the budget cannot separate a leak from shadow.
      FIXED rather than excluded: the assertion is now skipped under
      __has_feature(memory_sanitizer) only, so the guard keeps its teeth
      on every other platform, where inflating the budget would have
      blinded it. The suite stays IN the lane.

Verified: with (C) fixed, incremental is 163 passed / 0 failed and ZERO
stack-overflows under the local arm64 MSan container -- so it never
belonged in the overflow list on either architecture.

msan-lane.sh no longer forces MSAN_EXCLUDE empty. That override existed
to ask the architectural question; it is answered, and keeping it would
re-red the gate for causes already recorded. Both venues now read the one
authoritative list in scripts/msan.sh, which still warns loudly that the
lane is partial.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-04 04:58:44 +02:00

562 lines
26 KiB
YAML

# Reusable: unit + integration tests on all platforms
name: Test
on:
workflow_call:
inputs:
skip_perf:
description: 'Skip incremental perf tests (phases 2-7)'
type: boolean
default: true
broad_platforms:
description: 'Test the broad platform matrix (older glibc + extra OS versions) instead of the core set'
type: boolean
default: false
shard_suites:
description: 'Split each C-suite leg across parallel shard jobs (ubuntu x3, windows x2); off = one job per leg, byte-identical to the pre-shard topology'
type: boolean
default: false
permissions:
contents: read
jobs:
# Emit the platform matrices as JSON. The CORE set is the default (fast,
# unchanged); the BROAD set adds extra free runners (older glibc /
# additional OS versions) for a wider "does it build everywhere" picture.
setup-matrix:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
unix: ${{ steps.set.outputs.unix }}
windows: ${{ steps.set.outputs.windows }}
steps:
- name: Compute matrices
id: set
env:
BROAD: ${{ inputs.broad_platforms }}
SHARDS: ${{ inputs.shard_suites }}
run: |
CORE_UNIX='[
{"os":"ubuntu-latest","cc":"gcc","cxx":"g++"},
{"os":"ubuntu-24.04-arm","cc":"gcc","cxx":"g++"},
{"os":"macos-14","cc":"cc","cxx":"c++"},
{"os":"macos-15-intel","cc":"cc","cxx":"c++"}
]'
# Broad matrix legs are REQUIRED gates (no optional/continue-on-error
# escape hatches): the release dry run must be all-green, every platform.
BROAD_UNIX='[
{"os":"ubuntu-22.04","cc":"gcc","cxx":"g++"},
{"os":"ubuntu-22.04-arm","cc":"gcc","cxx":"g++"},
{"os":"macos-15","cc":"cc","cxx":"c++"}
]'
# Each Windows leg pins the msys2 environment + package arch to the
# RUNNER architecture so the build is native, never emulated:
# x86-64 runners -> CLANG64 (mingw-w64-clang-x86_64-*)
# ARM64 runner -> CLANGARM64 (mingw-w64-clang-aarch64-*)
# windows-11-arm previously used the x86-64 CLANG64 toolchain, so its
# binary ran under Windows-on-ARM x86-64 emulation and ASan's function
# interception crashed (interception_win: unhandled instruction). With
# the native ARM64 toolchain ASan instruments native ARM64 code, so it
# is a real (non-optional) gate, not a tolerated emulated-flake.
CORE_WIN='[{"os":"windows-latest","msystem":"CLANG64","pkg":"x86_64"}]'
BROAD_WIN='[{"os":"windows-2025","msystem":"CLANG64","pkg":"x86_64"},{"os":"windows-11-arm","msystem":"CLANGARM64","pkg":"aarch64"}]'
if [ "$BROAD" = "true" ]; then
UNIX=$(jq -cn --argjson a "$CORE_UNIX" --argjson b "$BROAD_UNIX" '$a + $b')
WIN=$(jq -cn --argjson a "$CORE_WIN" --argjson b "$BROAD_WIN" '$a + $b')
else
UNIX=$(jq -cn --argjson a "$CORE_UNIX" '$a')
WIN=$(jq -cn --argjson a "$CORE_WIN" '$a')
fi
# Suite sharding (opt-in via the shard_suites input): each platform's
# C-suite run is split across N runner jobs. CBM_TEST_SHARD="i/N"
# slices deterministically inside run-tests-parallel.sh; each
# shard's union guard proves it ran exactly its slice; shards are
# minted HERE from one expansion so a missing shard cannot be
# configured silently; and the shard-completeness job re-proves the
# cross-shard union at runtime. Ubuntu runners are plentiful (3
# shards); Windows gets 2 (every extra shard re-pays ~5 min of
# MSYS2 setup); macOS stays at 1 — mac legs are not the critical
# path and mac runner concurrency ceilings are the tightest.
if [ "$SHARDS" = "true" ]; then
UNIX=$(jq -c '[.[] | . as $e
| (if ($e.os | startswith("ubuntu")) then 3 else 1 end) as $n
| range(1; $n + 1) as $i
| $e + {shard: "\($i)/\($n)"}]' <<< "$UNIX")
WIN=$(jq -c '[.[] | . as $e
| range(1; 3) as $i
| $e + {shard: "\($i)/2"}]' <<< "$WIN")
fi
echo "unix={\"include\":$UNIX}" >> "$GITHUB_OUTPUT"
echo "windows={\"include\":$WIN}" >> "$GITHUB_OUTPUT"
test-unix:
needs: setup-matrix
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.setup-matrix.outputs.unix) }}
runs-on: ${{ matrix.os }}
# Broad-only legs (extra OS versions) are informational: visible but
# non-blocking, so a flaky/less-common runner can't block a release.
continue-on-error: ${{ matrix.optional == true }}
timeout-minutes: 240
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Install deps (Ubuntu)
if: startsWith(matrix.os, 'ubuntu')
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev ccache
- name: Install ccache (macOS)
if: startsWith(matrix.os, 'macos')
run: command -v ccache >/dev/null 2>&1 || brew install ccache
# Verified compiler cache: CCACHE_COMPILERCHECK=content keys every entry
# on the compiler-binary CONTENT plus the fully preprocessed input, so a
# hit is provably the identical compilation — a stale or foreign cache
# can only miss, never return wrong output (see scripts/env.sh).
# Keys embed github.ref; the final ref-less restore key falls back to
# the newest cache GitHub's scoping permits (a PR can only ever see its
# own ref and the base branch, and base-branch caches are written only
# by base-branch runs), so a fresh PR starts from main's warm cache
# instead of building cold. Content verification is what makes that
# safe; the fallback buys ~8-12 minutes on every first-of-ref build.
- name: Compiler cache (content-verified, restore)
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ github.workspace }}/.ccache
key: ccache-test-${{ matrix.os }}-${{ matrix.cc }}-${{ github.ref }}-${{ github.sha }}
# The cross-ref fallback is what keeps fresh PRs warm: without it a
# new PR always started from a cold cache (observed 0% hit legs).
# ccache verifies content hashes itself, so a stale cache can only
# miss, never mis-compile.
restore-keys: |
ccache-test-${{ matrix.os }}-${{ matrix.cc }}-${{ github.ref }}-
ccache-test-${{ matrix.os }}-${{ matrix.cc }}-
- name: Test
run: scripts/test.sh CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
env:
CBM_TEST_SHARD: ${{ matrix.shard }}
CBM_TEST_LEG: ${{ matrix.os }}-${{ matrix.cc }}
CBM_SKIP_PERF: ${{ inputs.skip_perf && '1' || '' }}
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_MAXSIZE: 1500M
# Every shard of a leg builds the identical runner, so one shard's
# cache carries the complete object set: shard 1 (or the unsharded
# job) saves, the rest only restore — same save volume as before
# sharding, no quota thrash, no same-key save races. Saving on failure
# too keeps red iterations warm.
- name: Shard manifest (completeness proof)
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: shard-manifest-${{ matrix.os }}-${{ matrix.cc }}-${{ strategy.job-index }}
path: build/c/test-logs/shard-manifest.txt
# The harness writes the manifest BEFORE any suite runs, so a
# missing file here means the job died before the harness started
# (contract step / build) — that failure is already the job's red;
# warn instead of stacking a second error on top of it. The
# shard-completeness job still gates the cross-shard union.
if-no-files-found: warn
- name: Compiler cache (save)
if: always() && (matrix.shard == null || startsWith(matrix.shard, '1/'))
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ github.workspace }}/.ccache
key: ccache-test-${{ matrix.os }}-${{ matrix.cc }}-${{ github.ref }}-${{ github.sha }}
- name: ccache stats
if: always()
run: ccache -s || true
env:
CCACHE_DIR: ${{ github.workspace }}/.ccache
# ThreadSanitizer data-race gate. The threaded production surfaces (watcher,
# subprocess, httpd, pipeline, mcp, worker pool, diagnostics, the runnable
# daemon-coordination paths) plus allocator concurrency all run under TSan
# here — see TEST_TSAN_SUITES in Makefile.cbm. Runs on both Linux
# architectures AND native ARM64 macOS: the threading code is shared, so a
# race is usually caught on all three, but scheduler differences mean each
# platform can surface a race the others miss. Windows has no TSan runtime on
# any toolchain (documented irreducible gap); this shared-code coverage is
# its substitute.
# Memory-diagnostics lane (user decision 2026-08-03: runner cost accepted).
# Newest pinned LLVM sanitizer runtime + straighter stacks over the full
# parallel suite — catches what an older compiler-rt can miss. Same
# canonical scripts/test.sh wave as every other leg.
test-diag:
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Install deps
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev ccache
- name: Install LLVM 22 (pinned diagnostic toolchain)
run: |
wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/trusted.gpg.d/apt.llvm.org.asc
echo "deb http://apt.llvm.org/noble/ llvm-toolchain-noble-22 main" | sudo tee /etc/apt/sources.list.d/llvm-22.list
sudo apt-get update
sudo apt-get install -y clang-22
- name: Compiler cache (content-verified, restore)
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ github.workspace }}/.ccache
key: ccache-diag-${{ github.ref }}-${{ github.sha }}
restore-keys: |
ccache-diag-${{ github.ref }}-
ccache-diag-
# Also enables the ASan checks that are off by default in every other
# lane: stack-use-after-return, stack-use-after-scope, and strict string
# checks. Mirrors `make -f Makefile.cbm diag`, including its reasoning
# for leaving detect_invalid_pointer_pairs out.
- name: Test (clang-22, ASan+UBSan + off-by-default ASan checks)
env:
ASAN_OPTIONS: "detect_stack_use_after_return=1:strict_string_checks=1:detect_stack_use_after_scope=1"
run: |
scripts/test.sh CC=clang-22 CXX=clang++-22 \
SANITIZE="-fsanitize=address,undefined -fno-omit-frame-pointer -fno-optimize-sibling-calls"
- name: Compiler cache (save)
if: always()
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ github.workspace }}/.ccache
key: ccache-diag-${{ github.ref }}-${{ github.sha }}
# MemorySanitizer lane (stage 2). Uninitialized READS are the one
# memory-error class no other lane covers dynamically. The image carries
# MSan-instrumented libc++/libc++abi/libunwind + zlib (vendored C deps
# instrument in-tree); scripts/msan.sh drives the build and the suite.
#
# THIS JOB IS THE AUTHORITATIVE VENUE for the lane. It previously ran WITHOUT
# the script's default exclusions to settle whether those were an aarch64
# artifact — the local ladder cannot answer that, having no faithful x86-64
# emulation. It has now answered it, and the answer SPLIT the list:
#
# - the five deep-recursion suites overflow their thread stacks on x86-64
# too, so that limit is NOT architectural;
# - `cli` does not overflow here at all — it fails in the install path for
# an unrelated, still-undiagnosed reason;
# - `incremental` was a shadow-memory RSS artifact, now fixed in the test
# itself rather than skipped, so it stays in the lane.
#
# Per-cause detail lives in scripts/msan.sh, which is the single authoritative
# list both venues now share. Zero use-of-uninitialized-value findings were
# reported across the lane on either architecture.
test-msan:
runs-on: ubuntu-latest
timeout-minutes: 180
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Buildx cache
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: /tmp/.buildx-msan
key: buildx-msan-${{ hashFiles('test-infrastructure/Dockerfile.msan') }}
- name: Build MSan image (cached layers)
run: scripts/ci/msan-lane.sh build
- name: MSan suite (exclusions per scripts/msan.sh)
run: scripts/ci/msan-lane.sh run
# macOS leak coverage. LeakSanitizer runs by default under ASan on Linux, so
# test-unix's ubuntu legs have always had it; on macOS it is off by default
# and Apple's clang refuses to turn it on, which left an entire platform with
# zero leak detection. Upstream (Homebrew) LLVM supports LSan on darwin/arm64,
# so this leg is the ordinary ASan suite built with that toolchain and run
# with detect_leaks=1. Mirrors the local `make -f Makefile.cbm test-lsan`.
test-lsan-macos:
runs-on: macos-latest
timeout-minutes: 120
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Install LSan-capable toolchain
run: brew install llvm ccache
- name: Compiler cache (content-verified, restore)
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ github.workspace }}/.ccache
key: ccache-lsan-${{ github.ref }}-${{ github.sha }}
restore-keys: |
ccache-lsan-${{ github.ref }}-
ccache-lsan-
- name: Test (Homebrew clang, ASan + LeakSanitizer)
env:
ASAN_OPTIONS: "detect_leaks=1:halt_on_error=1"
run: |
LLVM_PREFIX="$(brew --prefix llvm)"
scripts/test.sh CC="$LLVM_PREFIX/bin/clang" CXX="$LLVM_PREFIX/bin/clang++"
- name: Compiler cache (save)
if: always()
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ github.workspace }}/.ccache
key: ccache-lsan-${{ github.ref }}-${{ github.sha }}
test-tsan:
strategy:
fail-fast: false
matrix:
include:
- { os: ubuntu-latest, cc: clang, cxx: clang++ }
- { os: ubuntu-24.04-arm, cc: clang, cxx: clang++ }
- { os: macos-14, cc: cc, cxx: c++ }
runs-on: ${{ matrix.os }}
timeout-minutes: 120
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- name: Install deps (Ubuntu)
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y clang zlib1g-dev ccache
- name: Install deps (macOS)
if: runner.os == 'macOS'
run: brew install ccache
- name: Compiler cache (content-verified)
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ github.workspace }}/.ccache
key: ccache-tsan-${{ matrix.os }}-${{ github.ref }}-${{ github.sha }}
restore-keys: |
ccache-tsan-${{ matrix.os }}-${{ github.ref }}-
ccache-tsan-${{ matrix.os }}-
- name: ThreadSanitizer tests
# Calls make directly (no env.sh), so route the compilers through
# ccache's masquerade dir: /usr/lib/ccache on Debian, libexec under the
# Homebrew prefix on macOS. On Linux, reduce mmap ASLR entropy first:
# modern (esp. aarch64) kernels randomize high enough that TSan's
# shadow mapping aborts with "unexpected memory mapping" before any
# test runs. macOS TSan needs no such adjustment.
run: |
if [ "$RUNNER_OS" = "macOS" ]; then
export PATH="$(brew --prefix ccache)/libexec:$PATH"
else
export PATH=/usr/lib/ccache:$PATH
sudo sysctl -w vm.mmap_rnd_bits=28 || true
fi
# The TSan leg lives in the canonical test entry (same file the
# compose test-tsan service runs); env.sh supplies the verified
# ccache masquerade.
scripts/test.sh --tsan CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
env:
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_MAXSIZE: 1500M
- name: ccache stats
if: always()
run: ccache -s || true
env:
CCACHE_DIR: ${{ github.workspace }}/.ccache
test-windows:
needs: setup-matrix
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.setup-matrix.outputs.windows) }}
runs-on: ${{ matrix.os }}
continue-on-error: ${{ matrix.optional == true }}
timeout-minutes: 240
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
# Defender posture: real-time protection is POLICY-LOCKED OFF on hosted
# Windows images (verified 2026-07-27: WinDefend starts and
# Set-MpPreference accepts, but RTP stays off — untamperable from the
# job). The local VM leg enforces Defender-ON via the canonical Defender
# preflight script, so AV-interaction coverage lives there; runners run
# without RTP by platform constraint.
- uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2
with:
msystem: ${{ matrix.msystem }}
path-type: inherit
install: >-
mingw-w64-clang-${{ matrix.pkg }}-clang
mingw-w64-clang-${{ matrix.pkg }}-compiler-rt
mingw-w64-clang-${{ matrix.pkg }}-zlib
mingw-w64-clang-${{ matrix.pkg }}-python3
mingw-w64-clang-${{ matrix.pkg }}-ccache
make
git
- name: Create protected per-user temp root
# One implementation, shared with _soak.yml and the VM leg
# (test-infrastructure/vm/vm-run-tests.sh) — see the script's header for
# why the harness cannot run under a shared or ACL-inherited temp.
shell: pwsh
run: |
$root = & scripts/ci/new-protected-temp-root.ps1 `
-Prefix 'cbm-ci-tmp-' `
-ProtectDir (Join-Path $env:GITHUB_WORKSPACE "build\c")
"CBM_CI_TEMP_ROOT=$root" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
- name: Compiler cache (content-verified)
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ github.workspace }}/.ccache
key: ccache-test-${{ matrix.os }}-${{ matrix.msystem }}-${{ github.ref }}-${{ github.sha }}
restore-keys: |
ccache-test-${{ matrix.os }}-${{ matrix.msystem }}-${{ github.ref }}-
ccache-test-${{ matrix.os }}-${{ matrix.msystem }}-
- name: Test
shell: msys2 {0}
# AddressSanitizer is unavailable on native ARM64 Windows (LLVM ships no
# libclang_rt.asan for aarch64-w64-windows-gnu) and cannot intercept the
# system DLLs under x86-64 emulation either. Instead of running the
# ARM64 leg unsanitized, it runs UBSan in TRAP mode: -fsanitize-trap
# needs no runtime library (the very thing aarch64-w64-windows-gnu
# lacks), so it instruments natively and turns any undefined behavior
# into an illegal-instruction trap; -fstack-protector-strong adds
# stack-smash coverage the heap tools miss. To see WHICH check fired,
# reproduce under the emulated x86_64 UBSan (win.sh ubsan-build), which
# carries the full message. x86-64 Windows keeps full ASan+UBSan; the
# other native-ARM legs (Linux/macOS) carry ASan+LSan+TSan.
run: |
# Native tests resolve temp via TEMP/TMP (cbm_tmpdir); shell tools use
# TMPDIR. Route both through the protected root created above so no
# fixture lands under a shared or ACL-inherited ancestry.
export TEMP="$(cygpath -m "$CBM_CI_TEMP_ROOT")"
export TMP="$TEMP"
export TMPDIR="$(cygpath -u "$CBM_CI_TEMP_ROOT")"
# No per-leg flag logic here: test.sh itself applies the CLANGARM64
# trap-UBSan default, so local and CI build identical test binaries.
scripts/test.sh CC=clang CXX=clang++
env:
CBM_TEST_SHARD: ${{ matrix.shard }}
CBM_TEST_LEG: ${{ matrix.os }}-${{ matrix.msystem }}
CBM_SKIP_PERF: ${{ inputs.skip_perf && '1' || '' }}
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_MAXSIZE: 1500M
- name: ccache stats
if: always()
shell: msys2 {0}
run: ccache -s || true
env:
CCACHE_DIR: ${{ github.workspace }}/.ccache
- name: Shard manifest (completeness proof)
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: shard-manifest-${{ matrix.os }}-${{ matrix.msystem }}-${{ strategy.job-index }}
path: build/c/test-logs/shard-manifest.txt
# The harness writes the manifest BEFORE any suite runs, so a
# missing file here means the job died before the harness started
# (contract step / build) — that failure is already the job's red;
# warn instead of stacking a second error on top of it. The
# shard-completeness job still gates the cross-shard union.
if-no-files-found: warn
# Shard 1 (or the unsharded job) saves the compiler cache; every shard
# builds the identical runner, so one cache carries the full object set.
- name: Compiler cache (save)
if: always() && (matrix.shard == null || startsWith(matrix.shard, '1/'))
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ github.workspace }}/.ccache
key: ccache-test-${{ matrix.os }}-${{ matrix.msystem }}-${{ github.ref }}-${{ github.sha }}
# Cross-shard completeness: re-proves at runtime that each leg's shards
# agreed on the full suite list and that the union of their slices IS that
# list. This is the guard the per-shard union checks cannot provide: a
# mis-plumbed CBM_TEST_SHARD (two jobs running the same slice) passes every
# per-shard guard while a slice runs nowhere — only a cross-shard view
# catches it. Runs and gates on unsharded topologies too (every leg then
# contributes one 1/1 manifest whose slice must equal the full list).
shard-completeness:
needs: [test-unix, test-windows]
if: always()
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
# The union proof lives in the canonical scripts/ci entry, not inline
# YAML (venue-parity contract) — checkout (into repo/, so the manifest
# download below stays untouched) provides the script.
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
path: repo
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
pattern: shard-manifest-*
path: manifests
- name: Verify cross-shard union per leg
run: repo/scripts/ci/verify-shard-union.sh manifests
# Windows product-surface regression guards. Distinct from test-windows above
# (the sanitizer C suite): these drive a real product binary + embedded HTTP UI
# over stdio / CLI / HTTP and fail if a Windows bug already fixed on main comes
# back -- non-ASCII repo paths dropping definitions (#636/#357, fixed by #700),
# the PreToolUse hook augmenter no-op on drive-letter cwd (#618, fixed by #619),
# the UI directory picker not enumerating drives (#548, roots field), and non-ASCII
# CLI arguments being mangled by the narrow-argv main() (#423/#20, fixed by the
# wide-argv entrypoint that reads GetCommandLineW).
test-windows-guards:
runs-on: windows-latest
timeout-minutes: 60
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: msys2/setup-msys2@66cd2cce69caa17b53920067426061ca1de3a884 # v2
with:
msystem: CLANG64
path-type: inherit
install: >-
mingw-w64-clang-x86_64-clang
mingw-w64-clang-x86_64-zlib
mingw-w64-clang-x86_64-ccache
make
git
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22"
- name: Test npm wrapper failure handling
working-directory: pkg/npm
run: npm test
- name: Compiler cache (content-verified)
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ github.workspace }}/.ccache
key: ccache-guards-${{ github.ref }}-${{ github.sha }}
restore-keys: |
ccache-guards-${{ github.ref }}-
ccache-guards-
- name: Build product binary with embedded UI
shell: msys2 {0}
# --with-ui builds the frontend (npm) and embeds it, so the drive-picker
# guard's HTTP UI is available. Functional gate only (no sanitizers).
run: scripts/build.sh --with-ui CC=clang CXX=clang++
env:
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_MAXSIZE: 1500M
- name: Windows regression guards (#636/#357, #618, #548, #423/#20)
shell: pwsh
# -GuardsOnly runs the four green guards and gates on them. All guards run
# against the real supervisor->worker spawn path: under the mandatory
# coordination daemon, CBM_INDEX_SUPERVISOR=0 is a fail-closed refusal, so
# the driver no longer sets it anywhere.
run: ./scripts/test-windows.ps1 -GuardsOnly -Binary build/c/codebase-memory-mcp.exe