Add SQLite-backed registry with JSON migration support#15
Conversation
a56ee15 to
9436ad6
Compare
71f9b4f to
9124f51
Compare
Code reviewFound 2 issues:
Lines 541 to 551 in 9124f51
Lines 82 to 94 in 9124f51 Coordination note (not an issue, but worth flagging): PR #30 (still open) adds 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
review 反馈:当 storage_backend 为 sqlite(默认)时,hashes.json 不存在, 导致 lint 总是误判为无文档。对齐 print_list 和 print_status 的已有实现。
- 用 schema_meta 完成标记替代 DB 文件存在性判断,避免中断后永不重试 - get_registry() 只要 hashes.json 存在即传递 migrate_from,让 DbRegistry 自主判断 - 新增 get_by_path / remove_by_doc_name 到 HashRegistry 与 DbRegistry, 防止后续 PR VectifyAI#30 合并时 SQLite 后端出现 AttributeError
9124f51 to
8cb4522
Compare
Code reviewFound 1 issue:
🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
|
A few smaller follow-ups from the same pass that didn't make it into the main review — none are blocking, just worth tracking: Migration sentinel write window. Lines 100 to 116 in 8cb4522 Backend toggle silently drops JSON-only entries. If a user is on Missing DbRegistry coverage for the new methods. |
修复 JSON 到 SQLite 迁移完成标记的原子写入 对齐 get_by_path 和 remove_by_doc_name 与 PR VectifyAI#30 的字段契约 补充 HashRegistry 与 DbRegistry 的回归测试
|
Addressed the review feedback in Changes:
Validation:
|
同步 origin/main 并解决 state、CLI remove 和相关测试冲突 保持 SQLite registry 默认 backend 与上游 remove 流程兼容
|
Resolved the merge conflicts with Notes:
Validation:
PR is now mergeable on GitHub. |
|
Synced to the latest Status recap — all earlier review feedback is addressed and there are no open items on my side:
Validation on the merged branch:
One coordination question: how would you like to sequence this with #30, given the shared |
|
@kdush 建议先用cc先review 没问题了,我在帮你看哈。。。我找cc review了还是有不少问题 |
…ackend # Conflicts: # README.md
|
Adopted this alongside #86 (mutation locks) on current |
…es (#198) * feat(api): delete a knowledge base (POST /api/v1/kb/delete + openkb delete-kb) Physical, irreversible KB deletion with a type-the-name confirmation. - config.delete_kb: rmtree the KB directory + unregister it from the global registry (known_kbs / kb_aliases / default_kb, via the new unregister_kb). Guards against deleting a non-KB path; tolerates a ghost registry entry (directory already gone) by just unregistering. - POST /api/v1/kb/delete: confirm_name must equal kb, re-checked server-side. Lives in a new api_kbs_router (sibling of the config/graph/output routers) so api.py stays under the 800-line gate; delete_kb self-locks (config lock), no create_app closure needed. - openkb delete-kb NAME: type-the-name prompt (or --yes) then delete. Tests: endpoint (success + unregister, confirm mismatch, non-KB target) in test_api.py; config (physical delete + unregister, refuse non-KB, ghost tolerance) in test_config.py. Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG * feat(api): delete a wiki page (POST /api/v1/page/delete) with backlink impact Delete a concept/entity page and degrade references cleanly instead of leaving them dangling: - page_ops.delete_wiki_page: dry_run reports the backlink pages (whose inbound [[wikilinks]] would be demoted) without touching anything; execute removes the page under the KB ingest lock, strips its index.md entry outright (compiler.remove_doc_from_index — the entry is a [[link]] line), and demotes the now-dangling inbound [[links]] to plain text in ONLY those backlink pages (lint.fix_broken_links restrict_to). The page ref is "section/stem", validated to concepts/ or entities/ only (path-traversal-safe). - New api_pages_router hosts POST /api/v1/page (read — relocated here from api.py to keep it under the 800-line gate) + POST /api/v1/page/delete. Tests: dry-run impact + execute (page gone, inbound link demoted to text, index entry removed, sibling kept), 404 on missing page, and rejection of unsafe / non-editable refs. Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG * feat(api): edit a wiki page (PUT /api/v1/page) + link context (POST /api/v1/page/links) - page_ops.edit_wiki_page: replace a concept/entity page's BODY while preserving its code-managed OKF frontmatter (type/description/sources) verbatim; any frontmatter in the submitted content is dropped. Dead [[wikilinks]] the user typed are demoted to plain text on save (OpenKB keeps the wiki broken-link-free) and returned as ghosts_stripped so the UI can warn. Atomic write under the KB ingest lock. - page_ops.page_link_context (POST /api/v1/page/links): outbound + inbound links for the edit-impact panel. Editing the body does not break either (links are path-based), so this is context, not a blocker — the honest impact model. - PUT /api/v1/page added to api_pages_router. Tests: edit preserves frontmatter + keeps a resolvable link + demotes a dead one (reports ghosts_stripped) + replaces the body; 404 on a missing page; links reports out/backlinks. Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG * feat(web): delete a knowledge base from the settings sheet (type-name confirm) Adds a Danger Zone section to KbSettingsSheet: a type-the-name confirmation gates deleteKb() (POST /api/v1/kb/delete); on success KbDetail navigates back to the KB list. New deleteKb client + kbSettings danger* keys + common cancel (zh + en, identical key sets). Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG * feat(web): in-reader page edit + delete with impact preview (F2/F3) For an open concept/entity page, the reader header now shows Edit and Delete: - Delete: a dry-run first (deletePage dryRun) surfaces the backlink pages whose [[links]] will demote to plain text; a red confirm card lists them, then the real delete closes the page and refreshes the inventory. - Edit: a body textarea (frontmatter is preserved server-side), Save via PUT /api/v1/page, a links panel (getPageLinks: out/backlinks), a "recompile may overwrite" note, and a toast listing any dead links demoted to text. Adds the deletePage/editPage/getPageLinks wiki clients and kb:pageOps.* locale keys (zh + en, identical key sets). Build green (i18n guard + tsc + vite). Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG * fix(workbench): address code-review findings (content-mgmt correctness + concurrency) Backend (page_ops / kb_admin / lint): - delete/edit now do their read-modify-write INSIDE the KB ingest lock and re-check the page exists under it: no stale backlink snapshot, no resurrecting a concurrently-deleted page, no 500 on a racing unlink (missing_ok=True). [#4,#5,#6] - delete_kb serializes rmtree under the ingest lock (was fully unlocked). Moved delete_kb/unregister_kb into a new openkb/kb_admin.py so config.py drops back under the per-file line gate (751 lines). [#1,#15] - page deletion no longer scans or rewrites lint's _EXCLUDED_FILES (AGENTS.md/SCHEMA.md/log.md); fix_broken_links(restrict_to) also refuses them, which protects `openkb remove` too. [#2] - edit_wiki_page treats `content` as the body verbatim — no naive frontmatter split that silently dropped a body opening with a '---' block. [#3] - delete uses the real on-disk stem for the exact-match index-entry removal (case-insensitive FS), and adds index.md to the demotion set so a [[target]] embedded in another entry's brief no longer dangles. [#7,#9] API / CLI: - delete-KB endpoint & CLI can now clean a ghost registry entry (registered name whose directory was removed by hand), catch delete_kb's ValueError -> 400, and the endpoint is typed `-> KbDeleteResponse`. [#8,#13,E5] Frontend (KbDetail): - Deleting a KB dispatches `openkb:reload-kbs` so the sidebar refreshes immediately (was stale until a manual reload) — live-test fix. - Removed the dead `pageReloadSeq` path (editPage always returns content). [#14] Tests: excluded-doc-untouched, '---'-body preservation, ghost-KB unregister; non-KB-target test updated to the new resolution model. Backend 1234 passed, mypy/ruff clean, frontend build green. Not changed (intentional): pages_linking_to is NOT folded into build_graph (that excludes explorations/ backlinks) [#10]; the preview+confirm delete inherently scans per request [#11]; EDITABLE_SECTIONS stays duplicated in the frontend, which can't import the Python constant [#12]. Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG * feat(workbench): allow editing summary pages (edit ⊇ delete section sets) Summaries are compiled markdown like concepts/entities (a recompile regenerates them), so they are now editable via PUT /api/v1/page. They remain NOT independently deletable — a summary is removed by deleting its source document. Split page_ops into EDITABLE_SECTIONS (concepts/entities/summaries) vs DELETABLE_SECTIONS (concepts/entities); validate_page_ref takes the allowlist. Frontend ReaderBody gates Edit on canEdit (incl. summaries), Delete on canDelete. Test: summary editable (frontmatter preserved) + summary delete rejected (400). Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG * fix(api): cross-platform delete_kb + endpoint OSError handling (xhigh review #1,#2) - delete_kb no longer holds the ingest lock DURING rmtree (the lock file lives inside the KB dir; Windows cannot delete an open file — the prior review-fix regressed this). It now takes the lock as a BARRIER (drain + wait out any in-flight mutation), releases it, re-checks existence, then rmtrees. [#1] - delete-KB endpoint maps FileNotFoundError to an idempotent success (a concurrent delete already removed the tree) and other OSError to a clean 500 with a message, instead of an uncaught 500 stack trace. [#2] Tests: endpoint OSError -> clean 500, FileNotFoundError -> 200 deleted. Claude-Session: https://claude.ai/code/session_01XMxbhmAkxxVV8CFWCZDBaG
Summary
Add SQLite-backed registry as the default storage backend, with automatic JSON migration support.
Changes
DbRegistryclass with SQLite backend, WAL mode, and JSON migrationstorage_backendconfig option (sqlite | json)get_registry()factoryTesting
hashes.db,hashes.db-wal,hashes.db-shmBackward Compatibility
storage_backend: jsonstill works for existing setupshashes.jsontohashes.dbwhen switching to SQLitehashes.jsonpreserved after migration for safety