211 lines
6.2 KiB
Python
211 lines
6.2 KiB
Python
"""Storage contract for local Wiki pages, vectors, revisions and sync leases."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from abc import ABC, abstractmethod
|
|
from collections.abc import AsyncIterator
|
|
from typing import Any
|
|
|
|
|
|
class LlmWikiIndexStore(ABC):
|
|
@abstractmethod
|
|
async def get_page_by_slug_hash(self, mapping_id: str, slug_hash: str) -> dict[str, Any] | None: ...
|
|
|
|
@abstractmethod
|
|
async def get_page(self, mapping_id: str, slug: str) -> dict[str, Any] | None: ...
|
|
|
|
async def search_pages_by_keywords(
|
|
self,
|
|
mapping_ids: list[str],
|
|
tokens: list[str],
|
|
*,
|
|
limit: int = 100,
|
|
include_drafts: bool = False,
|
|
) -> list[dict[str, Any]]:
|
|
"""Return local Wiki body pages containing any token.
|
|
|
|
The default keeps lightweight test doubles and third-party stores
|
|
backward compatible. Database and in-memory stores override it.
|
|
"""
|
|
|
|
return []
|
|
|
|
async def list_pages(
|
|
self,
|
|
mapping_ids: list[str],
|
|
*,
|
|
limit: int = 500,
|
|
include_drafts: bool = False,
|
|
) -> list[dict[str, Any]]:
|
|
"""Return a bounded, newest-first Wiki page snapshot.
|
|
|
|
Smart-reasoning regex search applies its expression in Python so the
|
|
behavior stays identical on SQLite, MySQL and GaussDB. Third-party
|
|
stores may keep the empty default; the tool then uses the remote Wiki
|
|
API fallback.
|
|
"""
|
|
|
|
return []
|
|
|
|
async def replace_graph_edges(
|
|
self,
|
|
mapping_id: str,
|
|
edges: list[dict[str, Any]],
|
|
*,
|
|
owner: str,
|
|
sync_id: str,
|
|
) -> dict[str, Any] | None:
|
|
"""Replace one mapping's directed Wiki graph snapshot.
|
|
|
|
The default keeps lightweight third-party/test stores compatible. An
|
|
implementation must resolve both endpoints inside ``mapping_id`` and
|
|
discard dangling or cross-mapping edges. ``None`` means the caller no
|
|
longer owns the supplied sync lease and no mutation was made.
|
|
"""
|
|
|
|
return {"edge_count": 0, "changed": False}
|
|
|
|
async def get_adjacent_pages(
|
|
self,
|
|
mapping_ids: list[str],
|
|
seed_slugs: dict[str, list[str]] | list[str],
|
|
*,
|
|
direction: str = "both",
|
|
limit: int = 100,
|
|
include_drafts: bool = False,
|
|
) -> list[dict[str, Any]]:
|
|
"""Return one-hop neighbors within the explicitly allowed mappings.
|
|
|
|
Passing a ``{mapping_id: [slug, ...]}`` mapping is preferred because it
|
|
retains the exact seed provenance. A flat slug list is applied only to
|
|
the supplied ``mapping_ids`` and never broadens authorization.
|
|
"""
|
|
|
|
return []
|
|
|
|
@abstractmethod
|
|
async def touch_page(
|
|
self,
|
|
page_id: str,
|
|
sync_id: str,
|
|
*,
|
|
mapping_id: str | None = None,
|
|
owner: str | None = None,
|
|
) -> bool: ...
|
|
|
|
@abstractmethod
|
|
async def refresh_page_metadata(
|
|
self,
|
|
*,
|
|
page: dict[str, Any],
|
|
sync_id: str,
|
|
owner: str,
|
|
) -> dict[str, Any] | None:
|
|
"""Refresh a mirrored page without replacing its vectors.
|
|
|
|
The write is fenced by the mapping sync lease. ``None`` means the
|
|
caller no longer owns that lease (or the expected page disappeared),
|
|
while the returned ``changed`` flag reports whether durable page
|
|
metadata changed. The page is always marked as seen by ``sync_id``.
|
|
"""
|
|
|
|
...
|
|
|
|
@abstractmethod
|
|
async def replace_page_vectors(
|
|
self,
|
|
*,
|
|
page: dict[str, Any],
|
|
vectors: list[dict[str, Any]],
|
|
fingerprint: str,
|
|
sync_id: str,
|
|
owner: str | None = None,
|
|
) -> dict[str, Any] | None: ...
|
|
|
|
@abstractmethod
|
|
async def record_page_failure(
|
|
self,
|
|
*,
|
|
page: dict[str, Any],
|
|
sync_id: str,
|
|
error: str,
|
|
owner: str | None = None,
|
|
) -> bool: ...
|
|
|
|
@abstractmethod
|
|
async def mark_missing_pages_deleted(
|
|
self,
|
|
mapping_id: str,
|
|
sync_id: str,
|
|
*,
|
|
owner: str,
|
|
) -> int | None:
|
|
"""Retire pages absent from a fenced full scan.
|
|
|
|
``None`` means the supplied owner/sync pair no longer owns the mapping
|
|
lease and no mutation was made.
|
|
"""
|
|
|
|
...
|
|
|
|
@abstractmethod
|
|
async def load_vector_snapshot(self, mapping_id: str, fingerprint: str) -> dict[str, Any]: ...
|
|
|
|
async def iter_vector_snapshot_rows(
|
|
self,
|
|
mapping_id: str,
|
|
fingerprint: str,
|
|
*,
|
|
batch_size: int = 1000,
|
|
) -> AsyncIterator[dict[str, Any]]:
|
|
"""Iterate vector rows for streaming export.
|
|
|
|
Implementations with a database backend should override this method so
|
|
very large vector packages do not need to be materialized in memory.
|
|
"""
|
|
snapshot = await self.load_vector_snapshot(mapping_id, fingerprint)
|
|
for row in snapshot.get("rows") or []:
|
|
if isinstance(row, dict):
|
|
yield row
|
|
|
|
@abstractmethod
|
|
async def get_index_revision(self, mapping_id: str) -> tuple[int, str | None, str]: ...
|
|
|
|
@abstractmethod
|
|
async def try_acquire_sync_lease(self, mapping_id: str, *, owner: str, sync_id: str, lease_seconds: int, fingerprint: str) -> bool: ...
|
|
|
|
@abstractmethod
|
|
async def renew_sync_lease(self, mapping_id: str, *, owner: str, lease_seconds: int) -> bool: ...
|
|
|
|
@abstractmethod
|
|
async def update_sync_progress(
|
|
self,
|
|
mapping_id: str,
|
|
*,
|
|
owner: str,
|
|
sync_id: str,
|
|
remote_page_count: int,
|
|
) -> bool: ...
|
|
|
|
@abstractmethod
|
|
async def finish_sync(
|
|
self,
|
|
mapping_id: str,
|
|
*,
|
|
owner: str,
|
|
sync_id: str,
|
|
success: bool,
|
|
remote_page_count: int,
|
|
error: str | None = None,
|
|
result: dict[str, Any] | None = None,
|
|
) -> bool: ...
|
|
|
|
@abstractmethod
|
|
async def list_index_status(self, mapping_ids: list[str] | None = None) -> list[dict[str, Any]]: ...
|
|
|
|
@abstractmethod
|
|
async def list_page_index_status(self, mapping_id: str) -> list[dict[str, Any]]: ...
|
|
|
|
@abstractmethod
|
|
async def mark_rebuild_required(self, mapping_ids: list[str], fingerprint: str) -> int: ...
|