Dry Run #124
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |