Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 30 additions & 12 deletions .github/workflows/_build.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Reusable: build binaries (standard + UI) on all platforms
# Reusable: build release binaries on all supported platforms
name: Build

on:
Expand All @@ -12,6 +12,10 @@ on:
description: 'Generate build provenance attestations for release artifacts'
type: boolean
default: false
ui_only:
description: 'Build only the UI runtime archives'
type: boolean
default: false

permissions:
contents: read
Expand Down Expand Up @@ -64,6 +68,7 @@ jobs:
node-version: "22"

- name: Build standard binary
if: ${{ !inputs.ui_only }}
env:
VERSION: ${{ inputs.version }}
CC: ${{ matrix.cc }}
Expand All @@ -75,17 +80,16 @@ jobs:
scripts/build.sh "CC=$CC" "CXX=$CXX"
fi

- name: Ad-hoc sign macOS binary
if: startsWith(matrix.os, 'macos')
- name: Ad-hoc sign macOS standard binary
if: ${{ !inputs.ui_only && startsWith(matrix.os, 'macos') }}
run: codesign --sign - --force build/c/codebase-memory-mcp

# Archive layout/name live in the ONE canonical script the local
# artifact-flow smoke lane also runs (venue-parity contract).
- name: Archive standard binary (canonical package-release.sh)
if: ${{ !inputs.ui_only }}
run: scripts/package-release.sh ${{ matrix.goos }} ${{ matrix.goarch }}

- name: Attest standard binary provenance
if: ${{ inputs.attest }}
if: ${{ inputs.attest && !inputs.ui_only }}
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-path: codebase-memory-mcp-${{ matrix.goos }}-${{ matrix.goarch }}.tar.gz
Expand All @@ -110,6 +114,8 @@ jobs:
if: matrix.goos == 'linux' && matrix.goarch == 'amd64'
run: scripts/security-ui.sh

# Archive layout/name live in the ONE canonical script the local
# artifact-flow smoke lane also runs (venue-parity contract).
- name: Archive UI binary (canonical package-release.sh)
run: scripts/package-release.sh ${{ matrix.goos }} ${{ matrix.goarch }} --variant ui

Expand Down Expand Up @@ -145,6 +151,7 @@ jobs:
node-version: "22"

- name: Build standard binary
if: ${{ !inputs.ui_only }}
shell: msys2 {0}
env:
VERSION: ${{ inputs.version }}
Expand All @@ -155,14 +162,13 @@ jobs:
scripts/build.sh CC=clang CXX=clang++
fi

# Archive layout/name live in the ONE canonical script the local
# artifact-flow smoke lane also runs (venue-parity contract).
- name: Archive standard binary (canonical package-release.sh)
if: ${{ !inputs.ui_only }}
shell: msys2 {0}
run: scripts/package-release.sh windows amd64 CC=clang CXX=clang++

- name: Attest standard binary provenance
if: ${{ inputs.attest }}
if: ${{ inputs.attest && !inputs.ui_only }}
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-path: codebase-memory-mcp-windows-amd64.zip
Expand All @@ -178,6 +184,8 @@ jobs:
scripts/build.sh --with-ui CC=clang CXX=clang++
fi

# Archive layout/name live in the ONE canonical script the local
# artifact-flow smoke lane also runs (venue-parity contract).
- name: Archive UI binary (canonical package-release.sh)
shell: msys2 {0}
run: scripts/package-release.sh windows amd64 --variant ui CC=clang CXX=clang++
Expand Down Expand Up @@ -218,6 +226,7 @@ jobs:
node-version: "22"

- name: Build standard binary
if: ${{ !inputs.ui_only }}
shell: msys2 {0}
env:
VERSION: ${{ inputs.version }}
Expand All @@ -229,11 +238,12 @@ jobs:
fi

- name: Archive standard binary (canonical package-release.sh)
if: ${{ !inputs.ui_only }}
shell: msys2 {0}
run: scripts/package-release.sh windows arm64 CC=clang CXX=clang++

- name: Attest standard binary provenance
if: ${{ inputs.attest }}
if: ${{ inputs.attest && !inputs.ui_only }}
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-path: codebase-memory-mcp-windows-arm64.zip
Expand Down Expand Up @@ -288,6 +298,7 @@ jobs:
node-version: "22"

- name: Build standard binary (static)
if: ${{ !inputs.ui_only }}
env:
VERSION: ${{ inputs.version }}
run: |
Expand All @@ -297,16 +308,18 @@ jobs:
scripts/build.sh CC=gcc CXX=g++ STATIC=1
fi

