fix(mcp): resolve projects by internal name + filter ghost dbs (#704)#717
Merged
Conversation
Project resolution was filename-addressed but internal-name-validated:
resolve_store opened <cache>/<passed>.db and required the internal
projects.name row to equal the passed name, while list_projects advertised
the .db FILENAME. When a db's filename differed from its internal name (a
copied/renamed db, or a legacy '.'-vs-'-' username twin from a past path
sanitization), it showed in list_projects but every query returned "project
not found or not indexed". Node rows are keyed on the internal name, so just
opening the file is not enough -- queries must use the internal name too. The
intermittency ("works now, fails 10 min later") was a warm in-process store
cache masking a broken cold resolve; a fresh cli call is always cold.
Key list + resolve on the INTERNAL project name, with no file renames or
mutation:
- list_projects / collect_db_project_names / build_project_json_entry now
advertise each db's internal projects.name (via db_internal_project_name)
and skip ghost/empty/corrupt dbs (0-byte, no project row) so the listed
names are exactly the ones that resolve.
- resolve_store keeps the fast path (open <passed>.db, get_project -- the
common case where filename == internal name). On a miss it runs a bounded
cache-dir scan, adopting the db whose sole internal project name equals the
passed name (gated on exactly one project row so it never serves another
project's data). No match -> NULL (a typo stays not-found). The scan runs
only on the fallback.
Reproduce-first: tool_resolve_store_by_internal_name_issue704 builds an
isolated CBM_CACHE_DIR with a control db, a drifted db (filename != internal
name), and a 0-byte ghost; asserts list advertises the internal name (not the
filename), the drifted project resolves via the scan, and the ghost is neither
listed nor resolvable. RED (113/1, list lacked the internal name) -> GREEN
(114/0). Full suite 5723/0.
Also hardened tool_bad_project_name_no_overflow_issue235: its fixture used
1-byte non-SQLite files that the new ghost filter would skip, making the
buffer-overflow guard vacuous; rebuilt it with valid dbs carrying long
internal names so the guard still fires.
Signed-off-by: Martin Vogel <martin.vogel.tech@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #704.
Symptom:
query_graph/search_graph/get_architecturereturn"project not found or not indexed"for a project thatlist_projectsshows — inconsistently ("works now, fails 10 min later"). (_config.dbbeing empty is a red herring — it's a KV config store, never a registry.)Root cause: resolution is registry-less + filename-addressed (
list_projectsand the error'savailable_projectscome from.dbfilenames) but a query requires the internalprojects.namerow to equal the passed name. When a db's filename ≠ its internal name — a copied/renamed db, or a legacy.-vs--username twin from a past path sanitization — it lists but never resolves. The intermittency was a warm in-process store cache (60 s idle evict) masking a broken cold resolve; a freshclicall is always cold. Node rows are keyed on the internal name, so opening the file isn't enough — queries must use the internal name too.Fix — key list + resolve on the INTERNAL name; no file renames, no data mutation:
list_projects/collect_db_project_names/build_project_json_entryadvertise each db's internalprojects.nameand skip ghost dbs (0-byte / no project row), so listed names are exactly the resolvable ones.resolve_storekeeps the fast path (open<passed>.db+get_project— the common case). On a miss it runs a bounded cache-dir scan, adopting the db whose sole internal project name equals the passed name (gated on exactly one project row, so it never serves another project's data). No match → not-found (typos stay not-found). The scan runs only on the fallback.Reproduce-first:
tool_resolve_store_by_internal_name_issue704— isolatedCBM_CACHE_DIRwith a control db, a drifted db (filename ≠ internal name), and a 0-byte ghost; asserts list advertises the internal name (not the filename), the drifted project resolves via the scan, and the ghost is neither listed nor resolvable. RED (113/1— list lacked the internal name) → GREEN (114/0). Full suite 5723/0.Also hardened
tool_bad_project_name_no_overflow_issue235: its fixture used 1-byte non-SQLite files that the new ghost filter would skip, making the buffer-overflow guard vacuous — rebuilt with valid dbs carrying long internal names so the guard still fires.Design note: chose internal-name addressing over a file-rename migration — it makes list/resolve/queries consistent without renaming or mutating any user
.dbfile. The separate latentREADWRITE/WAL query-open bug (queries mutate dbs / fail on a read-only FS) is tracked as its own PR.Part of the CLI-reliability series: #640 → #714, #680 → #716.