From 0a706fc6873c0a6d80e436e3cde6d0bb2929b731 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Fri, 7 Aug 2026 12:40:51 +0100 Subject: [PATCH 1/6] fix(just): repoint cartridge recipes at the catalog root, not the deleted tree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bundled cartridges/ tree was retired in #300, but eight recipes still globbed it. Every one of them matched nothing: * `build`, `test`, `test-verbose`, `heal` looped `cartridges/*/ffi` — zero iterations, and `test` then printed "All FFI tests passed!" over an empty set. A green `just test` proving nothing is exactly the failure mode the retirement cleanup is meant to remove. * `clean` rm -rf'd `cartridges/*/ffi/{.zig-cache,zig-out}` and `cartridges/*/abi/build` — no such paths in tree. * `tour` computed `CART_COUNT=$(ls -d cartridges/*-mcp | wc -l)`, so it reported "Current cartridge count: 0" beside a line saying cartridges now live in the registry. All loops now read a catalog root, defaulting to the tracked fixture catalogue exactly as tests/e2e_full.sh does, and overridable with BOJ_CARTRIDGES_PATH for a cache populated by scripts/fetch-cartridges.sh. Each loop counts what it visited: `test`/`test-verbose` FAIL on an empty set rather than pass, `build`/`heal`/`tour` say so out loud. `clean` targets the in-tree fixture artefacts only — it must not delete an operator's fetched cache. Also drops the stale "17 cartridges" count from `help-me`. Co-Authored-By: Claude Opus 5 --- Justfile | 77 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 60 insertions(+), 17 deletions(-) diff --git a/Justfile b/Justfile index 67175d25..ce3cc32b 100644 --- a/Justfile +++ b/Justfile @@ -261,20 +261,35 @@ init: # BUILD & COMPILE # ═══════════════════════════════════════════════════════════════════════════════ -# Build all Zig FFI layers (catalogue + all cartridges) +# Build all Zig FFI layers (catalogue + every cartridge in the catalog root) +# +# The bundled cartridges/ tree was retired (canonical source: +# hyperpolymath/boj-server-cartridges). The catalog root defaults to the +# tracked fixture catalogue, as in tests/e2e_full.sh; point +# BOJ_CARTRIDGES_PATH at a fetched cache (scripts/fetch-cartridges.sh) to +# build the full registry. build *args: #!/usr/bin/env bash set -euo pipefail + CARTS="${BOJ_CARTRIDGES_PATH:-tests/fixtures/cartridges}" echo "Building BoJ catalogue FFI..." (cd ffi/zig && zig build {{args}}) - echo "Building cartridge FFIs..." + echo "Building cartridge FFIs from $CARTS ..." FAILED=() - for d in cartridges/*/ffi; do + BUILT=0 + for d in "$CARTS"/*/ffi; do [ -f "$d/build.zig" ] || continue + BUILT=$((BUILT + 1)) if ! (cd "$d" && zig build {{args}} 2>&1); then FAILED+=("$d") fi done + if [ "$BUILT" -eq 0 ]; then + echo "WARNING: no cartridge FFI (no */ffi/build.zig) under $CARTS — built the catalogue only." + echo " Populate a cache with scripts/fetch-cartridges.sh and set BOJ_CARTRIDGES_PATH." + else + echo "Attempted $BUILT cartridge FFI build(s)." + fi if [ ${#FAILED[@]} -gt 0 ]; then echo "WARNING: ${#FAILED[@]} cartridge FFI(s) failed to build:" for f in "${FAILED[@]}"; do echo " $f"; done @@ -293,8 +308,8 @@ build-watch: clean: @echo "Cleaning..." rm -rf ffi/zig/.zig-cache ffi/zig/zig-out - rm -rf cartridges/*/ffi/.zig-cache cartridges/*/ffi/zig-out - rm -rf src/abi/build cartridges/*/abi/build + rm -rf tests/fixtures/cartridges/*/ffi/.zig-cache tests/fixtures/cartridges/*/ffi/zig-out + rm -rf src/abi/build rm -rf target/ _build/ build/ dist/ out/ # Deep clean including caches [reversible: rebuild] @@ -305,36 +320,51 @@ clean-all: clean # TEST & QUALITY # ═══════════════════════════════════════════════════════════════════════════════ -# Run all Zig FFI tests (catalogue + all 111 cartridges with build.zig) +# Run all Zig FFI tests (catalogue + every cartridge FFI in the catalog root) test *args: #!/usr/bin/env bash set -euo pipefail + CARTS="${BOJ_CARTRIDGES_PATH:-tests/fixtures/cartridges}" echo "Running catalogue FFI tests..." (cd ffi/zig && zig build test) - echo "Running cartridge FFI tests..." + echo "Running cartridge FFI tests from $CARTS ..." FAILED=() - for d in cartridges/*/ffi; do + RAN=0 + for d in "$CARTS"/*/ffi; do [ -f "$d/build.zig" ] || continue + RAN=$((RAN + 1)) if ! (cd "$d" && zig build test 2>&1); then FAILED+=("$d") fi done + if [ "$RAN" -eq 0 ]; then + echo "FAILED: no cartridge FFI (no */ffi/build.zig) under $CARTS — nothing was tested." >&2 + echo " Populate a cache with scripts/fetch-cartridges.sh and set BOJ_CARTRIDGES_PATH." >&2 + exit 1 + fi if [ ${#FAILED[@]} -gt 0 ]; then - echo "FAILED: ${#FAILED[@]} cartridge FFI test(s):" + echo "FAILED: ${#FAILED[@]} of $RAN cartridge FFI test(s):" for f in "${FAILED[@]}"; do echo " $f"; done exit 1 fi - echo "All FFI tests passed!" + echo "All FFI tests passed ($RAN cartridge FFI(s))!" # Run tests with verbose output test-verbose *args: #!/usr/bin/env bash set -euo pipefail + CARTS="${BOJ_CARTRIDGES_PATH:-tests/fixtures/cartridges}" (cd ffi/zig && zig build test -- --verbose) - for d in cartridges/*/ffi; do + RAN=0 + for d in "$CARTS"/*/ffi; do [ -f "$d/build.zig" ] || continue + RAN=$((RAN + 1)) (cd "$d" && zig build test -- --verbose) done + if [ "$RAN" -eq 0 ]; then + echo "FAILED: no cartridge FFI (no */ffi/build.zig) under $CARTS — nothing was tested." >&2 + exit 1 + fi # Smoke test — type-check core ABI + run one FFI test test-smoke: @@ -1313,18 +1343,25 @@ heal: fi # --- Clear stale Zig caches --- echo "Clearing stale Zig caches..." - rm -rf ffi/zig/.zig-cache cartridges/*/ffi/.zig-cache 2>/dev/null || true + rm -rf ffi/zig/.zig-cache tests/fixtures/cartridges/*/ffi/.zig-cache 2>/dev/null || true HEALED=$((HEALED + 1)) echo " Cleared." echo "" # --- Rebuild all FFI layers --- if command -v zig >/dev/null 2>&1; then - echo "Rebuilding all FFI layers..." + CARTS="${BOJ_CARTRIDGES_PATH:-tests/fixtures/cartridges}" + echo "Rebuilding all FFI layers (cartridge catalog root: $CARTS)..." (cd ffi/zig && zig build) && echo " Catalogue FFI: OK" || echo " Catalogue FFI: FAILED" - for d in cartridges/*/ffi; do + REBUILT=0 + for d in "$CARTS"/*/ffi; do [ -f "$d/build.zig" ] || continue + REBUILT=$((REBUILT + 1)) (cd "$d" && zig build 2>/dev/null) && echo " $d: OK" || echo " $d: FAILED" done + if [ "$REBUILT" -eq 0 ]; then + echo " No cartridge FFI under $CARTS — catalogue only." + echo " Populate a cache with scripts/fetch-cartridges.sh and set BOJ_CARTRIDGES_PATH." + fi HEALED=$((HEALED + 1)) fi echo "" @@ -1361,8 +1398,14 @@ tour: echo " elixir/ REST server (Plug/Cowboy)" echo " container/ Stapeln container ecosystem" echo "" - CART_COUNT=$(ls -d cartridges/*-mcp 2>/dev/null | wc -l) - echo "Current cartridge count: $CART_COUNT" + CARTS="${BOJ_CARTRIDGES_PATH:-tests/fixtures/cartridges}" + CART_COUNT=$(ls -d "$CARTS"/*-mcp 2>/dev/null | wc -l) + if [ "$CART_COUNT" -eq 0 ]; then + echo "Cartridge catalog root $CARTS is empty or absent." + echo " Populate one: scripts/fetch-cartridges.sh; then export BOJ_CARTRIDGES_PATH." + else + echo "Cartridges visible in $CARTS: $CART_COUNT" + fi echo "" echo "Quick commands:" echo " just run Start server (REST 7700, gRPC 7701, GraphQL 7702)" @@ -1400,7 +1443,7 @@ help-me: echo " just tunnel Cloudflare quick tunnel only" echo "" echo "TEST & VERIFY:" - echo " just test Run all FFI tests (catalogue + 17 cartridges)" + echo " just test Run all FFI tests (catalogue + cartridge catalog root)" echo " just test-verbose Run tests with verbose output" echo " just test-smoke Quick smoke test (ABI check + catalogue test)" echo " just readiness Component Readiness Grade tests" From ef3927e39caeac113b1463dc85ca4e3121ac9280 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Fri, 7 Aug 2026 12:43:30 +0100 Subject: [PATCH 2/6] fix(scripts): retire refresh-bundled-cartridges.sh; repoint SELinux rule at the cache MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit refresh-bundled-cartridges.sh existed solely to overwrite the in-tree cartridges/ bundle from the canonical registry — it bailed with "run from repo root (cartridges/ not found here)" and, had that check passed, would have `rm -rf`'d directories that no longer exist. Its subject was deleted in #300; the script is deleted with it. Nothing in the tree referenced it (grepped: only its own header). boj-selinux-contexts.sh wrote an fcontext rule for /cartridges/.*/ffi/zig-out/lib/.*\.so and ran `restorecon -Rv /cartridges/`, neither of which resolves any more. Cartridge .so files now live in the host-local cache that scripts/fetch-cartridges.sh populates, so the rule is written against that root (BOJ_CARTRIDGES_PATH, default $HOME/.boj/cartridges) and is skipped with an explicit message when no cache is present — rather than labelling a path that cannot exist. The core ffi/zig rule is unchanged. Co-Authored-By: Claude Opus 5 --- scripts/boj-selinux-contexts.sh | 38 +++++++-- scripts/refresh-bundled-cartridges.sh | 112 -------------------------- 2 files changed, 31 insertions(+), 119 deletions(-) delete mode 100755 scripts/refresh-bundled-cartridges.sh diff --git a/scripts/boj-selinux-contexts.sh b/scripts/boj-selinux-contexts.sh index 99733b6f..20b0f322 100755 --- a/scripts/boj-selinux-contexts.sh +++ b/scripts/boj-selinux-contexts.sh @@ -4,26 +4,45 @@ # Must be run with sudo. Persists across restorecon / relabels. # # Usage: sudo ./scripts/boj-selinux-contexts.sh +# +# The bundled cartridges/ tree was retired (canonical source: +# hyperpolymath/boj-server-cartridges). Cartridge .so files now live under a +# host-local cache populated by scripts/fetch-cartridges.sh, so the cartridge +# rule is written against that cache root rather than against the repo. +# +# Environment: +# BOJ_CARTRIDGES_PATH cartridge cache root (default: $HOME/.boj/cartridges). +# Under sudo, $HOME is root's — pass this explicitly +# (sudo BOJ_CARTRIDGES_PATH=... ./scripts/...) to label +# a cache that belongs to the invoking user. set -euo pipefail # Derive repo root from script location SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" readonly BOJ_ROOT="$(dirname "$SCRIPT_DIR")" +readonly CARTRIDGES_ROOT="${BOJ_CARTRIDGES_PATH:-$HOME/.boj/cartridges}" # SELinux has an equivalency rule: /var/mnt -> /mnt # semanage fcontext requires the /mnt/... form for rule paths. # Strip /var prefix if present (semanage needs canonical /mnt paths). readonly BOJ_ROOT_SEMANAGE="${BOJ_ROOT#/var}" +readonly CARTRIDGES_ROOT_SEMANAGE="${CARTRIDGES_ROOT#/var}" echo "=== BoJ Server SELinux Context Setup ===" # 1. Cartridge shared libraries: lib_t -echo "[1/2] Setting fcontext rule: cartridge .so files -> lib_t" -semanage fcontext -a -t lib_t \ - "${BOJ_ROOT_SEMANAGE}/cartridges/.*/ffi/zig-out/lib/.*\\.so" 2>/dev/null \ - || semanage fcontext -m -t lib_t \ - "${BOJ_ROOT_SEMANAGE}/cartridges/.*/ffi/zig-out/lib/.*\\.so" +if [ -d "$CARTRIDGES_ROOT" ]; then + echo "[1/2] Setting fcontext rule: cartridge .so files -> lib_t ($CARTRIDGES_ROOT)" + semanage fcontext -a -t lib_t \ + "${CARTRIDGES_ROOT_SEMANAGE}/.*/ffi/zig-out/lib/.*\\.so" 2>/dev/null \ + || semanage fcontext -m -t lib_t \ + "${CARTRIDGES_ROOT_SEMANAGE}/.*/ffi/zig-out/lib/.*\\.so" +else + echo "[1/2] SKIP: no cartridge cache at $CARTRIDGES_ROOT." + echo " Populate one with scripts/fetch-cartridges.sh, or set" + echo " BOJ_CARTRIDGES_PATH, then re-run to label cartridge .so files." +fi # 2. Core FFI shared libraries: lib_t echo "[2/2] Setting fcontext rule: core FFI .so files -> lib_t" @@ -34,7 +53,12 @@ semanage fcontext -a -t lib_t \ # Apply the contexts echo "Applying contexts with restorecon..." -restorecon -Rv "${BOJ_ROOT}/cartridges/" 2>&1 || true +if [ -d "$CARTRIDGES_ROOT" ]; then + restorecon -Rv "${CARTRIDGES_ROOT}/" 2>&1 || true +fi restorecon -Rv "${BOJ_ROOT}/ffi/" 2>&1 || true -echo "=== Done. Verify with: ls -Z ${BOJ_ROOT}/cartridges/database-mcp/ffi/zig-out/lib/ ===" +echo "=== Done. Verify with: ls -Z ${BOJ_ROOT}/ffi/zig/zig-out/lib/ ===" +if [ -d "$CARTRIDGES_ROOT" ]; then + echo "=== and: ls -Z ${CARTRIDGES_ROOT}/*/ffi/zig-out/lib/ ===" +fi diff --git a/scripts/refresh-bundled-cartridges.sh b/scripts/refresh-bundled-cartridges.sh deleted file mode 100755 index 39a5bf6b..00000000 --- a/scripts/refresh-bundled-cartridges.sh +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env bash -# SPDX-License-Identifier: MPL-2.0 -# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) -# -# refresh-bundled-cartridges.sh — bring boj-server's bundled cartridges/ in sync -# with the canonical hyperpolymath/boj-server-cartridges registry while -# preserving the existing 125-cartridge curation. -# -# Differences from fetch-cartridges.sh: -# * fetch-cartridges.sh populates a host-local cache (default $HOME/.boj) -# from the FULL canonical (currently 139 cartridges). -# * this script overwrites the IN-TREE cartridges/ dir with the canonical -# content for only the names currently present in the bundle. -# -# Run from the repo root: -# scripts/refresh-bundled-cartridges.sh [registry-clone-dir] -# -# If no registry path is given, the script clones the canonical registry into -# a scratch dir and uses that. -# -# Three cartridges were renamed in boj-server-cartridges PR #27 to match the -# canonical schema's name pattern. The legacy bundled name maps to the new -# canonical name as follows: -# -# boj-health → boj-health-mcp -# origenemcp → origene-mcp -# opendatamcp → opendata-mcp - -set -euo pipefail - -REGISTRY="${1:-}" -WORK="" - -cleanup() { - if [ -n "$WORK" ] && [ -d "$WORK" ]; then - rm -rf "$WORK" - fi -} -trap cleanup EXIT - -if [ -z "$REGISTRY" ]; then - WORK="$(mktemp -d)" - REGISTRY="$WORK/registry" - echo "Cloning canonical registry into $REGISTRY" - git clone --quiet --depth 1 https://github.com/hyperpolymath/boj-server-cartridges.git "$REGISTRY" -fi - -if [ ! -d "$REGISTRY/cartridges" ]; then - echo "error: $REGISTRY/cartridges not found" >&2 - exit 1 -fi - -if [ ! -d "cartridges" ]; then - echo "error: run from repo root (cartridges/ not found here)" >&2 - exit 1 -fi - -# Build a name-mapping function. For most cartridges old==new; the rename trio -# from boj-server-cartridges#27 is handled explicitly. -canonical_name_for() { - local old="$1" - case "$old" in - boj-health) echo "boj-health-mcp" ;; - origenemcp) echo "origene-mcp" ;; - opendatamcp) echo "opendata-mcp" ;; - *) echo "$old" ;; - esac -} - -# Find a cartridge in the canonical registry by name. Returns the directory -# under $REGISTRY/cartridges/{domains|cross-cutting|templates}/.../$name/. -find_canonical_dir() { - local name="$1" - # mindepth 2 admits templates (cartridges/templates/) alongside - # domains// and cross-cutting//. - find "$REGISTRY/cartridges" -mindepth 2 -maxdepth 4 -type d -name "$name" | head -n 1 -} - -# Walk the existing bundle. README.md is not a cartridge. -refreshed=0 -renamed=0 -missing=0 -for bundled_dir in cartridges/*/; do - bundled="${bundled_dir%/}" - bundled_name="$(basename "$bundled")" - canonical_name="$(canonical_name_for "$bundled_name")" - canonical_dir="$(find_canonical_dir "$canonical_name")" - - if [ -z "$canonical_dir" ]; then - echo "MISSING in canonical: $bundled_name (no $canonical_name in registry)" - missing=$((missing + 1)) - continue - fi - - # If the name changed, drop the legacy directory. - if [ "$bundled_name" != "$canonical_name" ]; then - echo "RENAME: $bundled_name -> $canonical_name" - rm -rf "cartridges/$bundled_name" - renamed=$((renamed + 1)) - else - rm -rf "cartridges/$bundled_name" - fi - - cp -r "$canonical_dir" "cartridges/$canonical_name" - refreshed=$((refreshed + 1)) -done - -echo -echo "Summary" -echo " refreshed: $refreshed" -echo " renamed: $renamed" -echo " missing: $missing" From c7080242788f4f25a7f880847e438999373fe43d Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Fri, 7 Aug 2026 12:45:36 +0100 Subject: [PATCH 3/6] fix(mcp-bridge): stop the offline-menu generator defaulting to the deleted tree MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit generate-offline-menu.js fell back to join(__dirname, "../../cartridges") when BOJ_CARTRIDGES_PATH was unset. That path was removed in #300, so on a fresh clone the generator scanned nothing; where stale build residue still sits under cartridges/ on a developer's disk it scanned *that* instead, and either way the operator would be told to hand-edit offline-menu.js from a result that describes no real catalogue. The fallback is now the tracked fixture catalogue, matching tests/e2e_full.sh, and an empty or unreadable catalog root is a hard error with a pointer to scripts/fetch-cartridges.sh — regenerating a menu from zero cartridges must not look like success. offline-menu.js: corrected the header (it is generated from a catalog root, not from cartridges/; the regenerate command is a Deno invocation) and the `summary.total` note, which claimed 127 cartridges were "on disk" under a directory that no longer exists. The number is left as the snapshot it is, now labelled as such. Co-Authored-By: Claude Opus 5 --- mcp-bridge/lib/generate-offline-menu.js | 34 ++++++++++++++++++++----- mcp-bridge/lib/offline-menu.js | 17 ++++++++----- 2 files changed, 38 insertions(+), 13 deletions(-) diff --git a/mcp-bridge/lib/generate-offline-menu.js b/mcp-bridge/lib/generate-offline-menu.js index f810370c..d1b749c3 100644 --- a/mcp-bridge/lib/generate-offline-menu.js +++ b/mcp-bridge/lib/generate-offline-menu.js @@ -11,7 +11,11 @@ // scripts/fetch-cartridges.sh) for subdirectories matching the *-mcp // pattern and produces a static OFFLINE_MENU object. This prevents the // hardcoded menu from going stale as cartridges are added or removed. -// The bundled ../../cartridges tree this used to scan was retired. +// +// The bundled ../../cartridges tree this used to scan was retired, so the +// fallback is the tracked fixture catalogue (as in tests/e2e_full.sh) — +// never a path that cannot exist. Scanning an empty or absent root is a +// hard error: regenerating the menu from nothing would silently blank it. import { readdirSync, statSync } from "node:fs"; import { join, dirname } from "node:path"; @@ -19,20 +23,36 @@ import { fileURLToPath } from "node:url"; const __dirname = dirname(fileURLToPath(import.meta.url)); const cartridgesDir = - Deno.env.get("BOJ_CARTRIDGES_PATH") ?? join(__dirname, "../../cartridges"); + Deno.env.get("BOJ_CARTRIDGES_PATH") ?? + join(__dirname, "../../tests/fixtures/cartridges"); +let entries; try { - const entries = readdirSync(cartridgesDir) + entries = readdirSync(cartridgesDir) .filter(name => { const full = join(cartridgesDir, name); return statSync(full).isDirectory() && name.endsWith("-mcp"); }) .sort(); - - console.log(`Found ${entries.length} cartridges in ${cartridgesDir}`); - console.log("Cartridges:", entries.join(", ")); - console.log("\nUpdate mcp-bridge/lib/offline-menu.js with any new cartridges."); } catch (err) { console.error(`Error scanning cartridges directory: ${err.message}`); + console.error( + "Set BOJ_CARTRIDGES_PATH to a catalog root populated by " + + "scripts/fetch-cartridges.sh.", + ); + Deno.exit(1); +} + +if (entries.length === 0) { + console.error(`No *-mcp cartridges found in ${cartridgesDir}.`); + console.error( + "Refusing to regenerate the offline menu from an empty catalog — " + + "set BOJ_CARTRIDGES_PATH to a populated catalog root " + + "(scripts/fetch-cartridges.sh).", + ); Deno.exit(1); } + +console.log(`Found ${entries.length} cartridges in ${cartridgesDir}`); +console.log("Cartridges:", entries.join(", ")); +console.log("\nUpdate mcp-bridge/lib/offline-menu.js with any new cartridges."); diff --git a/mcp-bridge/lib/offline-menu.js b/mcp-bridge/lib/offline-menu.js index 28bd0aca..e68dbe5a 100644 --- a/mcp-bridge/lib/offline-menu.js +++ b/mcp-bridge/lib/offline-menu.js @@ -4,8 +4,11 @@ // BoJ Server — Offline menu // // Static cartridge manifest for offline/inspection mode. -// Generated from cartridges/ directory structure. -// Run `node mcp-bridge/lib/generate-offline-menu.js` to regenerate. +// Generated from a cartridge catalog root — the bundled cartridges/ tree +// was retired; the canonical source is hyperpolymath/boj-server-cartridges. +// Run `deno run --allow-read --allow-env mcp-bridge/lib/generate-offline-menu.js` +// to regenerate (BOJ_CARTRIDGES_PATH selects the catalog root; it defaults to +// the tracked tests/fixtures/cartridges catalogue). export const OFFLINE_MENU = { tier_teranga: [ @@ -48,9 +51,11 @@ export const OFFLINE_MENU = { tier_ayo: [ { name: "local-coord-mcp", version: "0.9.0", domain: "Agent", protocols: ["MCP","Agentic"], status: "Available", available: true, notes: "Localhost-only (127.0.0.1:7745) multi-instance AI coordination — peer discovery, typed envelopes, task claiming, master/journeyman/apprentice supervision with quarantine + watchdog TTL + track-record affinity + capability advertisement" }, ], - // `total` reflects the full cartridges/ directory (127 cartridges on disk); - // the tier_* arrays above enumerate the named ones exposed through the - // offline menu. Regenerate counts with - // `node mcp-bridge/lib/generate-offline-menu.js`. + // `total` is a hand-maintained count of the full canonical catalogue; the + // tier_* arrays above enumerate only the named ones exposed through the + // offline menu. It was last taken from the bundled cartridges/ tree before + // that tree was retired, so it is a snapshot, not a live figure — re-derive + // it against the catalog root with + // `deno run --allow-read --allow-env mcp-bridge/lib/generate-offline-menu.js`. summary: { total: 127, ready: 24, mounted: 0 }, }; From 291a4a44b8a3679b5ca97118496f7c557d2592f7 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Fri, 7 Aug 2026 12:52:15 +0100 Subject: [PATCH 4/6] fix(tests): repoint cartridge traversals at the catalog root and stop passing over empty sets MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit All three scripts still walked the cartridges/ tree deleted in #300. tests/aspect_tests.sh Aspect 1's cartridge-FFI loop globbed $PROJECT_DIR/cartridges/*/ffi, matched nothing, and said nothing. Aspect 4 was worse: `shopt -s nullglob` plus an empty glob gave zero iterations, so incomplete stayed 0 and it printed "PASS: All cartridges accounted for (0 complete, 0 stub, 0 ffi_only)" — a green check over an audit of nothing. Both loops now read a catalog root (BOJ_CARTRIDGES_PATH, default the tracked fixture catalogue). Aspect 1 reports how many FFI dirs it audited and WARNs when there are none; Aspect 4 FAILs outright on an empty root — "completeness verified nothing" — and otherwise names the root and the count in its pass line. Aspect 4 also learns the catalogue's status vocabulary: `catalogued` is the fixture/registry spelling of `stub` (manifest-only) and `ready` of `ffi_only`. Verified against all 23 fixture manifests: the 22 `catalogued` entries carry neither abi/ nor ffi/, and `ready` feedback-mcp carries ffi/ without abi/ — exactly the two existing rules. Without this they would all fall through to the strict `complete` branch and fail for having a shape they are supposed to have. tests/integration.sh Steps 4, 5 and 7 hard-coded cartridges//{adapter,ffi,abi}. Step 5's bare `cd "cartridges/$cart/ffi"` under `set -e` killed the run outright. All three now read the catalog root and distinguish three cases per cartridge: absent (SKIP, named), manifest-only catalogue entry (SKIP, named — nothing to audit, and reporting a missing adapter there would be a phantom defect), or implemented-but-missing-a-layer (FAIL, as before). Each step also says when it checked nothing at all. Fixes SC2144 while there: `[ -f dir/*_ffi.zig ]` is not a glob test — it errors on two matches and tests a literal pattern on none. tests/federation_multinode.sh LD_LIBRARY_PATH named cartridges/container-mcp/ffi/zig-out/lib, a path that cannot exist, so the entry was inert. Replaced with the catalog-root sweep tests/e2e_full.sh already uses, which adds only lib dirs that are really there. Co-Authored-By: Claude Opus 5 --- tests/aspect_tests.sh | 65 ++++++++++++++++------ tests/federation_multinode.sh | 18 +++++- tests/integration.sh | 102 ++++++++++++++++++++++++++++------ 3 files changed, 149 insertions(+), 36 deletions(-) diff --git a/tests/aspect_tests.sh b/tests/aspect_tests.sh index 370991a8..77fab197 100755 --- a/tests/aspect_tests.sh +++ b/tests/aspect_tests.sh @@ -22,6 +22,12 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +# Cartridge catalog root: the bundled cartridges/ tree was retired in favour +# of hyperpolymath/boj-server-cartridges. Default to the tracked fixture +# catalogue (as tests/e2e_full.sh does); point BOJ_CARTRIDGES_PATH at a cache +# populated by scripts/fetch-cartridges.sh to audit the full registry. +CARTRIDGES_ROOT="${BOJ_CARTRIDGES_PATH:-$PROJECT_DIR/tests/fixtures/cartridges}" + PASS=0 FAIL=0 WARN=0 @@ -95,9 +101,11 @@ for zigfile in "$zig_ffi_dir"/*.zig; do fi done -# Also check cartridge FFI modules -for cart_dir in "$PROJECT_DIR"/cartridges/*/ffi; do +# Also check cartridge FFI modules in the catalog root +cart_ffi_seen=0 +for cart_dir in "$CARTRIDGES_ROOT"/*/ffi; do [ -d "$cart_dir" ] || continue + cart_ffi_seen=$((cart_ffi_seen + 1)) cart_name=$(basename "$(dirname "$cart_dir")") # Find the main FFI .zig file @@ -122,6 +130,12 @@ for cart_dir in "$PROJECT_DIR"/cartridges/*/ffi; do fi fi done +if [[ "$cart_ffi_seen" -eq 0 ]]; then + warn "no cartridge FFI under $CARTRIDGES_ROOT — checked the core FFI only" + warn " populate a cache with scripts/fetch-cartridges.sh, then set BOJ_CARTRIDGES_PATH" +else + echo " (audited $cart_ffi_seen cartridge FFI dir(s) under $CARTRIDGES_ROOT)" +fi echo "" # ═══════════════════════════════════════════════════════════════════════ @@ -287,10 +301,18 @@ bold "Aspect 4: Cartridge layer completeness (ABI + FFI)" # (e.g., boj-health monitoring code, MCP # adapters bridging external APIs) # -# `stub` and `ffi_only` are passed but counted in a separate informational -# tally so they remain visible. The default-when-absent is `complete` -# (strict). Adding a new exemption requires editing cartridge.json with -# a stated rationale and is reviewable in PR diff. +# The catalogue that replaced the bundled tree spells the same two +# exemptions differently, so both vocabularies are accepted: +# +# "status": "catalogued" — manifest-only; identical rule to `stub` +# "status": "ready" — built and advertised; identical rule to +# `ffi_only` (FFI present, ABI optional) +# +# `stub`/`catalogued` and `ffi_only`/`ready` are passed but counted in a +# separate informational tally so they remain visible. The +# default-when-absent is `complete` (strict). Adding a new exemption +# requires editing cartridge.json with a stated rationale and is +# reviewable in PR diff. read_cartridge_status() { local manifest="$1" [ -f "$manifest" ] || { echo "complete"; return; } @@ -310,11 +332,14 @@ ffi_only=0 # The bundled cartridges/ tree was retired (canonical source: # hyperpolymath/boj-server-cartridges, which carries its own completeness -# gates). Nothing to audit here unless a checkout-local tree exists. +# gates). Audit whatever catalog root is configured; an empty root means +# this aspect verified nothing and must say so, never report success. +audited=0 shopt -s nullglob -for cart_dir in "$PROJECT_DIR"/cartridges/*/; do +for cart_dir in "$CARTRIDGES_ROOT"/*/; do cart_name=$(basename "$cart_dir") + audited=$((audited + 1)) has_abi=false; has_ffi=false [ -d "$cart_dir/abi" ] && has_abi=true @@ -323,26 +348,26 @@ for cart_dir in "$PROJECT_DIR"/cartridges/*/; do status=$(read_cartridge_status "$cart_dir/cartridge.json") case "$status" in - stub) + stub|catalogued) # Manifest-only design — both layers absent is the expected shape. if ! $has_abi && ! $has_ffi; then - pass "$cart_name: stub (manifest-only, by design)" + pass "$cart_name: $status (manifest-only, by design)" stubs=$((stubs + 1)) else - fail "$cart_name: marked stub but has partial implementation (ABI=$has_abi FFI=$has_ffi) — promote to ffi_only or complete" + fail "$cart_name: marked $status but has partial implementation (ABI=$has_abi FFI=$has_ffi) — promote to ffi_only/ready or complete" incomplete=$((incomplete + 1)) fi ;; - ffi_only) + ffi_only|ready) if $has_ffi && ! $has_abi; then - pass "$cart_name: ffi_only (FFI present, no formal ABI by design)" + pass "$cart_name: $status (FFI present, no formal ABI by design)" ffi_only=$((ffi_only + 1)) elif $has_ffi && $has_abi; then # If ABI got added later, the manifest is stale. Pass but warn. - pass "$cart_name: ffi_only (manifest stale — both layers present, complete)" + pass "$cart_name: $status (manifest stale — both layers present, complete)" complete=$((complete + 1)) else - fail "$cart_name: marked ffi_only but FFI missing" + fail "$cart_name: marked $status but FFI missing" incomplete=$((incomplete + 1)) fi ;; @@ -358,10 +383,14 @@ for cart_dir in "$PROJECT_DIR"/cartridges/*/; do done shopt -u nullglob -if [[ $incomplete -eq 0 ]]; then - pass "All cartridges accounted for ($complete complete, $stubs stub, $ffi_only ffi_only)" +if [[ $audited -eq 0 ]]; then + # An empty catalog root proves nothing. Say so; do not count a PASS. + fail "no cartridges found under $CARTRIDGES_ROOT — completeness verified nothing" + yellow " populate a cache with scripts/fetch-cartridges.sh, then set BOJ_CARTRIDGES_PATH" +elif [[ $incomplete -eq 0 ]]; then + pass "All $audited cartridges under $CARTRIDGES_ROOT accounted for ($complete complete, $stubs manifest-only, $ffi_only ffi-only)" else - red " $incomplete cartridges are incomplete" + red " $incomplete of $audited cartridges under $CARTRIDGES_ROOT are incomplete" fi echo "" diff --git a/tests/federation_multinode.sh b/tests/federation_multinode.sh index a7689089..a6954755 100755 --- a/tests/federation_multinode.sh +++ b/tests/federation_multinode.sh @@ -19,7 +19,23 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" ELIXIR_DIR="$PROJECT_DIR/elixir" -export LD_LIBRARY_PATH="$PROJECT_DIR/ffi/zig/zig-out/lib:$PROJECT_DIR/cartridges/container-mcp/ffi/zig-out/lib" + +# Cartridge catalog root: the bundled cartridges/ tree was retired in favour +# of hyperpolymath/boj-server-cartridges. Default to the tracked fixture +# catalogue; point BOJ_CARTRIDGES_PATH at a cache populated by +# scripts/fetch-cartridges.sh to load real cartridge .so files. +export BOJ_CARTRIDGES_PATH="${BOJ_CARTRIDGES_PATH:-$PROJECT_DIR/tests/fixtures/cartridges}" + +# Library path for Zig FFI shared objects. The old hard-coded +# cartridges/container-mcp/ffi/zig-out/lib entry pointed into the deleted +# tree, so it silently contributed nothing; take every cartridge lib dir +# that actually exists under the catalog root instead (as tests/e2e_full.sh +# does). +export LD_LIBRARY_PATH="$PROJECT_DIR/ffi/zig/zig-out/lib:${LD_LIBRARY_PATH:-}" +for cart_lib in "$BOJ_CARTRIDGES_PATH"/*/ffi/zig-out/lib; do + [ -d "$cart_lib" ] && LD_LIBRARY_PATH="$cart_lib:$LD_LIBRARY_PATH" +done +export LD_LIBRARY_PATH green() { printf '\033[32m%s\033[0m\n' "$*"; } red() { printf '\033[31m%s\033[0m\n' "$*"; } diff --git a/tests/integration.sh b/tests/integration.sh index 2ab68a58..1430d081 100755 --- a/tests/integration.sh +++ b/tests/integration.sh @@ -18,6 +18,29 @@ set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" + +# Cartridge catalog root: the bundled cartridges/ tree was retired in favour +# of hyperpolymath/boj-server-cartridges. Default to the tracked fixture +# catalogue (as tests/e2e_full.sh does); point BOJ_CARTRIDGES_PATH at a cache +# populated by scripts/fetch-cartridges.sh to exercise the full registry. +CARTRIDGES_ROOT="${BOJ_CARTRIDGES_PATH:-$PROJECT_DIR/tests/fixtures/cartridges}" + +# The four cartridges these steps were written against. They are no longer +# bundled, so each step reports per-cartridge whether it found a subject: +# absent or manifest-only means SKIP with a message, never a silent pass. +SUBJECT_CARTS=(database-mcp fleet-mcp nesy-mcp agent-mcp) + +# A cartridge carrying none of abi/, ffi/, adapter/ is a manifest-only +# catalogue entry — the shape of the tracked fixture catalogue, and of any +# registry entry not yet implemented. There is no implementation to audit, +# so the layer checks skip it rather than reporting a phantom defect. +# A cartridge with SOME layers but not the one under test is a real defect +# and still fails. +is_manifest_only() { + local d="$1" + [ ! -d "$d/abi" ] && [ ! -d "$d/ffi" ] && [ ! -d "$d/adapter" ] +} + PASS=0 FAIL=0 SKIP=0 @@ -76,37 +99,61 @@ fi # --- Step 4: Verify Zig adapter completeness --- echo "" -echo "Step 4: Verifying Zig adapter completeness..." +echo "Step 4: Verifying Zig adapter completeness (catalog root: $CARTRIDGES_ROOT)..." cd "$PROJECT_DIR" adapter_count=0 -for cart in database-mcp fleet-mcp nesy-mcp agent-mcp; do - if [ -f "cartridges/$cart/adapter/${cart%%-mcp}_adapter.zig" ]; then +adapter_skipped=0 +for cart in "${SUBJECT_CARTS[@]}"; do + cart_dir="$CARTRIDGES_ROOT/$cart" + if [ ! -d "$cart_dir" ]; then + yellow " SKIP: $cart is not in $CARTRIDGES_ROOT" + adapter_skipped=$((adapter_skipped + 1)) + elif is_manifest_only "$cart_dir"; then + yellow " SKIP: $cart is a manifest-only catalogue entry — no adapter to check" + adapter_skipped=$((adapter_skipped + 1)) + elif [ -f "$cart_dir/adapter/${cart%%-mcp}_adapter.zig" ]; then adapter_count=$((adapter_count + 1)) + else + red " $cart: has implementation layers but no adapter/${cart%%-mcp}_adapter.zig" + FAIL=$((FAIL + 1)) fi done -if [ $adapter_count -eq 4 ]; then - green " All Zig adapters present ($adapter_count/4)" +if [ $adapter_count -eq ${#SUBJECT_CARTS[@]} ]; then + green " All Zig adapters present ($adapter_count/${#SUBJECT_CARTS[@]})" PASS=$((PASS + 1)) -else - red " Missing Zig adapters ($adapter_count/4)" - FAIL=$((FAIL + 1)) +elif [ $adapter_skipped -gt 0 ]; then + yellow " $adapter_skipped/${#SUBJECT_CARTS[@]} subject cartridges carry no implementation here." + yellow " Adapters live in hyperpolymath/boj-server-cartridges — populate a cache" + yellow " with scripts/fetch-cartridges.sh and set BOJ_CARTRIDGES_PATH to check them." + SKIP=$((SKIP + 1)) fi # --- Step 5: Run cartridge FFI tests --- echo "" -echo "Step 5: Running cartridge FFI tests..." +echo "Step 5: Running cartridge FFI tests (catalog root: $CARTRIDGES_ROOT)..." cd "$PROJECT_DIR" -for cart in database-mcp fleet-mcp nesy-mcp agent-mcp; do - cd "cartridges/$cart/ffi" - if zig build test 2>/dev/null; then +ffi_ran=0 +for cart in "${SUBJECT_CARTS[@]}"; do + ffi_dir="$CARTRIDGES_ROOT/$cart/ffi" + if [ ! -f "$ffi_dir/build.zig" ]; then + yellow " SKIP: $cart has no $ffi_dir/build.zig — nothing to test" + SKIP=$((SKIP + 1)) + continue + fi + ffi_ran=$((ffi_ran + 1)) + if (cd "$ffi_dir" && zig build test 2>/dev/null); then green " $cart: tests passed" PASS=$((PASS + 1)) else red " $cart: tests failed" FAIL=$((FAIL + 1)) fi - cd "$PROJECT_DIR" done +if [ $ffi_ran -eq 0 ]; then + yellow " No cartridge FFI test ran — the subjects are in" + yellow " hyperpolymath/boj-server-cartridges. Populate a cache with" + yellow " scripts/fetch-cartridges.sh and set BOJ_CARTRIDGES_PATH to run them." +fi # --- Step 6: Run benchmarks --- echo "" @@ -123,11 +170,27 @@ fi echo "" echo "Step 7: Matrix verification..." cd "$PROJECT_DIR" -for cart in database-mcp fleet-mcp nesy-mcp agent-mcp; do +matrix_skipped=0 +for cart in "${SUBJECT_CARTS[@]}"; do + cart_dir="$CARTRIDGES_ROOT/$cart" + if [ ! -d "$cart_dir" ]; then + yellow " SKIP: $cart is not in $CARTRIDGES_ROOT" + matrix_skipped=$((matrix_skipped + 1)) + SKIP=$((SKIP + 1)) + continue + fi + if is_manifest_only "$cart_dir"; then + yellow " SKIP: $cart is a manifest-only catalogue entry — no layers to verify" + matrix_skipped=$((matrix_skipped + 1)) + SKIP=$((SKIP + 1)) + continue + fi abi_ok=false; ffi_ok=false; adapter_ok=false - find "cartridges/$cart/abi" -name '*.idr' 2>/dev/null | grep -q . && abi_ok=true - [ -f "cartridges/$cart/ffi"/*_ffi.zig ] 2>/dev/null && ffi_ok=true - [ -f "cartridges/$cart/adapter"/*_adapter.v ] 2>/dev/null && adapter_ok=true + # `[ -f dir/*_ffi.zig ]` is not a glob test (SC2144): with two matches it + # errors out, with none it tests the literal pattern. Use find. + find "$CARTRIDGES_ROOT/$cart/abi" -name '*.idr' 2>/dev/null | grep -q . && abi_ok=true + find "$CARTRIDGES_ROOT/$cart/ffi" -maxdepth 1 -name '*_ffi.zig' 2>/dev/null | grep -q . && ffi_ok=true + find "$CARTRIDGES_ROOT/$cart/adapter" -maxdepth 1 -name '*_adapter.v' 2>/dev/null | grep -q . && adapter_ok=true if $abi_ok && $ffi_ok && $adapter_ok; then green " $cart: ABI+FFI+Adapter complete" @@ -137,6 +200,11 @@ for cart in database-mcp fleet-mcp nesy-mcp agent-mcp; do FAIL=$((FAIL + 1)) fi done +if [ $matrix_skipped -eq ${#SUBJECT_CARTS[@]} ]; then + yellow " Matrix verification checked nothing — every subject cartridge is" + yellow " absent or manifest-only under $CARTRIDGES_ROOT. The implementations" + yellow " live in hyperpolymath/boj-server-cartridges (scripts/fetch-cartridges.sh)." +fi # --- Summary --- echo "" From 86cfe7bfe5755353ec46bec43cf319cd58254608 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Fri, 7 Aug 2026 12:52:34 +0100 Subject: [PATCH 5/6] docs(security): point vulnerability reporters at paths that exist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Affected Component" example in the report template named cartridges/browser-mcp/ and cartridges/cloudflare/ — neither of which is in this repo since #300, and cartridges/cloudflare/ was never even a cartridge directory name. A reporter following the template would file against a path the maintainer cannot open. Replaced with real in-tree components, plus an explicit pointer to hyperpolymath/boj-server-cartridges for cartridge-side findings. The rest of SECURITY.md's cartridge references are about the cartridge system as a concept (isolation, loading, sandbox escape) and remain accurate; left alone. Co-Authored-By: Claude Opus 5 --- SECURITY.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SECURITY.md b/SECURITY.md index a69a4331..bf14d7a7 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -133,7 +133,8 @@ A good vulnerability report helps us understand and reproduce the issue quickly. [e.g., Command Injection, SSRF, Path Traversal, Privilege Escalation, etc.] ## Affected Component -[e.g., cartridges/browser-mcp/, cartridges/cloudflare/, ffi/zig/src/, mcp-bridge/] +[e.g., ffi/zig/src/, elixir/lib/boj_rest/, mcp-bridge/, or a cartridge in +hyperpolymath/boj-server-cartridges — cartridges are no longer bundled here] ## Affected Versions [Version range or specific commits] From 5058fabb01661a515bc0040c14f63f92093804e2 Mon Sep 17 00:00:00 2001 From: "Jonathan D.A. Jewell" <6759885+hyperpolymath@users.noreply.github.com> Date: Fri, 7 Aug 2026 12:53:10 +0100 Subject: [PATCH 6/6] fix(git): drop the two orphaned .claude/worktrees gitlinks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit .claude/worktrees/gov-red and .claude/worktrees/zig-mutex were tracked with mode 160000 (gitlink) while the repo has no .gitmodules. Git had no URL to clone them from, so every fresh clone produced two empty directories that `git status` reported as permanently modified, and no tooling could ever resolve them. They are per-developer Claude Code scratch checkouts and were never meant to be shared. `git rm --cached` removes them from the index only — the directories on disk are untouched — and .gitignore now covers .claude/worktrees/ so the next scratch worktree cannot be committed by accident. Verified: `git ls-files -s` now reports no mode-160000 entries anywhere, and `git check-ignore -v` confirms both paths are covered. Co-Authored-By: Claude Opus 5 --- .claude/worktrees/gov-red | 1 - .claude/worktrees/zig-mutex | 1 - .gitignore | 5 +++++ 3 files changed, 5 insertions(+), 2 deletions(-) delete mode 160000 .claude/worktrees/gov-red delete mode 160000 .claude/worktrees/zig-mutex diff --git a/.claude/worktrees/gov-red b/.claude/worktrees/gov-red deleted file mode 160000 index 7add7bca..00000000 --- a/.claude/worktrees/gov-red +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 7add7bcac05ea4cb2fc0af62ec106dcd4779d73e diff --git a/.claude/worktrees/zig-mutex b/.claude/worktrees/zig-mutex deleted file mode 160000 index a1517a36..00000000 --- a/.claude/worktrees/zig-mutex +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a1517a3677eed03e96e859d5aad0e957d5f156ac diff --git a/.gitignore b/.gitignore index 40945e01..17f33a54 100644 --- a/.gitignore +++ b/.gitignore @@ -164,3 +164,8 @@ generated/abi/ # Superpowers docs and artefacts (local only) docs/superpowers/ .superpowers/ + +# Claude Code scratch worktrees — per-developer checkouts, never shared. +# Two of these were once committed as gitlinks (mode 160000) with no +# .gitmodules, so a fresh clone got two permanently-"modified" empty dirs. +.claude/worktrees/