Add durable search event indexing contract
This commit is contained in:
@@ -75,6 +75,10 @@ from govoplan_core.core.runtime_coordination import (
|
||||
runtime_identity,
|
||||
stop_runtime_node,
|
||||
)
|
||||
from govoplan_core.core.search import (
|
||||
CAPABILITY_SEARCH_INDEX_WRITER,
|
||||
SearchIndexCoordinator,
|
||||
)
|
||||
from govoplan_core.settings import settings
|
||||
from govoplan_core.db.session import configure_database, get_database
|
||||
from govoplan_core.server.registry import (
|
||||
@@ -528,6 +532,20 @@ def _platform_event_outbox(
|
||||
return capability
|
||||
|
||||
|
||||
def _search_index_coordinator(
|
||||
registry: PlatformRegistry | None = None,
|
||||
) -> SearchIndexCoordinator | None:
|
||||
registry = registry or _platform_registry()
|
||||
if not registry.has_capability(CAPABILITY_SEARCH_INDEX_WRITER):
|
||||
return None
|
||||
capability = registry.require_capability(
|
||||
CAPABILITY_SEARCH_INDEX_WRITER
|
||||
)
|
||||
if not isinstance(capability, SearchIndexCoordinator):
|
||||
raise RuntimeError("Search index coordinator capability is invalid")
|
||||
return capability
|
||||
|
||||
|
||||
def _idm_assignment_lifecycle(
|
||||
registry: PlatformRegistry | None = None,
|
||||
) -> IdmAssignmentLifecycle | None:
|
||||
@@ -884,6 +902,7 @@ def dispatch_platform_events(self, limit: int = 100):
|
||||
}
|
||||
dataflow_dispatcher = _dataflow_trigger_dispatcher(registry)
|
||||
workflow_dispatcher = _workflow_trigger_dispatcher(registry)
|
||||
search_coordinator = _search_index_coordinator(registry)
|
||||
consumers: list[DurableEventConsumer] = []
|
||||
if dataflow_dispatcher is not None:
|
||||
|
||||
@@ -923,6 +942,26 @@ def dispatch_platform_events(self, limit: int = 100):
|
||||
handler=deliver_to_workflow,
|
||||
)
|
||||
)
|
||||
if search_coordinator is not None:
|
||||
|
||||
def deliver_to_search(
|
||||
event: PlatformEvent,
|
||||
delivery_key: str,
|
||||
) -> None:
|
||||
search_coordinator.ingest_event(
|
||||
session,
|
||||
event=event,
|
||||
delivery_key=delivery_key,
|
||||
)
|
||||
|
||||
consumers.append(
|
||||
DurableEventConsumer(
|
||||
consumer_id="search.indexing.v1",
|
||||
event_types=frozenset({"*"}),
|
||||
classifications=frozenset({"public", "internal"}),
|
||||
handler=deliver_to_search,
|
||||
)
|
||||
)
|
||||
|
||||
result = dict(
|
||||
outbox.dispatch_pending(
|
||||
@@ -932,6 +971,13 @@ def dispatch_platform_events(self, limit: int = 100):
|
||||
limit=limit,
|
||||
)
|
||||
)
|
||||
if search_coordinator is not None:
|
||||
result["search_changes"] = dict(
|
||||
search_coordinator.process_changes(
|
||||
session,
|
||||
limit=limit,
|
||||
)
|
||||
)
|
||||
session.commit()
|
||||
return result
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import Literal, Protocol, runtime_checkable
|
||||
|
||||
from govoplan_core.core.events import PlatformEvent
|
||||
from govoplan_core.core.external_references import ExternalObjectReference
|
||||
from govoplan_core.core.modules import ModuleContext
|
||||
|
||||
@@ -379,6 +380,43 @@ class SearchSourceProvider(Protocol):
|
||||
"""Return an explicit decision for every requested reference key."""
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class SearchEventSourceProvider(Protocol):
|
||||
"""Optional source extension for committed, idempotent index deltas."""
|
||||
|
||||
def index_changes_for_event(
|
||||
self,
|
||||
session: object,
|
||||
*,
|
||||
event: PlatformEvent,
|
||||
delivery_key: str,
|
||||
) -> Sequence[SearchIndexChange]:
|
||||
"""Translate one committed event into authoritative index changes."""
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class SearchIndexCoordinator(Protocol):
|
||||
"""Worker-facing orchestration surface exposed by the Search module."""
|
||||
|
||||
def ingest_event(
|
||||
self,
|
||||
session: object,
|
||||
*,
|
||||
event: PlatformEvent,
|
||||
delivery_key: str,
|
||||
) -> Mapping[str, int]:
|
||||
...
|
||||
|
||||
def process_changes(
|
||||
self,
|
||||
session: object,
|
||||
*,
|
||||
limit: int = 100,
|
||||
tenant_id: str | None = None,
|
||||
) -> Mapping[str, int]:
|
||||
...
|
||||
|
||||
|
||||
SearchProviderFactory = Callable[[ModuleContext], SearchProvider]
|
||||
SearchSourceProviderFactory = Callable[
|
||||
[ModuleContext],
|
||||
@@ -455,8 +493,10 @@ __all__ = [
|
||||
"SearchBackfillRequest",
|
||||
"SearchContextKind",
|
||||
"SearchDocument",
|
||||
"SearchEventSourceProvider",
|
||||
"SearchIndexChange",
|
||||
"SearchIndexChangeKind",
|
||||
"SearchIndexCoordinator",
|
||||
"SearchIndexWriter",
|
||||
"SearchProvider",
|
||||
"SearchProviderFactory",
|
||||
|
||||
Reference in New Issue
Block a user