def mount_prospective_create_route(
router: Any, *, service: Any, resolve: Any, verified_resolver: bool,
) -> None:
"""Keep validation, authentication, and 404 when the layer is absent."""
from fastapi import Depends, Header, HTTPException, Request
async def resolved_scope(
request: Request,
x_tenant_id: str | None = Header(default=None), # noqa: B008
authorization: str | None = Header(default=None), # noqa: B008
) -> tuple[Any, Any]:
caller = (
await resolve(x_tenant_id, authorization, request=request, principal_only=True)
if verified_resolver else None
)
scope = caller.scope if caller is not None else await resolve(
x_tenant_id, authorization, request=request,
)
return scope, caller
@router.post(
"/memories/prospective", status_code=201, response_model=ProspectiveCreateResponse,
)
async def create_prospective(
body: ProspectiveCreateRequest,
request: Request,
resolved: tuple[Any, Any] = Depends(resolved_scope), # noqa: B008
) -> dict[str, Any]:
scope, caller = resolved
if service is None:
raise HTTPException(status_code=404, detail="prospective layer not available")
try:
row = await service.create(
memory_scope(scope), content=body.content, trigger=body.trigger,
task=body.task, kind=body.kind, importance=body.importance,
metadata=body.metadata,
)
except (SecurityScopeError, ScopeValidationError) as exc:
raise raise_mapped(exc, CRUD_POLICY) from exc
logger = get_audit_logger()
if logger is not None:
from symfonic.agent.fastapi.identity import request_user_id
user_id = caller.principal_id if caller is not None else request_user_id(request)
entry = AuditLogEntry(
tenant_id=str(scope.tenant_id),
user_id=str(user_id) if user_id else None,
action="memory.create.prospective", resource_type="memory_node",
resource_id=row["id"], metadata={"kind": body.kind},
ip_address=request.client.host if request.client else None,
user_agent=request.headers.get("user-agent"),
)
try:
await logger.record(entry)
except Exception: # noqa: BLE001 - legacy audit is best effort
import logging
logging.getLogger(__name__).warning(
"Audit logger record() raised — audit event lost (action=%s)",
entry.action,
)
return row