Skip to content

Dry Run

Dry Run #124

Workflow file for this run

name: Dry Run
on:
workflow_dispatch:
inputs:
skip_lint:
description: 'Skip lint step (faster iteration)'
type: boolean
default: false
skip_tests:
description: 'Skip test steps (faster iteration on build/smoke)'
type: boolean
default: false
skip_builds:
description: 'Skip build+smoke steps (faster iteration on lint/tests)'
type: boolean
default: false
soak_level:
description: 'Soak test level: full (quick+asan+nightly), quick (10min only), none'
type: choice
options: ['full', 'quick', 'none']
default: 'quick'
permissions:
contents: read
jobs:
# ── Step 1: Lint (clang-format + cppcheck) ───────────────────
lint:
if: ${{ inputs.skip_lint != true }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Install build deps
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev cmake
- name: Install LLVM 20
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-20 main" | sudo tee /etc/apt/sources.list.d/llvm-20.list
sudo apt-get update
sudo apt-get install -y clang-format-20
- uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
id: cppcheck-cache
with:
path: /opt/cppcheck
key: cppcheck-2.20.0-ubuntu-amd64
- name: Build cppcheck 2.20.0
if: steps.cppcheck-cache.outputs.cache-hit != 'true'
run: |
git clone --depth 1 --branch 2.20.0 https://github.com/danmar/cppcheck.git /tmp/cppcheck
cmake -S /tmp/cppcheck -B /tmp/cppcheck/build -DCMAKE_BUILD_TYPE=Release -DHAVE_RULES=OFF -DCMAKE_INSTALL_PREFIX=/opt/cppcheck
cmake --build /tmp/cppcheck/build -j$(nproc)
cmake --install /tmp/cppcheck/build
- name: Add cppcheck to PATH
run: echo "/opt/cppcheck/bin" >> "$GITHUB_PATH"
- name: Lint
run: scripts/lint.sh CLANG_FORMAT=clang-format-20
# ── Step 1b: Security audit (source-only, runs parallel with lint+tests) ──
# No build needed — scans source files and vendored deps only.
# Binary-level security (L2/L3/L4/L7) runs in smoke jobs per-platform.
security-static:
if: ${{ inputs.skip_lint != true }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: "Layer 1: Static allow-list audit"
run: scripts/security-audit.sh
- name: "Layer 6: UI security audit"
run: scripts/security-ui.sh
- name: "Layer 8: Vendored dependency integrity"
run: scripts/security-vendored.sh
# ── Step 1c: CodeQL SAST gate ────────────────────────────────
codeql-gate:
if: ${{ inputs.skip_lint != true }}
runs-on: ubuntu-latest
steps:
- name: Wait for CodeQL on current commit (max 45 min)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
CURRENT_SHA="${{ github.sha }}"
echo "Current commit: $CURRENT_SHA"
echo "Waiting for CodeQL to complete on this commit..."
for attempt in $(seq 1 90); do
LATEST=$(gh api repos/${{ github.repository }}/actions/workflows/codeql.yml/runs?per_page=5 \
--jq '.workflow_runs[] | select(.head_sha == "'"$CURRENT_SHA"'") | "\(.conclusion) \(.status)"' 2>/dev/null | head -1 || echo "")
if [ -z "$LATEST" ]; then
echo " Attempt $attempt/90: No CodeQL run found for $CURRENT_SHA yet..."
sleep 30
continue
fi
CONCLUSION=$(echo "$LATEST" | cut -d' ' -f1)
STATUS=$(echo "$LATEST" | cut -d' ' -f2)
if [ "$STATUS" = "completed" ] && [ "$CONCLUSION" = "success" ]; then
echo "=== CodeQL completed successfully on current commit ==="
exit 0
elif [ "$STATUS" = "completed" ]; then
echo "BLOCKED: CodeQL completed with conclusion: $CONCLUSION"
exit 1
fi
echo " Attempt $attempt/90: CodeQL status=$STATUS (waiting 30s)..."
sleep 30
done
echo "BLOCKED: CodeQL did not complete within 45 minutes"
exit 1
- name: Check for open code scanning alerts
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Wait for GitHub to finish processing alert state changes.
# There is a race between CodeQL marking the workflow as "completed"
# and the alerts API reflecting new/closed alerts from that scan.
echo "Waiting 60s for alert API to settle after CodeQL completion..."
sleep 60
# Poll alerts twice with a gap to confirm the count is stable
ALERTS1=$(gh api 'repos/${{ github.repository }}/code-scanning/alerts?state=open' --jq 'length' 2>/dev/null || echo "0")
echo "Open alerts (check 1): $ALERTS1"
sleep 15
ALERTS2=$(gh api 'repos/${{ github.repository }}/code-scanning/alerts?state=open' --jq 'length' 2>/dev/null || echo "0")
echo "Open alerts (check 2): $ALERTS2"
# Use the higher count (conservative — if either check sees alerts, block)
ALERTS=$ALERTS2
if [ "$ALERTS1" -gt "$ALERTS2" ]; then
ALERTS=$ALERTS1
fi
if [ "$ALERTS" -gt 0 ]; then
echo "BLOCKED: $ALERTS open code scanning alert(s) found."
gh api 'repos/${{ github.repository }}/code-scanning/alerts?state=open' \
--jq '.[] | " #\(.number) [\(.rule.security_severity_level // .rule.severity)] \(.rule.id) — \(.most_recent_instance.location.path):\(.most_recent_instance.location.start_line)"' 2>/dev/null || true
echo "Fix them: https://github.com/${{ github.repository }}/security/code-scanning"
exit 1
fi
echo "=== CodeQL gate passed (0 open alerts) ==="
# ── Step 2: Unit tests (ASan + UBSan) ───────────────────────
# macOS: use cc (Apple Clang) — GCC on macOS doesn't ship ASan runtime
# Linux: use system gcc — full ASan/UBSan support
# Windows: MSYS2 MinGW GCC
test-unix:
if: ${{ inputs.skip_tests != true && always() && (needs.lint.result == 'success' || needs.lint.result == 'skipped') }}
needs: [lint]
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
arch: amd64
cc: gcc
cxx: g++
- os: ubuntu-24.04-arm
arch: arm64
cc: gcc
cxx: g++
- os: macos-14
arch: arm64
cc: cc
cxx: c++
- os: macos-15-intel
arch: amd64
cc: cc
cxx: c++
runs-on: ${{ matrix.os }}
timeout-minutes: 30
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Install deps (Ubuntu)
if: startsWith(matrix.os, 'ubuntu')
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev
- name: Test (unit + integration, no perf)
run: scripts/test.sh CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
env:
CBM_SKIP_PERF: 1
test-windows:
if: ${{ inputs.skip_tests != true && always() && (needs.lint.result == 'success' || needs.lint.result == 'skipped') }}
needs: [lint]
runs-on: windows-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: msys2/setup-msys2@4f806de0a5a7294ffabaff804b38a9b435a73bda # v2
with:
msystem: CLANG64
path-type: inherit
install: >-
mingw-w64-clang-x86_64-clang
mingw-w64-clang-x86_64-compiler-rt
mingw-w64-clang-x86_64-zlib
make
- name: Test
shell: msys2 {0}
run: scripts/test.sh CC=clang CXX=clang++
# ── Step 3: Build binaries (standard + UI, all OS) ──────────
build-unix:
if: ${{ inputs.skip_builds != true && always() && (needs.test-unix.result == 'success' || needs.test-unix.result == 'skipped') && (needs.test-windows.result == 'success' || needs.test-windows.result == 'skipped') }}
needs: [test-unix, test-windows]
strategy:
matrix:
include:
- os: ubuntu-latest
goos: linux
goarch: amd64
cc: gcc
cxx: g++
- os: ubuntu-24.04-arm
goos: linux
goarch: arm64
cc: gcc
cxx: g++
- os: macos-14
goos: darwin
goarch: arm64
cc: cc
cxx: c++
- os: macos-15-intel
goos: darwin
goarch: amd64
cc: cc
cxx: c++
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- name: Install deps (Ubuntu)
if: startsWith(matrix.os, 'ubuntu')
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: "22"
- name: Build standard binary
run: scripts/build.sh CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
- name: Ad-hoc sign macOS binary
if: startsWith(matrix.os, 'macos')
run: codesign --sign - --force build/c/codebase-memory-mcp
- name: Archive standard binary
run: |
cp LICENSE build/c/
tar -czf codebase-memory-mcp-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz \
-C build/c codebase-memory-mcp LICENSE
- name: Build UI binary
run: scripts/build.sh --with-ui CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
- name: Ad-hoc sign macOS UI binary
if: startsWith(matrix.os, 'macos')
run: codesign --sign - --force build/c/codebase-memory-mcp
- name: Frontend integrity scan (post-build dist/)
if: matrix.goos == 'linux' && matrix.goarch == 'amd64'
run: scripts/security-ui.sh
- name: Archive UI binary
run: |
cp LICENSE build/c/
tar -czf codebase-memory-mcp-ui-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz \
-C build/c codebase-memory-mcp LICENSE
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: binaries-${{ matrix.goos }}-${{ matrix.goarch }}
path: "*.tar.gz"
# ── Step 3b: Build portable Linux binaries (musl static) ────
build-linux-portable:
if: ${{ inputs.skip_builds != true && always() && (needs.test-unix.result == 'success' || needs.test-unix.result == 'skipped') && (needs.test-windows.result == 'success' || needs.test-windows.result == 'skipped') }}
needs: [test-unix, test-windows]
strategy:
matrix:
include:
- arch: amd64
runner: ubuntu-latest
- arch: arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
container:
image: alpine:3.21
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install musl build deps
run: apk add --no-cache build-base linux-headers zlib-dev zlib-static nodejs npm
- name: Build standard binary (static)
run: scripts/build.sh CC=gcc CXX=g++ STATIC=1
- name: Verify static linking
run: |
file build/c/codebase-memory-mcp | grep -q "statically linked"
echo "=== Confirmed: fully static binary ==="
- name: Archive standard binary
run: |
cp LICENSE build/c/
tar -czf codebase-memory-mcp-linux-${{ matrix.arch }}-portable.tar.gz \
-C build/c codebase-memory-mcp LICENSE
- name: Build UI binary (static)
run: scripts/build.sh --with-ui CC=gcc CXX=g++ STATIC=1
- name: Archive UI binary
run: |
cp LICENSE build/c/
tar -czf codebase-memory-mcp-ui-linux-${{ matrix.arch }}-portable.tar.gz \
-C build/c codebase-memory-mcp LICENSE
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: binaries-linux-${{ matrix.arch }}-portable
path: "*.tar.gz"
build-windows:
if: ${{ inputs.skip_builds != true && always() && (needs.test-unix.result == 'success' || needs.test-unix.result == 'skipped') && (needs.test-windows.result == 'success' || needs.test-windows.result == 'skipped') }}
needs: [test-unix, test-windows]
runs-on: windows-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: msys2/setup-msys2@4f806de0a5a7294ffabaff804b38a9b435a73bda # v2
with:
msystem: CLANG64
path-type: inherit
install: >-
mingw-w64-clang-x86_64-clang
mingw-w64-clang-x86_64-zlib
make
zip
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: "22"
- name: Build standard binary
shell: msys2 {0}
run: scripts/build.sh CC=clang CXX=clang++
- name: Archive standard binary
shell: msys2 {0}
run: |
BIN=build/c/codebase-memory-mcp
[ -f "${BIN}.exe" ] && BIN="${BIN}.exe"
cp "$BIN" codebase-memory-mcp.exe
zip codebase-memory-mcp-windows-amd64.zip codebase-memory-mcp.exe LICENSE
- name: Build UI binary
shell: msys2 {0}
run: scripts/build.sh --with-ui CC=clang CXX=clang++
- name: Archive UI binary
shell: msys2 {0}
run: |
BIN=build/c/codebase-memory-mcp
[ -f "${BIN}.exe" ] && BIN="${BIN}.exe"
cp "$BIN" codebase-memory-mcp-ui.exe
zip codebase-memory-mcp-ui-windows-amd64.zip codebase-memory-mcp-ui.exe LICENSE
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: binaries-windows-amd64
path: "*.zip"
# ── Step 4: Smoke test every binary ─────────────────────────
smoke-unix:
if: ${{ !cancelled() && inputs.skip_builds != true }}
needs: [build-unix]
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
goos: linux
goarch: amd64
- os: ubuntu-24.04-arm
goos: linux
goarch: arm64
- os: macos-14
goos: darwin
goarch: arm64
- os: macos-15-intel
goos: darwin
goarch: amd64
variant: [standard, ui]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: binaries-${{ matrix.goos }}-${{ matrix.goarch }}
- name: Extract binary
run: |
SUFFIX=${{ matrix.variant == 'ui' && '-ui' || '' }}
tar -xzf codebase-memory-mcp${SUFFIX}-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz
chmod +x codebase-memory-mcp
- name: Start artifact server for E2E smoke tests
run: |
mkdir -p /tmp/smoke-server
cp codebase-memory-mcp /tmp/smoke-server/
OS=${{ matrix.goos }}
ARCH=${{ matrix.goarch }}
SUFFIX=${{ matrix.variant == 'ui' && '-ui' || '' }}
tar -czf "/tmp/smoke-server/codebase-memory-mcp${SUFFIX}-${OS}-${ARCH}.tar.gz" \
-C /tmp/smoke-server codebase-memory-mcp
# Also serve under standard name so install.sh + update --standard work
if [ -n "$SUFFIX" ]; then
cp "/tmp/smoke-server/codebase-memory-mcp${SUFFIX}-${OS}-${ARCH}.tar.gz" \
"/tmp/smoke-server/codebase-memory-mcp-${OS}-${ARCH}.tar.gz"
fi
cd /tmp/smoke-server
sha256sum *.tar.gz > checksums.txt 2>/dev/null || shasum -a 256 *.tar.gz > checksums.txt
python3 -m http.server 18080 -d /tmp/smoke-server &
- name: Smoke test (${{ matrix.variant }}, ${{ matrix.goos }}-${{ matrix.goarch }})
run: scripts/smoke-test.sh ./codebase-memory-mcp
env:
SMOKE_DOWNLOAD_URL: http://localhost:18080
- name: Binary string audit (${{ matrix.goos }}-${{ matrix.goarch }})
if: matrix.variant == 'standard'
run: scripts/security-strings.sh ./codebase-memory-mcp
- name: Install output audit (${{ matrix.goos }}-${{ matrix.goarch }})
if: matrix.variant == 'standard'
run: scripts/security-install.sh ./codebase-memory-mcp
- name: Network egress test (${{ matrix.goos }}-${{ matrix.goarch }})
if: matrix.variant == 'standard'
run: scripts/security-network.sh ./codebase-memory-mcp
- name: MCP robustness test
if: matrix.variant == 'standard' && matrix.goos == 'linux' && matrix.goarch == 'amd64'
run: scripts/security-fuzz.sh ./codebase-memory-mcp
- name: Fuzz testing (60s random input)
if: matrix.variant == 'standard' && matrix.goos == 'linux' && matrix.goarch == 'amd64'
run: scripts/security-fuzz-random.sh ./codebase-memory-mcp 60
- name: ClamAV scan (Linux)
if: matrix.variant == 'standard' && startsWith(matrix.os, 'ubuntu')
run: |
sudo apt-get update -qq && sudo apt-get install -y -qq clamav > /dev/null 2>&1
sudo sed -i 's/^Example/#Example/' /etc/clamav/freshclam.conf 2>/dev/null || true
grep -q "DatabaseMirror" /etc/clamav/freshclam.conf 2>/dev/null || \
echo "DatabaseMirror database.clamav.net" | sudo tee -a /etc/clamav/freshclam.conf > /dev/null
sudo freshclam --quiet
echo "=== ClamAV scan ==="
clamscan --no-summary ./codebase-memory-mcp
echo "=== ClamAV: clean ==="
- name: ClamAV scan (macOS)
if: matrix.variant == 'standard' && startsWith(matrix.os, 'macos')
run: |
brew install clamav > /dev/null 2>&1
CLAMAV_ETC=$(brew --prefix)/etc/clamav
if [ ! -f "$CLAMAV_ETC/freshclam.conf" ]; then
cp "$CLAMAV_ETC/freshclam.conf.sample" "$CLAMAV_ETC/freshclam.conf" 2>/dev/null || true
sed -i '' 's/^Example/#Example/' "$CLAMAV_ETC/freshclam.conf" 2>/dev/null || true
echo "DatabaseMirror database.clamav.net" >> "$CLAMAV_ETC/freshclam.conf"
fi
freshclam --quiet --no-warnings 2>/dev/null || freshclam --quiet 2>/dev/null || echo "WARNING: freshclam update failed, using bundled signatures"
echo "=== ClamAV scan (macOS) ==="
clamscan --no-summary ./codebase-memory-mcp
echo "=== ClamAV: clean ==="
smoke-windows:
if: ${{ !cancelled() && inputs.skip_builds != true }}
needs: [build-windows]
strategy:
fail-fast: false
matrix:
variant: [standard, ui]
runs-on: windows-latest
steps:
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: msys2/setup-msys2@4f806de0a5a7294ffabaff804b38a9b435a73bda # v2
with:
msystem: CLANG64
path-type: inherit
install: >-
mingw-w64-clang-x86_64-python3
unzip
zip
coreutils
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: binaries-windows-amd64
- name: Extract binary
shell: msys2 {0}
run: |
SUFFIX=${{ matrix.variant == 'ui' && '-ui' || '' }}
unzip -o "codebase-memory-mcp${SUFFIX}-windows-amd64.zip"
[ -n "$SUFFIX" ] && cp "codebase-memory-mcp${SUFFIX}.exe" codebase-memory-mcp.exe || true
- name: Start artifact server for E2E smoke tests
shell: msys2 {0}
run: |
mkdir -p /tmp/smoke-server
cp codebase-memory-mcp.exe /tmp/smoke-server/codebase-memory-mcp.exe
SUFFIX=${{ matrix.variant == 'ui' && '-ui' || '' }}
cd /tmp/smoke-server
zip -q "codebase-memory-mcp${SUFFIX}-windows-amd64.zip" codebase-memory-mcp.exe
# Also serve under standard name
if [ -n "$SUFFIX" ]; then
cp "codebase-memory-mcp${SUFFIX}-windows-amd64.zip" "codebase-memory-mcp-windows-amd64.zip"
fi
sha256sum *.zip > checksums.txt
python3 -m http.server 18080 -d /tmp/smoke-server &
- name: Smoke test (${{ matrix.variant }}, windows-amd64)
shell: msys2 {0}
run: scripts/smoke-test.sh ./codebase-memory-mcp.exe
env:
SMOKE_DOWNLOAD_URL: http://localhost:18080
- name: Binary string audit (windows-amd64)
if: matrix.variant == 'standard'
shell: msys2 {0}
run: scripts/security-strings.sh ./codebase-memory-mcp.exe
- name: Install output audit (windows-amd64)
if: matrix.variant == 'standard'
shell: msys2 {0}
run: scripts/security-install.sh ./codebase-memory-mcp.exe
- name: Windows Defender scan
if: matrix.variant == 'standard'
shell: pwsh
run: |
Write-Host "=== Windows Defender scan (with ML heuristics) ==="
& "C:\Program Files\Windows Defender\MpCmdRun.exe" -SignatureUpdate 2>$null
$result = & "C:\Program Files\Windows Defender\MpCmdRun.exe" -Scan -ScanType 3 -File "$PWD\codebase-memory-mcp.exe" -DisableRemediation
Write-Host $result
if ($LASTEXITCODE -ne 0) {
Write-Host "BLOCKED: Windows Defender flagged the binary!"
exit 1
}
Write-Host "=== Windows Defender: clean ==="
# ── Step 4b: Smoke test portable Linux binaries ──────────────
smoke-linux-portable:
if: ${{ !cancelled() && inputs.skip_builds != true }}
needs: [build-linux-portable]
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
arch: amd64
- os: ubuntu-24.04-arm
arch: arm64
variant: [standard, ui]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
with:
name: binaries-linux-${{ matrix.arch }}-portable
- name: Extract binary
run: |
SUFFIX=${{ matrix.variant == 'ui' && '-ui' || '' }}
tar -xzf codebase-memory-mcp${SUFFIX}-linux-${{ matrix.arch }}-portable.tar.gz
chmod +x codebase-memory-mcp
- name: Start artifact server for E2E smoke tests
run: |
mkdir -p /tmp/smoke-server
cp codebase-memory-mcp /tmp/smoke-server/
ARCH=${{ matrix.arch }}
SUFFIX=${{ matrix.variant == 'ui' && '-ui' || '' }}
tar -czf "/tmp/smoke-server/codebase-memory-mcp${SUFFIX}-linux-${ARCH}-portable.tar.gz" \
-C /tmp/smoke-server codebase-memory-mcp
# Also serve under standard name so install.sh + update --standard work
cp "/tmp/smoke-server/codebase-memory-mcp${SUFFIX}-linux-${ARCH}-portable.tar.gz" \
"/tmp/smoke-server/codebase-memory-mcp-linux-${ARCH}.tar.gz"
if [ -n "$SUFFIX" ]; then
cp "/tmp/smoke-server/codebase-memory-mcp${SUFFIX}-linux-${ARCH}-portable.tar.gz" \
"/tmp/smoke-server/codebase-memory-mcp${SUFFIX}-linux-${ARCH}.tar.gz"
fi
cd /tmp/smoke-server
sha256sum *.tar.gz > checksums.txt 2>/dev/null || shasum -a 256 *.tar.gz > checksums.txt
python3 -m http.server 18080 -d /tmp/smoke-server &
- name: Smoke test (portable ${{ matrix.variant }}, linux-${{ matrix.arch }})
run: scripts/smoke-test.sh ./codebase-memory-mcp
env:
SMOKE_DOWNLOAD_URL: http://localhost:18080
- name: Binary string audit (portable linux-${{ matrix.arch }})
if: matrix.variant == 'standard'
run: scripts/security-strings.sh ./codebase-memory-mcp
- name: Install output audit (portable linux-${{ matrix.arch }})
if: matrix.variant == 'standard'
run: scripts/security-install.sh ./codebase-memory-mcp
- name: Network egress test (portable linux-${{ matrix.arch }})
if: matrix.variant == 'standard'
run: scripts/security-network.sh ./codebase-memory-mcp
- name: ClamAV scan (portable Linux)
if: matrix.variant == 'standard'
run: |
sudo apt-get update -qq && sudo apt-get install -y -qq clamav > /dev/null 2>&1
sudo sed -i 's/^Example/#Example/' /etc/clamav/freshclam.conf 2>/dev/null || true
grep -q "DatabaseMirror" /etc/clamav/freshclam.conf 2>/dev/null || \
echo "DatabaseMirror database.clamav.net" | sudo tee -a /etc/clamav/freshclam.conf > /dev/null
sudo freshclam --quiet
echo "=== ClamAV scan (portable) ==="
clamscan --no-summary ./codebase-memory-mcp
echo "=== ClamAV: clean ==="
# ── Step 6: Soak tests (after smoke, per-platform) ──────────
soak-quick:
if: ${{ !cancelled() && inputs.soak_level != 'none' && inputs.skip_builds != true }}
needs: [build-unix]
runs-on: ${{ matrix.os }}
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
goos: linux
goarch: amd64
cc: gcc
cxx: g++
- os: ubuntu-24.04-arm
goos: linux
goarch: arm64
cc: gcc
cxx: g++
- os: macos-14
goos: darwin
goarch: arm64
cc: cc
cxx: c++
- os: macos-15-intel
goos: darwin
goarch: amd64
cc: cc
cxx: c++
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Install deps (Linux)
if: startsWith(matrix.os, 'ubuntu')
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev python3 git
- name: Build (release mode)
run: scripts/build.sh CC=${{ matrix.cc }} CXX=${{ matrix.cxx }}
- name: Quick soak (10 min)
run: scripts/soak-test.sh build/c/codebase-memory-mcp 10
- name: Upload soak metrics
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: soak-quick-${{ matrix.goos }}-${{ matrix.goarch }}
path: soak-results/
retention-days: 14
soak-quick-windows:
if: ${{ !cancelled() && inputs.soak_level != 'none' && inputs.skip_builds != true }}
needs: [build-windows]
runs-on: windows-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: msys2/setup-msys2@4f806de0a5a7294ffabaff804b38a9b435a73bda # 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-python3
make
git
coreutils
- name: Build (release mode)
shell: msys2 {0}
run: scripts/build.sh CC=clang CXX=clang++
- name: Quick soak (10 min)
shell: msys2 {0}
run: |
BIN=build/c/codebase-memory-mcp
[ -f "${BIN}.exe" ] && BIN="${BIN}.exe"
scripts/soak-test.sh "$BIN" 10
- name: Upload soak metrics
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: soak-quick-windows-amd64
path: soak-results/
retention-days: 14
soak-asan:
if: ${{ !cancelled() && inputs.soak_level == 'full' && inputs.skip_builds != true }}
needs: [build-unix]
runs-on: ${{ matrix.os }}
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
goos: linux
goarch: amd64
cc: gcc
cxx: g++
- os: ubuntu-24.04-arm
goos: linux
goarch: arm64
cc: gcc
cxx: g++
- os: macos-14
goos: darwin
goarch: arm64
cc: cc
cxx: c++
- os: macos-15-intel
goos: darwin
goarch: amd64
cc: cc
cxx: c++
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- name: Install deps (Linux)
if: startsWith(matrix.os, 'ubuntu')
run: sudo apt-get update && sudo apt-get install -y zlib1g-dev python3 git
- name: Build (ASan + LeakSanitizer)
run: |
SANITIZE="-fsanitize=address,undefined -fno-omit-frame-pointer"
scripts/build.sh CC=${{ matrix.cc }} CXX=${{ matrix.cxx }} EXTRA_CFLAGS="$SANITIZE" EXTRA_LDFLAGS="$SANITIZE"
- name: ASan soak (15 min)
env:
ASAN_OPTIONS: "detect_leaks=1:halt_on_error=0:log_path=soak-results/asan"
run: scripts/soak-test.sh build/c/codebase-memory-mcp 15
- name: Upload ASan soak metrics
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: soak-asan-${{ matrix.goos }}-${{ matrix.goarch }}
path: soak-results/
retention-days: 14
soak-asan-windows:
if: ${{ !cancelled() && inputs.soak_level == 'full' && inputs.skip_builds != true }}
needs: [build-windows]
runs-on: windows-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: msys2/setup-msys2@4f806de0a5a7294ffabaff804b38a9b435a73bda # 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-python3
make
git
coreutils
- name: Build (ASan, no LeakSan on Windows)
shell: msys2 {0}
run: |
SANITIZE="-fsanitize=address,undefined -fno-omit-frame-pointer"
scripts/build.sh CC=clang CXX=clang++ EXTRA_CFLAGS="$SANITIZE" EXTRA_LDFLAGS="$SANITIZE"
- name: ASan soak (15 min, no leak detection)
shell: msys2 {0}
env:
ASAN_OPTIONS: "detect_leaks=0:halt_on_error=0:log_path=soak-results/asan"
run: |
BIN=build/c/codebase-memory-mcp
[ -f "${BIN}.exe" ] && BIN="${BIN}.exe"
scripts/soak-test.sh "$BIN" 15
- name: Upload ASan soak metrics
if: always()
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: soak-asan-windows-amd64
path: soak-results/
retention-days: 14
# ── Step 6c: Quick soak (portable Linux, musl static) ──────────
soak-quick-linux-portable:
if: ${{ !cancelled() && inputs.soak_level != 'none' && inputs.skip_builds != true }}
needs: [build-linux-portable]
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: ubuntu-latest
- arch: arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
container:
image: alpine:3.21
timeout-minutes: 30
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install musl build deps
run: apk add --no-cache build-base linux-headers zlib-dev zlib-static python3 git nodejs npm
- name: Build (release mode, static)
run: scripts/build.sh CC=gcc CXX=g++ STATIC=1
- name: Quick soak (10 min)
run: scripts/soak-test.sh build/c/codebase-memory-mcp 10
- name: Upload soak metrics
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: soak-quick-linux-${{ matrix.arch }}-portable
path: soak-results/
retention-days: 14
# ── Step 6d: ASan soak (portable Linux, musl static) ──────────
soak-asan-linux-portable:
if: ${{ !cancelled() && inputs.soak_level == 'full' && inputs.skip_builds != true }}
needs: [build-linux-portable]
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: ubuntu-latest
- arch: arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
container:
image: alpine:3.21
timeout-minutes: 45
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install musl build deps
run: apk add --no-cache build-base linux-headers zlib-dev zlib-static python3 git nodejs npm
- name: Build (ASan + LeakSanitizer, static)
run: |
SANITIZE="-fsanitize=address,undefined -fno-omit-frame-pointer"
scripts/build.sh CC=gcc CXX=g++ STATIC=1 EXTRA_CFLAGS="$SANITIZE" EXTRA_LDFLAGS="$SANITIZE"
- name: ASan soak (15 min)
env:
ASAN_OPTIONS: "detect_leaks=1:halt_on_error=0:log_path=soak-results/asan"
run: scripts/soak-test.sh build/c/codebase-memory-mcp 15
- name: Upload ASan soak metrics
if: always()
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: soak-asan-linux-${{ matrix.arch }}-portable
path: soak-results/
retention-days: 14