Files
codebase-memory-mcp/scripts
soren d61811d27e test(watcher): process-level regression for watcher_enabled + startup-read docs (#335)
Follow-up to the PR #1105 review: the unit test proves the config predicate but
would still pass even if the daemon-side gate were removed. Add a process-level
regression that drives the real binary and proves the gate actually prevents the
watcher from being built, started or registered when disabled.

- tests/test_watcher_disabled.sh (wired into scripts/test.sh as Step 5e) drives
  the binary against an isolated CBM_CACHE_DIR + tiny git fixture. With
  watcher_enabled=false it asserts: watcher.disabled reason=config emitted,
  watcher.start absent, no watcher.watch registration, auto_index still runs, and
  manual index_repository is served. A watcher_enabled=true positive control
  asserts watcher.start + watcher.watch appear, so the test fails if the gate is
  removed (verified by neutering the gate in host_state_prepare and observing the
  failure).

  No assertion is decided by a timeout. The watcher lives in the daemon, which
  logs to $CBM_CACHE_DIR/logs/cbm-daemon.log in a fixed order: watcher.disabled
  (host_state_prepare) -> daemon.start -> the thread's own watcher.start -> the
  join -> daemon.stop. Each run therefore retires its daemon and waits for that
  CLOSED lifecycle before asserting, so absence is a fact rather than a race.
  Every wait is a bounded poll on an asserted state following the soak harness's
  wait_for_daemon_stop doctrine, and an exhausted budget fails loudly with the
  reason instead of passing quietly; session stdin is held open through a FIFO
  until the last JSON-RPC response lands, not for an interval.

  Registration is asynchronous and unreachable until the project DB exists
  (application_refresh_watch_locked requires application_regular_db_exists), so a
  closed lifecycle alone would not carry the negative — the session could simply
  have ended first. The disabled+auto_index run waits for that DB, i.e.
  registration's own precondition; the enabled control waits for watcher.watch
  itself, so its absence in the disabled runs is evidence rather than noise.

  Honors CBM_TEST_BINARY like tests/test_worker_watchdog.sh, so the BUILD_DIR=
  sanitizer and container legs test the binary they built.

- docs: CONFIGURATION.md + README.md document that watcher_enabled is read once
  when the daemon starts — the daemon outlives individual MCP sessions, so
  reconnecting a client is not enough; `daemon stop` retires it. This corrects
  the earlier claim that auto_watch is also startup-read: auto_watch is consulted
  per registration, and the two keys are now contrasted on read time as well as
  on scope (session registration vs whether the subsystem starts at all).

No production code change in this commit: watcher.start and watcher.watch already
exist as production logs.

Signed-off-by: soren <nguiasoren@gmail.com>
2026-08-24 07:25:34 +09:00
..

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.
  • 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 with SANITIZE= for a plain build, or use the emulated win.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 (+ soak when the change touches memory/daemon paths).
  • Concurrency-touching change: add scripts/test.sh --tsan early — 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.sh automatic; scripts/ci/preflight-docker.sh) and test-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.