Commit Graph
3321 Commits
Author SHA1 Message Date
Martin Vogel c86be38954 docs: correct CLI-mode claim that one-shot commands never touch the daemon (#2183, #2167)
README.md:132, README.md:633, README.md:761, and docs/CONFIGURATION.md:203
said one-shot `cli` commands "never start or connect to the coordination
daemon" / read "their own environment without starting the daemon". That
was already contradicted a few lines above it (README.md:126 lists
one-shot CLI commands among the processes that "share a crash-safe OS
admission barrier" with the daemon) and by the code:
main_local_cli_daemon_execute() (src/main.c) bootstraps a client
connection to the shared per-user coordination daemon via
main_client_bootstrap_with_upgrade(), spawning one (bootstrap.daemon_spawned)
when none is running, before dispatching the tool call.

Verified live: `cli --verbose list_projects` against an isolated
HOME/CBM_CACHE_DIR/CBM_RUNTIME_DIR printed "hint: this command started a
temporary CBM daemon", and its cbm-daemon.log showed
daemon.start -> daemon.runtime_stopping (reason=last_committed_client_disconnected)
-> daemon.lifetime_end, i.e. the daemon it spawned exited again once the
CLI command's own connection closed. bootstrap_production_spawn() passes
the calling process's `environ` straight to posix_spawn(), so a CLI
invocation that starts the daemon also seeds that daemon's captured
daemon-owned environment (CBM_DIAGNOSTICS, CBM_LOG_LEVEL, etc.) - it does
not read "its own environment" independently as the docs claimed.

Corrected all four passages to state the real behavior: CLI commands
connect to the shared daemon (starting one if none is running) for the
same admission barrier and per-project locks, hold a `cli_session` that
is never registered with the background watcher, and - if their own
connection is what started the daemon - that daemon exits again once the
command's connection closes and no other session is attached. No opt-out
env var or flag for an in-process/no-daemon CLI mode exists (grepped the
whole tree), so none is documented.

This is a pure documentation fix; no production behavior changed. Kept
clear of PR #2348 (fix/issue-2167), which separately rewrites the
watcher-scope paragraph at README.md:152/237/672 and
docs/CONFIGURATION.md:88 for issue #2167's auto-sync-scope correction -
none of the lines here overlap with that diff.

Refs #2183, #2167

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-30 23:05:04 +02:00
Martin Vogel 0f52d30c29 Merge pull request #2412 from DeusData/fix/activation-unknown-daemon-is-foreign
fix(cli): never stop a daemon whose cache is not confirmed as ours
2026-09-30 22:21:59 +02:00
Martin Vogel 878379a8af Merge pull request #2411 from DeusData/fix/harness-timeout-race-deterministic
test(harness): hold the scheduler contract's fixtures by construction
2026-09-30 22:21:53 +02:00
Martin Vogel f4ff01b10b Merge pull request #2410 from DeusData/fix/test-runner-isolate-runtime-dir
fix(test): isolate the C test runners' daemon runtime
2026-09-30 22:21:48 +02:00
Martin Vogel 8b8cafb616 Merge pull request #2409 from DeusData/test/daemon-ipc-startup-gate-no-timeout
test(daemon-ipc): wait for the startup gate event, not an attempt budget
2026-09-30 22:21:43 +02:00
Martin Vogel 218f0e5b78 Merge pull request #2408 from DeusData/test/watcher-poll-only-watched-deflake
test(watcher): poll-only-watched-projects waits for the watched reindex, not one poll
2026-09-30 22:21:35 +02:00
Martin Vogel 0bee4c1b79 Merge pull request #2406 from DeusData/fix/spill-test-disk-determinism
test(spill): make spill test verdicts independent of host free disk
2026-09-30 22:21:17 +02:00
Martin Vogel 48acb559e6 Merge pull request #2405 from DeusData/fix/internal-db-enumeration
fix(cli): never treat internal cache stores as project indexes
2026-09-30 22:21:11 +02:00
Martin Vogel 083bc99d80 Merge pull request #2404 from DeusData/fix/issue-2118
fix(mcp): annotate get_file_outline and ingest_traces as read-only (#2118)
2026-09-30 22:21:06 +02:00
Martin Vogel 39da5de00c Merge pull request #2403 from DeusData/fix/issue-1297
docs(readme): semantic_query is a search_graph parameter; team artifact is opt-in (#1297)
2026-09-30 22:21:00 +02:00
Martin Vogel d0ec2313d9 Merge pull request #2394 from DeusData/fix/issue-2184-post-extraction-budget
fix(pipeline): spill before the post-extraction phases when they would cross the budget (#2184)
2026-09-30 22:20:56 +02:00
Martin Vogel 6d19350e37 Merge pull request #2358 from DeusData/fix/issue-2311
docs(pipeline): CBM_EXTRACT_BUDGET is a CPU-time parse budget, not bytes (#2311)
2026-09-30 22:20:51 +02:00
Martin Vogel cb251ed472 Merge pull request #2357 from DeusData/fix/issue-2144
feat(mcp): opt-in async index_repository with status polling (#2144)
2026-09-30 22:20:46 +02:00
Martin Vogel 029919c660 Merge pull request #2355 from DeusData/fix/issue-2209
fix(install): Hermes hook edit no longer refuses unrelated YAML sections (#2209)
2026-09-30 22:20:30 +02:00
Martin Vogel 1ff6299976 Merge pull request #2354 from DeusData/fix/issue-2235
fix(extract): read the url of a JS/TS request-config object (#2235)
2026-09-30 22:20:23 +02:00
Martin Vogel 368f33f9e0 Merge pull request #2353 from DeusData/fix/issue-2200
test(cli): pin that update -y never deletes project indexes (#2200)
2026-09-30 22:20:18 +02:00
Martin Vogel 372aff2b5c Merge pull request #2351 from DeusData/fix/issue-2208
fix(cypher): carry the real node through WITH, not a name-only stub (#2208)
2026-09-30 22:20:14 +02:00
Martin Vogel 6746f43549 Merge pull request #2343 from DeusData/fix/issue-2133
fix(cross-repo): a pre-#768 store no longer aborts ["*"] or wipes edges (#2133)
2026-09-30 22:19:37 +02:00
Martin Vogel 94097f0727 Merge pull request #2341 from DeusData/fix/git-spawn-local-env
fix(git): clear git's repository-local env for cbm's own git children (#2003)
2026-09-30 22:19:32 +02:00
Martin Vogel 9df483f0af Merge pull request #2338 from DeusData/fix/issue-2044
fix(install): accept a Codex hook binary path that contains a space (#2044)
2026-09-30 22:19:27 +02:00
Martin Vogel 30a410b398 Merge pull request #2335 from DeusData/fix/issue-1955
fix(daemon): hash a verified peer image copy once, not on every HELLO (#1955)
2026-09-30 22:19:22 +02:00
Martin Vogel 78795ad76a Merge pull request #2339 from DeusData/fix/issue-2053
fix(rust): never let a std-typed receiver bind a same-named project method (#2053)
2026-09-30 22:19:17 +02:00
Martin Vogel 5910c47a97 Merge pull request #2332 from DeusData/fix/issue-1916
fix(pipeline): compose axios.create baseURL into HTTP_CALLS (#1916)
2026-09-30 22:19:12 +02:00
Martin Vogel e2f9fcc399 Merge pull request #2334 from DeusData/fix/issue-1951
fix(mcp): translate Git-root paths for subdirectory projects in detect_changes (#1951)
2026-09-30 22:19:07 +02:00
Martin Vogel 7821a1babb Merge pull request #2331 from DeusData/fix/issue-1827
fix(mcp): resolve projects by their non-ASCII folder name (#1827)
2026-09-30 22:19:02 +02:00
Martin Vogel 6bce86383e Merge pull request #2330 from DeusData/fix/issue-1783
fix(sqlite-writer): never end a B-tree level with a zero-cell interior page (#1783)
2026-09-30 22:18:48 +02:00
Martin Vogel 09819ba144 Merge pull request #2328 from DeusData/fix/issue-1743
fix(lsp): bound structured-binding field reads and TS method heritage walk (#1743)
2026-09-30 22:18:43 +02:00
Martin Vogel 0cff5dbe7c Merge pull request #2325 from DeusData/fix/issue-1735
fix(discover): skip Laravel's compiled Blade view cache by default (#1735)
2026-09-30 22:18:38 +02:00
Martin Vogel e9a3d18fbd Merge pull request #2322 from DeusData/fix/issue-1687
fix(daemon): name the WSL DrvFs remedy when a /mnt/<drive> cache is refused (#1687)
2026-09-30 22:18:33 +02:00
Martin Vogel f5ecb5f739 Merge pull request #2321 from DeusData/fix/issue-1457
fix(dart): index extension declarations and their members (#1457)
2026-09-30 22:18:27 +02:00
Martin Vogel 2bed1c8c4a Merge pull request #2319 from DeusData/fix/issue-1459
fix(cross-repo): skip CROSS_HTTP_CALLS for calls the caller serves itself (#1459)
2026-09-30 22:18:22 +02:00
Martin Vogel aa53278b29 Merge pull request #2318 from DeusData/fix/issue-1133
fix(cross-repo): fail explicitly when zero target projects resolve (#1133)
2026-09-30 22:17:47 +02:00
Martin Vogel 80eb92a701 Merge pull request #2252 from DeusData/distill/1245-scope-lookup
perf(lsp): collapse repeated Python scope-chain lookups
2026-09-29 00:32:27 +02:00
Martin Vogel 92910147ab Merge pull request #2181 from AmirF194/fix/510-enclosing-gitignore-not-inherited
fix(discover): honor an enclosing repo's .gitignore for a git-less subfolder
2026-09-29 00:32:20 +02:00
Martin Vogel 69fb951cd0 Merge pull request #2089 from AmirF194/fix/2077-opencode-v2-plugin-shape
fix(cli): emit the OpenCode plugin as a V2 default export
2026-09-29 00:32:06 +02:00
Martin Vogel 2b34ebcbf1 Merge pull request #2396 from SarletteD/fix/cppcheck-nullable-strcmp
style(mcp): compare nullable strings through one helper
2026-09-29 00:32:01 +02:00
Martin Vogel 5621a1c9c4 Merge pull request #2065 from kavish-19/fix-count-failed-read
fix(store): report a failed COUNT read instead of returning zero
2026-09-29 00:31:23 +02:00
Martin Vogel 17718126ea Merge pull request #2375 from DeusData/fix/complexity-walker-no-silent-cap
fix(extract): AST walker stacks grow instead of silently capping content
2026-09-29 00:31:19 +02:00
Martin Vogel 2b4084ff0c Merge pull request #2247 from DeusData/distill/1789-artifact-error-surfacing
fix(mcp): name the artifact phase when a post-publish export fails
2026-09-29 00:30:50 +02:00
Martin Vogel 13ce2c0cd5 Merge pull request #2073 from astandrik/astandrik/fix-issue-2034
fix(tests): create nested pipeline fixtures reliably
2026-09-28 22:08:45 +02:00
Martin Vogel 8fa5eefcca Merge pull request #2370 from BobbieBarker/fix/registry-cfg-fence-bare-name
fix(registry): reach a cfg-fenced function from its bare name
2026-09-28 22:08:30 +02:00
Martin Vogel ed76cf8e88 fix(cli): never stop a daemon whose cache is not confirmed as ours
Why: the activation rendezvous is per OS account, so an install or update
run under another HOME / CBM_CACHE_DIR (a sandbox, a second profile) meets
the account's live daemon at the same endpoint. The daemon's
activation-shutdown handler verifies only the requester's build, never its
cache, so the CLI's scope decision is the only thing that tells "my daemon"
from "someone else's". That decision drained the cohort unless the active
cache was positively DIFFERENT: a busy session group (another activation or
a session start held it, so the active identity was never read) and an
unreadable active cache both fell through to "ours", printed "Stopping
active CBM sessions" and shut the daemon and every session behind it down.

Fix (user decision 2026-09-28, "unknown = foreign, keep it"): only a
positive match may drain. cli_activation_resolve_scope returns
CLI_ACTIVATION_SCOPE_ACTIVE only for an OK admission (exact identity, cache
included, or an empty group) or for a conflict whose cache fingerprint
equals this activation's. BUSY and a blank active cache now return the new
CLI_ACTIVATION_SCOPE_UNCONFIRMED, handled like a foreign cohort: no lock is
taken, no drain request is sent, the mutation proceeds, and the audit log
records "quiesce_skipped: active cohort ownership unconfirmed (...)". A
read-only status probe names the daemon that was left (pid, version) and
the notice says how to stop it: close its sessions, then run
`codebase-memory-mcp daemon stop`. The confirmed paths are unchanged: a
normal self-upgrade still drains its own daemon, a clearly foreign cache is
still left alone.

Proof (product binaries; private CBM_RUNTIME_DIR; an own permanent test
daemon started from the same build under a host HOME/cache; a sandbox
HOME/cache on the same runtime dir; the cohort maintenance gate held by a
second process, i.e. a busy session group):
- origin/main: "Stopping active CBM sessions and operations for
  install...", the test daemon was stopped (daemon status: not running),
  and the install still failed (rc=1 after 23 s, "could not be stopped
  safely").
- this change: "Leaving the running CBM daemon untouched (pid 76713,
  version dev): this install could not confirm that it serves the cache
  this install targets (the session group was busy, so its cache identity
  could not be read)." The test daemon kept running with the same pid and
  the install completed (rc=0, 10 s).
A lifetime record whose cache field is zeroed is refused (UNSAFE) by both
builds with the daemon kept; that path never drained and is unchanged.

Tests (cli suite; fixture daemon in a private runtime dir; sandbox HOME):
- cli_install_foreign_home_unreadable_cohort_cache_keeps_host_daemon
  (the new seam cbm_cli_set_activation_scope_cache_unreadable_for_test
  blanks the observed active cache fingerprint)
- cli_install_foreign_home_busy_cohort_keeps_host_daemon (the maintenance
  gate is held through the product's own lock-directory handle)
Both were RED 3x on origin/main for the right reason (the host daemon was
drained: ASSERT(host_serving)), are green with the fix, and turn RED again
when the fix is reverted. Controls stay green:
cli_install_into_host_namespace_still_drains_host_cohort (confirmed same
cache: drained, as before) and
cli_install_{skip_binary,binary}_into_foreign_home_never_drains_host_cohort
(clearly different cache: kept, as before).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-28 20:24:48 +02:00
Martin Vogel 0d783bcca4 test(harness): hold the scheduler contract's fixtures by construction
The parallel-scheduler contract failed twice on test-windows CLANG64 while
running fake suites only:

- PR #2394 (run 36364479687): "Windows timeout race refused without a
  surviving descendant to refuse over". The refusal came 38s after the
  forced leader exit (a cold powershell/CIM descendant probe, once in the
  wave loop and again in the cleanup pass), while the fixture's descendant
  was a `time.sleep(30)` that had already exited on its own.
- PR #2345 (run 36199749518): "suite 'hang_after_summary' taskkill could
  not prove process-tree cleanup". The scheduler bounded taskkill.exe with
  --kill-grace, which the contract passes as 1s, so a slow cold start of
  the tool was reported as a failed cleanup.

Both verdicts were a race between a clock and runner latency. An audit of
the fixture found more of the same shape:

- Every hanging fixture ended on its own after 30s. A starved scheduler
  could therefore see a "hung" leader exit 0, and a descendant the
  scheduler failed to kill could vanish before the leak check looked.
- The 1s --timeout could kill a slow leader before it had printed its
  summary (asserted pass=1), spawned its descendant or written the
  descendant pid. That pid was then read back with int() from a file
  written create-then-write, so a reader could see no file or an empty one.
  The empty-file read recorded earlier was the scheduler's ready file,
  which 3b9052c9 already publishes atomically; the fixture's own writer
  had the same shape.
- The descendant installed its SIGTERM-ignore during its own start-up. A
  SIGTERM that won that race skipped the SIGKILL escalation the POSIX leg
  exists to cover, silently and with the verdict unchanged.
- On Windows, the stubborn-tree leak check was a single look right after
  taskkill, but TerminateProcess returns before its target has exited.

Fix it by construction, not by budget:

- A gate. Every process that must outlive the scheduler's decision blocks
  reading stdin. That is a pipe whose only write end the contract holds,
  handed down through the scheduler, and the read returns only at EOF. The
  contract closes the pipe once the verdict is recorded. If the contract
  dies, EOF releases everything, so nothing is orphaned and no fixture
  ends on a clock.
- The three hanging scenarios run under the scheduler's existing
  pre-terminate barrier. They are released only once the fixture has
  published `<suite>.established`, which happens after the summary is
  flushed, the descendant has confirmed it is armed, and its pid is known.
  The marker is written to a temp file and moved into place with
  os.replace, so a reader sees either no file or the whole file.
- The Windows leak check waits on the descendant's process handle. That
  wait is exact, not a race: the descendant is held by the gate, so the
  scheduler's kill is the only way it can end.
- run-test-wave.py runs taskkill through windows_taskkill_tree() with its
  own stable-state budget (WINDOWS_TASKKILL_SECONDS = 15), as 132e8fc3 did
  for the descendant probe. --kill-grace keeps bounding what its name
  says. Real runs already pass --kill-grace 15, so they are unchanged.
  The contract pins this structurally, the same way it pins the probe:
  exactly one taskkill site, no kill-grace there, and a helper bounded by
  the constant that fails closed on a timeout, a start failure or a
  non-zero exit.

Every assertion keeps its contract:
- A leader that hangs after a green summary is recorded as rc=124 pass=1.
- A zero-test child is recorded as rc=97.
- The wave continues after a bounded failure.
- A SIGTERM-resistant tree is killed in full.
- A leader that exits between the timeout decision and termination is
  still cleaned up on POSIX. On Windows the scheduler refuses (rc=2,
  naming the cleanup failure) over a descendant that is really alive.
A new self-check fails loudly if a held leader or descendant is not live
while the barrier holds, that is, if the fixture no longer exercises what
those assertions claim.

Proof, on macOS arm64 and a Linux arm64 container. The Windows branch was
reasoned through; the VM is down.
- Torture hook (proof only) that delays each hanging leader 2s, past the
  1s timeout. The old fixture is RED 3/3 on every scenario (pass=0,
  FileNotFoundError on descendant.pid, "lost its bounded result"). The new
  one is green, including at 8s, and 10/10 under the hook plus CPU load.
- The #2394 mechanism: after an injected 31s refusal latency the old
  descendant is gone and the new one is live. It exits 0.02s after the
  gate closes.
- The taskkill pin is RED on the old scheduler and green on the new one.
- Stress, 20 runs each under CPU load. The old full contract passed 20/20
  on macOS and 20/20 on Linux, so these hosts do not reproduce the CI
  timing. The rebuilt held scenarios passed 20/20 on macOS and 20/20 on
  Linux. The full contract after the change passes a single run on macOS
  and on Linux, and 20/20 on each leg under CPU load (4 load workers;
  the Linux container is capped at 4 CPUs).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-28 19:45:45 +02:00
Martin Vogel 2278498371 Merge pull request #2393 from DeusData/fix/issue-2264
fix(install): migrate v0.10.8 OpenCode agent profiles on upgrade (#2264)
2026-09-28 19:45:27 +02:00
Martin Vogel cb536768be Merge pull request #2392 from DeusData/fix/issue-2277
fix(daemon): name the conflicting daemon and the way out on a build conflict (#2277)
2026-09-28 19:45:04 +02:00
Martin Vogel d82e1e6014 Merge pull request #2326 from DeusData/fix/issue-1735-sql-cost
fix(sql,coverage): make large SQL dumps cheap to parse (#1735)
2026-09-28 19:44:27 +02:00
Martin Vogel d17f97f431 Merge pull request #2352 from DeusData/fix/issue-2176
fix(rescript): stop binary .res resources hanging the indexer (#2176)
2026-09-28 19:43:46 +02:00
Martin Vogel 4c6ecffdcd test(daemon-ipc): wait for the startup gate event, not an attempt budget
daemon_ipc_windows_startup_retries_transient_rendezvous_reader failed on
a windows-latest CLANG64 runner (ASSERT(startup_observed); the whole
suite took 7 s) on a PR that touched only CLI files.

ipc_test_startup_gate_wait_acquired polled 50000 x cbm_usleep(200) and
returned false once the budget ran out. On POSIX that is ~10 s, but on
Windows cbm_usleep(us) is Sleep(us / 1000), so cbm_usleep(200) is
Sleep(0): the budget collapsed to 50000 bare yields, which a loaded
runner spends in milliseconds before the startup thread reaches the
gate. A timeout decided the verdict.

The helper now waits until the startup thread has either parked in the
gate or returned without reaching it (a new `finished` flag, set by
ipc_test_win_startup_call after its call returns). Both flags only go
false -> true and one of them is always set: before the gate the startup
path only try-acquires, and a thread parked in the gate cannot return
until the test releases it. No bound is left to decide the verdict. A
thread that returns without reaching the gate fails at once, and the
test reports its try-acquire status as the reason.

The same shape is removed from two cross-platform tests:

- daemon_ipc_wait_forever_is_interruptible waited for the server's
  forever-wait under a 2 s wall-clock deadline. Past the deadline the
  test skipped the interrupt, so the join then blocked on a server
  parked in WAIT_FOREVER: a hang, not a failure. It now waits for
  `waiting` or for a new `finished` flag set when the server thread
  returns.
- daemon_ipc_connect_waits_for_delayed_listener capped its wait for
  `delay_started` at 2000 polls. That store is the thread's first
  action, so the wait needs no attempt bound.

No production change and no new sleep: the existing per-poll yields
stay.

Proof. A standalone harness mirrors the gate, both helpers and the
thread wrapper, with cbm_usleep modelled as on Windows
(Sleep(us / 1000), so cbm_usleep(200) is a bare yield):
- startup thread arriving only after 50000 polls: old helper 20/20 FAIL
  (startup_observed=false with call.result=1, the CI signature), new
  helper 20/20 PASS;
- startup thread descheduled 500 ms: the old helper gives up after 9 ms,
  10/10 FAIL; new helper 10/10 PASS; control, old helper with POSIX
  usleep: 10/10 PASS;
- thread returning without reaching the gate: the new helper fails
  within 11 ms with call.result=-1 (the old one took 16 s under POSIX).

Real daemon_ipc suite on macOS: with the server and listener threads
held 4 s and 6 s (beyond both old bounds) the two cross-platform tests
pass. The old 2000-poll loop under the 6 s hold fails
ASSERT(delay_observed). The old 2 s deadline under the 4 s hold hangs in
the join until killed. A server that skips accept makes the new loop
fail ASSERT(reached_wait) with no added wait.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-28 19:43:10 +02:00
Martin Vogel 23d48336d3 test(watcher): poll-only-watched-projects waits for the watched reindex, not one poll
watcher_poll_only_watched_projects failed on the macOS LSan job of PR
#2295, whose diff was Java-only, with
`FAIL tests/test_watcher.c:2971: index_call_count == 0, expected 1`
(8211 passed, 1 failed). The LSan report that followed points into the
aborted test body (watcher and store never freed after the early FAIL):
a consequence, not a separate leak.

The assert expected ONE touch+poll after a dirty change to reindex
exactly once. That needs every git probe of the poll to succeed. A poll
whose git spawn is refused under runner load is a fail-closed no-op by
design (#937: the baseline stays uncommitted and the next poll retries),
and that path returns without a log line. This is the same test race
that #2376 fixed in watcher_unwatch_drains_pending_free and
watcher_sustained_failure_logs_once_issue2015, not a production defect.
Holding one exhausted spawn with
cbm_subprocess_force_spawn_eagain_for_testing at that poll reproduces
the CI output exactly: watch, baseline, then
`FAIL tests/test_watcher.c:2971: index_call_count == 0, expected 1 == 1`,
with no watcher.changed and no watcher.git.failed.

The old test was also partly inert. The Go original (TestPollAllOnlyWatched,
from the #49 fix that replaced scan-all with an explicit watch list)
registered BOTH projects in the store and asserted that the unwatched
one gets no watcher state. The C port dropped the store registration, so
nothing let the watcher learn about B, and the B half could not fail.

The test now:
- registers A and B in the store, and watches only A;
- asserts that the baseline poll indexes nothing and does not adopt B;
- makes both worktrees dirty (a dirty change, not a commit, so a late
  baseline still sees it);
- polls until A's reindex has happened, asserting after EVERY poll that B
  has no watcher state (cbm_watcher_index_failure_count == -1), was never
  reindexed, and that the watch count is still 1;
- asserts that A was reindexed exactly once.
No timer or deadline decides the verdict. A watcher that never reindexes
A hangs, which the runner reports as a failure, never a pass.

Evidence (macOS arm64, ASan/UBSan test-runner, CBM_SANITIZED, so one
exhausted spawn = 10 injected refusals), with temporary injection that
has since been removed:
- CI-shaped (baseline OK, first change poll refused): consumed=10 at
  loop poll 1, watcher.changed at loop poll 2, PASS.
- Heavy (baseline poll plus loop polls 1-3 refused): loop poll 4 takes
  the baseline, poll 5 reindexes A; loop polls=5 watched=1 other=0, PASS.
- Mutation, the pre-#49 scan-all (poll_once adopts every stored project):
  `FAIL tests/test_watcher.c:3001: cbm_watcher_index_failure_count(w,
  "projB-ow") == 0, expected -1 == -1`, both unforced and with the heavy
  refusals (A's and B's baseline spawns both refused). The other 82
  watcher tests stay green under that mutation, so this is the only
  test guarding #49.
Final tree: build/c/test-runner watcher, 5/5 runs, 83 passed each.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-28 19:43:10 +02:00
Martin Vogel 78a2ec399f test(parallel): rename the near graph buffer for MinGW
The #2184 parallel test named a local `near`. Windows headers define
`near` (and `far`) as empty legacy pointer qualifiers, so under MinGW
`cbm_gbuf_t *near = ...` loses its name and test_parallel.c fails to
compile (test-windows CLANG64 2/2: "expected identifier or '('").

Rename it to `gb_near`. A MinGW syntax-only check of tests/test_parallel.c
reported 4 errors before this change and none after.

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