Skip to content

Commit 3bbabc8

Browse files
kiyotisclaude
andauthored
docs: verify R2 fabrication findings classification (#153) (#172)
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 53cd48b commit 3bbabc8

20 files changed

Lines changed: 2083 additions & 66 deletions

.claude/rules/knowledge-creator.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,14 @@ Each Phase B/D/E/F execution saves metrics to `executions/` directory:
9494
- Test only complex logic that is hard to verify through E2E
9595
- Write for: split criteria, merge edge cases, validation rules, link resolution, boundary values, error cases not reachable via E2E
9696
- Skip: Context properties, simple delegation, E2E happy path coverage
97+
98+
### Pre-PR Requirement
99+
100+
Before requesting PR review for any kc change, all automated tests must pass:
101+
102+
```bash
103+
cd tools/knowledge-creator
104+
pytest
105+
```
106+
107+
If any test fails, fix all failures — including pre-existing ones — before creating the PR. Do not leave known failures unresolved.
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
# Task: Fix E2E test assertions for round-numbered findings and docs count
2+
3+
## Background
4+
5+
PR #172 introduced round-numbered findings files (`{file_id}_r{round_num}.json`) in Phase D.
6+
Phase E reads these files but no longer deletes them (by design — findings history is preserved).
7+
The E2E test `_assert_full_output` still asserts that `findings_dir` is empty after Phase E, which is now incorrect.
8+
9+
Additionally, the docs count assertion was incorrectly changed from `M + 1` to `M` in this PR branch.
10+
Phase M generates M knowledge docs + 1 README.md = M + 1 files total.
11+
12+
## File to edit
13+
14+
`tools/knowledge-creator/tests/e2e/test_e2e.py`
15+
16+
## Change 1: Update `_assert_full_output` signature and findings_dir assertion
17+
18+
### Before (lines 126–155 on main):
19+
20+
```python
21+
def _assert_full_output(ctx, expected, catalog_entries, U, M):
22+
"""全kcコマンド共通の出力検証。
23+
24+
Phase Mまで実行した後の全出力を検証する。
25+
CC呼び出し回数は各テストで個別にアサートするため含まない。
26+
"""
27+
# catalog.json entries
28+
catalog = _load_json(ctx.classified_list_path)
29+
catalog_ids = {f["id"] for f in catalog["files"]}
30+
expected_ids = {e["id"] for e in catalog_entries}
31+
assert catalog_ids == expected_ids, (
32+
f"Catalog IDs mismatch: extra={catalog_ids - expected_ids}, "
33+
f"missing={expected_ids - catalog_ids}"
34+
)
35+
assert len(catalog["files"]) == U
36+
37+
# knowledge_cache_dir
38+
assert _count_json_files(ctx.knowledge_cache_dir) == U, (
39+
f"Expected {U} files in knowledge_cache_dir, "
40+
f"got {_count_json_files(ctx.knowledge_cache_dir)}"
41+
)
42+
for entry in catalog_entries:
43+
cache_path = f"{ctx.knowledge_cache_dir}/{entry['output_path']}"
44+
assert os.path.exists(cache_path), f"Missing cache file: {cache_path}"
45+
46+
# findings_dir empty
47+
if os.path.isdir(ctx.findings_dir):
48+
assert _count_all_files(ctx.findings_dir, ".json") == 0, (
49+
"findings_dir should be empty after Phase E"
50+
)
51+
```
52+
53+
### After:
54+
55+
```python
56+
def _assert_full_output(ctx, expected, catalog_entries, U, M,
57+
expected_findings_count=0):
58+
"""全kcコマンド共通の出力検証。
59+
60+
Phase Mまで実行した後の全出力を検証する。
61+
CC呼び出し回数は各テストで個別にアサートするため含まない。
62+
63+
expected_findings_count: Phase D が保存したラウンド番号付き findings ファイルの
64+
期待数。Phase D/E ループはラウンドごとに findings を保持する設計のため、
65+
findings_dir には processed_files × max_rounds 個のファイルが残る。
66+
"""
67+
# catalog.json entries
68+
catalog = _load_json(ctx.classified_list_path)
69+
catalog_ids = {f["id"] for f in catalog["files"]}
70+
expected_ids = {e["id"] for e in catalog_entries}
71+
assert catalog_ids == expected_ids, (
72+
f"Catalog IDs mismatch: extra={catalog_ids - expected_ids}, "
73+
f"missing={expected_ids - catalog_ids}"
74+
)
75+
assert len(catalog["files"]) == U
76+
77+
# knowledge_cache_dir
78+
assert _count_json_files(ctx.knowledge_cache_dir) == U, (
79+
f"Expected {U} files in knowledge_cache_dir, "
80+
f"got {_count_json_files(ctx.knowledge_cache_dir)}"
81+
)
82+
for entry in catalog_entries:
83+
cache_path = f"{ctx.knowledge_cache_dir}/{entry['output_path']}"
84+
assert os.path.exists(cache_path), f"Missing cache file: {cache_path}"
85+
86+
# findings_dir: round-numbered findings files are preserved
87+
if os.path.isdir(ctx.findings_dir):
88+
actual_findings = _count_all_files(ctx.findings_dir, ".json")
89+
assert actual_findings == expected_findings_count, (
90+
f"findings_dir should have {expected_findings_count} round-numbered files, "
91+
f"got {actual_findings}"
92+
)
93+
# Verify all findings files follow _r{N}.json naming convention
94+
for root, _, files in os.walk(ctx.findings_dir):
95+
for f in files:
96+
if f.endswith(".json"):
97+
assert "_r" in f, (
98+
f"Findings file {f} missing round number suffix (_rN.json)"
99+
)
100+
```
101+
102+
## Change 2: Restore docs count assertion
103+
104+
### Before (current branch):
105+
106+
```python
107+
# docs
108+
docs_count = _count_all_files(ctx.docs_dir)
109+
assert docs_count == M, (
110+
f"Expected {M} doc files, got {docs_count}"
111+
)
112+
```
113+
114+
### After:
115+
116+
```python
117+
# docs (M knowledge .md files + 1 README.md generated by Phase M)
118+
docs_count = _count_all_files(ctx.docs_dir)
119+
assert docs_count == M + 1, (
120+
f"Expected {M + 1} doc files (M={M} knowledge docs + 1 README.md), got {docs_count}"
121+
)
122+
```
123+
124+
## Change 3: Pass `expected_findings_count` at all 5 call sites
125+
126+
Each call to `_assert_full_output` needs `expected_findings_count` added.
127+
The value depends on how many files Phase D processes and how many rounds run.
128+
129+
### TestGen (around line 478):
130+
131+
```python
132+
_assert_full_output(ctx, expected, catalog_entries, U, M,
133+
expected_findings_count=U * ctx.max_rounds)
134+
```
135+
136+
### TestGenResume (around line 538):
137+
138+
```python
139+
_assert_full_output(ctx, expected, catalog_entries, U, M,
140+
expected_findings_count=U * ctx.max_rounds)
141+
```
142+
143+
### TestRegenTarget (around line 598):
144+
145+
```python
146+
_assert_full_output(ctx, expected, catalog_entries, U, M,
147+
expected_findings_count=target_count * ctx.max_rounds)
148+
```
149+
150+
### TestFix (around line 665):
151+
152+
```python
153+
_assert_full_output(ctx, expected, catalog_entries, U, M,
154+
expected_findings_count=U * ctx.max_rounds)
155+
```
156+
157+
### TestFixTarget (around line 722):
158+
159+
```python
160+
_assert_full_output(ctx, expected, catalog_entries, U, M,
161+
expected_findings_count=target_count * ctx.max_rounds)
162+
```
163+
164+
## Why these values
165+
166+
| Test | Phase D processes | Rounds | expected_findings_count |
167+
|------|------------------|--------|------------------------|
168+
| TestGen | All U files | max_rounds (2) | U × 2 |
169+
| TestGenResume | All U files (Phase B skips 1, but D checks all) | max_rounds (2) | U × 2 |
170+
| TestRegenTarget | target_count files only | max_rounds (2) | target_count × 2 |
171+
| TestFix | All U files | max_rounds (2) | U × 2 |
172+
| TestFixTarget | target_count files only | max_rounds (2) | target_count × 2 |
173+
174+
`_copy_state` does NOT copy findings_dir, so target-based tests start with empty findings_dir.
175+
176+
## Verification
177+
178+
```bash
179+
cd tools/knowledge-creator
180+
181+
# UT (should pass in ~20s)
182+
python3 -m pytest tests/ut/ --tb=short
183+
184+
# E2E v6 (should pass in ~3min)
185+
python3 -m pytest tests/e2e/ -k "6" --tb=short
186+
187+
# E2E v5 (should pass in ~4min)
188+
python3 -m pytest tests/e2e/ -k "5" --tb=short
189+
```
190+
191+
All 174 UT tests and 10 E2E tests (5 per version) must pass.

.pr/00172/notes.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# Notes
2+
3+
## 2026-03-12
4+
5+
### Context: R2 Fabrication Findings Background
6+
7+
The large kc run (run_id: 20260309T232615, work1) ran ACDEM with max_rounds=2 on all 421 v6 knowledge files:
8+
- Phase D R1: 48 fabrication findings among 289 "has_issues" files (total 419 files)
9+
- Phase E R1: Fixed 289 files, deleted their findings files
10+
- Phase D R2: 30 fabrication findings among 205 "has_issues" files
11+
- Phase E R2: Fixed 205 files, deleted their findings files
12+
13+
### Problem: R2 Findings Are Gone
14+
15+
Phase E deletes findings files after fixing (`os.remove(findings_path)` in phase_e_fix.py:100).
16+
Phase D R2's findings files for the 205 "has_issues" files were deleted by Phase E R2.
17+
18+
The findings directory (`20260309T232615/phase-d/findings/`) only contains:
19+
- 212 clean files (findings were never deleted - only has_issues files get deleted by Phase E)
20+
- 1 file with a single `section_issue` finding
21+
- Total: 213 files = the clean ones from Phase D R2
22+
23+
The 30 fabrication findings from Phase D R2 are not recoverable from the findings directory.
24+
25+
### Approach: Re-run Phase D on Phase D R2 ISSUE Files
26+
27+
Identified the 205 Phase D R2 ISSUE files from the execution log (lines 889-1097).
28+
Selected 20 diverse representative files from 6+ categories.
29+
Launched subagent to run `python3 tools/knowledge-creator/scripts/run.py --version 6 --phase D`
30+
on the sample in work2.
31+
32+
### Technical Notes
33+
34+
- work2/.lw → symlink to work1/.lw (RST sources)
35+
- Phase D findings write to .logs/v6/{run_id}/phase-d/findings/ (gitignored)
36+
- Knowledge files read from .cache/v6/knowledge/ (post-Phase E R2 state)
37+
- clean_phase not needed for fresh run (new run_id = new findings dir)
38+
39+
### Results
40+
41+
- Batch 1 (run 20260312T102320): 2 fabrications, both real, in testing-framework-batch--csv
42+
- Batch 2 (run 20260312T103251): 7 fabrications (6 real, 1 ambiguous) across 6 files
43+
- Total: 9 findings across 40 sampled files, false positive rate = 0%
44+
- All unambiguous findings are genuine fabrications
45+
- Phase E R2 fixed many but not all fabrications
46+
- 3 systematic patterns: grid-table header invention, empty-split generation, inference-as-fact
47+
48+
See report.md for full analysis including per-finding RST comparison.
49+
50+
## 2026-03-12(追加調査:RST vs JSON 直接比較)
51+
52+
### 全9件の捏造をRST原文と直接比較して確認
53+
54+
PR #172 マージ後、各 finding の RST ソース(Phase D in.txt)と JSON を直接比較。
55+
全9件とも「RSTに書いていないことが書かれている」ことを確認した。
56+
57+
### 捏造の原因パターン(6種)
58+
59+
1. **フォーマット変換の副作用**(F1)
60+
RSTのグリッドテーブルはヘッダなしが合法だが、Markdownはヘッダ行が構文上必須。
61+
変換時にLLMが列名(区分/フィールド1/フィールド2/フィールド3)を架空生成した。
62+
63+
2. **例→ルール化(推論の事実化)**(F2, F6)
64+
例示データ(空セル、`//` 行)を見て暗示的なルールを「読み取り」、明文化して事実として記述。
65+
RSTは例を示しているだけでルールを明言していない。
66+
67+
3. **目的からの補完**(F3)
68+
テストが「何をすべきか」を理解して、RSTに書いていない検証の意図を追記。
69+
論理的には正しいが根拠がない。
70+
71+
4. **空コンテンツへの対処**(F4)
72+
splitが本文ゼロの場合、LLMが「何か書かなければ」と判断してプレースホルダーを生成。
73+
74+
5. **コードの「正規化」**(F5, F7)
75+
不完全・不整合に見えるコード(`<!-- 中略 -->``$`なし記法)を「正しく」修正。
76+
意図的な省略を「バグ」と誤解している。
77+
78+
6. **関連情報からの推論**(F8 ambiguous, F9)
79+
アーティファクト名(`nablarch-fw-standalone`)や他箇所の記述から用途を推論して記述。
80+
81+
### NTFに集中している理由
82+
83+
9件中7件がNablarch Testing Framework(NTF)のファイルから。
84+
NTFドキュメントには捏造を誘発しやすい構造が集中している:
85+
- ヘッダなしグリッドテーブルが多い
86+
- テスト動作を例示スタイルで説明する(ルールを明言しない)
87+
- 複数行にまたがる結合セルが多い
88+
- 長いファイルのsplit境界で本文ゼロになりやすい
89+
90+
### 検知できる理由・修正が難しい理由
91+
92+
**検知が比較的できる理由**
93+
Phase DはRSTとJSONを両方持った状態で「この文字列がRSTに存在するか」という
94+
文字列マッチング的なチェックができる。テーブル構造を完全に理解しなくても、
95+
「区分」という文字列がRSTのどこにも存在しないことは確認できる。
96+
97+
**修正が難しい理由**
98+
RSTグリッドテーブルを正しくMarkdownに変換するには空間的な構造の理解が必要。
99+
LLMはトークン列として処理するため、`+---+---+`の視覚的構造が潰れる。
100+
「何が間違いか」はわかっても「どう直すか」の正解を出すのが難しい。
101+
102+
### 「HTML vs RST」の本質的なギャップ
103+
104+
NTFドキュメントはブラウザでレンダリングされたHTMLで読むことを前提に書かれており、
105+
人間ですらHTMLで読んで初めて理解できる複雑なテーブルがある。
106+
AIはその生のRSTテキストを処理しており、人間が参照しているHTMLとの乖離がある。
107+
108+
根本的な改善案:RSTではなくレンダリング済みHTMLまたはテキストを入力にする。
109+
人間が理解する形と同じものをAIに見せることで精度向上が期待できる。
110+
111+
### 全体アーキテクチャへの示唆
112+
113+
Phase B(曖昧な理解で生成)→ Phase D(文字列照合的に検知)→ Phase E(構造理解が必要で修正が困難)
114+
という非対称性があり、各フェーズで「できること」と「できないこと」が異なる。
115+
R1→R2の2ラウンドでも解消しきれない残留捏造はこの構造的限界に起因する。
116+
117+
Phase Eの改善より「Phase Bが最初から作らない」ほうが根本的な解決策。
118+
具体的にはPhase Bのプロンプトに「各記述がソースに根拠があるか自己確認せよ」を追加する方向。

0 commit comments

Comments
 (0)