Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ __pycache__/
build/
dist/
*.db
*.d1-export.json
artifacts/d1/
tests/fixtures/.full-*
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ python -m neuron_graph_rag eval

指標は mean reciprocal rank、hit at 3、baseline より expected node の rank が改善した query 数です。この eval は品質ベンチマークではなく、graph 経路が baseline と異なる順位信号を生むことを検証する最小 smoke test です。

## D1 real-corpus fixture

`tests/fixtures/d1_liplus_wiki.json` は github-rag-mcp の本番 D1 から read-only で取得した決定論的な小 fixture です。`diff` と `wiki_doc` の時系列 metadata、Decision Structure の `mention` edge、検索、graph activation、success feedback を統合テストで再現します。

取得 tool は D1 の単一 `SELECT / WITH` だけを許可し、Wrangler が `rows_written=0` を返したことを query ごとに検証します。認証、再取得、schema fingerprint、coverage、既知 gap、完全性の限界は [docs/d1-corpus-fixture.md](docs/d1-corpus-fixture.md) を参照してください。

## Public API

```python
Expand Down
65 changes: 65 additions & 0 deletions docs/d1-corpus-fixture.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# D1 corpus fixture

## 目的と境界

`tests/fixtures/d1_liplus_wiki.json` は github-rag-mcp の本番 D1 から読み取り専用で取得した、小規模な NGR 統合 fixture である。`search_docs` を文書の正本とし、FTS5 virtual table と shadow table は取得しない。`doc_edges` は fixture 内に両端 node がある edge だけを変換する。

D1 は検索用の損失ありスナップショットである。content は切り詰められることがあり、binary / patchless file は diff index に存在しない場合がある。byte-exact な履歴復元の正本は GitHub とする。

## 変換

- `search_docs.vector_id` → `DocumentNode.node_id`
- `search_docs.content` → `DocumentNode.text`
- `content` と派生 `content_fts` 以外の source 列 → node metadata
- `doc_edges.src_vector_id / dst_vector_id` → edge endpoint
- `doc_edges.edge_kind` → `TypedEdge.edge_type`
- node confidence、edge weight、edge factuality → `1.0`

fixture edge の `metadata.source_record` は現在の `mention` と将来の typed edge を source record として区別する。NGR core の `TypedEdge` 自体へ metadata は渡さない。

## 読み取り専用取得

Wrangler の OAuth 認証は host 側で行う。token を引数、環境変数の出力、fixture、provenance へ含めない。取得 tool は任意 SQL を受け取らず、内部 query も単一の `SELECT` / `WITH` 以外を拒否する。各 Wrangler 応答について `rows_written=0`、`changes=0`、`changed_db=false` を検証し、一つでも崩れたら出力しない。

workspace root から次を実行する。

```powershell
python tools/acquire_d1_fixture.py `
--repo Liplus-Project/liplus-language `
--type diff `
--type wiki_doc `
--per-type-limit 3 `
--output tests/fixtures/d1_liplus_wiki.json `
--provenance-output tests/fixtures/d1_liplus_wiki.provenance.json `
--wrangler-project C:\path\to\github-rag-mcp
```

この例は取得時点で未解消の既知 gap がないため、`--known-gap` を指定しない。未解消の gap がある場合だけ、その issue と取得時点の状態を `--known-gap` で追加する。

選択順は `(repo, type, updated_at, vector_id)` で固定する。同一 snapshot と引数から fixture JSON は byte-identical になる。取得時刻は provenance report だけに置く。credential らしい文字列は、node / edge だけでなく source 引数と既知 gap を含む最終 fixture / provenance 全体で `[REDACTED_SECRET]` へ決定論的に置換する。fixture、provenance、合計の置換件数を report に残す。

完全 export や作業中の raw JSON は commit しない。`.gitignore` は `*.d1-export.json`、`artifacts/d1/`、`tests/fixtures/.full-*` を除外する。

## Provenance と coverage 監査

`tests/fixtures/d1_liplus_wiki.provenance.json` は次を保持する。

- source database、repo、type、source schema fingerprint
- type 別の source count、最古 / 最新 `updated_at`、空文字 sentinel を除外した distinct commit count
- fixture の node / edge 数と欠損 endpoint 除外数
- query ごとの zero-write evidence
- 取得日時、既知 gap、完全性の限界、redaction 件数

`known_gaps` は取得時点で未解消の gap だけを保持し、確認済みの gap がない場合は空配列にする。

backfill 完了後は同じ引数で別 path へ再取得し、次で coverage を比較する。

```powershell
python tools/compare_d1_provenance.py previous.provenance.json current.provenance.json
```

