Commit Graph

2093 Commits

Author SHA1 Message Date
Martin Vogel 579a910886 fix(subprocess): spawn instead of fork+exec on macOS
fork() duplicates the parent's address-space bookkeeping. Under an
ASan-instrumented parent that includes an enormous shadow mapping, and past a
footprint threshold the child is jetsam-killed BEFORE exec replaces the image.
The reaped corpse then looks like the launched tool dying, so a `git init` in a
fixture "fails" only once enough suites have run ahead of it — a spawn defect
that reads as an unrelated assertion. tests/test_daemon_runtime.c already
switched to posix_spawn for this exact reason; this moves the shared
subprocess layer over as well.

posix_spawn never copies the parent address space, so the parent's footprint
stops being a variable. Every guarantee of the fork path is carried over:

  - own process group        SETPGROUP + setpgroup(0)   (kill-tree contract)
  - default signal handling  SETSIGDEF + SETSIGMASK
  - std{in,out,err} wiring   adddup2
  - all other fds closed     CLOEXEC_DEFAULT, Apple's equivalent of the
                             child's close-everything loop (the three dup2'd
                             fds survive, since dup2 clears close-on-exec)
  - PATH lookup              posix_spawnp keeps execvp semantics

One deliberate difference is reconciled rather than adopted: posix_spawn
reports an unusable binary to the PARENT, where fork+exec instead yields a
child that exits 127. cbm_subprocess_run's contract treats spawn_failed as
"the spawn mechanism failed", not "the tool was missing", so exec-class errors
fall back to fork+exec and macOS keeps classifying a bogus binary exactly as
Linux does. That path forks a child that exits immediately, so it does not
reintroduce the hazard.

The existing suite binds each guarantee, verified by breaking them:
dropping CLOEXEC_DEFAULT leaks the sentinel descriptor and reddens
subprocess_posix_child_closes_unrelated_descriptors; dropping SETPGROUP makes
the kill-tree turn on the runner's own group and reddens the suite wholesale.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-06 17:25:24 +02:00
Martin Vogel 0d6f26a170 Merge pull request #1464 from DeusData/fix/bounds-and-workspace-roots
fix: tighten buffer, recursion and indexing-root bounds
2026-08-06 04:44:25 +02:00
Martin Vogel 353bbb918d Merge pull request #1465 from DeusData/feat/ci-run-isolation
fix(ci): isolate local-CI runs per run id, and tidy what each run creates
2026-08-06 04:23:45 +02:00
Martin Vogel 52303157bf fix(test): guard the POSIX-only includes in the gitignore test
sys/wait.h does not exist on the Windows toolchain, so the unguarded include
broke the Windows test build outright — the suites never ran. The test body is
already skipped on Windows, so the includes belong behind the same guard, as
test_cypher.c does for the same pair.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-06 03:30:32 +02:00
Martin Vogel 75ac22781d test(contract): assert the sync HEAD-comparison property, not one spelling
The Windows bundle contract pinned the literal
  remote_head="$(vm clangarm64 "cd /c/cbm && git rev-parse --verify HEAD")"
so parametrising the checkout path for per-run isolation failed it, even
though the property it guards - capture the remote HEAD into a local variable
and compare it HERE, never nesting quotes through cmd.exe - still holds.

Match the shape with the path as \S+ instead. Verified the contract still
binds: replacing the capture with a constant fails it for the right reason,
restoring it passes.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-06 02:56:55 +02:00
Martin Vogel ba5724319c fix(ci): accept the documented test-amd64 leg spelling
run.sh --help advertises 'amd64|test-amd64|tsan-amd64', but only 'amd64'
was dispatched, so the documented spelling failed with
"unknown leg 'test-amd64'". Accept both.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-06 02:54:23 +02:00
Martin Vogel 3e06362160 fix(ci): isolated VM checkout inherits the base's real origin URL
`git clone --local` points origin at the base PATH, so a later
`git fetch origin <branch>` inside the isolated tree would resolve against
whatever the shared base happens to hold - i.e. another session's state, the
exact coupling the per-run checkout exists to remove. Inherit the base's
origin URL at clone time so the isolated tree fetches from GitHub itself.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-06 02:12:01 +02:00
Martin Vogel cad2b02cee fix(ci): give the Windows VM leg a per-run checkout and a HEAD assertion
The VM's last piece of shared state was the working tree itself. /c/cbm is
ONE checkout and update/sync REPLACE it (git reset --hard + clean -fdx), so a
second session syncing its branch swaps the code under a running leg.

