fix: Accept string instance_id in telemetry endpoint

The orchestrator sends instance_id as a string (e.g., "letsbe-orchestrator")
but the endpoint was expecting a UUID path parameter. This caused 422
validation errors when orchestrators tried to send telemetry.

- Changed path parameter from UUID to str
- Lookup instance by Instance.instance_id (string) instead of Instance.id (UUID)
- Store telemetry with instance.id (UUID) for correct FK relationship
- Updated TelemetryPayload schema to use str instead of UUID

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Matt 2025-12-24 15:11:30 +01:00
parent 2a1270bfbd
commit 02fc18f009
2 changed files with 6 additions and 7 deletions

View File

@ -7,7 +7,6 @@ It validates authentication, stores metrics, and updates instance state.
import hashlib
import logging
import secrets
from uuid import UUID
from fastapi import APIRouter, Header, HTTPException, status
from sqlalchemy import select
@ -27,7 +26,7 @@ router = APIRouter(prefix="/api/v1/instances", tags=["Telemetry"])
@router.post("/{instance_id}/telemetry", response_model=TelemetryResponse)
async def receive_telemetry(
instance_id: UUID,
instance_id: str,
payload: TelemetryPayload,
db: AsyncSessionDep,
hub_api_key: str = Header(..., alias="X-Hub-Api-Key"),
@ -64,8 +63,8 @@ async def receive_telemetry(
},
)
# Find instance by UUID (id column, not instance_id string)
result = await db.execute(select(Instance).where(Instance.id == instance_id))
# Find instance by instance_id string (e.g., "letsbe-orchestrator")
result = await db.execute(select(Instance).where(Instance.instance_id == instance_id))
instance = result.scalar_one_or_none()
if instance is None:
@ -114,8 +113,9 @@ async def receive_telemetry(
# Store telemetry sample
# Use PostgreSQL upsert to handle duplicates gracefully
# Note: instance_id in DB is the UUID (instance.id), not the string instance_id
telemetry_data = {
"instance_id": instance_id,
"instance_id": instance.id,
"window_start": payload.window_start,
"window_end": payload.window_end,
"uptime_seconds": payload.uptime_seconds,

View File

@ -6,7 +6,6 @@ unknown fields, preventing accidental PII leaks.
from datetime import datetime
from typing import Optional
from uuid import UUID
from pydantic import BaseModel, ConfigDict, Field
@ -87,7 +86,7 @@ class TelemetryPayload(BaseModel):
model_config = ConfigDict(extra="forbid")
instance_id: UUID = Field(..., description="Instance UUID (must match path)")
instance_id: str = Field(..., description="Instance ID string (must match path)")
window_start: datetime = Field(..., description="Start of telemetry window")
window_end: datetime = Field(..., description="End of telemetry window")
uptime_seconds: int = Field(ge=0, description="Orchestrator uptime in seconds")