fix: persist and recover the Agent's real installation_id (heartbeat activation)

POST /v1/agents/enroll now returns installation_id; new installs persist it
to agent.env immediately. Runtime recovery precedence: existing SQLite
installation_identity -> configured INSTALLATION_ID -> authenticated
GET /v1/agents/me/identity. Recovery fails closed on missing/malformed/
mismatched identity rather than inventing a UUID (which the Hub would then
reject as a conflicting Installation on an already-enrolled Agent). An
explicit AGENT_JOBS_ENABLED gate stops the service from claiming a job right
after the first heartbeat.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
gti
2026-09-23 22:13:56 -03:00
co-authored by Claude Sonnet 5
parent a9aded1e49
commit ac7b503c2f
16 changed files with 754 additions and 15 deletions
+1
View File
@@ -194,6 +194,7 @@ def router(session_factory):
target_type="agent", target_id=str(agent.id), context={"ip": ip, "installation": enr.enrollment_uuid})
session.commit()
return {"agent_id": agent.id, "agent_token": plain_token, "tenant_id": enr.tenant_id,
"installation_id": enr.enrollment_uuid,
"hub_url": config.settings.base_url, "hostname_mismatch": mismatch}
except enrollment.EnrollmentRejected as rejected:
session.rollback()
+20
View File
@@ -226,6 +226,26 @@ def router(session_factory, bootstrap_token):
return {"event_id": event.id}
finally: session.close()
@routes.get("/agents/me/identity")
def agent_identity(identity=Depends(principal)):
"""Return only the authenticated Agent's existing installation identity."""
session, agent = identity
try:
installation = session.scalar(select(Installation).where(Installation.agent_id == agent.id))
last_seen = agent.last_seen_at
if last_seen is not None and last_seen.tzinfo is None:
last_seen = last_seen.replace(tzinfo=timezone.utc)
online = last_seen is not None and (datetime.now(timezone.utc) - last_seen).total_seconds() <= settings.agent_online_threshold_seconds
return {
"agent_id": agent.id,
"installation_id": installation.installation_id if installation else None,
"hostname": installation.hostname if installation else None,
"version": agent.agent_version,
"status": "ONLINE" if online else "OFFLINE",
}
finally:
session.close()
@routes.get("/agents/me")
def agent_status(identity=Depends(principal)):
session, agent = identity