Observed 2026-08-06: a gate run on main compiled tests/test_workspace.c and
src/foundation/workspace.c - files that do not exist in main - and died on
'sys/wait.h' not found. The VM was sitting at another session's commit
7fa5b07. That is worse than a red: a confident verdict about the wrong tree.

- CBM_CI_RUN_ID gives the run its own checkout, /c/cbm-run-<id>, created with
  a LOCAL clone so .git/objects are hardlinked (seconds, near-zero disk).
  Unset keeps the historical single-tree behaviour byte-for-byte.
- CBM_VM_EXPECT_HEAD=<sha> asserts the tree is the intended one BEFORE and
  AFTER the suites, because the swap can land mid-flight.
- win.sh drop-run-checkout removes THIS run's tree (refuses in shared mode -
  a run may only delete what it created; another session's tree is never ours
  to remove).

Verified: bash -n and shellcheck -S error clean.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-06 02:09:21 +02:00
Martin Vogel c5f5c91b4a Merge remote-tracking branch 'origin/main' into feat/ci-run-isolation 2026-08-06 02:07:00 +02:00
Martin Vogel c6478790d8 fix(foundation): match Windows system trees by first component only
"Users" was matched the way credential directory names are — against every
path component. On Windows every user's files live under C:\Users\<name>, so
that refused every ordinary project path, including a CI runner's own
workspace. The Windows smoke job on the pull request caught it; the unit
tests could not, because they only ever run compiled on macOS here.

System trees (Windows, ProgramData, Program Files) now match only as the
first component below the drive, which still refuses anything inside them at
any depth. "Users" is handled separately as the tree root only, mirroring
POSIX where "/Users" is refused but "/Users/dev/projects" is not.

Tests cover the regression directly: C:/Users/dev/projects/app and
C:/Users/runneradmin/work/repo are allowed, C:/Users is not, C:/Windows/System32
is refused at depth, and C:/dev/Windows-app — merely named after a system
tree — is allowed.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-06 01:58:22 +02:00
Martin Vogel 9b9b6ff5ad Merge pull request #1463 from DeusData/fix/test-binary-builddir
fix(tests): resolve the prod binary from CBM_TEST_BINARY, not a hardcoded build/c
2026-08-06 01:55:01 +02:00
Martin Vogel 5444da4166 fix(ci): isolate local-CI runs per run id, and tidy what each run creates
Concurrent local-CI legs clobbered each other. Container names were already
unique per `compose run --rm`, but the mutable state was not:

- Docker: `cbm-build` is ONE named volume shared by every service and every
  concurrent run, so two legs wrote the same /src/build - objects and
  test-logs included. The parallel scheduler then dies reading a suite log
  another run replaced ('cannot read suite log .../extraction.log').
- Windows VM: one checkout (/c/cbm) plus a FIXED log path (/tmp/win-test.log)
  and a shared build dir; -PruneStale could also delete a live run's temp root.

Each run now carries a unique id (pid+epoch, overridable via CBM_CI_RUN_ID):

- docker-compose: the build volume takes its name from CBM_CI_BUILD_VOLUME,
  defaulting to today's `cbm-build` so the single-run path is unchanged.
- run.sh: derives the id, points the build volume at it, and removes that
  volume on success. CBM_CI_SHARED_BUILD=1 opts back into the shared volume.
- vm-run-tests.sh: per-run log; and when a CALLER sets CBM_CI_RUN_ID (i.e.
  declares concurrency) a per-run BUILD_DIR too, removed on success.

ccache and the fixture cache stay SHARED deliberately - ccache is
concurrency-safe and content-verified, and sharing them is what keeps an
isolated run fast instead of cold.

Cleanup follows one rule: a run tidies what it created, EXCEPT when it failed -
then the artifacts are the post-mortem and the run prints how to inspect and
drop them. CBM_CI_KEEP=1 keeps them regardless.

Verified: bash -n on both scripts; `docker compose config` valid, and with
CBM_CI_BUILD_VOLUME set the volume resolves to the per-run name.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-06 01:45:56 +02:00
Martin Vogel 7fa5b07718 feat: enforce one workspace boundary on every indexing entry point
Routes the MCP index_repository handler and the graph UI's POST /api/index
through a single decision function. The UI route previously checked only that
root_path was a directory, so an operator's configured boundary held on one
entry point and not the other; it now canonicalizes first and applies the same
policy, answering 403 with the reason.