- name: Verify static linking
- name: Verify standard static linking
if: ${{ !inputs.ui_only }}
run: |
file build/c/codebase-memory-mcp
ldd build/c/codebase-memory-mcp 2>&1 | grep -q "not a dynamic executable" || ldd build/c/codebase-memory-mcp 2>&1 | grep -q "statically linked"

- name: Archive standard binary (canonical package-release.sh)
if: ${{ !inputs.ui_only }}
run: scripts/package-release.sh linux ${{ matrix.arch }}-portable

- name: Attest standard binary provenance
if: ${{ inputs.attest }}
if: ${{ inputs.attest && !inputs.ui_only }}
uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1
with:
subject-path: codebase-memory-mcp-linux-${{ matrix.arch }}-portable.tar.gz
Expand All @@ -321,6 +334,11 @@ jobs:
scripts/build.sh --with-ui CC=gcc CXX=g++ STATIC=1
fi

- name: Verify UI static linking
run: |
file build/c/codebase-memory-mcp
ldd build/c/codebase-memory-mcp 2>&1 | grep -q "not a dynamic executable" || ldd build/c/codebase-memory-mcp 2>&1 | grep -q "statically linked"

- name: Archive UI binary (canonical package-release.sh)
run: scripts/package-release.sh linux ${{ matrix.arch }}-portable --variant ui

Expand Down
30 changes: 18 additions & 12 deletions .github/workflows/_smoke.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Reusable: smoke test every binary (standard + UI, all platforms)
# Reusable: smoke test shipped release binaries on all platforms
name: Smoke

on:
Expand All @@ -8,6 +8,10 @@ on:
description: 'Smoke the shipped binaries on the broad platform matrix (extra OS versions) instead of the core set'
type: boolean
default: false
ui_only:
description: 'Smoke only the UI runtime archives'
type: boolean
default: false

permissions:
contents: read
Expand All @@ -30,6 +34,7 @@ jobs:
id: set
env:
BROAD: ${{ inputs.broad_platforms }}
UI_ONLY: ${{ inputs.ui_only }}
run: |
CORE_UNIX='[
{"os":"ubuntu-latest","goos":"linux","goarch":"amd64"},
Expand Down Expand Up @@ -72,13 +77,14 @@ jobs:
WIN=$(jq -cn --argjson a "$CORE_WIN" '$a')
PORTABLE=$(jq -cn --argjson a "$CORE_PORTABLE" '$a')
fi
# Expand each OS entry over both variants into a FLAT include list so
# every {os,variant} runs as its own job. A {variant:[...],include:$LIST}
# matrix collapses under GitHub's include-merge (later os entries
# overwrite the variant combos last-wins), which silently dropped every
# OS except the last — e.g. only windows-11-arm ran, never windows-latest.
# Building the cartesian explicitly avoids that.
VARIANTS='["standard","ui"]'
# Keep the variant dimension explicit because the smoke wrappers use it
# to require and validate the UI pack. VT-focused dry runs select only
# UI; release compatibility retains both variants until migration.
if [ "$UI_ONLY" = "true" ]; then
VARIANTS='["ui"]'
else
VARIANTS='["standard","ui"]'
fi
UNIX_M=$(jq -cn --argjson a "$UNIX" --argjson v "$VARIANTS" '{include:[$a[] as $o | $v[] as $t | ($o + {variant:$t})]}')
WIN_M=$(jq -cn --argjson a "$WIN" --argjson v "$VARIANTS" '{include:[$a[] as $o | $v[] as $t | ($o + {variant:$t})]}')
PORTABLE_M=$(jq -cn --argjson a "$PORTABLE" --argjson v "$VARIANTS" '{include:[$a[] as $o | $v[] as $t | ($o + {variant:$t})]}')
Expand Down Expand Up @@ -133,8 +139,8 @@ jobs:
scripts/security-install.sh "$BIN"
scripts/security-network.sh "$BIN"

- name: MCP robustness test (linux-amd64 standard only)
if: matrix.variant == 'standard' && matrix.goos == 'linux' && matrix.goarch == 'amd64'
- name: MCP robustness test (selected linux-amd64 runtime)
if: matrix.goos == 'linux' && matrix.goarch == 'amd64' && (inputs.ui_only || matrix.variant == 'standard')
run: |
BIN="$RUNNER_TEMP/cbm-artifact/codebase-memory-mcp"
scripts/security-fuzz.sh "$BIN"
Expand Down Expand Up @@ -206,8 +212,8 @@ jobs:
mkdir -p "$ARTIFACT_DIR"
unzip -o "codebase-memory-mcp${SUFFIX}-windows-${ARCH}.zip" -d "$ARTIFACT_DIR"
test -s "$ARTIFACT_DIR/codebase-memory-mcp.exe"
# ONE binary per platform: a payload sibling would mean the
# AV-flagged launcher stub came back.
# ONE executable per runtime set: a payload sibling would mean the
# removed launcher split came back.
test ! -e "$ARTIFACT_DIR/codebase-memory-mcp.payload.exe"
# Pre-flight the image from a profile-rooted directory: the shared
# runner workspace is deliberately not a valid install root.
Expand Down
47 changes: 38 additions & 9 deletions .github/workflows/_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,37 @@ jobs:
env:
CCACHE_DIR: ${{ github.workspace }}/.ccache

