Files
pikasTech-HWLAB/scripts/validate-runtime-boundary.mjs
2026-06-08 22:19:30 +08:00

455 lines
21 KiB
JavaScript

#!/usr/bin/env bun
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { DEV_DB_ENV_CONTRACT } from "../internal/cloud/db-contract.ts";
import { DEV_ENDPOINT, ENVIRONMENT_DEV, SERVICE_IDS } from "../internal/protocol/index.mjs";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
const serviceIdLabel = "hwlab.pikastech.local/service-id";
const namespace = "hwlab-dev";
const forbiddenRuntimeSubstitutes = Object.freeze([
"unidesk-backend",
"provider-gateway",
"microservice-proxy"
]);
async function readText(relativePath) {
return readFile(path.join(repoRoot, relativePath), "utf8");
}
async function readJSON(relativePath) {
const raw = await readText(relativePath);
return JSON.parse(raw);
}
function assertObject(value, label) {
assert.ok(value && typeof value === "object" && !Array.isArray(value), `${label} must be an object`);
}
function assertArray(value, label) {
assert.ok(Array.isArray(value), `${label} must be an array`);
}
function assertUnique(values, label) {
assert.equal(new Set(values).size, values.length, `${label} must be unique`);
}
function assertSameMembers(actual, expected, label) {
assert.deepEqual([...actual].sort(), [...expected].sort(), `${label} must match expected members`);
}
function assertServiceId(serviceId, label) {
assert.ok(SERVICE_IDS.includes(serviceId), `${label} uses unknown serviceId ${JSON.stringify(serviceId)}`);
assert.match(serviceId, /^hwlab-/, `${label} must be an HWLAB service id`);
assertNoForbiddenSubstitute(serviceId, label);
}
function assertDevOnlyEnvironment(value, label) {
assert.equal(value, ENVIRONMENT_DEV, `${label} must be dev`);
}
function assertDevNamespace(value, label) {
assert.equal(value, namespace, `${label} must be ${namespace}`);
}
function assertForbiddenList(actual, label) {
assertArray(actual, label);
assertSameMembers(actual, forbiddenRuntimeSubstitutes, label);
}
function splitCsv(value) {
assert.equal(typeof value, "string", "CSV value must be a string");
return value
.split(",")
.map((item) => item.trim())
.filter(Boolean);
}
function assertNoForbiddenSubstitute(value, label) {
if (typeof value !== "string") {
return;
}
const lower = value.toLowerCase();
for (const forbidden of forbiddenRuntimeSubstitutes) {
assert.equal(
lower.includes(forbidden),
false,
`${label} must not use forbidden runtime substitute ${forbidden}`
);
}
}
function assertNoForbiddenSubstitutesDeep(value, label) {
if (
label.endsWith(".HWLAB_RUNTIME_SUBSTITUTE_FORBIDDEN") ||
label.endsWith(".forbiddenRuntimeSubstitutes")
) {
return;
}
if (typeof value === "string") {
assertNoForbiddenSubstitute(value, label);
return;
}
if (Array.isArray(value)) {
value.forEach((item, index) => assertNoForbiddenSubstitutesDeep(item, `${label}[${index}]`));
return;
}
if (value && typeof value === "object") {
if (value.name === "HWLAB_RUNTIME_SUBSTITUTE_FORBIDDEN") {
return;
}
for (const [key, item] of Object.entries(value)) {
assertNoForbiddenSubstitutesDeep(item, `${label}.${key}`);
}
}
}
function containerList(workload, label) {
const template = workload.kind === "Job" ? workload.spec?.template : workload.spec?.template;
const containers = template?.spec?.containers;
assertArray(containers, `${label}.containers`);
return containers;
}
function labelsFor(resource, label) {
const labels = resource.metadata?.labels;
assertObject(labels, `${label}.metadata.labels`);
return labels;
}
function assertMetadataServiceId(resource, expectedServiceId, label) {
if (!resource.metadata?.labels?.["hwlab.pikastech.local/instance-id"]) {
assert.equal(resource.metadata?.name, expectedServiceId, `${label}.metadata.name must match service id`);
} else {
assert.ok(resource.metadata?.name.startsWith(`${expectedServiceId}-`), `${label}.metadata.name must include service id prefix`);
}
assertDevNamespace(resource.metadata?.namespace, `${label}.metadata.namespace`);
const labels = labelsFor(resource, label);
assert.equal(labels[serviceIdLabel], expectedServiceId, `${label} service-id label`);
assert.equal(labels["app.kubernetes.io/name"], expectedServiceId, `${label} app name label`);
}
function assertTemplateServiceId(workload, expectedServiceId, label) {
const templateLabels = workload.spec?.template?.metadata?.labels;
assertObject(templateLabels, `${label}.spec.template.metadata.labels`);
assert.equal(templateLabels[serviceIdLabel], expectedServiceId, `${label} template service-id label`);
assert.equal(templateLabels["app.kubernetes.io/name"], expectedServiceId, `${label} template app name label`);
}
function assertHwlabImage(image, serviceId, label) {
assert.equal(
typeof image,
"string",
`${label}.image must be a string`
);
assert.match(
image,
new RegExp(`^(ghcr\\.io/pikastech|127\\.0\\.0\\.1:5000/hwlab|localhost:5000/hwlab)/${serviceId}:[a-f0-9]{7,40}$`),
`${label}.image must be the HWLAB image for ${serviceId}`
);
}
function assertHealthProbe(container, serviceId, label) {
for (const probeName of ["readinessProbe", "livenessProbe"]) {
assert.equal(
container[probeName]?.httpGet?.path,
"/health/live",
`${label}.${probeName} must use /health/live`
);
}
}
function assertNoExternalNameSubstitute(service, label) {
assert.notEqual(service.spec?.type, "ExternalName", `${label} must not be an ExternalName substitute`);
assert.equal(service.spec?.type, "ClusterIP", `${label} must be a ClusterIP skeleton service`);
}
function assertKustomizationDevOnly(kustomization, label) {
assert.equal(kustomization.kind, "Kustomization", `${label}.kind`);
assertDevNamespace(kustomization.namespace, `${label}.namespace`);
assert.equal(kustomization.commonLabels?.["hwlab.pikastech.local/profile"], "dev", `${label} profile label`);
assert.ok(kustomization.resources?.includes("../base"), `${label} must include ../base`);
}
function assertRuntimePolicyText(value, label) {
assert.equal(typeof value, "string", `${label} must be a string`);
assert.ok(value.includes("HWLAB runtime"), `${label} must name HWLAB runtime`);
assert.ok(value.includes("UniDesk backend"), `${label} must name UniDesk backend`);
assert.ok(value.includes("provider-gateway"), `${label} must name provider-gateway`);
assert.ok(value.includes("microservice proxy"), `${label} must name microservice proxy`);
}
function assertIncludes(value, expected, label) {
assert.equal(typeof value, "string", `${label} must be a string`);
assert.ok(value.includes(expected), `${label} must include ${JSON.stringify(expected)}`);
}
function assertDurableRuntimeRunbook(value) {
const label = "docs/reference/dev-runtime-boundary.md durable runtime runbook";
for (const expected of [
"DB live readiness and durable runtime readiness are separate gates",
"runtime_durable_adapter_query_blocked",
"DEV DB Provisioning Automation",
"Source migration contract",
"Live provisioning apply",
"Static runtime boundary contract",
"Public health observation",
"Live DB read verification",
"Live migration/repair apply",
"Durable runtime postflight",
"Do not run `kubectl get secret`",
"secretValuesPrinted=false",
"Before any authorized live migration or repair",
"After an authorized migration/repair",
"Cleared:",
"Moved:",
"Still blocked:",
"No full M3, M4, or M5 acceptance is allowed while runtime durability is blocked",
"bun cmd/hwlab-cloud-api/provision.ts --check",
"bun cmd/hwlab-cloud-api/provision.ts --dry-run --allow-live-db-read --confirm-dev --report /tmp/hwlab-dev-gate/dev-runtime-provisioning-report.json",
"bun cmd/hwlab-cloud-api/provision.ts --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/dev-runtime-provisioning-report.json",
"bun cmd/hwlab-cloud-api/migrate.ts --check",
"bun cmd/hwlab-cloud-api/migrate.ts --dry-run --report /tmp/hwlab-dev-gate/dev-runtime-migration-report.json",
"bun cmd/hwlab-cloud-api/migrate.ts --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/dev-runtime-migration-report.json",
"DEV CD runtime migration Job uses",
"node scripts/dev-runtime-provisioning.mjs --check",
"node scripts/dev-runtime-provisioning.mjs --dry-run --allow-live-db-read --confirm-dev --report /tmp/hwlab-dev-gate/dev-runtime-provisioning-report.json",
"node scripts/dev-runtime-provisioning.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/dev-runtime-provisioning-report.json",
"node scripts/dev-runtime-migration.mjs --check",
"node scripts/dev-runtime-migration.mjs --dry-run --report /tmp/hwlab-dev-gate/dev-runtime-migration-report.json",
"node scripts/dev-runtime-migration.mjs --dry-run --allow-live-db-read --confirm-dev --report /tmp/hwlab-dev-gate/dev-runtime-migration-report.json",
"node scripts/dev-runtime-migration.mjs --apply --confirm-dev --confirmed-non-production --report /tmp/hwlab-dev-gate/dev-runtime-migration-report.json",
"node scripts/dev-runtime-postflight.mjs --check",
"node scripts/dev-runtime-postflight.mjs --live --confirm-dev --confirmed-non-production --target api --report /tmp/hwlab-dev-gate/dev-runtime-postflight-report.json"
]) {
assertIncludes(value, expected, label);
}
}
function assertCloudApiImageRuntimeEntrypoints({ packageJson, artifactPublisher, gitopsRenderSource }) {
const label = "cloud-api image runtime maintenance entrypoints";
for (const expected of [
"COPY internal ./internal",
"COPY cmd ./cmd",
"COPY scripts ./scripts",
"COPY package.json ./package.json"
]) {
assertIncludes(artifactPublisher, expected, `${label} artifact publisher`);
}
for (const expected of [
"cmd/hwlab-cloud-api/provision.ts",
"cmd/hwlab-cloud-api/migrate.ts"
]) {
assertIncludes(packageJson.scripts?.check, `bun build ${expected} --target=bun --packages=external`, `${label} package check`);
assertIncludes(packageJson.scripts?.check, `bun ${expected} --check`, `${label} package runtime check`);
}
for (const expected of ["scripts/artifact-publish.mjs", "runtime-dev", "runtime-prod"]) {
assertIncludes(gitopsRenderSource, expected, `${label} node GitOps render source`);
}
for (const expected of ["name: \"prepare-source\"", "name: \"repo-reports-guard\"", "name: \"node-contract-check\"", "name: \"codex-api-forwarder-check\""]) {
assertIncludes(gitopsRenderSource, expected, `${label} Tekton primitive CI task`);
}
}
function assertGuardFixture(guard) {
assert.equal(guard.guard, "hwlab-runtime-boundary");
assert.equal(guard.schemaVersion, "v1");
assertDevOnlyEnvironment(guard.environment, "guard.environment");
assert.equal(guard.endpoint, DEV_ENDPOINT, "guard.endpoint");
assertDevNamespace(guard.namespace, "guard.namespace");
assert.equal(guard.policy?.runtimeSubstitution, "forbidden", "guard runtime substitution policy");
assert.equal(guard.policy?.prodDeployment, "forbidden", "guard prod policy");
assertForbiddenList(guard.policy?.forbiddenRuntimeSubstitutes, "guard forbidden substitutes");
assertSameMembers(guard.requiredManifest?.requiredServiceIds, SERVICE_IDS, "guard manifest service ids");
assertSameMembers(guard.requiredK3sSkeleton?.requiredWorkloadServiceIds, SERVICE_IDS, "guard workload service ids");
assert.ok(
guard.validation?.commands?.includes("node scripts/validate-runtime-boundary.mjs"),
"guard must record executable validation command"
);
}
function assertDeployManifest(manifest, guard) {
assert.equal(manifest.manifestVersion, "v1");
assertDevOnlyEnvironment(manifest.environment, "deploy manifest environment");
assertDevNamespace(manifest.namespace, "deploy manifest namespace");
assert.equal(manifest.endpoint, DEV_ENDPOINT, "deploy manifest endpoint");
assert.ok(manifest.profiles?.dev?.enabled, "deploy manifest dev profile must be enabled");
assertDevNamespace(manifest.profiles.dev.namespace, "deploy manifest dev profile namespace");
assert.equal(manifest.profiles.dev.endpoint, DEV_ENDPOINT, "deploy manifest dev profile endpoint");
assert.equal(manifest.profiles?.prod?.enabled, false, "deploy manifest prod profile must stay disabled");
assertArray(manifest.services, "deploy manifest services");
const serviceIds = manifest.services.map((service) => service.serviceId);
assertUnique(serviceIds, "deploy manifest service ids");
assertSameMembers(serviceIds, guard.requiredManifest.requiredServiceIds, "deploy manifest service ids");
assertSameMembers(serviceIds, SERVICE_IDS, "deploy manifest service ids");
for (const service of manifest.services) {
assertServiceId(service.serviceId, `deploy service ${service.serviceId}`);
assertHwlabImage(service.image, service.serviceId, `deploy service ${service.serviceId}`);
assertDevNamespace(service.namespace, `${service.serviceId}.namespace`);
assert.equal(service.profile, "dev", `${service.serviceId}.profile must be dev`);
assert.match(service.healthPath, /^\//, `${service.serviceId}.healthPath must be absolute`);
assertNoForbiddenSubstitutesDeep(service.env ?? {}, `${service.serviceId}.env`);
}
const cloudApi = manifest.services.find((service) => service.serviceId === "hwlab-cloud-api");
assertForbiddenList(
splitCsv(cloudApi?.env?.HWLAB_RUNTIME_SUBSTITUTE_FORBIDDEN),
"hwlab-cloud-api HWLAB_RUNTIME_SUBSTITUTE_FORBIDDEN"
);
}
function assertMasterHealthContract(contract, guard) {
assertDevOnlyEnvironment(contract.environment, "master health contract environment");
assert.equal(contract.endpoint, DEV_ENDPOINT, "master health contract endpoint");
assert.equal(contract.prodAcceptance, false, "master health contract prodAcceptance");
assert.equal(contract.reverseLink?.mode, guard.requiredHealthContract.reverseLink.mode, "reverse link mode");
assert.equal(
contract.reverseLink?.direction,
guard.requiredHealthContract.reverseLink.direction,
"reverse link direction"
);
assert.equal(contract.reverseLink?.client, "hwlab-tunnel-client", "reverse link client");
assert.equal(contract.reverseLink?.publicPort, 16667, "reverse link public port");
assertForbiddenList(contract.forbiddenRuntimeSubstitutes, "master forbidden substitutes");
assertArray(contract.contracts, "master health contracts");
const serviceIds = contract.contracts.map((item) => item.serviceId);
assertUnique(serviceIds, "master health contract service ids");
assertSameMembers(
serviceIds,
guard.requiredHealthContract.requiredContractServiceIds,
"master health contract service ids"
);
for (const item of contract.contracts) {
assertServiceId(item.serviceId, `master health contract ${item.serviceId}`);
}
}
function assertWorkloads(workloads, guard) {
assert.equal(workloads.kind, "List", "workloads kind");
assertArray(workloads.items, "workloads items");
const serviceIds = workloads.items.map((item) => item.metadata?.labels?.[serviceIdLabel]);
assertUnique(serviceIds, "workload service ids");
assertSameMembers(serviceIds, guard.requiredK3sSkeleton.requiredWorkloadServiceIds, "workload service ids");
for (const workload of workloads.items) {
const serviceId = workload.metadata?.labels?.[serviceIdLabel];
assertServiceId(serviceId, `workload ${workload.metadata?.name}`);
const expectedName = workload.kind === "Job" ? `${serviceId}-template` : serviceId;
assert.equal(workload.metadata?.name, expectedName, `${serviceId} workload name`);
assertDevNamespace(workload.metadata?.namespace, `${serviceId} workload namespace`);
assert.equal(labelsFor(workload, serviceId)["app.kubernetes.io/name"], serviceId, `${serviceId} workload app label`);
assertTemplateServiceId(workload, serviceId, `workload ${serviceId}`);
const containers = containerList(workload, `workload ${serviceId}`);
assert.ok(containers.length >= 1, `${serviceId} workload must define at least one container`);
assertUnique(
containers.map((container) => container.name),
`${serviceId} container names`
);
const primaryContainer = containers.find((container) => container.name === serviceId);
assert.ok(primaryContainer, `${serviceId} workload must include primary container ${serviceId}`);
for (const container of containers) {
assertHwlabImage(container.image, serviceId, `${serviceId} container ${container.name}`);
assertNoForbiddenSubstitutesDeep(container.env ?? [], `${serviceId} container ${container.name} env`);
}
assertHealthProbe(primaryContainer, serviceId, `${serviceId} container ${serviceId}`);
}
}
function assertServices(services, guard) {
assert.equal(services.kind, "List", "services kind");
assertArray(services.items, "services items");
const runtimeServices = services.items;
const serviceNames = runtimeServices.map((item) => item.metadata?.name);
const serviceIds = [...new Set(runtimeServices.map((item) => item.metadata?.labels?.[serviceIdLabel]))];
assertUnique(serviceNames, "k8s service names");
assertSameMembers(serviceIds, guard.requiredK3sSkeleton.requiredServiceServiceIds, "k8s service ids");
assert.equal(
services.items.some((item) => item.metadata?.name === DEV_DB_ENV_CONTRACT.dns.serviceName),
false,
"cloud-api-db is an optional future alias and is not required in the current runtime boundary"
);
assert.equal(DEV_DB_ENV_CONTRACT.endpointAuthority.source, "secret-url-host", "DB readiness authority must be the Secret URL host");
assert.equal(DEV_DB_ENV_CONTRACT.dns.requiredForReadiness, false, "DB DNS alias must not be a readiness hard requirement");
assert.equal(DEV_DB_ENV_CONTRACT.dns.usedForProbe, false, "DB DNS alias must not be the runtime probe target");
for (const service of runtimeServices) {
const serviceId = service.metadata?.labels?.[serviceIdLabel];
assertServiceId(serviceId, `k8s service ${service.metadata?.name}`);
assertMetadataServiceId(service, serviceId, `k8s service ${serviceId}`);
assertNoExternalNameSubstitute(service, `k8s service ${serviceId}`);
if (service.metadata?.labels?.["hwlab.pikastech.local/instance-id"]) {
assert.match(
service.spec?.selector?.["statefulset.kubernetes.io/pod-name"],
new RegExp(`^${escapeRegExp(serviceId)}-\\d+$`),
`${serviceId} indexed selector`
);
} else {
assert.equal(service.spec?.selector?.["app.kubernetes.io/name"], serviceId, `${serviceId} selector`);
}
}
}
function escapeRegExp(value) {
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
function assertDevHealthConfig(configMap) {
assert.equal(configMap.kind, "ConfigMap", "dev health contract kind");
assertDevNamespace(configMap.metadata?.namespace, "dev health contract namespace");
assert.equal(configMap.data?.endpoint, DEV_ENDPOINT, "dev health contract endpoint");
assertRuntimePolicyText(configMap.data?.["runtime-substitute-policy"], "dev runtime substitute policy");
}
function assertProdDisabled(prodKustomization, prodDisabled) {
assert.equal(prodKustomization.kind, "Kustomization", "prod kustomization kind");
assert.deepEqual(prodKustomization.resources, ["prod-disabled.yaml"], "prod kustomization resources");
assert.equal(prodDisabled.kind, "ConfigMap", "prod disabled kind");
assert.equal(prodDisabled.metadata?.labels?.["hwlab.pikastech.local/profile"], "prod", "prod disabled profile");
assert.equal(
prodDisabled.metadata?.labels?.["hwlab.pikastech.local/acceptance"],
"disabled",
"prod disabled acceptance"
);
assert.equal(prodDisabled.data?.status, "disabled", "prod disabled status");
}
const guard = await readJSON("protocol/examples/runtime-boundary/guard.dev.json");
assertGuardFixture(guard);
assertDeployManifest(await readJSON(guard.requiredManifest.path), guard);
assertMasterHealthContract(await readJSON(guard.requiredHealthContract.path), guard);
assertWorkloads(await readJSON(guard.requiredK3sSkeleton.workloadsPath), guard);
assertServices(await readJSON(guard.requiredK3sSkeleton.servicesPath), guard);
assertKustomizationDevOnly(await readJSON(guard.requiredK3sSkeleton.devKustomizationPath), "dev kustomization");
assertDevHealthConfig(await readJSON(guard.requiredK3sSkeleton.devHealthContractPath));
assertProdDisabled(
await readJSON(guard.requiredK3sSkeleton.prodKustomizationPath),
await readJSON(guard.requiredK3sSkeleton.prodDisabledPath)
);
assertDurableRuntimeRunbook(await readText("docs/reference/dev-runtime-boundary.md"));
assertCloudApiImageRuntimeEntrypoints({
packageJson: await readJSON("package.json"),
artifactPublisher: await readText("scripts/artifact-publish.mjs"),
gitopsRenderSource: await readText("scripts/gitops-render.mjs")
});
console.log(
`validated HWLAB runtime boundary guard for ${SERVICE_IDS.length} services; forbidden substitutes: ${forbiddenRuntimeSubstitutes.join(", ")}`
);