`diff` の `source_count_delta`、`distinct_commit_count_delta`、`newest_extended` を監査する。比較結果を確認してから管理対象 fixture と provenance を置き換え、全テストを再実行する。

## 統合検証

`tests/test_d1_fixture.py` は実 fixture を SQLite に ingest し、文書検索、`updated_at / commit_date` metadata、`mention` edge の graph activation、利用 node に対する success feedback と edge reinforcement を再現する。
6 changes: 6 additions & 0 deletions docs/requirements.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
16. MCP adapter は trace、node、enum、stage 順序、idempotency を境界で検証する。
17. 各 MCP tool の model-facing description 自体が、feedback の呼び分けと reinforcement 条件を consuming AI へ伝える。
18. persistent core の trace は自動 expiry しない。retention を設ける deployment は `search` description と output に期限を明示し、expiry 後の feedback を `unknown_trace` とする。
19. github-rag-mcp の D1 `search_docs` を正本として、repo / type / per-type limit と固定順から決定論的な小 fixture を生成できる。
20. `search_docs.vector_id / content` を node ID / text へ、`doc_edges` の両端と `edge_kind` を typed edge へ変換し、欠損 endpoint は node を捏造せず除外理由を記録できる。
21. D1 取得は単一 SELECT / WITH query のみに制限し、各 query の `rows_written=0`、`changes=0`、`changed_db=false` を検証できる。
22. fixture と分離した provenance report に schema fingerprint、coverage、取得時刻、取得時点で未解消の既知 gap、redaction 件数を記録し、再取得前後の count / commit / 最新時刻を比較できる。未解消の既知 gap がない場合は空配列を記録する。

## 4. Constraints

Expand All @@ -50,3 +54,5 @@
- `python -m neuron_graph_rag eval` が baseline hybrid と graph retrieval の比較指標を出力する。
- CI が editable install、test、eval を新規環境で実行する。
- [Optional MCP Feedback Interface](optional-mcp-interface.md) が tool semantics、input、output、failure、core mapping、依存境界、repository 分離条件を定義する。
- `tests/fixtures/d1_liplus_wiki.json` が実 D1 形状から ingest、検索、時系列 metadata、graph activation、success feedback を再現する。
- [D1 corpus fixture](d1-corpus-fixture.md) が read-only 取得、認証境界、provenance、coverage 比較、再取得手順を定義する。
73 changes: 73 additions & 0 deletions src/neuron_graph_rag/d1_fixture.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from __future__ import annotations

import json
from dataclasses import dataclass
from pathlib import Path
from typing import Any

from .engine import NeuronGraphRAG


FIXTURE_SCHEMA_VERSION = 1


@dataclass(frozen=True, slots=True)
class FixtureLoadResult:
node_count: int
edge_count: int


def read_fixture(path: str | Path) -> dict[str, Any]:
with Path(path).open(encoding="utf-8") as stream:
fixture = json.load(stream)
if fixture.get("schema_version") != FIXTURE_SCHEMA_VERSION:
raise ValueError(
f"Unsupported fixture schema version: {fixture.get('schema_version')!r}"
)
if not isinstance(fixture.get("nodes"), list) or not isinstance(
fixture.get("edges"), list
):
raise ValueError("Fixture must contain nodes and edges arrays")
return fixture


def load_fixture(
engine: NeuronGraphRAG, path: str | Path
) -> FixtureLoadResult:
fixture = read_fixture(path)
node_ids: set[str] = set()
for record in fixture["nodes"]:
node_id = str(record["node_id"])
if node_id in node_ids:
raise ValueError(f"Duplicate fixture node_id: {node_id}")
node_ids.add(node_id)
engine.add_document(
node_id,
str(record["text"]),
metadata=dict(record.get("metadata", {})),
confidence=float(record.get("confidence", 1.0)),
)

edge_keys: set[tuple[str, str, str]] = set()
for record in fixture["edges"]:
source_id = str(record["source_id"])
target_id = str(record["target_id"])
edge_type = str(record["edge_type"])
if source_id not in node_ids or target_id not in node_ids:
raise ValueError(
"Fixture edge endpoints must both be present: "
f"{source_id} -> {target_id}"
)
key = (source_id, target_id, edge_type)
if key in edge_keys:
raise ValueError(f"Duplicate fixture edge: {key!r}")
edge_keys.add(key)
engine.add_edge(
source_id,
target_id,
edge_type,
weight=float(record.get("weight", 1.0)),
factuality=float(record.get("factuality", 1.0)),
)

return FixtureLoadResult(len(node_ids), len(edge_keys))
Loading