feat: enforce v02 runtime authority

This commit is contained in:
Codex
2026-05-29 08:10:19 +08:00
parent bc816cea4b
commit b99c663614
18 changed files with 461 additions and 144 deletions
+28 -6
View File
@@ -5,28 +5,50 @@ import test from "node:test";
const bunCommand = process.env.HWLAB_TEST_BUN_COMMAND || process.env.HWLAB_BUN_COMMAND || (process.versions.bun ? process.execPath : "bun");
test("device pod fake service exposes health, list, status, events, and method guards", async () => {
test("device pod executor exposes cloud-api authority boundary and method guards", async () => {
const service = await startDevicePod("device-pod-test");
try {
const health = await fetchJson(`http://127.0.0.1:${service.port}/health/live`);
assert.equal(health.serviceId, "hwlab-device-pod");
assert.equal(health.status, "live");
assert.equal(health.devicePodId, "device-pod-test");
assert.equal(health.role, "fake-device-pod-server");
assert.equal(health.contractVersion, "device-pod-executor-v1");
assert.equal(health.role, "device-pod-internal-executor");
assert.equal(health.authority, "hwlab-cloud-api");
assert.equal(health.fake, false);
const list = await fetchJson(`http://127.0.0.1:${service.port}/v1/device-pods`);
assert.equal(list.serviceId, "hwlab-device-pod");
assert.equal(list.selectedDevicePodId, "device-pod-test");
assert.equal(list.devicePods.length, 1);
assert.equal(list.source.fake, false);
const status = await fetchJson(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/status`);
assert.equal(status.devicePod.devicePodId, "device-pod-test");
assert.equal(status.summary.targetId, "stm32f103-minsys-01");
assert.equal(status.devicePodId, "device-pod-test");
assert.equal(status.status, "blocked");
assert.equal(status.summary.blocker.code, "gateway_dispatch_unavailable");
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);
assert.equal(events.truncation.limit, 1);
assert.equal(events.truncation.truncated, true);
assert.equal(events.truncation.truncated, false);
assert.equal(events.events[0].blocker.code, "gateway_dispatch_unavailable");
const externalJob = await fetch(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/jobs`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ intent: "workspace.ls" })
});
assert.equal(externalJob.status, 403);
assert.equal((await externalJob.json()).error.code, "cloud_api_authority_required");
const internalJob = await fetch(`http://127.0.0.1:${service.port}/v1/device-pods/device-pod-test/jobs`, {
method: "POST",
headers: { "content-type": "application/json", "x-hwlab-internal-service": "hwlab-cloud-api" },
body: JSON.stringify({ intent: "workspace.ls", traceId: "trc_device_pod_test" })
});
assert.equal(internalJob.status, 409);
assert.equal((await internalJob.json()).blocker.code, "gateway_dispatch_unavailable");
const missing = await fetch(`http://127.0.0.1:${service.port}/missing`);
assert.equal(missing.status, 404);
@@ -34,7 +56,7 @@ test("device pod fake service exposes health, list, status, events, and method g
const methodGuard = await fetch(`http://127.0.0.1:${service.port}/health`, { method: "POST" });
assert.equal(methodGuard.status, 405);
assert.deepEqual(await methodGuard.json(), { error: "method_not_allowed", method: "POST" });
assert.deepEqual(await methodGuard.json(), { error: "method_not_allowed", method: "POST", path: "/health" });
} finally {
await service.stop();
}
+156 -29
View File
@@ -1,47 +1,174 @@
#!/usr/bin/env node
import { createServer } from "node:http";
import { randomUUID } from "node:crypto";
import { jsonResponse, listen, parsePort } from "../../internal/sim/http.mjs";
import {
DEFAULT_DEVICE_POD_ID,
DEVICE_POD_SERVICE_ID,
buildDevicePodRestPayload
} from "../../internal/device-pod/fake-data.mjs";
import { jsonResponse, listen, parsePort, readJson } from "../../internal/sim/http.mjs";
const SERVICE_ID = "hwlab-device-pod";
const CONTRACT_VERSION = "device-pod-executor-v1";
const DEFAULT_DEVICE_POD_ID = "device-pod-71-freq";
const port = parsePort(process.env.HWLAB_DEVICE_POD_PORT, parsePort(process.env.PORT, 7601));
const devicePodId = process.env.HWLAB_DEVICE_POD_ID || DEFAULT_DEVICE_POD_ID;
const environment = process.env.HWLAB_ENVIRONMENT || process.env.HWLAB_GITOPS_PROFILE || "dev";
listen(createServer((request, response) => {
const url = new URL(request.url ?? "/", "http://localhost");
if (request.method !== "GET") {
jsonResponse(response, 405, { error: "method_not_allowed", method: request.method ?? "UNKNOWN" });
return;
listen(createServer(async (request, response) => {
try {
const url = new URL(request.url ?? "/", "http://localhost");
if (request.method === "GET" && (url.pathname === "/health" || url.pathname === "/health/live")) {
jsonResponse(response, 200, healthPayload());
return;
}
if (url.pathname === "/health" || url.pathname === "/health/live") {
jsonResponse(response, 405, { error: "method_not_allowed", method: request.method ?? "UNKNOWN", path: url.pathname });
return;
}
if (request.method === "GET" && url.pathname === "/v1/device-pods") {
jsonResponse(response, 200, listPayload());
return;
}
const route = parseDevicePodPath(url.pathname);
if (!route) {
jsonResponse(response, 404, { error: "not_found", path: url.pathname });
return;
}
if (request.method === "GET" && route.subpath === "status") {
jsonResponse(response, 200, statusPayload(route.devicePodId));
return;
}
if (request.method === "GET" && route.subpath === "events") {
jsonResponse(response, 200, eventsPayload(route.devicePodId, url.searchParams));
return;
}
if (request.method === "POST" && route.subpath === "jobs") {
await handleInternalJob(request, response, route.devicePodId);
return;
}
jsonResponse(response, 405, { error: "method_not_allowed", method: request.method ?? "UNKNOWN", path: url.pathname });
} catch (error) {
jsonResponse(response, 500, { error: "internal_error", message: error?.message ?? String(error), valuesRedacted: true });
}
if (url.pathname === "/health" || url.pathname === "/health/live") {
jsonResponse(response, 200, healthPayload());
return;
}
const payload = buildDevicePodRestPayload(url.pathname, url.searchParams, {
devicePodId,
sourceKind: "FAKE-SERVICE"
});
if (!payload) {
jsonResponse(response, 404, { error: "not_found", path: url.pathname });
return;
}
jsonResponse(response, 200, payload);
}), port);
function healthPayload() {
return {
serviceId: DEVICE_POD_SERVICE_ID,
environment: process.env.HWLAB_ENVIRONMENT || "dev",
serviceId: SERVICE_ID,
environment,
status: "live",
ready: true,
contractVersion: "device-pod-fake-v1",
contractVersion: CONTRACT_VERSION,
devicePodId,
observedAt: new Date().toISOString(),
role: "fake-device-pod-server",
note: "Only fake frontend data is served; no hardware connection is opened."
role: "device-pod-internal-executor",
authority: "hwlab-cloud-api",
acceptsUserAuthority: false,
fake: false,
source: sourcePayload(),
note: "Profile, grant, lease, and user-facing job authority live in hwlab-cloud-api; this service only exposes the internal executor boundary."
};
}
function listPayload() {
return {
serviceId: SERVICE_ID,
contractVersion: CONTRACT_VERSION,
status: "ok",
source: sourcePayload(),
selectedDevicePodId: devicePodId,
devicePods: [{
devicePodId,
status: "executor-ready",
authority: "hwlab-cloud-api",
fake: false,
routes: {
cloudAuthority: "/v1/device-pods",
internalJob: `/v1/device-pods/${encodeURIComponent(devicePodId)}/jobs`
}
}]
};
}
function statusPayload(id) {
return {
serviceId: SERVICE_ID,
contractVersion: CONTRACT_VERSION,
status: "blocked",
devicePodId: id,
observedAt: new Date().toISOString(),
source: sourcePayload(),
summary: {
devicePodId: id,
status: "blocked",
blocker: gatewayAdapterBlocker("device-host-cli dispatch is owned by hwlab-cloud-api gateway routing")
}
};
}
function eventsPayload(id, searchParams) {
const limit = Math.min(Math.max(Number.parseInt(searchParams.get("limit") ?? "80", 10) || 80, 1), 1000);
const event = {
eventId: `evt_${id}_executor_boundary`,
devicePodId: id,
ts: new Date().toISOString(),
level: "warn",
scope: "executor",
status: "blocked",
summary: "No standalone fake device events are emitted by hwlab-device-pod; use hwlab-cloud-api job authority.",
blocker: gatewayAdapterBlocker("standalone device-pod executor has no gateway session")
};
return {
serviceId: SERVICE_ID,
contractVersion: CONTRACT_VERSION,
status: "ok",
devicePodId: id,
source: sourcePayload(),
events: limit > 0 ? [event] : [],
truncation: { limit, returned: limit > 0 ? 1 : 0, truncated: false }
};
}
async function handleInternalJob(request, response, id) {
if (!isInternalCaller(request)) {
jsonResponse(response, 403, {
accepted: false,
status: "blocked",
error: {
code: "cloud_api_authority_required",
message: "Device Pod jobs must be authorized by hwlab-cloud-api before reaching the internal executor."
},
source: sourcePayload()
});
return;
}
const body = await readJson(request);
const traceId = typeof body.traceId === "string" && body.traceId.startsWith("trc_") ? body.traceId : `trc_devicepod_${randomUUID()}`;
jsonResponse(response, 409, {
accepted: false,
status: "blocked",
contractVersion: CONTRACT_VERSION,
devicePodId: id,
traceId,
operationId: typeof body.operationId === "string" ? body.operationId : `op_devicepod_${randomUUID()}`,
blocker: gatewayAdapterBlocker("gateway/device-host-cli adapter is not connected to this standalone executor service"),
source: sourcePayload()
});
}
function parseDevicePodPath(pathname) {
const prefix = "/v1/device-pods/";
if (!pathname.startsWith(prefix)) return null;
const [id, ...rest] = pathname.slice(prefix.length).split("/").filter(Boolean).map((part) => decodeURIComponent(part));
return id ? { devicePodId: id, subpath: rest.join("/") } : null;
}
function isInternalCaller(request) {
const header = request.headers["x-hwlab-internal-service"];
return String(Array.isArray(header) ? header[0] : header ?? "").trim() === "hwlab-cloud-api";
}
function sourcePayload() {
return { kind: "INTERNAL_EXECUTOR", serviceId: SERVICE_ID, authority: "hwlab-cloud-api", fake: false, devLiveEvidence: false };
}
function gatewayAdapterBlocker(summary) {
return { code: "gateway_dispatch_unavailable", layer: "device-pod", retryable: true, summary, userMessage: "Device Pod executor boundary is present, but gateway/device-host-cli dispatch is not connected here." };
}