/* * SPEC: PJ2026-010102 HWPOD 工具;PJ2026-010103 HWPOD 服务; * PJ2026-01010305 71FREQ 预装 draft-2026-06-26-71freq-v03-hwpod-preinstall。 * 责任: 为 L0-L3 提供唯一 host PostgreSQL runtime spec registry。 */ import { createHash } from "node:crypto"; import { readdirSync, readFileSync } from "node:fs"; import path from "node:path"; import pg from "pg"; import { parse as parseYaml } from "yaml"; import { buildPostgresPoolConfig, postgresSslModeFromConnectionString } from "../db/runtime-store-core.ts"; import { normalizeHwpodSpec } from "./spec-document.ts"; const { Pool } = pg; const CONTRACT_VERSION = "hwpod-spec-registry-v3"; type BuiltInInput = { configRef: string; sha256?: string; document: unknown }; export type HwpodRuntimeSpecRecord = { authority: "runtime"; hwpodId: string; createdAt: string; updatedAt: string; document: unknown }; export interface HwpodRuntimeSpecStore { list(): Promise; get(hwpodId: string): Promise; create(record: HwpodRuntimeSpecRecord): Promise; update(hwpodId: string, document: unknown, updatedAt: string): Promise; delete(hwpodId: string): Promise; close?(): Promise; } export function hwpodSpecRegistryFromEnv(env: Record = process.env) { const databaseUrl = requiredEnv(env, "HWPOD_SPEC_DATABASE_URL"); const schema = postgresIdentifier(requiredEnv(env, "HWPOD_SPEC_DATABASE_SCHEMA"), "HWPOD_SPEC_DATABASE_SCHEMA"); const table = postgresIdentifier(requiredEnv(env, "HWPOD_SPEC_DATABASE_TABLE"), "HWPOD_SPEC_DATABASE_TABLE"); const connectionTimeoutMs = positiveIntegerEnv(env, "HWPOD_SPEC_DATABASE_CONNECTION_TIMEOUT_MS"); const builtIns = parseBuiltIns(env.HWPOD_BUILTIN_SPEC_DOCUMENTS_JSON, env.HWPOD_BUILTIN_SPEC_CONFIG_DIRECTORY); return createHwpodSpecRegistry({ store: createPostgresHwpodRuntimeSpecStore({ databaseUrl, schema, table, connectionTimeoutMs }), builtIns }); } export function createHwpodSpecRegistry(options: { store: HwpodRuntimeSpecStore; builtIns?: BuiltInInput[]; now?: () => string }) { const store = options.store; const now = options.now ?? (() => new Date().toISOString()); const builtIns = normalizeBuiltIns(options.builtIns ?? []); async function list() { const runtime = new Map((await store.list()).map((record) => [record.hwpodId, record])); assertNoAuthorityConflicts(builtIns, runtime); const specs = [...builtIns.values(), ...runtime.values()].map(summary).sort((left, right) => left.hwpodId.localeCompare(right.hwpodId)); return { ok: true, status: specs.length === 0 ? "empty" : "completed", contractVersion: CONTRACT_VERSION, count: specs.length, specs, valuesPrinted: false }; } async function get(hwpodId: string) { const id = validId(hwpodId); const builtIn = builtIns.get(id); if (builtIn) return entity(builtIn); const runtime = await store.get(id); if (!runtime) throw codedError("hwpod_spec_not_found", `HWPOD spec was not found: ${id}`, { hwpodId: id }); return entity(runtime); } async function create(documentInput: unknown) { const normalized = normalizedDocument(documentInput, "runtime:create"); const id = specId(normalized); assertNotFrozen(id, builtIns); const observedAt = now(); const record: HwpodRuntimeSpecRecord = { authority: "runtime", hwpodId: id, createdAt: observedAt, updatedAt: observedAt, document: normalized }; if (!await store.create(record)) throw codedError("hwpod_spec_exists", `HWPOD runtime spec already exists: ${id}`, { hwpodId: id }); return mutation("created", record); } async function update(hwpodId: string, documentInput: unknown) { const id = validId(hwpodId); assertNotFrozen(id, builtIns); const normalized = normalizedDocument(documentInput, `runtime:${id}`); const documentId = specId(normalized); if (documentId !== id) throw codedError("hwpod_spec_id_mismatch", `HWPOD spec document id ${documentId} does not match route id ${id}`, { hwpodId: id, documentHwpodId: documentId }); const record = await store.update(id, normalized, now()); if (!record) throw codedError("hwpod_spec_not_found", `HWPOD runtime spec was not found: ${id}`, { hwpodId: id }); return mutation("updated", record); } async function remove(hwpodId: string) { const id = validId(hwpodId); assertNotFrozen(id, builtIns); const existing = await store.delete(id); if (!existing) throw codedError("hwpod_spec_not_found", `HWPOD runtime spec was not found: ${id}`, { hwpodId: id }); return { ok: true, status: "deleted", contractVersion: CONTRACT_VERSION, hwpodId: id, authority: "runtime", mutable: true, frozen: false, mutation: true, previousFingerprint: documentFingerprint(existing.document), valuesPrinted: false }; } return { list, get, create, update, delete: remove, close: async () => store.close?.() }; } export function createPostgresHwpodRuntimeSpecStore(options: { databaseUrl: string; schema: string; table: string; connectionTimeoutMs: number }): HwpodRuntimeSpecStore { const schema = postgresIdentifier(options.schema, "schema"); const table = postgresIdentifier(options.table, "table"); const qualifiedTable = `${quoteIdentifier(schema)}.${quoteIdentifier(table)}`; const pool = new Pool(buildPostgresPoolConfig({ dbUrl: options.databaseUrl, sslMode: postgresSslModeFromConnectionString(options.databaseUrl) ?? "disable", timeoutMs: options.connectionTimeoutMs, })); let ready: Promise | undefined; function ensureSchema() { ready ??= migrate(); return ready; } async function migrate() { const client = await pool.connect(); try { await client.query("BEGIN"); await client.query(`CREATE SCHEMA IF NOT EXISTS ${quoteIdentifier(schema)}`); await client.query(`CREATE TABLE IF NOT EXISTS ${qualifiedTable} ( hwpod_id TEXT PRIMARY KEY, authority TEXT NOT NULL DEFAULT 'runtime' CHECK (authority = 'runtime'), document JSONB NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT now(), updated_at TIMESTAMPTZ NOT NULL DEFAULT now() )`); await client.query(`CREATE INDEX IF NOT EXISTS ${quoteIdentifier(`${table}_updated_at_idx`)} ON ${qualifiedTable} (updated_at DESC, hwpod_id)`); await client.query("COMMIT"); } catch (error) { await client.query("ROLLBACK").catch(() => {}); ready = undefined; throw error; } finally { client.release(); } } return { async list() { await ensureSchema(); const result = await pool.query(`SELECT hwpod_id, authority, document, created_at, updated_at FROM ${qualifiedTable} ORDER BY hwpod_id`); return result.rows.map(runtimeRow); }, async get(hwpodId) { await ensureSchema(); const result = await pool.query(`SELECT hwpod_id, authority, document, created_at, updated_at FROM ${qualifiedTable} WHERE hwpod_id=$1`, [hwpodId]); return result.rows[0] ? runtimeRow(result.rows[0]) : null; }, async create(record) { await ensureSchema(); const result = await pool.query(`INSERT INTO ${qualifiedTable} (hwpod_id, authority, document, created_at, updated_at) VALUES ($1,'runtime',$2::jsonb,$3,$4) ON CONFLICT (hwpod_id) DO NOTHING RETURNING hwpod_id`, [record.hwpodId, JSON.stringify(record.document), record.createdAt, record.updatedAt]); return result.rowCount === 1; }, async update(hwpodId, document, updatedAt) { await ensureSchema(); const result = await pool.query(`UPDATE ${qualifiedTable} SET document=$2::jsonb, updated_at=$3 WHERE hwpod_id=$1 AND authority='runtime' RETURNING hwpod_id, authority, document, created_at, updated_at`, [hwpodId, JSON.stringify(document), updatedAt]); return result.rows[0] ? runtimeRow(result.rows[0]) : null; }, async delete(hwpodId) { await ensureSchema(); const result = await pool.query(`DELETE FROM ${qualifiedTable} WHERE hwpod_id=$1 AND authority='runtime' RETURNING hwpod_id, authority, document, created_at, updated_at`, [hwpodId]); return result.rows[0] ? runtimeRow(result.rows[0]) : null; }, close: async () => pool.end(), }; } function runtimeRow(row: any): HwpodRuntimeSpecRecord { if (row?.authority !== "runtime") throw codedError("invalid_hwpod_runtime_spec", `runtime HWPOD spec authority is invalid: ${String(row?.hwpod_id ?? "")}`); const document = normalizedDocument(row.document, `postgres:${String(row.hwpod_id ?? "")}`); const hwpodId = specId(document); if (hwpodId !== row.hwpod_id) throw codedError("invalid_hwpod_runtime_spec", `runtime HWPOD spec identity is invalid: ${String(row.hwpod_id ?? "")}`); return { authority: "runtime", hwpodId, createdAt: isoTimestamp(row.created_at), updatedAt: isoTimestamp(row.updated_at), document }; } function parseBuiltIns(raw: string | undefined, configDirectory: string | undefined): BuiltInInput[] { if (String(raw ?? "").trim() && String(configDirectory ?? "").trim()) throw codedError("invalid_hwpod_builtin_specs", "HWPOD built-in specs must use either JSON or a config directory, not both"); if (String(configDirectory ?? "").trim()) return parseBuiltInConfigDirectory(String(configDirectory)); if (!String(raw ?? "").trim()) return []; let value: unknown; try { value = JSON.parse(String(raw)); } catch (error: any) { throw codedError("invalid_hwpod_builtin_specs", `HWPOD_BUILTIN_SPEC_DOCUMENTS_JSON is invalid JSON: ${error?.message ?? String(error)}`); } if (!Array.isArray(value)) throw codedError("invalid_hwpod_builtin_specs", "HWPOD_BUILTIN_SPEC_DOCUMENTS_JSON must be an array"); return value.map((item: any, index) => { if (!item || typeof item !== "object" || Array.isArray(item)) throw codedError("invalid_hwpod_builtin_specs", `built-in spec item ${index} must be an object`); const configRef = String(item.configRef ?? "").trim(); if (!configRef) throw codedError("invalid_hwpod_builtin_specs", `built-in spec item ${index} is missing configRef`); return { configRef, sha256: String(item.sha256 ?? "").trim() || undefined, document: item.document }; }); } function parseBuiltInConfigDirectory(directoryInput: string): BuiltInInput[] { const directory = path.resolve(directoryInput.trim()); return readdirSync(directory, { withFileTypes: true }) .filter((entry) => entry.isFile() && /\.ya?ml$/iu.test(entry.name)) .map((entry) => entry.name) .sort() .map((fileName) => { const source = readFileSync(path.join(directory, fileName), "utf8"); const document = parseYaml(source); return { configRef: `configMap:hwlab-v03-hwpod-preinstalled-specs/${fileName}`, sha256: createHash("sha256").update(stableJson(document)).digest("hex"), document, }; }); } function normalizeBuiltIns(inputs: BuiltInInput[]) { const records = new Map(); for (const input of inputs) { const document = normalizedDocument(input.document, input.configRef); const hwpodId = specId(document); if (records.has(hwpodId)) throw codedError("hwpod_spec_authority_conflict", `duplicate YAML-first built-in HWPOD spec: ${hwpodId}`, { hwpodId }); records.set(hwpodId, { authority: "yaml-first-builtin", hwpodId, configRef: input.configRef, sourceFingerprint: input.sha256 ?? null, document }); } return records; } function requiredEnv(env: Record, key: string) { const value = String(env[key] ?? "").trim(); if (!value) throw codedError("hwpod_spec_database_config_required", `${key} is required`); return value; } function positiveIntegerEnv(env: Record, key: string) { const value = Number(requiredEnv(env, key)); if (!Number.isSafeInteger(value) || value <= 0) throw codedError("invalid_hwpod_spec_database_config", `${key} must be a positive integer`); return value; } function postgresIdentifier(value: string, name: string) { if (!/^[a-z_][a-z0-9_]*$/u.test(value)) throw codedError("invalid_hwpod_spec_database_identifier", `${name} must be a lowercase PostgreSQL identifier`); return value; } function quoteIdentifier(value: string) { return `"${postgresIdentifier(value, "identifier")}"`; } function isoTimestamp(value: unknown) { const date = value instanceof Date ? value : new Date(String(value ?? "")); if (!Number.isFinite(date.getTime())) throw codedError("invalid_hwpod_runtime_spec", "runtime HWPOD spec timestamp is invalid"); return date.toISOString(); } function normalizedDocument(value: unknown, source: string) { return normalizeHwpodSpec(value, source); } function specId(document: any) { return validId(String(document?.metadata?.name ?? document?.metadata?.uid ?? "")); } function validId(value: string) { const id = String(value ?? "").trim(); if (!/^[A-Za-z0-9_.:-]{1,120}$/u.test(id)) throw codedError("invalid_hwpod_spec_id", `invalid HWPOD spec id: ${id || ""}`); return id; } function assertNotFrozen(id: string, builtIns: Map) { const item = builtIns.get(id); if (item) throw codedError("hwpod_spec_frozen", `YAML-first built-in HWPOD spec is frozen: ${id}`, { hwpodId: id, authority: item.authority, configRef: item.configRef, mutable: false, frozen: true, documentFingerprint: documentFingerprint(item.document), mutation: false }); } function assertNoAuthorityConflicts(builtIns: Map, runtime: Map) { for (const id of runtime.keys()) if (builtIns.has(id)) throw codedError("hwpod_spec_authority_conflict", `runtime HWPOD spec conflicts with YAML-first built-in spec: ${id}`, { hwpodId: id }); } function summary(record: any) { const document = record.document; return { hwpodId: record.hwpodId, authority: record.authority, mutable: record.authority === "runtime", frozen: record.authority !== "runtime", documentFingerprint: documentFingerprint(document), ...(record.configRef ? { configRef: record.configRef, sourceFingerprint: record.sourceFingerprint } : { createdAt: record.createdAt, updatedAt: record.updatedAt }), nodeId: document.spec.nodeBinding.nodeId, workspacePath: document.spec.workspace.path }; } function entity(record: any) { return { ok: true, status: "completed", contractVersion: CONTRACT_VERSION, ...summary(record), document: record.document, valuesPrinted: false }; } function mutation(status: "created" | "updated", record: HwpodRuntimeSpecRecord) { return { ...entity(record), status, mutation: true }; } function documentFingerprint(document: unknown) { return `sha256:${createHash("sha256").update(stableJson(document)).digest("hex")}`; } function stableJson(value: unknown): string { if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`; if (value && typeof value === "object") return `{${Object.keys(value as any).sort().map((key) => `${JSON.stringify(key)}:${stableJson((value as any)[key])}`).join(",")}}`; return JSON.stringify(value) ?? "null"; } function codedError(code: string, message: string, details?: unknown) { return Object.assign(new Error(message), { code, details }); }