diff --git a/tools/hwlab-cli/hwpod.test.ts b/tools/hwlab-cli/hwpod.test.ts index d3353a21..5c4125a0 100644 --- a/tools/hwlab-cli/hwpod.test.ts +++ b/tools/hwlab-cli/hwpod.test.ts @@ -196,6 +196,41 @@ test("HWPOD operation status requires explicit over-api mode", async () => { assert.equal(result.payload.error.code, "hwpod_operation_status_over_api_required"); }); +test("HWPOD over-api resolves a registry spec from the API envelope", async () => { + let submitted: any = null; + const fetchImpl = async (input: string | URL | Request, init?: RequestInit) => { + const url = new URL(String(input)); + if (url.pathname === "/v1/hwpod/specs" && init?.method === "GET") { + return Response.json({ + ok: true, + status: "completed", + body: { + specs: [{ + hwpodId: "builtin-hwpod", + authority: "yaml-first-builtin", + document: hwpodSpecDocument("builtin-hwpod", "/builtin") + }] + } + }); + } + if (url.pathname === "/v1/hwpod/operations" && init?.method === "POST") { + submitted = JSON.parse(String(init.body)); + return Response.json({ ok: true, status: "accepted", operationId: submitted.operationId }, { status: 202 }); + } + return Response.json({ ok: false, error: { code: "unexpected_route" } }, { status: 404 }); + }; + + const result = await runHwpodCli([ + "workspace", "ls", ".", "--hwpod-id", "builtin-hwpod", "--over-api", "--api-base-url", "http://hwpod.test" + ], { fetchImpl: fetchImpl as typeof fetch }); + + assert.equal(result.exitCode, 0); + assert.equal(result.payload.hwpodId, "builtin-hwpod"); + assert.equal(result.payload.specAuthority, "yaml-first-builtin"); + assert.equal(submitted.plan.intent, "workspace.ls"); + assert.equal(submitted.plan.ops[0].args.workspacePath, "/builtin"); +}); + test("HWPOD L0 CLI completes runtime spec CRUD and rejects built-in mutation", async () => { const root = await mkdtemp(path.join(os.tmpdir(), "hwpod-spec-cli-test-")); const createPath = path.join(root, "create.yaml"); diff --git a/tools/src/hwpod-harness-lib.ts b/tools/src/hwpod-harness-lib.ts index 319e1d3b..e1926a1a 100644 --- a/tools/src/hwpod-harness-lib.ts +++ b/tools/src/hwpod-harness-lib.ts @@ -597,7 +597,8 @@ async function fetchHwpodSpecById({ hwpodId, parsed, env, fetchImpl }: { hwpodId : await requestJsonNative(url, { method: route.method, headers, timeoutMs: numberValue(parsed.timeoutMs) ?? DEFAULT_TIMEOUT_MS }); const body = await response.json().catch(() => null); if (!body || body.ok === false) throw cliError("hwpod_registry_request_failed", "failed to query HWPOD registry", { hwpodId, httpStatus: response.status, body: compactObject(body), runtimeEndpoint: runtimeEndpointVisibility(endpoint) }); - const specs = Array.isArray(body.specs) ? body.specs : []; + const registryBody = isPlainObject(body.body) ? body.body : body; + const specs = Array.isArray(registryBody.specs) ? registryBody.specs : []; const match = specs.find((item: any) => text(item.hwpodId) === hwpodId || text(item.name) === hwpodId || text(item.uid) === hwpodId); if (!match) throw cliError("hwpod_id_not_found", `HWPOD id not found in runtime registry: ${hwpodId}`, { hwpodId, count: specs.length, runtimeEndpoint: runtimeEndpointVisibility(endpoint) }); const document = objectValue(match.document);