generated from coulomb/repo-seed
Finalize user-engine contracts and operability
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
"""Headless user-domain and profile engine."""
|
||||
|
||||
from user_engine.projections import ClaimsEnrichmentProjectionCache
|
||||
from user_engine.projections import CacheStatus, ClaimsEnrichmentProjectionCache
|
||||
from user_engine.service import PLATFORM_TENANT, UserEngineService
|
||||
|
||||
__all__ = [
|
||||
"CacheStatus",
|
||||
"ClaimsEnrichmentProjectionCache",
|
||||
"PLATFORM_TENANT",
|
||||
"UserEngineService",
|
||||
"__version__",
|
||||
]
|
||||
|
||||
__version__ = "0.0.0"
|
||||
__version__ = "0.1.0"
|
||||
|
||||
@@ -8,6 +8,14 @@ from user_engine.domain import Actor, ProjectionType
|
||||
from user_engine.service import Projection, UserEngineService
|
||||
|
||||
|
||||
@dataclass
|
||||
class CacheStatus:
|
||||
entries: int
|
||||
tenants: tuple[str, ...]
|
||||
applications: tuple[str, ...]
|
||||
users: tuple[str, ...]
|
||||
|
||||
|
||||
@dataclass
|
||||
class ClaimsEnrichmentProjectionCache:
|
||||
"""Cache claims-enrichment projections for external token adapters.
|
||||
@@ -47,3 +55,11 @@ class ClaimsEnrichmentProjectionCache:
|
||||
for key in tuple(self._cache):
|
||||
if key[2] == user_id:
|
||||
del self._cache[key]
|
||||
|
||||
def status(self) -> CacheStatus:
|
||||
return CacheStatus(
|
||||
entries=len(self._cache),
|
||||
tenants=tuple(sorted({key[0] for key in self._cache})),
|
||||
applications=tuple(sorted({key[1] for key in self._cache})),
|
||||
users=tuple(sorted({key[2] for key in self._cache})),
|
||||
)
|
||||
|
||||
@@ -97,6 +97,21 @@ class TenantDiagnostics:
|
||||
memberships: tuple[Membership, ...]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OutboxDiagnostics:
|
||||
pending_count: int
|
||||
event_types: Mapping[str, int]
|
||||
oldest_correlation_id: str | None
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class OperabilitySnapshot:
|
||||
ready: bool
|
||||
checks: Mapping[str, bool]
|
||||
metrics: Mapping[str, int]
|
||||
issues: tuple[str, ...]
|
||||
|
||||
|
||||
class UserEngineService:
|
||||
"""Headless service API for isolated user and profile management."""
|
||||
|
||||
@@ -667,6 +682,61 @@ class UserEngineService:
|
||||
def outbox_events(self) -> tuple[OutboxEvent, ...]:
|
||||
return tuple(self.store.outbox_events)
|
||||
|
||||
def outbox_diagnostics(self) -> OutboxDiagnostics:
|
||||
event_types: dict[str, int] = {}
|
||||
for event in self.store.outbox_events:
|
||||
event_types[event.event_type] = event_types.get(event.event_type, 0) + 1
|
||||
oldest = self.store.outbox_events[0].correlation_id if self.store.outbox_events else None
|
||||
return OutboxDiagnostics(
|
||||
pending_count=len(self.store.outbox_events),
|
||||
event_types=event_types,
|
||||
oldest_correlation_id=oldest,
|
||||
)
|
||||
|
||||
def operability_snapshot(self) -> OperabilitySnapshot:
|
||||
audit_correlation_ok = all(
|
||||
bool(record.correlation_id) for record in self.store.audit_records
|
||||
)
|
||||
checks = {
|
||||
"ready": self.readiness().ready,
|
||||
"audit_correlation": audit_correlation_ok,
|
||||
"outbox_diagnostics": self.outbox_diagnostics().pending_count >= 0,
|
||||
}
|
||||
metrics = {
|
||||
"users": len(self.store.users),
|
||||
"accounts": len(self.store.accounts),
|
||||
"tenant_accounts": len(self.store.tenant_accounts),
|
||||
"memberships": len(self.store.memberships),
|
||||
"applications": len(self.store.applications),
|
||||
"catalogs": len(self.store.catalogs),
|
||||
"profile_values": len(self.store.profile_values),
|
||||
"audit_records": len(self.store.audit_records),
|
||||
"pending_outbox_events": len(self.store.outbox_events),
|
||||
}
|
||||
issues = tuple(key for key, passed in checks.items() if not passed)
|
||||
return OperabilitySnapshot(
|
||||
ready=all(checks.values()),
|
||||
checks=checks,
|
||||
metrics=metrics,
|
||||
issues=issues,
|
||||
)
|
||||
|
||||
def structured_log_context(
|
||||
self,
|
||||
*,
|
||||
correlation_id: str,
|
||||
tenant: str,
|
||||
actor: Actor | None = None,
|
||||
) -> Mapping[str, str]:
|
||||
context = {
|
||||
"correlation_id": correlation_id,
|
||||
"tenant": tenant,
|
||||
}
|
||||
if actor is not None:
|
||||
context["actor_issuer"] = actor.issuer
|
||||
context["actor_subject"] = actor.subject
|
||||
return context
|
||||
|
||||
def _session(self, actor: Actor, user: User, account: Account) -> UserSession:
|
||||
identities = tuple(
|
||||
identity
|
||||
|
||||
Reference in New Issue
Block a user