Commit Graph
1642 Commits
Author SHA1 Message Date
Martin Vogel dc735c01fb fix(install): migrate v0.10.8 OpenCode agent profiles on upgrade (#2264)
Upgrading from v0.10.8 to v0.11.0 on a machine with OpenCode reported all
three OpenCode subagent profiles as "preserved modified profile" plus an
op=agent_install error each, and the activation stopped ("one or more agent
configurations failed; the published/current executable was kept"), although
nobody had edited those files.

Root cause: #1933 (v0.11.0) added the tool_search / tool_search_regex
permissions to the rendered OpenCode profile. Install and uninstall recognise
a profile as ours only when it matches the current rendering or one of the
known released renderings (the other access mode, the v0.9.1-rc.1 Codex
shape, the pre-tier Verify file). The v0.10.8 OpenCode shape was not in that
set, so cbm_text_migrate_owned_document classified the untouched v0.10.8
files as user-modified. Reproduced end to end: install with the v0.10.8
release binary, then install with v0.11.0/main, into an isolated HOME; the
three profiles are 1729/1880/1910 bytes, the same sizes as in the report.

cbm_render_graph_profile_opencode_v0108() renders the pre-#1933 shape
(both access modes), and install/uninstall now list it as a released
rendering, so those files are upgraded in place and uninstall removes them
as owned. A profile the user actually edited is still preserved. The
released-list assembly that install and uninstall duplicated moves into one
helper, and the two legacy renderers share one implementation (cli.c drops
three raw free sites; the memory-core baseline is tightened 173 -> 170).

The Codex and OpenCode op=mcp_install errors in the same report do not
reproduce from a pristine v0.10.8 config and depend on the reporter's own
config.toml / opencode.json; they are not addressed here.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-28 00:01:58 +02:00
Martin Vogel f4654a7c77 Merge pull request #2373 from DeusData/fix/defs-walk-no-cap
fix(extract): definitions walk stack grows without a ceiling
2026-09-27 23:39:31 +02:00
Martin Vogel b0fe8aef94 Merge pull request #2376 from DeusData/test/watcher-pending-free-held-state
test(watcher): pin the pending-free drain on a held state, not a git probe
2026-09-27 23:34:28 +02:00
Martin Vogel 07eb426c95 test(watcher): wait for the 14th reindex attempt in the sustained-failure test
watcher_sustained_failure_logs_once_issue2015 failed on macos-14 CI with
`failing_index_calls == 13, expected 14`. Its loop ran exactly 14
touch+poll rounds and assumed each one reached the index callback. A poll
whose git probe fails transiently (spawn refused under runner load) is a
fail-closed no-op by design (#937): the baseline stays uncommitted and the
next poll retries. So one refused spawn costs one attempt, and the count
ends at 13. This is the same test race as watcher_unwatch_drains_pending_free,
not a production defect. Holding one exhausted spawn mid-loop with
cbm_subprocess_force_spawn_eagain_for_testing reproduces the CI line
exactly.

The loop now polls until the 14th failed attempt instead of counting
polls. Each poll runs at most one attempt for the single project, so it
stops at exactly 14. A watcher that never reindexes hangs, and the runner
reports that as a failure, never a pass. The log-once contract means the
same thing as before: watcher.index.sustained_failure is emitted only on a
failed index attempt when the streak equals the threshold, so extra no-op
polls cannot add lines. An `==` to `>=` mutation in watcher.c still turns
it red (`sustained_log_hits == 5, expected 1`).

The change is now a dirty worktree instead of a commit. If the baseline
poll itself failed, the old commit would be adopted as the baseline HEAD
and never reindexed, so the wait would never end. The baseline keeps the
dirty signature at "clean known", so a dirty change is seen whenever the
baseline lands. With spawns forced to fail at the baseline poll and again
mid-loop, the test passes after 16 polls for 14 attempts.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-27 16:06:29 +02:00
Martin Vogel 5df8b0442c Merge pull request #2301 from ivanlinhf/chore/scoop-manifest-0.11.0
chore(scoop): bump packaged manifest to 0.11.0
2026-09-27 16:03:17 +02:00
Martin Vogel 6935df23a9 Merge pull request #1816 from z2250389/codex-z2250389/fix-windows-unicode-git-root
fix(windows): support Unicode repository paths
2026-09-27 16:02:10 +02:00
Martin Vogel f7cfccfd0c Merge pull request #2160 from htarnacki/fix/cli-json-array-args
test(cli): pin JSON array literal handling for array-typed arguments (#2150)
2026-09-27 16:02:05 +02:00
Martin Vogel 4f7fa84e32 Merge pull request #2359 from TheDarkniteFalls/codex/gh023-branch-head-regression
test(pipeline): cover changed-file and empty-commit Branch HEAD refresh
2026-09-27 16:01:59 +02:00
Martin Vogel 11b662f9f7 Merge pull request #1716 from astandrik/codex/fix-1648-sanitized-build-config
test: verify sanitizer build configuration
2026-09-27 11:02:35 +02:00
Martin Vogel 4b0b5f258d Merge pull request #2158 from htarnacki/fix/jaxrs-relative-path
fix(routes): accept relative JAX-RS @Path templates and compose Scala class-level @Path (#2147)
2026-09-27 11:02:30 +02:00
Martin Vogel 77ab4bea87 fix(extract): definitions walk stack grows without a ceiling
walk_defs keeps its pending frames in a growable stack that was bounded by
an 8M-frame ceiling (env CBM_WALK_DEFS_MAX). At the ceiling wd_push logged
"extract.walk_defs_capped" once and then stopped pushing. Children are
pushed last-to-first, so a file wider than the ceiling lost its FIRST
definitions: a work cap decided graph content.

Fix: no ceiling. The stack doubles on demand, heap frames now come from the
memory core (cbm_realloc/cbm_free, class EXTRACT) instead of safe_realloc and
raw free, and the doubling is overflow-guarded (int capacity and size_t
byte count). The only thing that can stop the walk is an allocation failure,
and that is loud: logged at error level as "extract.walk_stack_alloc_failed"
(walker=walk_defs, pending, path) -- the same event the complexity walkers
use -- and the file's result is marked has_error with
"definitions walk: stack allocation failed", which the definitions passes
already report as a per-file extract error. The arena-backed path
(ctx->scratch) gets the same failure handling. Traversal order is unchanged.

CBM_WALK_DEFS_MAX is removed. It was read only in wd_stack_max(); it was not
documented anywhere (docs, --help, config, tests), so nothing else changes.

extract_defs.c loses its raw free() call, so the memory-core baseline for
the file drops from 5 to 4.

Test (extraction suite): walk_defs_wide_file_extracts_every_definition
extracts a C file with 1000 top-level functions with CBM_WALK_DEFS_MAX=256
set, and asserts every wd0..wd999 definition is present. RED on main
(extract.walk_defs_capped limit=256; wd0 missing), GREEN with the fix, RED
again with the production change reverted. The extraction (379), complexity
(5), ast_profile (10) and semantic (35) suites pass.

Real input: Linux kernel, index-only, fresh cache and runtime dirs, same
machine. main and fix both produce 8,530,336 nodes and 15,764,128 edges,
SEMANTICALLY_RELATED 607 each, and the full node set (label, QN, lines) and
edge set (type, source QN, target QN) hash identically -- no kernel file
approaches the old ceiling. Index wall time 210.0 s (main) vs 194.4 s (fix),
within run-to-run noise.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-26 20:59:00 +02:00
Hubert Tarnacki bc960d87d6 test(cli): pin JSON array literal handling for array-typed arguments
`codebase-memory-mcp cli <tool> --flag value` builds the tool's JSON
arguments from typed flags. Array-typed parameters such as
`semantic_query` used to accept only repeated flags; the JSON spelling
shown in the MCP schema (`--semantic-query '["a","b"]'`) was stored as a
single opaque string element (#2150). `cli_add_typed` now expands such a
literal; this pins the edge cases of that contract so it cannot regress:

- a well-formed array of strings contributes its elements
- an explicit empty array `[]` contributes nothing (not the keyword "[]")
- a malformed literal stays one literal string, without an error
- non-string items are copied as typed values, not stringified
- literals and repeated flags mix, accumulating in argument order
- repeated literals accumulate

Signed-off-by: Hubert Tarnacki <hubert.tarnacki@gmail.com>
2026-09-26 09:26:00 +02:00
Mike Parsons efb231285d test(pipeline): cover changed-file and empty-commit branch HEAD refresh
Signed-off-by: Mike Parsons <212198286+TheDarkniteFalls@users.noreply.github.com>
2026-09-26 13:55:16 +12:00
Martin Vogel 5a9c72d8f8 test(watcher): pin the pending-free drain on a held state, not a git probe
watcher_unwatch_drains_pending_free failed three times on macos-14 CI
with `index_call_count == 0, expected 1` at its dirty-poll precondition.
That precondition needs every git subprocess of one poll (rev-parse
HEAD, status, submodule foreach) to succeed. A transient probe failure
(spawn refused under runner load, a non-zero exit) makes that poll a
fail-closed no-op by design: #937 leaves the baseline uncommitted and
the next poll retries. Several of those paths return without a log
line, which matches the CI output exactly: watch, baseline, FAIL, with
no watcher.changed and no watcher.git.failed. Holding a spawn failure
with cbm_subprocess_force_spawn_eagain_for_testing reproduces that
output deterministically. This is a test race, not a production defect.

The precondition was also unrelated to the contract under test. A poll
that stopped draining would neither crash nor leak, because
cbm_watcher_free frees the same deferred list. So the old test could not
see a broken drain at all.

Add a test-only seam, cbm_watcher_test_pending_free_count, and assert
the stable states the watcher holds: 0 parked before unwatch, 1 after,
0 after the next poll with no index work, and still 0 after a further
poll. No assertion depends on a git probe succeeding. Dirty-poll
reindexing stays covered by watcher_detects_dirty_worktree and
watcher_modify_tracked_file.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-26 00:27:53 +02:00
Ivan Lin ff8dd47011 fix(scoop): address v0.11.0 review feedback
Signed-off-by: Ivan Lin <35771002+ivanlinhf@users.noreply.github.com>
2026-09-26 06:23:52 +08:00
Martin Vogel 3e67838b05 Merge branch 'main' into fix/windows-unicode-repo-paths
Resolve the RUN_TEST list conflict in tests/test_daemon_application.c by keeping both sides' new tests.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-25 23:15:24 +02:00
Martin Vogel 64c23fab6c Merge pull request #2282 from knewstimek/pr/windows-worker-recovery-fix
fix(windows): preserve worker recovery errors
2026-09-25 19:12:42 +02:00
Martin Vogel ff127ea44c Merge branch 'main' into codex/fix-1648-sanitized-build-config 2026-09-25 19:07:24 +02:00
Martin Vogel d772b1e5af Merge branch 'main' into fix/jaxrs-relative-path 2026-09-25 19:07:21 +02:00
Martin Vogel da3ea36339 Merge pull request #1832 from cdeust/feat/doclinks-markdown
feat(pipeline): link markdown file references into the graph
2026-09-25 19:06:55 +02:00
Martin Vogel c98323bff7 Merge pull request #2199 from astandrik/fix/1696-smoke-runtime-isolation
fix(test): isolate smoke harness daemon runtime
2026-09-25 19:06:31 +02:00
Martin Vogel a5550e1a9e Merge pull request #1768 from bmcnaboe/fix/hook-augment-worktree-tests
test(hook-augment): pin worktree payload-cwd resolution
2026-09-25 19:05:56 +02:00
Martin Vogel 92cafd1ec8 Merge branch 'main' into fix/hook-augment-worktree-tests 2026-09-24 07:39:35 +02:00
Martin Vogel f07c18702a Merge branch 'main' into fix/1696-smoke-runtime-isolation 2026-09-24 07:39:17 +02:00
Martin Vogel a6d3d5d820 Merge branch 'main' into codex/fix-1648-sanitized-build-config 2026-09-24 07:39:13 +02:00
Martin Vogel cf7eecd7d0 Merge branch 'main' into fix/jaxrs-relative-path 2026-09-24 06:57:47 +02:00
Martin Vogel 9b5bf70dc4 Merge branch 'main' into fix/test-daemon-bootstrap-sync-spawn 2026-09-24 04:51:00 +02:00
Martin Vogel 5958f546dc Merge pull request #2273 from DeusData/distill/1245-service-pattern-boundaries
fix(service-patterns): match library ids on identifier boundaries
2026-09-24 04:35:34 +02:00
Martin Vogel 5c0b241877 Merge pull request #2275 from DeusData/fix/win-endpoint-dir-create-race
fix(daemon): losing the runtime-directory creation race is not a failure on Windows
2026-09-24 04:35:31 +02:00
Martin Vogel 1160fa3591 Merge pull request #2274 from DeusData/distill/1245-c-pointer-return-types
fix(extract): a C-family function's return type keeps its pointer and qualifiers
2026-09-24 01:21:43 +02:00
Martin Vogel b0691e0d2f Merge pull request #1742 from bmcnaboe/codex/fix-daemon-runtime-sanitizer-timeout-only
fix(tests): scale daemon runtime sanitizer timeout
2026-09-24 01:21:38 +02:00
Martin Vogel 95c91b8d8d Merge pull request #2140 from Lowpower/cursor/cli-help-format-json-cb65
feat(cli): point top-level cli --help at --format json
2026-09-23 23:53:21 +02:00
Hubert Tarnacki 246e4190c3 fix(routes): keep @Path("") unset instead of yielding an empty route path
Accepting relative JAX-RS templates made route_path_from_string_node return
"" for @Path(""), which is non-NULL and so replaced the "/" fallback with an
empty method path. @Path("") is legal JAX-RS and means "the class path
itself"; on main the empty value was rejected and the method fell back to
"/" like a method without @Path. Return NULL for the empty case so that
behaviour is unchanged.

The regression covers both shapes: with a class-level @Path the method
composes to the class path, and without one the route stays "/" (with the
empty string it was dropped entirely, because join_route_paths returns the
NULL prefix for an empty path).

Signed-off-by: Hubert Tarnacki <hubert.tarnacki@gmail.com>
2026-09-23 11:18:16 +02:00
News 1d2bbc4259 fix(windows): preserve worker recovery errors
Signed-off-by: News <knewstimek@users.noreply.github.com>
2026-09-22 22:34:11 +09:00
Clément DeustandClaude Sonnet 5 6ae6cd4c3f fix(pipeline): address doclinks review feedback
Replace raw malloc/free with cbm_alloc/cbm_free (CBM_MEM_CLASS_EXTRACT)
and a growable CBM_DYN_ARRAY for matches. Remove the 4KiB line-copy
cap; lines are split in place instead of copied. Remove the
DOCLINK_MAX_REFS(256) cap; the ref vector now grows, is sorted by
target id, then collapsed. Track reference kind (bare, relative,
rooted) so /x never attempts a directory join and ./x never falls
back to root. Report per-file skips via cbm_pipeline_add_file_error
and cbm_log_warn, and check cbm_pipeline_check_cancel in the per-file
loop.

Add negative-control tests for all of the above, plus CRLF handling,
fenced-code-block pinning, bare URLs, backtick-basename exactness,
and ".." escaping.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Clément Deust <clement.deust@gmail.com>
2026-09-22 08:48:33 +02:00
Clément DeustandClaude Fable 5 f3bdf11a03 feat(pipeline): link markdown file references into the graph
Markdown docs reference other repo files constantly (a coding-standards
doc links the modules it governs, a README points at entry points) but
none of that surfaced as edges, so fan-in queries were blind to
documentation hubs. In an A/B retrieval benchmark I ran in August on a
docs-heavy repo, the graph's top fan-in answer was off by roughly 17x:
the most-referenced file was a standards doc with 173 references and
zero inbound edges.

New pre-dump pass, pass_doclinks.c, modeled on pass_configlink.c: three
strategies emit REFERENCES_FILE edges between existing File nodes only
(unresolvable targets are dropped, the pass never invents nodes):
inline links, backtick paths, bare path mentions. Targets resolve
against the referencing file's directory and the repo root; repeated
references collapse into one edge carrying strategy, confidence and
count. Registered in the pre-dump sequence after configlink and in the
incremental post-passes; REFERENCES_FILE added to the skill's edge-type
list and the structural/language contract tests.

Tests mirror test_configlink.c (real files in a tmpdir, File nodes in a
gbuf, run the pass, assert edges): inline link, backtick, bare mention,
http/anchor ignored, anchor-suffixed file link, dedupe with count,
relative-vs-root resolution, unresolvable-target guard, NULL repo_path
skip.

A companion change adds the same linking for shell files (source lines
and script invocations); split out to keep each change reviewable.

Validation: scripts/build.sh clean; focused serial runner (doclinks,
configlink, pipeline, edge_structural, lang_contract) 340 passed under
ASan/UBSan; cppcheck clean on the new files with the repo's flags.

Signed-off-by: Clément Deust <clement.deust@gmail.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-22 08:06:50 +02:00
Martin Vogel 9b7d63f579 Merge pull request #1931 from ilyabrykau-orca/fix/go-cgo-pseudo-import
fix(extract): drop cgo's `import "C"` pseudo-package
2026-09-22 02:00:11 +02:00
Lowpower 2f557f5398 style(cli): clang-format CBM_CLI_USAGE and help-text test
Signed-off-by: Lowpower <3133734+Lowpower@users.noreply.github.com>
2026-09-21 23:47:50 +00:00
Lowpower aaa72fbe60 feat(cli): point top-level cli --help at --format json
CLI_USAGE described outer --json but did not mention tool-level
--format json. Add a help-only pointer that tools which accept format
support --format tree|json, and distinguish payload JSON from the full
MCP envelope. No new flags, env vars, or output-default changes.

Fixes #2102

Signed-off-by: Lowpower <3133734+Lowpower@users.noreply.github.com>
2026-09-21 23:47:50 +00:00
Martin Vogel 28b4093c99 Merge pull request #1976 from CaptainMittens/fix/swift-nested-url-constructor
fix(extraction): reach a Swift URL built by a constructor
2026-09-22 01:46:34 +02:00
Martin Vogel dfae618b0e Merge branch 'main' into codex/fix-daemon-runtime-sanitizer-timeout-only 2026-09-22 01:28:18 +02:00
Martin Vogel a041794f8e fix(daemon): losing the runtime-directory creation race is not a failure on Windows
Several processes first-starting together against a runtime directory
that does not exist yet -- test-windows-guards' section_cold_storm, or a
host that launches more than one MCP server on its very first run -- all
observe the final path component absent. One CreateDirectoryW wins; the
others get ERROR_ALREADY_EXISTS. win_private_directory_tree_secure()
treated that as a failure and recorded no validation detail, so every
loser exited with a bare

    secure CLI coordination could not be created (endpoint)

The function called right after the walk, win_runtime_directory_secure(),
has always tolerated ERROR_ALREADY_EXISTS for the same directory, and the
POSIX walk tolerates EEXIST at the same point; only the Windows walk in
front of them did not.

Seen four times on 2026-09-21 on unrelated PRs (#1768 three attempts in
a row, #2140, #1999, #808). The racy lines date from July; the guard
began exercising them on 2026-09-03, when each guard section was given
its own empty CBM_RUNTIME_DIR, so the final component is now absent at
storm time on every run.

The walk now treats ERROR_ALREADY_EXISTS from its own CreateDirectoryW as
"the directory I wanted exists". Nothing is trusted because of that: an
ancestor still goes through win_directory_component_secure(), and the
final component through win_runtime_directory_secure(), which refuses a
non-directory or reparse point and enforces owner and DACL.

Every refusal on this path now names its component and its rule. The
walk reports the Windows error when it can neither create nor inspect a
component, and win_runtime_directory_secure() says whether the path
could not be created, cannot be inspected, exists but is not a directory,
or is a reparse point. One helper owns the wide-to-UTF-8 conversion for
these messages and the existing ancestor message now uses it too, so
src/daemon/ipc.c stays at its memory-core baseline.

Deterministic reproduction, no threads and no timing: a test seam fires
in the walk between "component observed absent" and CreateDirectoryW,
and the test plays the process that wins the creation.

  daemon_ipc_windows_private_directory_survives_lost_creation_race
  daemon_ipc_windows_private_directory_refuses_and_names_a_planted_file

Verification. macOS arm64 (ASan+UBSan): build clean, daemon_ipc 53 passed
(2 Windows-only skips), daemon_bootstrap 28 passed, lint-memory-core
unchanged (ipc.c stays at 146 raw sites). The two new tests are Windows-only
and have NOT been run locally: the Windows VM was down when this was written,
so the RED run on the seam-only tree and the GREEN run with this fix are
delegated to CI's windows-latest legs (test-windows shards, test-windows-guards)
by an explicit maintainer decision. If the guard suite's cold storm still
fails with this in place, the attribution above is wrong and this reverts.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-22 01:12:51 +02:00
Martin VogelandAndrew Hundt 343d7dd5e4 fix(extract): a C-family function's return type keeps its pointer and qualifiers
Distilled from #1245 by Andrew Hundt (c51e5cb0, the extract_defs.c
half; the mcp.c and token-reduction parts are not taken).

For C-family functions return_type was the text of the declaration's
`type` node alone. In these grammars the `*`, `&` and `&&` live on the
declarator and cv-qualifiers are sibling nodes, so

    const char *get_name(void)     published   char
    robj **lookupKeys(...)         published   robj
    Text &operator=(const Text &)  published   Text

and no C or C++ function in any index had a pointer return type at all.
The C/C++ resolver's return-type parser already strips a leading
const/volatile and handles trailing `*`, `&`, `&&` -- branches that were
unreachable for functions, so every `Foo *f()` was registered as
returning Foo by value.

c_declared_return_type() renders the declared type at both sites
(functions and methods) for C, C++, CUDA, GLSL, HLSL, ISPC, Slang and
Objective-C -- the list resolve_func_name_c_family uses:

    <leading cv-qualifiers, source order> <base type, verbatim>
    <one space><markers, outermost first>

so `const char *`, `char **`, `Text &`, `Text &&`, `Text *&`,
`volatile unsigned long *`; a pointer-level qualifier follows its star
(`char *const *`); east-const `char const *` normalises to
`const char *`. With no qualifier and no marker the old text is returned
unchanged. It measures, then writes into one exact-size arena
allocation; the declarator walk is a plain child chain, no recursion.
def.return_types is untouched.

Two deliberate differences from the upstream hunk. Upstream copied every
type_qualifier node, which in these grammars includes constexpr,
_Noreturn, mutable and __extension__ -- `constexpr int f()` would have
become `constexpr int`. Only const, volatile, restrict, __restrict,
__restrict__ and _Atomic are kept, and both cases are asserted unchanged.
And the walk stops at the first declarator that is neither pointer nor
reference, so `int (*f(void))(int)` stays `int` rather than becoming a
wrong `int *`.

RED, production reverted and the final tests kept:
  c_function_return_type_preserves_pointer_and_qualifier
      FAIL tests/test_extraction.c:1005: "char" != "const char *"
  cpp_method_return_type_preserves_pointer_and_qualifier
      FAIL tests/test_extraction.c:1072: "char" != "const char *"
  351 passed, 2 failed
c_function_return_type_plain_unchanged is the control: it passes before
and after.

GREEN: extraction 353, c_lsp 762, lang_contract 41, pipeline 281.

Effect on real code -- Redis, production binaries, old against new.
39,132 nodes in both. 1,442 Function nodes change, in return_type only:
void -> void * (270), char -> const char * (101), unsigned char ->
unsigned char * (91), char -> char * (81), robj -> robj * (57), ...
Functions with a `*` in return_type: 0 -> 1,440. With one worker the
complete edge set apart from SEMANTICALLY_RELATED is byte-identical old
against new across six runs (three each) -- CALLS, USAGE, WRITES, IMPORTS
and the rest do not move. SEMANTICALLY_RELATED goes 350 -> 325 because
that pass tokenises the return-type string into its type vector.

One thing measured rather than assumed. With several workers, which
`dict.h` Redis' src/dict.c imports (src/ or deps/hiredis/) depends on
worker merge order. That is main's defect, not this change's: unmodified
main picked src/dict.h in 2 of 10 runs and the hiredis header in 8; with
one worker both binaries always pick src/dict.h. This change picked
src/dict.h in 6 of 10. At ten runs a side that gap is not separable from
chance (Fisher exact, two-sided p = 0.17), and a shifted rate would be
unsurprising for a timing-dependent pick; it is recorded here rather
than explained. The include-target fix is a separate change (#2227).

Known limits, all pre-existing and out of scope: struct and class FIELDS
have the same defect (`const char *data;` publishes char); C prototypes
and declaration-only C++ methods are not extracted as definitions, so
they are not reached; where a macro is misparsed as the type
(`LUA_API const char *f()`), the base was already wrong and now reads
`const LUA_API *`; the resolver's parser does not yet understand a
pointer-level qualifier or a leading _Atomic/restrict and falls back to
a named-type lookup for those, as it effectively did for every pointer
return before.

Co-authored-by: Andrew Hundt <ATHundt@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-22 00:48:44 +02:00
Martin VogelandAndrew Hundt c314dea4c1 fix(service-patterns): match library ids on identifier boundaries
Distilled from #1245 by Andrew Hundt (d15071c9, the match_qn half).

match_qn was a raw strstr(qn, library_id). Short ids fired inside
unrelated words, and a service kind REPLACES the plain CALLS edge, so a
false match both invents a service edge and deletes a real call:

  "gin."    in plugin.           -> route registration; any "/"-prefixed
                                    first argument then minted a Route node
  "nconf"   in encryptionconfig  -> CONFIGURES for every call into the
                                    package
  "tonic"   in Monotonic         -> gRPC; unparseable, so NO edge at all
  "express" in expression        -> route registration
  "get_env" in get_envelope, get_environ -> CONFIGURES
  "dio" "surf" "curl" "resty" "phin" "hyper" "treq" "rocket" in studio,
  surface, curly, restyle, dolphin, hyperbolic, streq, rocketmq

The rule (qn_hit_on_boundary; every occurrence of an id is tried, not
only the first). A separator is any character that is not an ASCII
letter or digit.

  before: start of string | separator | the id itself starts with a
          separator ("@trpc/server") | the id starts with an uppercase
          letter -- a capital opens a new CamelCase word whatever
          precedes it (AsyncHttpClient, IHttpClientFactory, NSURLSession)
  after:  end of string | separator | the id itself ends in a separator
          ("gin.") | uppercase letter (GuzzleHttp, FeignClient,
          KafkaProducer) | digit (urllib2, Mint.HTTP2, amqp091-go)

Rejected: a lowercase- or digit-initial id glued to a preceding letter
or digit, and any id continued by a lowercase letter. Two deliberate
differences from the upstream hunk: upstream's before-rule needed a
GuzzleHttp table entry and would have dropped IHttpClientFactory and
NSURLSession, and without the digit after-boundary urllib2 and
amqp091-go stop matching. All three match on main today.

No recall is traded for this. No boundary rule can tell "grequests"
from "myrequests", so a library whose own name glues a prefix or suffix
onto another id gets an explicit entry with the kind (and broker) it had
on main -- 47 of them, found mechanically by running main's matcher and
the new one over 173 candidate names and listing every one that went
from a kind to NONE:

  HTTP      grequests txrequests redaxios gaxios libcurl curlpp curlcpp
            hyperlocal guzzlehttp
  ASYNC     aiokafka pykafka rskafka librdkafka rdkafkacpp aioamqp
            amqpstorm pamqp pyamqp amqprs amqpcpp pynats jnats aiomqtt
            amqtt hbmqtt umqtt mqttools emqtt rumqtt gomqtt libmosquitto
            mosquittopp pubsublite gocelery
  CONFIG    wgetenv getenvb qgetenv dotenvx phpdotenv
  ROUTE_REG apiflask honox fasthttprouter
  GRAPHQL   gqlparser gqlgenc aiogqlc

Left at NONE on purpose: names that are not clients of the id they
contain and were misclassified on main -- hypercorn, hyperlink,
hyperopt, openresty, gqlalchemy, celeryconfig, kafkacat, gnatsd,
pypubsub. One visible reclassification: hyperium/tonic was HTTP through
"hyper" inside "hyperium" and is now gRPC through "tonic".

RED, production reverted and the final tests kept:
  infrascan_service_pattern_match_rejects_ids_inside_words
      FAIL tests/test_infrascan.c:105:
      svc_case_mismatches(cases) == 28, expected 0 == 0
  4 passed, 1 failed
All 28 negatives mismatch on main. The 47 glued-library positives are
RED the other way round: against the boundary rule without the entries.
Each positive uses a name containing no second id, so it binds to its
own entry -- "aiokafka.AIOKafkaProducer" would have passed through
"KafkaProducer" and proven nothing.

GREEN: infrascan 5, pipeline 281, parallel 74, extraction 350,
edge_types_probe 59, route_canon 11, lang_contract 41, cross_repo 8,
registry 64, mcp 318 (4 Windows-only skips). 0 failed.

Effect on real code -- production binaries, main 92abefa3 against this
change. main against main differs by 0 edges and 0 nodes on all three
corpora, so every delta below is the change:

  django      CONFIGURES 76 -> 74; both calls return as CALLS
              (geom.get_envelope, WSGIRequestHandler.get_environ)
  typescript  CONFIGURES 12,026 -> 12,024; one bogus Route "/*type*/"
              gone with its CALLS and HANDLES; 3 calls return as CALLS
  kubernetes  CONFIGURES 7,962 -> 7,609: all 353 are "nconf" inside
              encryptionconfig / authorizationconfig /
              authenticationconfig, and all 353 return as CALLS.
              8 bogus Routes gone -- os.ReadFile of the serviceaccount
              namespace file, t.Run("/TwoWay"), "/tmp/test" -- with 26
              CALLS, 2 HANDLES and 21 TESTS edges that pointed at them.
              374 plain CALLS restored; 8 of them (validate.Monotonic and
              friends) had NO edge at all on main. GRPC_CALLS 60 -> 60.
              HTTP_CALLS 398 -> 407: nine calls main swallowed as route
              registrations now reach the existing arg_url heuristic like
              every other package.

88 of the 408 removed service-derived edges were read at the call site
-- all of django and typescript, 82 of 402 in kubernetes: 88 false
positives, 0 true positives lost. None of the three corpora uses a
glued-name library, so the 47 entries are covered by the unit test only.

Known limits: "_" is a separator, so optical_fiber.len still matches
"fiber." exactly as requests_get matches "requests" by design; a
lowercase id followed by a capital matches (kafkaProducer), which also
admits a dioXide-style name; a user wrapper glued without a separator
(mygetenv) is no longer classified; a glued-name library missing from
the list is NONE until someone adds it.

Co-authored-by: Andrew Hundt <ATHundt@gmail.com>
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-22 00:48:43 +02:00
Martin Vogel da4bd25a01 fix(test): the ENOSPC bootstrap test no longer lets a deadline decide its verdict
daemon_bootstrap_fails_fast_when_daemon_dies_at_publication went red on
the MSan lane of three PRs that touch no daemon code at all (#2245,
#2158, #2140) with

    tests/test_daemon_bootstrap.c:1116: ASSERT(daemon_named_cause)

and green on a rerun of the identical commit.

Cause, attributed 2026-09-20. The test forks a real daemon host whose
record publication fails with ENOSPC, and asserts that the client names
that cause instead of waiting out its 30 s startup deadline. But before
it listens, the host SHA-256s its own executable image, and the test
runner is ~450 MB: ~3 s natively, 20-32 s under MSan (48 CI samples,
median 25.8 s). The spawn override returned as soon as it had forked, so
the client's 30 s clock raced the host's hash. On a slow run the
deadline fired first, the reaper SIGKILLed a host that had not failed
yet, and the log had no listen_failed line to find. Nothing was wrong
with the product: with a 28 s artificial delay the client does learn the
recorded ENOSPC cause.

The spawn override now reaps the forked host before it returns. The
host writes its start-failure record and only then releases its lifetime
reservation and exits, so "record on disk, reservation released" is a
stable state pinned by construction when the client takes its first look
-- and that first look is guaranteed: the client's post-spawn wait loop
runs at least once even past the deadline, and its first failure check
after a spawn is unthrottled. How long the host takes no longer matters.
No production code changes; the assertions are unchanged.

A regression to the pre-#1828 behaviour (a client that ignores the
record) still fails the unchanged assertions: its message no longer
names the failure, the errno and the path.

Proof, one build with temporary env-switched instrumentation that is
not part of this commit (a sleep in the forked host before it starts,
and a switch back to the old fire-and-forget spawn):

  spawn          forked host delayed   daemon_bootstrap suite
  synchronous    no                    28 passed
  old (async)    31 s                  27 passed, 1 failed:
                                       ASSERT(log_read)
  synchronous    31 s                  28 passed

The old spawn fails under the delay one assertion earlier than CI did
(log_read, not daemon_named_cause): the injected sleep sits before the
host opens its log, whereas in CI the host was killed mid-hash with the
log already open. Same mechanism -- the client's deadline beating the
host -- and the 2026-09-20 attribution reproduced the CI message itself
with a delay placed after the log is opened.

The final tree, without the instrumentation: daemon_bootstrap 28 passed,
three runs in a row.

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-22 00:48:20 +02:00
Martin Vogel 61cfb09b52 Merge branch 'main' into fix/jaxrs-relative-path 2026-09-21 20:56:22 +02:00
Martin Vogel 1a3425536b fix(graph): quality round after the v0.11.0 probe — deterministic resolution, findability, coverage, names
Nine findings from the 2026-09-16 graph-quality probe over the eleven bench corpora, each with a
test that fails without its fix and a before/after measurement on the real corpus.

Deterministic resolution (O9). Two indexes of one kernel tree differed in 5,821 IMPORTS and 632
CALLS edges. Three order/load dependencies carried it: the `#include` target was the first by-name
hit whose path ends with the include path — a bucket in worker merge order (include/linux/device.h
vs tools/virtio/linux/device.h); the registry broke suffix_match score ties by bucket position; the
LSP budget-share rule measured wall time. Include targets now prefer the File node, the least nested
path, then the lexicographic path; registry ties prefer the least nested QN, then the smaller QN; the
budget share is measured in thread CPU time like the budget itself. Two kernel indexes are now
identical (8,529,723 nodes, 1,974,940 CALLS, zero churn), as are two elasticsearch indexes.

Findability. search_graph's BM25 path honours the `label` filter and ranks an exact-name definition
above test methods that repeat the token (15/15 probe queries).

Coverage. The parse-coverage gap tree subtracts definitions the walker recovered inside an error
region (multi-line only); kernel/sched/core.c reports 476 flagged lines instead of 7,674.

Precision. Config-language callers (Makefile, CMake, YAML) no longer bind unique_name into code
(kernel Makefile -> C CALLS 195 -> 0). JS-family token names ({}, 1, (x: T) => U) are not
definitions; computed, quoted and escaped member keys stay (TypeScript 204 junk -> 0, 1,782 real
names kept). Names are cut at their first line (elasticsearch YAML 8,306 -> 0); a comment inside an
argument list is not an argument, so no Route node is named by a Java block comment; the wildcard
route `/*` is still a route. HTML/Vue/Svelte/Astro embedded member calls stay unbound.

Recall. A Python call through a self- or cls-rooted attribute keeps a specific unique_name member
target (Django +751, elasticsearch +3,288 CALLS, no new callee names).

Robustness. Edge properties never carry a split UTF-8 sequence (the 120-byte expression cut and the
JSON escaper both trim a partial code point). CLI array flags accept a JSON-array literal. Cypher
seeds a single-hop pattern from its selective end (kernel `MATCH (a)-[:CALLS]->(b) WHERE b.name =
'_printk'`: execution limit -> 7.9 s).

Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
2026-09-21 20:29:27 +02:00
Martin Vogel d615088a3c Merge pull request #2259 from DeusData/carry/2051-atomic-publish-errno
fix(graph-buffer): log why an atomic publish failed instead of a successful dump
2026-09-21 20:23:03 +02:00
Martin Vogel c7488b031c Merge pull request #2005 from XIYBHK/fix/1989-export-macro-parsing
fix(parsing): recover export-macro-hidden type definitions (#1989)
2026-09-21 20:22:03 +02:00
Martin Vogel 9b10337907 Merge pull request #1809 from Sakil786/fix-pi-extension-typebox-schemas
Fix Pi extension to use TypeBox schemas instead of JSON
2026-09-21 20:21:50 +02:00