Commit Graph
1340 Commits
Author SHA1 Message Date
Martin Vogel 9846c3f171 fix(daemon): compare session roots separator-equivalently without respelling the policy root
The first cut on this branch (c1b9c451) respelled the session root and
the allowed root with forward slashes on the way into the session policy
(cbm_mcp_server_set_session_context) and at the three application.c sites
that canonicalize a root. CI's Windows shard then failed two tests:

  tests/test_daemon_application.c:2256: ASSERT(sensitive_blocked)
  tests/test_daemon_application.c:2380: ASSERT(sensitive_blocked)

Both set HOME to the cbm_canonical_path spelling of a directory - the
platform's native form, backslashes on Windows - and expect a session
rooted there to be refused for auto-index and for watch. The sensitive-root
and allowed-root containment walk (ws_is_ancestor_or_equal in
src/foundation/workspace.c) compares prefixes byte-exact, so a root that
now read C:/Users/... no longer matched a HOME of C:\Users\...: the
sensitive refusal was lost and $HOME was admitted. Respelling the policy
root was the wrong layer.

This commit keeps the policy roots in their canonical native spelling
(mcp.c and the three application.c sites are back to what main runs) and
folds the separator spelling at the one comparison the original defect
lives in: application_index_args_equal, where the running job's args are
compared with an explicit index_repository request's. The auto-index job
spells repo_path the way the session policy holds it; the handler spells
it with forward slashes. On Windows the two never matched, and the request
was refused as OPTIONS_CONFLICT instead of joining the job already running
for its root. The fold acts on the parsed copies only (yyjson_mut_obj_replace
with a cbm_normalize_path_sep'd string), so nothing the daemon stores
changes spelling and every other option stays exact.

Why this is correct on Windows without a local Windows run: the policy
spelling is untouched, so the containment path is byte-for-byte what main
runs there today; cbm_normalize_path_sep folds backslashes on every
platform, so the new fold is exercised by the new test wherever the suite
runs.

Tests: daemon_session_context_keeps_one_spelling_of_a_root asserted that
the policy respells a root with forward slashes. That was the wrong
contract: the policy's containment checks compare its spelling byte-exact
against HOME and the granted roots, so a respelled root is exactly what
stops matching them. It is replaced by
daemon_session_context_keeps_the_policy_spelling_of_a_root, which binds
the opposite: the policy stores the root as given. The comparison is exposed through
application_internal.h as cbm_daemon_application_index_args_equal_for_test
and bound by daemon_application_index_args_compare_repo_path_separator_equivalently:
a backslash and a forward-slash repo_path compare equal; a different path,
a sub-path, and a different mode still do not.

Refs #1726

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-06 16:23:45 +02:00
Martin Vogeland刘冲 c1b9c451c7 fix(daemon): keep one separator spelling for the session root on Windows
On Windows cbm_canonical_path answers in the platform's native backslash
form, and the daemon stored that spelling verbatim for the client's
session_root and allowed_root (cbm_mcp_server_set_session_context) and for
the auto-index job's repo_path (application.c canonicalizes with the bare
call at three sites). Every tool handler, however, normalizes separators
after canonicalizing. One directory therefore had two names, and the exact
comparison in application_index_args_equal never matched: an explicit
index_repository request for the session root was refused as
OPTIONS_CONFLICT instead of joining the auto-index job already running for
it.

Normalize the separator spelling of both session roots in
set_session_context, and canonicalize plus normalize at the three
application.c sites through one helper, application_canonical_root().
Separators fold on every platform, so the new test
daemon_session_context_keeps_one_spelling_of_a_root binds wherever the
suite runs; it fails on main with "C:\repos\cbm" != "C:/repos/cbm".

Distilled from #1726 with co-author credit.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Co-authored-by: 刘冲 <mail@liuchong.dev>
2026-09-06 02:48:16 +02:00
Martin Vogel b795203025 fix(log): keep the library default at INFO; quiet is a process policy
The lean-output branch had lowered the logger's raw default from INFO to
WARN so allocator setup could not pollute protocol or CLI output before
argv is classified. That moved a process policy into the library: any
process that never applies the role policy — the test runner, an
embedder — silently lost every INFO record, and tests that observe INFO
records through the log sink (the index-format boundary's
`pipeline.route path=format_change_reindex`, the importance pass
counters) went red for a contract that was in fact intact.

