Commit Graph
3 Commits
Author SHA1 Message Date
Martin Vogel 3909e5a0e1 feat(mcp): make tool output lean and lossless
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel 73f4a432e4 test(windows): isolate the daemon guards' rendezvous and stop hiding their failures
Every Windows guard shared ONE daemon. test_daemon_stability.py said each
section "runs under its OWN cache directory: daemon coordination is
cache-scoped" -- it is not. The rendezvous key is FNV-1a over a compile-time
domain string (service.c: cbm_daemon_rendezvous_key) under a machine-global
parent (win_default_runtime_parent -> SHGetFolderPathW(CSIDL_LOCAL_APPDATA),
which ignores the LOCALAPPDATA variable). So the guard's own `daemon stop`
and kill -9 sections, the sibling guards, and anything else on the runner
all met the same daemon, and a daemon wedged anywhere failed every check
after it. That is the shape of this week's test-windows-guards red on
#1874: lifecycle's `daemon start` connected, the UI-config handshake never
answered, and stability / hook_augment / ui_drive / cli_non_ascii went red
in a row with an orphan daemon killed at cleanup -- on a diff and a base that
were each green elsewhere.

CBM_RUNTIME_DIR is the only relocation hook (bootstrap.c, resolved in
cbm_daemon_bootstrap_endpoint_new for every product endpoint). Each stability
section now gets a private runtime parent next to its cache, created before
the binary runs -- the endpoint validates the parent and never creates it --
and the daemon inherits it from the client that spawns it. Proven on macOS:
a daemon on the default rendezvous keeps its pid across the whole guard
(three stops and a kill -9 inside it).

The reporting was hiding the cause as well:
* every SETUP FAIL printed nothing at all, and every RED printed a 300-400
  character HEAD slice, which is the allocator preamble, never the error.
  Excerpts now show the tail (1500 chars); setup paths print the daemon's
  full output.
* a subprocess timeout raised on a parallel-client worker thread was lost
  and the client reported as "rc=none" with no output; it is now kept as a
  failed result with its partial output. A section-level timeout is a
  named RED with the command and output, not a bare traceback.
* the MCP session's stderr went to DEVNULL; it now goes to a file that is
  printed when initialize never answers -- and that case is a RED against a
  running daemon, not a "setup" skip.
* exit 2 was documented and never produced. Only `daemon start` failing
  raises it now (test_daemon_lifecycle.py owns that surface as a
  regression, so the board loses nothing); main() runs every section and
  names all of the failing ones.
* test_hook_augment.py / test_cli_non_ascii_arg.py print full stdout AND
  stderr on their index-setup failures instead of 300 chars of stdout.

On POSIX (local iteration only) the namespace lives under /tmp: sun_path
caps the socket at 104 bytes on macOS and TMPDIR there is ~50 bytes deep.

Out of scope, noted: test_daemon_lifecycle.py, test_non_ascii_path.py and
test_ui_drive_listing.py still use the machine-global rendezvous; a too-long
CBM_RUNTIME_DIR on POSIX is refused (ipc.c sun_path check) with no
validation detail.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-03 14:56:42 +02:00
Martin Vogel 34f9ad1b3d fix(daemon,win): CI-green fixes and a daemon-stability guard
Fixes surfaced while bringing the shared-coordination-daemon branch
green across macOS, the Linux containers, and the real Windows-ARM64 VM,
plus a new stability guard that caught one of them.

- daemon(win): the CLI teardown transition latched a failed release as
  permanent, but a Windows participant-state release must briefly
  try-hold the shared startup/legacy gates and legitimately collides
  with a concurrent one-shot's teardown; the release is retriable by
  contract (it always retains the transition), so parallel one-shot
  commands no longer report "CLI coordination cleanup failed" despite
  succeeding. Found by the new stability guard's churn section.
- cli/main(win): install/update/uninstall and the Augment hook-script
  removal derive the managed launcher's identity from its plain
  drive-form path (cli_windows_plain_utf8), not the \\?\ extended-length
  form, so agent-config ownership matches on Windows and uninstall no
  longer leaves the MCP entry or owned hook scripts behind.
- security: the `daemon start --open` browser launch is now shell-free
  (ShellExecuteW on Windows, cbm_exec_no_shell elsewhere) instead of
  system(); the CORS origin check spells out its two literal loopback
  URLs so the static URL audit sees a complete value. Both clear the
  Layer-1 allow-list audit.
- coordination: version-cohort lock retries now sleep a per-process
  jittered interval — fixed-period retries can phase-lock two
  participants so one starves. The activation-quiesce test's
  observation window now covers worst-case candidate staging (three
  tamper-defense hashes over a ~1 GB sanitizer binary exceed the old
  30 s budget on container I/O).
- smoke/soak: the agent-config smoke matches Windows config paths
  through their escaped-backslash quoted form; the soak parser skips the
  update-available banner before the JSON summary and gained a one-shot
  CLI admission-churn phase inside the RSS/FD leak window. The build-dir
  safety fixture creates a real symlink/junction (MSYS2 ln -s otherwise
  deep-copies) so its traversal-refusal contract is genuinely exercised,
  and the symlinked-agent-roots test demotes its planted links to an
  unprivileged owner when run as root so the refusal is really tested.
- tests(win): a new daemon-stability guard exercises the parameter
  surface, hook fail-open with its rate-limited notice, start-twice and
  occupied-port handling, busy-stop refusal against a live MCP session,
  kill -9 crash recovery, and sequential/parallel one-shot churn.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-07-22 17:48:20 +02:00