# The package wrappers own their own runtime-set publication and lease-lock
# implementations. Run the same suites against both host families so Unix
# inode/flock behavior and Windows handle/link behavior are each gating,
# without adding Node/Go provisioning to every sanitizer/container leg.
test-package-wrappers:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 30
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: pkg/go/go.mod
cache: false

- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22"

- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.12"

- name: Test Go, npm, and PyPI runtime-set wrappers
shell: bash
run: scripts/ci/test-package-wrappers.sh

# 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
Expand Down Expand Up @@ -394,6 +425,7 @@ jobs:
mingw-w64-clang-${{ matrix.pkg }}-ccache
make
git
zip

- name: Create protected per-user temp root
# One implementation, shared with _soak.yml and the VM leg
Expand Down Expand Up @@ -502,7 +534,7 @@ jobs:
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
# (the sanitizer C suite): these drive a real product binary + external UI pack
# 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),
Expand Down Expand Up @@ -530,10 +562,6 @@ jobs:
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:
Expand All @@ -543,11 +571,12 @@ jobs:
ccache-guards-${{ github.ref }}-
ccache-guards-

- name: Build product binary with embedded UI
- name: Build product binary with external UI assets and guard seams
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++
# --with-ui builds the frontend and its content-addressed asset pack, 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++ TEST_SEAMS=1
env:
CCACHE_DIR: ${{ github.workspace }}/.ccache
CCACHE_MAXSIZE: 1500M
Expand Down
35 changes: 28 additions & 7 deletions .github/workflows/dry-run.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ jobs:
uses: ./.github/workflows/_build.yml
with:
attest: false
ui_only: true

# ── Smoke test every binary ────────────────────────────────────
# Run unless builds were skipped or a build leg FAILED. Every build leg
Expand All @@ -102,6 +103,7 @@ jobs:
uses: ./.github/workflows/_smoke.yml
with:
broad_platforms: true
ui_only: true

# ── Soak tests (optional, parallel with smoke) ────────────────
soak:
Expand Down Expand Up @@ -149,22 +151,41 @@ jobs:
# Same extraction the release verify job performs, so the bytes handed to
# VirusTotal here are the bytes a user would download and run.
- name: Extract archive artifacts
run: scripts/ci/extract-release-archives.sh assets binaries
run: |
scripts/ci/extract-release-archives.sh assets binaries \
--archive-scope=ui \
--expect-archives=8 \
--expect-binaries=8 \
--expect-packs=8 \
--expect-runtime-files=32

- name: VirusTotal scan
uses: crazy-max/ghaction-virustotal@936d8c5c00afe97d3d9a1af26d017cfdf26800a2 # v5.0.0
id: virustotal
with:
vt_api_key: ${{ secrets.VIRUS_TOTAL_SCANNER_API_KEY }}
files: binaries/*
files: binaries/objects/*
request_rate: 4

# Same ZERO-tolerance gate as the release. A detection here is a real
# finding even when it is a false positive: it is what the release gate
# will do, so the dry run should say so now. Resolve it upstream (vendor
# false-positive submission for the exact hashes, then re-run) — never by
# loosening this gate.
# Same ZERO-tolerance gate as the release. Every artifact must arrive
# naturally clean; any malicious or suspicious verdict fails this run.
- name: Wait for VirusTotal results
env:
VT_API_KEY: ${{ secrets.VIRUS_TOTAL_SCANNER_API_KEY }}
VT_ANALYSIS: ${{ steps.virustotal.outputs.analysis }}
VT_EXPECTED_SCAN_SET: binaries/scan-set.tsv
VT_ASSOCIATIONS: binaries/associations.tsv
VT_RESULTS_PATH: binaries/vt-results.tsv
MIN_ENGINES: 50
run: scripts/ci/check-virustotal.sh

- name: Preserve VirusTotal scope and result evidence
if: ${{ always() }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: virustotal-evidence-dry-run-${{ github.run_id }}
path: |
binaries/associations.tsv
binaries/scan-set.tsv
binaries/vt-results.tsv
if-no-files-found: warn
Loading
Loading