fix(search): publish lexical route corrections (successor to #183) - #194
Conversation
|
Independent automated preflight: HOLD at The MCP The five PostgreSQL lexical cases and latest 20 successful CI checks do not cover this interface gap. Correcting the migration-number expectation alone is insufficient. Implementation follow-up has been assigned; no approval or merge readiness is claimed. Migration ordering is also being coordinated with #195/#197 without weakening Goose or renumbering deployed migrations. |
|
Independent automated preflight — bounded code PASS, candidate The earlier MCP blocker is fixed: tools/list advertises lexical, and the new JSON-RPC list/call regression verifies schema plus route/filter forwarding through the real MCP server and HTTP client to a loopback fixture. The coordinator inspected this delta; independent reviewer Mendel found no remaining source blocker. Worker evidence separately includes all nine MCP tests, five real PostgreSQL lexical cases and the populated strict schema 23→24 upgrade. This is not a live embedding-provider benchmark. The final docs-only correction from Migration numbers and SQL identities are retained. Cumulative branches enforce #194 (24) → #197 (25) → #195 (26), with a gap guard and real sequential-upgrade tests; a pre-existing 23+26 database is rejected, not silently repaired or granted AllowMissing. Product/review gate remains open: #176 is still an RFC with status:needs-triage, asking whether the model-free lane is intended. Technical preflight and CI do not accept that RFC, authorize deployment or satisfy human approval. This candidate remains draft pending that decision and review. #197's query-strategy acceptance and #195's dependency HOLD are outside this verdict. |
Code ReviewOverall the design is solid — the three-tier scoring (exact → FTS → trigram) mirrors the existing memory Recall shape, migration safety is well-considered, and test coverage is thorough (especially the sequential upgrade regression). No blocking bugs found. Below are specific findings and suggested fixes. 1.
|
waterbro-8
left a comment
There was a problem hiding this comment.
Code review(draft,给作者的清单,非批准)
一、这份 PR 最有价值的东西,是它把"栈"这个风险自己写下来了
我独立排查时担心的那件事——三条 PR 各自带一个迁移(0024/0025/0026),而 #195/#197 的 base 又是另一个 PR 的分支,部署顺序一旦错乱就会出现"跳过 24 直接到 26"——docs/MIGRATION_SEQUENCE.md 已经把它当成一等公民写清楚了:
- 明确三条是累积的、不可独立部署的,并给出顺序表与前驱关系;
- 明确写出"不要因为假设没人用过草稿就重排迁移号"——它甚至论证了为什么 main 只有 0001–0023 不能证明私有部署没应用过;
- 明确写了 Goose 保持严格(没有
WithAllowMissing),已经应用到 26 却缺 24/25 的库应当正确地在启动时失败,并且"不要改它的迁移历史、不要重排、不要倒序应用来凑一个通过"; - 还明确声明这份文档本身不批准查询策略变更、不豁免评审闸门、不授权部署。
配套 TestMigrationUpgradeSequence 也不是摆设:不开数据库就能拒绝编号缺口(TestMigrationFilesContiguous)、拒绝已有 Goose 历史的库、强制 _test 库名、从 23 一级一级升到分支声明的 head、每级都校验"应用历史完整 + 数据保留",在 ≥24 校验词法回填、≥25 校验三个 HNSW 索引有效、≥26 校验去重与唯一约束拒绝,最后还要让生产启动路径 DB.Migrate 原样接受升级后的历史。
这正是这条链需要的测试。它把"栈"从一个口头约定变成了 CI 里会失败的断言。
二、路由接入是完整的,不是只加了枚举
我把每个入口都核了一遍:search.go 新增 RouteLexical 常量与 searchLexical 实现;api.go 的 bad_route 白名单与错误文案同步;managed_embeddings.go 让 lexical 与 visual 一样不触发托管文本 embedding 提供器(这是"无模型"的关键,否则省了 worker 却去调 provider 就自相矛盾);CLI 的 --route 帮助文案;MCP mem_search 的 enum;SPEC 与 docs/mcp.md 也都改了。
MCP 那条测试也是真的在验证转发,而不只是读 schema 文本:它用 httptest 接住 /v1/search,断言 route/scope/type/limit 都被原样转发,并且断言 tools/list 里 mem_search 的 enum 恰好是 4 个值(少一个就失败)。
三、必须改的一条:0024 的注释声称覆盖 path,实际只覆盖 name
server/internal/db/migrations/0024_files_lexical_search.sql 的注释写:
Mirrors the FTS + trigram shape already established for memories (0008) so that filename and path substring search works without an embedding worker.
但生成的列和索引都只覆盖 name:
ADD COLUMN IF NOT EXISTS search_tsv tsvector GENERATED ALWAYS AS (
to_tsvector('simple', coalesce(name, '')) -- 只有 name
) STORED;
CREATE INDEX ... ON files USING gin (lower(name) gin_trgm_ops); -- 还是只有 namesearchLexical 里也确实只有 strpos(lower(f.name), ...)、f.search_tsv @@ ...、word_similarity(..., lower(f.name))——没有任何一处碰 f.path。所以按路径片段检索在 lexical 路由下是搜不到的。
同一份 PR 里 CHANGELOG 写的是 "FTS + trigram over files.name"、SPEC 写的是"文件名无模型词法召回"——这两处是对的,只有迁移注释说多了。二选一:把注释改成只提 name(推荐,改动最小且与既有说法一致),或者真的把 path 纳入(那就是功能扩张,需要单独讨论,PathPrefix 过滤跟"检索 path"是两回事,别混)。
四、非阻塞,但请知悉的两点
- 这次迁移在生产库上不是无感的。
ALTER TABLE files ADD COLUMN ... GENERATED ALWAYS AS (...) STORED会让 PostgreSQL 重写整张files表并为每一行计算 tsvector,两个CREATE INDEX也都不是CONCURRENTLY。也就是说迁移期间files上会有一段排他锁。个人盘规模可以接受,但建议在docs/MIGRATION_SEQUENCE.md或 DEPLOYMENT 里写一句预期窗口,让自建部署的人有心理准备。 exact_phrase这个名字其实测的是子串(strpos(lower(f.name), lower($1)) > 0),不是 tsvector 意义上的短语匹配。它作为第一档打分是合理的,只是名字容易误导,建议改成name_contains之类,或加一句注释。- trigram 阈值
>= 0.12相当宽松,word_similarity在这个量级会放进不少弱相关结果。这是召回取向的取舍,我不反对;只是如果后面有人抱怨"搜什么都出来",第一个该调的就是它。
五、我没有声称验证的事
本机没有 PostgreSQL + pgvector,我没有跑过 TestMigrationUpgradeSequence 或 TestLexicalSearchWithoutWorker。以上是逐行读代码、迁移与测试得出的结论,不是运行时观测。
(本次为独立评审:我不是本 PR 作者,也不是其 head 的推送者。draft 状态下我不投批准票;第三节那条改完后我再来复评。)
…ytefolk#197) Three comments referenced "migration 0024" for the HNSW index, but 0024 is the lexical search migration from bytefolk#194. The HNSW indexes live in 0025. Fix the number in 0001_init.sql, 0019_versioned_index_generations.sql and face.go.
…ly (bytefolk#194) The migration comment claimed "filename and path substring search" but search_tsv and the trigram index only cover name. No code in the repo touches f.path for lexical search. Fix the comment to match reality.
已修复:migration 注释
|
PeterGuy326
left a comment
There was a problem hiding this comment.
Current-head follow-up review at db452f6:
The previous blocking documentation mismatch is resolved: migration 0024 now accurately states that lexical substring search covers the filename/name column, not path. I rechecked the route integration and ran the affected Go packages locally; all passed.
I am not approving or recommending merge yet because the PR remains Draft, is currently DIRTY against main, and has no fresh exact-head checks. Please resolve the base conflict/update, run exact-head CI (including the populated migration sequence test), then request final review. Non-blocking notes remain: document the migration lock/window, and consider renaming exact_phrase to name_contains.
db452f6 to
25a50ea
Compare
Review SummaryStatus: LGTM ✅ Reviewed the lexical search corrections:
The implementation correctly handles:
No issues found. Ready to merge once #197's test fix is resolved. |
waterbro-8
left a comment
There was a problem hiding this comment.
Reviewed as the #176 lexical-lane implementation.
What holds:
- Migration 0024 mirrors memories FTS+trigram on
files.nameonly (filename lane, not extracted body text). That is a conservative, model-free degrade path. - MCP
tools/listgrows alexicalroute with tests. - Sequential-upgrade guard is documented; deployment order is recorded as #194 → #197 → #195.
I am not treating this as a retrieval-quality claim. Filename FTS is enough to answer “is a model-free file lane intended?” with “yes, for names.” Merge this before #197 so migration numbers stay ordered.
) (#196) ## Linked draft successor — original #184 remains open Refs #175. Preserves @sun-970 / liyuanyang's authored chain through `89bb12bdf2ae5edbdfa17260953ec6352ab9239f` for #175. Do not close #184 before a replacement is independently verified and merged. This draft remains HOLD for live acceptance. Source-fork Git-data writes returned HTTP 404, with repository `push:false`; no ACL override was attempted. The canonical branch preserves original commit hashes and identities. Added blobs, trees, and commits were individually hash-verified; no force update was used. ## Bounded corrections - Fail closed on ambiguous cross-workspace mappings, unknown result paths, malformed responses, non-finite scores, and failed requests; retain an error artifact and exit 2. - Map the shipping folder `path` plus file `name`; do not silently discard unknown hits or infer tenant identity from snippets. - Vector mode sends `route=text`; lexical mode sends `route=lexical` with null provider/model/dimension metadata. Do not claim `auto` is lexical/vector hybrid. - Reject structured-memory queries this file-search endpoint cannot serve. Document the existing file-only `profile-text-v1` fixture as the bounded corpus. - Remove hostname collection and invented provider/index identity. Configuration labels remain explicitly operator-declared, not server-verified. - Carry #192 audit remediation as a separate `cherry-pick -x` of `11e02e21ef2c3dbd2dae26e4376872e54e78ecb5`; audit threshold unchanged. ## Validation at `651bec1679c50a9cd07cf77b27b5556c20e3273e` - PASS: Python 3.11.14, `python3.11 -m unittest discover -s benchmarks/recall/tests`: 39 tests, including the reproduced fail-closed regressions and a loopback HTTP fixture. - PASS: `python3.11 -m benchmarks.recall verify`: deterministic harness and intentional leakage failure gate. - PASS: Web audit with the explicit shared fix. - NOT VERIFIED: real memd retrieval, actual embedding-provider quality, real index selection, production latency, or full structured-memory corpus acceptance. The HTTP handler is a fixture, not memd; its timing is not live benchmark evidence. ## Exact remaining live prerequisites (no external provider authorized) 1. An isolated, authorized test deployment of real memd and its Worker, with PostgreSQL/pgvector and ingest dependencies configured, plus a token verified to belong to the test workspace. No existing user deployment or provider credentials have been used. 2. Ingest all five synthetic files from `benchmarks/recall/data/profile-text-v1/corpus.jsonl`, preserving their full paths and contents, into that workspace. The producer is not an ingestor. Confirm indexing completed and file/result identities match the fixture. 3. For the bounded fixed-text experiment, use the same locally available, explicitly selected 768-dimensional text embedding model for corpus and query. Verify the corpus/provider metadata and which active generation or fixed table the server actually uses. A label passed to the producer proves none of these facts. No paid provider, external endpoint, model download, or provider configuration was enabled by this correction. 4. Execute all four file queries through real `/v1/search` with `--mode vector`, retain sanitized rankings, then score with `run --rankings` and record the exact memd head, actual model/dimension/index, environment, and errors. An empty/error run does not satisfy live acceptance. 5. Model-free lexical is a separate optional real-server experiment requiring #183's server capability (draft successor #194); it cannot establish vector quality. Full v1 structured-memory acceptance remains unsupported by this producer and must not be reported as passed. Fresh exact-head CI and independent review/human approval remain separate required gates. No fake or paid live run is substituted for the missing evidence.
## Summary Completes the work [#213](#213) left undone against [#173](#173). - Migration **0025** (0024 is already lexical from #194) adds cosine HNSW indexes on `embeddings_text` (768), `embeddings_visual` (512), and `embeddings_face` (512) with `vector_cosine_ops`. - Shipping **text** search no longer uses `DISTINCT ON (f.id) ORDER BY f.id, distance` as the primary plan. That shape cannot use HNSW. It now walks `ORDER BY embedding <=> $1 LIMIT n`, keeps the first sighting of each file, excludes selected files, and **falls back** to the original exact `DISTINCT ON` query if a bounded scan underfills (one file owning many near chunks). - Relator text neighbors use the same continuation/fallback. - Visual already matched HNSW. Face clustering stays in-process; the face index is DDL only. - `index_generation_vectors` is not indexed (scope boundary). - Transactional `CREATE INDEX` (not `CONCURRENTLY`): a failed concurrent build leaves an `INVALID` index that `IF NOT EXISTS` will skip. - Wrong dimensions fail at the `vector(N)` column. Recall is **not** claimed; harness is [#175](#175). Does not reopen #213. #197 remains the earlier HOLD attempt; this branch is based on current `main` (schema 24) and takes 0025. ## Changes - `server/internal/db/migrations/0025_ann_hnsw_indexes.sql` - `server/internal/search/search.go` — text continuation + exact fallback - `server/internal/relator/relator.go` — same policy - `TestHNSWMigrationPostgres` — populated 24→25→24→25, ingest, dimension rejection, EXPLAIN - `TestTextANNFileSemanticsPostgres` — 101-chunk file still returns 10 eligible files - `scripts/verify_hnsw_indexes.sh` + `scripts/verify.sh` head 25 - `docs/VALIDATION_HNSW.md`, SPEC, CHANGELOG ## Validation ledger | ID | Criterion | Command | Status | | --- | --- | --- | --- | | V1 | Server builds, unit contracts | `make test-server` | Pending CI (sandbox has Go 1.22; module requires 1.25) | | V2 | Worker | `make test-worker` | Not affected | | V3 | Web | `make test-web` | Not affected | | V4 | Race | `make test-race` | Pending CI | | V5 | Fresh schema, rollback, PostgreSQL | `make test-integration` | Pending CI — `EXPECTED_MIGRATION_HEAD=25`; `TestHNSWMigrationPostgres` + `verify_hnsw_indexes.sh` | | V6 | DB race | `make test-integration-race` | Pending CI | | V7–V9 | Acceptance / MCP / visual quality | — | Not affected | | V10 | Recall | `make test-recall` | Not measured. Recorded harness: #175 | | Text semantics | 101-chunk file still yields k files | `TestTextANNFileSemanticsPostgres` | Pending CI | | Planner | EXPLAIN uses HNSW for text cosine-order and visual | `TestHNSWMigrationPostgres` + `scripts/verify_hnsw_indexes.sh` | Pending CI. No `enable_seqscan=off`. | Local sandbox could not run Docker or download Go 1.25, so EXPLAIN was not executed here. CI `memory-validation.yml` / `verify.sh integration` is the evidence path. ## Design notes - Defaults `m=16`, `ef_construction=64`. No iterative-scan GUC. - Empty `uuid[]` exclude lists are never sent as SQL NULL (`ANY(NULL)` would drop all rows). - Editing 0001/0019 is comment-only. Goose does not checksum like Flyway. Fixes #173 --------- Co-authored-by: waterbro-8 <318569545+waterbro-8@users.noreply.github.com>
Linked draft successor — original #183 remains open
Refs #176.
Current follow-up:
c0421168bd145077bf164f91c0d2b454a79760eftools/listnow includeslexical. The in-process MCP regression reproduced the missing enum before the fix and now verifies both schema andtools/callHTTP forwarding. All 9 MCP tests pass on Linux; affected-package vet passes.AllowMissingoption or migration renumbering.docs/MIGRATION_SEQUENCE.mdfor deployment and existing-gap recovery boundaries.2f2e965b6794863d7f5a38a748262da4691beb35; this final head only corrects the documented verification command toscripts/verify.sh integration. Fresh exact-head CI and independent review remain required. The earlier evidence below is historical, not a fresh-head approval.Preserves @sun-970 / liyuanyang's complete authored chain through
13ebe9efa6ba3c733199d374e8db3d107f598ca2for #176. This draft makes the bounded reviewed corrections accessible; it does not replace independent review, CI, or human approval. Do not close #183 before a replacement is verified and merged.The original fork's Git-data write returned HTTP 404 and its repository permissions report
push:false. No ACL override was attempted. This canonical branch preserves original commit identities and hashes. Added blobs, trees, and commits were checked against local Git hashes; no force update was used.Corrections
scripts/verify.sh: expect migration 24, matching this branch's lexical migration. The former expectation of 23 caused the PostgreSQL validation failure.11e02e21ef2c3dbd2dae26e4376872e54e78ecb5via a separatecherry-pick -xcommit. Audit threshold unchanged.Validation at head
3d885a8427e06fd7175011ef0188a06f32028a3fTestLexicalSearchWithoutWorker, five cases: lexical without worker, text/auto fail closed, CJK trigram, path restriction. Go tests cross-compiled for Linux and executed against real disposable PostgreSQL withGOMAXPROCS=2and serial package builds.Original feature scope: model-free file-corpus lexical route, migration 0024, API/CLI/docs, managed-provider bypass. Scope excludes tokenizer, generation lifecycle, and ranking redesign.