Knowledge.document_id FK + 孤儿清算 CLI(Phase 3 · stacked on #484) - #486
Merged
ThreeFish-AI merged 4 commits intoMay 9, 2026
Conversation
ISSUE-078 Phase 3 — DB Schema 防御层 + 历史脏数据一次性清算。Phase 1 修计数口径、
Phase 2 修应用层级联、Phase 3 在 DB 层加 FK ON DELETE CASCADE 兜底,三层防御
正交可独立回滚。
变更:
- alembic 0030: ADD COLUMN knowledge.document_id UUID NULL +
FK fk_knowledge_document_id REFERENCES knowledge_documents(id) ON DELETE
CASCADE + 部分索引 ix_knowledge_document_id WHERE document_id IS NOT NULL;
仅加列与约束、不做数据变更(避免大表 UPDATE 长锁);downgrade 仅 DROP COLUMN
- ORM 同步: models/perception.py Knowledge.document_id Mapped[UUID|None]
- KnowledgeChunk dataclass: 加 document_id 字段;types.py
- 写入路径 stamp: _ingest_text_with_tracker 加 document_id 参数,单点
dataclasses.replace stamp 到每个 chunk;repository.add_knowledge values 加
document_id 字段。仅 2 个 ingest 入口(execute_ingest_url_document_pipeline /
execute_ingest_file_pipeline)传入 document_id,其余 10 个入口(纯文本、URL、
replace、rebuild、KG)保留 None
- 独立 CLI scripts/cleanup_orphan_knowledge.py:
uv run python -m negentropy.scripts.cleanup_orphan_knowledge \
--dry-run | --commit [--corpus-id ...] [--app-name ...] [--json]
三步流水线: 回填 document_id → 按 corpus 分组报告四档计数 → --commit 才
真正 DELETE(白名单 source_uri 形态防误删);不放进 alembic 迁移因为
DELETE 不可逆需 dry-run + 审批
- 观测函数 count_orphan_knowledge: 与 CLI 共享过滤口径,cron 接入将
total_orphans 上报为 metric negentropy.knowledge.orphan_count{corpus_id}
dev DB 实地验证:
- 在 user 真实数据上跑 --dry-run 报告 Harness Engineering corpus
total=849, would_delete=0;--commit 后 849 条 Knowledge.document_id 全部
回填,与对应 KnowledgeDocument 建立 FK 关联
- Phase 1 → Phase 3 三层防御端到端在用户实际数据上闭环
测试: test_phase3_document_fk.py 8 场景覆盖 ORM 字段读写、DB 层 FK CASCADE
(直接 DELETE knowledge_documents 行触发 Knowledge 级联清理)、CLI dry-run/
commit、KG NULL source_uri 保留、非白名单 URI 形态保留、观测函数、migration
round-trip;既有 67 项单测无回归;alembic upgrade/downgrade 已在 dev DB 验证
详见 docs/issue.md ISSUE-078 Phase 3 补充章节。
🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist)
Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>
…iew #1); 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>
- conftest: 补丁 storage.service / cleanup_orphan_knowledge 的模块级 AsyncSessionLocal 引用(from-import 绑定问题),修复 asyncpg 事件循环边界 RuntimeError - _sync_document_chunk_stats: 包裹 try/except 降级为 warning,避免纯单元 测试(FakeRepository 无真实 DB)触发 DB 连接崩溃 - _backfill_sql: 改回 UPDATE ... FROM + DISTINCT ON 子查询,仅更新有文档 匹配的行(孤儿行自然排除),修复 backfilled 计数断言失败 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>
- _backfill_sql: 子查询内 scope 条件引用外部别名 k → 改为 k_sub, 修复 PG UndefinedTableError - conftest: 补丁 knowledge.api 的模块级 AsyncSessionLocal 引用, 修复 _scenario_soft_delete_archives 调用 list_corpora 时的事件循环边界错误 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com>
ThreeFish-AI
merged commit May 9, 2026
769344a
into
ThreeFish-AI/feat-corpus-delete-cascade
7 checks passed
ThreeFish-AI
added a commit
that referenced
this pull request
Jul 5, 2026
…-2026-4372 处置) (#1057) * fix(deps): ts-deepmerge 补丁地板至 8.0.0 修复 GHSA-87mf-gv2c-c62c (#481); 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * fix(negentropy): pydantic-settings 升 2.14.2 + joserfc>=1.6.7 补丁地板修复 #482/#488; 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * fix(cognizes): joserfc/msgpack/python-socketio/python-engineio 补丁地板修复 #483/#486/#487/#490/#491; 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@gmail.com> * docs(perceives): ISSUE-092 增补 CVE-2026-4372 威胁模型;transformers 钉 4.x + dismiss #492(marker 5.x 不兼容实测); 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang<threefish.ai@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.
背景
ISSUE-078 Phase 3 — DB Schema 防御层 + 历史脏数据一次性清算。三层防御正交可独立回滚:
改动
1. alembic 迁移 0030
apps/negentropy/src/negentropy/db/migrations/versions/0030_knowledge_document_fk.py:ALTER TABLE knowledge ADD COLUMN document_id UUID NULLFOREIGN KEY (document_id) REFERENCES knowledge_documents(id) ON DELETE CASCADECREATE INDEX ix_knowledge_document_id ON knowledge (document_id) WHERE document_id IS NOT NULL2. ORM 同步
apps/negentropy/src/negentropy/models/perception.py:Knowledge.document_id: Mapped[UUID | None]+ FKondelete=CASCADE、nullable=True(KG 类直连知识无 doc 来源必须允许 NULL)。3. KnowledgeChunk dataclass + 写入路径单点 stamp
apps/negentropy/src/negentropy/knowledge/types.pyKnowledgeChunk加document_id: UUID | None = Noneservice.py_ingest_text_with_tracker加document_id参数,在_build_chunks之后通过dataclasses.replace单点 stamp 到每个 chunk——避免大面积改造下游函数execute_ingest_url_document_pipeline/execute_ingest_file_pipeline)传入document_id,其余 10 个入口(纯文本、URL、replace、rebuild、KG)保留 Nonerepository.pyadd_knowledge的pg_insert.values()加入document_id字段4. 独立 CLI
cleanup_orphan_knowledgeapps/negentropy/src/negentropy/scripts/cleanup_orphan_knowledge.py:```bash
uv run python -m negentropy.scripts.cleanup_orphan_knowledge \
--dry-run | --commit [--corpus-id ...] [--app-name ...] [--json]
```
三步流水线:
(corpus_id, app_name, source_uri ↔ d.gcs_uri OR d.metadata->>'origin_url')关联(包含软删 doc)total / linked / unlinked / would_delete四档计数--commit模式真正 DELETE,且仅当document_id IS NULL AND source_uri IS NOT NULL AND source_uri ~ '^(gs://|https?://)'(白名单形态防误删);KG 类(NULL source_uri)永不被清理为什么独立 CLI 而非 alembic data migration:DELETE 不可逆、迁移强制执行不利于审计;CLI 提供 dry-run + JSON 报告 + scope 过滤更稳。
5. 观测函数
count_orphan_knowledgeCLI 模块同时导出
count_orphan_knowledge(corpus_id?, app_name?)函数,与 CLI 共享同一过滤口径,cron 接入:每日扫描 →total_orphans上报为 metricnegentropy.knowledge.orphan_count{corpus_id}→> 0即告警。即便三层防御兜住,仍是入侵 / 异常 SQL 的 canary。dev DB 实地验证(关键)
在 user 真实数据上跑:
```
$ uv run python -m negentropy.scripts.cleanup_orphan_knowledge --dry-run
{
"mode": "dry-run",
"backfilled": 849,
"would_delete_total": 0,
"deleted": 0,
"per_corpus": [{
"corpus_id": "43bacd7e-...",
"corpus_name": "Harness Engineering",
"total": 849,
"linked": 849,
"unlinked": 0,
"would_delete": 0
}]
}
```
结论:user 当时走的是软删(doc 还在 DB 里
status='deleted'),所以没有 FK 意义孤儿。--commit后 849 条 Knowledge.document_id 全部成功回填,与对应 KnowledgeDocument 建立 FK 关联。Phase 1 → Phase 3 三层防御已在用户实际数据上端到端闭环。影响面 & 兼容性
KnowledgeChunk加可选字段,KnowledgeRepository.add_knowledge签名不变document_id→ chunk 写入document_id=NULL→ DB 接受ADD COLUMN ... NULL是元数据级 O(1) 操作,FK 约束不会回扫(新列默认 NULL);CREATE INDEX用 partial index 范围极小集成测试
test_phase3_document_fk.py8 场景覆盖:orm_field_roundtripKnowledge.document_id读写正确,nullable 接受 Nonefk_cascade_db_levelcli_dry_run_no_persistcli_commit_persistskg_null_source_preservedunknown_uri_form_preservedcount_orphan_observationmigration_round_trip_safe既有 67 项单测无回归。
Phase 化交付(本 PR 是 Phase 3 · 收官)
Test plan
pytest tests/integration_tests/knowledge/test_phase3_document_fk.py8 场景全 passpytest tests/unit_tests/knowledge/test_api_corpus.py tests/unit_tests/knowledge/test_api_documents.py tests/unit_tests/knowledge/test_chunking.py67 项无回归docs/issue.mdISSUE-078 追加 Phase 3 补充章节python -m negentropy.scripts.cleanup_orphan_knowledge --dry-run --json审计报告python -m negentropy.scripts.cleanup_orphan_knowledge --commit --json实际清算count_orphan_knowledge观测函数🤖 Generated with Claude Code