Files
Martin Vogel 533eee07ec fix(test): isolate the C test runners' daemon runtime
Why: the test runner isolated HOME and CBM_CACHE_DIR but not
CBM_RUNTIME_DIR, and neither of the first two moves the daemon
rendezvous. Its key is a constant product hash (cbm_daemon_rendezvous_key,
"c888acc3ae367a1e"), and with no explicit parent and CBM_RUNTIME_DIR unset
cbm_daemon_bootstrap_endpoint_new falls back to the account-wide default
parent (/private/tmp on macOS, /tmp on Linux, LocalAppData on Windows).
Every endpoint a test resolved without an explicit parent was therefore
byte-identical to the developer's live daemon socket
(/private/tmp/cbm-daemon-<uid>/cbm-c888acc3ae367a1e.sock), whatever HOME
said.

The only in-runner product caller is CLI activation
(cli_activation_production_context_init, reached from cbm_cmd_install,
cbm_cmd_uninstall and update). Only a process-global seam kept it off the
default: SUITE(cli) sets that seam on entry and clears it on exit. On the
default endpoint, activation admits into the live daemon's cohort. When
the cohort is ours or busy, or the active cache fingerprint is unreadable,
activation prints "Stopping active CBM sessions" and drains them. That is
the same class of failure as an old binary's install, which stopped a
user daemon and six MCP sessions. One test outside SUITE(cli), or one
fixture that drops the variable, was enough to reach it.
daemon_bootstrap_runtime_dir_env_relocates_rendezvous did exactly that:
it restored by unsetting CBM_RUNTIME_DIR.

Fix:
- tests/test_main.c: tf_setup_cache_sentinel creates a fresh per-run
  runtime parent (th_secure_runtime_parent_new "run", under /tmp on POSIX
  and LocalAppData on Windows) and sets CBM_RUNTIME_DIR to it for the
  whole run. It replaces any inherited value, the rule
  scripts/test-runtime.sh applies to the shell harness. The existing
  atexit cleanup removes the directory.
- src/daemon/bootstrap.c, CBM_ENABLE_TEST_SEAMS only: once a test runner
  arms the guard, an endpoint request with no explicit parent and no
  CBM_RUNTIME_DIR is refused with a stderr diagnostic instead of
  resolving the default. No product binary arms it.
- tests/repro/repro_main.c arms the same guard. No reproduction needs the
  rendezvous.
- tests/test_daemon_bootstrap.c: the relocation test restores the value
  it found instead of unsetting it.
- tests/test_helpers.h: th_secure_runtime_base() names the anchor that
  th_secure_runtime_parent_new already used.

Proof:
- Before, on origin/main 1f9b4db1: runner_isolation is RED 3/3 at its
  first assertion (CBM_RUNTIME_DIR is NULL during the run).
- After: green 3/3. The endpoint resolved through the product resolver
  lies inside /private/tmp/cbm-runtime-run-XXXXXX/, never at
  /private/tmp/cbm-daemon-<uid>. All 43 activation-guard scopes logged
  by the suites below are fixture or cli-suite directories, and the
  guard never fired. The per-run directory is gone after exit, and the
  live daemon and its runtime directory are unchanged.
- Revert: dropping the setenv gives RED (value is NULL). Disarming the
  guard gives RED (refused_without).

Tests:
- runner_isolation: runner_isolation_daemon_runtime_dir_is_private,
  runner_isolation_refuses_default_daemon_runtime
- Green on macOS: cli (328), mcp + mcp_mutation_guard (344),
  daemon_runtime + daemon_application (106), daemon + daemon_version +
  daemon_frontend + daemon_bootstrap + daemon_ipc (117), runner_isolation
  + activation_transaction + version_cohort + project_lock +
  index_supervisor + private_file_lock + lock_registry (73). All skips
  are existing Windows- or Linux-only platform skips.
- make -f Makefile.cbm lint-ci passes. test-repro-runner builds.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-28 19:34:20 +02:00
..