/* Single-sourced module loader maps (RFC-1125, RFC-1148) — WORKSHOP_MODULE_MAP is the canonical list of kernel modules a Werkstatt workshop loads; SITE_MODULE_MAP is the canonical list a Sternsystem site loads, shared between kernel.wire templates and listStaticCommandSurface. Do not add platform-development validators (werkstatt-shared-validate, werkstatt-commands-validate, werkstatt-e2e, typescript-checks) — they stay repo-local in tools/kernel.config.ts. Do not add warpgogol-product modules (portal, billing, warpgogol-lifecycle, projektarchiv-operations) or platform codegen (icons) — they stay repo-local. Do not import module specifiers statically — werkstatt-engine does not depend on @warpgogol/werkstatt-site; specs resolve from the consumer's node_modules at runtime. SITE_MODULE_MAP is the single enumeration source — kernel.config.template.ts and listStaticCommandSurface share it, drift impossible. Descriptors carry spec+export strings only — engine never imports the site plugin statically; specs resolve in consumer node_modules. Loaders materialize lazily via dynamic import — module cost is paid only when a surface is enumerated. RFC-1125: initial WorkshopModuleMap — descriptors + workshopModuleLoaders() materializer; consumed by tools/kernel.config.ts and the site-workshop profile's scaffolded kernel.config.ts. RFC-1140: steps 1-4 — shared resolver, queue module, registration Extract pipeline-status derivation into packages/forge/src/pipeline-status.ts, refactor rfc.pipeline.status onto it, add os/queue module with queue.validate command, register in WORKSHOP_MODULE_MAP.forge + bin/cli.ts + package.json exports. RFC-1148: SITE_MODULE_MAP + siteModuleLoaders() — canonical 18-loader site module set shared by kernel.config.template.ts and listStaticCommandSurface; materializer refactored into a shared helper. RFC-1148: review fix — materializeDescriptors wraps import failure with descriptor key/spec context. RFC-1281: intake contract and onboarding.intake.validate */ import { readFileSync } from "node:fs"; import { fileURLToPath } from "node:url"; import type { ModuleExport } from "@warpgogol/werkstatt-shared/kernel"; // @ai-invariant: SITE_MODULE_MAP is the only enumeration of the site module set — never maintain a parallel list in wire templates or consumers. /** * Descriptor for one kernel module. `spec` is the npm subpath specifier resolved * at runtime from the CONSUMER's node_modules (this repo's root or a client * workshop root — both install @warpgogol/* as dependencies). `export` is the * named export holding the module: a factory function (`createXModule`) or a * module object (`xModule`) — the materializer handles both. */ export interface WorkshopModuleDescriptor { spec: string; export: string; } /** * Single-sourced module map covering the workshop-facing module subset * (RFC-1125). Consumed by this repo's tools/kernel.config.ts AND embedded in * the site-workshop profile's scaffolded kernel.config.ts — drift between * profile and reference workshop is impossible by construction. * * Buckets are descriptive only; workshopModuleLoaders() flattens them. */ export interface WorkshopModuleMap { /** @warpgogol/werkstatt-engine modules — site lifecycle + platform services. */ engine: Record; /** @warpgogol/werkstatt-site plugin modules — checks, onboarding, testing. */ sitePlugin: Record; /** @warpgogol/forge governance modules — rfc, adr, session, compass, … */ forge: Record; } const ENGINE = "@warpgogol/werkstatt-engine"; const SITE = "@warpgogol/werkstatt-site"; const FORGE = "@warpgogol/forge"; const engine = (spec: string, exp: string): WorkshopModuleDescriptor => ({ spec: `${ENGINE}/${spec}`, export: exp, }); const sitePlugin = (spec: string, exp: string): WorkshopModuleDescriptor => ({ spec: `${SITE}/${spec}`, export: exp, }); const forge = (spec: string, exp: string): WorkshopModuleDescriptor => ({ spec: `${FORGE}/${spec}`, export: exp, }); export const WORKSHOP_MODULE_MAP: WorkshopModuleMap = { engine: { mission: engine("mission-module", "createMissionModule"), sternsystem: engine("sternsystem-module", "createSternsystemModule"), bordbuch: engine("bordbuch-module", "createBordbuchModule"), release: engine("release-module", "createReleaseModule"), leitstand: engine("leitstand-module", "createLeitstandModule"), notausgang: engine("notausgang-module", "createNotausgangModule"), evidence: engine("evidence-module", "createEvidenceModule"), identity: engine("identity-module", "createIdentityModule"), fleet: engine("fleet-module", "createFleetModule"), gitmesh: engine("kernel", "createGitmeshModule"), dht: engine("kernel/dht-module", "createDhtModule"), swim: engine("kernel/swim-module", "createSwimModule"), "artifact-store": engine("artifact-store-module", "createArtifactStoreModule"), "behavior-snapshot": engine("behavior-snapshot-module", "createBehaviorSnapshotModule"), handoff: engine("handoff-module", "createHandoffModule"), platform: engine("platform-module", "createPlatformModule"), signing: engine("signing-module", "createSigningModule"), nachweis: engine("nachweis-module", "createNachweisModule"), sichtpass: engine("sichtpass-module", "createSichtpassModule"), subdomain: engine("subdomain-module", "createSubdomainModule"), customdomain: engine("customdomain-module", "createCustomdomainModule"), dns: engine("dns-module", "createDnsModule"), deploy: engine("deploy-module", "createDeployModule"), observability: engine("observability/module", "createObservabilityModule"), cache: engine("kernel/cache-module", "createCacheModule"), "commit-message": engine("kernel/commit-message", "createCommitMessageModule"), "pipeline-budget": engine("kernel/pipeline-budget", "createPipelineBudgetModule"), "validator-inventory": engine("kernel/validator-inventory", "createValidatorInventoryModule"), remediation: engine("remediation-module", "createRemediationModule"), "command-manifest": engine("kernel/command-manifest-module", "createCommandManifestModule"), "component-runtime": engine("component-runtime-module", "createComponentRuntimeModule"), effects: engine("effects-module", "createEffectsModule"), evolution: engine("evolution-module", "createEvolutionModule"), isolation: engine("isolation-module", "createIsolationModule"), scope: engine("scope-module", "createScopeModule"), composition: engine("composition-module", "compositionModule"), lagebild: engine("lagebild-module", "createLagebildModule"), intake: engine("intake-module", "createIntakeModule"), "werkstatt-autonomy": engine("os/werkstatt-autonomy-module", "werkstattAutonomyModule"), }, sitePlugin: { check: sitePlugin("checks/module", "createStandardCheckModule"), service: sitePlugin("surface/service-module", "serviceModule"), changelog: sitePlugin("surface/changelog-module", "changelogModule"), integrity: sitePlugin("surface/integrity-module", "integrityModule"), onboarding: sitePlugin("onboarding/module", "createOnboardingModule"), testing: sitePlugin("testing/module", "createTestingModule"), }, forge: { "forge-core": forge("os/core", "createForgeCoreModule"), "forge-compass": forge("os/compass", "forgeCompassModule"), "forge-naming": forge("os/naming-module", "createForgeNamingModule"), workflow: forge("os/workflow-module", "createForgeWorkflowModule"), rfc: forge("os/rfc-module", "createForgeRfcModule"), "forge-adr": forge("os/adr-module", "createForgeAdrModule"), "forge-plan": forge("os/plan-module", "createForgePlanModule"), "forge-audit": forge("os/audit-module", "createForgeAuditModule"), "forge-session": forge("os/session-module", "createForgeSessionModule"), "forge-mission": forge("os/mission-module", "createForgeMissionModule"), "forge-werkstatt": forge("os/werkstatt", "forgeWerkstattModule"), "forge-spec": forge("os/spec-module", "createForgeSpecModule"), "forge-exploration": forge("os/exploration", "createForgeExplorationModule"), "forge-notes": forge("os/notes", "createForgeNotesModule"), "forge-program": forge("os/program", "createForgeProgramModule"), "forge-plugin": forge("os/plugin", "forgePluginModule"), "forge-queue": forge("os/queue-module", "createForgeQueueModule"), }, }; /** * Canonical site module map (RFC-1148) — the 18 loaders kernel.wire emits into * every Sternsystem's tools/kernel.config.ts. Consumed by the wire template * (via siteModuleLoaders()) and by listStaticCommandSurface, so the generated * site config and the command manifest share one enumeration source. * * Buckets: 8 plugin-owned site-stack modules (surface/* + onboarding/testing), * 5 forge guests, 5 engine guests — same factories the site config loads. */ export const SITE_MODULE_MAP: Record = { // site-stack (plugin-owned definitions under surface/) check: sitePlugin("surface/check-module", "createSiteCheckModule"), service: sitePlugin("surface/service-module", "serviceModule"), deploy: sitePlugin("surface/deploy-module", "deployModule"), integrity: sitePlugin("surface/integrity-module", "integrityModule"), changelog: sitePlugin("surface/changelog-module", "changelogModule"), pipelines: sitePlugin("surface/pipelines-module", "pipelinesModule"), onboarding: sitePlugin("onboarding/module", "createOnboardingModule"), testing: sitePlugin("testing/module", "createTestingModule"), // forge guests — same factories the site config loads rfc: forge("os/rfc-module", "createForgeRfcModule"), workflow: forge("os/workflow-module", "createForgeWorkflowModule"), compass: forge("os/compass", "forgeCompassModule"), naming: forge("os/naming-module", "createForgeNamingModule"), werkstatt: forge("os/werkstatt", "forgeWerkstattModule"), // engine guests "change-impact": engine("kernel", "createChangeImpactModule"), bordbuch: engine("bordbuch-module", "createBordbuchModule"), nachweis: engine("nachweis-module", "createNachweisModule"), sichtpass: engine("sichtpass-module", "createSichtpassModule"), dns: engine("dns-module", "createDnsModule"), }; /** * Derive the declaring source file for a module descriptor: resolves the * descriptor spec to the real .ts file and returns the path relative to the * `packages/` tree. Returns undefined when the specifier resolves outside a * packages/ layout (e.g. an npm-installed node_modules copy — there the * repo-relative modulePath convention has no meaning and explicit values keep * working unchanged). */ function deriveModuleFile(spec: string): { abs: string; rel: string } | undefined { try { const resolved = import.meta.resolve(spec); const abs = resolved.startsWith("file://") ? fileURLToPath(resolved) : resolved; const anchor = abs.indexOf("/packages/"); return anchor >= 0 ? { abs, rel: abs.slice(anchor + 1) } : undefined; } catch { return undefined; } } function readSource(path: string): string | undefined { try { return readFileSync(path, "utf8"); } catch { return undefined; } } /** * Locate the file that actually declares a command: the descriptor's module * file, or — when that file is an index.ts barrel that names nothing — the * first `export ... from "..."` target containing the command's name (one hop, * relative specifiers only). Returns undefined when no file names the command. */ function findDeclaringFile( name: string, file: { abs: string; rel: string }, ): { abs: string; rel: string } | undefined { const content = readSource(file.abs); if (content === undefined) return undefined; if (content.includes(`"${name}"`)) return file; for (const m of content.matchAll(/\bexport\s+(?:\*|\{[^}]*\})\s+from\s+["']([^"']+)["']/g)) { const spec = m[1]; if (!spec.startsWith("./") && !spec.startsWith("../")) continue; const base = file.abs.slice(0, file.abs.lastIndexOf("/") + 1); const resolved = new URL(spec, `file://${base}`).pathname; const target = resolved.endsWith(".ts") ? resolved : `${resolved.replace(/\/$/, "")}.ts`; if (readSource(target)?.includes(`"${name}"`)) { const anchor = target.indexOf("/packages/"); if (anchor >= 0) return { abs: target, rel: target.slice(anchor + 1) }; } } return undefined; } /** * Stamp a default modulePath onto commands that don't declare one — the file * that actually declares the command (index.ts barrels are followed one hop * through their re-exports). Explicit per-command modulePath always wins. * Prevents OWN-XCHECK-03 drift: generated-artifact ownership can always name * the registering module without hand-maintained paths. */ function withDerivedModulePath( mod: ModuleExport, file: { abs: string; rel: string }, ): ModuleExport { return { ...mod, commands: (mod.commands ?? []).map((c) => { if (c.modulePath) return c; const declaring = findDeclaringFile(c.name, file); return declaring ? { ...c, modulePath: declaring.rel } : c; }), }; } /** * Materialize a descriptor map into the `moduleLoaders` record shape that * defineKernelConfig expects. Each thunk resolves its specifier lazily via * dynamic import — specifiers are resolved from the consumer's node_modules, * so this works identically in the reference monorepo, a scaffolded client * workshop, and a generated Sternsystem site. */ function materializeDescriptors( maps: Record[], ): Record Promise> { const loaders: Record Promise> = {}; for (const map of maps) { for (const [key, desc] of Object.entries(map)) { loaders[key] = async () => { let mod: Record; try { mod = (await import(desc.spec)) as Record; } catch (error) { throw new Error( `[module-map] failed to load '${desc.spec}' (descriptor '${key}'): ` + `${error instanceof Error ? error.message : String(error)}`, ); } const exported = mod[desc.export]; if (exported === undefined) { throw new Error( `[module-map] '${desc.spec}' has no export '${desc.export}' — check descriptor entry '${key}'`, ); } const moduleExport = ( typeof exported === "function" ? await exported() : exported ) as ModuleExport; const moduleFile = deriveModuleFile(desc.spec); return moduleFile ? withDerivedModulePath(moduleExport, moduleFile) : moduleExport; }; } } return loaders; } /** * Materialize WORKSHOP_MODULE_MAP into the `moduleLoaders` record shape that * defineKernelConfig expects. Each thunk resolves its specifier lazily via * dynamic import — specifiers are resolved from the consumer's node_modules, * so this works identically in the reference monorepo and in a scaffolded * client workshop. */ export function workshopModuleLoaders(): Record Promise> { return materializeDescriptors([ WORKSHOP_MODULE_MAP.engine, WORKSHOP_MODULE_MAP.sitePlugin, WORKSHOP_MODULE_MAP.forge, ]); } /** * Materialize only the forge bucket of WORKSHOP_MODULE_MAP — the forge-only * command surface (governance modules without engine/site-plugin extras). * Consumed by the forge-profile scaffolded kernel.config.ts templates, which * load forge modules plus profile-specific extras (e.g. typescript-checks) * but not the full workshop engine set. */ export function forgeModuleLoaders(): Record Promise> { return materializeDescriptors([WORKSHOP_MODULE_MAP.forge]); } /** * Materialize SITE_MODULE_MAP into the `moduleLoaders` record shape — the * canonical site command surface (RFC-1148). Consumed by * kernel.config.template.ts (generated site configs) and by * listStaticCommandSurface (command manifest enumeration). */ export function siteModuleLoaders(): Record Promise> { return materializeDescriptors([SITE_MODULE_MAP]); }