Merge pull request #1316 from DeusData/fix/launcher-av-false-positive

fix(windows): ship one binary — remove the launcher stub flagged as a dropper
This commit is contained in:
Martin Vogel
2026-07-28 17:04:21 +02:00
committed by GitHub
33 changed files with 1063 additions and 11070 deletions
+8 -10
View File
@@ -206,18 +206,18 @@ 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"
test -s "$ARTIFACT_DIR/codebase-memory-mcp.payload.exe"
# Pre-flight both images from a profile-rooted directory: the shared
# runner workspace is deliberately not a valid launcher bundle root.
# ONE binary per platform: a payload sibling would mean the
# AV-flagged launcher stub 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.
PROFILE_ROOT="$(cygpath -u "$USERPROFILE")"
LAUNCH_DIR="$(mktemp -d "$PROFILE_ROOT/cbm-release-version.XXXXXX")"
# Best-effort cleanup with one retry: Windows holds the just-exited
# image's file busy for ~100ms (section teardown / first-touch AV),
# and a temp dir on an ephemeral runner must never fail the job.
trap 'rm -rf "$LAUNCH_DIR" 2>/dev/null || { sleep 2; rm -rf "$LAUNCH_DIR" 2>/dev/null || true; }' EXIT
cp "$ARTIFACT_DIR/codebase-memory-mcp.exe" \
"$ARTIFACT_DIR/codebase-memory-mcp.payload.exe" "$LAUNCH_DIR/"
"$LAUNCH_DIR/codebase-memory-mcp.payload.exe" --version
cp "$ARTIFACT_DIR/codebase-memory-mcp.exe" "$LAUNCH_DIR/"
"$LAUNCH_DIR/codebase-memory-mcp.exe" --version
# ONE harness for every venue: the same script win.sh smoke-install and
@@ -241,13 +241,11 @@ jobs:
run: |
ARTIFACT_DIR="$(cygpath -u "$RUNNER_TEMP")/cbm-artifact"
scripts/security-strings.sh "$ARTIFACT_DIR/codebase-memory-mcp.exe"
scripts/security-strings.sh "$ARTIFACT_DIR/codebase-memory-mcp.payload.exe"
PROFILE_ROOT="$(cygpath -u "$USERPROFILE")"
SECURITY_DIR="$(mktemp -d "$PROFILE_ROOT/cbm-release-security.XXXXXX")"
# Best-effort cleanup with one retry (see the version preflight).
trap 'rm -rf "$SECURITY_DIR" 2>/dev/null || { sleep 2; rm -rf "$SECURITY_DIR" 2>/dev/null || true; }' EXIT
cp "$ARTIFACT_DIR/codebase-memory-mcp.exe" \
"$ARTIFACT_DIR/codebase-memory-mcp.payload.exe" "$SECURITY_DIR/"
cp "$ARTIFACT_DIR/codebase-memory-mcp.exe" "$SECURITY_DIR/"
TMPDIR="$SECURITY_DIR" \
scripts/security-install.sh "$SECURITY_DIR/codebase-memory-mcp.exe"
@@ -256,7 +254,7 @@ jobs:
run: |
& "C:\Program Files\Windows Defender\MpCmdRun.exe" -SignatureUpdate 2>$null
$artifactDir = Join-Path $env:RUNNER_TEMP "cbm-artifact"
foreach ($binary in @("codebase-memory-mcp.exe", "codebase-memory-mcp.payload.exe")) {
foreach ($binary in @("codebase-memory-mcp.exe")) {
$result = & "C:\Program Files\Windows Defender\MpCmdRun.exe" -Scan -ScanType 3 -File (Join-Path $artifactDir $binary) -DisableRemediation
$code = $LASTEXITCODE
Write-Host $result
+6 -35
View File
@@ -311,12 +311,7 @@ GIT_SRCS = src/git/git_context.c
CLI_SRCS = src/cli/cli.c src/cli/progress_sink.c src/cli/hook_augment.c \
src/cli/agent_clients.c src/cli/agent_profiles.c \
src/cli/config_json_like.c src/cli/config_toml_edit.c src/cli/config_yaml_edit.c \
src/cli/config_text_edit.c src/cli/activation_transaction.c \
src/cli/windows_launcher_state.c
# Tiny permanent Windows launcher. It deliberately links only the stable
# launcher-state module and Windows SDK libraries, never the parser/indexer.
WINDOWS_LAUNCHER_SRC = src/launcher/windows_launcher.c
src/cli/config_text_edit.c src/cli/activation_transaction.c
# UI module (graph visualization)
UI_SRCS = \
@@ -490,8 +485,7 @@ TEST_TRACES_SRCS = tests/test_traces.c
TEST_CLI_SRCS = tests/test_cli.c tests/test_agent_clients.c tests/test_agent_profiles.c \
tests/test_config_json_like.c \
tests/test_config_toml_edit.c tests/test_config_yaml_edit.c tests/test_config_text_edit.c \
tests/test_activation_transaction.c \
tests/test_windows_launcher_state.c
tests/test_activation_transaction.c
TEST_MEM_SRCS = tests/test_mem.c
@@ -601,7 +595,7 @@ PP_OBJ_TSAN = $(BUILD_DIR)/tsan_preprocessor.o
# ── Targets ──────────────────────────────────────────────────────
.PHONY: test test-par test-repro test-foundation test-tsan test-daemon-smoke cbm cbm-launcher cbm-with-ui frontend embed clean-c lint lint-tidy lint-cppcheck lint-format security
.PHONY: test test-par test-repro test-foundation test-tsan test-daemon-smoke cbm cbm-with-ui frontend embed clean-c lint lint-tidy lint-cppcheck lint-format security
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
@@ -841,26 +835,6 @@ OBJS_VENDORED_PROD = $(MIMALLOC_OBJ_PROD) $(SQLITE3_OBJ_PROD) $(TRE_OBJ_PROD) $(
MAIN_SRC = src/main.c
WINDOWS_LAUNCHER_TARGET :=
ifeq ($(IS_MINGW),yes)
WINDOWS_LAUNCHER_TARGET := $(BUILD_DIR)/codebase-memory-mcp-launcher.exe
endif
# Keep the explicit .exe target even on non-Windows hosts: packaging contracts
# and native-Windows invocations share this single artifact name. A non-Windows
# direct invocation builds a diagnostic stub and is not part of `cbm`.
$(BUILD_DIR)/codebase-memory-mcp-launcher.exe: $(WINDOWS_LAUNCHER_SRC) src/cli/windows_launcher_state.c src/cli/windows_launcher_state.h | $(BUILD_DIR)
$(CC) $(CFLAGS_PROD) -o $@ \
$(WINDOWS_LAUNCHER_SRC) src/cli/windows_launcher_state.c \
$(if $(filter yes,$(IS_MINGW)),-municode -lbcrypt $(WIN32_LIBS),)
# Native-Windows compatibility fixture: its launcher ABI intentionally does
# not fit the production payload's default [1,1] range. It is never packaged.
$(BUILD_DIR)/codebase-memory-mcp-launcher-abi2.exe: $(WINDOWS_LAUNCHER_SRC) src/cli/windows_launcher_state.c src/cli/windows_launcher_state.h | $(BUILD_DIR)
$(CC) $(CFLAGS_PROD) -DCBM_WINDOWS_LAUNCHER_ABI_CURRENT=2 -o $@ \
$(WINDOWS_LAUNCHER_SRC) src/cli/windows_launcher_state.c \
$(if $(filter yes,$(IS_MINGW)),-municode -lbcrypt $(WIN32_LIBS),)
$(BUILD_DIR)/codebase-memory-mcp: $(MAIN_SRC) $(PROD_SRCS) $(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) $(OBJS_VENDORED_PROD) | $(BUILD_DIR)
$(CC) $(CFLAGS_PROD) -o $@ \
$(MAIN_SRC) $(PROD_SRCS) \
@@ -868,12 +842,9 @@ $(BUILD_DIR)/codebase-memory-mcp: $(MAIN_SRC) $(PROD_SRCS) $(EXTRACTION_SRCS) $(
$(OBJS_VENDORED_PROD) \
$(LDFLAGS)
cbm: $(BUILD_DIR)/codebase-memory-mcp $(WINDOWS_LAUNCHER_TARGET)
cbm: $(BUILD_DIR)/codebase-memory-mcp
@echo "Built: $(BUILD_DIR)/codebase-memory-mcp"
cbm-launcher: $(BUILD_DIR)/codebase-memory-mcp-launcher.exe
@echo "Built: $(BUILD_DIR)/codebase-memory-mcp-launcher.exe"
# ── Build with embedded UI (requires Node.js) ───────────────────
# Swap embedded_stub.c for the generated embedded_assets.c
@@ -889,7 +860,7 @@ frontend:
embed: frontend
scripts/embed-frontend.sh graph-ui/dist $(BUILD_DIR)/embedded
cbm-with-ui: embed $(OBJS_VENDORED_PROD) $(WINDOWS_LAUNCHER_TARGET)
cbm-with-ui: embed $(OBJS_VENDORED_PROD)
$(CC) $(CFLAGS_PROD) -o $(BUILD_DIR)/codebase-memory-mcp \
$(MAIN_SRC) $(PROD_SRCS_WITH_ASSETS) \
$(EXTRACTION_SRCS) $(AC_LZ4_SRCS) $(ZSTD_SRCS) $(SQLITE_WRITER_SRC) \
@@ -919,7 +890,7 @@ SYSROOT_FLAG = $(if $(SYSROOT),-isysroot $(SYSROOT),)
LINT_SRCS = $(FOUNDATION_SRCS) $(STORE_SRCS) $(CYPHER_SRCS) $(MCP_SRCS) $(DAEMON_SRCS) \
$(DISCOVER_SRCS) $(GRAPH_BUFFER_SRCS) $(PIPELINE_SRCS) $(SIMHASH_SRCS) $(SEMANTIC_SRCS) \
$(TRACES_SRCS) $(WATCHER_SRCS) $(CLI_SRCS) $(EXTRACTION_SRCS) $(AC_LZ4_SRCS) \
$(ZSTD_SRCS) $(SQLITE_WRITER_SRC) $(MAIN_SRC) $(WINDOWS_LAUNCHER_SRC)
$(ZSTD_SRCS) $(SQLITE_WRITER_SRC) $(MAIN_SRC)
LINT_HDRS = $(wildcard src/**/*.h src/*.h $(CBM_DIR)/*.h)
LINT_TEST_SRCS = $(ALL_TEST_SRCS)
+37 -20
View File
@@ -14,10 +14,8 @@ Add-Type -AssemblyName System.Net.Http
$Repo = "DeusData/codebase-memory-mcp"
$InstallDir = "$env:LOCALAPPDATA\Programs\codebase-memory-mcp"
$BinName = "codebase-memory-mcp.exe"
$PayloadName = "codebase-memory-mcp.payload.exe"
$WindowsArchiveNames = @(
$BinName,
$PayloadName,
"LICENSE",
"install.ps1",
"THIRD_PARTY_NOTICES.md"
@@ -237,32 +235,28 @@ try {
exit 1
}
# Extract the complete, validated bundle. The portable launcher owns the
# managed install transaction and contains its adjacent payload.
# Extract the validated bundle. Windows ships ONE binary, exactly like Linux
# and macOS; this script is what replaces it, because a running executable
# cannot replace itself on Windows. Re-running this script IS the update.
Write-Host "Extracting..."
Expand-Archive -Path "$TmpDir\$Archive" -DestinationPath $TmpDir -Force
$DownloadedLauncher = Join-Path $TmpDir $BinName
$DownloadedPayload = Join-Path $TmpDir $PayloadName
if (-not (Test-Path -LiteralPath $DownloadedLauncher -PathType Leaf) -or
-not (Test-Path -LiteralPath $DownloadedPayload -PathType Leaf)) {
Write-Host "error: launcher or payload not found after extraction" -ForegroundColor Red
$DownloadedBinary = Join-Path $TmpDir $BinName
if (-not (Test-Path -LiteralPath $DownloadedBinary -PathType Leaf)) {
Write-Host "error: $BinName not found after extraction" -ForegroundColor Red
Remove-Item -Recurse -Force $TmpDir
exit 1
}
$launcherItem = Get-Item -LiteralPath $DownloadedLauncher
$payloadItem = Get-Item -LiteralPath $DownloadedPayload
if (($launcherItem.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -or
($payloadItem.Attributes -band [System.IO.FileAttributes]::ReparsePoint)) {
$binaryItem = Get-Item -LiteralPath $DownloadedBinary
if ($binaryItem.Attributes -band [System.IO.FileAttributes]::ReparsePoint) {
Write-Host "error: refusing reparse-point executable in release archive" -ForegroundColor Red
Remove-Item -Recurse -Force $TmpDir
exit 1
}
# Verify the complete launcher/payload path before it asks all coordinated CBM
# processes to stop.
# Prove the downloaded binary runs before touching an existing installation.
try {
$candidateVersion = & $DownloadedLauncher --version 2>&1
$candidateVersion = & $DownloadedBinary --version 2>&1
if ($LASTEXITCODE -ne 0) { throw "candidate exited with $LASTEXITCODE" }
Write-Host "Verified candidate: $candidateVersion"
} catch {
@@ -272,13 +266,36 @@ try {
}
$Dest = Join-Path $InstallDir $BinName
# Retire the running installation before replacing it. Windows keeps an image
# lock on a running .exe: the file cannot be overwritten, but it CAN be renamed
# out of the way, which is what makes an in-place update possible from here.
if (Test-Path -LiteralPath $Dest -PathType Leaf) {
try { & $Dest daemon stop 2>&1 | Out-Null } catch { }
$retired = "$Dest.retired-$(Get-Date -Format yyyyMMddHHmmss)"
$renamed = $false
foreach ($attempt in 1..10) {
try { Move-Item -LiteralPath $Dest -Destination $retired -Force -ErrorAction Stop; $renamed = $true; break }
catch { Start-Sleep -Milliseconds 500 }
}
if (-not $renamed) {
Write-Host "error: could not retire the existing $BinName — close all running" -ForegroundColor Red
Write-Host " codebase-memory-mcp sessions and coding agents, then re-run." -ForegroundColor Red
Remove-Item -Recurse -Force $TmpDir -ErrorAction SilentlyContinue
exit 1
}
# A retired image stays locked until its last process exits; delete it when
# we can, and leave it for the next run when we cannot. Never fail here.
Remove-Item -LiteralPath $retired -Force -ErrorAction SilentlyContinue
}
Get-ChildItem -LiteralPath $InstallDir -Filter "$BinName.retired-*" -ErrorAction SilentlyContinue |
ForEach-Object { Remove-Item -LiteralPath $_.FullName -Force -ErrorAction SilentlyContinue }
$InstallArgs = @("install", "-y", "--force", "--dir=$InstallDir")
if ($SkipConfig) { $InstallArgs += "--skip-config" }
# PowerShell's call operator waits for the launcher, which in turn contains the
# install payload in its native job and relays the exact exit status.
& $DownloadedLauncher @InstallArgs
& $DownloadedBinary @InstallArgs
if ($LASTEXITCODE -ne 0) {
Write-Host "error: coordinated activation failed (exit code $LASTEXITCODE)" -ForegroundColor Red
Write-Host "error: installation failed (exit code $LASTEXITCODE)" -ForegroundColor Red
Remove-Item -Recurse -Force $TmpDir -ErrorAction SilentlyContinue
exit 1
}
+10 -14
View File
@@ -8,23 +8,19 @@ const fs = require('fs');
const { spawnSync } = require('child_process');
const isWindows = process.platform === 'win32';
const windowsLauncherName = 'codebase-memory-mcp.exe';
// npm is a portable one-shot wrapper. On Windows the public .exe in the
// release archive is the permanent launcher, so privately validate the
// release pair, cache both files, and enter the native process through that
// launcher. The adjacent payload remains the immutable cache readiness signal.
const binName = isWindows ? 'codebase-memory-mcp.payload.exe' : 'codebase-memory-mcp';
// npm is a portable one-shot wrapper. Every platform ships exactly ONE binary,
// so the cached file is the executed file — there is no launcher pair to keep
// in sync and no adjacent payload to validate.
const binName = isWindows ? 'codebase-memory-mcp.exe' : 'codebase-memory-mcp';
const binPath = path.join(__dirname, 'bin', binName);
const launcherPath = path.join(__dirname, 'bin', windowsLauncherName);
const executionPath = isWindows ? launcherPath : binPath;
const executionPath = binPath;
const cacheReady = () => {
if (!fs.existsSync(binPath) || (isWindows && !fs.existsSync(launcherPath))) {
if (!fs.existsSync(binPath)) {
return false;
}
if (!isWindows) return true;
// The launcher resolving --version through its adjacent payload validates
// both halves of the immutable Windows cache pair.
const probe = spawnSync(launcherPath, ['--version'], {
// A cached Windows binary that cannot report its own version is not usable.
const probe = spawnSync(binPath, ['--version'], {
stdio: 'ignore',
timeout: 15_000,
windowsHide: true,
@@ -77,8 +73,8 @@ if (mutation) {
process.stderr.write(
`This npm Windows copy is portable. Use "${packageCommand}" for package ` +
'maintenance (add -g for a global install), or run ' +
'"codebase-memory-mcp install --yes" once to create ' +
'a managed launcher with coordinated self-update/uninstall.\n',
'"codebase-memory-mcp install --yes" once for a managed install with its ' +
'own install.ps1 for updates.\n',
);
}
+46 -54
View File
@@ -18,8 +18,7 @@ const MAX_REDIRECTS = 5;
const DOWNLOAD_HOP_TIMEOUT_MS = 120_000;
const CANDIDATE_TIMEOUT_MS = 15_000;
const MAX_CHECKSUM_MANIFEST_BYTES = 1024 * 1024;
const WINDOWS_LAUNCHER_NAME = 'codebase-memory-mcp.exe';
const WINDOWS_PAYLOAD_NAME = 'codebase-memory-mcp.payload.exe';
const WINDOWS_BINARY_NAME = 'codebase-memory-mcp.exe';
const UNIX_ARCHIVE_NAMES = [
'codebase-memory-mcp',
'LICENSE',
@@ -27,8 +26,7 @@ const UNIX_ARCHIVE_NAMES = [
'THIRD_PARTY_NOTICES.md',
];
const WINDOWS_ARCHIVE_NAMES = [
WINDOWS_LAUNCHER_NAME,
WINDOWS_PAYLOAD_NAME,
WINDOWS_BINARY_NAME,
'LICENSE',
'install.ps1',
'THIRD_PARTY_NOTICES.md',
@@ -258,7 +256,7 @@ function processIsAlive(pid) {
}
}
function readPairLockOwner(lockPath) {
function readBinaryLockOwner(lockPath) {
try {
const owner = JSON.parse(
fs.readFileSync(path.join(lockPath, 'owner.json'), 'utf8'),
@@ -273,9 +271,9 @@ function readPairLockOwner(lockPath) {
}
}
function tryReclaimPairLock(lockPath, contenderToken) {
function tryReclaimBinaryLock(lockPath, contenderToken) {
let reclaim = false;
const owner = readPairLockOwner(lockPath);
const owner = readBinaryLockOwner(lockPath);
if (owner) {
reclaim = !processIsAlive(owner.pid);
} else {
@@ -299,7 +297,7 @@ function tryReclaimPairLock(lockPath, contenderToken) {
return true;
}
function acquireWindowsPairLock(destDir) {
function acquireWindowsBinaryLock(destDir) {
const lockPath = path.join(destDir, WINDOWS_PAIR_LOCK_NAME);
const token = crypto.randomBytes(16).toString('hex');
const deadline = Date.now() + WINDOWS_PAIR_LOCK_WAIT_MS;
@@ -319,7 +317,7 @@ function acquireWindowsPairLock(destDir) {
return { lockPath, token };
} catch (err) {
if (err.code !== 'EEXIST') throw err;
if (tryReclaimPairLock(lockPath, token)) continue;
if (tryReclaimBinaryLock(lockPath, token)) continue;
if (Date.now() >= deadline) {
throw new Error('timed out waiting for Windows package-cache publication lock');
}
@@ -328,8 +326,8 @@ function acquireWindowsPairLock(destDir) {
}
}
function releaseWindowsPairLock(lock) {
const owner = readPairLockOwner(lock.lockPath);
function releaseWindowsBinaryLock(lock) {
const owner = readBinaryLockOwner(lock.lockPath);
if (!owner || owner.token !== lock.token || owner.pid !== process.pid) {
throw new Error('Windows package-cache publication lock ownership changed');
}
@@ -337,17 +335,14 @@ function releaseWindowsPairLock(lock) {
fs.rmdirSync(lock.lockPath);
}
function windowsPairReady(destDir, verifier = verifyCandidate) {
const launcher = path.join(destDir, WINDOWS_LAUNCHER_NAME);
const payload = path.join(destDir, WINDOWS_PAYLOAD_NAME);
function windowsBinaryReady(destDir, verifier = verifyCandidate) {
const binary = path.join(destDir, WINDOWS_BINARY_NAME);
try {
const launcherStatus = fs.lstatSync(launcher);
const payloadStatus = fs.lstatSync(payload);
if (!launcherStatus.isFile() || launcherStatus.isSymbolicLink() ||
!payloadStatus.isFile() || payloadStatus.isSymbolicLink()) {
const status = fs.lstatSync(binary);
if (!status.isFile() || status.isSymbolicLink()) {
return false;
}
verifier(launcher);
verifier(binary);
return true;
} catch (_) {
return false;
@@ -364,8 +359,8 @@ function pathMatchesDigest(candidatePath, expectedDigest) {
}
}
function removeOwnedPublishedPair(destDir, publishedDigests) {
for (const name of [WINDOWS_PAYLOAD_NAME, WINDOWS_LAUNCHER_NAME]) {
function removeOwnedPublishedBinary(destDir, publishedDigests) {
for (const name of [WINDOWS_BINARY_NAME]) {
const digest = publishedDigests.get(name);
const target = path.join(destDir, name);
if (digest && pathMatchesDigest(target, digest)) {
@@ -374,8 +369,8 @@ function removeOwnedPublishedPair(destDir, publishedDigests) {
}
}
function restorePairBackups(destDir, backups) {
for (const name of [WINDOWS_LAUNCHER_NAME, WINDOWS_PAYLOAD_NAME]) {
function restoreBinaryBackups(destDir, backups) {
for (const name of [WINDOWS_BINARY_NAME]) {
const backup = backups.get(name);
const target = path.join(destDir, name);
if (!backup || fs.existsSync(target)) continue;
@@ -383,15 +378,15 @@ function restorePairBackups(destDir, backups) {
}
}
function installWindowsPairAtomically(sourceDir, destDir, verifier = verifyCandidate) {
// Authenticate the complete source pair before it can contend for the cache.
verifier(path.join(sourceDir, WINDOWS_LAUNCHER_NAME));
const lock = acquireWindowsPairLock(destDir);
function installWindowsBinaryAtomically(sourceDir, destDir, verifier = verifyCandidate) {
// Authenticate the source binary before it can contend for the cache.
verifier(path.join(sourceDir, WINDOWS_BINARY_NAME));
const lock = acquireWindowsBinaryLock(destDir);
let operationError = null;
try {
// A contender may have completed while this process waited. Its executable
// pair wins and is never moved or deleted.
if (windowsPairReady(destDir, verifier)) return;
// wins and is never moved or deleted.
if (windowsBinaryReady(destDir, verifier)) return;
const transaction = crypto.randomBytes(16).toString('hex');
const stagedPaths = new Map();
@@ -399,7 +394,7 @@ function installWindowsPairAtomically(sourceDir, destDir, verifier = verifyCandi
const backups = new Map();
const publishedDigests = new Map();
try {
for (const name of [WINDOWS_LAUNCHER_NAME, WINDOWS_PAYLOAD_NAME]) {
for (const name of [WINDOWS_BINARY_NAME]) {
const staged = path.join(destDir, `.cbm-pair-stage-${transaction}-${name}`);
fs.copyFileSync(path.join(sourceDir, name), staged, fs.constants.COPYFILE_EXCL);
fs.chmodSync(staged, 0o755);
@@ -407,7 +402,7 @@ function installWindowsPairAtomically(sourceDir, destDir, verifier = verifyCandi
stagedDigests.set(name, fileSha256(staged));
}
for (const name of [WINDOWS_LAUNCHER_NAME, WINDOWS_PAYLOAD_NAME]) {
for (const name of [WINDOWS_BINARY_NAME]) {
const target = path.join(destDir, name);
const status = fs.lstatSync(target, { throwIfNoEntry: false });
if (!status) continue;
@@ -419,32 +414,31 @@ function installWindowsPairAtomically(sourceDir, destDir, verifier = verifyCandi
backups.set(name, backup);
}
// Payload is the readiness signal and is deliberately published last.
for (const name of [WINDOWS_LAUNCHER_NAME, WINDOWS_PAYLOAD_NAME]) {
for (const name of [WINDOWS_BINARY_NAME]) {
const target = path.join(destDir, name);
fs.renameSync(stagedPaths.get(name), target);
stagedPaths.delete(name);
publishedDigests.set(name, stagedDigests.get(name));
}
if (!windowsPairReady(destDir, verifier)) {
throw new Error('published Windows package-cache pair failed verification');
if (!windowsBinaryReady(destDir, verifier)) {
throw new Error('published Windows package-cache binary failed verification');
}
for (const backup of backups.values()) {
try { fs.unlinkSync(backup); } catch (_) { /* valid pair is already committed */ }
try { fs.unlinkSync(backup); } catch (_) { /* valid binary is already committed */ }
}
return;
} catch (err) {
// If a non-cooperating contender nevertheless published a valid pair,
// If a non-cooperating contender nevertheless published a valid binary,
// preserve that winner. Otherwise remove only our exact staged bytes and
// restore the prior files when their target names are still absent.
if (windowsPairReady(destDir, verifier)) {
if (windowsBinaryReady(destDir, verifier)) {
for (const backup of backups.values()) {
try { fs.unlinkSync(backup); } catch (_) { /* winner remains authoritative */ }
}
return;
}
removeOwnedPublishedPair(destDir, publishedDigests);
restorePairBackups(destDir, backups);
removeOwnedPublishedBinary(destDir, publishedDigests);
restoreBinaryBackups(destDir, backups);
throw err;
} finally {
for (const staged of stagedPaths.values()) {
@@ -456,7 +450,7 @@ function installWindowsPairAtomically(sourceDir, destDir, verifier = verifyCandi
throw err;
} finally {
try {
releaseWindowsPairLock(lock);
releaseWindowsBinaryLock(lock);
} catch (releaseError) {
if (!operationError) throw releaseError;
}
@@ -552,25 +546,24 @@ async function main() {
const platform = getPlatform();
const arch = getArch();
const ext = platform === 'windows' ? 'zip' : 'tar.gz';
// Package-manager shims are portable one-shot instances on Windows. They
// enter through the cached launcher and never create managed launcher state.
// The payload path remains the immutable pair's readiness signal.
// Package-manager shims are portable one-shot instances: one cached binary
// per platform, entered directly.
const binName = platform === 'windows'
? WINDOWS_PAYLOAD_NAME
? WINDOWS_BINARY_NAME
: 'codebase-memory-mcp';
const binPath = path.join(BIN_DIR, binName);
const cacheNames = platform === 'windows'
? [WINDOWS_LAUNCHER_NAME, WINDOWS_PAYLOAD_NAME]
? [WINDOWS_BINARY_NAME]
: [binName];
const extractedNames = platform === 'windows'
? [WINDOWS_LAUNCHER_NAME, WINDOWS_PAYLOAD_NAME]
? [WINDOWS_BINARY_NAME]
: [binName];
const archiveNames = platform === 'windows'
? WINDOWS_ARCHIVE_NAMES
: UNIX_ARCHIVE_NAMES;
const cachePaths = cacheNames.map((name) => path.join(BIN_DIR, name));
if (platform === 'windows' && windowsPairReady(BIN_DIR)) return;
if (platform === 'windows' && windowsBinaryReady(BIN_DIR)) return;
if (platform !== 'windows' &&
cachePaths.every((candidate) => fs.existsSync(candidate))) {
try {
@@ -631,13 +624,13 @@ async function main() {
if (platform === 'windows') {
// The launcher resolves the adjacent portable payload in this directory.
verifyCandidate(extractedPaths.get(WINDOWS_LAUNCHER_NAME));
verifyCandidate(extractedPaths.get(WINDOWS_BINARY_NAME));
} else {
verifyCandidate(extractedPaths.get(binName));
}
if (platform === 'windows') {
installWindowsPairAtomically(tmpDir, BIN_DIR);
installWindowsBinaryAtomically(tmpDir, BIN_DIR);
} else {
const staged = path.join(
BIN_DIR,
@@ -683,10 +676,9 @@ if (require.main === module || module.parent == null) {
module.exports = {
UNIX_ARCHIVE_NAMES,
WINDOWS_LAUNCHER_NAME,
WINDOWS_PAYLOAD_NAME,
WINDOWS_BINARY_NAME,
extractExactTarArchive,
installWindowsPairAtomically,
installWindowsBinaryAtomically,
validateExactTarMemberListing,
windowsPairReady,
windowsBinaryReady,
};
+31 -51
View File
@@ -8,10 +8,9 @@ const test = require('node:test');
const {
UNIX_ARCHIVE_NAMES,
WINDOWS_LAUNCHER_NAME,
WINDOWS_PAYLOAD_NAME,
WINDOWS_BINARY_NAME,
extractExactTarArchive,
installWindowsPairAtomically,
installWindowsBinaryAtomically,
validateExactTarMemberListing,
} = require('../install.js');
@@ -53,25 +52,20 @@ test('Unix extraction requests only the validated root executable', () => {
);
});
function writePair(directory, tag) {
function writeBinary(directory, tag) {
fs.mkdirSync(directory, { recursive: true });
fs.writeFileSync(path.join(directory, WINDOWS_LAUNCHER_NAME), `launcher:${tag}`);
fs.writeFileSync(path.join(directory, WINDOWS_PAYLOAD_NAME), `payload:${tag}`);
fs.writeFileSync(path.join(directory, WINDOWS_BINARY_NAME), `binary:${tag}`);
}
function fakePairVerifier(launcherPath) {
const launcher = fs.readFileSync(launcherPath, 'utf8');
const payload = fs.readFileSync(
path.join(path.dirname(launcherPath), WINDOWS_PAYLOAD_NAME), 'utf8',
);
const match = /^launcher:(.+)$/.exec(launcher);
if (!match || payload !== `payload:${match[1]}`) {
throw new Error('launcher/payload test pair mismatch');
function fakeBinaryVerifier(binaryPath) {
const binary = fs.readFileSync(binaryPath, 'utf8');
if (!/^binary:(.+)$/.test(binary)) {
throw new Error('cached Windows binary failed verification');
}
}
function withPairDirectories(callback) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cbm-npm-pair-test-'));
function withBinaryDirectories(callback) {
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'cbm-npm-binary-test-'));
const source = path.join(root, 'source');
const destination = path.join(root, 'destination');
fs.mkdirSync(destination);
@@ -82,56 +76,42 @@ function withPairDirectories(callback) {
}
}
function assertPair(directory, tag) {
function assertBinary(directory, tag) {
assert.equal(
fs.readFileSync(path.join(directory, WINDOWS_LAUNCHER_NAME), 'utf8'),
`launcher:${tag}`,
);
assert.equal(
fs.readFileSync(path.join(directory, WINDOWS_PAYLOAD_NAME), 'utf8'),
`payload:${tag}`,
fs.readFileSync(path.join(directory, WINDOWS_BINARY_NAME), 'utf8'),
`binary:${tag}`,
);
}
test('Windows publication repairs a corrupt launcher', () => {
withPairDirectories(({ source, destination }) => {
writePair(source, 'candidate');
writePair(destination, 'old');
fs.writeFileSync(path.join(destination, WINDOWS_LAUNCHER_NAME), 'corrupt');
test('Windows publication repairs a corrupt cached binary', () => {
withBinaryDirectories(({ source, destination }) => {
writeBinary(source, 'candidate');
writeBinary(destination, 'old');
fs.writeFileSync(path.join(destination, WINDOWS_BINARY_NAME), 'corrupt');
installWindowsPairAtomically(source, destination, fakePairVerifier);
installWindowsBinaryAtomically(source, destination, fakeBinaryVerifier);
assertPair(destination, 'candidate');
assertBinary(destination, 'candidate');
});
});
test('Windows publication repairs corrupt or missing payload state', () => {
for (const payload of ['corrupt', null]) {
withPairDirectories(({ source, destination }) => {
writePair(source, 'candidate');
fs.writeFileSync(
path.join(destination, WINDOWS_LAUNCHER_NAME), 'launcher:partial',
);
if (payload !== null) {
fs.writeFileSync(
path.join(destination, WINDOWS_PAYLOAD_NAME), payload,
);
}
test('Windows publication installs into an empty cache', () => {
withBinaryDirectories(({ source, destination }) => {
writeBinary(source, 'candidate');
installWindowsPairAtomically(source, destination, fakePairVerifier);
installWindowsBinaryAtomically(source, destination, fakeBinaryVerifier);
assertPair(destination, 'candidate');
});
}
assertBinary(destination, 'candidate');
});
});
test('Windows publication preserves a valid concurrent winner', () => {
withPairDirectories(({ source, destination }) => {
writePair(source, 'loser');
writePair(destination, 'winner');
withBinaryDirectories(({ source, destination }) => {
writeBinary(source, 'loser');
writeBinary(destination, 'winner');
installWindowsPairAtomically(source, destination, fakePairVerifier);
installWindowsBinaryAtomically(source, destination, fakeBinaryVerifier);
assertPair(destination, 'winner');
assertBinary(destination, 'winner');
});
});
+9 -7
View File
@@ -95,7 +95,7 @@ test('non-Windows npm shim keeps its native payload execution path', () => {
assert.equal(path.basename(observed.calls[0].executable), 'codebase-memory-mcp');
});
test('PowerShell install mutation runs through the downloaded launcher', () => {
test('PowerShell install mutation runs through the downloaded binary', () => {
const installer = fs.readFileSync(
path.join(__dirname, '..', '..', '..', 'install.ps1'),
'utf8',
@@ -103,14 +103,16 @@ test('PowerShell install mutation runs through the downloaded launcher', () => {
assert.match(
installer,
/^\s*\$candidateVersion\s*=\s*&\s*\$DownloadedLauncher\s+--version\b/m,
/^\s*\$candidateVersion\s*=\s*&\s*\$DownloadedBinary\s+--version\b/m,
);
assert.match(
installer,
/^\s*&\s*\$DownloadedLauncher\s+@InstallArgs\b/m,
);
assert.doesNotMatch(
installer,
/^\s*&\s*\$DownloadedPayload\s+@InstallArgs\b/m,
/^\s*&\s*\$DownloadedBinary\s+@InstallArgs\b/m,
);
// One binary ships per platform — no launcher/payload pair to resolve.
assert.doesNotMatch(installer, /payload/i);
// A running .exe cannot be overwritten, so an in-place update has to retire
// the existing binary by renaming it aside first. This script IS the Windows
// update path, so losing that step would silently break every update.
assert.match(installer, /Move-Item[\s\S]{0,80}\$Dest[\s\S]{0,40}\$retired/);
});
+19 -45
View File
@@ -15,11 +15,9 @@ import urllib.request
from pathlib import Path
REPO = "DeusData/codebase-memory-mcp"
_WINDOWS_LAUNCHER_NAME = "codebase-memory-mcp.exe"
_WINDOWS_PAYLOAD_NAME = "codebase-memory-mcp.payload.exe"
_WINDOWS_BINARY_NAME = "codebase-memory-mcp.exe"
_WINDOWS_ARCHIVE_NAMES = (
_WINDOWS_LAUNCHER_NAME,
_WINDOWS_PAYLOAD_NAME,
_WINDOWS_BINARY_NAME,
"LICENSE",
"install.ps1",
"THIRD_PARTY_NOTICES.md",
@@ -332,37 +330,26 @@ def _cache_dir() -> Path:
def _bin_path(version: str) -> Path:
# The payload remains the immutable Windows cache pair's readiness signal.
# _execution_path selects the adjacent launcher for process execution.
# One binary per platform: the cached file is the executed file.
name = (
_WINDOWS_PAYLOAD_NAME
_WINDOWS_BINARY_NAME
if sys.platform == "win32"
else "codebase-memory-mcp"
)
return _cache_dir() / version / name
def _windows_pair_paths(version: str):
version_dir = _cache_dir() / version
return (
version_dir / _WINDOWS_LAUNCHER_NAME,
version_dir / _WINDOWS_PAYLOAD_NAME,
)
def _execution_path(binary: Path, target_platform: str) -> Path:
"""The cached binary is executed directly on every platform."""
return binary
def _execution_path(payload: Path, target_platform: str) -> Path:
"""Select the permanent Windows launcher without changing other platforms."""
if target_platform == "win32":
return payload.with_name(_WINDOWS_LAUNCHER_NAME)
return payload
def _windows_pair_ready(version: str) -> bool:
launcher, payload = _windows_pair_paths(version)
if not launcher.is_file() or not payload.is_file():
def _windows_binary_ready(version: str) -> bool:
binary = _cache_dir() / version / _WINDOWS_BINARY_NAME
if not binary.is_file():
return False
try:
_verify_candidate(launcher)
_verify_candidate(binary)
except RuntimeError:
return False
return True
@@ -404,21 +391,12 @@ def _download(version: str) -> Path:
_verify_checksum(tmp_archive, archive, version)
if os_name == "windows":
bin_name = _WINDOWS_PAYLOAD_NAME
extraction_names = (
_WINDOWS_LAUNCHER_NAME,
_WINDOWS_PAYLOAD_NAME,
)
else:
bin_name = "codebase-memory-mcp"
extraction_names = (bin_name,)
cache_names = extraction_names
publish_names = (
(_WINDOWS_LAUNCHER_NAME, _WINDOWS_PAYLOAD_NAME)
if os_name == "windows"
else cache_names
bin_name = (
_WINDOWS_BINARY_NAME if os_name == "windows" else "codebase-memory-mcp"
)
extraction_names = (bin_name,)
cache_names = extraction_names
publish_names = cache_names
if ext == "tar.gz":
import tarfile
with tarfile.open(tmp_archive) as tf:
@@ -447,11 +425,7 @@ def _download(version: str) -> Path:
)
extracted_paths[name] = extracted_path
try:
if os_name == "windows":
# The portable launcher must resolve this adjacent payload.
_verify_candidate(extracted_paths[_WINDOWS_LAUNCHER_NAME])
else:
_verify_candidate(extracted_paths[bin_name])
_verify_candidate(extracted_paths[bin_name])
except RuntimeError as exc:
sys.exit(f"codebase-memory-mcp: {exc}")
@@ -488,7 +462,7 @@ def _download(version: str) -> Path:
if not _files_equal_sha256(staged_paths[name], target):
raise publish_error
if os_name == "windows":
_verify_candidate(dest.parent / _WINDOWS_LAUNCHER_NAME)
_verify_candidate(dest.parent / _WINDOWS_BINARY_NAME)
else:
_verify_candidate(dest)
except RuntimeError as exc:
@@ -508,7 +482,7 @@ def main() -> None:
bin_path = _bin_path(version)
cache_ready = (
_windows_pair_ready(version)
_windows_binary_ready(version)
if sys.platform == "win32"
else bin_path.is_file()
)
+9 -10
View File
@@ -9,19 +9,18 @@ sys.path.insert(0, str(PACKAGE_ROOT / "src"))
from codebase_memory_mcp import _cli # noqa: E402
class WindowsLauncherSelectionTests(unittest.TestCase):
def test_windows_uses_adjacent_launcher(self):
payload = Path("cache") / "0.8.1" / _cli._WINDOWS_PAYLOAD_NAME
class BinarySelectionTests(unittest.TestCase):
"""One binary ships per platform: the cached file is the executed file."""
self.assertEqual(
_cli._execution_path(payload, "win32"),
payload.with_name(_cli._WINDOWS_LAUNCHER_NAME),
)
def test_windows_executes_the_cached_binary(self):
binary = Path("cache") / "0.8.1" / _cli._WINDOWS_BINARY_NAME
def test_non_windows_keeps_payload(self):
payload = Path("cache") / "0.8.1" / "codebase-memory-mcp"
self.assertEqual(_cli._execution_path(binary, "win32"), binary)
self.assertEqual(_cli._execution_path(payload, "linux"), payload)
def test_non_windows_executes_the_cached_binary(self):
binary = Path("cache") / "0.8.1" / "codebase-memory-mcp"
self.assertEqual(_cli._execution_path(binary, "linux"), binary)
def test_portable_mutation_guidance_classification_is_preserved(self):
self.assertEqual(_cli._portable_mutation_action(["update"]), "update")
+3 -1
View File
@@ -104,7 +104,9 @@ mkdir -p "$EXTRACT_DIR"
if [ "$GOOS" = "windows" ]; then
unzip -q -o "$WORK_DIR/$NAME.zip" -d "$EXTRACT_DIR"
test -s "$EXTRACT_DIR/codebase-memory-mcp.exe"
test -s "$EXTRACT_DIR/codebase-memory-mcp.payload.exe"
# ONE binary per platform: a payload sibling means the AV-flagged launcher
# stub came back.
test ! -e "$EXTRACT_DIR/codebase-memory-mcp.payload.exe"
echo "=== smoke-artifact: smoking EXTRACTED $NAME.zip via vm-smoke.sh ==="
SMOKE_ARCH="$GOARCH" SMOKE_VARIANT="$VARIANT" \
CBM_SMOKE_ARTIFACT_DIR="$EXTRACT_DIR" \
+15 -12
View File
@@ -30,16 +30,16 @@ local artifact-flow smoke lane.
matching binary must already have been built (--with-ui for ui).
--out-dir where to place the archive (default: repository root).
Make passthrough (VAR=VAL, forwarded to the Windows launcher build):
Make passthrough (VAR=VAL, forwarded to the build):
CC= CXX= compiler override, e.g. CC=clang CXX=clang++.
Environment:
BUILD_DIR build tree to archive from (default build/c).
Archive contents (defined here, canonical):
Archive contents (defined here, canonical) — ONE binary per platform:
unix: codebase-memory-mcp LICENSE install.sh THIRD_PARTY_NOTICES.md (.tar.gz)
windows: codebase-memory-mcp.exe (launcher) codebase-memory-mcp.payload.exe
LICENSE install.ps1 THIRD_PARTY_NOTICES.md (.zip)
windows: codebase-memory-mcp.exe LICENSE install.ps1
THIRD_PARTY_NOTICES.md (.zip)
EOF
}
@@ -92,25 +92,28 @@ OUT_DIR="$(mkdir -p "$OUT_DIR" && cd "$OUT_DIR" && pwd)"
NAME="codebase-memory-mcp${SUFFIX}-${GOOS}-${GOARCH}"
if [ "$GOOS" = "windows" ]; then
# The launcher is part of the ARCHIVE layout (launcher fronts the payload),
# so it is built here, exactly as the release venue does.
make -f Makefile.cbm "$BUILD_DIR/codebase-memory-mcp-launcher.exe" \
BUILD_DIR="$BUILD_DIR" ${MAKE_ARGS[@]+"${MAKE_ARGS[@]}"}
# Windows ships ONE binary, exactly like every other platform. There is no
# launcher stub: a small unsigned PE whose entire job is to verify and
# execute another binary is statically indistinguishable from a dropper,
# and Defender's ML scored it Trojan:Win32/Wacatac.B!ml on x64 regardless
# of what we changed (bcrypt-free, stripped, versioned, and even
# resource-free builds were all flagged, while the product binary itself
# scans clean on every platform). Self-update — the launcher's whole reason
# to exist — moves OUT of the running process into install.ps1: Windows'
# executable lock only blocks a process from replacing ITSELF.
PAYLOAD="$BUILD_DIR/codebase-memory-mcp"
[ -f "${PAYLOAD}.exe" ] && PAYLOAD="${PAYLOAD}.exe"
[ -f "$PAYLOAD" ] || { echo "package-release: build first; missing $PAYLOAD" >&2; exit 2; }
PACK_DIR="$(mktemp -d "${TMPDIR:-/tmp}/cbm-package.XXXXXX")"
trap 'rm -rf "$PACK_DIR"' EXIT
cp "$BUILD_DIR/codebase-memory-mcp-launcher.exe" "$PACK_DIR/codebase-memory-mcp.exe"
cp "$PAYLOAD" "$PACK_DIR/codebase-memory-mcp.payload.exe"
cp "$PAYLOAD" "$PACK_DIR/codebase-memory-mcp.exe"
cp LICENSE install.ps1 "$PACK_DIR/"
scripts/gen-third-party-notices.sh "$PACK_DIR/THIRD_PARTY_NOTICES.md"
(
cd "$PACK_DIR"
rm -f "$OUT_DIR/$NAME.zip"
zip -q "$OUT_DIR/$NAME.zip" \
codebase-memory-mcp.exe codebase-memory-mcp.payload.exe \
LICENSE install.ps1 THIRD_PARTY_NOTICES.md
codebase-memory-mcp.exe LICENSE install.ps1 THIRD_PARTY_NOTICES.md
)
echo "=== package-release: $OUT_DIR/$NAME.zip ==="
else
+60 -48
View File
@@ -57,24 +57,11 @@ smoke_mktemp_dir() {
fi
}
# Windows release archives contain a small permanent launcher plus a portable
# payload. Whenever a smoke fixture copies the launcher, keep the payload next
# to it so the copied fixture remains a complete portable bundle.
WINDOWS_PAYLOAD=""
if [[ "$BINARY" == *.exe ]]; then
WINDOWS_PAYLOAD="$(cd "$(dirname "$BINARY")" && pwd)/codebase-memory-mcp.payload.exe"
if [ ! -f "$WINDOWS_PAYLOAD" ]; then
echo "FAIL: Windows launcher has no adjacent codebase-memory-mcp.payload.exe"
exit 1
fi
fi
# Every platform ships ONE binary, Windows included: a fixture copy is complete
# with nothing beside it.
copy_smoke_binary() {
local destination="$1"
cp "$BINARY" "$destination"
if [ -n "$WINDOWS_PAYLOAD" ]; then
cp "$WINDOWS_PAYLOAD" "$(dirname "$destination")/codebase-memory-mcp.payload.exe"
fi
}
# Retire the shared account daemon (if one is running) and wait until it
@@ -1174,26 +1161,32 @@ HOME="$FAKE_HOME" \
"$BINARY" install -y > "$PHASE8_INSTALL_LOG" 2>&1 || PHASE8_INSTALL_RC=$?
cat "$PHASE8_INSTALL_LOG"
if [[ "$BINARY" == *.exe ]]; then
# The managed install itself must SUCCEED on Windows — an install-time
# staging/ACL refusal used to scroll past as tolerated noise while the
# downstream config assertions kept passing against a previous generation
# ("staging transaction open failed (status -3, os 0)" hid a real install
# failure class on Administrators-default-owner profiles).
# The install itself must SUCCEED on Windows — an install-time staging/ACL
# refusal used to scroll past as tolerated noise while the downstream config
# assertions kept passing against a previous copy ("staging transaction open
# failed (status -3, os 0)" hid a real install failure class on
# Administrators-default-owner profiles).
if [ "$PHASE8_INSTALL_RC" -ne 0 ]; then
echo "FAIL 8-0: managed install exited rc=$PHASE8_INSTALL_RC"
echo "FAIL 8-0: install exited rc=$PHASE8_INSTALL_RC"
exit 1
fi
PHASE8_CANONICAL="$FAKE_HOME/.local/bin/codebase-memory-mcp.exe"
if [ ! -f "$PHASE8_CANONICAL" ]; then
echo "FAIL 8-0: canonical launcher missing after managed install"
echo "FAIL 8-0: installed binary missing after install"
exit 1
fi
PHASE8_LINKS=$(stat -c %h "$PHASE8_CANONICAL" 2>/dev/null || echo 0)
if [ "$PHASE8_LINKS" != "2" ]; then
echo "FAIL 8-0: canonical launcher is not an exact two-link file (links=$PHASE8_LINKS)"
# ONE binary, one link: a second hard link here would mean the retired
# launcher/generation layout came back.
PHASE8_LINKS=$(stat -c %h "$PHASE8_CANONICAL" 2>/dev/null || echo 1)
if [ "$PHASE8_LINKS" != "1" ]; then
echo "FAIL 8-0: installed binary is not a single-link file (links=$PHASE8_LINKS)"
exit 1
fi
echo "OK 8-0: managed install committed an exact two-link canonical launcher"
if [ -e "$FAKE_HOME/.local/bin/codebase-memory-mcp.payload.exe" ]; then
echo "FAIL 8-0: install produced a launcher/payload pair"
exit 1
fi
echo "OK 8-0: install committed exactly one Windows binary"
fi
# Helper for JSON validation (pipe file to python — avoids MSYS2 path translation issues)
@@ -2911,9 +2904,9 @@ if [ -n "${SMOKE_DOWNLOAD_URL:-}" ]; then
UPDATE_HOME=$(smoke_mktemp_dir)
mkdir -p "$UPDATE_HOME/.claude" "$UPDATE_HOME/.local/bin"
if [[ "$BINARY" == *.exe ]]; then
# Keep the managed canonical absent until WINDOWS_PAYLOAD installs the
# authenticated two-link launcher layout below.
:
cp "$BINARY" "$UPDATE_HOME/.local/bin/codebase-memory-mcp.exe"
mkdir -p "$UPDATE_HOME/retired-install"
cp "$BINARY" "$UPDATE_HOME/retired-install/codebase-memory-mcp.exe"
else
cp "$BINARY" "$UPDATE_HOME/.local/bin/codebase-memory-mcp"
chmod 755 "$UPDATE_HOME/.local/bin/codebase-memory-mcp"
@@ -2927,18 +2920,12 @@ if [ -n "${SMOKE_DOWNLOAD_URL:-}" ]; then
fi
fi
# A portable Windows payload may install a managed launcher, but it must not
# perform update/uninstall directly. Establish the managed layout first and
# exercise those mutations through its canonical launcher.
UPDATE_DRIVER="$BINARY"
# POSIX runs `update` from the retired image so the in-process replacement is
# exercised end to end. Windows has no in-process replacement: a running .exe
# cannot replace itself, so `update` hands off to install.ps1 (asserted in
# 14a below) and the installed copy drives the later uninstall phases.
if [[ "$BINARY" == *.exe ]]; then
HOME="$UPDATE_HOME" "$WINDOWS_PAYLOAD" install -y --force --skip-config \
"--dir=$UPDATE_HOME/.local/bin"
UPDATE_DRIVER="$UPDATE_HOME/.local/bin/codebase-memory-mcp.exe"
if [ ! -f "$UPDATE_DRIVER" ]; then
echo "FAIL 14a: managed Windows launcher missing after install"
exit 1
fi
else
RETIRED_DIR=$(cd "$UPDATE_HOME/retired-install" && pwd -P)
UPDATE_DRIVER="$RETIRED_DIR/codebase-memory-mcp"
@@ -2965,8 +2952,31 @@ if [ -n "${SMOKE_DOWNLOAD_URL:-}" ]; then
if curl --noproxy '*' -sf "$SMOKE_DOWNLOAD_URL/" 2>/dev/null | grep -q "ui-"; then
UPDATE_VARIANT="--ui"
fi
UPDATE_LOG=$(smoke_mktemp_file)
HOME="$UPDATE_HOME" CBM_DOWNLOAD_URL="$UPDATE_DOWNLOAD_URL" \
"$UPDATE_DRIVER" update $UPDATE_VARIANT -y 2>&1
"$UPDATE_DRIVER" update $UPDATE_VARIANT -y > "$UPDATE_LOG" 2>&1
UPDATE_RC=$?
cat "$UPDATE_LOG"
if [[ "$BINARY" == *.exe ]]; then
# Windows contract: update NEVER replaces the running image in-process. It
# exits 0 and prints the install.ps1 command. Regressing to an in-process
# self-update means reintroducing the AV-flagged launcher stub.
if [ "$UPDATE_RC" -ne 0 ]; then
echo "FAIL 14a: Windows update exited rc=$UPDATE_RC (expected 0)"
exit 1
fi
if ! grep -q "install.ps1" "$UPDATE_LOG"; then
echo "FAIL 14a: Windows update did not print the install.ps1 command"
exit 1
fi
if ! cmp -s "$BINARY" "$UPDATE_HOME/.local/bin/codebase-memory-mcp.exe"; then
echo "FAIL 14a: Windows update replaced the binary in-process"
exit 1
fi
echo "OK 14a: Windows update handed off to install.ps1 without touching the binary"
fi
rm -f "$UPDATE_LOG"
# 14b: Verify new binary exists and runs
if [[ "$BINARY" == *.exe ]]; then
@@ -2988,6 +2998,11 @@ if [ -n "${SMOKE_DOWNLOAD_URL:-}" ]; then
echo "OK 14b: updated binary runs"
# 14c: Verify agent config was refreshed to the exact installed binary.
# Windows has no in-process update, so there is no config refresh to assert:
# install.ps1 re-runs `install`, which is covered by Phase 8 and Phase 13.
if [[ "$BINARY" == *.exe ]]; then
echo "SKIP 14c: Windows update hands off to install.ps1 (config refresh covered by install)"
else
UPD_CMD=$(cat "$UPDATE_HOME/.claude.json" 2>/dev/null | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('mcpServers',{}).get('codebase-memory-mcp',{}).get('command',''))" 2>/dev/null || echo "")
EXPECTED_UPD_CMD="$UPD_BIN"
if command -v cygpath &>/dev/null; then
@@ -3000,6 +3015,7 @@ if [ -n "${SMOKE_DOWNLOAD_URL:-}" ]; then
exit 1
fi
echo "OK 14c: agent config refreshed (path=$UPD_CMD)"
fi
# ── 14d-f: Real uninstall with binary removal ──
# First verify binary + configs exist
@@ -3158,9 +3174,10 @@ echo "--- Phase 12d: extraction ---"
(cd "$DL_DIR" && if [ "$DL_EXT" = "zip" ]; then unzip -q "$DL_ARCHIVE"; else tar -xzf "$DL_ARCHIVE"; fi)
if [ "$DL_OS" = "windows" ]; then
DL_BIN="$DL_DIR/codebase-memory-mcp.exe"
DL_PAYLOAD="$DL_DIR/codebase-memory-mcp.payload.exe"
if [ ! -f "$DL_PAYLOAD" ]; then
echo "FAIL 12d: Windows payload not found after extraction"
# ONE binary per platform: a second executable in the archive would mean the
# AV-flagged launcher/payload split came back.
if [ -e "$DL_DIR/codebase-memory-mcp.payload.exe" ]; then
echo "FAIL 12d: Windows archive still ships a launcher/payload pair"
exit 1
fi
else
@@ -3173,11 +3190,6 @@ fi
chmod +x "$DL_BIN"
echo "OK 12d: binary extracted"
if [ "$DL_OS" = "windows" ] && ! "$DL_PAYLOAD" --version > /dev/null 2>&1; then
echo "FAIL 12d: extracted Windows payload doesn't run"
exit 1
fi
# 12e: extracted binary runs
if ! "$DL_BIN" --version > /dev/null 2>&1; then
# On macOS arm64, may need signing
+21 -61
View File
@@ -3,10 +3,10 @@
Run the native-Windows product-surface test suite for codebase-memory-mcp.
.DESCRIPTION
Builds the payload and permanent launcher if they are not already present,
stages them under their release names, then runs the deterministic Windows
integration tests under tests/windows/ through the launcher (real stdio /
CLI / HTTP UI, real SQLite DB).
Builds the product binary if it is not already present, stages it under its
release name, then runs the deterministic Windows integration tests under
tests/windows/ against it (real stdio / CLI / HTTP UI, real SQLite DB).
Windows ships ONE binary, exactly like Linux and macOS.
Two categories of test:
@@ -20,8 +20,9 @@
* test_cli_non_ascii_arg.py guards #423/#20 (wide-argv main())
* test_daemon_stability.py guards the daemon parameter
surface, crash recovery, busy-stop refusal, and churn
* test_windows_launcher.py guards the permanent launcher,
managed layout, portable refusal, and crash containment
* test_windows_update_handoff.py guards that `update`
hands off to install.ps1 instead of replacing its own
running image (the removed launcher stub's only job)
KNOWN REDS - genuine, still-open Windows bugs reproduced at the product
surface. They are EXPECTED to be RED (exit 1) and are opt-in
@@ -40,13 +41,8 @@
AddressSanitizer/UBSan (Linux containers, WSL), prefer scripts/test.sh.
.PARAMETER Binary
Path to an existing portable payload executable. If omitted, the script
builds it (target selected by -Target) into build/c/.
.PARAMETER Launcher
Path to the permanent launcher executable. If omitted, the script uses
build/c/codebase-memory-mcp-launcher.exe, building target cbm-launcher when
needed.
Path to an existing product executable. If omitted, the script builds it
(target selected by -Target) into build/c/.
.PARAMETER Target
Makefile.cbm target used when building: 'cbm-with-ui' (default; needed for the
@@ -68,7 +64,6 @@
[CmdletBinding()]
param(
[string]$Binary,
[string]$Launcher,
[ValidateSet("cbm-with-ui", "cbm")]
[string]$Target = "cbm-with-ui",
[switch]$GuardsOnly,
@@ -103,36 +98,8 @@ function Resolve-Binary {
return $built
}
function Resolve-Launcher {
param([string]$Explicit)
if ($Explicit) { return (Resolve-Path $Explicit).Path }
$built = Join-Path $repoRoot "build\c\codebase-memory-mcp-launcher.exe"
if (Test-Path $built) { return $built }
Write-Host "Building permanent launcher via Makefile.cbm ..." -ForegroundColor Cyan
& $Make "-j" "-f" "Makefile.cbm" "cbm-launcher" "SANITIZE=" "TMP=$tmp" "TEMP=$tmp" "TMPDIR=$tmp" | Out-Host
$buildExit = $LASTEXITCODE
if ($buildExit -ne 0) { throw "launcher build failed (exit $buildExit)" }
if (-not (Test-Path $built)) { throw "launcher not produced at $built" }
return $built
}
function Resolve-AbiMismatchLauncher {
$built = Join-Path $repoRoot "build\c\codebase-memory-mcp-launcher-abi2.exe"
if (Test-Path $built) { return $built }
Write-Host "Building launcher ABI mismatch fixture via Makefile.cbm ..." -ForegroundColor Cyan
& $Make "-j" "-f" "Makefile.cbm" "build/c/codebase-memory-mcp-launcher-abi2.exe" "SANITIZE=" "TMP=$tmp" "TEMP=$tmp" "TMPDIR=$tmp" | Out-Host
$buildExit = $LASTEXITCODE
if ($buildExit -ne 0) { throw "launcher ABI fixture build failed (exit $buildExit)" }
if (-not (Test-Path $built)) { throw "launcher ABI fixture not produced at $built" }
return $built
}
$bin = Resolve-Binary -Explicit $Binary
$launcherBin = Resolve-Launcher -Explicit $Launcher
$abiMismatchLauncher = Resolve-AbiMismatchLauncher
Write-Host "Payload: $bin" -ForegroundColor Green
Write-Host "Launcher: $launcherBin" -ForegroundColor Green
Write-Host "ABI mismatch fixture: $abiMismatchLauncher" -ForegroundColor Green
Write-Host "Binary: $bin" -ForegroundColor Green
$previousTemp = $env:TEMP
$previousTmp = $env:TMP
@@ -145,8 +112,7 @@ try {
New-Item -ItemType Directory -Path $guardRoot | Out-Null
# GitHub-hosted runner profile children can inherit mutation-capable ACEs
# even though the profile ancestry itself passes the launcher's bounded
# trust policy. Replace that inheritance before creating any executable or
# even though the profile ancestry itself passes the bounded trust policy. Replace that inheritance before creating any executable or
# Python temporary descendant. Use SIDs rather than localized account names.
$currentSid = [System.Security.Principal.WindowsIdentity]::GetCurrent().User
if (-not $currentSid) { throw "could not resolve the current user's SID" }
@@ -167,24 +133,22 @@ try {
$guardBundle = Join-Path $guardRoot ("cbm-windows-guards-" + [guid]::NewGuid().ToString("N"))
New-Item -ItemType Directory -Path $guardBundle | Out-Null
$guardBin = Join-Path $guardBundle "codebase-memory-mcp.exe"
$guardPayload = Join-Path $guardBundle "codebase-memory-mcp.payload.exe"
Copy-Item -LiteralPath $launcherBin -Destination $guardBin
Copy-Item -LiteralPath $bin -Destination $guardPayload
Copy-Item -LiteralPath $bin -Destination $guardBin
# Ownership is never inherited on Windows: descendants created under the
# hardened root by an admin-group token can default to the Administrators
# SID, and the launcher's exe policy demands the exact current user as
# owner. Stamp the current SID explicitly on everything staged here.
foreach ($staged in @($guardBundle, $guardBin, $guardPayload)) {
# SID, while the exe policy demands the exact current user as owner. Stamp
# the current SID explicitly on everything staged here.
foreach ($staged in @($guardBundle, $guardBin)) {
$stagedAcl = Get-Acl -LiteralPath $staged
$stagedAcl.SetOwner($currentSid)
Set-Acl -LiteralPath $staged -AclObject $stagedAcl
}
Write-Host "Guard bundle: $guardBin" -ForegroundColor Green
# The launcher deliberately rejects GitHub's shared D:\a ancestry and the
# hosted runner's inherited LocalAppData\Temp ACL. Keep launcher fixtures
# and Python-created descendants below the accepted profile ancestry.
# The guards deliberately reject GitHub's shared D:\a ancestry and the
# hosted runner's inherited LocalAppData\Temp ACL. Keep staged fixtures and
# Python-created descendants below the accepted profile ancestry.
$env:TEMP = $guardRoot
$env:TMP = $guardRoot
$env:TMPDIR = $guardRoot
@@ -202,7 +166,7 @@ $guards = @(
"tests\windows\test_hook_augment.py",
"tests\windows\test_ui_drive_listing.py",
"tests\windows\test_cli_non_ascii_arg.py",
"tests\windows\test_windows_launcher.py"
"tests\windows\test_windows_update_handoff.py"
)
# Opt-in known-red repros - EXPECTED red (exit 1); never gate CI. Currently empty:
@@ -216,15 +180,11 @@ $fixedKeepers = @()
Write-Host "`n--- Green guards ---" -ForegroundColor Cyan
foreach ($t in $guards) {
Write-Host "`n=== $t ===" -ForegroundColor Cyan
if ($t -eq "tests\windows\test_windows_launcher.py") {
& $py $t $guardBin $guardPayload $abiMismatchLauncher
} else {
& $py $t $guardBin
}
& $py $t $guardBin
$code = $LASTEXITCODE
if ($code -eq 0) {
Write-Host "GREEN ($t)" -ForegroundColor Green
} elseif ($code -eq 1 -or $t -eq "tests\windows\test_windows_launcher.py") {
} elseif ($code -eq 1 -or $t -eq "tests\windows\test_windows_update_handoff.py") {
Write-Host "RED ($t) - REGRESSION: a fixed Windows bug is broken again" -ForegroundColor Red
$guardFailures += $t
} elseif ($code -eq 2) {
+47 -2296
View File
File diff suppressed because it is too large Load Diff
-21
View File
@@ -348,19 +348,6 @@ unsigned char *cbm_extract_binary_from_targz(const unsigned char *data, int data
* Returns NULL on error. Caller must free. */
unsigned char *cbm_extract_binary_from_zip(const unsigned char *data, int data_len, int *out_len);
/* Strict two-file Windows release bundle extraction. The archive must contain
* exactly one root launcher and one root payload; ambiguous aliases,
* traversal, duplicates, and malformed central/local metadata fail closed. */
typedef struct {
unsigned char *launcher;
int launcher_len;
unsigned char *payload;
int payload_len;
} cbm_windows_release_pair_t;
bool cbm_extract_windows_release_pair_from_zip(const unsigned char *data, int data_len,
cbm_windows_release_pair_t *pair_out);
void cbm_windows_release_pair_free(cbm_windows_release_pair_t *pair);
/* ── Index management ─────────────────────────────────────────── */
/* List .db files in the cache directory (~/.cache/codebase-memory-mcp/).
@@ -447,14 +434,6 @@ void cbm_cli_set_activation_ops_for_test(const cbm_cli_activation_ops_t *ops);
* command-line or environment override. */
void cbm_cli_set_activation_runtime_parent_for_test(const char *runtime_parent);
/* Consume and authenticate any inherited permanent-launcher context before
* process-role classification. An absent context is the normal portable
* payload case; an advertised but invalid context fails closed. */
int cbm_cli_windows_launcher_startup_authenticate(int argc, char *const argv[]);
/* Internal release-pair probe. Returns -1 when argv does not select the role,
* otherwise a process exit code. It runs before cache/daemon initialization. */
int cbm_cli_windows_payload_descriptor_role(int argc, char *const argv[]);
/* ── Subcommands (wired from main.c) ─────────────────────────── */
/* install: copy binary, install skills, install editor MCP configs, ensure PATH.
File diff suppressed because it is too large Load Diff
-157
View File
@@ -1,157 +0,0 @@
/*
* windows_launcher_state.h -- Stable state shared by the Windows launcher and
* the CBM payload.
*
* The on-disk current-v1 record is deliberately fixed-size and contains no
* native C layout. Keep its codec platform-independent: release compatibility
* is a byte contract, not a compiler/architecture contract.
*/
#ifndef CBM_WINDOWS_LAUNCHER_STATE_H
#define CBM_WINDOWS_LAUNCHER_STATE_H
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <wchar.h>
#define CBM_WINDOWS_CURRENT_V1_SIZE 128U
#define CBM_WINDOWS_RELEASE_DESCRIPTOR_V1_SIZE 128U
#ifndef CBM_WINDOWS_LAUNCHER_ABI_CURRENT
#define CBM_WINDOWS_LAUNCHER_ABI_CURRENT 1U
#endif
#ifndef CBM_WINDOWS_PAYLOAD_LAUNCHER_ABI_MIN
#define CBM_WINDOWS_PAYLOAD_LAUNCHER_ABI_MIN 1U
#endif
#ifndef CBM_WINDOWS_PAYLOAD_LAUNCHER_ABI_MAX
#define CBM_WINDOWS_PAYLOAD_LAUNCHER_ABI_MAX 1U
#endif
#define CBM_WINDOWS_LAUNCHER_PATH_CAP 32768U
/* Hex-char count of the payload digest embedded in a retired-state directory
* name. 16 chars (64 bits) uniquely identifies the retired generation while
* keeping the retired path short enough for the managed uninstall's
* handle-based directory rename at the deepest supported install depth. */
#define CBM_WINDOWS_RETIRED_TAG_HEX 16U
typedef struct {
uint32_t launcher_abi_min;
uint32_t launcher_abi_max;
uint64_t payload_size;
char payload_sha256[65];
} cbm_windows_current_v1_t;
typedef struct {
uint32_t launcher_abi;
uint32_t payload_launcher_abi_min;
uint32_t payload_launcher_abi_max;
uint64_t payload_size;
char payload_sha256[65];
} cbm_windows_release_descriptor_v1_t;
typedef enum {
CBM_WINDOWS_TRANSITION_INCOMPATIBLE = 0,
CBM_WINDOWS_TRANSITION_LAUNCHER_FIRST = 1,
CBM_WINDOWS_TRANSITION_CURRENT_FIRST = 2,
} cbm_windows_transition_plan_t;
typedef enum {
CBM_WINDOWS_LAUNCHER_ACTION_ORDINARY = 0,
CBM_WINDOWS_LAUNCHER_ACTION_UPDATE = 1,
CBM_WINDOWS_LAUNCHER_ACTION_UNINSTALL = 2,
} cbm_windows_launcher_action_t;
bool cbm_windows_current_v1_encode(const cbm_windows_current_v1_t *state,
uint8_t out[CBM_WINDOWS_CURRENT_V1_SIZE]);
bool cbm_windows_current_v1_decode(const uint8_t *record, size_t record_size,
cbm_windows_current_v1_t *state_out);
bool cbm_windows_current_v1_supports_launcher_abi(const cbm_windows_current_v1_t *state,
uint32_t launcher_abi);
bool cbm_windows_release_descriptor_v1_encode(const cbm_windows_release_descriptor_v1_t *descriptor,
uint8_t out[CBM_WINDOWS_RELEASE_DESCRIPTOR_V1_SIZE]);
bool cbm_windows_release_descriptor_v1_decode(const uint8_t *record, size_t record_size,
cbm_windows_release_descriptor_v1_t *descriptor_out);
cbm_windows_transition_plan_t cbm_windows_transition_plan(
const cbm_windows_current_v1_t *current, const cbm_windows_release_descriptor_v1_t *candidate);
/* Resolve the immutable generation pair using the canonical launcher's
* directory. A managed generation contains exactly these two executables:
* the payload and the launcher's hard-link backing. */
bool cbm_windows_generation_payload_path(const wchar_t *canonical_launcher_path,
const char payload_sha256[65], wchar_t *path_out,
size_t path_capacity);
bool cbm_windows_generation_launcher_path(const wchar_t *canonical_launcher_path,
const char payload_sha256[65], wchar_t *path_out,
size_t path_capacity);
/* Derive the race-free retired state sibling shared by the uninstall payload
* (its own PID) and supervising launcher (the authenticated child PID). */
bool cbm_windows_retired_state_path(const wchar_t *canonical_launcher_path,
const char payload_sha256[65], uint32_t payload_pid,
wchar_t *path_out, size_t path_capacity);
/* Match main's top-level dispatch. Tokens after a mode selector (cli,
* install, config, hook-augment, help/version) are opaque user input. */
cbm_windows_launcher_action_t cbm_windows_launcher_classify_action(int argc,
const char *const argv[]);
bool cbm_windows_launcher_action_allowed(cbm_windows_launcher_action_t action, bool managed);
/* Trusted launch data delivered over a launcher-owned inherited named pipe.
* An absent context is valid and returns true with present=false (direct
* portable payload). An advertised but invalid context fails closed. */
typedef struct {
bool present;
bool managed;
bool private_activation;
cbm_windows_launcher_action_t action;
uint64_t payload_size;
char expected_payload_sha256[65];
wchar_t canonical_launcher_path[CBM_WINDOWS_LAUNCHER_PATH_CAP];
/* Opaque one-shot authority retained only until startup validation has
* completed. Callers must use context_complete, never inspect it. */
uintptr_t _authority_handle;
} cbm_windows_launcher_context_t;
bool cbm_windows_launcher_context_consume(cbm_windows_launcher_context_t *context_out, char *error,
size_t error_size);
bool cbm_windows_launcher_context_complete(cbm_windows_launcher_context_t *context, bool accepted,
char *error, size_t error_size);
/* Windows managed-install primitives. They fail closed on other platforms.
* Diagnostics are optional and always NUL-terminated when capacity is nonzero.
*/
bool cbm_windows_launcher_capability_probe(const wchar_t *target_directory,
const wchar_t *launcher_candidate, char *error,
size_t error_size);
bool cbm_windows_launcher_file_secure(const wchar_t *launcher_path, char *error, size_t error_size);
/* Find the unique managed-generation backing for canonical_launcher_path.
* The canonical name and backing must be the same file identity with exactly
* two links. The backing need not be in the current payload generation while
* a crash-safe cross-ABI transition is between its two publication steps. */
bool cbm_windows_managed_launcher_backing(const wchar_t *canonical_launcher_path,
wchar_t *backing_path_out, size_t backing_path_capacity,
char *error, size_t error_size);
bool cbm_windows_release_descriptor_probe(const wchar_t *launcher_candidate,
cbm_windows_release_descriptor_v1_t *descriptor_out,
char *error, size_t error_size);
bool cbm_windows_current_v1_write_atomic(const wchar_t *canonical_launcher_path,
const cbm_windows_current_v1_t *state, char *error,
size_t error_size);
/* Publish an exact-one-link immutable generation backing at the canonical
* name by hard-linking a same-directory stage and POSIX-renaming that stage.
* Success leaves canonical and backing as the same exact-two-link file. */
bool cbm_windows_launcher_replace_atomic(const wchar_t *target_path, const wchar_t *backing_path,
char *error, size_t error_size);
bool cbm_windows_launcher_remove_posix(const wchar_t *target_path, char *error, size_t error_size);
/* Retire .cbm to its generation/PID-qualified sibling, then unlink canonical
* as the final uninstall commit. A failed unlink restores .cbm. */
bool cbm_windows_launcher_uninstall_commit(const wchar_t *canonical_launcher_path,
const char payload_sha256[65], char *error,
size_t error_size);
bool cbm_windows_generation_rollback_if_unreferenced(const wchar_t *canonical_launcher_path,
const char payload_sha256[65],
bool created_by_activation, char *error,
size_t error_size);
bool cbm_windows_generations_prune(const wchar_t *canonical_launcher_path, size_t *removed_out,
char *error, size_t error_size);
#endif /* CBM_WINDOWS_LAUNCHER_STATE_H */
File diff suppressed because it is too large Load Diff
-7
View File
@@ -1719,13 +1719,6 @@ int main(int argc, char **argv) {
}
}
#endif
int windows_descriptor_role = cbm_cli_windows_payload_descriptor_role(argc, argv);
if (windows_descriptor_role >= 0) {
return windows_descriptor_role;
}
if (cbm_cli_windows_launcher_startup_authenticate(argc, argv) != 0) {
return EXIT_FAILURE;
}
cbm_daemon_process_role_t role = cbm_daemon_process_role(argc, argv);
if (role == CBM_DAEMON_PROCESS_INVALID) {
(void)fprintf(stderr, "codebase-memory-mcp: invalid internal process arguments\n");
+2 -3
View File
@@ -198,9 +198,8 @@ services:
command:
- |
scripts/build.sh CC=x86_64-w64-mingw32-clang CXX=x86_64-w64-mingw32-clang++ BUILD_DIR=build/win-cross &&
mv build/win-cross/codebase-memory-mcp.exe build/win-cross/codebase-memory-mcp.payload.exe &&
mv build/win-cross/codebase-memory-mcp-launcher.exe build/win-cross/codebase-memory-mcp.exe &&
WINEDEBUG=-all wine64 ./build/win-cross/codebase-memory-mcp.payload.exe --version &&
test ! -e build/win-cross/codebase-memory-mcp.payload.exe &&
WINEDEBUG=-all wine64 ./build/win-cross/codebase-memory-mcp.exe --version &&
WINEDEBUG=-all wine64 cmd /c build/win-cross/codebase-memory-mcp.exe --version
# ── Linux portable (Alpine musl static) ────────────────────
+3 -3
View File
@@ -206,11 +206,11 @@ case "${1:-full}" in
$COMPOSE run --rm -e CBM_SKIP_PERF=1 test-portable
;;
windows)
echo "=== Windows: cross-compile + launcher/payload version check (Wine) ==="
echo "=== Windows: cross-compile + binary version check (Wine) ==="
$COMPOSE run --rm smoke-windows
;;
smoke-windows)
echo "=== Windows: launcher/payload version check (cross-compile + Wine) ==="
echo "=== Windows: binary version check (cross-compile + Wine) ==="
$COMPOSE run --rm smoke-windows
;;
amd64)
@@ -237,7 +237,7 @@ case "${1:-full}" in
$COMPOSE run --rm -e CBM_SKIP_PERF=1 test-amd64
$COMPOSE run --rm build-amd64
$COMPOSE run --rm smoke-amd64
echo "=== Windows: cross-compile + launcher/payload version check (Wine) ==="
echo "=== Windows: cross-compile + binary version check (Wine) ==="
$COMPOSE run --rm smoke-windows
print_real_windows_gate
;;
+1 -1
View File
@@ -110,7 +110,7 @@ else
vm "clangarm64" "git clone --branch ${BRANCH} --single-branch --depth 200 ${REPO_URL} /c/cbm && cd /c/cbm && git log --oneline -1"
fi
step "5/6 build: native ARM64 binary + launcher + test-runner (no ASan on arm64)"
step "5/6 build: native ARM64 binary + test-runner (no ASan on arm64)"
vm "clangarm64" "cd /c/cbm && make -j${JOBS} -f Makefile.cbm CC=clang CXX=clang++ SANITIZE= cbm build/c/test-runner > /tmp/provision-build.log 2>&1 && echo BUILD_OK || (echo BUILD_FAIL; tail -15 /tmp/provision-build.log; exit 1)"
step "6/6 smoke: binary + test-runner start"
+15 -23
View File
@@ -6,7 +6,7 @@
#
# Stages and serves a complete Windows release fixture from a profile-rooted
# directory. MSYS2 /tmp and the shared runner workspace are intentionally not
# valid launcher bundle roots.
# valid install roots.
#
# Run inside the VM's CLANGARM64 shell from the repo root:
# bash test-infrastructure/vm/vm-smoke.sh
@@ -19,7 +19,7 @@ Usage: [env] bash test-infrastructure/vm/vm-smoke.sh
The canonical WINDOWS smoke entry: identical in local CI (win.sh
smoke-install), PR CI (pr.yml) and the release venues (_smoke.yml). Stages a
complete launcher+payload release fixture under a disposable profile root,
complete single-binary release fixture under a disposable profile root,
serves it on a kernel-assigned port, prepares/verifies/cleans the user-PATH
registry via windows-user-path-guard.ps1, neutralizes every agent-config
destination override, then runs scripts/smoke-test.sh (all phases).
@@ -33,11 +33,11 @@ Environment:
binary cannot pass a ui run.
CBM_SMOKE_ARTIFACT_DIR
Release mode: an EXTRACTED windows release artifact
(codebase-memory-mcp.exe + .payload.exe + LICENSE +
install.ps1 + THIRD_PARTY_NOTICES.md). All five are required
and served verbatim — an incomplete archive fails the smoke.
Unset (default): stages the freshly built launcher/payload
out of build/c and synthesizes the sidecars (local/PR mode).
(codebase-memory-mcp.exe + LICENSE + install.ps1 +
THIRD_PARTY_NOTICES.md). All four are required and served
verbatim — an incomplete archive fails the smoke.
Unset (default): stages the freshly built binary out of
build/c and synthesizes the sidecars (local/PR mode).
On failure the smoke root is preserved for post-mortem (path printed).
EOF
@@ -51,27 +51,22 @@ cd "$(dirname "$0")/../.."
ROOT="$PWD"
# Two staging sources, one smoke. Unset (the local + PR default): stage the
# freshly built launcher/payload out of build/c and synthesize the release
# sidecars. Set: stage an EXTRACTED release artifact verbatim, so the release
# freshly built binary out of build/c and synthesize the release sidecars. Set: stage an EXTRACTED release artifact verbatim, so the release
# venue smokes the bytes it is about to publish rather than a local rebuild of
# them. Requiring the sidecars here also makes an incomplete archive a smoke
# failure instead of a discovery made after publishing.
ARTIFACT_DIR="${CBM_SMOKE_ARTIFACT_DIR:-}"
if [ -n "$ARTIFACT_DIR" ]; then
ARTIFACT_DIR="$(cd "$ARTIFACT_DIR" && pwd)"
for required in codebase-memory-mcp.exe codebase-memory-mcp.payload.exe \
for required in codebase-memory-mcp.exe \
LICENSE install.ps1 THIRD_PARTY_NOTICES.md; do
[ -s "$ARTIFACT_DIR/$required" ] ||
{ echo "vm-smoke: release artifact is missing $required" >&2; exit 2; }
done
LAUNCHER_SRC="$ARTIFACT_DIR/codebase-memory-mcp.exe"
PAYLOAD_SRC="$ARTIFACT_DIR/codebase-memory-mcp.payload.exe"
BINARY_SRC="$ARTIFACT_DIR/codebase-memory-mcp.exe"
else
LAUNCHER_SRC="build/c/codebase-memory-mcp-launcher.exe"
PAYLOAD_SRC="build/c/codebase-memory-mcp.exe"
for binary in "$LAUNCHER_SRC" "$PAYLOAD_SRC"; do
[ -x "$binary" ] || { echo "build first; missing $binary" >&2; exit 2; }
done
BINARY_SRC="build/c/codebase-memory-mcp.exe"
[ -x "$BINARY_SRC" ] || { echo "build first; missing $BINARY_SRC" >&2; exit 2; }
fi
SMOKE_ARCH="${SMOKE_ARCH:-arm64}"
@@ -149,10 +144,8 @@ MSYS2_ARG_CONV_EXCL='*' powershell.exe -NoProfile -ExecutionPolicy Bypass \
-RunId "$PATH_RUN_ID" \
-SnapshotPath "$(cygpath -w "$PATH_SNAPSHOT")" \
-SmokeRoot "$(cygpath -w "$SMOKE_DIR")"
cp "$LAUNCHER_SRC" "$SMOKE_DIR/codebase-memory-mcp.exe"
cp "$PAYLOAD_SRC" "$SMOKE_DIR/codebase-memory-mcp.payload.exe"
cp "$SMOKE_DIR/codebase-memory-mcp.exe" \
"$SMOKE_DIR/codebase-memory-mcp.payload.exe" "$FIXTURE_DIR/"
cp "$BINARY_SRC" "$SMOKE_DIR/codebase-memory-mcp.exe"
cp "$SMOKE_DIR/codebase-memory-mcp.exe" "$FIXTURE_DIR/"
# The install/update phases fetch these out of the served archive, so they must
# be the artifact's own copies whenever one was supplied — regenerating them
# here would smoke a sidecar the release never ships.
@@ -168,8 +161,7 @@ EXPECTED_ARTIFACT="codebase-memory-mcp${SUFFIX}-windows-${SMOKE_ARCH}.zip"
(
cd "$FIXTURE_DIR"
zip -q "$EXPECTED_ARTIFACT" \
codebase-memory-mcp.exe codebase-memory-mcp.payload.exe \
LICENSE install.ps1 THIRD_PARTY_NOTICES.md
codebase-memory-mcp.exe LICENSE install.ps1 THIRD_PARTY_NOTICES.md
if [ -n "$SUFFIX" ]; then
cp "$EXPECTED_ARTIFACT" "codebase-memory-mcp-windows-${SMOKE_ARCH}.zip"
fi
+4 -4
View File
@@ -196,7 +196,7 @@ sync)
;;
build)
# The canonical build entry, exactly as CI runs it: a CLEAN scripts/build.sh
# (product binary + launcher). ccache engages through env.sh's masquerade
# (the single product binary). ccache engages through env.sh's masquerade
# (content-verified, so a warm cache only accelerates, never goes stale) —
# NOT via CC='ccache clang', which bypassed the verified-cache env layer.
# The test-runner is no longer built here: the test leg (scripts/test.sh)
@@ -213,8 +213,8 @@ test)
vm clangarm64 "cd /c/cbm && bash test-infrastructure/vm/vm-run-tests.sh $*"
;;
guards)
# Match the Windows CI product build: a clean, embedded-UI payload plus the
# permanent launcher. Passing those freshly built artifacts to the maintained
# Match the Windows CI product build: a clean, embedded-UI product binary.
# Passing that freshly built artifact to the maintained
# PowerShell driver prevents an earlier non-UI `win.sh build` from silently
# turning product guards into precondition skips. BUILD_DIR isolates the
# clean product build from build/c, which build.sh would otherwise wipe —
@@ -230,7 +230,7 @@ guards)
# different environment shape than CI's profile-rooted TEMP. Python must
# be PREPENDED: the Microsoft Store python.exe alias stub lives early in
# the profile PATH and otherwise shadows any appended interpreter.
vm_cmd "cd /d C:\\cbm && set PATH=C:\\msys64\\clangarm64\\bin;C:\\msys64\\usr\\bin;%PATH%&& powershell -NoProfile -ExecutionPolicy Bypass -File scripts\\test-windows.ps1 -GuardsOnly -Binary build\\guards\\codebase-memory-mcp.exe -Launcher build\\guards\\codebase-memory-mcp-launcher.exe -Make C:\\msys64\\usr\\bin\\make.exe"
vm_cmd "cd /d C:\\cbm && set PATH=C:\\msys64\\clangarm64\\bin;C:\\msys64\\usr\\bin;%PATH%&& powershell -NoProfile -ExecutionPolicy Bypass -File scripts\\test-windows.ps1 -GuardsOnly -Binary build\\guards\\codebase-memory-mcp.exe -Make C:\\msys64\\usr\\bin\\make.exe"
;;
smoke-install)
# EXACTLY the PR CI smoke job (pr.yml pr-smoke windows): a clean canonical
+15 -306
View File
@@ -465,10 +465,10 @@ static cbm_cli_activation_ops_t cli_activation_fake_ops(cli_activation_fake_t *f
/* Every install/update/uninstall in this suite dispatches through here. On
* Windows a test that has not installed its own activation ops gets a default
* fake for the duration of the command: without the seam, the portable-payload
* gate (correctly) refuses managed mutations before the shared agent-config
* logic these tests verify ever runs. POSIX behavior is untouched tests
* without ops keep exercising the real activation machinery. */
* fake for the duration of the command: without the seam, `update` (correctly)
* hands off to install.ps1 before the shared agent-config logic these tests
* verify ever runs. POSIX behavior is untouched tests without ops keep
* exercising the real activation machinery. */
static cli_activation_fake_t g_cli_test_seam_fake;
static cbm_cli_activation_ops_t g_cli_test_seam_ops;
@@ -3688,249 +3688,6 @@ static unsigned char *create_test_zip_stored(const char *filename, const unsigne
return zip;
}
static void test_zip_put_u16(unsigned char *output, uint16_t value) {
output[0] = (unsigned char)value;
output[1] = (unsigned char)(value >> 8);
}
static void test_zip_put_u32(unsigned char *output, uint32_t value) {
output[0] = (unsigned char)value;
output[1] = (unsigned char)(value >> 8);
output[2] = (unsigned char)(value >> 16);
output[3] = (unsigned char)(value >> 24);
}
typedef struct {
const char *name;
const unsigned char *content;
size_t content_size;
} test_zip_entry_t;
static unsigned char *create_test_zip_entries(const test_zip_entry_t *entries, size_t entry_count,
int *out_len) {
enum { TEST_ZIP_ENTRY_MAX = 8 };
if (!entries || !out_len || entry_count == 0 || entry_count > TEST_ZIP_ENTRY_MAX) {
return NULL;
}
size_t local_size = 0;
size_t central_size = 0;
for (size_t index = 0; index < entry_count; index++) {
size_t name_size = strlen(entries[index].name);
local_size += 30U + name_size + entries[index].content_size;
central_size += 46U + name_size;
}
size_t total = local_size + central_size + 22U;
if (total > INT_MAX) {
return NULL;
}
unsigned char *zip = calloc(1, total);
if (!zip) {
return NULL;
}
uint32_t local_offsets[TEST_ZIP_ENTRY_MAX];
uint32_t crcs[TEST_ZIP_ENTRY_MAX];
size_t cursor = 0;
for (size_t index = 0; index < entry_count; index++) {
size_t name_size = strlen(entries[index].name);
local_offsets[index] = (uint32_t)cursor;
crcs[index] =
(uint32_t)crc32(0L, entries[index].content, (uInt)entries[index].content_size);
zip[cursor] = 0x50;
zip[cursor + 1U] = 0x4b;
zip[cursor + 2U] = 0x03;
zip[cursor + 3U] = 0x04;
test_zip_put_u16(zip + cursor + 4U, 20U);
test_zip_put_u32(zip + cursor + 14U, crcs[index]);
test_zip_put_u32(zip + cursor + 18U, (uint32_t)entries[index].content_size);
test_zip_put_u32(zip + cursor + 22U, (uint32_t)entries[index].content_size);
test_zip_put_u16(zip + cursor + 26U, (uint16_t)name_size);
memcpy(zip + cursor + 30U, entries[index].name, name_size);
cursor += 30U + name_size;
memcpy(zip + cursor, entries[index].content, entries[index].content_size);
cursor += entries[index].content_size;
}
size_t central_offset = cursor;
for (size_t index = 0; index < entry_count; index++) {
size_t name_size = strlen(entries[index].name);
zip[cursor] = 0x50;
zip[cursor + 1U] = 0x4b;
zip[cursor + 2U] = 0x01;
zip[cursor + 3U] = 0x02;
test_zip_put_u16(zip + cursor + 4U, 20U);
test_zip_put_u16(zip + cursor + 6U, 20U);
test_zip_put_u32(zip + cursor + 16U, crcs[index]);
test_zip_put_u32(zip + cursor + 20U, (uint32_t)entries[index].content_size);
test_zip_put_u32(zip + cursor + 24U, (uint32_t)entries[index].content_size);
test_zip_put_u16(zip + cursor + 28U, (uint16_t)name_size);
test_zip_put_u32(zip + cursor + 42U, local_offsets[index]);
memcpy(zip + cursor + 46U, entries[index].name, name_size);
cursor += 46U + name_size;
}
size_t central_length = cursor - central_offset;
zip[cursor] = 0x50;
zip[cursor + 1U] = 0x4b;
zip[cursor + 2U] = 0x05;
zip[cursor + 3U] = 0x06;
test_zip_put_u16(zip + cursor + 8U, (uint16_t)entry_count);
test_zip_put_u16(zip + cursor + 10U, (uint16_t)entry_count);
test_zip_put_u32(zip + cursor + 12U, (uint32_t)central_length);
test_zip_put_u32(zip + cursor + 16U, (uint32_t)central_offset);
*out_len = (int)total;
return zip;
}
static unsigned char *create_test_zip_pair(const test_zip_entry_t entries[2], int *out_len) {
return create_test_zip_entries(entries, 2U, out_len);
}
static unsigned char *create_test_windows_release_zip(const char *launcher_name,
const char *payload_name, int *out_len) {
static const unsigned char launcher[] = "MZ-launcher";
static const unsigned char payload[] = "MZ-payload";
static const unsigned char license[] = "license";
static const unsigned char installer[] = "installer";
static const unsigned char notices[] = "notices";
test_zip_entry_t entries[5] = {
{
.name = launcher_name,
.content = launcher,
.content_size = sizeof(launcher) - 1U,
},
{
.name = payload_name,
.content = payload,
.content_size = sizeof(payload) - 1U,
},
{"LICENSE", license, sizeof(license) - 1U},
{"install.ps1", installer, sizeof(installer) - 1U},
{"THIRD_PARTY_NOTICES.md", notices, sizeof(notices) - 1U},
};
return create_test_zip_entries(entries, 5U, out_len);
}
TEST(cli_extract_windows_release_pair_rejects_incomplete_release_namespace) {
static const unsigned char launcher[] = "MZ-launcher";
static const unsigned char payload[] = "MZ-payload";
const test_zip_entry_t entries[2] = {
{"codebase-memory-mcp.exe", launcher, sizeof(launcher) - 1U},
{"codebase-memory-mcp.payload.exe", payload, sizeof(payload) - 1U},
};
int zip_length = 0;
unsigned char *zip = create_test_zip_pair(entries, &zip_length);
ASSERT_NOT_NULL(zip);
cbm_windows_release_pair_t pair;
ASSERT_FALSE(cbm_extract_windows_release_pair_from_zip(zip, zip_length, &pair));
cbm_windows_release_pair_free(&pair);
free(zip);
PASS();
}
/* Release archives retain their legal notices and the standalone installer.
* The updater must accept that exact official namespace while extracting only
* the launcher/payload pair. Synthetic two-file fixtures previously hid that
* every published Windows update would be rejected. */
TEST(cli_extract_windows_release_pair_accepts_official_release_namespace) {
static const unsigned char launcher[] = "MZ-launcher";
static const unsigned char payload[] = "MZ-payload";
static const unsigned char license[] = "license";
static const unsigned char installer[] = "installer";
static const unsigned char notices[] = "notices";
const test_zip_entry_t entries[] = {
{"codebase-memory-mcp.exe", launcher, sizeof(launcher) - 1U},
{"codebase-memory-mcp.payload.exe", payload, sizeof(payload) - 1U},
{"LICENSE", license, sizeof(license) - 1U},
{"install.ps1", installer, sizeof(installer) - 1U},
{"THIRD_PARTY_NOTICES.md", notices, sizeof(notices) - 1U},
};
int zip_length = 0;
unsigned char *zip =
create_test_zip_entries(entries, sizeof(entries) / sizeof(entries[0]), &zip_length);
ASSERT_NOT_NULL(zip);
cbm_windows_release_pair_t pair;
ASSERT_TRUE(cbm_extract_windows_release_pair_from_zip(zip, zip_length, &pair));
ASSERT_EQ(pair.launcher_len, 11);
ASSERT_EQ(pair.payload_len, 10);
ASSERT_MEM_EQ(pair.launcher, "MZ-launcher", 11);
ASSERT_MEM_EQ(pair.payload, "MZ-payload", 10);
cbm_windows_release_pair_free(&pair);
free(zip);
PASS();
}
TEST(cli_extract_windows_release_pair_rejects_unknown_release_member) {
static const unsigned char content[] = "x";
const test_zip_entry_t entries[] = {
{"codebase-memory-mcp.exe", content, sizeof(content) - 1U},
{"codebase-memory-mcp.payload.exe", content, sizeof(content) - 1U},
{"LICENSE", content, sizeof(content) - 1U},
{"install.ps1", content, sizeof(content) - 1U},
{"unexpected.dll", content, sizeof(content) - 1U},
};
int zip_length = 0;
unsigned char *zip =
create_test_zip_entries(entries, sizeof(entries) / sizeof(entries[0]), &zip_length);
ASSERT_NOT_NULL(zip);
cbm_windows_release_pair_t pair;
ASSERT_FALSE(cbm_extract_windows_release_pair_from_zip(zip, zip_length, &pair));
cbm_windows_release_pair_free(&pair);
free(zip);
PASS();
}
TEST(cli_extract_windows_release_pair_rejects_aliases_and_duplicates) {
static const struct {
const char *launcher;
const char *payload;
} attacks[] = {
{
"CODEBASE-MEMORY-MCP.EXE",
"codebase-memory-mcp.payload.exe",
},
{
"codebase-memory-mcp.exe",
"codebase-memory-mcp.exe",
},
{
".\\codebase-memory-mcp.exe",
"codebase-memory-mcp.payload.exe",
},
{
"codebase-memory-mcp.exe.",
"codebase-memory-mcp.payload.exe",
},
{
"codebase-memory-mcp.exe",
"codebase-memory-mcp.payload.exe ",
},
};
for (size_t index = 0; index < sizeof(attacks) / sizeof(attacks[0]); index++) {
int zip_length = 0;
unsigned char *zip = create_test_windows_release_zip(attacks[index].launcher,
attacks[index].payload, &zip_length);
ASSERT_NOT_NULL(zip);
cbm_windows_release_pair_t pair;
ASSERT_FALSE(cbm_extract_windows_release_pair_from_zip(zip, zip_length, &pair));
cbm_windows_release_pair_free(&pair);
free(zip);
}
PASS();
}
TEST(cli_extract_windows_release_pair_rejects_local_central_mismatch) {
int zip_length = 0;
unsigned char *zip = create_test_windows_release_zip(
"codebase-memory-mcp.exe", "codebase-memory-mcp.payload.exe", &zip_length);
ASSERT_NOT_NULL(zip);
/* Local name starts at offset 30; central metadata remains unchanged. */
zip[30] = 'x';
cbm_windows_release_pair_t pair;
ASSERT_FALSE(cbm_extract_windows_release_pair_from_zip(zip, zip_length, &pair));
cbm_windows_release_pair_free(&pair);
free(zip);
PASS();
}
TEST(cli_extract_binary_from_zip) {
const char *content = "#!/bin/sh\necho test\n";
int zip_len = 0;
@@ -11950,12 +11707,14 @@ TEST(cli_sha256_file_matches_known_vector) {
}
#ifdef _WIN32
/* The fail-closed release contract, asserted with the activation seam OFF:
* these calls take the exact dispatch a release binary ships (the portable
* body is unreachable), so the gates themselves keep direct unit coverage. */
TEST(cli_windows_release_gates_refuse_portable_mutations) {
/* The Windows update contract, asserted with the activation seam OFF so this
* takes the exact dispatch a release binary ships: `update` never replaces the
* running image in-process (Windows locks it), it prints the install.ps1
* command and exits 0. Regressing to an in-process self-update would mean
* reintroducing the launcher stub Defender flags as Trojan:Win32/Wacatac.B!ml. */
TEST(cli_windows_update_hands_off_to_install_script) {
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-portable-refusal-XXXXXX");
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-update-handoff-XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("cbm_mkdtemp failed");
}
@@ -11972,61 +11731,17 @@ TEST(cli_windows_release_gates_refuse_portable_mutations) {
snprintf(bin_dir, sizeof(bin_dir), "%s/.local/bin", tmpdir);
test_mkdirp(bin_dir);
snprintf(bin_target, sizeof(bin_target), "%s/codebase-memory-mcp.exe", bin_dir);
write_test_file(bin_target, "portable refusal must not touch this");
write_test_file(bin_target, "in-process update must not touch this");
/* A one-shot portable payload (this test runner has no launcher context)
* may never self-mutate a managed surface. */
char *uninstall_argv[] = {"--yes"};
int uninstall_rc = cbm_cmd_uninstall(1, uninstall_argv);
char *update_argv[] = {"--yes"};
int update_rc = cbm_cmd_update(1, update_argv);
const char *installed = read_test_file(bin_target);
bool preserved = installed && strcmp(installed, "portable refusal must not touch this") == 0;
bool preserved = installed && strcmp(installed, "in-process update must not touch this") == 0;
cli_activation_restore_env(old_home, old_cache);
test_rmdir_r(tmpdir);
ASSERT_EQ(uninstall_rc, 1);
ASSERT_EQ(update_rc, 1);
ASSERT_TRUE(preserved);
PASS();
}
TEST(cli_windows_release_gate_refuses_foreign_install_target) {
char tmpdir[256];
snprintf(tmpdir, sizeof(tmpdir), "/tmp/cli-foreign-target-XXXXXX");
if (!cbm_mkdtemp(tmpdir)) {
FAIL("cbm_mkdtemp failed");
}
char *old_home = NULL;
char *old_cache = NULL;
cli_activation_save_env(&old_home, &old_cache);
cbm_setenv("HOME", tmpdir, 1);
char cache_dir[512];
snprintf(cache_dir, sizeof(cache_dir), "%s/cache", tmpdir);
cbm_setenv("CBM_CACHE_DIR", cache_dir, 1);
char bin_dir[512];
char bin_target[640];
snprintf(bin_dir, sizeof(bin_dir), "%s/.local/bin", tmpdir);
test_mkdirp(bin_dir);
snprintf(bin_target, sizeof(bin_target), "%s/codebase-memory-mcp.exe", bin_dir);
write_test_file(bin_target, "foreign binary must be preserved");
/* An unmanaged file at the canonical launcher path is a conflict the
* managed transaction refuses to adopt or replace even under --force
* with prompts auto-answered. */
cbm_set_auto_answer_for_test(-1);
char *argv[] = {"--force", "-y"};
int rc = cbm_cmd_install(2, argv);
cbm_set_auto_answer_for_test(0);
const char *installed = read_test_file(bin_target);
bool preserved = installed && strcmp(installed, "foreign binary must be preserved") == 0;
cli_activation_restore_env(old_home, old_cache);
test_rmdir_r(tmpdir);
ASSERT_EQ(rc, 1);
ASSERT_EQ(update_rc, 0);
ASSERT_TRUE(preserved);
PASS();
}
@@ -12070,8 +11785,7 @@ SUITE(cli) {
RUN_TEST(cli_uninstall_preserves_binary_and_index_when_cohort_does_not_drain);
RUN_TEST(cli_activation_guard_is_bypassed_for_dry_run_and_plan);
#ifdef _WIN32
RUN_TEST(cli_windows_release_gates_refuse_portable_mutations);
RUN_TEST(cli_windows_release_gate_refuses_foreign_install_target);
RUN_TEST(cli_windows_update_hands_off_to_install_script);
#endif
/* Version (2 tests — selfupdate_test.go) */
@@ -12159,11 +11873,6 @@ SUITE(cli) {
RUN_TEST(cli_extract_binary_from_targz_not_found);
RUN_TEST(cli_extract_binary_from_targz_invalid_data);
RUN_TEST(cli_extract_binary_from_zip);
RUN_TEST(cli_extract_windows_release_pair_rejects_incomplete_release_namespace);
RUN_TEST(cli_extract_windows_release_pair_accepts_official_release_namespace);
RUN_TEST(cli_extract_windows_release_pair_rejects_unknown_release_member);
RUN_TEST(cli_extract_windows_release_pair_rejects_aliases_and_duplicates);
RUN_TEST(cli_extract_windows_release_pair_rejects_local_central_mismatch);
RUN_TEST(cli_extract_binary_from_zip_not_found);
RUN_TEST(cli_extract_binary_from_zip_path_traversal);
RUN_TEST(cli_extract_binary_from_zip_invalid);
-2
View File
@@ -682,7 +682,6 @@ extern void suite_config_toml_edit(void);
extern void suite_config_yaml_edit(void);
extern void suite_config_text_edit(void);
extern void suite_activation_transaction(void);
extern void suite_windows_launcher_state(void);
extern void suite_system_info(void);
extern void suite_worker_pool(void);
extern void suite_parallel(void);
@@ -949,7 +948,6 @@ int main(int argc, char **argv) {
RUN_SELECTED_SUITE(config_yaml_edit);
RUN_SELECTED_SUITE(config_text_edit);
RUN_SELECTED_SUITE(activation_transaction);
RUN_SELECTED_SUITE(windows_launcher_state);
/* System info + worker pool (parallelism) */
RUN_SELECTED_SUITE(system_info);
+19 -11
View File
@@ -163,16 +163,20 @@ require(
"fixture checksums must name exact artifact basenames, never ./-prefixed paths",
)
# Native Windows packages and serves the exact five-file release bundle, then
# runs the full smoke from a protected profile-rooted directory/cache.
# Native Windows packages and serves the exact four-file release bundle (ONE
# binary, like every other platform), then runs the full smoke from a protected
# profile-rooted directory/cache.
for name in (
"codebase-memory-mcp.exe",
"codebase-memory-mcp.payload.exe",
"LICENSE",
"install.ps1",
"THIRD_PARTY_NOTICES.md",
):
require(name in vm_smoke, f"vm-smoke.sh archive must include {name}")
require(
"codebase-memory-mcp.payload.exe" not in vm_smoke,
"vm-smoke.sh must not stage a Windows launcher/payload pair",
)
require("checksums.txt" in vm_smoke, "vm-smoke.sh must generate checksums.txt")
require(
"SMOKE_DOWNLOAD_URL=" in vm_smoke
@@ -245,17 +249,21 @@ for service in ("smoke-windows:",):
)
section = match.group("body") if match else ""
require(
"mv build/win-cross/codebase-memory-mcp.exe "
"build/win-cross/codebase-memory-mcp.payload.exe" in section
and "mv build/win-cross/codebase-memory-mcp-launcher.exe "
"build/win-cross/codebase-memory-mcp.exe" in section,
f"docker-compose {service[:-1]} must assemble the Windows launcher/payload pair",
"codebase-memory-mcp-launcher" not in section
and "codebase-memory-mcp.payload.exe" not in section.replace(
"test ! -e build/win-cross/codebase-memory-mcp.payload.exe", ""
),
f"docker-compose {service[:-1]} must build ONE Windows binary, not a launcher/payload pair",
)
require(
"test ! -e build/win-cross/codebase-memory-mcp.payload.exe" in section,
f"docker-compose {service[:-1]} must assert no payload sibling is produced",
)
require(
"wine64 ./build/win-cross/codebase-memory-mcp.payload.exe --version" in compose
"wine64 ./build/win-cross/codebase-memory-mcp.exe --version" in compose
and "wine64 cmd /c build/win-cross/codebase-memory-mcp.exe --version" in compose,
"docker-compose Windows cross-smoke must execute the payload through Wine and the launcher "
"through a Wine Windows parent",
"docker-compose Windows cross-smoke must execute the single binary through Wine and through a "
"Wine Windows parent",
)
require(
"soak-windows:" not in compose,
+2 -3
View File
@@ -98,9 +98,8 @@ ALLOWED_CMDS = {
# CI plumbing
"gh", "python3", "node", "codesign", "xcrun", "command", "awk",
}
# Per-file additions: _build.yml packages artifacts (its make invocation builds
# the launcher exe for the zip — packaging, not a test leg) and drives npm for
# the embedded UI; release.yml pushes finished artifacts to registries after
# Per-file additions: _build.yml packages artifacts (make/strip are packaging
# steps, not test legs) and drives npm for the embedded UI; release.yml pushes finished artifacts to registries after
# every product-exercising gate already ran; the brew tap smoke installs the
# released formula.
ALLOWED_EXTRA = {
+3 -4
View File
@@ -116,16 +116,15 @@ if ! grep -Fq "JOBS='\$(nproc)'" "$provisioner" ||
exit 1
fi
guards_block="$(sed -n '/^guards)/,/^ ;;/p' "$driver")"
# The guards leg builds the CI-shaped product payload (embedded UI + launcher)
# into an isolated BUILD_DIR, then hands those artifacts to the maintained
# The guards leg builds the CI-shaped product binary (embedded UI) into an
# isolated BUILD_DIR, then hands that artifact to the maintained
# PowerShell driver through a plain cmd shell (CI's environment shape, not
# the MSYS2 login shell whose TMP ancestry the daemon correctly refuses).
if ! grep -Fq 'scripts/build.sh --with-ui CC=clang CXX=clang++ SANITIZE= BUILD_DIR=build/guards' \
<<<"$guards_block" ||
! grep -Fq 'powershell -NoProfile -ExecutionPolicy Bypass -File scripts\\test-windows.ps1' \
<<<"$guards_block" ||
! grep -Fq -- '-GuardsOnly -Binary build\\guards\\codebase-memory-mcp.exe' <<<"$guards_block" ||
! grep -Fq -- '-Launcher build\\guards\\codebase-memory-mcp-launcher.exe' <<<"$guards_block"; then
! grep -Fq -- '-GuardsOnly -Binary build\\guards\\codebase-memory-mcp.exe' <<<"$guards_block"; then
echo "FAIL: Windows VM guards must delegate to the maintained native-Windows driver" >&2
exit 1
fi
File diff suppressed because it is too large Load Diff
-450
View File
@@ -1,450 +0,0 @@
/*
* test_windows_launcher_state.c Permanent Windows launcher state contract.
*
* This suite exercises the stable public byte contract for the managed Windows
* layout: a tiny launcher reads one fixed current-v1 record and resolves the
* immutable payload generation
* relative to its own location. Keep these pure helpers platform-independent
* so the record format and command policy are exercised on every CI host.
*/
#include "test_framework.h"
#include <cli/windows_launcher_state.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <wchar.h>
static const char launcher_sha256[] = "0123456789abcdef0123456789abcdef"
"0123456789abcdef0123456789abcdef";
static cbm_windows_current_v1_t launcher_valid_state(void) {
cbm_windows_current_v1_t state;
memset(&state, 0, sizeof(state));
state.launcher_abi_min = 1;
state.launcher_abi_max = 3;
state.payload_size = UINT64_C(0x0102030405060708);
memcpy(state.payload_sha256, launcher_sha256, sizeof(launcher_sha256));
return state;
}
static bool launcher_memory_is_zero(const void *memory, size_t size) {
const uint8_t *bytes = memory;
for (size_t index = 0; index < size; index++) {
if (bytes[index] != 0) {
return false;
}
}
return true;
}
static void launcher_put_u32_le(uint8_t *out, uint32_t value) {
out[0] = (uint8_t)value;
out[1] = (uint8_t)(value >> 8);
out[2] = (uint8_t)(value >> 16);
out[3] = (uint8_t)(value >> 24);
}
static void launcher_put_u64_le(uint8_t *out, uint64_t value) {
for (unsigned int index = 0; index < 8U; index++) {
out[index] = (uint8_t)(value >> (index * 8U));
}
}
static cbm_windows_release_descriptor_v1_t launcher_valid_descriptor(void) {
cbm_windows_release_descriptor_v1_t descriptor;
memset(&descriptor, 0, sizeof(descriptor));
descriptor.launcher_abi = 2U;
descriptor.payload_launcher_abi_min = 1U;
descriptor.payload_launcher_abi_max = 3U;
descriptor.payload_size = UINT64_C(0x0102030405060708);
memcpy(descriptor.payload_sha256, launcher_sha256, sizeof(launcher_sha256));
return descriptor;
}
TEST(windows_release_descriptor_v1_encoding_is_exact_and_round_trips) {
cbm_windows_release_descriptor_v1_t descriptor = launcher_valid_descriptor();
uint8_t actual[CBM_WINDOWS_RELEASE_DESCRIPTOR_V1_SIZE];
uint8_t expected[CBM_WINDOWS_RELEASE_DESCRIPTOR_V1_SIZE];
memset(expected, 0, sizeof(expected));
memcpy(expected, "CBMWRD1\0", 8U);
launcher_put_u32_le(expected + 8U, 1U);
launcher_put_u32_le(expected + 12U, CBM_WINDOWS_RELEASE_DESCRIPTOR_V1_SIZE);
launcher_put_u32_le(expected + 16U, descriptor.launcher_abi);
launcher_put_u32_le(expected + 20U, descriptor.payload_launcher_abi_min);
launcher_put_u32_le(expected + 24U, descriptor.payload_launcher_abi_max);
launcher_put_u64_le(expected + 32U, descriptor.payload_size);
memcpy(expected + 40U, launcher_sha256, 64U);
ASSERT_TRUE(cbm_windows_release_descriptor_v1_encode(&descriptor, actual));
ASSERT_MEM_EQ(actual, expected, sizeof(actual));
cbm_windows_release_descriptor_v1_t decoded;
ASSERT_TRUE(cbm_windows_release_descriptor_v1_decode(actual, sizeof(actual), &decoded));
ASSERT_EQ(decoded.launcher_abi, descriptor.launcher_abi);
ASSERT_EQ(decoded.payload_launcher_abi_min, descriptor.payload_launcher_abi_min);
ASSERT_EQ(decoded.payload_launcher_abi_max, descriptor.payload_launcher_abi_max);
ASSERT_EQ(decoded.payload_size, descriptor.payload_size);
ASSERT_STR_EQ(decoded.payload_sha256, descriptor.payload_sha256);
PASS();
}
TEST(windows_release_descriptor_v1_rejects_noncanonical_or_incompatible) {
cbm_windows_release_descriptor_v1_t descriptor = launcher_valid_descriptor();
uint8_t record[CBM_WINDOWS_RELEASE_DESCRIPTOR_V1_SIZE];
ASSERT_TRUE(cbm_windows_release_descriptor_v1_encode(&descriptor, record));
record[28] = 1U;
ASSERT_FALSE(cbm_windows_release_descriptor_v1_decode(record, sizeof(record), &descriptor));
record[28] = 0U;
record[104] = 1U;
ASSERT_FALSE(cbm_windows_release_descriptor_v1_decode(record, sizeof(record), &descriptor));
descriptor = launcher_valid_descriptor();
descriptor.launcher_abi = 4U;
ASSERT_FALSE(cbm_windows_release_descriptor_v1_encode(&descriptor, record));
descriptor = launcher_valid_descriptor();
descriptor.payload_launcher_abi_min = 0U;
ASSERT_FALSE(cbm_windows_release_descriptor_v1_encode(&descriptor, record));
PASS();
}
TEST(windows_transition_plan_requires_a_crash_safe_compatibility_bridge) {
cbm_windows_current_v1_t current = launcher_valid_state();
cbm_windows_release_descriptor_v1_t candidate = launcher_valid_descriptor();
current.launcher_abi_min = 1U;
current.launcher_abi_max = 1U;
candidate.launcher_abi = 1U;
candidate.payload_launcher_abi_min = 1U;
candidate.payload_launcher_abi_max = 1U;
ASSERT_EQ(cbm_windows_transition_plan(&current, &candidate),
CBM_WINDOWS_TRANSITION_LAUNCHER_FIRST);
candidate.launcher_abi = 2U;
candidate.payload_launcher_abi_min = 1U;
candidate.payload_launcher_abi_max = 2U;
ASSERT_EQ(cbm_windows_transition_plan(&current, &candidate),
CBM_WINDOWS_TRANSITION_CURRENT_FIRST);
current.launcher_abi_min = 1U;
current.launcher_abi_max = 2U;
candidate.payload_launcher_abi_min = 2U;
candidate.payload_launcher_abi_max = 2U;
ASSERT_EQ(cbm_windows_transition_plan(&current, &candidate),
CBM_WINDOWS_TRANSITION_LAUNCHER_FIRST);
current.launcher_abi_min = 1U;
current.launcher_abi_max = 1U;
ASSERT_EQ(cbm_windows_transition_plan(&current, &candidate),
CBM_WINDOWS_TRANSITION_INCOMPATIBLE);
current.launcher_abi_min = 1U;
current.launcher_abi_max = 3U;
candidate.launcher_abi = 4U;
candidate.payload_launcher_abi_min = 2U;
candidate.payload_launcher_abi_max = 4U;
ASSERT_EQ(cbm_windows_transition_plan(&current, &candidate),
CBM_WINDOWS_TRANSITION_INCOMPATIBLE);
ASSERT_EQ(cbm_windows_transition_plan(NULL, &candidate), CBM_WINDOWS_TRANSITION_INCOMPATIBLE);
PASS();
}
TEST(windows_current_v1_encoding_is_exact_fixed_little_endian_record) {
cbm_windows_current_v1_t state = launcher_valid_state();
state.launcher_abi_min = UINT32_C(0x01020304);
state.launcher_abi_max = UINT32_C(0x05060708);
uint8_t actual[CBM_WINDOWS_CURRENT_V1_SIZE];
memset(actual, 0xa5, sizeof(actual));
ASSERT_TRUE(cbm_windows_current_v1_encode(&state, actual));
uint8_t expected[128] = {0};
const uint8_t magic[8] = {'C', 'B', 'M', 'C', 'U', 'R', '1', '\0'};
memcpy(expected, magic, sizeof(magic));
launcher_put_u32_le(expected + 8, 1);
launcher_put_u32_le(expected + 12, 128);
launcher_put_u32_le(expected + 16, state.launcher_abi_min);
launcher_put_u32_le(expected + 20, state.launcher_abi_max);
expected[24] = 0x08;
expected[25] = 0x07;
expected[26] = 0x06;
expected[27] = 0x05;
expected[28] = 0x04;
expected[29] = 0x03;
expected[30] = 0x02;
expected[31] = 0x01;
memcpy(expected + 32, launcher_sha256, 64);
ASSERT_EQ(CBM_WINDOWS_CURRENT_V1_SIZE, 128);
ASSERT_MEM_EQ(actual, expected, sizeof(expected));
ASSERT_TRUE(launcher_memory_is_zero(actual + 96, 32));
PASS();
}
TEST(windows_current_v1_round_trip_preserves_authenticated_payload_fields) {
cbm_windows_current_v1_t input = launcher_valid_state();
uint8_t record[CBM_WINDOWS_CURRENT_V1_SIZE];
ASSERT_TRUE(cbm_windows_current_v1_encode(&input, record));
cbm_windows_current_v1_t output;
memset(&output, 0xa5, sizeof(output));
ASSERT_TRUE(cbm_windows_current_v1_decode(record, sizeof(record), &output));
ASSERT_EQ(output.launcher_abi_min, input.launcher_abi_min);
ASSERT_EQ(output.launcher_abi_max, input.launcher_abi_max);
ASSERT_EQ(output.payload_size, input.payload_size);
ASSERT_STR_EQ(output.payload_sha256, input.payload_sha256);
ASSERT_EQ(output.payload_sha256[64], '\0');
PASS();
}
TEST(windows_current_v1_launcher_abi_range_is_inclusive_and_fail_closed) {
cbm_windows_current_v1_t state = launcher_valid_state();
ASSERT_FALSE(cbm_windows_current_v1_supports_launcher_abi(&state, 0));
ASSERT_TRUE(cbm_windows_current_v1_supports_launcher_abi(&state, 1));
ASSERT_TRUE(cbm_windows_current_v1_supports_launcher_abi(&state, 2));
ASSERT_TRUE(cbm_windows_current_v1_supports_launcher_abi(&state, 3));
ASSERT_FALSE(cbm_windows_current_v1_supports_launcher_abi(&state, 4));
ASSERT_FALSE(cbm_windows_current_v1_supports_launcher_abi(NULL, 2));
PASS();
}
TEST(windows_current_v1_encoder_rejects_noncanonical_state) {
cbm_windows_current_v1_t state = launcher_valid_state();
uint8_t record[CBM_WINDOWS_CURRENT_V1_SIZE];
ASSERT_FALSE(cbm_windows_current_v1_encode(NULL, record));
ASSERT_FALSE(cbm_windows_current_v1_encode(&state, NULL));
state.launcher_abi_min = 0;
ASSERT_FALSE(cbm_windows_current_v1_encode(&state, record));
state = launcher_valid_state();
state.launcher_abi_min = 4;
state.launcher_abi_max = 3;
ASSERT_FALSE(cbm_windows_current_v1_encode(&state, record));
state = launcher_valid_state();
state.payload_size = 0;
ASSERT_FALSE(cbm_windows_current_v1_encode(&state, record));
state = launcher_valid_state();
state.payload_sha256[0] = 'A';
ASSERT_FALSE(cbm_windows_current_v1_encode(&state, record));
state = launcher_valid_state();
state.payload_sha256[17] = 'g';
ASSERT_FALSE(cbm_windows_current_v1_encode(&state, record));
state = launcher_valid_state();
memset(state.payload_sha256, 'a', sizeof(state.payload_sha256));
ASSERT_FALSE(cbm_windows_current_v1_encode(&state, record));
PASS();
}
static int launcher_assert_decode_rejected(const uint8_t *record, size_t size) {
cbm_windows_current_v1_t output;
memset(&output, 0xa5, sizeof(output));
if (cbm_windows_current_v1_decode(record, size, &output)) {
return 1;
}
/* A rejected attacker-controlled record must not leave partial state that
* a caller could accidentally consume. */
return launcher_memory_is_zero(&output, sizeof(output)) ? 0 : 1;
}
TEST(windows_current_v1_decoder_rejects_every_noncanonical_field) {
cbm_windows_current_v1_t state = launcher_valid_state();
uint8_t valid[CBM_WINDOWS_CURRENT_V1_SIZE + 1];
memset(valid, 0, sizeof(valid));
ASSERT_TRUE(cbm_windows_current_v1_encode(&state, valid));
ASSERT_EQ(launcher_assert_decode_rejected(valid, 127), 0);
ASSERT_EQ(launcher_assert_decode_rejected(valid, 129), 0);
ASSERT_FALSE(cbm_windows_current_v1_decode(NULL, 128, &state));
ASSERT_FALSE(cbm_windows_current_v1_decode(valid, 128, NULL));
uint8_t damaged[CBM_WINDOWS_CURRENT_V1_SIZE];
memcpy(damaged, valid, sizeof(damaged));
damaged[0] ^= 1;
ASSERT_EQ(launcher_assert_decode_rejected(damaged, sizeof(damaged)), 0);
memcpy(damaged, valid, sizeof(damaged));
launcher_put_u32_le(damaged + 8, 2);
ASSERT_EQ(launcher_assert_decode_rejected(damaged, sizeof(damaged)), 0);
memcpy(damaged, valid, sizeof(damaged));
launcher_put_u32_le(damaged + 12, 127);
ASSERT_EQ(launcher_assert_decode_rejected(damaged, sizeof(damaged)), 0);
memcpy(damaged, valid, sizeof(damaged));
launcher_put_u32_le(damaged + 16, 0);
ASSERT_EQ(launcher_assert_decode_rejected(damaged, sizeof(damaged)), 0);
memcpy(damaged, valid, sizeof(damaged));
launcher_put_u32_le(damaged + 16, 4);
launcher_put_u32_le(damaged + 20, 3);
ASSERT_EQ(launcher_assert_decode_rejected(damaged, sizeof(damaged)), 0);
memcpy(damaged, valid, sizeof(damaged));
memset(damaged + 24, 0, 8);
ASSERT_EQ(launcher_assert_decode_rejected(damaged, sizeof(damaged)), 0);
memcpy(damaged, valid, sizeof(damaged));
damaged[32] = 'A';
ASSERT_EQ(launcher_assert_decode_rejected(damaged, sizeof(damaged)), 0);
memcpy(damaged, valid, sizeof(damaged));
damaged[63] = 'g';
ASSERT_EQ(launcher_assert_decode_rejected(damaged, sizeof(damaged)), 0);
memcpy(damaged, valid, sizeof(damaged));
damaged[96] = 1;
ASSERT_EQ(launcher_assert_decode_rejected(damaged, sizeof(damaged)), 0);
PASS();
}
TEST(windows_generation_path_is_launcher_relative_for_custom_unicode_directory) {
const wchar_t launcher[] =
L"D:\\CBM Custom\\M\u00e4rtin\\\u5de5\u5177\\codebase-memory-mcp.exe";
const wchar_t expected[] = L"D:\\CBM Custom\\M\u00e4rtin\\\u5de5\u5177\\.cbm\\generations\\"
L"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\\"
L"codebase-memory-mcp.payload.exe";
wchar_t actual[512];
ASSERT_TRUE(cbm_windows_generation_payload_path(launcher, launcher_sha256, actual,
sizeof(actual) / sizeof(actual[0])));
ASSERT_EQ(wcscmp(actual, expected), 0);
const wchar_t root_launcher[] = L"C:\\codebase-memory-mcp.exe";
const wchar_t root_expected[] =
L"C:\\.cbm\\generations\\"
L"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\\"
L"codebase-memory-mcp.payload.exe";
ASSERT_TRUE(cbm_windows_generation_payload_path(root_launcher, launcher_sha256, actual,
sizeof(actual) / sizeof(actual[0])));
ASSERT_EQ(wcscmp(actual, root_expected), 0);
PASS();
}
TEST(windows_generation_launcher_path_shares_the_payload_generation) {
const wchar_t launcher[] =
L"D:\\CBM Custom\\M\u00e4rtin\\\u5de5\u5177\\codebase-memory-mcp.exe";
const wchar_t expected[] = L"D:\\CBM Custom\\M\u00e4rtin\\\u5de5\u5177\\.cbm\\generations\\"
L"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\\"
L"codebase-memory-mcp.exe";
wchar_t actual[512];
ASSERT_TRUE(cbm_windows_generation_launcher_path(launcher, launcher_sha256, actual,
sizeof(actual) / sizeof(actual[0])));
ASSERT_EQ(wcscmp(actual, expected), 0);
const wchar_t root_launcher[] = L"C:\\codebase-memory-mcp.exe";
const wchar_t root_expected[] =
L"C:\\.cbm\\generations\\"
L"0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\\"
L"codebase-memory-mcp.exe";
ASSERT_TRUE(cbm_windows_generation_launcher_path(root_launcher, launcher_sha256, actual,
sizeof(actual) / sizeof(actual[0])));
ASSERT_EQ(wcscmp(actual, root_expected), 0);
PASS();
}
TEST(windows_retired_state_path_is_unique_safe_and_launcher_relative) {
const wchar_t launcher[] =
L"D:\\CBM Custom\\M\u00e4rtin\\\u5de5\u5177\\codebase-memory-mcp.exe";
const wchar_t expected[] =
L"D:\\CBM Custom\\M\u00e4rtin\\\u5de5\u5177\\.cbm-retired-v1-"
L"0123456789abcdef-4242";
wchar_t actual[512];
ASSERT_TRUE(cbm_windows_retired_state_path(launcher, launcher_sha256, 4242U, actual,
sizeof(actual) / sizeof(actual[0])));
ASSERT_EQ(wcscmp(actual, expected), 0);
ASSERT_FALSE(cbm_windows_retired_state_path(launcher, launcher_sha256, 0U, actual,
sizeof(actual) / sizeof(actual[0])));
ASSERT_EQ(actual[0], L'\0');
ASSERT_FALSE(cbm_windows_retired_state_path(L"codebase-memory-mcp.exe", launcher_sha256, 4242U,
actual, sizeof(actual) / sizeof(actual[0])));
ASSERT_EQ(actual[0], L'\0');
ASSERT_FALSE(cbm_windows_retired_state_path(launcher, launcher_sha256, 4242U, actual, 32U));
ASSERT_EQ(actual[0], L'\0');
PASS();
}
TEST(windows_generation_path_rejects_ambiguous_or_truncated_inputs) {
wchar_t output[512];
memset(output, 0xa5, sizeof(output));
ASSERT_FALSE(cbm_windows_generation_payload_path(L"codebase-memory-mcp.exe", launcher_sha256,
output, sizeof(output) / sizeof(output[0])));
ASSERT_EQ(output[0], L'\0');
ASSERT_FALSE(cbm_windows_generation_payload_path(L"C:\\codebase-memory-mcp.exe",
launcher_sha256, output, 12));
ASSERT_EQ(output[0], L'\0');
char uppercase[65];
memcpy(uppercase, launcher_sha256, sizeof(uppercase));
uppercase[0] = 'A';
ASSERT_FALSE(cbm_windows_generation_payload_path(L"C:\\codebase-memory-mcp.exe", uppercase,
output, sizeof(output) / sizeof(output[0])));
ASSERT_EQ(output[0], L'\0');
ASSERT_FALSE(cbm_windows_generation_launcher_path(L"codebase-memory-mcp.exe", launcher_sha256,
output, sizeof(output) / sizeof(output[0])));
ASSERT_EQ(output[0], L'\0');
ASSERT_FALSE(cbm_windows_generation_launcher_path(L"C:\\codebase-memory-mcp.exe",
launcher_sha256, output, 12));
ASSERT_EQ(output[0], L'\0');
ASSERT_FALSE(cbm_windows_generation_launcher_path(L"C:\\codebase-memory-mcp.exe", uppercase,
output, sizeof(output) / sizeof(output[0])));
ASSERT_EQ(output[0], L'\0');
PASS();
}
TEST(windows_launcher_action_classifier_matches_top_level_dispatch) {
const char *ordinary_mcp[] = {"codebase-memory-mcp.exe"};
const char *ordinary_cli[] = {"codebase-memory-mcp.exe", "cli", "search_graph",
"{\"query\":\"update\"}"};
const char *ordinary_install[] = {"codebase-memory-mcp.exe", "install", "update"};
const char *ordinary_help[] = {"codebase-memory-mcp.exe", "--help", "update"};
const char *update[] = {"codebase-memory-mcp.exe", "--profile", "update", "--yes"};
const char *uninstall[] = {"codebase-memory-mcp.exe", "ignored", "uninstall", "--yes"};
const char *first_wins[] = {"codebase-memory-mcp.exe", "update", "uninstall"};
ASSERT_EQ(cbm_windows_launcher_classify_action(1, ordinary_mcp),
CBM_WINDOWS_LAUNCHER_ACTION_ORDINARY);
ASSERT_EQ(cbm_windows_launcher_classify_action(4, ordinary_cli),
CBM_WINDOWS_LAUNCHER_ACTION_ORDINARY);
ASSERT_EQ(cbm_windows_launcher_classify_action(3, ordinary_install),
CBM_WINDOWS_LAUNCHER_ACTION_ORDINARY);
ASSERT_EQ(cbm_windows_launcher_classify_action(3, ordinary_help),
CBM_WINDOWS_LAUNCHER_ACTION_ORDINARY);
ASSERT_EQ(cbm_windows_launcher_classify_action(4, update), CBM_WINDOWS_LAUNCHER_ACTION_UPDATE);
ASSERT_EQ(cbm_windows_launcher_classify_action(4, uninstall),
CBM_WINDOWS_LAUNCHER_ACTION_UNINSTALL);
ASSERT_EQ(cbm_windows_launcher_classify_action(3, first_wins),
CBM_WINDOWS_LAUNCHER_ACTION_UPDATE);
ASSERT_EQ(cbm_windows_launcher_classify_action(0, NULL), CBM_WINDOWS_LAUNCHER_ACTION_ORDINARY);
PASS();
}
TEST(windows_portable_payload_rejects_only_managed_mutations) {
ASSERT_TRUE(cbm_windows_launcher_action_allowed(CBM_WINDOWS_LAUNCHER_ACTION_ORDINARY, false));
ASSERT_FALSE(cbm_windows_launcher_action_allowed(CBM_WINDOWS_LAUNCHER_ACTION_UPDATE, false));
ASSERT_FALSE(cbm_windows_launcher_action_allowed(CBM_WINDOWS_LAUNCHER_ACTION_UNINSTALL, false));
ASSERT_TRUE(cbm_windows_launcher_action_allowed(CBM_WINDOWS_LAUNCHER_ACTION_ORDINARY, true));
ASSERT_TRUE(cbm_windows_launcher_action_allowed(CBM_WINDOWS_LAUNCHER_ACTION_UPDATE, true));
ASSERT_TRUE(cbm_windows_launcher_action_allowed(CBM_WINDOWS_LAUNCHER_ACTION_UNINSTALL, true));
ASSERT_FALSE(cbm_windows_launcher_action_allowed((cbm_windows_launcher_action_t)99, true));
PASS();
}
SUITE(windows_launcher_state) {
RUN_TEST(windows_release_descriptor_v1_encoding_is_exact_and_round_trips);
RUN_TEST(windows_release_descriptor_v1_rejects_noncanonical_or_incompatible);
RUN_TEST(windows_transition_plan_requires_a_crash_safe_compatibility_bridge);
RUN_TEST(windows_current_v1_encoding_is_exact_fixed_little_endian_record);
RUN_TEST(windows_current_v1_round_trip_preserves_authenticated_payload_fields);
RUN_TEST(windows_current_v1_launcher_abi_range_is_inclusive_and_fail_closed);
RUN_TEST(windows_current_v1_encoder_rejects_noncanonical_state);
RUN_TEST(windows_current_v1_decoder_rejects_every_noncanonical_field);
RUN_TEST(windows_generation_path_is_launcher_relative_for_custom_unicode_directory);
RUN_TEST(windows_generation_launcher_path_shares_the_payload_generation);
RUN_TEST(windows_retired_state_path_is_unique_safe_and_launcher_relative);
RUN_TEST(windows_generation_path_rejects_ambiguous_or_truncated_inputs);
RUN_TEST(windows_launcher_action_classifier_matches_top_level_dispatch);
RUN_TEST(windows_portable_payload_rejects_only_managed_mutations);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,211 @@
"""GREEN native-Windows guard for the `update` -> install.ps1 handoff.
Windows ships ONE binary, exactly like Linux and macOS: ``codebase-memory-mcp.exe``.
There used to be a second, permanently resident launcher stub whose only job
was to swap the product binary out from under itself, because a running .exe
cannot replace its own image on Windows. Defender's ML scored that stub
Trojan:Win32/Wacatac.B!ml on x64 no matter what the build changed (bcrypt-free,
stripped, versioned and resource-free variants were all flagged), while the
product binary itself scans clean everywhere. So the swap moved OUT of the
process into install.ps1, which runs while CBM is NOT running.
This guard asserts the replacement contract on real native Windows:
* ``update`` exits 0 and prints the exact install.ps1 command.
* ``update`` NEVER replaces the running image in-process the executable's
bytes are unchanged, and no launcher/payload sibling appears next to it.
* ``update`` refuses to reach the network first: it hands off before it
consults CBM_DOWNLOAD_URL, so it stays fast even when that URL is a black
hole.
* ``update`` does not disturb an already-open MCP/daemon session.
A regression here means the in-process self-update and therefore the
AV-flagged launcher stub came back.
Exit code: 0 == contract honored, 1 == regression, 2 == precondition failure.
Usage:
python test_windows_update_handoff.py <codebase-memory-mcp.exe>
"""
import hashlib
import os
import pathlib
import shutil
import subprocess
import sys
import tempfile
import time
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from mcp_stdio import McpError, McpServer # noqa: E402
class GuardFailure(Exception):
pass
def require(condition, message):
if not condition:
raise GuardFailure(message)
def sha256_file(path):
return hashlib.sha256(pathlib.Path(path).read_bytes()).hexdigest()
def output_text(result):
return ((result.stdout or b"") + b"\n" + (result.stderr or b"")).decode(
"utf-8", "replace"
)
def run(command, env, timeout=30):
try:
return subprocess.run(
[str(part) for part in command],
input=b"",
capture_output=True,
env=env,
timeout=timeout,
)
except subprocess.TimeoutExpired as exc:
raise GuardFailure(
"command exceeded %ss: %s" % (timeout, " ".join(map(str, command)))
) from exc
def isolated_environment(work):
home = work / "home"
cache = work / "cache"
home.mkdir(parents=True)
cache.mkdir(parents=True)
env = dict(os.environ)
env.update(
{
"HOME": str(home),
"USERPROFILE": str(home),
"APPDATA": str(home / "AppData" / "Roaming"),
"LOCALAPPDATA": str(home / "AppData" / "Local"),
"CBM_CACHE_DIR": str(cache),
"PYTHONUTF8": "1",
}
)
return env, cache
def copy_binary(source, directory):
directory.mkdir(parents=True, exist_ok=True)
binary = directory / "codebase-memory-mcp.exe"
shutil.copy2(source, binary)
return binary
def assert_update_hands_off_to_install_script(source, env, work):
binary = copy_binary(source, work / "update-handoff")
before = sha256_file(binary)
command_env = dict(env)
# If the handoff regresses into a real in-process update, keep its
# unintended network path deterministic and fast: a correct implementation
# never consults this URL.
command_env["CBM_DOWNLOAD_URL"] = "https://127.0.0.1:1"
# Warm the freshly-copied cold binary first: first-touch antivirus scanning
# of the just-written image inflates process load time, and the handoff
# itself is a fast STATELESS local print (no daemon IPC, no download) whose
# timing is what this guard measures. The warm-up behaves identically.
run([binary, "update", "--yes", "--standard"], command_env, timeout=20)
started = time.monotonic()
result = run([binary, "update", "--yes", "--standard"], command_env, timeout=20)
elapsed = time.monotonic() - started
diagnostic = output_text(result)
lowered = diagnostic.lower()
require(
result.returncode == 0,
"update exited %s; the Windows handoff must succeed: %s"
% (result.returncode, diagnostic[-800:]),
)
require(
"install.ps1" in lowered,
"update did not print the install.ps1 command: %s" % diagnostic[-800:],
)
require(
"powershell" in lowered,
"update did not print a runnable PowerShell command: %s" % diagnostic[-800:],
)
require(
elapsed < 8.0,
"update took %.1fs — it must hand off before any network I/O" % elapsed,
)
require(
sha256_file(binary) == before,
"update replaced the running image in-process (a running .exe cannot "
"replace itself; that is exactly what the removed launcher stub was for)",
)
require(
not (binary.parent / "codebase-memory-mcp.payload.exe").exists(),
"update recreated a launcher/payload pair beside the binary",
)
require(
not list(binary.parent.glob("*launcher*")),
"update produced a launcher artifact beside the binary",
)
print("PASS: update handed off to install.ps1 without touching its own image")
def assert_update_does_not_drain_active_session(source, env, cache, work):
binary = copy_binary(source, work / "update-session")
with McpServer(str(binary), cache_dir=str(cache), extra_env=env) as server:
server.initialize(timeout=30)
require(server.tools_list(timeout=30), "MCP control session has no tools")
command = copy_binary(source, work / "update-session-command")
command_env = dict(env)
command_env["CBM_DOWNLOAD_URL"] = "https://127.0.0.1:1"
result = run([command, "update", "--yes", "--standard"], command_env, timeout=20)
require(
result.returncode == 0,
"update exited %s beside a live session: %s"
% (result.returncode, output_text(result)[-800:]),
)
# The same already-open stdio session must still own the same live
# daemon connection. A stop-and-transparent-restart is not enough: the
# existing pipe itself has to remain usable.
require(
server.tools_list(timeout=10),
"update drained the active MCP/daemon session",
)
print("PASS: update left the active MCP/daemon session untouched")
def main():
if os.name != "nt":
print("PRECONDITION: native Windows is required")
return 2
if len(sys.argv) != 2:
print("usage: python test_windows_update_handoff.py <codebase-memory-mcp.exe>")
return 2
source = pathlib.Path(sys.argv[1]).resolve()
if not source.is_file():
print("PRECONDITION: binary not found: %s" % source)
return 2
work = pathlib.Path(tempfile.mkdtemp(prefix="cbm_win_update_"))
try:
env, cache = isolated_environment(work)
assert_update_hands_off_to_install_script(source, env, work)
assert_update_does_not_drain_active_session(source, env, cache, work)
print("\nGREEN: Windows update handoff contract honored.")
return 0
except (GuardFailure, McpError, OSError, subprocess.SubprocessError) as exc:
print("\nRED: %s" % exc)
return 1
finally:
shutil.rmtree(work, ignore_errors=True)
if __name__ == "__main__":
sys.exit(main())