The decision is two-tier, because a bare default-deny would refuse every
first run and a bare opt-in leaves the default open:

- Breadth is always enforced, with nothing configured. Filesystem, drive and
  share roots, top-level system trees, the home directory itself and
  credential directories are refused as indexing roots out of the box.
- Containment in a declared root applies once CBM_ALLOWED_ROOT is set or a
  grant exists, and is evaluated first so a path outside a configured root is
  reported as exactly that.

Three things the tests caught, each a real defect rather than a test fix:

- On macOS /etc, /tmp and /var are firmlinked under /private, so
  canonicalizing "/etc" yields "/private/etc" and counted two deep — sailing
  past a minimum of two, missing the very path being guarded. Depth now
  discounts a leading "private" component.
- An earlier draft refused any root containing the cache directory. That was
  over-claimed: the indexer only parses recognised source files and a graph
  database is binary SQLite it would never extract. Refusing a whole root is
  also the wrong remedy where the concern does hold — not walking the cache
  is. Removed, with the reasoning recorded at the site.
- Rewording the refusal to "outside every allowed root" broke an assertion
  matching "outside the allowed root", and that test's early return skipped
  its CBM_ALLOWED_ROOT cleanup, leaking the variable into every later test in
  the suite. The original wording is kept and guidance appended instead.
  Worth remembering: these contracts match strings, not properties.

Docs updated in the same change, since both env-var tables said "unset
imposes no restriction" and that is no longer true: CONFIGURATION.md and
README.md describe the two tiers, and CONFIGURATION.md lists the always-
refused roots along with the two limits that matter — this constrains scope
rather than sensitivity, and the credential list is a denylist that raises the
cost of a mistake rather than closing the class. SECURITY.md's supported-
versions table was still on 0.8.x and now reads 0.9.x.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-06 01:02:02 +02:00
Martin Vogel ff4cc170ea fix(tests): resolve the prod binary from CBM_TEST_BINARY, not a hardcoded build/c
The Linux legs of the local 3-OS ladder build into build/linux-arm64 /
build/linux-amd64, not build/c, so Step 5c died with
'missing binary: /src/build/c/codebase-memory-mcp' and took the whole
leg down. Steps 5 and 5b already pass CBM_TEST_BINARY (derived from
$BUILD_DIR) to their scripts; 5c did not, and its test hardcoded the
path.

CI never caught this because every CI leg uses the default BUILD_DIR of
build/c - the container legs are the only ones that differ, which is
precisely what the local ladder is for.

- test_worker_error_response.sh honours ${CBM_TEST_BINARY:-build/c/...}
  like its sibling watchdog tests; the default keeps bare manual runs working
- scripts/test.sh passes CBM_TEST_BINARY to Step 5c, matching 5 and 5b
- test_hook_conflict_notice.sh carried the identical hardcoding and is
  fixed the same way (local-only today, but wrong is wrong)

Verified: with the fix the test passes against an out-of-tree binary;
reverting the fix reproduces the ladder's exact failure (rc=2,
'missing binary: <worktree>/build/c/codebase-memory-mcp').

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-06 00:33:34 +02:00
Martin Vogel 03e935576b feat(foundation): classify indexing roots by breadth and sensitivity
Adds cbm_workspace_classify_root, the breadth half of the workspace
boundary. It answers "is this path obviously too broad or too sensitive to
index as one root", not "is this caller allowed to index it" — that second
question needs the user-level grant store, since it is the only input a
caller cannot write. The header says so, because a reader who mistakes one
for the other would draw the wrong conclusion about what is enforced.

Three rules, each doing what the others cannot:

- Depth below the volume. Two components on POSIX refuses every top-level
  tree in one rule with no list to maintain. Windows and UNC count
  drive/share-relative and require one, because there the first component is
  already user space ("D:/repos") — the system trees that sit at the same
  depth ("C:/Windows") are covered by name instead, which is tractable
  because that set is small and stable.
- The home directory itself, which is two deep on macOS and Linux and so is
  invisible to depth.
- Credential directory names, matched against every component so both
  "…/.ssh" and "…/.ssh/keys" are refused. This list is deliberately additive
  so extending it needs no design authority.

A path that is or contains the cache directory is refused outright: indexing
such a tree would pull every other project's graph database into this
project's index.

