282 lines
14 KiB
JavaScript
282 lines
14 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 {
|
|
DEV_DB_ENV_CONTRACT,
|
|
parseDbUrlContract
|
|
} from "../internal/cloud/db-contract.mjs";
|
|
import {
|
|
DEV_CODE_AGENT_PROVIDER_CONTRACT,
|
|
codeAgentSecretRefPlaceholder
|
|
} from "../internal/cloud/code-agent-contract.mjs";
|
|
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;
|
|
}
|
|
|
|
async function parseK8sList(relativePath) {
|
|
const raw = await readFile(path.join(repoRoot, relativePath), "utf8");
|
|
const doc = JSON.parse(raw);
|
|
assert.equal(doc.kind, "List", `${relativePath} kind`);
|
|
assert.ok(Array.isArray(doc.items), `${relativePath} items`);
|
|
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 k8sServices = await parseK8sList("deploy/k8s/base/services.yaml");
|
|
const k8sWorkloads = await parseK8sList("deploy/k8s/base/workloads.yaml");
|
|
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");
|
|
const cloudApiWorkloadEnv = getWorkloadEnv(k8sWorkloads, "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_CLOUD_DB_SERVICE_NAME, DEV_DB_ENV_CONTRACT.dns.serviceName, "cloud-api DB service name");
|
|
assert.equal(cloudApi.env.HWLAB_CLOUD_DB_SERVICE_NAMESPACE, DEV_DB_ENV_CONTRACT.dns.namespace, "cloud-api DB service namespace");
|
|
assert.equal(cloudApi.env.HWLAB_CLOUD_DB_HOST, DEV_DB_ENV_CONTRACT.dns.host, "cloud-api DB stable host");
|
|
assert.equal(cloudApi.env.HWLAB_CLOUD_DB_PORT, String(DEV_DB_ENV_CONTRACT.dns.port), "cloud-api DB stable port");
|
|
assert.equal(cloudApi.env.HWLAB_CLOUD_RUNTIME_ADAPTER, "postgres", "cloud-api runtime durable adapter");
|
|
assert.equal(cloudApi.env.HWLAB_CLOUD_RUNTIME_DURABLE, "true", "cloud-api runtime durable flag");
|
|
assert.equal(cloudApiWorkloadEnv.HWLAB_CLOUD_DB_SERVICE_NAME?.value, DEV_DB_ENV_CONTRACT.dns.serviceName, "cloud-api workload DB service name");
|
|
assert.equal(cloudApiWorkloadEnv.HWLAB_CLOUD_DB_SERVICE_NAMESPACE?.value, DEV_DB_ENV_CONTRACT.dns.namespace, "cloud-api workload DB service namespace");
|
|
assert.equal(cloudApiWorkloadEnv.HWLAB_CLOUD_DB_HOST?.value, DEV_DB_ENV_CONTRACT.dns.host, "cloud-api workload DB host");
|
|
assert.equal(cloudApiWorkloadEnv.HWLAB_CLOUD_DB_PORT?.value, String(DEV_DB_ENV_CONTRACT.dns.port), "cloud-api workload DB port");
|
|
assert.equal(cloudApiWorkloadEnv.HWLAB_CLOUD_RUNTIME_ADAPTER?.value, "postgres", "cloud-api workload runtime durable adapter");
|
|
assert.equal(cloudApiWorkloadEnv.HWLAB_CLOUD_RUNTIME_DURABLE?.value, "true", "cloud-api workload runtime durable flag");
|
|
assertCloudApiDbDnsContract(k8sServices, cloudApi.env);
|
|
assert.equal(cloudApi.env.HWLAB_CODE_AGENT_PROVIDER, "openai", "cloud-api Code Agent provider");
|
|
assert.equal(cloudApi.env.HWLAB_CODE_AGENT_MODEL, DEV_CODE_AGENT_PROVIDER_CONTRACT.model, "cloud-api Code Agent model");
|
|
assert.equal(
|
|
cloudApi.env.HWLAB_CODE_AGENT_OPENAI_BASE_URL,
|
|
DEV_CODE_AGENT_PROVIDER_CONTRACT.egress.defaultBaseUrl,
|
|
"cloud-api Code Agent OpenAI base URL must use DEV egress/proxy"
|
|
);
|
|
assert.notEqual(
|
|
cloudApi.env.HWLAB_CODE_AGENT_OPENAI_BASE_URL,
|
|
DEV_CODE_AGENT_PROVIDER_CONTRACT.egress.forbiddenDirectBaseUrl,
|
|
"cloud-api Code Agent OpenAI base URL must not point directly at public api.openai.com"
|
|
);
|
|
assert.equal(cloudApi.env.OPENAI_API_KEY, codeAgentSecretRefPlaceholder(), "cloud-api Code Agent OpenAI key must be a Secret reference placeholder");
|
|
assertCodeAgentProviderWorkloadContract(cloudApiWorkloadEnv);
|
|
assertDbForbiddenInvalidHostContract();
|
|
assertValidationDoesNotExposeSecretValues(cloudApi.env, cloudApiWorkloadEnv);
|
|
|
|
console.log(`validated ${schemas.length + deploySchemas.length} JSON files and ${SERVICE_IDS.length} service ids`);
|
|
|
|
function listItems(document) {
|
|
return document?.kind === "List" ? document.items ?? [] : [document].filter(Boolean);
|
|
}
|
|
|
|
function getWorkloadEnv(workloads, serviceId) {
|
|
for (const item of listItems(workloads)) {
|
|
const itemServiceId =
|
|
item?.metadata?.labels?.["hwlab.pikastech.local/service-id"] ||
|
|
item?.spec?.template?.metadata?.labels?.["hwlab.pikastech.local/service-id"] ||
|
|
item?.metadata?.name;
|
|
if (itemServiceId !== serviceId) {
|
|
continue;
|
|
}
|
|
const container = item?.spec?.template?.spec?.containers?.find((entry) => entry.name === serviceId);
|
|
return Object.fromEntries((container?.env ?? []).map((entry) => [entry.name, entry]));
|
|
}
|
|
return {};
|
|
}
|
|
|
|
function assertCloudApiDbDnsContract(services, env) {
|
|
const dns = DEV_DB_ENV_CONTRACT.dns;
|
|
const service = listItems(services).find((item) => item?.metadata?.name === dns.serviceName);
|
|
assert.ok(service, `deploy/k8s/base/services.yaml must define Service/${dns.serviceName}`);
|
|
assert.equal(service.metadata.namespace, dns.namespace, "cloud-api DB Service namespace");
|
|
assert.equal(service.kind, "Service", "cloud-api DB Service kind");
|
|
assert.equal(service.spec?.type, "ClusterIP", "cloud-api DB Service type");
|
|
assert.equal(service.spec?.selector, undefined, "cloud-api DB Service must be selectorless until DB endpoints are provisioned");
|
|
assert.equal(service.metadata?.labels?.["hwlab.pikastech.local/source-contract"], "db-dns", "cloud-api DB Service source-contract label");
|
|
const port = service.spec?.ports?.find((entry) => entry.name === dns.portName);
|
|
assert.ok(port, `Service/${dns.serviceName} must expose ${dns.portName}`);
|
|
assert.equal(port.port, dns.port, "cloud-api DB Service port");
|
|
assert.equal(port.targetPort, dns.portName, "cloud-api DB Service targetPort");
|
|
|
|
const expectedHost = `${dns.serviceName}.${dns.namespace}.svc.cluster.local`;
|
|
assert.equal(dns.host, expectedHost, "DB DNS contract host matches service namespace");
|
|
assert.equal(env.HWLAB_CLOUD_DB_HOST, expectedHost, "cloud-api DB host env matches Service DNS");
|
|
assert.equal(env.HWLAB_CLOUD_DB_SERVICE_NAME, service.metadata.name, "cloud-api DB service env matches Service name");
|
|
assert.equal(env.HWLAB_CLOUD_DB_SERVICE_NAMESPACE, service.metadata.namespace, "cloud-api DB namespace env matches Service namespace");
|
|
assert.equal(env.HWLAB_CLOUD_DB_PORT, String(port.port), "cloud-api DB port env matches Service port");
|
|
const secretUrl = parseDbUrlContract(`postgres://${env.HWLAB_CLOUD_DB_HOST}:${env.HWLAB_CLOUD_DB_PORT}/hwlab`);
|
|
assert.equal(secretUrl.ok, true, "cloud-api DB stable host/port must form a supported postgres URL");
|
|
}
|
|
|
|
function assertCodeAgentProviderWorkloadContract(env) {
|
|
const contract = DEV_CODE_AGENT_PROVIDER_CONTRACT;
|
|
const secretRef = contract.secretRefs[0];
|
|
const apiKey = env[secretRef.env]?.valueFrom?.secretKeyRef;
|
|
assert.equal(env.HWLAB_CODE_AGENT_PROVIDER?.value, contract.provider, "cloud-api workload Code Agent provider");
|
|
assert.equal(env.HWLAB_CODE_AGENT_MODEL?.value, contract.model, "cloud-api workload Code Agent model");
|
|
assert.equal(
|
|
env.HWLAB_CODE_AGENT_OPENAI_BASE_URL?.value,
|
|
contract.egress.defaultBaseUrl,
|
|
"cloud-api workload Code Agent OpenAI base URL must use DEV egress/proxy"
|
|
);
|
|
assert.notEqual(
|
|
env.HWLAB_CODE_AGENT_OPENAI_BASE_URL?.value,
|
|
contract.egress.forbiddenDirectBaseUrl,
|
|
"cloud-api workload Code Agent OpenAI base URL must not point directly at public api.openai.com"
|
|
);
|
|
assert.equal(apiKey?.name, secretRef.secretName, "cloud-api workload Code Agent OpenAI Secret name");
|
|
assert.equal(apiKey?.key, secretRef.secretKey, "cloud-api workload Code Agent OpenAI Secret key");
|
|
}
|
|
|
|
function assertDbForbiddenInvalidHostContract() {
|
|
const invalidHost = parseDbUrlContract("postgres://user:password@hwlab-dev-db.invalid:5432/hwlab");
|
|
assert.equal(invalidHost.ok, false, "DEV DB contract must reject .invalid runtime hosts");
|
|
assert.equal(invalidHost.result, "forbidden_runtime_host", "DEV DB invalid host result");
|
|
assert.equal(invalidHost.classification, "db_url_forbidden_invalid_host", "DEV DB invalid host classification");
|
|
}
|
|
|
|
function assertValidationDoesNotExposeSecretValues(manifestEnv, workloadEnv) {
|
|
const serialized = JSON.stringify({ manifestEnv, workloadEnv });
|
|
assert.equal(/sk-[A-Za-z0-9._-]{16,}/u.test(serialized), false, "validation contract must not include OpenAI key material");
|
|
assert.equal(/postgres:\/\/[^" ]+:[^" ]+@/u.test(serialized), false, "validation contract must not include DB URL credentials");
|
|
}
|