Skip to content

Commit 802d0e6

Browse files
committed
Route serial add paths through coordinator
1 parent 238fc49 commit 802d0e6

2 files changed

Lines changed: 322 additions & 105 deletions

File tree

openkb/cli.py

Lines changed: 81 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def filter(self, record: logging.LogRecord) -> bool:
5252
from openkb.indexer import _write_long_doc_artifacts, prepare_cloud_import
5353
from openkb.locks import atomic_write_json, atomic_write_text, kb_ingest_lock, kb_read_lock
5454
from openkb.log import append_log
55-
from openkb.mutation import MutationSnapshot, publish_staged_tree, snapshot_paths
55+
from openkb.mutation import publish_staged_tree
5656
from openkb.schema import AGENTS_MD, INDEX_SEED, PAGE_CONTENT_DIRS
5757

5858
# Suppress warnings after all imports — markitdown overrides filters at import time
@@ -457,29 +457,15 @@ def _add_single_file_locked(
457457
index_result = None # populated only on the long-doc branch
458458

459459
final_raw, final_source = _final_artifact_paths(result, kb_dir)
460-
try:
461-
snapshot = snapshot_paths(
462-
kb_dir,
463-
_snapshot_add_paths(kb_dir, doc_name, final_raw, final_source),
464-
operation="add",
465-
details={
466-
"file_hash": result.file_hash,
467-
"name": file_path.name,
468-
"doc_name": doc_name,
469-
},
470-
hardlink_dirs={
471-
kb_dir / "wiki" / "concepts",
472-
kb_dir / "wiki" / "entities",
473-
kb_dir / ".openkb" / "files",
474-
},
475-
)
460+
461+
def commit_body() -> None:
462+
nonlocal index_result
476463
publish_staged_tree(staging_dir, kb_dir)
477464
if final_raw is not None:
478465
result.raw_path = final_raw
479466
if final_source is not None:
480467
result.source_path = final_source
481468

482-
# 3/4. Index and compile
483469
if result.is_long_doc:
484470
if result.raw_path is None:
485471
raise RuntimeError(f"Converted long document has no raw artifact: {file_path.name}")
@@ -542,34 +528,30 @@ def _add_single_file_locked(
542528
registry.remove_by_hash(existing_hash)
543529
registry.add(result.file_hash, meta)
544530

545-
snapshot.mark_committed()
546-
except Exception:
547-
if snapshot is None:
548-
click.echo(f" [ERROR] Failed to prepare mutation snapshot for {file_path.name}.")
549-
_cleanup_staging(staging_dir)
550-
return "failed"
551-
rollback_error = snapshot.rollback_best_effort()
552-
if rollback_error is None:
553-
snapshot.discard_best_effort()
554-
else:
555-
click.echo(
556-
" [ERROR] Rollback failed; mutation journal retained for recovery: "
557-
f"{snapshot.journal_path}"
558-
)
559-
_cleanup_staging(staging_dir)
560-
return "failed"
561-
finally:
562-
_cleanup_staging(staging_dir)
563-
564-
try:
531+
def append_ingest_log() -> None:
565532
append_log(kb_dir / "wiki", "ingest", file_path.name)
566-
except Exception as exc:
567-
logger.warning("Failed to append ingest log for %s: %s", file_path.name, exc)
568-
cleanup_error = snapshot.discard_best_effort()
569-
if cleanup_error is not None:
570-
click.echo(
571-
f" [WARN] {file_path.name} added, but mutation journal cleanup failed: {cleanup_error}"
572-
)
533+
534+
from openkb.add_coordinator import AddMutationPlan, run_add_mutation
535+
536+
plan = AddMutationPlan(
537+
operation="add",
538+
details={
539+
"file_hash": result.file_hash,
540+
"name": file_path.name,
541+
"doc_name": doc_name,
542+
},
543+
touched_paths=_snapshot_add_paths(kb_dir, doc_name, final_raw, final_source),
544+
body=commit_body,
545+
post_commit_hooks=[append_ingest_log],
546+
hardlink_dirs={
547+
kb_dir / "wiki" / "concepts",
548+
kb_dir / "wiki" / "entities",
549+
kb_dir / ".openkb" / "files",
550+
},
551+
staging_dirs=[staging_dir],
552+
)
553+
if not run_add_mutation(kb_dir, plan):
554+
return "failed"
573555
click.echo(f" [OK] {file_path.name} added to knowledge base.")
574556
return "added"
575557

@@ -602,8 +584,9 @@ def import_from_pageindex_cloud(
602584
return "skipped"
603585

604586
click.echo(f"Importing from PageIndex Cloud: {doc_id}")
605-
snapshot: MutationSnapshot | None = None
606587
doc_name = ""
588+
from openkb.add_coordinator import AddMutationPlan, DirtyRollbackError, run_add_mutation
589+
607590
try:
608591
try:
609592
cloud = prepare_cloud_import(doc_id, kb_dir, path_key)
@@ -613,76 +596,69 @@ def import_from_pageindex_cloud(
613596
return "failed"
614597

615598
doc_name = cloud.doc_name
616-
snapshot = snapshot_paths(
617-
kb_dir,
618-
_snapshot_add_paths(kb_dir, doc_name, None, None),
599+
600+
def commit_body() -> None:
601+
summary_path = _write_long_doc_artifacts(
602+
cloud.tree,
603+
cloud.all_pages,
604+
doc_name,
605+
doc_id,
606+
kb_dir,
607+
description=cloud.description,
608+
)
609+
_run_compile_with_retry(
610+
lambda: compile_long_doc(
611+
doc_name,
612+
summary_path,
613+
doc_id,
614+
kb_dir,
615+
model,
616+
doc_description=cloud.description,
617+
),
618+
label=f"Compiling imported doc (doc_id={doc_id})",
619+
)
620+
621+
# Register the raw-less cloud entry only after successful compilation.
622+
registry = HashRegistry(openkb_dir / "hashes.json")
623+
meta = {
624+
"name": cloud.cloud_name,
625+
"doc_name": doc_name,
626+
"type": "pageindex_cloud",
627+
"origin": "cloud",
628+
"path": path_key,
629+
"source_path": _registry_path(
630+
kb_dir / "wiki" / "sources" / f"{doc_name}.json", kb_dir
631+
),
632+
"doc_id": doc_id,
633+
}
634+
registry.remove_by_doc_name(doc_name)
635+
registry.add(synthetic_hash, meta)
636+
637+
def append_cloud_log() -> None:
638+
append_log(kb_dir / "wiki", "ingest", doc_name)
639+
640+
plan = AddMutationPlan(
619641
operation="cloud_import",
620642
details={"doc_id": doc_id, "doc_name": doc_name},
643+
touched_paths=_snapshot_add_paths(kb_dir, doc_name, None, None),
644+
body=commit_body,
645+
post_commit_hooks=[append_cloud_log],
621646
hardlink_dirs={
622647
kb_dir / "wiki" / "concepts",
623648
kb_dir / "wiki" / "entities",
624649
kb_dir / ".openkb" / "files",
625650
},
626651
)
627-
summary_path = _write_long_doc_artifacts(
628-
cloud.tree,
629-
cloud.all_pages,
630-
doc_name,
631-
doc_id,
632-
kb_dir,
633-
description=cloud.description,
634-
)
635-
_run_compile_with_retry(
636-
lambda: compile_long_doc(
637-
doc_name,
638-
summary_path,
639-
doc_id,
640-
kb_dir,
641-
model,
642-
doc_description=cloud.description,
643-
),
644-
label=f"Compiling imported doc (doc_id={doc_id})",
645-
)
646-
647-
# Register the raw-less cloud entry only after successful compilation.
648-
registry = HashRegistry(openkb_dir / "hashes.json")
649-
meta = {
650-
"name": cloud.cloud_name,
651-
"doc_name": doc_name,
652-
"type": "pageindex_cloud",
653-
"origin": "cloud",
654-
"path": path_key,
655-
"source_path": _registry_path(
656-
kb_dir / "wiki" / "sources" / f"{doc_name}.json", kb_dir
657-
),
658-
"doc_id": doc_id,
659-
}
660-
registry.remove_by_doc_name(doc_name)
661-
registry.add(synthetic_hash, meta)
662-
snapshot.mark_committed()
652+
with kb_ingest_lock(kb_dir / ".openkb"):
653+
if not run_add_mutation(kb_dir, plan):
654+
return "failed"
655+
except DirtyRollbackError:
656+
raise
663657
except Exception:
664-
if snapshot is None:
665-
click.echo(f" [ERROR] Failed to prepare mutation snapshot for cloud import {doc_id}.")
666-
return "failed"
667-
rollback_error = snapshot.rollback_best_effort()
668-
if rollback_error is None:
669-
snapshot.discard_best_effort()
670-
else:
671-
click.echo(
672-
" [ERROR] Rollback failed; mutation journal retained for recovery: "
673-
f"{snapshot.journal_path}"
674-
)
658+
click.echo(f" [ERROR] Failed to prepare mutation snapshot for cloud import {doc_id}.")
659+
logger.debug("Cloud import mutation traceback:", exc_info=True)
675660
return "failed"
676661

677-
try:
678-
append_log(kb_dir / "wiki", "ingest", doc_name)
679-
except Exception as exc:
680-
logger.warning("Failed to append ingest log for cloud import %s: %s", doc_id, exc)
681-
cleanup_error = snapshot.discard_best_effort()
682-
if cleanup_error is not None:
683-
click.echo(
684-
f" [WARN] {doc_name} imported, but mutation journal cleanup failed: {cleanup_error}"
685-
)
686662
click.echo(f" [OK] {doc_name} imported from PageIndex Cloud.")
687663
return "added"
688664

0 commit comments

Comments
 (0)