feat: report independent instance and workspace identities (#2394)

* feat: report independent instance and workspace identities

* test: include workspace in OAuth callback fixture

* ci: pin production cloud adapter to Space release

* fix: preserve authenticated Workspace telemetry attribution

* ci: pin production cloud adapter to final Space release

---------

Co-authored-by: dadachann <185672915+dadachann@users.noreply.github.com>
This commit is contained in:
Hyu
2026-08-04 17:27:23 +08:00
committed by GitHub
parent 7820949d3a
commit c08bfc8ced
14 changed files with 196 additions and 28 deletions
+18
View File
@@ -120,6 +120,7 @@ async def build_heartbeat_payload(
ap: core_app.Application,
*,
workspace_uuid: str,
workspace_create_ts: int = 0,
workspace_resource: WorkspaceResourceSnapshot | None = None,
) -> dict:
"""Collect one anonymous Workspace profile snapshot."""
@@ -212,7 +213,9 @@ async def build_heartbeat_payload(
'event_type': 'instance_heartbeat',
'query_id': '',
'version': constants.semantic_version,
'instance_id': constants.instance_id,
'workspace_uuid': workspace_uuid,
'workspace_create_ts': workspace_create_ts,
'instance_create_ts': constants.instance_create_ts,
'edition': constants.edition,
'features': features,
@@ -220,10 +223,24 @@ async def build_heartbeat_payload(
}
def _workspace_created_timestamp(created_at: datetime | None) -> int:
if created_at is None:
return 0
if created_at.tzinfo is None:
# SQLAlchemy may return persisted UTC values without tzinfo. Never
# reinterpret them in the host's local timezone.
created_at = created_at.replace(tzinfo=timezone.utc)
return int(created_at.timestamp())
async def build_heartbeat_payloads(ap: core_app.Application) -> list[dict]:
"""Build one heartbeat per active Workspace."""
bindings = await ap.workspace_service.list_active_execution_bindings()
workspace_uuids = sorted({binding.workspace_uuid for binding in bindings})
workspace_create_ts = {
binding.workspace_uuid: _workspace_created_timestamp(getattr(binding, 'workspace_created_at', None))
for binding in bindings
}
resources = {
resource['workspace_uuid']: resource for resource in await _cloud_workspace_resource_counts(ap, bindings)
}
@@ -231,6 +248,7 @@ async def build_heartbeat_payloads(ap: core_app.Application) -> list[dict]:
await build_heartbeat_payload(
ap,
workspace_uuid=workspace_uuid,
workspace_create_ts=workspace_create_ts.get(workspace_uuid, 0),
workspace_resource=resources.get(workspace_uuid),
)
for workspace_uuid in workspace_uuids
+8 -2
View File
@@ -4,13 +4,19 @@ import typing
class WorkspaceExecutionContext(typing.Protocol):
@property
def instance_uuid(self) -> str: ...
@property
def workspace_uuid(self) -> str: ...
def workspace_identity(execution_context: WorkspaceExecutionContext) -> dict[str, str]:
"""Build the canonical telemetry identity for one Workspace execution."""
"""Build both first-class telemetry identities for one execution."""
instance_id = execution_context.instance_uuid.strip()
workspace_uuid = execution_context.workspace_uuid.strip()
if not instance_id:
raise ValueError('Telemetry execution instance ID is empty')
if not workspace_uuid:
raise ValueError('Telemetry execution Workspace UUID is empty')
return {'workspace_uuid': workspace_uuid}
return {'instance_id': instance_id, 'workspace_uuid': workspace_uuid}
+24 -5
View File
@@ -136,12 +136,31 @@ class TelemetryManager:
try:
# Use asyncio.wait_for to ensure we always bound the total time
telemetry_token = os.getenv('LANGBOT_TELEMETRY_INGEST_TOKEN', '').strip()
headers: dict[str, str] = {}
if telemetry_token:
request = client.post(
url,
json=sanitized,
headers={'X-LangBot-Telemetry-Token': telemetry_token},
)
headers['X-LangBot-Telemetry-Token'] = telemetry_token
else:
workspace_uuid = str(sanitized.get('workspace_uuid', '')).strip()
user_service = getattr(self.ap, 'user_service', None)
if workspace_uuid and user_service is not None:
try:
owner = await user_service.get_workspace_owner(workspace_uuid)
owner_email = str(getattr(owner, 'user', '') or '').strip()
space_service = getattr(self.ap, 'space_service', None)
access_token = (
await space_service.get_valid_access_token(owner_email)
if owner_email and space_service is not None
else None
)
access_token = str(access_token or '').strip()
if access_token:
headers['Authorization'] = f'Bearer {access_token}'
except Exception:
self.ap.logger.debug(
'Could not resolve authenticated telemetry reporter', exc_info=True
)
if headers:
request = client.post(url, json=sanitized, headers=headers)
else:
request = client.post(url, json=sanitized)
resp = await asyncio.wait_for(request, timeout=10 + 1)