hub-core/hub_core/runtime/store.py
tegwick ab936a1e98
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / pytest-smoke (push) Failing after 2s
feat: transport authoritative workload projections
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a0230c-b06c-7641-808a-e191b6d1da49
2026-08-23 10:54:40 +02:00

259 lines
9.1 KiB
Python

from __future__ import annotations
import asyncio
import hashlib
import json
from collections.abc import Mapping
from copy import deepcopy
from datetime import datetime, timezone
from typing import Any, Protocol
from uuid import UUID, uuid4
from hub_core.contracts import CONTRACT_VERSION
from hub_core.runtime.models import (
EventCommand,
MessageCommand,
PortAccepted,
PortCollection,
PortRecord,
Provenance,
RegistryRegistration,
)
from hub_core.runtime.repository_navigation import NavigationProjection
from hub_core.runtime.workload_projection import WorkloadProjection
class PortStore(Protocol):
"""Persistence boundary for the initial hub-core runtime ports."""
backend_name: str
async def readiness_checks(self) -> dict[str, str]: ...
async def register_extension(
self,
registration: RegistryRegistration,
correlation_id: UUID,
) -> PortAccepted: ...
async def send_message(self, command: MessageCommand) -> PortAccepted: ...
async def list_messages(self, address: str, conversation_id: UUID | None) -> PortCollection: ...
async def append_progress(self, command: EventCommand) -> PortAccepted: ...
async def append_interaction(self, command: EventCommand) -> PortAccepted: ...
async def query_projection(self, projection_id: str) -> PortRecord | None: ...
async def get_repository_navigation(self) -> NavigationProjection | None: ...
async def replace_repository_navigation(
self, projection: NavigationProjection
) -> None: ...
async def mark_repository_navigation_stale(
self, *, checked_at: datetime, diagnostic: Mapping[str, Any]
) -> None: ...
async def get_workload_projection(self) -> WorkloadProjection | None: ...
async def replace_workload_projection(self, projection: WorkloadProjection) -> None: ...
async def mark_workload_projection_stale(
self, *, checked_at: datetime, diagnostic: Mapping[str, Any]
) -> None: ...
class InMemoryPortStore:
"""Deterministic ephemeral backend for local runtime and conformance tests.
Production readiness rejects this backend unless explicitly allowed. The
store deliberately keeps progress and interaction event families separate.
"""
backend_name = "memory"
def __init__(self) -> None:
self._lock = asyncio.Lock()
self._registrations: dict[str, dict[str, Any]] = {}
self._messages: list[dict[str, Any]] = []
self._progress_events: list[dict[str, Any]] = []
self._interaction_events: list[dict[str, Any]] = []
self._repository_navigation: NavigationProjection | None = None
self._workload_projection: WorkloadProjection | None = None
async def readiness_checks(self) -> dict[str, str]:
return {"database": "not_applicable"}
async def register_extension(
self,
registration: RegistryRegistration,
correlation_id: UUID,
) -> PortAccepted:
hub_slug = str(registration.descriptor["hub_slug"])
value = registration.model_dump(mode="json")
async with self._lock:
duplicate = self._registrations.get(hub_slug) == value
self._registrations[hub_slug] = deepcopy(value)
return PortAccepted(
id=hub_slug,
status="duplicate" if duplicate else "accepted",
correlation_id=correlation_id,
)
async def send_message(self, command: MessageCommand) -> PortAccepted:
message_id = uuid4()
value = {
"id": str(message_id),
"created_at": _now().isoformat(),
**command.model_dump(mode="json"),
}
async with self._lock:
self._messages.append(value)
return PortAccepted(
id=str(message_id),
status="accepted",
correlation_id=command.correlation_id,
)
async def list_messages(self, address: str, conversation_id: UUID | None) -> PortCollection:
async with self._lock:
values = [
deepcopy(message)
for message in self._messages
if address in message["to_addresses"]
and (
conversation_id is None
or message.get("conversation_id") == str(conversation_id)
)
]
return PortCollection(items=[self._record("message", value) for value in values])
async def append_progress(self, command: EventCommand) -> PortAccepted:
return await self._append_event(command, self._progress_events, "progress")
async def append_interaction(self, command: EventCommand) -> PortAccepted:
return await self._append_event(command, self._interaction_events, "interaction")
async def query_projection(self, projection_id: str) -> PortRecord | None:
async with self._lock:
sources: Mapping[str, Any] = {
"hub_registry": list(self._registrations.values()),
"messages": self._messages,
"progress_events": self._progress_events,
"interaction_events": self._interaction_events,
}
if projection_id not in sources:
return None
items = deepcopy(sources[projection_id])
return self._record(
projection_id,
{
"projection_id": projection_id,
"items": items,
"rebuild_from": _rebuild_sources(projection_id),
},
)
async def get_repository_navigation(self) -> NavigationProjection | None:
async with self._lock:
return deepcopy(self._repository_navigation)
async def replace_repository_navigation(
self, projection: NavigationProjection
) -> None:
async with self._lock:
self._repository_navigation = deepcopy(projection)
async def mark_repository_navigation_stale(
self, *, checked_at: datetime, diagnostic: Mapping[str, Any]
) -> None:
async with self._lock:
current = self._repository_navigation
if current is None:
return
self._repository_navigation = NavigationProjection(
projection_status="stale",
source_snapshot=deepcopy(current.source_snapshot),
source_checked_at=checked_at,
rebuilt_at=current.rebuilt_at,
content_hash=current.content_hash,
repositories=deepcopy(current.repositories),
facets=deepcopy(current.facets),
diagnostics=(deepcopy(dict(diagnostic)),),
)
async def get_workload_projection(self) -> WorkloadProjection | None:
async with self._lock:
return deepcopy(self._workload_projection)
async def replace_workload_projection(self, projection: WorkloadProjection) -> None:
async with self._lock:
self._workload_projection = deepcopy(projection)
async def mark_workload_projection_stale(
self, *, checked_at: datetime, diagnostic: Mapping[str, Any]
) -> None:
async with self._lock:
current = self._workload_projection
if current is None:
return
self._workload_projection = WorkloadProjection(
projection_status="stale",
source_snapshot=deepcopy(current.source_snapshot),
source_checked_at=checked_at,
rebuilt_at=current.rebuilt_at,
content_hash=current.content_hash,
workloads=deepcopy(current.workloads),
diagnostics=(deepcopy(dict(diagnostic)),),
)
async def _append_event(
self,
command: EventCommand,
target: list[dict[str, Any]],
family: str,
) -> PortAccepted:
event_id = uuid4()
value = {
"id": str(event_id),
"family": family,
"recorded_at": _now().isoformat(),
**command.model_dump(mode="json"),
}
async with self._lock:
target.append(value)
return PortAccepted(
id=str(event_id),
status="accepted",
correlation_id=command.correlation_id,
)
def _record(self, kind: str, value: dict[str, Any]) -> PortRecord:
encoded = json.dumps(value, sort_keys=True, separators=(",", ":")).encode()
record_id = str(value.get("id") or kind)
return PortRecord(
id=record_id,
data=deepcopy(value),
provenance=Provenance(
source_system="hub-core-memory",
source_ref=f"memory://{kind}/{record_id}",
schema_version=CONTRACT_VERSION,
content_hash=hashlib.sha256(encoded).hexdigest(),
indexed_at=_now(),
),
)
def _now() -> datetime:
return datetime.now(timezone.utc)
def _rebuild_sources(projection_id: str) -> list[str]:
return {
"hub_registry": ["hub_descriptors", "hub_manifests"],
"messages": ["messages"],
"progress_events": ["progress_events"],
"interaction_events": ["interaction_events"],
}[projection_id]