Restore the INFO library default and leave the quiet default where the
branch applies it, in cbm_log_init_for_process at process startup: thin
frontends go quiet, detached daemons keep INFO, physical workers require
it. The format-migration route record returns to INFO as on main; the
previous commit's WARN promotion was the wrong layer for this fix.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 07:29:13 +02:00
Martin Vogel c25c16206b fix(pipeline): keep the format-migration route visible at the quiet default
The lean-output branch starts the raw logger at WARN so allocator setup
cannot pollute protocol or CLI output before argv is classified. The
index-format boundary's route record, `pipeline.route
path=format_change_reindex`, was an INFO record, so a process that never
applies the role policy (the test runner, an embedder) and every thin
frontend at its default lost the only log-side evidence that an index was
rebuilt once because its format changed (#1104) — the response's
`format_migration:true` was unaffected. Log the migration route at WARN,
like the sibling `invalid_existing_db` deviation: a once-per-upgrade full
rebuild is exactly what explains a long index and a cursor reset.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 07:17:19 +02:00
Martin Vogel b1100a499a fix(cli): print the cold-start hint as a plain line under --verbose
The hint moved into a structured info record when one-shot CLI output
went quiet by default. Structured values escape spaces, so the record
read `daemon_start` and a person (and the Windows lifecycle guard) could
not find `daemon start` in it. Keep the quiet default and, when --verbose
or CBM_LOG_LEVEL=info opts in, print the same plain stderr hint the CLI
printed before.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:27 +02:00
Martin Vogel 619c683480 fix(mcp): settle the lean-output branch on current main
Fix-forward after rebasing onto main (545 commits, 26 conflicted-file
resolutions). Where main had shipped a behaviour in the meantime, main
wins; the PR keeps its output contract on top of it:

- search_code: the scan runs under main's supervised, deadline- and
  output-bounded runner and feeds the branch's streaming classifier from
  the runner's output file; the POSIX wrappers map grep's no-match to 0,
  so every non-zero exit fails closed (no exit-1 special case).
- trace_path: strategy/confidence precede args in header and rows (#1542),
  in both the tree table and the json legs.
- cypher: relationship expansion is never capped (a cap falsified
  aggregates) and unlabeled MATCH scans every candidate; `truncated` is
  raised only by a real ceiling — a clamped hop range is probed one hop
  beyond the cap and reports truncated only when a candidate exists there.
  The plain BFS and the trail BFS both probe one row past max_results so
  saturation is observable instead of silent.
- search_code full mode and the final json use main's U+FFFD sanitizer
  instead of '?' replacement.
- search_graph json keeps an empty `groups` array for semantic-only calls;
  list_projects json echoes offset/limit next to next_offset and accepts
  main's include_details spelling for the stats projection.
- discovery budget: main added get_file_outline, compare_graphs, manage_adr
  set_sections and the debug/include_details parameters; their descriptions
  are trimmed to the lean style and the ceiling moves from 15 to 18 KiB.

Tests that asserted a JSON default now request format:"json"; the
fail-closed search test uses an unreadable regular file (a missing or
non-regular indexed path is deliberately not a scan operand on main); the
non-regular fixture gives its real sources File nodes, as every indexed
project has; list_projects total counts listable projects (the 0-byte
ghost is never a row, so it is not a page either).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel 7a1e069b29 fix(search): make scoped file patterns portable
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel f4c7d2010d test(watchdog): isolate worker runtime
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel f6afd6138e fix(mcp): drop the tool title accessor with the title field
Tool definitions no longer carry a separate title (the name is the MCP
display fallback), so the accessor main added for the pi adapter has no
field to read. The adapter already used the name when no title existed.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel e93db6a95a test(cli): isolate activation runtime
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>

# Conflicts:
#	tests/test_cli.c
2026-09-05 06:05:08 +02:00
Martin Vogel cc9c2f4c0f fix(search): scope scans to canonical source files
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel 0735fdccb7 fix(mcp): keep compact pagination lossless
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel 4cbc0536ac fix(store): bind pagination to published generations
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel 48149cac08 fix(output): align discovery defaults and help
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel 56516eff2d fix(mcp): propagate fallback discovery failures
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel 26650255ef fix(mcp): scan code search without hit loss
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel daa5dc8247 fix(mcp): make semantic search contracts explicit
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel 7be95be570 fix(output): require token-shaped directory savings
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel 8a8c6839f5 perf(output): score prefix directories efficiently
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel 10af29d50c fix(mcp): preserve malformed raw source bytes
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel f30ae39eb7 fix(mcp): preserve deletion snapshot cursors
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel 5630f11f21 fix(mcp): fail closed on incomplete snapshots
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel 14c229b158 feat(mcp): add lossless semantic pagination
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel a31d2679c1 fix(mcp): make search code pagination lossless
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel 4a9fcd97d0 feat(cli): add explicit quiet output mode
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel 240f252b70 fix(mcp): bind live pagination to snapshots
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel 10ece1bb86 fix(output): preserve non-utf8 identities
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel dbadb736dd chore(output): satisfy final static checks
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel 5f58e233dc fix(output): preserve compact output safety signals
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
Martin Vogel e842fd78cf fix(mcp): preserve lean output continuations
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-05 06:05:08 +02:00
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 ceba42728a Merge pull request #1796 from liuchong/fix/daemon-start-survives-ui-config
fix(cli): a refused UI configuration is not a failed daemon start
2026-09-05 06:02:36 +02:00
Martin Vogel b6e4beb9b1 Merge pull request #1849 from DeusData/fix/issue-1733-windows-hook-wrapper-resume
fix: register Claude hooks without a shell
2026-09-05 05:15:47 +02:00
Martin Vogel a1e1beb242 Merge pull request #1851 from DeusData/fix/issue-391-sparse-absolute-loss
fix(dump): detect sparse absolute persistence loss
2026-09-05 03:23:27 +02:00
Martin Vogel ad479b61e9 Merge branch 'main' into fix/daemon-start-survives-ui-config 2026-09-04 22:51:27 +02:00
Martin Vogel 7d20ff9398 fix: register Claude hooks without a shell
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-04 22:00:37 +02:00
Martin Vogel 20c7dd3dbb Merge branch 'main' into fix/issue-391-sparse-absolute-loss 2026-09-04 21:54:36 +02:00
Martin Vogel 7e47043acc fix(tests): make the lock-registry absolute-deadline fixture deterministic
lock_registry_absolute_deadline_survives_repeated_wakes has failed on the
test-unix (macos-15-intel) leg of three unrelated pull requests in one
week -- #1342 (08-28), #1811 (09-02) and #1819 (09-03, run 33799475629,
job 100823626965) -- always with the same signature:

  FAIL tests/test_lock_registry.c:1153: ASSERT(tail_queued)
  7648 passed / 1 failed

The registry is not racy. cbm_lock_registry_acquire enqueues the waiter
synchronously under the registry mutex before any wait, so waiter_count
and the attempting count are exact. The defect is in the fixture: it
raced two wall-clock windows against each other, both anchored to a
timestamp the observer thread took before the tail thread had even been
scheduled.

  deadline_start = cbm_now_ms();
  tail.deadline_ms = deadline_start + 200;  /* the tail's acquire deadline */
  queue_deadline   = deadline_start + 100;  /* the observer's budget */

Because both windows start before the tail runs, a loaded runner breaks
the fixture two different ways:

  * the tail is scheduled inside its deadline but after the observer's
    100 ms budget has expired -- the queued state existed and was simply
    no longer being looked at; or
  * the tail is scheduled more than 200 ms late, in which case its
    deadline has already passed when it finally calls acquire, the
    pre-registration deadline check in lock_registry_acquire_internal
    returns BUSY immediately, and the tail never enqueues at all -- the
    asserted state can then never occur, however long the observer waits.

lock_registry is in the parallel wave of run-tests-parallel.sh, not the
serial tail, so on a small CI runner it competes with a full wave of
sanitized suites -- exactly the scheduling delay both paths need.

Widening the observer's budget would only paper over the first path, and
the state it waits for is transient by construction: it exists only
between the tail's enqueue and the tail's own deadline. So the fixture is
rebuilt to observe states that cannot evaporate.

  * The tail anchors its absolute deadline itself, in its own thread,
    immediately before the acquire it bounds. Scheduling delay can no
    longer consume the deadline before the call starts, so the enqueue is
    unconditional, and elapsed is measured from the tail's own anchor --
    it now times the registry instead of timing the scheduler.

  * Enqueue is exposed as a monotonic counter,
    cbm_lock_registry_waiter_enqueue_count_for_test, next to the existing
    test_condition_wait_calls counter it is modelled on. Because the
    count only ever grows, the observer reads it once after the tail has
    returned rather than trying to catch a live queue depth: the polling
    loop, and with it the window, is gone.

  * The fixture's remaining 500 ms and 600 ms budgets become the file's
    LOCK_REGISTRY_TEST_TIMEOUT_MS backstop, and each loop exits on the
    state it waits for instead of on the clock, so the backstop only
    fires when the product is actually broken.

The contract is unchanged: the tail must still return at its absolute
deadline (150 <= elapsed < 350 ms for a 200 ms deadline) despite ~40
unrelated cancel broadcasts, still with BUSY and no lease, with the head
still holding the attempt. The broadcast loop now starts immediately
after the tail is released, so it overlaps the tail's wait at least as
much as it did before.

Verification, all on macOS arm64 with the sanitized runner:

  * The mechanism was reproduced locally by delaying only the tail thread
    after its start gate, with production untouched. A 120 ms delay (the
    state exists, outside the observer's budget) and a 250 ms delay (the
    tail never enqueues) each produced exactly one failure,
    ASSERT(tail_queued) -- the CI signature.
  * After the rebuild the same injections are green at 120, 250, 400 and
    900 ms: an arbitrary scheduling delay no longer decides the verdict.
  * lock_registry 30/30 green plain and 30/30 green under four CPU hogs;
    16/16 inside the saturated 18-job parallel wave.
  * private_file_lock, lock_registry, daemon, project_lock and the
    daemon_* suites: 222 passed, 1 skipped.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-04 15:42:15 +02:00
Martin Vogel 13b7d06701 Merge pull request #2047 from DeusData/fix/2046-cohort-conflict-retry-until-deadline
fix(daemon): retry a mismatched cohort holder until the caller's deadline
2026-09-04 15:35:39 +02:00
Martin Vogel 8f64c36d85 Merge pull request #1918 from CaptainMittens/fix/return-star-respects-with-scope
fix(cypher): RETURN * must read the live scope, not the query pattern
2026-09-04 12:27:44 +02:00
Martin Vogel 54a434743e Merge pull request #1880 from CaptainMittens/fix/env-int-parse-strict
fix(env): stop an unreadable environment setting from picking a wrong default
2026-09-04 12:27:18 +02:00
Martin Vogel 3bbabf187c Merge pull request #1811 from pcristin/fix/issue-1682-dotted-ts-imports
fix(pipeline): preserve dotted TypeScript import basenames
2026-09-04 09:45:57 +02:00
Martin Vogel 9ed01a478c Merge pull request #2024 from DeusData/feat/cshtml-razor-pages
feat(extract): discover Razor Pages / MVC views (.cshtml) and route their @page
2026-09-04 09:45:53 +02:00
Martin Vogel 91a1723175 Merge pull request #1897 from CaptainMittens/fix/resolve-receiver-chain-mismatch
fix(registry): refuse a name-only match the receiver chain contradicts
2026-09-04 09:45:49 +02:00
Martin Vogel 9104feb67f fix(daemon): retry a mismatched cohort holder until the caller's deadline
A participant admitted to the version cohort holds the cohort lifetime lock
SH until its lease is released at exit. An internal daemon that has just
lost its last stdio client keeps that lock through its teardown, so a local
CLI (`config get`, `index_status`, ...) arriving in that few-hundred-ms
window met a holder whose cache root differed and was refused immediately
with "active account daemon uses a different cache directory" — a pure
lifecycle race, previously masked by the slow scalar self-hash and exposed
as soon as the hash got fast (#1767 pr-smoke on macOS).

cbm_version_cohort_acquire now retries a CONFLICT until the caller's finite
deadline_ms, holding no guard between attempts, exactly as host.c already
waits out the same handoff for the daemon claim marker. Every production
caller passes a finite deadline; UINT64_MAX keeps failing fast so a genuine
conflict against a long-lived peer is never waited on indefinitely.

Tests (deterministic, no timing assertions on transient windows): one proves
the retry runs until the deadline and a UINT64_MAX caller still fails
immediately; one proves a waiter that met the mismatched holder is admitted
the moment the holder releases. Both fail with the retry removed.

Fixes #2046

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-04 09:41:30 +02:00
Martin Vogel f47cd47afe Merge branch 'main' into fix/issue-1682-dotted-ts-imports 2026-09-03 21:58:07 +02:00
Martin Vogel 380b1c5af1 Merge branch 'main' into feat/cshtml-razor-pages 2026-09-03 21:58:04 +02:00
Martin Vogel 429141261d Merge branch 'main' into fix/resolve-receiver-chain-mismatch 2026-09-03 21:57:58 +02:00
Martin Vogel 4d4af28672 Merge branch 'main' into fix/span-duration-unreadable-timestamps 2026-09-03 21:57:55 +02:00
Martin Vogel e63292a8d4 Merge branch 'main' into fix/env-int-parse-strict 2026-09-03 21:57:52 +02:00