Classification order is load-bearing and commented as such. The home
directory normally contains the cache directory, so testing the cache first
reported every home as "holds the cache" and made it non-overridable, which
contradicts the intent that a person may override it. Depth precedes the
cache test for the same reason: "/Users" satisfies both and "too broad" is
the reason that helps the reader.

Takes home and cache as parameters rather than reading the environment, so
the policy is a pure function and the tests need no filesystem. Twelve table
tests; three of them failed on the first implementation and drove the
ordering fix above.

Not yet wired to any caller — deliberately, so the policy can be reviewed on
its own.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-06 00:11:37 +02:00
Martin Vogel 886a8856a4 fix: bound response buffers, parse depth and glob matching
A pass over places where a bound was either miscalculated or missing.

UI response builders (src/ui/http_server.c) mixed the clamping helper
http_appendf with raw cursor indexing. Route the per-line separators and
quotes through the helper too, so the cursor cannot leave the buffer once
output saturates it, and terminate explicitly afterwards — the helper pins
the cursor and then writes nothing, which would otherwise leave the "%s"
reply with no terminator in range. The log buffer was budgeted at
LOG_LINE_MAX + 10 per line while JSON escaping can double every byte, so
size it for the escaped worst case. Job status entries now escape their path
and error fields, which were interpolated raw.

Cypher (src/cypher/cypher.c): the WHERE grammar descends once per nested '('
and once per NOT, so parse depth followed the query text rather than
anything bounded. Track depth on parser_t and refuse past 256 levels with a
parse error. Bounding parse depth also bounds the resulting tree, so the
recursive evaluator inherits the limit.

Gitignore (src/discover/gitignore.c): '**' retries the remainder at every
position and consecutive groups multiply, so match cost is exponential in
the number of groups. Thread a step budget through the matcher and give up
past 20000 steps, reporting no-match so a pathological pattern fails to
ignore rather than ignoring the wrong files. glob_match keeps its name so
recursion_whitelist.h still describes the functions that recurse; the new
non-recursive wrapper that seeds the budget is glob_match_bounded.

Call extraction (internal/cbm/extract_calls.c): extract_fp_callee recursed
once per applied argument, so stack use followed the parse-tree depth of the
indexed file. Rewritten as a left-spine loop. Reassigning node/nk before
continuing leaves the fall-through cases where the recursive form left them,
so behaviour is unchanged and the extraction suite is untouched.

CALLS props (src/pipeline/pass_calls.c): `cap - pos - PAIR_LEN` wraps once
pos reaches cap - PAIR_LEN and stops bounding the copy below. Guard
additively, matching the closing write in the same function.

Shell-bound paths (src/foundation/str_util.*): the three git shell-out sites
each wrap a repo path in cmd.exe-compatible double quotes, where %VAR%,
!VAR! and ^ remain active. Two carried private copies of the check that
rejects those three on Windows and the third used the bare validator.
Promote the stricter form to cbm_validate_shell_path_arg and route all three
through it, so the copies cannot drift apart again.

