Finalize user-engine contracts and operability
This commit is contained in:
parent
b81a9e05da
commit
ce2d620f4e
12 changed files with 392 additions and 15 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue