fix: rebuild HWPOD native L1 shell

This commit is contained in:
root
2026-07-21 08:46:18 +02:00
parent 8065ed4c13
commit c59a814ebb
8 changed files with 164 additions and 13 deletions
+21 -1
View File
@@ -6,6 +6,7 @@
*/
import { validateHwpodOperationInput } from "./contracts.ts";
import { buildHwpodTopologyReadModel } from "../cloud/hwpod-topology-read-model.ts";
export function createHwpodHttpApp(options: { runtimeApiUrl: string; runtimeApiAuthorization: string; temporal: any; specRegistry: any }) {
return {
@@ -28,7 +29,9 @@ export function createHwpodHttpApp(options: { runtimeApiUrl: string; runtimeApiA
if (request.method === "DELETE") return json(200, await options.specRegistry.delete(hwpodId));
return json(405, { ok: false, error: { code: "method_not_allowed", message: "GET, PUT, or DELETE required" }, valuesPrinted: false });
}
if (url.pathname === "/v1/hwpod/topology" && request.method === "GET") return proxyGet(options.runtimeApiUrl, `/v1/hwpod/topology${url.search}`, options.runtimeApiAuthorization);
if (url.pathname === "/v1/hwpod/topology" && request.method === "GET") {
return json(200, await buildHwpodTopologyFromRegistry(options.specRegistry, Object.fromEntries(url.searchParams)));
}
if (url.pathname === "/v1/hwpod-node-ops" && request.method === "GET") return proxyGet(options.runtimeApiUrl, `/v1/hwpod-node-ops${url.search}`, options.runtimeApiAuthorization);
if (url.pathname === "/v1/hwpod/operations" && request.method === "POST") {
const body = await request.json();
@@ -59,6 +62,23 @@ export function createHwpodHttpApp(options: { runtimeApiUrl: string; runtimeApiA
};
}
export async function buildHwpodTopologyFromRegistry(specRegistry: any, query: Record<string, string> = {}) {
const listed = await specRegistry.list();
const specs = await Promise.all((listed.specs ?? []).map(async (summary: any) => {
const entity = await specRegistry.get(summary.hwpodId);
return {
...summary,
document: entity.document,
authority: entity.authority,
nodeId: entity.nodeId,
};
}));
return buildHwpodTopologyReadModel(specs, {
mode: "hwpod-native-api-no-node-registry",
nodes: [],
}, query);
}
async function readiness(options: { runtimeApiUrl: string; runtimeApiAuthorization: string; temporal: any; specRegistry: any }) {
try {
const specs = await options.specRegistry.list();
+26 -1
View File
@@ -6,7 +6,7 @@
import { expect, test } from "bun:test";
import { createHwpodHttpApp } from "./http.ts";
import { buildHwpodTopologyFromRegistry, createHwpodHttpApp } from "./http.ts";
import { createHwpodSpecRegistry, type HwpodRuntimeSpecRecord, type HwpodRuntimeSpecStore } from "./spec-registry.ts";
test("runtime spec repository persists CRUD and freezes YAML-first built-ins", async () => {
@@ -53,6 +53,31 @@ test("L1 HWPOD HTTP exposes runtime CRUD and typed frozen errors", async () => {
await app.close();
});
test("L0 HWPOD topology is built from the local registry without Cloud API", async () => {
const registry = createHwpodSpecRegistry({
store: memoryStore(),
builtIns: [{ configRef: "config/hwpod.yaml#spec", document: spec("builtin-pod", "/builtin") }],
});
await registry.create(spec("runtime-pod", "/runtime"));
const topology = await buildHwpodTopologyFromRegistry(registry, { resource: "device", q: "runtime-pod" });
expect(topology).toMatchObject({
ok: true,
contractVersion: "hwpod-topology-v1",
resource: "device",
service: { connectedNodeCount: 0, websocketMode: "hwpod-native-api-no-node-registry" },
summary: { deviceCount: 2, onlineNodeCount: 0 },
items: [{ hwpodId: "runtime-pod", nodeId: "node-test", status: "offline", source: { authority: "runtime" } }],
});
expect(topology.items[0].blockers).toContainEqual(expect.objectContaining({ code: "hwpod_node_offline" }));
const temporal = { close: async () => {}, start: async () => ({}), describe: async () => ({}), result: async () => ({}) };
const app = createHwpodHttpApp({ runtimeApiUrl: "http://runtime.invalid", runtimeApiAuthorization: "Bearer test", temporal, specRegistry: registry });
const response = await request(app, "GET", "/v1/hwpod/topology?resource=node");
expect(response).toMatchObject({ status: 200, body: { ok: true, resource: "node", items: [{ nodeId: "node-test", status: "offline" }] } });
await app.close();
});
async function request(app: any, method: string, pathname: string, body?: unknown) {
const response = await app.fetch(new Request(`http://hwpod.test${pathname}`, { method, headers: { "content-type": "application/json" }, ...(body === undefined ? {} : { body: JSON.stringify(body) }) }));
return { status: response.status, body: await response.json() };