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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 88 additions & 20 deletions .github/workflows/_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,13 @@ permissions:
attestations: write

jobs:
# The standard (dynamic) Linux binaries build in build-linux below, inside
# the pinned Ubuntu 22.04 container; this job builds the two macOS binaries.
build-unix:
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
goos: linux
goarch: amd64
cc: gcc
cxx: g++
- os: ubuntu-24.04-arm
goos: linux
goarch: arm64
cc: gcc
cxx: g++
# The macOS minimum (14.0) is pinned in Makefile.cbm, not inherited
# from this runner's SDK, so both darwin binaries still start on macOS 14.
- os: macos-15
Expand All @@ -64,11 +56,6 @@ jobs:
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Install deps (Ubuntu)
if: startsWith(matrix.os, 'ubuntu')
timeout-minutes: 10
run: APT_RETRIES=3; [ -f /etc/apt/apt-mirrors.txt ] && APT_RETRIES=0; sudo apt-get -o Acquire::Retries=$APT_RETRIES -o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30 --error-on=any update && sudo apt-get -o Acquire::Retries=$APT_RETRIES -o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30 install -y zlib1g-dev

- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22"
Expand All @@ -85,10 +72,6 @@ jobs:
scripts/build.sh --with-ui "CC=$CC" "CXX=$CXX"
fi

- name: Frontend integrity scan
if: matrix.goos == 'linux' && matrix.goarch == 'amd64'
run: scripts/security-ui.sh

- name: Derive immutable stripped and unstripped release candidates
run: |
scripts/ci/prepare-release-candidates.sh \
Expand All @@ -114,6 +97,91 @@ jobs:
path: release-candidates/${{ matrix.goos }}-${{ matrix.goarch }}
if-no-files-found: error

# The standard (dynamic) Linux binaries build INSIDE a pinned Ubuntu 22.04
# container; the hosted runner only hosts it. A dynamic binary requires the
# glibc and libstdc++ symbol versions of the userland it was built on: built
# on the runners' own Ubuntu 24.04 it required GLIBC_2.38 / GLIBCXX_3.4.32
# and refused to start on Ubuntu 22.04 (#2486). The distro toolchain (GCC 11
# against glibc 2.35 headers) IS the floor, so it is used as shipped, and the
# image is pinned by the digest of its multi-arch index -- one pin for both
# arches, so the floor cannot drift with the tag, and no hosted runner
# image's retirement date moves it. Resolved 2026-11-02 from Docker Hub
# (library/ubuntu, tag 22.04): registry Docker-Content-Digest == sha256 of
# the index body; bump deliberately, never to a tag. CBM_GLIBC_FLOOR /
# CBM_GLIBCXX_FLOOR make the composition gate (A6) assert the floor on every
# candidate: nothing newer than this image's glibc 2.35 / GLIBCXX_3.4.30.
# pr.yml's glibc-floor leg builds in the same image with the same floor.
build-linux:
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
runner: ubuntu-latest
- arch: arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
container:
image: ubuntu:22.04@sha256:076e7bf82319619f28dd0aa9c02ab34b055ad37100395b4f49a2ffba04fb5e11
timeout-minutes: 240
steps:
# The container starts bare: install what the runner image preinstalls
# for a host build (gcc/g++/make, git for the checkout, python3 for the
# candidate derivation) plus zlib1g-dev, as the host legs did.
- name: Install deps (Ubuntu 22.04 container)
timeout-minutes: 10
env:
DEBIAN_FRONTEND: noninteractive
run: APT_RETRIES=3; [ -f /etc/apt/apt-mirrors.txt ] && APT_RETRIES=0; apt-get -o Acquire::Retries=$APT_RETRIES -o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30 --error-on=any update && apt-get -o Acquire::Retries=$APT_RETRIES -o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30 install -y --no-install-recommends ca-certificates git gcc g++ make python3 zlib1g-dev

- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

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

- name: Build release binary with UI
env:
VERSION: ${{ inputs.version }}
run: |
if [ -n "$VERSION" ]; then
scripts/build.sh --with-ui --version "$VERSION" CC=gcc CXX=g++
else
scripts/build.sh --with-ui CC=gcc CXX=g++
fi

- name: Frontend integrity scan
if: matrix.arch == 'amd64'
run: scripts/security-ui.sh

- name: Derive immutable stripped and unstripped release candidates
env:
CBM_GLIBC_FLOOR: "2.35"
CBM_GLIBCXX_FLOOR: "3.4.30"
run: |
scripts/ci/prepare-release-candidates.sh \
linux "${{ matrix.arch }}" \
--binary build/c/codebase-memory-mcp \
--out-dir release-candidates
scripts/gen-third-party-notices.sh \
"release-candidates/linux-${{ matrix.arch }}/THIRD_PARTY_NOTICES.md"

