import { createHash } from "node:crypto"; import { normalize } from "node:path"; export interface CanonicalEffectiveConfig { schemaVersion: 3; dwh: CanonicalDwhConfig; vector: CanonicalVectorConfig; embedding: CanonicalEmbeddingConfig; roots: CanonicalRootsConfig; } export interface CanonicalDwhConfig { engine: "postgres"; database: string; schema: string; transport: "postgres_direct" | "rest_api" | "ssh_tunnel"; host?: string; port?: number; baseUrl?: string; user?: string; } export interface CanonicalVectorConfig { collections: { reference: string; memory: string; }; dimensions: number; distance: string; } export interface CanonicalEmbeddingConfig { id: string; model: string; dimensions: number; } export interface CanonicalRootsConfig { artifacts: string; indexes: string; } function asRecord(value: unknown): Record | undefined { if (typeof value === "object" && value !== null && !Array.isArray(value)) { return value as Record; } return undefined; } function requireString(value: Record, key: string): string { const candidate = value[key]; if (typeof candidate !== "string" || candidate.length === 0) { throw new TypeError(`effective config is missing ${key}`); } return candidate; } function optionalString(value: Record, key: string): string | undefined { const candidate = value[key]; if (candidate === undefined || candidate === null) return undefined; if (typeof candidate !== "string") return undefined; return candidate; } function optionalNumber(value: Record, key: string): number | undefined { const candidate = value[key]; if (candidate === undefined || candidate === null) return undefined; if (typeof candidate !== "number" || Number.isNaN(candidate)) return undefined; return candidate; } function requireNumber(value: Record, key: string): number { const candidate = value[key]; if (typeof candidate !== "number" || Number.isNaN(candidate)) { throw new TypeError(`effective config is missing numeric ${key}`); } return candidate; } function normalizeRoot(value: string): string { return normalize(value); } function dwhTransport(rendered: Record): CanonicalDwhConfig["transport"] { const dwh = asRecord(rendered.dwh); const type = dwh ? requireString(dwh, "type") : undefined; if (type === "postgres_direct") return "postgres_direct"; if (type === "thoth_rest") return "rest_api"; if (type === "ssh_tunnel") return "ssh_tunnel"; throw new TypeError(`effective config has unsupported dwh transport ${type}`); } function buildDwhConfig(rendered: Record): CanonicalDwhConfig { const transport = dwhTransport(rendered); const databaseRecord = asRecord(rendered.database) ?? asRecord(asRecord(asRecord(rendered.dwh)?.connection)?.database); if (!databaseRecord) { throw new TypeError("effective config is missing database identity"); } const engine = "postgres"; const database = requireString(databaseRecord, "database"); const schema = requireString(databaseRecord, "schema"); const dwh: Record = { engine, database, schema, transport }; if (transport === "postgres_direct") { const host = optionalString(databaseRecord, "host"); const port = optionalNumber(databaseRecord, "port"); const user = optionalString(databaseRecord, "user"); if (host !== undefined) dwh.host = host; if (port !== undefined) dwh.port = port; if (user !== undefined) dwh.user = user; } else if (transport === "rest_api") { const rest = asRecord(rendered.rest) ?? asRecord(asRecord(asRecord(rendered.dwh)?.endpoint)); const baseUrl = rest ? optionalString(rest, "base_url") : undefined; if (baseUrl !== undefined) dwh.baseUrl = baseUrl; } return dwh as unknown as CanonicalDwhConfig; } function buildVectorConfig(rendered: Record): CanonicalVectorConfig { const resources = asRecord(rendered.resources); const vector = resources ? asRecord(resources.vector) : undefined; if (!vector) { throw new TypeError("effective config is missing vector resources"); } const collections = asRecord(vector.collections); if (!collections) { throw new TypeError("effective config is missing vector collections"); } const semanticIndex = asRecord(rendered.semantic_index); const vectorStore = semanticIndex ? asRecord(semanticIndex.vector_store) : undefined; const dimensions = vectorStore ? requireNumber(vectorStore, "dimensions") : requireNumber(vector, "dimensions"); const distance = vectorStore ? requireString(vectorStore, "distance") : (optionalString(vector, "distance") ?? "cosine"); return { collections: { reference: requireString(collections, "reference"), memory: requireString(collections, "memory"), }, dimensions, distance, }; } function buildEmbeddingConfig(rendered: Record): CanonicalEmbeddingConfig { const resources = asRecord(rendered.resources); const embeddings = resources ? asRecord(resources.embeddings) : undefined; if (!embeddings) { throw new TypeError("effective config is missing embedding resources"); } const model = requireString(embeddings, "model"); return { id: optionalString(embeddings, "id") ?? `ollama/${model}`, model, dimensions: requireNumber(embeddings, "dimensions"), }; } function buildRootsConfig(rendered: Record): CanonicalRootsConfig { const roots = asRecord(rendered.roots) ?? asRecord(rendered.paths); if (!roots) { throw new TypeError("effective config is missing roots"); } return { artifacts: normalizeRoot(requireString(roots, "artifacts")), indexes: normalizeRoot(requireString(roots, "indexes")), }; } /** * Build the versioned, non-secret effective DWH/preprocessing configuration from a * rendered runtime configuration object. The result contains only the fields that * affect DWH generation identity; credentials, runtime identity, session storage, * evidence, and service endpoints are excluded. */ export function buildCanonicalEffectiveConfig(renderedConfig: unknown): CanonicalEffectiveConfig { const rendered = asRecord(renderedConfig); if (!rendered) { throw new TypeError("effective config requires a rendered configuration object"); } return { schemaVersion: 3, dwh: buildDwhConfig(rendered), vector: buildVectorConfig(rendered), embedding: buildEmbeddingConfig(rendered), roots: buildRootsConfig(rendered), }; } function sha256(value: string | Buffer): string { return `sha256:${createHash("sha256").update(value).digest("hex")}`; } /** * Serialize the canonical effective config to a deterministic JSON string with the * fixed key order defined by the shared contract. No whitespace is included. */ export function canonicalEffectiveConfigJson(config: CanonicalEffectiveConfig): string { const ordered: Record = { schemaVersion: config.schemaVersion }; ordered.dwh = { ...config.dwh }; ordered.vector = { ...config.vector }; ordered.embedding = { ...config.embedding }; ordered.roots = { ...config.roots }; return JSON.stringify(ordered); } /** * Return the stable logical config-source identity for a workspace revision. * This is `workspace://@v1:`. */ export function effectiveConfigIdentity(workspaceId: string, renderedConfig: unknown): string { const canonical = buildCanonicalEffectiveConfig(renderedConfig); const digest = createHash("sha256").update(canonicalEffectiveConfigJson(canonical)).digest("hex"); return `workspace://${workspaceId}@v1:${digest}`; } /** * Return the config fingerprint: `sha256:` + the SHA-256 of the canonical effective * config JSON bytes. */ export function configFingerprint(renderedConfig: unknown): string { const canonical = buildCanonicalEffectiveConfig(renderedConfig); return sha256(canonicalEffectiveConfigJson(canonical)); } /** * Return the input fingerprint: `sha256:` + the SHA-256 of the logical config-source * identity string. */ export function inputFingerprint(workspaceId: string, renderedConfig: unknown): string { return sha256(effectiveConfigIdentity(workspaceId, renderedConfig)); } /** * Fingerprint every non-Catalog input consumed by complete preprocessing. The immutable Git * revision covers the workspace descriptor and its revision-pinned Evidence tree; the effective * configuration identity covers the Catalog-derived DWH binding and semantic runtime contract. */ export function preprocessingInputFingerprint( workspaceId: string, workspaceRevision: string, renderedConfig: unknown, ): string { return sha256(JSON.stringify({ workspaceId, workspaceRevision, effectiveConfigIdentity: effectiveConfigIdentity(workspaceId, renderedConfig), })); }