Files
pikasTech-HWLAB/scripts/validate-contract.mjs
T
Lyon c7de4745f4 feat: add Cloud Workbench Code Agent chat
Adds the Cloud Workbench Code Agent chat path and keeps provider/credential gaps explicit instead of faking replies.
2026-05-22 21:17:11 +08:00

161 lines
6.9 KiB
JavaScript

#!/usr/bin/env node
import assert from "node:assert/strict";
import { readdir, readFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import {
ENVIRONMENT_DEV,
SERVICE_IDS,
TABLES,
validateRequest,
validateResponse
} from "../internal/protocol/index.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
async function parseJSONFiles(dir, { requireSchema = true } = {}) {
const entries = await readdir(path.join(repoRoot, dir), { withFileTypes: true });
const parsed = [];
for (const entry of entries) {
if (!entry.isFile() || !entry.name.endsWith(".json")) {
continue;
}
const relativePath = path.join(dir, entry.name);
const raw = await readFile(path.join(repoRoot, relativePath), "utf8");
const doc = JSON.parse(raw);
if (requireSchema) {
assert.ok(doc.$schema, `${relativePath} missing $schema`);
assert.ok(doc.$id, `${relativePath} missing $id`);
}
parsed.push({ relativePath, doc });
}
return parsed;
}
async function parseJSONSchemaFiles(dir) {
const parsed = [];
for (const item of await parseJSONFiles(dir, { requireSchema: false })) {
if (!item.doc.$schema || !item.doc.$id) {
continue;
}
parsed.push(item);
}
return parsed;
}
async function parseDeployManifest(relativePath) {
const raw = await readFile(path.join(repoRoot, relativePath), "utf8");
const doc = JSON.parse(raw);
assert.equal(doc.manifestVersion, "v1", `${relativePath} manifestVersion`);
assert.equal(doc.environment, ENVIRONMENT_DEV, `${relativePath} environment`);
assert.ok(doc.commitId, `${relativePath} commitId`);
assert.ok(Array.isArray(doc.services) && doc.services.length >= 1, `${relativePath} services`);
return doc;
}
function assertUnique(name, values) {
assert.equal(new Set(values).size, values.length, `${name} must be unique`);
}
function assertCommonSchema(commonSchema) {
const schemaServiceIds = commonSchema.$defs.serviceId.enum;
assert.deepEqual(schemaServiceIds, SERVICE_IDS, "common service ids must match runtime constants");
assert.deepEqual(commonSchema.$defs.environment.enum, [ENVIRONMENT_DEV]);
}
function assertDeploySchema(deploySchema) {
const serviceIds = deploySchema.$defs.serviceId.enum;
assert.deepEqual(serviceIds, SERVICE_IDS, "deploy service ids must match runtime constants");
assert.equal(deploySchema.properties.endpoint.const, "http://74.48.78.17:16667");
assert.ok(deploySchema.required.includes("health"), "deploy schema requires health source");
assert.ok(deploySchema.required.includes("publicEndpoints"), "deploy schema requires public endpoint source");
assert.ok(deploySchema.required.includes("frp"), "deploy schema requires frp source");
assert.ok(deploySchema.required.includes("k3s"), "deploy schema requires k3s source");
assert.equal(deploySchema.$defs.health.properties.path.const, "/health/live", "deploy health path source");
}
function assertEnvelopeValidation() {
validateRequest({
jsonrpc: "2.0",
id: "req_01J00000000000000000000000",
method: "hardware.operation.request",
params: {
projectId: "prj_01J00000000000000000000000"
},
meta: {
traceId: "trc_01J00000000000000000000000",
serviceId: "hwlab-cloud-api",
environment: "dev"
}
});
validateResponse({
jsonrpc: "2.0",
id: "req_01J00000000000000000000000",
result: {
accepted: true
},
meta: {
traceId: "trc_01J00000000000000000000000",
serviceId: "hwlab-agent-mgr",
environment: "dev"
}
});
assert.throws(
() =>
validateResponse({
jsonrpc: "2.0",
id: "req_01J00000000000000000000000",
result: {},
error: { code: -32000, message: "bad" },
meta: {
traceId: "trc_01J00000000000000000000000",
serviceId: "hwlab-agent-mgr",
environment: "dev"
}
}),
/exactly one/
);
}
const schemas = await parseJSONSchemaFiles("protocol/schemas");
const deploySchemas = await parseJSONSchemaFiles("deploy");
const deployManifest = await parseDeployManifest("deploy/deploy.json");
const docsByName = new Map([...schemas, ...deploySchemas].map((item) => [path.basename(item.relativePath), item.doc]));
assert.ok(schemas.length >= 13, "expected protocol schemas");
assert.ok(TABLES.includes("patch_panel_status"), "frozen tables must include patch_panel_status");
assertUnique("service ids", SERVICE_IDS);
assertUnique("tables", TABLES);
assertCommonSchema(docsByName.get("common.json"));
assertDeploySchema(docsByName.get("deploy.schema.json"));
assertEnvelopeValidation();
const deployServicesById = new Map(deployManifest.services.map((service) => [service.serviceId, service]));
assert.equal(deployServicesById.has("hwlab-agent-mgr"), true);
assert.equal(deployServicesById.has("hwlab-agent-worker"), true);
assert.equal(deployServicesById.has("hwlab-agent-skills"), true);
const cloudApi = deployServicesById.get("hwlab-cloud-api");
assert.equal(deployManifest.health.path, "/health/live", "deploy health source path");
assert.equal(deployManifest.publicEndpoints.frontend.url, "http://74.48.78.17:16666", "deploy frontend endpoint");
assert.equal(deployManifest.publicEndpoints.api.url, "http://74.48.78.17:16667", "deploy api endpoint");
assert.equal(deployManifest.frp.proxies.find((proxy) => proxy.endpointId === "frontend")?.remotePort, 16666, "deploy frontend frp remote port");
assert.equal(deployManifest.frp.proxies.find((proxy) => proxy.endpointId === "api")?.remotePort, 16667, "deploy api frp remote port");
assert.ok(
deployManifest.k3s.serviceMappings.some((mapping) => mapping.serviceId === "hwlab-edge-proxy" && mapping.port === 6667),
"deploy edge proxy k3s mapping"
);
assert.equal(cloudApi.healthPath, "/health/live", "cloud-api health path");
assert.equal(cloudApi.env.HWLAB_COMMIT_ID, deployManifest.commitId, "cloud-api health commit evidence");
assert.equal(cloudApi.env.HWLAB_IMAGE, cloudApi.image, "cloud-api health image evidence");
assert.equal(cloudApi.env.HWLAB_IMAGE_TAG, deployManifest.commitId.slice(0, 7), "cloud-api health image tag evidence");
assert.equal(cloudApi.env.HWLAB_CLOUD_DB_URL, "secretRef:hwlab-cloud-api-dev-db/database-url", "cloud-api DB URL must be a Secret reference placeholder");
assert.equal(cloudApi.env.HWLAB_CLOUD_DB_SSL_MODE, "require", "cloud-api DB SSL mode");
assert.equal(cloudApi.env.HWLAB_CLOUD_DB_CONTRACT, "dev-redacted-presence-only", "cloud-api DB contract marker");
assert.equal(cloudApi.env.HWLAB_CODE_AGENT_PROVIDER, "openai", "cloud-api Code Agent provider");
assert.equal(cloudApi.env.HWLAB_CODE_AGENT_MODEL, "gpt-5.5", "cloud-api Code Agent model");
assert.equal(cloudApi.env.OPENAI_API_KEY, "secretRef:hwlab-code-agent-provider/openai-api-key", "cloud-api Code Agent OpenAI key must be a Secret reference placeholder");
console.log(`validated ${schemas.length + deploySchemas.length} JSON files and ${SERVICE_IDS.length} service ids`);