When a call resolved to a GraphQL client, emit_graphql_edge named the
Route after the whole first string argument, stripping only a leading
"query " or "mutation " (the mutation strip was also one byte short, so
names kept a leading space). An anonymous `query { ... }` therefore became
one long Route, and even a named operation carried its variables and
selection set in the name ("GetUser($id: ID!) { user(id: $id) { name } }").
Every distinct operation text minted its own Route node, and names were
cut off at 256 bytes.
Add cbm_service_pattern_graphql_operation(), a small GraphQL document
scanner. It skips comments, strings, block strings, `${...}` template
interpolations and leading fragment definitions, and reports the first
operation's type and name without copying any operation text. Routes are
now keyed as follows:
named operation __graphql__<Name> name <Name>
anonymous query / mutation / __graphql__<type>__anonymous name (anonymous <type>)
subscription, incl. the `{ ... }` query shorthand
no GraphQL document __graphql__operation__anonymous name (anonymous operation)
(URL, bare word, no string argument)
Keys are deterministic and bounded by the GraphQL Name grammar. The
GRAPHQL_CALLS edge keeps "operation" (now the Route name) and gains
"operation_type". The old fallback to the callee name ("client.query")
is gone: a callee is not an operation.
The sequential path (pass_calls.c) emits no GRAPHQL_CALLS at all, so it
had no copy of this logic to fix.
Proof on graphql-python/gql (374a70df, Python GraphQL client, index-only):
GraphQL Routes 170 -> 16, longest QN 266 -> 34 bytes, 106 -> 0 Routes
carrying operation text. GRAPHQL_CALLS 2643 -> 1158 with the same 984
distinct callers: edges are one per (caller, operation) pair, so calls
that used to hit distinct text-keyed Routes now share their operation's
Route.
Tests (edge_types_probe): graphql_route_keyed_by_operation_name_issue598
asserts the exact GraphQL Route set (QN + name) on the parallel path.
graphql_operation_identity_parser_issue598 covers braces inside comments,
strings and interpolations, fragments before the operation, anonymous
operations with variables, name truncation and non-document arguments.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The full Edge Types list near the graph schema section still missed
the new REFERENCES_FILE type added by the doclinks pass. The
"selected" list earlier in the file is explicitly a subset and is
left as is.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Clément Deust <clement.deust@gmail.com>
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>
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>
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>
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>
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>
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>
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>
Found while proving the previous commit, in the same step, and the worse
of the two defects because it fails OPEN. The classification was
if printf '%s\n' "$FILES" | grep -qE '^(src/|internal/|...)'; then
under the runner's `bash -eo pipefail`. grep -q exits on its first match;
once the list is larger than the pipe buffer printf is still writing,
dies of SIGPIPE, and pipefail turns the MATCH into a failed pipeline. The
else branch then writes product=false, and pr-smoke and memwaste are
skipped -- on exactly the PRs that change the most files. The previous
commit's message flagged this as a follow-up; it is fixed here instead.
The list now reaches grep through a here-string, so there is no pipe to
break. The regex is unchanged.
Proof, by executing the workflow's own run block under
`bash -eo pipefail` with a stub gh, ten runs each:
849 KB list, first line src/a.c before: product=false 10/10
after: product=true 10/10
849 KB list, docs only product=false before and after
small docs-only list product=false
422 fallback on the real #2246 product=true
fallback, nonexistent merge ref rc 128, no output written
It takes roughly 1,500 changed files to cross the buffer, which is why
ordinary PRs never showed it.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The `changes` job decides whether a PR touches product code, and ci-ok
requires it. It asks GitHub for the PR's file list. An earlier fix moved
it off the .diff endpoint, which rejects PRs over 20k lines, onto the
paginated pulls/files endpoint -- but that endpoint renders the diff
server-side as well, and for a PR that regenerates a vendored parser it
answers
HTTP 422: Sorry, this diff is taking too long to generate.
Observed on #2246 (a 42 MB sql/parser.c): `changes` red, therefore ci-ok
red, for a reason that has nothing to do with the contribution. The
compare endpoint fails the same way, so there is no API route to the
file list for such a PR. Every grammar refresh would hit this.
When the API call fails, the job now takes the list from git instead.
The PR merge ref's first parent is the base, so a name-only diff across
that one merge commit is exactly the PR's changed files. The fetch is
depth 2 with --filter=blob:none into a bare repository under RUNNER_TEMP:
commits and trees only, no blobs, nothing checked out and nothing from
the PR executed. No new action, no new permission, and the API path and
the classification regex are untouched, so ordinary PRs behave as before.
A ::notice:: line records when the fallback was used. If the fetch fails
too, the step exits non-zero and writes no output -- the gate fails
closed, as it did before.
Proof, by executing the workflow's own run block under
`bash -eo pipefail` with a stub gh:
API path, docs-only list rc 0 product=false
fallback, real #2246 rc 0 product=true, the same five
files `git diff --numstat`
reports for the PR; 0.85 s,
188 KB fetched
fallback, nonexistent merge ref rc 128 no output written
Contract tests that read pr.yml -- security_gate_fail_closed,
smoke_fixture_contract, windows_bundle_contract, venue_parity_contract --
pass.
Not addressed here, noted for a follow-up: the classification pipes the
list into `grep -q` under pipefail, which can report a match as a
failure once the list outgrows the pipe buffer.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
CONTRIBUTING.md said nothing about contributions written by or with an
AI agent, and the project now receives both kinds: PRs from accounts
that openly identify as an agent acting for a person (#2225 is one, and
a good one), and, in September, an account that posted approvals and
change requests styled as maintainer decisions across nineteen threads.
The first deserves a clear welcome; the second needs a rule that was
until now only enforced, never written down.
The new section says: disclosed AI authorship is welcome and never held
against a change; the DCO sign-off is a legal certification only a
person can make, so a human is accountable for every commit; the
evidence bar is the same for everyone -- a claim is something a reviewer
can check. Co-authored-by trailers naming a tool are fine, links to
private agent sessions are not. High-volume low-effort submissions are
closed whoever produced them.
A second subsection states who speaks for the project -- the maintainer
account only -- and that impersonating a maintainer, by a person or an
agent, gets the reviews dismissed, the comments hidden, a public
correction in the affected threads, and the account reported and
blocked. That is what was done in September; it is now written where a
contributor can read it first.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Fallout of rebasing this branch onto main. This branch was written while
the LSP walk could be skipped for a file whose parse had used more than
its share of the budget, and that share was measured in the thread's CPU
time. Main has since removed the rule entirely -- no clock decides what
ends up in the graph -- so cpu_start_ns is gone from the function and the
only line still reading it was computing a value with no remaining
consumer.
The conflict resolution kept main's side in cbm.c, which is what leaves
this line stranded: it is dead by then, and the build says so.
error: use of undeclared identifier 'cpu_start_ns'
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The lint chain stops at its first red target, so a clang-format failure hid the memory-core linter
until PR CI: the coverage line-offset table, the Cypher direction swap and the search label each
added a raw allocator site. The table now comes from cbm_alloc/cbm_free (extract class), the swap
frees through safe_str_free, and the BM25 row array moves to cbm_calloc/cbm_free so mcp.c's raw-site
count goes down (baseline 814 -> 813) rather than up.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
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>
Review on #2199: cbm_test_runtime_init ran more than a hundred lines
before the only EXIT trap, so under `set -e` a failure in the fixture
mktemp or its cygpath conversion left the private root behind, and the
fixture trap ran smoke_rmtree ahead of the runtime cleanup.
Arm `trap 'cbm_test_runtime_cleanup "$BINARY"' EXIT` immediately after
a successful init, as soak-test.sh and memlab.sh do. The fixture trap
that replaces it now retires the private daemon first and removes the
fixtures second, so no earlier cleanup step stands between the exit and
the runtime cleanup; on Windows the retirement is also what unblocks
the fixture rm.
tests/test_smoke_runtime_isolation_contract.sh pins the early trap to
the window between the init call and the first fixture; the pin fails
against the previous head.
Part of #1696.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Anton Standrik <astandrik@yandex-team.ru>
scripts/smoke-test.sh retires the account daemon from seven call sites,
but its wrappers sandbox only HOME/TMPDIR/CBM_CACHE_DIR and only
CBM_RUNTIME_DIR moves the daemon rendezvous, so every retirement reached
the operator's live daemon. Source scripts/test-runtime.sh so every
product process runs under a harness-owned runtime and cache, and clean
that root up from the EXIT trap.
Add tests/test_smoke_runtime_isolation_contract.sh, which fails before
this change, and wire it into scripts/test.sh.
Part of #1696.
Signed-off-by: Anton Standrik <astandrik@yandex-team.ru>