Tests cover the buffer bounds, the parse-depth refusal, matcher termination
and the validator. The bounds tests run in a forked child so a violation
surfaces as a signal instead of silently.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-05 23:27:48 +02:00
Martin Vogel eb3e5711fd Merge pull request #1441 from DeusData/fix/1388-hook-conflict-notice
fix(hook): surface daemon build conflicts to the hook caller (#1388)
2026-08-05 18:44:32 +02:00
Martin Vogel 856ec69f84 Merge remote-tracking branch 'origin/main' into fix/1388-hook-conflict-notice
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>

# Conflicts:
#	tests/test_cli.c
2026-08-05 17:18:17 +02:00
Martin Vogel d5eb22df0c ci: re-trigger PR validation
GitHub dropped the pull_request synchronize dispatch for ea466487: the
commit's github-actions check-suite closed as complete carrying only the
DCO check, so the PR workflow never ran and its required ci-ok context
was never created. Close/reopen did not re-dispatch it and check-suite
rerequest is not permitted for that suite, so an empty commit is the
remaining way to produce the events. No code change.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-05 17:05:22 +02:00
Martin Vogel ad96bf34b5 Merge pull request #1440 from DeusData/ci/codeql-4.37.4
ci(codeql): bump init and analyze together to v4.37.4
2026-08-05 16:14:49 +02:00
Martin Vogel ea466487e8 test(hook): bind the conflict notice with a portable reproduce-first test
The end-to-end shell regression could not run on CI (recorded in the
previous commit), which left the fix without a gating test. Move the
notice DECISION out of main.c - which is not linked into the test
runner, and is why no C test could reach it - into src/cli/
hook_augment.c, which is. main.c now calls the shared API for both the
absent-daemon and build-conflict cases instead of carrying its own
copies of the strings, so the test binds the real production path
rather than a parallel one.

cli_hook_conflict_emits_stdout_notice_issue1388 asserts what the bug
actually broke: a build conflict yields a stdout systemMessage naming a
different build and pointing at 'daemon stop'; the absent-daemon notice
stays distinct and keeps pointing at 'daemon start' (which cannot heal
a conflict); and non-Claude dialects receive no bare stdout JSON, whose
channel it would corrupt.

RED-on-revert verified: restoring the pre-fix behaviour (conflict emits
nothing on stdout) fails this test alone, for the right reason
('conflict is NULL'). cli suite 258 passed, the local end-to-end shell
test still green against a seam-bearing binary, lint-ci clean.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-05 15:56:18 +02:00
Martin Vogel 0e0e481b67 test(hook): stop gating on the conflict-notice regression, documented
The regression is green locally against a seam-bearing binary but never
raises the forced conflict on ANY CI leg, so it was reddening six legs
for a reason unrelated to the fix under test. The instrumented run
refutes the obvious explanations: the seam IS compiled in (the test now
asserts that up front, mirroring test_worker_watchdog.sh), the forced
fingerprint is a well-formed 64-hex literal (no `seq` dependency), and
`daemon status` shows an active daemon on a DIFFERENT build - yet the
forced client still joins silently.

Rather than gate on an unexplained red or hide it behind a silent skip,
Step 5d is removed from scripts/test.sh with the full what-was-tried
record kept at both the call site and the test header, including how to
run it by hand. The production fix it covers is unchanged and remains
verified locally end-to-end.

Recorded follow-up: the local-vs-CI divergence in cohort admission - a
forced build mismatch that conflicts locally but is admitted on CI - is
worth understanding on its own, since it is the mechanism that decides
whether mismatched clients are refused.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-05 15:11:22 +02:00
Martin Vogel e0d229fc65 test(hook): assert the seam precondition and self-diagnose CI failures
The conflict regression went red on six CI legs with an unhelpful
message: every probe produced empty stderr, which reads as "the notice
is missing" when it actually means no conflict was ever raised. Three
changes, all diagnosis rather than tolerance:

- Assert the binary carries the hook-client seam up front, failing with
  the rebuild hint, exactly as tests/test_worker_watchdog.sh does for
  the crash-orphan probe. A seam-less binary can never raise the
  conflict, and that must be said plainly rather than inferred from a
  silent backstop expiry.
- Drop the `seq`-derived fingerprint for a literal 64-char constant
  (length-checked), removing a dependency on a tool whose presence
  varies across the MSYS2/macOS/Linux legs.
- On backstop expiry, dump stdout, the forced fingerprint and its
  length, and daemon status, so the next run names the cause instead of
  requiring another round trip.

Still green locally (seam-bearing binary): the conflict is observed and
the systemMessage asserted.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-05 13:14:14 +02:00
Martin Vogel 4bbe0984f7 Merge pull request #1436 from DeusData/fix/1334-multikey-orderby-limit
fix(cypher): parse and apply the full multi-key ORDER BY list, keeping LIMIT (#1334)
2026-08-05 04:57:05 +02:00
Martin Vogel c9bbd880c7 Merge pull request #1449 from DeusData/fix/1416-activation-refusal-attribution
fix(cli): attribute activation refusals to the recorded safety check (#1416)
2026-08-05 03:28:32 +02:00
Martin Vogel 24bee5703f Merge pull request #1437 from DeusData/fix/1425-no-corrupt-litter-on-invalid-name
fix(mcp): stop quarantining an anonymous temp db on invalid project names (#1425)
2026-08-04 23:59:10 +02:00
Martin Vogel 5da4698640 Merge pull request #1439 from DeusData/fix/1383-image-rejection-response
fix(daemon): answer the client when its binary image is rejected (#1383)
2026-08-04 23:43:00 +02:00
Martin Vogel 92e5983e50 test(hook): wait for the daemon cohort before asserting the conflict notice
The daemon's cohort join completes asynchronously after 'daemon start'
returns; on slower CI hosts the single probe landed pre-cohort, never
observed the conflict, and the seam guard failed the leg. Poll for the
asserted state (a probe observing the build conflict) with a bounded
backstop, clearing the throttled notice marker before each probe so the
stdout assertion holds on whichever probe first sees the conflict.
Daemon-start failures and backstop exhaustion now dump diagnostics.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-04 21:48:24 +02:00
Martin Vogel d12924641e fix(cypher): free complete return-clause state on parse-error paths
The over-cap ORDER BY rejection path (and the two sibling error paths
in parse_return_or_with) freed the items array but not the strings
inside it - ASan flagged 7 leaked bytes on the diag and Linux legs the
moment the new over-cap test exercised the path. Route all three error
paths through free_return_clause(), which already releases item
strings, CASE expressions, args and the ORDER BY key list.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-04 21:45:04 +02:00
Martin Vogel 2044c5c657 fix(cli): attribute activation refusals to the recorded safety check (#1416)
Windows users hit 'active CBM sessions and operations could not be
stopped safely; no activation was committed' from install, uninstall
and doctor with ZERO CBM processes running - they rebooted, killed
phantom handles, and deleted runtime folders chasing sessions that did
not exist. The real failure was the activation transaction's Windows
ACL safety check refusing a directory carrying cross-account mutation
grants (commonly the stock Authenticated Users:(M) inheritance from a
drive root - reproduced on the Windows VM with a plain mkdir under C:\
and with an icacls-granted parent), and the refusal detail was recorded
internally but never shown.

cli_activation_diagnostic now prefixes the recorded refusal note - the
predicate, SID and path - plus one remediation line (remove the flagged
grant or use an owner-private directory). The sessions wording remains
for genuine stop/reservation failures, which record no note.

Adds a CBM_ENABLE_TEST_SEAMS setter for the refusal note so the
attribution is testable portably; the regression test asserts the note
reaches the diagnostic (and that the no-note path keeps the sessions
text). RED before the fix, GREEN after, RED again on revert. Verified
end-to-end on the Windows VM: uninstall/doctor against an ACL-tainted
tree now name the ACL check instead of sessions.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-04 21:35:24 +02:00
Martin Vogel 4203752683 Merge pull request #1435 from DeusData/fix/1387-install-preserves-hooks
fix(install): never remove existing hook entries on a refused script rewrite (#1387)
2026-08-04 20:06:22 +02:00
Martin Vogel 716f1d683c fix(hook): surface daemon build conflicts to the hook caller (#1388)
A hook client that could not join because the active daemon runs a
DIFFERENT build reported the conflict on stderr only - and stdout is
the only hook channel Claude Code surfaces, so in-session the result
was eternal silent skips. Worse, the connect-failure path classified a
conflicted daemon as ABSENT and suggested 'daemon start', which cannot
heal a build conflict. This is the rc.1-over-0.9.0 experience in
#1388/#1335: install the new build, the old warm daemon keeps running,
and every hook goes quiet.

Both failure sites now emit a throttled stdout systemMessage naming the
real state (active daemon runs a different build) with the actionable
step ('daemon stop', next command starts a matching daemon). The
version-cohort conflict path keeps its unconditional stderr detail; the
connect-conflict path now prints the formatted conflict to stderr too.

Test infrastructure: a CBM_ENABLE_TEST_SEAMS-only env override
(CBM_TEST_HOOK_CLIENT_BUILD) lets one binary present a foreign build
fingerprint as a hook client, so the conflict is reproducible without a
second build. tests/test_hook_conflict_notice.sh (scripts/test.sh step
5d, on the step-5 TEST_SEAMS binary) asserts fail-open exit 0 + the
stderr conflict + the stdout systemMessage with restart guidance. RED
before the fix (empty stdout), GREEN after, RED again on revert.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-04 19:37:57 +02:00
Martin Vogel 88ed09838f ci(codeql): bump init and analyze together to v4.37.4
Same treatment as v4.37.3 (#1434): dependabot splits the codeql-action
bump into init-only and analyze-only PRs (#1398, #1399 after their
rebase to 4.37.4), but the two actions must run at the same version, so
each split PR fails its own analyze job. SHA verified against the
upstream v4.37.4 tag.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-04 19:17:29 +02:00
Martin Vogel ff891d066a Merge pull request #1397 from DeusData/dependabot/github_actions/actions/setup-python-7.0.0
build(deps): bump actions/setup-python from 6.3.0 to 7.0.0
2026-08-04 19:16:36 +02:00
Martin Vogel 7982a8881b Merge pull request #1396 from DeusData/dependabot/github_actions/softprops/action-gh-release-3.0.2
build(deps): bump softprops/action-gh-release from 3.0.1 to 3.0.2
2026-08-04 19:16:32 +02:00
Martin Vogel 2dd296bab4 fix(daemon): answer the client when its binary image is rejected (#1383)
An image-verification rejection logged daemon.client_image_rejected and
finished the worker WITHOUT sending a hello response - the only
admission-failure path that never answered the peer. The client
reported status "pending" indefinitely, indistinguishable from a slow
cold start, with the reason visible only in the daemon log.

The rejection now sends a CONNECT_REJECTED hello response carrying the
reason (fingerprint_mismatch / image_unverifiable) and an actionable
message, matching the version-conflict and capacity paths. The
version-conflict path already responds to unverified peers, so this
discloses nothing new to a same-uid local peer; admission stays
rejected either way.

Adds a CBM_ENABLE_TEST_SEAMS-only seam forcing peer-image verification
to fail, since the in-process harness's peer pid is OS-authenticated
socket credentials and always verifies against the service's own
active image.

Test: daemon_runtime_image_rejection_reaches_client_issue1383 - the
client receives CONNECT_REJECTED with the reason in the message. RED
without the response block (client times out), GREEN with it.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-04 19:15:11 +02:00
Martin Vogel c872962623 Merge pull request #1434 from DeusData/ci/codeql-4.37.3
ci(codeql): bump init and analyze together to v4.37.3
2026-08-04 18:48:55 +02:00
Martin Vogel 99dfc19c86 fix(mcp): stop quarantining an anonymous temp db on invalid project names (#1425)
A project name failing cbm_validate_project_name made project_db_path()
return an empty path, which cbm_store_open_path_query passed to SQLite -
and SQLite opens "" as an anonymous temp database. The healthy temp db
then failed the integrity check (no projects table) and
quarantine_corrupt_store rendered ".corrupt.<hex>" from the empty
prefix: a RELATIVE path, dropped as a 4 KB file into whatever directory
the daemon was started from, on every such query. The caller only ever
saw a clean 'project not found', so nothing pointed at the litter.

Two guards, per the reporter's analysis: resolve_store_internal skips
the direct open on an empty path and falls through to the existing
fallback scan (which can still resolve legacy dbs whose internal name
predates validation), and quarantine_corrupt_store refuses an empty
path outright (belt-and-braces - nothing at such a path is worth
quarantining).

Regression test: tools/call search_graph with project "bad name" from
a temp cwd asserts the clean not-found error AND that no .corrupt.*
file appears in the cwd. RED before (litter created), GREEN after,
RED again on revert.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-04 18:44:21 +02:00
Martin Vogel 53ed2c0ff2 Merge pull request #1433 from DeusData/test/1234-java-interface-enum-dedup
test(extract): bind the Java interface/enum method dedup fix (#1234)
2026-08-04 18:12:17 +02:00
Martin Vogel 0a0ef516d6 fix(cypher): parse and apply the full multi-key ORDER BY list, keeping LIMIT (#1334)
The Cypher request model stored a single order_by expression; on
ORDER BY key1 DESC, key2 ASC LIMIT n the parser consumed only key1,
left ', key2 ... LIMIT n' unparsed, and the query silently returned the
entire result set (6326 rows / 117 KB instead of 5 on the reporter's
graph - a token flood straight into agent context).

The return clause now models up to CBM_CYPHER_ORDER_KEYS_MAX (8) sort
keys with per-key direction (Cypher semantics). Both sort sites -
rb_apply_order_by for RETURN and sort_bindings for the WITH pipeline -
compare key-by-key with later keys breaking ties. More keys than the
modeled maximum is a loud parse error, never a silently dropped
remainder. The no-ORDER-BY projection fast path is preserved via
order_key_count == 0.

Tests: parse-level (2 keys, per-key direction, LIMIT consumed;
9-key over-cap rejected), exec-level RETURN (limit kept, tiebreak
ordering, per-key direction), and exec-level WITH pipeline (limit kept).
All RED before the fix (row_count 4 instead of 2), GREEN after.

Recorded, pre-existing and unchanged: an ORDER BY key that is not part
of the projection is silently skipped (single-key main behaves the
same); sorting by unprojected properties needs hidden-column projection
and is a separate issue.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-04 18:09:33 +02:00
Martin Vogel 55c2994b08 fix(install): never remove existing hook entries on a refused script rewrite (#1387)
Installing 0.9.1-rc.1 over an existing setup deleted the user's working
Claude Code hook entries: when the on-disk hook script is not byte-owned
(a manual install embedding another binary path, or a user-modified
reminder), cbm_write_owned_hook_script_with_legacy correctly refuses the
rewrite (TEXT_UNOWNED) - but the failure branch then called
cbm_remove_claude_hooks / cbm_remove_session_hooks /
cbm_remove_claude_subagent_hooks, destroying the registrations for
scripts that still exist and work. The reporter lost their PreToolUse
Grep|Glob entry and all four SessionStart entries.

A refused script write now records the error and leaves settings.json
untouched; entry removal belongs to uninstall only (which keeps its own
removal path, cli.c:9622).

Regression test builds the reporter's state - unowned gate script with a
foreign BIN path, user-modified session reminder, 0.9.0-form settings
entries plus a foreign Bash hook - runs the full install, and asserts
every pre-existing entry and both script bodies survive byte-identically
while the failure is still reported. RED before this fix (entries
deleted), GREEN after, RED again on revert.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-04 17:54:20 +02:00
Martin Vogel 181eafba9a ci(codeql): bump init and analyze together to v4.37.3
Dependabot split the codeql-action bump into two PRs (#1398 init,
#1399 analyze), but init and analyze must run the same action version:
each PR alone fails its own analyze job with "Loaded a configuration
file for version '4.37.3', but running version '4.36.2'". Bump both
pins in one change instead. SHA verified against the upstream v4.37.3
tag.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-04 17:16:51 +02:00
Martin Vogel ae5e9e4ffa Merge pull request #1395 from DeusData/dependabot/github_actions/actions/checkout-7.0.1
build(deps): bump actions/checkout from 7.0.0 to 7.0.1
2026-08-04 17:15:18 +02:00
Martin Vogel 2cf4235862 Merge pull request #1424 from ahmadgamal15-art/fix/windows-stdio-binary-mode
fix(mcp): set stdio to binary mode on Windows
2026-08-04 17:11:05 +02:00
Martin Vogel 6a5f850fe6 Merge pull request #1369 from WarGloom/agent/fix-worker-error-transport
fix(worker): preserve delivered MCP errors
2026-08-04 17:10:58 +02:00
Martin Vogel e512c80e58 Merge pull request #1408 from dergachoff/fix/codex-agent-toml-transport
fix(install): declare a transport in generated Codex agent role TOMLs
2026-08-04 17:10:45 +02:00
Martin Vogel 5eae0a4371 test(extract): bind the Java interface/enum method dedup fix (#1234)
Regression coverage for #1234: Java interface and enum methods were
emitted as both a Method node and a duplicate top-level Function node.
The production fix landed via 87a0e3f (language-agnostic class-body
routing); this adds the missing test coverage so it cannot silently
regress:

- java_interface_no_duplicate_function_issue1234: interface methods
  produce Method nodes only, zero Function nodes
- java_enum_dedup_preserves_calls_issue1234: enum methods dedup'd while
  cross-class CALLS edges survive
- cp_interface_method_no_dup_function: convergence probe asserting the
  surviving Method still carries its CALLS edge end-to-end

Distilled from PR #1327.

Co-Authored-By: Harshita Joshi <j.harshitaa06@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-08-04 16:37:06 +02:00
Martin Vogel b6fe834eb5 Merge pull request #1368 from WarGloom/agent/fix-empty-similarity-sort
fix(similarity): skip empty fingerprint sort
2026-08-04 16:29:40 +02:00
Martin Vogel 66e9aec28d Merge pull request #1392 from DeusData/feat/generated-client-adapters
feat(install): generate pi and OpenCode extensions from the tool registry
2026-08-04 13:52:09 +02:00
dependabot[bot] e0bf963910 build(deps): bump actions/checkout from 7.0.0 to 7.0.1
Bumps [actions/checkout](https://github.com/actions/checkout) from 7.0.0 to 7.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0...3d3c42e5aac5ba805825da76410c181273ba90b1)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 7.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-08-04 09:58:51 +00:00