feat(graph): close out Wave 7 — delete legacy graphindex + list_entities (W7-10) - #1765
Conversation
…l (W7-10) Final close-out of Wave 7 §K.12: deletes the legacy ``aperag/domains/knowledge_graph/graphindex/`` package, drops the legacy ``graphindex_*`` tables via alembic, and adds the one new Protocol method (``LineageGraphStore.list_entities``) the architect ratified to replace the legacy ``list_entities_for_curation`` / ``get_knowledge_graph`` enumerate-by-label paths the legacy package owned. This commit also folds in the grep-zero verification helper (formerly PR #1763 by 冬柏) with ``_TASK10_LEGACY_DELETED`` flipped to True so the 10-pattern grep-zero contract becomes an active gate in this same PR (architect-preferred atomic close-out, per simple-stable directive #1 — fewer PRs, single CI run). ## Scope (per architect msg=28afe6ab + 4-question Q1-Q4 ratify msg=838d57c3 / msg=f3216dfc) 1. **NEW Protocol method ``LineageGraphStore.list_entities``** (``label / limit / offset`` kwargs) + ``EntityWithLineage`` rows sorted by ``name`` for deterministic pagination. InMemory reference + Postgres / Neo4j / Nebula production backends (mirror ``query_entities_by_keyword`` W6 #33 chunk 2 pattern). 2. **``aperag/domains/knowledge_graph/service.py:get_knowledge_graph`` cutover** — 2-step pipeline replacing the legacy ``GraphIndexService.get_knowledge_graph``: 1. ``store.list_entities(label, limit=query_max_nodes)`` — label-filtered entity list (primary work). 2. ``GraphSearchService.get_subgraph(names, hops=max_depth)`` — optional edge expansion when ``max_depth > 0``. Each layer keeps clean semantics (W7-5 ``get_subgraph`` is anchor-expansion, not label-filter; using it as primary entry would force a wrapper that re-enumerated entities just to compute anchors — drift caught in architect own-up msg=838d57c3 → revise to ``list_entities`` primary). 3. **``CurationEntity`` adapter** (new ``aperag/graph_curation/dto.py``) replacing the legacy ``Entity`` DTO. ``from_lineage(EntityWithLineage)`` constructor adapts the storage view into the shape ``build_candidate_pairs`` / ``_pair_score`` / ``_jaccard`` / ``entity_snapshot`` already accept — production-validated algorithm keeps its 0-signature change (architect Q2 ratify, simple-stable directive #3). 4. **``aperag/graph_curation/service.py`` cutover** — ``accept_suggestion`` and ``generate_run`` migrated off the legacy ``GraphIndexService`` bundle: - ``accept_suggestion`` delegates to ``LineageEntityMerger.merge_entities`` (W7-6, PR #1758) the same way the W7-8 ``GraphService.merge_entities`` route already does; both surfaces converge on a single merge path so user-merge- from-curation vs user-merge-from-graph-view never diverge. - ``generate_run`` signature now takes ``store`` / ``vector_connector`` / ``embedder`` / ``llm`` (architect Q3 ratify) and uses two new helpers: - ``_enumerate_curation_entities`` — paged ``list_entities`` loop adapting each row to ``CurationEntity``. - ``_fetch_shadow_neighbours`` — ANN search via ``VectorStoreConnector`` with the Wave 7 W7-3 3-field payload (``Eq("indexer", "graph_entity")`` filter) replacing the legacy ``find_entity_shadow_neighbors`` that filtered on the deleted ``entity_id`` payload field. 5. **``aperag/graph_curation/integration.py`` rewrite** — ``run_graph_curation_run_sync`` resolves the four Wave 7 deps via the same ``worker_factory`` factories the indexer / curation merger use, with a ``_SyncEmbedderShim`` adapter mirroring the one in ``worker_factory`` for the merge candidate detector. 6. **``build_collection_llm_callable`` relocation** — production call sites (``worker_factory._build_collection_graph_compactor`` / ``_build_collection_summarizer``, ``aperag/graph_curation/lineage_merge.py:build_lineage_entity_merger_for``, ``aperag/graph_curation/integration.py``) all import from the canonical home ``aperag/indexing/llm.py`` (Q3 ratify; the file already exists, the legacy package was just re-exporting). 7. **Legacy package + tests deleted**: - ``aperag/domains/knowledge_graph/graphindex/`` (entire package) - ``tests/unit_test/graphindex/`` (entire dir) - ``tests/integration/compat/test_graph_compat.py`` (replaced by ``test_lineage_graph_compat.py`` in W7-1) 8. **Alembic drop migration** ``c7e3a1b9f4d6`` removes ``graphindex_chunks`` / ``graphindex_edges`` / ``graphindex_nodes`` plus their indexes / unique constraints. Hard-cut policy per spec §K.12.12: legacy graph indexing was gated behind ``enable_knowledge_graph=False`` until Wave 4, then never wired into the new pipeline (``run_index_document_sync`` had 0 production callers since Wave 4 hard-cut), so the tables are empty across every deployment. Downgrade recreates empty schema. 9. **Test rewrites** — three test files that consumed the legacy ``Entity`` DTO got updated: - ``tests/unit_test/graph_curation/test_service.py`` / ``test_candidate_generation.py`` — switched to ``CurationEntity as Entity``. - ``tests/unit_test/service/test_search_graph_contract.py`` — rewritten to consume ``EntityWithLineage`` / ``RelationWithLineage`` via the new ``_adapt_lineage_entities`` / ``_adapt_lineage_relations`` adapters (the W7-1 lineage-side replacements for the deleted ``_adapt_nodes`` / ``_adapt_edges`` helpers). - 7 new InMemory ``list_entities`` unit tests in ``tests/unit_test/indexing/test_t1_2_graph.py`` covering empty-collection, sort, label filter, pagination, zero-or-negative limit, negative offset, compacted forward-compat. ## §K.12 invariant cross-check | # | Invariant | This PR | |---|-----------|---------| | 1 | L1 graph data not polluted | ✅ ``list_entities`` is read-only; storage view → adapter projection only | | 2 | L1 → L2 single-direction derive | ✅ no derived writes | | 3 | Compactor before vector embed | N/A — read path | | 4 | Vector store via Adaptor | ✅ ``_fetch_shadow_neighbours`` uses ``VectorStoreConnector`` only | | 5 | payload indexer filter | ✅ ``Eq("indexer","graph_entity")`` filter; no legacy ``entity_id`` payload reference | | 6 | uuid5 vector point id | N/A — read path | | 7 | snapshot-diff lineage name set | N/A — read path | | 8 | alias_map persist orphan | ✅ unaffected; alias_map is W7-6 owned | | 9 | upsert_entity alias redirect | ✅ unaffected; decorator pattern preserved (curation flow uses inner store directly per architect msg=cf860ae4) | | 10 | DB column length application-cap | ✅ no schema CHECK constraints introduced | | 11 | candidate detection write-only | ✅ ``MergeCandidateDetector`` unchanged; ``generate_run`` uses same write boundary | | 12 | grep-zero LightRAG | ✅ `rg "from aperag.domains.knowledge_graph.graphindex" aperag/ tests/` returns only the assertion in ``test_graph_search_migration.py:55``. ``rg "graphindex_*"`` against ``aperag/`` is 0 outside the alembic migration history. The 8 Wave 6-era ``# -- LightRAG-style query layer`` comments + W7-4 line 249 fallback + W7-5 docstrings remain (architect msg=3fe200be — they are descriptive comments referencing design heritage, removable in Wave 8 cleanup if desired) | ## 4-pattern pre-check matrix (paste from PR thread reply) * **P1 v1** — ``rg "from aperag.domains.knowledge_graph.graphindex" aperag/ tests/`` produced 6 production / 11 import sites pre-PR; post-PR matches only ``test_graph_search_migration.py:55`` (the assertion-as-test that itself proves the migration is complete). * **P1 v2** — every method on the legacy ``GraphIndexService`` that a non-legacy caller used is now accounted for: ``merge_entities`` → W7-8, ``get_knowledge_graph`` → 2-step pipeline above, ``list_entities_for_curation`` → ``LineageGraphStore.list_entities`` + ``CurationEntity.from_lineage``, ``find_entity_shadow_neighbors`` → ``_fetch_shadow_neighbours`` via ``VectorStoreConnector``, ``list_labels`` → already migrated W6 #40 + W7-1 ``compacted_description`` field. * **P2** — alembic ``c7e3a1b9f4d6`` drops the legacy tables, alembic env.py loses the legacy ``graphindex.models`` import (replaced with explanatory comment); ``aperag_lineage_*`` tables stay intact. * **P3** — single Protocol method addition (``list_entities``) — implemented across InMemory + 3 production backends + 7 unit tests. ## simple-stable 4-guardrail | Guardrail | Status | |---|---| | #1 不无限扩范围 | ✅ ``list_entities`` is base capability mirroring `delete_entity` / `query_entities_by_keyword`; no new endpoints, no new schema tables | | #2 尽快上线 | ✅ single PR closes Wave 7; all 11 prior task PRs already merged | | #3 简单稳定 | ✅ adapter pattern preserves production-validated `build_candidate_pairs`; ``list_entities`` follows existing pagination idiom | | #4 私有化部署免维护 | ✅ alembic auto-drops legacy tables; no operator config; ``list_entities`` uses the same backend factories the rest of Wave 7 wires | ## Test plan - [x] All 1142 unit tests pass (``uv run pytest tests/unit_test/``) - [x] ``alembic upgrade head --sql`` generates the expected ``DROP INDEX`` / ``DROP TABLE`` cascade - [x] ``alembic heads`` resolves to single head ``c7e3a1b9f4d6`` - [x] ``ruff format --check`` / ``ruff check`` clean on touched files - [ ] CI compat-graph + e2e-http stages — both gated post-merge - [ ] Pair with 冬柏's grep-zero helper PR #1763 — flip ``_TASK10_LEGACY_DELETED=True`` once both PRs merge Closes Wave 7. Next: architect final review per spec §K.12.12. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> ## Fold-in: grep-zero verification helper (formerly PR #1763) Per architect ratify + 冬柏 authorization, PR #1763 is folded into this commit instead of shipping as a separate PR. Contents: * **``tests/integration/test_w7_grep_zero_legacy_graphindex.py``** (NEW, 343 LOC) — 10 ripgrep contracts, one per legacy pattern, flipped to active gate (``_TASK10_LEGACY_DELETED = True``). Patterns cover: 1. ``from aperag.domains.knowledge_graph.graphindex`` imports 2. ``graphindex_(nodes|edges)`` table names (excludes the migration script itself) 3. bare ``import aperag.domains.knowledge_graph.graphindex`` 4. ``_sync_entity_relation_vectors`` (W7-3 superseded) 5. ``_compact_oversized_descriptions`` (W7-2 superseded) 6. ``_summarize_description`` (W7-2 superseded) 7. ``_fallback_truncate`` (renamed-and-kept on new ``GraphIndexCompactor`` — exception list documents the new home) 8. ``_delete_removed_shadow_vectors`` (W7-3 superseded) 9. ``GraphSearchContract.query_context`` (port name kept on the retrieval Protocol; legacy ``GraphIndexService.query_context`` historical-context comments allow-listed) 10. ``GraphIndexService.merge_entities`` (legacy class binding; historical-context comments in lineage_merge.py + test_wave7_task8_wiring.py allow-listed) * **Self-exclusion**: ``_rg_count`` always excludes this helper file itself (every pattern is named in the docstring + assertion call site, which would otherwise self-trigger). * **``aperag/mcp/server.py``** — bundled ruff-format/import-sort drift fix (post-#1762/#1759 leftover that pre-commit catches). Kept here so the close-out PR lands cleanly through ``make lint``. ## Test plan (final) - [x] 1141 unit tests pass (``uv run pytest tests/unit_test/``) - [x] 10/10 grep-zero integration tests pass (``uv run pytest tests/integration/test_w7_grep_zero_legacy_graphindex.py``) - [x] ``alembic upgrade head --sql`` generates the expected ``DROP INDEX`` / ``DROP TABLE`` cascade - [x] ``ruff format --check`` / ``ruff check`` clean on touched files - [ ] CI e2e-http-smoke + e2e-http-provider — gated post-merge Closes Wave 7. Next: architect final review per spec §K.12.12. Co-Authored-By: 冬柏 <noreply@anthropic.com> Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
720613e to
0de5f13
Compare
earayu
left a comment
There was a problem hiding this comment.
🟢 LGTM ✅ (huangheng pass-1, per spec §K.12.11) — GitHub 不允许同账号 approve;verdict = Wave 7 close-out ready to merge
Wave 7 物理交付 PR — legacy graphindex/ 包整删 (8000+ LOC) + list_entities Protocol 加(4 backend + InMemory) + get_knowledge_graph 2-step pipeline + 4 graph_curation 文件迁移 + CurationEntity adapter + worker_factory 移到 canonical home + alembic drop 老表(含 reversible downgrade) + grep-zero helper fold-in (10/10 ripgrep assertions, reality-based exceptions explicitly documented) + format sweep + Co-Authored-By 冬柏。1141 unit tests + 10 grep-zero tests + ruff clean + alembic SQL verified。
12-invariant cross-check (Wave 7 close-out scope)
| # | Invariant | This PR | 验证依据 |
|---|---|---|---|
| 1 | L1 graph data 不污染 | ✅ | list_entities 返 EntityWithLineage storage view; kg.jsonl EntityRecord raw extract DTO 不被改 |
| 2 | L1 → L2 单向派生 | ✅ implicit | list_entities 仅 read,不破坏 derive direction |
| 3 | Compactor 顺序 | n/a | task #3 范畴 |
| 4 | Vector store via Adaptor | ✅ implicit | get_knowledge_graph 通过 GraphSearchService.get_subgraph 走 Adaptor |
| 5 | payload indexer="graph_entity" filter |
✅ implicit | get_subgraph internally filters |
| 6 | uuid5 deterministic id | n/a | task #3 范畴 |
| 7 | snapshot-diff via name set | n/a | task #3 范畴 |
| 8 | alias_map orphan persist | ✅ | post-delete legacy 后 alias_map 表 + decorator wrap 仍 active (verified by surviving worker_factory _build_lineage_graph_store) |
| 9 | upsert 透明 alias redirect | ✅ | post-delete decorator 仍 wrap inner store; list_entities 走 inner read 不需 redirect (read 路径设计正确) |
| 10 | DB column length cap | ✅ | compacted_description 列保留;alembic c7e3a1b9f4d6 仅 drop legacy graphindex_* 表,不动 lineage 表 schema;downgrade 重建空 schema-only legacy 表为 rollback safety |
| 11 | 候选检测仅写不自动合并 (D-3) | ✅ | MergeCandidateDetector 仍 read-only 写 PENDING(task #4 不 import legacy);graph_curation/service.py cutover 仍维持 user-driven start_run flow |
| 12 | 命名 grep-zero LightRAG | ✅ 10/10 | grep-zero helper test_w7_grep_zero_legacy_graphindex.py 10 patterns 全 0 hits; rg "from aperag.domains.knowledge_graph.graphindex" → 0 ✅ |
4 architect-final-lock 实施 verification
| Q | Lock | 落点 |
|---|---|---|
Q1 get_knowledge_graph 2-step |
list_entities(label=X) primary + get_subgraph 可选 |
service.py 走 _adapt_lineage_entities + _adapt_lineage_relations,2-step pipeline 干净 |
Q2 CurationEntity adapter |
aperag/graph_curation/dto.py |
from_lineage(EntityWithLineage) 静态 method;build_candidate_pairs 0 签名改 |
Q3 aperag/llm/integration.py |
new file | worker_factory import switch 完成 |
| Q4 limit/offset pagination | mirror query_entities_by_keyword |
list_entities(label, limit=1000, offset=0) 4 backend impl 一致 |
grep-zero helper 10 patterns + reality-based exceptions verification
10 patterns 全 0 hits,每个 exception 有 explicit 理由:
| Pattern | 内容 | Reality-based exception |
|---|---|---|
| 1 | from aperag.domains.knowledge_graph.graphindex import |
test_graph_search_migration.py (sister negative-assertion test) ✅ |
| 2 | graphindex_nodes/graphindex_edges/graphindex_chunks table refs |
exclude aperag/migration/** (alembic drop 自身) ✅ |
| 3 | import graphindex bare |
n/a |
| 4 | _sync_entity_relation_vectors |
n/a |
| 5 | _compact_oversized_descriptions |
n/a |
| 6 | _summarize_description |
n/a |
| 7 | _fallback_truncate |
exclude graph_compactor.py + unit test (renamed-and-kept on new GraphIndexCompactor) ✅ |
| 8 | _delete_removed_shadow_vectors |
n/a |
| 9 | query_context |
exclude retrieval/ports.py + pipeline.py + indexing/graph.py (port semantics deliberate keep / historical comments) ✅ |
| 10 | GraphIndexService.merge_entities |
exclude lineage_merge.py + test_wave7_task8_wiring.py (historical-context docstrings "Replaces the legacy ...") ✅ |
每条 exception 都有 docstring 注释解释 reason。grep-zero 是 intent-driven (no legacy CALLABLE references),不是字面 word elimination — 这个区分是正确的产品意识。👍
Alembic safety verification
- upgrade
c7e3a1b9f4d6: drop 3 legacy 表 + 8 indexes,CASCADE-safe order (nodes / edges / chunks) - downgrade: 重建 legacy 表 + indexes empty schema-only,rollback safe
- migration docstring 说明 hard-cut policy (legacy 表自 Wave 4 起 0 production data)
per feedback_alembic_drift_check.md:建议 Bryce 在 PR push 前实测 alembic upgrade head + alembic check + alembic downgrade -1 + alembic upgrade head 4 步 runtime 跑(不只 SQL preview)。PR 已 paste alembic upgrade --sql verified,但 alembic check 实测建议 in CI gate;如已在 CI 跑则 ✅。
simple-stable 4-guardrail
PR description 4 项全显式 ✅:
- #1 不无限扩范围:
list_entities是 base capability 非 expansion (Q1 architect ratified) - #2 尽快上线: 单 PR 全 close-out + helper fold-in
- #3 简单稳定: 走现有 pattern (mirror
query_entities_by_keyword/ 复用 production-validatedbuild_candidate_pairs) - #4 私有化部署免维护: operator 0 schema migration burden (alembic drop 老表是 cleanup 不是新表)
修完会 LGTM 的清单
实际上已经可以 merge ✅。0 architecture concern。
1 个 minor LightRAG word 残留观察 (非阻塞,sediment 候选)
post-delete 后仍有 10 处 LightRAG 字面 mention(service.py 2 / schemas.py 1 / db/models.py 1 / api/routes.py 1 / pipeline.py 2 / mcp/tools/graph_tools.py 1 / schema/common.py 2)— 全是 historical context comments / docstrings / invariant compliance annotations,不是 callable code 引用。
grep-zero helper 10 patterns 是 intent-driven (no legacy CODE references),不检字面 word — 这个区分正确。但 spec §K.12.10 strict reading "代码 + REST API + MCP tool name 中完全去掉 LightRAG" 角度 borderline。
建议(不阻塞 merge):Wave 8 candidate W8-7 加一项 "documentation narrative LightRAG word sweep"(替换为 neutral phrasing 如 "legacy graph workflow"),或 forever-OK 因都是 historical context。架构师拍板。
Wave 7 close-out final review trigger
PR #1765 merge 后:
- Wave 7 PR merge tally: 13 PR merged (2 spec PR + 11 task PR: #1-#10 + #11)
- legacy graphindex/ 0 LOC code ✅
- alembic head 含 graphindex_* drop migration ✅
- grep-zero 10/10 物理钉死 living artifact ✅
- task #11 narrative-correctness 9-step happy path + W8-3 trigger pin + failure-mode ✅
- @符炫炜 trigger Wave 7 final review (mirror Wave 5/6 close-out msg pattern)
- @earayu2 sign-off
- Wave 8 candidate list (W8-1~W8-7 含我刚加的 W8-7) 交付 next wave kickoff
@bryce 30min 兑现 1.5-2 day ETA + 完整 fold-in #1763 + reality-based exceptions 设计精细 + 1141 unit + 10 grep-zero + ruff + alembic 全验证 — Wave 7 close-out implementer 高效率 + 高质量。👍
@符炫炜 LGTM,CI 绿后即可 merge ratify。
@不穷 推进 task #10 → done after merge → architect Wave 7 final review trigger → 整 Wave 7 ship。
…W8-7) (#1770) Wave 8 task #16 (W8-7) — sweep historical-context "LightRAG" mentions in production code + tests, replacing each with neutral graph-RAG phrasing per `notes/wave8-candidate-list.md` close-out sediment from huangheng's task #10 PR #1765 CR. ## Why The Wave 7 close-out grep-zero contract (PR #1765's folded-in helper) is intent-driven: it asserts no legacy *callable code* still binds to ``GraphIndexService.*`` or ``graphindex_*`` symbols. It does NOT check the literal word "LightRAG" because comments naming a prior reference implementation aren't a behavioural regression. But spec §K.12.10 strict reading wants the legacy provenance fully neutralised, and ~30 LightRAG word mentions remained in historical- context comments / docstrings / section headers across 20 files. This PR completes that last-mile naming sweep so the codebase reads as a self-contained graph-RAG implementation, not "ours, descended from LightRAG". ## Scope (literal word sweep only — 0 functional change) * Production code (16 files): - `aperag/schema/common.py` × 2 (KG config docstring) - `aperag/service/prompt_template_service.py` × 2 (Agent / graph indexing prompt resolver) - `aperag/migration/sql/extensions_init.sql` (legacy table-name list in pgvector init comment) - `aperag/indexing/graph_storage/{postgres,neo4j,nebula}.py` section headers `LightRAG-style query layer` → `Graph-RAG query layer` - `aperag/indexing/graph_search_service.py` × 5 (module docstring, class docstring, compose_context docstring, llm reservation comment) - `aperag/indexing/graph_extractor.py` × 3 (module docstring, failure semantics paragraph, JSON-shape comment) - `aperag/indexing/graph.py` × 5 (extractor seam paragraph, Wave 6 #33 query-layer header, dropped-vector-recall paragraph, chunk-2 real-impls header, production-extractor comment) - `aperag/indexing/worker_factory.py` (Wave 4 T1 self-disable note) - `aperag/mcp/tools/graph_tools.py` (invariant #12 mention) - `aperag/domains/knowledge_graph/api/routes.py` (kg-eval historical-removal comment) - `aperag/domains/knowledge_graph/service.py` × 2 (UI adapter docstring, weight-field artefact note) - `aperag/domains/knowledge_graph/schemas.py` (invariant #12 mention) - `aperag/domains/knowledge_graph/db/models.py` (curation audit docstring) - `aperag/domains/retrieval/pipeline.py` × 2 (compose docstring, backend dispatch docstring) * Tests (4 files): - `tests/unit_test/test_modularization_boundaries.py` - `tests/unit_test/indexing/test_graph_search_service.py` - `tests/unit_test/indexing/test_wave7_task8_wiring.py` - `tests/unit_test/indexing/test_lineage_query_protocol.py` × 3 ## 12-invariant cross-check (Wave 7 §K.12) * #12 (grep-zero LightRAG word): ✅ — `rg LightRAG aperag/ tests/` returns 0 hits post-sweep. Spec docs / READMEs / blueprint / rewrite-plan retain the word as historical attribution per spec-allowed list. * All other invariants: n/a (pure docstring / comment edit, 0 functional change). ## 4-pattern pre-check matrix * Pattern A (kg.jsonl shape): unchanged * Pattern B (Lineage SET semantics): unchanged * Pattern C (Cypher LIST<MAP>): unchanged * Pattern D (vector 3-field payload): unchanged ## Simple-stable 4-guardrail * No new public API surface * No new dependency * No frontend change * No config change ## Test plan - [x] `rg LightRAG aperag/ tests/` → 0 hits - [x] `uv run pytest tests/unit_test/` — 1141 unit tests pass (no behavioural change) - [x] `uv run pytest tests/integration/test_w7_grep_zero_legacy_graphindex.py` — 10/10 pass (intent-driven gate unaffected) - [x] `uv run ruff format --check` / `ruff check` — clean
…(W7-10 drift) (#1773) ## What broke Wave 7 task #10 (PR #1765) added ``list_entities`` to the ``LineageGraphStore`` Protocol + 4 backends. The ``LineageGraphStoreWithAliasRedirect`` decorator was NOT updated, and the spelled-out passthrough invariant test (``test_decorator_passthrough_for_non_redirected_methods``) only covers the methods that existed when it was written — so the gap landed silently. Production crash on ``GET /api/v2/collections/{id}/graphs``: ``` AttributeError: 'LineageGraphStoreWithAliasRedirect' object has no attribute 'list_entities' File "aperag/domains/knowledge_graph/service.py", line 149, in get_knowledge_graph entities = await store.list_entities(label=normalized_label, limit=query_max_nodes) ``` This is also the e2e-http-provider failure visible on every Wave 7+8 PR including the just-merged #1771 / #1772 — pre-existing on main since W7-10 close-out, masked because the e2e-http-smoke shapes don't exercise the graph view endpoint. ## Fix 1. Add ``list_entities`` passthrough method on the decorator. Filter is by ``label`` (entity type) + pagination, not by name — rows are already canonical (alias rows live in ``aperag_lineage_entity_alias``, not in the entity table). Same passthrough rationale as ``list_entity_labels`` / ``query_entities_by_keyword``; module docstring updated. 2. Add ``list_entities`` row to the spelled-out passthrough test. 3. **NEW meta-invariant test**: ``test_decorator_covers_every_lineage_graph_store_method`` — introspect ``LineageGraphStore`` Protocol via ``dir()`` and assert the decorator implements every public method. Catches the "decorator missing method" half of the drift gap that the spelled-out test couldn't catch (because spelled-out tests only cover methods that existed when written). A future Protocol addition without a decorator update now fails CI immediately with a clear "missing method(s): X" assertion. ## Hard-gate format checklist (Wave 7+8 mirror) ### 4-pattern pre-check matrix - [x] **#1 不丢失好的算法和设计**: passthrough rationale preserved per module docstring (filter-by-label not filter-by-name). - [x] **#2 尽快上线**: 1 fix + 2 new tests (1 spelled-out row + 1 meta-test). No new abstraction. - [x] **#3 简单稳定**: passthrough is byte-for-byte forward, mirror of ``list_entity_labels``. Meta-test uses stdlib introspection, no new dep. - [x] **#4 私有化部署免维护**: zero new env / config / dep. Backend- portable (decorator wraps the 4-backend Protocol; the actual ``list_entities`` implementation already covered by 4-backend compat tests). ### Simple-stable 4-guardrail - [x] No new abstraction. - [x] No backward-compat shim. - [x] No silent behavior change — fix is additive (method that didn't exist now exists; behavior matches 4-backend Protocol contract). - [x] Meta-test prevents this exact class of drift recurring. ## Test plan - [x] ``uv run pytest tests/unit_test/indexing/test_alias_redirect_store.py`` — **16 passed** (was 15, +1 meta-test, +1 spelled-out ``list_entities`` row covered by existing test function). - [x] ``uvx ruff check`` — clean. ``uvx ruff format --check`` — clean. - [ ] Production verify: ``GET /collections/{id}/graphs`` returns 200 after this lands (the original crash from main e2e-http-provider log) — will be visible on next PR's e2e-http-provider checks. Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Summary
Wave 7 close-out (task #10): hard-deletes the legacy
aperag/domains/knowledge_graph/graphindex/package and migrates all remaining call-sites to the new lineage-based graph layer. Adds thelist_entitiesProtocol method (Q1 final ratify) soGraphService.get_knowledge_graphcan run the 2-steplist_entities + get_subgraphpipeline. Drops legacygraphindex_*tables via alembicc7e3a1b9f4d6.Fold-in: this PR also folds in @冬柏's grep-zero verification helper (formerly PR #1763) with
_TASK10_LEGACY_DELETED=Trueflipped, so the 10-pattern grep-zero contract is an active gate on this same PR. Architect-preferred atomic close-out, per simple-stable directive #1.Scope (9 numbered items)
list_entitiesProtocol method — added toaperag/indexing/graph.py+ 4 backends (InMemory / Postgres / Neo4j / Nebula). Sorted-by-name, optional label filter, offset/limit pagination.GraphService.get_knowledge_graph2-step rewrite —list_entities(label=…)then optionalget_subgraphfor edge expansion. Removed_adapt_nodes/_adapt_edges, added_adapt_lineage_entities/_adapt_lineage_relations.graph_curationmigration — 4 files (candidate_generation.py,merge_candidate_detector.py,service.py,lineage_merge.py) cut over to lineage layer. Newaperag/graph_curation/dto.pyCurationEntityadapter withfrom_lineage()preservesbuild_candidate_pairs0-signature change (architect Q2 ratify).service.generate_runnow takes(store, vector_connector, embedder)instead ofgraph_service.worker_factoryimport switch —build_collection_llm_callablenow imported fromaperag.indexing.llm(canonical) instead of legacy graphindex re-export.c7e3a1b9f4d6— dropsgraphindex_chunks,graphindex_edges,graphindex_nodes(+ indexes). Down-revision recreates empty schema (hard-cut policy, no production data).migration/env.py— removedaperag.domains.knowledge_graph.graphindex.modelsimport; replaced with W7-10 explanatory comment.aperag/domains/knowledge_graph/graphindex/(17 files) +tests/unit_test/graphindex/(8 files) +tests/integration/compat/test_graph_compat.py.test_search_graph_contract.py::test_lineage_adapter_exposes_public_graph_properties_onlynow usesEntityWithLineage/RelationWithLineage+ new adapters. 7 new InMemorylist_entitiestests intest_t1_2_graph.py.graph_curationtests switched toCurationEntity as Entity.tests/integration/test_w7_grep_zero_legacy_graphindex.py(343 LOC) + flag flipped True + reality-based exception list (see commit body for per-pattern exceptions).aperag/mcp/server.pyruff-format/import-sort drift bundled in.12-invariant cross-check (Wave 7 §K.12)
All 12 invariants verified — see commit body. Highlights:
list_entitiesreturnsEntityWithLineage(storage view); kg.jsonl raw extraction untouched._build_lineage_graph_storedecorator wrap remains active;list_entitiesreads through inner store (read path doesn't need redirect).compacted_descriptioncolumn preserved; alembic upgrade/downgrade roundtrip safe.4-pattern pre-check matrix
Simple-stable 4-guardrail
list_entities(already in spec)Test plan
uv run pytest tests/unit_test/— 1141 unit tests passuv run pytest tests/integration/test_w7_grep_zero_legacy_graphindex.py— 10/10 passuv run ruff format --check ./aperag— clean on touched filesuv run ruff check ./aperag— clean on touched filesalembic upgrade --sql c7e3a1b9f4d6— DROP statements verified🤖 Generated with Claude Code