/*
Process-lifetime singleton cache for ActualState instances (ADR-0022).
Avoids rebuilding the actual state from scratch when executeKernelPipeline or
executeKernelCommand is called multiple times within the same Node.js process.
- Do not persist cache across processes — each process starts with an empty cache.
- Do not cache single-module registries built by buildRegistryForModule — only full registries are cached.
- ActualState is cached per process — rebuilding the registry per command is avoided.
- RFC-1026: add clearModule for incremental single-module invalidation without clearing the entire cache.
- RFC-1038: cache ActualState instead of KernelRegistry; remove clearModule (no module unload in desired-state model).
- RFC-1097: step 6 — compass.migrate codemod run
Mechanical v1 to v2 header migration across the workspace: 942 files rewritten — CHANGE_SUMMARY windows collapsed into history, forbidden v1 blocks stripped, KEY_DECISIONS seeded from @ai-invariant comments (5 files) or TODO placeholders (103 files), blocks reordered to canonical order.
- RFC-1097: sweep — werkstatt-engine clean
Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics.
- RFC-1147: getOrBuildSiteModuleStates — process-lifetime cache for materialized site-module ActualStates keyed by workspaceRoot; cleared alongside the registry cache.
ADR-0022
*/
import type { KernelAppConfig } from "@warpgogol/werkstatt-shared/kernel";
import type { ActualState, ModuleExport } from "@warpgogol/werkstatt-shared/kernel";
import { buildRegistry, type RegistryLoadOptions } from "./registry.ts";
import { buildActualState } from "../../runtime/reconciler.ts";
import { loadWorkspaceConfig } from "../discovery.ts";
const registryCache = new Map();
const siteModuleStatesCache = new Map>();
let cacheEnabled = true;
/**
* Returns a cached ActualState for the given cache key, or builds and caches
* a new one. The cache key must uniquely identify the config source within the
* process (e.g. `workspace:` or `site:`).
*
* When the cache is disabled (via `setRegistryCacheEnabled(false)`), always
* builds a fresh actual state without reading from or writing to the cache.
*/
export async function getOrBuildRegistry(
cacheKey: string,
config: KernelAppConfig,
options?: RegistryLoadOptions,
): Promise {
if (cacheEnabled) {
const cached = registryCache.get(cacheKey);
if (cached) {
if (!options?.quiet) process.stderr.write(` [registry] cache hit for ${cacheKey}\n`);
return cached;
}
}
const registry = await buildRegistry(config, options);
if (cacheEnabled) {
registryCache.set(cacheKey, registry);
}
return registry;
}
/**
* Load workspace config and return a cached actual state for it.
* Returns undefined when no workspace config exists.
*/
export async function getOrBuildWorkspaceRegistry(
workspaceRoot: string,
options?: RegistryLoadOptions,
): Promise {
const config = await loadWorkspaceConfig(workspaceRoot, options);
if (!config) return undefined;
const cacheKey = `workspace:${workspaceRoot}`;
return getOrBuildRegistry(cacheKey, config, options);
}
/**
* RFC-1147: materialize the canonical site module set once per workspace root.
* The loaders resolve @warpgogol/werkstatt-site specifiers from the consumer's
* node_modules — keyed by workspaceRoot because different workshops may pin
* different site-plugin versions. Returns module-name → ActualState pairs so
* callers keep the registering-module attribution.
*/
export async function getOrBuildSiteModuleStates(
workspaceRoot: string,
loaders: Record Promise>,
): Promise