feat: render revision-bound evidence configuration
This commit is contained in:
@@ -1,7 +1,13 @@
|
||||
import { basename, join } from "node:path";
|
||||
import { stringify } from "yaml";
|
||||
import { buildInstallationContract } from "./contracts.js";
|
||||
import { validateWorkspaceDescriptor, type WorkspaceDescriptor, type WorkspaceV2 } from "./schema.js";
|
||||
import type { ResolvedBinding, RuntimeBindings } from "./bindings.js";
|
||||
import {
|
||||
validateWorkspaceDescriptor,
|
||||
type WorkspaceDescriptor,
|
||||
type WorkspaceV2,
|
||||
type WorkspaceV3,
|
||||
} from "./schema.js";
|
||||
import type { ResolvedBinding, ResolvedEvidenceBinding, RuntimeBindings } from "./bindings.js";
|
||||
export type { RuntimeBindings } from "./bindings.js";
|
||||
|
||||
export interface RuntimePaths {
|
||||
@@ -15,6 +21,11 @@ export interface RuntimeIdentity {
|
||||
workspaceRevision: string;
|
||||
}
|
||||
|
||||
/** Immutable, explicit inputs needed to bind descriptor-relative content to one revision. */
|
||||
export interface RuntimeRenderContext extends RuntimeIdentity {
|
||||
revisionContentRoot: string;
|
||||
}
|
||||
|
||||
export interface RuntimeInstallationOverlay {
|
||||
session_storage?: unknown;
|
||||
profile?: unknown;
|
||||
@@ -80,6 +91,103 @@ function legacyRestEndpoint(
|
||||
return endpoint;
|
||||
}
|
||||
|
||||
function exactSeconds(timeoutMs: number): number {
|
||||
return timeoutMs / 1_000;
|
||||
}
|
||||
|
||||
function requireRuntimeRenderContext(
|
||||
identity: RuntimeIdentity | RuntimeRenderContext | undefined,
|
||||
): RuntimeRenderContext {
|
||||
if (!identity || !("revisionContentRoot" in identity)) {
|
||||
throw new Error("runtime Evidence requires an immutable revision content root");
|
||||
}
|
||||
return identity;
|
||||
}
|
||||
|
||||
function evidenceBindingValue(binding: ResolvedEvidenceBinding, name: string): string | undefined {
|
||||
return binding.values[name];
|
||||
}
|
||||
|
||||
function requireEvidenceBinding(binding: ResolvedEvidenceBinding, name: string): string {
|
||||
const value = evidenceBindingValue(binding, name);
|
||||
if (value === undefined) throw new Error(`runtime binding is missing ${name}`);
|
||||
return value;
|
||||
}
|
||||
|
||||
function renderEvidence(
|
||||
workspace: WorkspaceV3,
|
||||
binding: ResolvedEvidenceBinding,
|
||||
context: RuntimeRenderContext,
|
||||
bindingName: (suffix: string) => string,
|
||||
): { evidence: Record<string, unknown>; vector: Record<string, unknown> } | undefined {
|
||||
if (workspace.evidence === undefined) return undefined;
|
||||
if (binding.missing.length > 0) {
|
||||
throw new Error("runtime configuration requires complete Evidence bindings");
|
||||
}
|
||||
if (basename(context.revisionContentRoot) !== context.workspaceRevision) {
|
||||
throw new Error("runtime revision content root does not match workspace revision");
|
||||
}
|
||||
|
||||
const source = workspace.evidence.source;
|
||||
let renderedSource: Record<string, unknown>;
|
||||
if (source.type === "filesystem") {
|
||||
renderedSource = {
|
||||
type: "filesystem",
|
||||
root: join(context.revisionContentRoot, source.uri),
|
||||
patterns: source.patterns,
|
||||
max_bytes: source.max_bytes,
|
||||
};
|
||||
} else if (source.type === "http") {
|
||||
renderedSource = {
|
||||
type: "http",
|
||||
...(source.authentication === "none"
|
||||
? { urls: source.uris }
|
||||
: {
|
||||
provenance_urls: source.uris,
|
||||
signed_urls_file: requireEvidenceBinding(binding, bindingName("SIGNED_URLS_FILE")),
|
||||
}),
|
||||
connect_timeout: exactSeconds(source.connect_timeout_ms),
|
||||
read_timeout: exactSeconds(source.read_timeout_ms),
|
||||
max_bytes: source.max_bytes,
|
||||
max_redirects: source.max_redirects,
|
||||
allow_private_hosts: source.allow_private_hosts,
|
||||
max_cache_bytes: source.max_cache_bytes,
|
||||
};
|
||||
} else {
|
||||
const uri = new URL(source.uri);
|
||||
const sessionTokenFile = source.credentials === "static_files"
|
||||
? evidenceBindingValue(binding, bindingName("SESSION_TOKEN_FILE"))
|
||||
: undefined;
|
||||
renderedSource = {
|
||||
type: "s3",
|
||||
bucket: uri.hostname,
|
||||
prefix: uri.pathname.replace(/^\//, ""),
|
||||
...(source.endpoint_url === undefined ? {} : { endpoint_url: source.endpoint_url }),
|
||||
...(source.region === undefined ? {} : { region: source.region }),
|
||||
...(source.credentials === "ambient" ? {} : {
|
||||
access_key_file: requireEvidenceBinding(binding, bindingName("ACCESS_KEY_FILE")),
|
||||
secret_key_file: requireEvidenceBinding(binding, bindingName("SECRET_KEY_FILE")),
|
||||
...(sessionTokenFile === undefined ? {} : { session_token_file: sessionTokenFile }),
|
||||
}),
|
||||
trusted_endpoint: source.trusted_endpoint,
|
||||
allow_private_endpoint: source.allow_private_endpoint,
|
||||
allow_insecure_endpoint: source.allow_insecure_endpoint,
|
||||
max_bytes: source.max_bytes,
|
||||
max_objects: source.max_objects,
|
||||
max_pages: source.max_pages,
|
||||
page_size: source.page_size,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
evidence: { sources: [renderedSource] },
|
||||
vector: {
|
||||
max_chunk_chars: workspace.evidence.policy.max_chunk_chars,
|
||||
retain_published_generations: workspace.evidence.policy.retain_published_generations,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function placeholderConnection(identity: { database: string; schema: string }): Record<string, unknown> {
|
||||
return {
|
||||
host: "localhost",
|
||||
@@ -97,13 +205,13 @@ export function renderRuntimeConfig(
|
||||
workspace: WorkspaceDescriptor,
|
||||
bindings: RuntimeBindings,
|
||||
paths: RuntimePaths,
|
||||
identity?: RuntimeIdentity,
|
||||
identity?: RuntimeIdentity | RuntimeRenderContext,
|
||||
installation: RuntimeInstallationOverlay = {},
|
||||
semanticRuntime: SemanticRuntimeConfig = DEFAULT_SEMANTIC_RUNTIME,
|
||||
): string {
|
||||
const descriptor = validateWorkspaceDescriptor(workspace);
|
||||
const contract = buildInstallationContract(descriptor);
|
||||
const name = (role: "DWH" | "VECTOR" | "EMBEDDING", suffix: string) => {
|
||||
const name = (role: "DWH" | "VECTOR" | "EMBEDDING" | "EVIDENCE", suffix: string) => {
|
||||
const variable = contract.variables.find((entry) => entry.role === role && entry.suffix === suffix);
|
||||
if (!variable) throw new Error(`workspace contract is missing ${role}_${suffix}`);
|
||||
return variable.name;
|
||||
@@ -112,6 +220,15 @@ export function renderRuntimeConfig(
|
||||
if (descriptor.workspace.schema_version === 1) {
|
||||
throw new Error("Workspace descriptor requires explicit migration to schema version 2");
|
||||
}
|
||||
const canonicalV3 = descriptor as WorkspaceV3;
|
||||
const renderedEvidence = canonicalV3.evidence === undefined
|
||||
? undefined
|
||||
: renderEvidence(
|
||||
canonicalV3,
|
||||
bindings.evidence,
|
||||
requireRuntimeRenderContext(identity),
|
||||
(suffix) => name("EVIDENCE", suffix),
|
||||
);
|
||||
if (bindings.dwh.missing.length > 0) {
|
||||
throw new Error("runtime configuration requires complete bindings");
|
||||
}
|
||||
@@ -155,6 +272,7 @@ export function renderRuntimeConfig(
|
||||
},
|
||||
roots: paths,
|
||||
paths,
|
||||
...(renderedEvidence ?? {}),
|
||||
};
|
||||
if (bindings.dwh.transport === "postgres_direct") {
|
||||
renderedV3.dwh = { type: "postgres_direct", connection: database };
|
||||
|
||||
Reference in New Issue
Block a user