Five field reports in the 24 hours after v0.10.0 all pointed at the same thing: gates that were right in principle refused real, ordinary setups, and then failed to say why. Per the consolidated strictness decision, each gate keeps the protection that matters and drops the part that was refusing legitimate users — and every refusal now names what it refused and how to proceed. **Daemon image gate: npx and every ephemeral install path (#1539, #1383).** The admission check treated "the peer's image hashes differently" and "the peer's image cannot be examined at all" as one failure. The second is what `npx codebase-memory-mcp` always produces (ephemeral cache path, unfingerprintable), so every npx-invoked client was rejected — and, because the client never reported it, agents saw a transport that closed mid-handshake with zero bytes on stdout. Reported by @wassolles with the admission path already read and the fix space mapped. An unverifiable image is now admitted: the rendezvous HELLO immediately above it has already proven semantic version, build fingerprint, and protocol/store/ feature ABI, and the image check was trading that real proof for an unavailable one. It logs daemon.client_image_unverifiable_admitted so the weaker check is never invisible. A fingerprint MISMATCH — the tamper case the gate exists for — still rejects hard. Separate test seams keep the two modes testable apart. **Client bootstrap failures are no longer silent (#1539).** An MCP client that cannot reach the daemon now emits a JSON-RPC error on stdout naming the reason, plus the same text on stderr. Previously the reason sat in bootstrap_result.message and the process exited having written nothing at all. **POSIX activation: group-writable ancestors (#1535, discussion #1526).** activation_directory_secure required no group or other write bit on the install directory AND every ancestor. WSL2 ships ~ and ~/.local at 0775, as do several distro skeletons and any site using a shared primary group, so install.sh failed for a large fraction of Linux users — reporting a policy refusal as "activation transaction I/O failed", which sent reporters after disk errors and filesystem types. Root-caused by @AmirF194 in a clean ubuntu container; @shochdoerfer and @iandol confirmed independently. World-writable ancestors are still refused (any local user could swap a path component mid-transaction). Group-writable ancestors are now warned about and admitted. The LEAF directory stays strictly owner-private — that is where the binary is published, and group write there would let another account replace the executable between validation and exec. Refusals now name the directory, its mode, and which rule refused. **The obsolete ui/standard chooser (#1538, from discussion #1526).** v0.10.0 consolidated to one archive per platform with the UI always embedded, but `update` still offered a variant choice: "ui" could only 404, and "standard" quietly WAS the UI build. Reported by @iandol upgrading 0.9.0 -> 0.10.0. The chooser, its --standard/--ui flags, and the ui- URL plumbing are removed, along with the CBM_VARIANT=ui remnant in the npm installer. Already-released 0.9.x binaries cannot be fixed retroactively, so the release workflow now publishes byte-identical ui-*-named alias assets — their updaters work again with no user action. The aliases are uploaded AFTER the VirusTotal gate: they are the same bytes as archives it already cleared, and uploading them earlier would duplicate every object in the scan set and the provenance manifest. **macOS install noise and attribution (#1537).** install.sh silenced the "No such xattr: com.apple.quarantine" line, which is what happens when a curl-downloaded archive carries no quarantine attribute — harmless, and it became the title of a bug report about an unrelated failure. The session-stop refusal now points at `daemon status` to list the client processes actually holding the daemon, instead of asserting sessions exist and leaving the reader to guess. Reported by @listepo. **Riders.** hatchling is pinned in pkg/pypi (an unpinned backend resolved fresh inside `python -m build` is what emitted Metadata-Version 2.5 and broke the v0.10.1 publish); SECURITY.md's supported-versions table moves to 0.10.x. Tests: separate seams for unverifiable vs mismatched peer images with a test per outcome; activation refusal must name directory + mode + rule; a group-writable ancestor must stage successfully. The update tests drop the flag that no longer exists. Verified against each reporter's environment shape. **Open security alerts (all three, OSSF Scorecard).** - HIGH, binary artifact: an 8.8 MB compiled Go ELF wrapper had been committed at pkg/go/codebase-memory-mcp by accident. Removed, and both it and its .exe sibling are gitignored so `go build` in that directory cannot repeat it. - HIGH, GHSA-2v37-7h3g-55p8: nanoid < 3.3.17 loops forever when a custom generator is called with size 0. It reaches us transitively (postcss -> vite), so it is pinned through the existing graph-ui overrides block rather than promoted to a direct dependency; the lockfile resolves 3.3.18. - MEDIUM, unpinned pip command: the publish step installed build/twine by version only, leaving the whole transitive graph resolved at run time. pkg/pypi/requirements-publish.txt now hash-pins the complete toolchain (316 hashes), generated on a linux/amd64 python:3.12 image so the wheels match what ubuntu-latest resolves, and the step runs pip with --require-hashes. Verified by installing from it in that same image. Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
scripts/ — the canonical leg entries
The doctrine (enforced, not advisory): a venue may provision a machine;
only a canonical leg script may exercise the product. Local CI, PR CI, the
dry run and the release all call the SAME files below — venues differ only in
host specs, architecture and inputs, never in what a leg does. Platform
differences (arm64 sanitizer flags, native Windows behavior, the Linux portable
binary) live INSIDE these scripts, once, never per-venue.
tests/test_venue_parity_contract.sh (run as Step 0j of every test leg) turns
any violation — inline harness logic in a workflow, a bypassed wrapper, a
missing --help — into a red build.
Every entry answers --help (authoritative, agent-oriented: modes, env, exit
codes) and rejects unknown flags with exit 2 + Please consult --help.
The legs
| leg | entry | what a run gives you |
|---|---|---|
| test | test.sh |
DEFAULT = the venue leg: static contracts (Step 0a–0r) + CLEAN ASan+UBSan build + all suites via the parallel harness + prod-binary guards. --suites a,b = iteration mode (incremental rebuild, subset, seconds). --tsan = the ThreadSanitizer leg. CLANGARM64 gets CI's trap-UBSan flags automatically. |
| package wrappers | ci/test-package-wrappers.sh |
Go, npm, and PyPI runtime-set publication/lock suites on the current host. CI runs the same entry on Linux and Windows so both platform lock implementations gate. |
| build | build.sh |
CLEAN production runtime set (native executable + authenticated integration asset; --with-ui adds one content-addressed UI pack). ccache via env.sh makes repeats fast; CCACHE_COMPILERCHECK=content guarantees a hit is byte-identical to a cold compile — never stale. --version, STATIC=1, BUILD_DIR=. |
| lint | lint.sh |
clang-tidy + cppcheck + clang-format (+ no-skips policy). --ci = the CI gate set (no clang-tidy). Drives the same make targets as make lint/lint-ci. |
| smoke (unix) | smoke-local.sh |
Stages a full release fixture, serves it on a kernel-assigned port, runs smoke-test.sh (ALL phases incl. download/install/update E2E) inside a disposable HOME/XDG/TMP sandbox. ui variant makes a missing verified UI pack a FAILURE. CBM_SMOKE_ARTIFACT_DIR = smoke an extracted release artifact verbatim (release mode). |
| smoke (windows) | ../test-infrastructure/vm/vm-smoke.sh |
Same verified runtime-set contract on the real Windows VM, plus the user-PATH registry guard (prepare/verify/cleanup). |
| smoke-invariants | smoke-invariants.sh |
Production-path resilience battery (MCP handshake, all tools invocable, malformed-input handling, supervised crash/hang recovery) — no fixture server or install E2E. smoke.yml runs an explicitly seam-enabled build on the WIDEST source matrix; release artifacts remain seam-free and use the release-shaped smoke legs above. |
| soak | soak-legs.sh |
The release-gating soak SEQUENCE: quick then query-leak (the #581 detector — never reindexes, so RSS growth = query-path leak), each guarded by a completion-summary check. --legs quick for the ASan single-leg variant. Duration is per leg. |
Internal harnesses — never called directly by a venue (the contract forbids
it): smoke-test.sh (phases; wrappers provide fixture server + sandbox),
soak-test.sh (one soak run; soak-legs.sh provides the sequence + guards),
run-tests-parallel.sh (reached through test.sh).
Conventions
- Exit codes: 0 = pass · 2 = usage error · 90 = guard (a run died without its completion summary — never counts as green) · anything else = the leg's real failure.
- Iteration is a flag, not a side-tool: the fast paths (
--suites,--legs) are modes of the SAME entry the gates run, so a dev loop can never drift from the venue behaviour. - Env sandboxing: the smoke wrappers neutralize every agent-config destination override; a smoke can never scribble on your real config.
Recommended workflows
- Iterating on a change:
scripts/test.sh --suites <suite>(seconds, incremental, same ASan+UBSan flags as the gate). List suites:build/c/test-runner --list-suites. Debugging a Windows-on-ARM trap: re-run withSANITIZE=for a plain build, or use the emulatedwin.sh ubsan-*pair for full diagnostics. - Before any push (the 3-OS ladder):
scripts/test.sh(macOS, full) →./test-infrastructure/run.sh full(Linux + TSan + smoke) →test-infrastructure/vm/win.sh test-par+guards+smoke-install(+soakwhen the change touches memory/daemon paths). - Concurrency-touching change: add
scripts/test.sh --tsanearly — the same leg CI gates on. - Release-shaped verification:
CBM_SMOKE_ARTIFACT_DIR=<extracted artifact> scripts/smoke-local.sh <binary> [ui]smokes exactly what would ship. - A leg is red in CI but green locally: first suspect environment shape,
not code — the preflights (
win.shautomatic;scripts/ci/preflight-docker.sh) andtest-infrastructure/README.md's residuals list cover the knowable differences.
See scripts/ci/README.md for the CI plumbing and
test-infrastructure/README.md for the venue map.