diff --git a/cmd/hwlab-device-pod/main.test.ts b/cmd/hwlab-device-pod/main.test.ts index 912cd627..6a083d44 100644 --- a/cmd/hwlab-device-pod/main.test.ts +++ b/cmd/hwlab-device-pod/main.test.ts @@ -28,6 +28,13 @@ test("device pod executor exposes cloud-api authority boundary and method guards assert.equal(status.devicePodId, "device-pod-test"); assert.equal(status.status, "blocked"); assert.equal(status.summary.blocker.code, "gateway_dispatch_unavailable"); + assert.equal(status.targetId, null); + assert.equal(status.profileHash, ""); + assert.match(status.traceId, /^trc_devicepod_/u); + assert.match(status.operationId, /^op_devicepod_/u); + assert.equal(status.freshness.stale, true); + assert.equal(status.truncation.truncated, false); + assert.match(status.output.summary, /device-host-cli dispatch is owned/u); const events = await fetchJson(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/events?limit=1`); assert.equal(events.events.length, 1); diff --git a/cmd/hwlab-device-pod/main.ts b/cmd/hwlab-device-pod/main.ts index 54301102..dc6d490c 100644 --- a/cmd/hwlab-device-pod/main.ts +++ b/cmd/hwlab-device-pod/main.ts @@ -103,17 +103,34 @@ function listPayload() { } function statusPayload(id) { + const blocker = gatewayAdapterBlocker("device-host-cli dispatch is owned by hwlab-cloud-api gateway routing"); + const observedAt = new Date().toISOString(); + const output = boundedOutput({ text: blocker.summary, summary: blocker.summary }); + const freshnessPayload = freshness(observedAt, blocker); return { serviceId: SERVICE_ID, contractVersion: CONTRACT_VERSION, status: "blocked", devicePodId: id, - observedAt: new Date().toISOString(), + targetId: null, + profileHash: "", + traceId: `trc_devicepod_${randomUUID()}`, + operationId: `op_devicepod_${randomUUID()}`, + observedAt, source: sourcePayload(), + blocker, + freshness: freshnessPayload, + output, + text: output.text, + bytes: output.bytes, + truncation: output.truncation, summary: { devicePodId: id, + targetId: null, + profileHash: "", status: "blocked", - blocker: gatewayAdapterBlocker("device-host-cli dispatch is owned by hwlab-cloud-api gateway routing") + freshness: freshnessPayload, + blocker } }; } diff --git a/internal/cloud/access-control.test.ts b/internal/cloud/access-control.test.ts index 5e0ff10e..b8fd0820 100644 --- a/internal/cloud/access-control.test.ts +++ b/internal/cloud/access-control.test.ts @@ -110,6 +110,19 @@ test("cloud api access control grants visible device pods and requires device-po assert.equal(visible.body.devicePods[0].devicePodId, "device-pod-71-freq"); assert.match(visible.body.devicePods[0].profileHash, /^sha256:/u); + const status = await getJson(port, "/v1/device-pods/device-pod-71-freq/status", aliceCookie); + assert.equal(status.status, 200); + assert.equal(status.body.devicePodId, "device-pod-71-freq"); + assert.equal(status.body.targetId, "target-71-freq"); + assert.equal(status.body.profileHash, visible.body.devicePods[0].profileHash); + assert.match(status.body.traceId, /^trc_devicepod_/u); + assert.match(status.body.operationId, /^op_devicepod_/u); + assert.equal(status.body.status, "ok"); + assert.equal(status.body.freshness.stale, false); + assert.equal(status.body.blocker, null); + assert.equal(status.body.truncation.truncated, false); + assert.equal(status.body.output.summary, "device-pod status ok"); + const job = await postJson(port, "/v1/device-pods/device-pod-71-freq/jobs", { intent: "workspace.ls", args: { path: "." } diff --git a/internal/cloud/access-control.ts b/internal/cloud/access-control.ts index 32338304..c8afc539 100644 --- a/internal/cloud/access-control.ts +++ b/internal/cloud/access-control.ts @@ -609,19 +609,34 @@ class AccessController { const gatewaySessionId = textOr(route.gatewaySessionId, ""); const gatewayOnline = gatewaySessionId && this.gatewayRegistry?.isOnline?.(gatewaySessionId) === true; const blocker = gatewayOnline ? null : gatewayDispatchBlocker(gatewaySessionId ? "gateway session is not connected" : "profile route.gatewaySessionId is missing"); + const observedAt = this.now(); + const status = blocker ? "blocked" : "ok"; + const output = boundedOutput({ text: blocker?.summary ?? "device-pod status ok", summary: blocker?.summary ?? "device-pod status ok" }); + const freshnessPayload = freshness(observedAt, blocker); return { serviceId: CLOUD_API_SERVICE_ID, contractVersion: "device-pod-authority-v1", - status: blocker ? "blocked" : "ok", - observedAt: this.now(), + status, + devicePodId: pod.id, + targetId: targetIdFromProfile(pod.profile), + profileHash: pod.profileHash, + traceId: `trc_devicepod_${randomUUID()}`, + operationId: `op_devicepod_${randomUUID()}`, + observedAt, source: authoritySource(), actor: publicActor(actor), devicePod: publicDevicePod(pod), + blocker, + freshness: freshnessPayload, + output: output.output, + text: output.text, + bytes: output.bytes, + truncation: output.truncation, summary: { devicePodId: pod.id, targetId: targetIdFromProfile(pod.profile), - status: blocker ? "blocked" : "ok", - freshness: freshness(this.now(), blocker), + status, + freshness: freshnessPayload, profileHash: pod.profileHash, blocker }