Owner-exact /metrics reads for the selected native chat root.
The collector keys a record by (tenant, conversation_id) and nothing else:
it does not know the sub-tenant, namespace, or principal that ran the turn. So
the collector cannot answer "whose is this" and this router never asks it to.
The conversation authority does: a record is served only for a conversation
whose ownership check (authorize, the refusal transcript answers with,
minus the message read) passes for this principal, and the list is built from
that principal's own sessions. /metrics/summary is a tenant aggregate
with no owner, so it is not served here.
The record itself is still per-process memory. A conversation the owner holds
but this process never metered has no record, and answers 404 or is left out
of the list rather than being reported as zero.
create_owned_metrics_router
create_owned_metrics_router(conversation: Any, collector: Any) -> APIRouter
Serve a conversation's record only to the principal that owns it.
Source code in src/symfonic/agent/fastapi/shared_metrics.py
| def create_owned_metrics_router(conversation: Any, collector: Any) -> APIRouter:
"""Serve a conversation's record only to the principal that owns it."""
require_metrics_collector(collector)
router = APIRouter(tags=["metrics"])
def record_of(tenant: str, session_id: str) -> Any:
record = collector.get_conversation(session_id, tenant_id=tenant)
if record is None or (record.tenant_id, record.conversation_id) != (tenant, session_id):
return None
return record
@router.get("/metrics/conversations")
async def list_conversations(
request: Request,
limit: int = 50,
scope: Any = Depends(get_tenant_scope), # noqa: B008
) -> list[dict[str, Any]]:
try:
principal = principal_of(request, scope)
owned = await conversation.list_sessions(principal)
except Exception as exc:
raise raise_mapped(exc, CRUD_POLICY) from exc
tenant = str(principal.scope.tenant_id)
records = [record_of(tenant, str(row["session_id"])) for row in owned]
present = sorted(
(record for record in records if record is not None),
key=lambda record: record.last_seen,
reverse=True,
)
return [_row(record) for record in present[: max(limit, 0)]]
@router.get("/metrics/conversations/{conversation_id}")
async def get_conversation(
conversation_id: str,
request: Request,
scope: Any = Depends(get_tenant_scope), # noqa: B008
) -> dict[str, Any]:
try:
principal = principal_of(request, scope)
# The refusal the transcript answers with, without reading it:
# not owned by exactly this principal and scope is 404, as there.
await conversation.authorize(principal, conversation_id)
except Exception as exc:
raise raise_mapped(exc, CRUD_POLICY) from exc
record = record_of(str(principal.scope.tenant_id), conversation_id)
if record is None:
# The legacy wire's 404 body, through the mapper, not a literal.
absent = ConversationRefused(f"Conversation {conversation_id!r} not found")
raise raise_mapped(absent, CRUD_POLICY)
return {**_row(record), "call_records": [_call(call) for call in record.call_records]}
return router
|
require_metrics_collector
require_metrics_collector(collector: Any) -> None
Refuse at mount anything that cannot read one tenant's record by id.
Source code in src/symfonic/agent/fastapi/shared_metrics.py
| def require_metrics_collector(collector: Any) -> None:
"""Refuse at mount anything that cannot read one tenant's record by id."""
if not callable(getattr(collector, "get_conversation", None)):
raise TypeError("selected metrics require a conversation metrics collector")
|