With #2134, an unnamed index_repository of a root that another indexed
project already owns adopts that owner's name, and the daemon keys the
job by it (application_index_project_key reads the forwarded "name").
The unnamed status poll that the #2144 async reply asks for ("the same
repo_path and status: true") still derived its key from the path alone
in index_status_project_key. For an owned root it never found the job
it had just started and answered "no index job or index is known for
project '<path-derived>'"; an ambiguous root got that same misleading
answer instead of the loud error the start returns.
index_status_project_key now runs the start path's
index_root_owner_resolve on the same canonical repo_path: an explicit
name still wins, a single other owner is the key, several owners fail
with the same error naming them, otherwise the path-derived key stays.
The status error buffer grows to hold that owner list.
The daemon's other readers of the key (job dedupe in
application_job_subscribe_locked, the synchronous cut-short marker set
and taken around application_index_run_sync) derive it from the
forwarded args, which already carry the owner name, so they agree with
the start; status was the only reader that diverged.
Refs #2134, #2144
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The memory-core ratchet flagged src/pipeline/pass_pkgmap.c growing by 2 raw
allocator sites (82 -> 84). Both are frees of strings returned by
cbm_pipeline_fqn_compute and cbm_pipeline_resolve_module, which the core
never counted, so they now go through safe_free as the rest of the codebase
does rather than raw free or cbm_free. Behaviour is unchanged;
pass_pkgmap.c is back at its baseline of 82.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
`from unittest.mock import patch` names a module outside the project.
Strategy 1 of cbm_pipeline_resolve_import_node cannot resolve it, so the
Strategy-3 symbol-name fallback bound the import to the only project
definition whose leaf is `patch` -- a REST view's PkgConfigView.patch --
and every `patch(...)` call became an import_map CALLS edge at confidence
0.95. Once that IMPORTS edge is gone the same call falls through to the
weak short-name strategies (unique_name 0.75, suffix_match), which bind it
again; `mock.patch(...)`, `json.loads(...)`, `os.path.join(...)` reached
project methods the same way, because the member guard exempts
import-rooted receivers.
Two per-language (Python-only) rules, both keyed on the import's module
chain rather than on any spelling list:
- Import side (pass_pkgmap.c, shared by both drivers): for an import
whose module is not in the project, a Strategy-3 hit must spell the
import's module chain as an ordered subsequence of its QN (a sys.path
root or src/ layout the resolver missed still qualifies), and whatever
names a module (an enclosing path segment, any segment of a plain
`import x`) may only bind a Module/File node. Imports of project
modules are unchanged (they may re-export from anywhere).
- Call side (pass_calls.c + pass_parallel.c, identical gates): a call
whose root identifier is bound by an external import (no IMPORTS edge
materialized it) and whose weak-strategy target contradicts that
import's chain is not emitted as a plain CALLS edge. import_map /
same_module / lsp_* edges are never touched.
django/django (parallel pipeline): CALLS 62,430 -> 60,123, TESTS
29,872 -> 28,571, IMPORTS 16,068 -> 15,491. Removed CALLS are
member calls through stdlib imports (1,900: os.path.join ->
defaultfilters.join, json.loads -> signing.loads, mock.patch ->
RedirectView.patch) and bare calls of stdlib names (398: date ->
defaultfilters.date, urlsplit -> MultiPartParser.parse); a random
25-edge sample was 25/25 false. Six IMPORTS are re-targeted to the real
definition (template_tests.utils.setup, multiple_database TestRouter,
pr_quality errors.Message).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
A method call on an instance of a class defined in another file produced
no CALLS edge, while the identical code in the class's own file resolved
via lsp_ts_method. The type-aware tier typed the receiver wrongly in two
places, so the method lookup in the shared TS registry missed and the
call fell to method_not_in_registry:
1. `new C()` always qualified a bare constructor name against the
CURRENT module. `const t = new ImportedClass(); t.m()` and
`new ImportedClass().m()` were typed as a phantom
`<this module>.ImportedClass`. The constructor type now follows TS
scoping: a class declared in this module, else the class an
unambiguous import binding names, else the old spelling.
2. ts_import_symbol_qn (and the copy of its heuristic in
resolve_type_with_imports) treated an import value ending in
".<name>" as an already-qualified symbol QN. For the dominant TS
convention of a file named after its class (`Foo.ts` exports
`class Foo`, module `x.Foo`, class `x.Foo.Foo`), the receiver was
typed as the MODULE, for `new`, annotations and parameters alike.
The registry now decides the ambiguous case: a registered
`module.name` wins, else the value is the symbol QN as before.
Both are resolved through the existing shared cross-file registry; no
name-based fallback is reintroduced. Cost is one or two hashed lookups
on the finalized overlay / sealed shared registry, per `new` expression
or per ambiguous import spelling during the walk; nothing is looked up
during registration and nothing is written to the shared registry.
typeorm (3,593 .ts files, shallow f279fd13): lsp_ts_method 2,551 ->
3,130, all 579 new edges cross-file (cross-file lsp_ts_method 696 ->
1,275), 0 edges lost, other strategies unchanged, index time
unchanged. 20 sampled new edges checked against source: 20 true
positives.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
README.md:132, README.md:633, README.md:761, and docs/CONFIGURATION.md:203
said one-shot `cli` commands "never start or connect to the coordination
daemon" / read "their own environment without starting the daemon". That
was already contradicted a few lines above it (README.md:126 lists
one-shot CLI commands among the processes that "share a crash-safe OS
admission barrier" with the daemon) and by the code:
main_local_cli_daemon_execute() (src/main.c) bootstraps a client
connection to the shared per-user coordination daemon via
main_client_bootstrap_with_upgrade(), spawning one (bootstrap.daemon_spawned)
when none is running, before dispatching the tool call.
Verified live: `cli --verbose list_projects` against an isolated
HOME/CBM_CACHE_DIR/CBM_RUNTIME_DIR printed "hint: this command started a
temporary CBM daemon", and its cbm-daemon.log showed
daemon.start -> daemon.runtime_stopping (reason=last_committed_client_disconnected)
-> daemon.lifetime_end, i.e. the daemon it spawned exited again once the
CLI command's own connection closed. bootstrap_production_spawn() passes
the calling process's `environ` straight to posix_spawn(), so a CLI
invocation that starts the daemon also seeds that daemon's captured
daemon-owned environment (CBM_DIAGNOSTICS, CBM_LOG_LEVEL, etc.) - it does
not read "its own environment" independently as the docs claimed.
Corrected all four passages to state the real behavior: CLI commands
connect to the shared daemon (starting one if none is running) for the
same admission barrier and per-project locks, hold a `cli_session` that
is never registered with the background watcher, and - if their own
connection is what started the daemon - that daemon exits again once the
command's connection closes and no other session is attached. No opt-out
env var or flag for an in-process/no-daemon CLI mode exists (grepped the
whole tree), so none is documented.
This is a pure documentation fix; no production behavior changed. Kept
clear of PR #2348 (fix/issue-2167), which separately rewrites the
watcher-scope paragraph at README.md:152/237/672 and
docs/CONFIGURATION.md:88 for issue #2167's auto-sync-scope correction -
none of the lines here overlap with that diff.
Refs #2183, #2167
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
urllib3 2.7.0, pinned in pkg/pypi/requirements-publish.txt for the
PyPI publish step, is affected by three advisories published on
2026-09-30, all fixed in 2.8.0:
- GHSA-8988-9cw3-xx77 (high): HTTPS proxy TLS configuration may be
ignored or overridden
- GHSA-vxq7-64xx-v4gw (high): unbounded chunk-size line buffering in
HTTPResponse.stream() / read_chunked()
- GHSA-gh4c-6fx4-qh6g (medium): chunked deflate streaming can loop
forever
OSSF Scorecard reports them as code-scanning alert #33
(VulnerabilitiesID), which the CodeQL gate counts as an open alert,
so every PR's gate is blocked until the pin moves.
Only urllib3 changes. The file was regenerated with the documented
recipe (pip-compile 7.5.1 on a linux/amd64 python:3.12 image) in
upgrade-only mode (--upgrade-package urllib3==2.8.0), so every other
pin and hash is byte-identical. Both new hashes match PyPI's published
digests for urllib3-2.8.0-py3-none-any.whl and urllib3-2.8.0.tar.gz.
Proof: `pip install --require-hashes --no-deps -r
requirements-publish.txt` on linux/amd64 python:3.12 installs the full
set with every hash verified, imports urllib3 2.8.0, twine and build,
and `pip check` reports no broken requirements.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Codex rewrites its [mcp_servers] tables whenever it edits MCP servers
itself (`codex mcp add`, its MCP settings, wrappers such as Orca that
drive it) and drops comment decor on the way. From our managed block it
keeps the table but loses the begin marker, omits `args = []`, and leaves
the end marker as an orphan below whichever table follows. Reproduced
with codex-cli 0.153.4: after `codex mcp add orca -- /bin/echo hi` the
file holds an unmarked
[mcp_servers.codebase-memory-mcp]
command = ".../codebase-memory-mcp"
env_vars = ["CBM_CACHE_DIR", "CBM_RUNTIME_DIR"]
and a stray `# <<< codebase-memory-mcp MCP <<<` under [mcp_servers.orca].
Every later install then failed with op=mcp_install on a table cbm itself
had written, twice over: cbm_toml_remove_legacy_table refused the orphan
marker as malformed, and its owned-shape check predated env_vars (#1562,
#1664), so even without the orphan the table counted as foreign.
cbm_toml_remove_legacy_table now drops a lone orphaned marker line in the
same atomic write (we are the only author of those markers, #1558; only
that line is removed, nothing is guessed) and treats env_vars naming only
CBM_CACHE_DIR / CBM_RUNTIME_DIR as part of the owned shape. A table with
any other key or forwarded name is still foreign and left byte-identical,
and a foreign table keeps the file untouched, orphan included. The scan is
split into toml_strip_legacy_table + toml_legacy_classify_assignment so
the entry point reads the file, heals, strips and writes once.
The same helper backs the Grok Build install, which gains the same
self-heal.
Tests: cli_upsert_codex_mcp_adopts_codex_rewritten_table uses the exact
bytes Codex produced; config_toml_legacy_remove_adopts_owned_env_vars_only
pins the env_vars boundary (owned names adopted, any other name foreign).
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
tool_index_repository_reuses_existing_project_for_root_issue2134 counts
how often the canonical repo root occurs in list_projects. On Windows,
cbm_canonical_path returns "C:\Users\...", while the stored root_path is
"C:/Users/...", so the count was 0 and the test failed on the CLANG64
leg (test_mcp.c:8448: owners == 0, expected 1).
Normalize the expected root with cbm_normalize_path_sep, a no-op on
POSIX, before counting.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
Linux, MSan, TSan and diag CI failed in mcp_mutation_guard:
tool_cross_repo_missing_inputs_fail_without_creating_ghost_databases
ASSERT(source_failed). handle_index_repository resolved the root owner
before dispatching cross-repo-intelligence mode. That test registers its
named target with the source's own root, so the unindexed source adopted
existing-cross-target as its project (log: index.root_owner_reused
project=existing-cross-target), and the missing source was no longer
reported. Cross-repo mode reads an existing source index and never writes
one, so the #2134 reuse now runs only on the re-index path, after that
dispatch. macOS CI stayed green because cbm_tmpdir() is /tmp, a symlink
to /private/tmp there, so the canonical repo_path never equalled the
fixture's stored root; with the fixture path canonicalized, the failure
reproduced 3/3 on macOS and passes 3/3 with this change.
The #2134 test itself called realpath() into a 1 KiB buffer. glibc's
FORTIFY check aborts on that ("buffer overflow detected", a SIGABRT of the
mcp suite on the ubuntu 2/3 shards), and MinGW has no realpath at all
(Windows compile error). It now uses cbm_canonical_path with a 4 KiB
buffer, the same canonicalizer production uses.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
The memory-core ratchet flagged src/mcp/mcp.c growing by 10 raw allocator
sites (784 -> 794) from index_root_owner_resolve and its caller. The owner
list buffer, which this code allocates and frees itself, now uses
cbm_realloc/cbm_free (class other). Strings handed over by other APIs
(cbm_project_name_from_path, heap_strdup, the index argument strings) are
released through safe_free, as elsewhere in the file, so no memory the core
never counted reaches cbm_free. Behaviour is unchanged; mcp.c is back at its
baseline of 784.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
index_repository without `name` derived the project key from the path
alone. When the root had first been indexed under an explicit name, a
later unnamed re-index forked a second index (same root_path,
path-derived name) and left the original untouched, so the stale index
kept being served while the call reported success.
Resolve the key from the indexes on disk before any work starts:
- the path-derived project exists -> keep it (unchanged behavior);
- exactly one other project owns the canonical root -> adopt its name
and update it in place, so existing indexes are never orphaned;
- several projects own the root -> fail loudly, name them and ask for
`name` instead of guessing or forking again;
- none -> first index under the derived name, as before.
The resolved name is written into the args handed to the daemon job
coordinator and the supervised worker, so the project lease key and the
index actually written stay the same project.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>