- name: Attest release candidate provenance
if: ${{ inputs.attest }}
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
with:
subject-path: |
release-candidates/linux-${{ matrix.arch }}/candidate-provenance.tsv
release-candidates/linux-${{ matrix.arch }}/stripped/*
release-candidates/linux-${{ matrix.arch }}/unstripped/*
release-candidates/linux-${{ matrix.arch }}/THIRD_PARTY_NOTICES.md

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-candidates-linux-${{ matrix.arch }}
path: release-candidates/linux-${{ matrix.arch }}
if-no-files-found: error

build-windows:
runs-on: windows-latest
timeout-minutes: 240
Expand Down Expand Up @@ -304,7 +372,7 @@ jobs:
# only choose within that tuple. A hard result on ANY sibling blocks
# the all-target release rather than selecting around a real detection.
select-package:
needs: [build-unix, build-windows, build-windows-arm64, build-linux-portable]
needs: [build-unix, build-linux, build-windows, build-windows-arm64, build-linux-portable]
runs-on: ubuntu-latest
timeout-minutes: 330
permissions:
Expand Down
31 changes: 23 additions & 8 deletions .github/workflows/_smoke.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,15 @@ jobs:
# Broad legs are REQUIRED gates (no optional / continue-on-error):
# every smoke leg must pass. No `optional` flags anywhere.
#
# NOTE: the *dynamic* linux binary links glibc 2.38+ and cannot run on
# older distros by design — older-glibc coverage is the -portable (static)
# binary's job, exercised green by the smoke-linux-portable broad legs
# (ubuntu-22.04 / 22.04-arm). So the dynamic broad legs stay on
# forward-compatible OSes only (macOS); running the dynamic binary on
# ubuntu-22.04 would fail Phase 1 (glibc too old), not a real regression.
# NOTE: the *dynamic* linux binary is built in a pinned Ubuntu 22.04
# container (_build.yml build-linux) and links glibc 2.35+; the
# composition gate (A6) asserts that floor on the built bytes and
# each core Linux leg runs the binary in a plain container of that
# image (smoke-unix "Runtime start on the glibc floor"), so no
# broad leg re-runs it on a 22.04 runner. Older-glibc coverage stays
# the -portable (static) binary's job, exercised green by the
# smoke-linux-portable broad legs (ubuntu-22.04 / 22.04-arm) and the
# glibc 2.31 check below.
# Empty since the move off macos-14 (retired 2026-11-02): the darwin-arm64
# artifact is now built AND smoked on macos-15, the core leg, which was
# this broad leg.
Expand Down Expand Up @@ -128,6 +131,18 @@ jobs:
SMOKE_REQUIRE_UI: "1"
SMOKE_REQUIRE_WRAPPERS: "1"

# The standard (dynamic) Linux binary must START and INDEX on the oldest
# userland it claims (#2486): a plain container of the SAME pinned
# ubuntu:22.04 image _build.yml build-linux builds in, nothing installed.
# A6 asserts the floor on the bytes; this runs them, on both arches (each
# Linux leg checks its own artifact). The venue-parity contract holds
# this digest equal to build-linux's.
- name: Runtime start on the glibc floor (plain ubuntu:22.04)
if: matrix.goos == 'linux'
env:
CBM_FLOOR_IMAGE: ubuntu:22.04@sha256:076e7bf82319619f28dd0aa9c02ab34b055ad37100395b4f49a2ffba04fb5e11
run: scripts/ci/check-glibc-floor-runtime.sh --image "$CBM_FLOOR_IMAGE" "$RUNNER_TEMP/cbm-artifact/codebase-memory-mcp"

- name: Security audits
run: |
BIN="$RUNNER_TEMP/cbm-artifact/codebase-memory-mcp"
Expand Down Expand Up @@ -329,8 +344,8 @@ jobs:

# The -portable binary is what all linux install/update paths now deliver;
# it MUST start on old glibc (Debian 11 / RHEL 8 / Ubuntu 20.04). Runs it
# in debian:bullseye (glibc 2.31) — the standard dynamic binary would fail
# here with `GLIBC_2.38 not found`.
# in debian:bullseye (glibc 2.31) — the standard dynamic binary (glibc
# 2.35 floor) would not start here.
- name: Old-glibc compatibility
run: scripts/ci/check-glibc-compat.sh "$RUNNER_TEMP/cbm-artifact/codebase-memory-mcp"

Expand Down
37 changes: 36 additions & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,41 @@ jobs:
with:
mode: pr

# ── glibc floor (lane glibc-floor, GATING): builds the production binary in
# the SAME pinned ubuntu:22.04 container the standard Linux release
# binaries build in (_build.yml build-linux) and runs the composition gate
# with the same declared floor, so a change that needs a newer glibc or
# libstdc++ symbol version -- or stops compiling with that GCC 11
# toolchain -- fails the PR instead of a release run. Light on purpose: a
# headless build (the shipped composition only swaps
# src/ui/embedded_stub.c for the generated asset data) and no smoke. The
# selector picks the lane whenever it picks smoke-ubuntu, i.e. whenever
# the production binary can change; ci-ok needs it, and a selected
# glibc-floor that fails, skips or never runs fails ci-ok. ──
glibc-floor:
needs: [changes]
if: ${{ !cancelled() && needs.changes.result == 'success' && contains(fromJSON(needs.changes.outputs.lanes), 'glibc-floor') }}
runs-on: ubuntu-latest
container:
image: ubuntu:22.04@sha256:076e7bf82319619f28dd0aa9c02ab34b055ad37100395b4f49a2ffba04fb5e11
timeout-minutes: 45
steps:
- name: Install deps (Ubuntu 22.04 container)
timeout-minutes: 10
env:
DEBIAN_FRONTEND: noninteractive
run: APT_RETRIES=3; [ -f /etc/apt/apt-mirrors.txt ] && APT_RETRIES=0; apt-get -o Acquire::Retries=$APT_RETRIES -o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30 --error-on=any update && apt-get -o Acquire::Retries=$APT_RETRIES -o Acquire::http::Timeout=30 -o Acquire::https::Timeout=30 install -y --no-install-recommends ca-certificates git gcc g++ make python3 zlib1g-dev
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Build the production binary in the release container
run: scripts/build.sh CC=gcc CXX=g++
- name: Composition gate with the release glibc floor
env:
CBM_GLIBC_FLOOR: "2.35"
CBM_GLIBCXX_FLOOR: "3.4.30"
run: scripts/ci/check-binary-composition.sh build/c/codebase-memory-mcp

# ── Test-impact SHADOW, REPORT-ONLY: predicts from the code graph which C
# suites this change can affect (the latest released binary indexes the
# PR checkout; detect_changes at depth 15 against the base, mapped to
Expand Down Expand Up @@ -394,7 +429,7 @@ jobs:
# every lane the selector chose ran green: `skipped` is OK only for a lane
# nobody selected (a selected lane that skipped is a silent gate loss).
# memwaste and the test-impact shadow are report-only and not needed here.
needs: [changes, security, lint, test, pr-smoke, contracts, codeql]
needs: [changes, security, lint, test, pr-smoke, contracts, codeql, glibc-floor]
if: ${{ always() }}
runs-on: ubuntu-latest
timeout-minutes: 5
Expand Down
6 changes: 3 additions & 3 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,9 @@ else
EXT="tar.gz"
fi

# Linux ships a fully-static "-portable" build; the standard linux binary
# dynamically links glibc 2.38+ and fails on older distros (Debian 11, RHEL 8,
# Ubuntu 20.04). macOS/Windows have no such variant.
# Linux ships a fully-static "-portable" build; the standard linux binary is
# built on Ubuntu 22.04, dynamically links glibc 2.35+ and fails on older
# distros (Debian 11, RHEL 8, Ubuntu 20.04). macOS/Windows have no such variant.
PORTABLE=""
[ "$OS" = "linux" ] && PORTABLE="-portable"

Expand Down
4 changes: 2 additions & 2 deletions pkg/homebrew/Formula/codebase-memory-mcp.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ class CodebaseMemoryMcp < Formula
end
end

# The static -portable build: the dynamic one needs glibc 2.38, which
# Ubuntu 22.04, Debian 12 and RHEL 9 do not have.
# The static -portable build: the dynamic one is built on Ubuntu 22.04 and
# needs glibc 2.35, which Debian 11, RHEL 8/9 and Ubuntu 20.04 do not have.
on_linux do
on_arm do
url "https://github.com/DeusData/codebase-memory-mcp/releases/download/v#{version}/codebase-memory-mcp-linux-arm64-portable.tar.gz"
Expand Down
7 changes: 4 additions & 3 deletions pkg/npm/install.js
Original file line number Diff line number Diff line change
Expand Up @@ -742,9 +742,10 @@ async function main() {
const source = resolveDownloadBase();
fs.mkdirSync(BIN_DIR, { recursive: true });

// Linux ships a fully-static "-portable" build; the standard linux binary
// dynamically links glibc 2.38+ and fails on older distros. macOS/Windows
// have no such variant. Keep in sync with install.sh / pypi _cli.py / cli.c.
// Linux ships a fully-static "-portable" build; the standard linux binary is
// built on Ubuntu 22.04, dynamically links glibc 2.35+ and fails on older
// distros. macOS/Windows have no such variant. Keep in sync with install.sh /
// pypi _cli.py / cli.c.
const variant = platform === 'linux' ? '-portable' : '';
// No UI/standard split since v0.10.0: one archive per platform, graph UI
// always embedded. The old CBM_VARIANT=ui opt-in pointed at ui-* archives
Expand Down
7 changes: 4 additions & 3 deletions pkg/pypi/src/codebase_memory_mcp/_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1290,9 +1290,10 @@ def _download(version: str) -> Path:
os_name = _os_name()
arch = _arch()
ext = "zip" if os_name == "windows" else "tar.gz"
# Linux ships a fully-static "-portable" build; the standard linux binary
# dynamically links glibc 2.38+ and fails on older distros. macOS/Windows
# have no such variant. Keep in sync with install.sh / install.js / cli.c.
# Linux ships a fully-static "-portable" build; the standard linux binary is
# built on Ubuntu 22.04, dynamically links glibc 2.35+ and fails on older
# distros. macOS/Windows have no such variant. Keep in sync with install.sh /
# install.js / cli.c.
portable = "-portable" if os_name == "linux" else ""
archive = f"codebase-memory-mcp-{os_name}-{arch}{portable}.{ext}"
base, loopback = _download_base(version)
Expand Down
3 changes: 2 additions & 1 deletion scripts/ci/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,12 @@ CI and the local infrastructure — both of which the venue-parity contract
| `check-glibc-compat.sh` | Run a linux binary in debian:bullseye (glibc 2.31) — the portable binary must start on old glibc. | `_smoke.yml` portable legs |
| `generate-sbom.py` | The release SPDX SBOM (vendored versions reviewable here, diffable by vendoring PRs — was inline YAML). | `release.yml` |
| `require-all-green.sh` | The aggregate gate: fail unless every needed job succeeded or legitimately skipped (was inline YAML) — and, under lane selection, every SELECTED lane ran and succeeded (skipped/cancelled/missing = red; unselected lanes may skip). | `pr.yml ci-ok` |
| `select-lanes.sh` | Decide which PR lanes a change set runs (tier, lanes, full, reasons) from its file list — the R1 rule set, fail-safe to every lane. Pinned by `tests/test_select_lanes.sh` and the September 2026 history replay (`scripts/test-impact/replay-selector.sh`). | `pr.yml changes` |
| `select-lanes.sh` | Decide which PR lanes a change set runs (tier, lanes, full, reasons) from its file list — the R1 rule set, fail-safe to every lane; `glibc-floor` rides with `smoke-ubuntu`. Pinned by `tests/test_select_lanes.sh` and the September 2026 history replay (`scripts/test-impact/replay-selector.sh`). | `pr.yml changes` |
| `test-impact-shadow.sh` | The report-only test-impact shadow: fetch the latest released binary (checksum-verified) or take a given one, index this checkout into a throwaway cache, run `detect_changes` against the base and map it to suites (`scripts/test-impact/predict.py`). Never gates. | `pr.yml test-impact-shadow` |
| `verify-shard-union.sh` | Prove sharded test legs lost nothing: shard count agreement, indices 1..n, identical suite lists, union of slices == full list (was inline YAML). No manifests is green only when the lane selection holds no test leg. | `_test.yml` shard-completeness |
| `prepare-release-candidates.sh` | Copy one linker output into stripped/unstripped candidates, finalize signatures, composition-check them without execution, and record their hashes. | `_build.yml`, local artifact smoke |
| `stage-release-candidates.py` | Admit exactly eight candidate artifacts / sixteen byte-distinct binaries into the content-addressed VirusTotal scan set. | `_build.yml` |
| `select-release-candidates.py` | Apply the reviewed tuple-local VT truth table, or the explicit dry-run stripped default, and atomically copy one content-bound binary per target. | `_build.yml` |
| `verify-release-selection.py` | Recompute the selection policy and prove every executable member in all 14 public containers equals its selected SHA-256. | `_build.yml`, `release.yml` final draft verification |
| `check-virustotal.sh` | Poll and validate the exact candidate scan set, enforce engine coverage and the narrow documented Microsoft `!ml` policy, and emit content-bound results evidence. | `_build.yml` |
| `check-glibc-floor-runtime.sh` | Run the standard (dynamic) linux binary in a plain container of the pinned ubuntu:22.04 floor image (digest required, nothing installed): `--version`, `cli index_repository` on a tiny fixture and `cli list_projects` must all succeed. | `_smoke.yml` smoke-unix Linux legs |
Loading
Loading