/* RFC-1065: lagebild.validate command — check 4 LAGEBILD_* secrets via wrangler secret list, verify .env key presence, best-effort GET /health. Do not use the API key for authenticated requests — GET /health only. Do not block on health check failure — best-effort, non-blocking. Do not modify system-state.yaml. RFC-1065: initial lagebild.validate command handler. 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-1249: wire werkstatt.commands.validate into packages.check + retire CMD-OUTPUT debt */ import { join } from "node:path"; import type { KernelCommandInput, KernelCommandResult, KernelRuntimeContext, } from "@warpgogol/werkstatt-engine/kernel"; import { readSystemConfigSmart, readSystemStateSmart, resolveCacheClonePath, } from "../sternsystem/registry-io.ts"; import { filterEnv, sourceDotenv } from "../leitstand/adapters/index.ts"; import { LAGEBILD_SECRET_NAMES, resolveWorkerName } from "./lagebild-helpers.ts"; import { createDefaultIO, type WorkspaceIO } from "@warpgogol/werkstatt-shared/kernel/workspace-io"; import { failResult } from "@warpgogol/werkstatt-shared/checks/result-helpers"; function flagString(input: KernelCommandInput, name: string): string | undefined { const v = input.flags[name]; return typeof v === "string" ? v : undefined; } interface SecretCheckResult { name: string; present: boolean; } interface HealthCheckResult { reachable: boolean; statusCode?: number; error?: string; } async function runWranglerSecretList( workerName: string, env: Record, cwd: string, io: WorkspaceIO = createDefaultIO().io, ): Promise<{ exitCode: number; stdout: string; stderr: string }> { try { const envFiltered = Object.fromEntries( Object.entries(env).filter((e): e is [string, string] => e[1] !== undefined), ); const result = await io.exec( "npx", ["--yes", "wrangler", "secret", "list", "--name", workerName], { cwd, env: envFiltered }, ); const listed: { exitCode: number; stdout: string; stderr: string } = { exitCode: result.exitCode ?? 1, stdout: result.stdout, stderr: result.stderr, }; return listed; } catch { const failed: { exitCode: number; stdout: string; stderr: string } = { exitCode: 1, stdout: "", stderr: "Failed to spawn wrangler", }; return failed; } } export async function runLagebildValidate( input: KernelCommandInput, context: KernelRuntimeContext, ): Promise< KernelCommandResult<{ site: string; channel: string; workerName: string; secrets: SecretCheckResult[]; allSecretsPresent: boolean; apiKeyInEnv: boolean; apiReachable: HealthCheckResult; state: { connected: boolean; apiUrl?: string; channel?: string }; }> > { const systemId = flagString(input, "site"); if (!systemId) throw new Error("[lagebild.validate] --site is required"); const channel = flagString(input, "channel") ?? "main"; const workspaceRoot = context.workspaceRoot; let systemConfig; try { systemConfig = await readSystemConfigSmart(workspaceRoot, systemId); } catch (err) { const msg = err instanceof Error ? err.message : String(err); if (msg.includes("ENOENT") || msg.includes("no such file")) { throw new Error(`Site '${systemId}' not found`); } throw err; } const workerName = resolveWorkerName(systemConfig, channel); const cacheCloneDir = resolveCacheClonePath(workspaceRoot, systemId); const envPath = join(cacheCloneDir, ".env"); const secretsEnv = (await context.io.exists(envPath)) ? await sourceDotenv(envPath) : {}; const env: Record = { ...filterEnv(process.env as Record), ...secretsEnv, }; // Spawn wrangler secret list const listResult = await runWranglerSecretList(workerName, env, workspaceRoot); // Parse secret names from wrangler output // wrangler secret list outputs lines like: "SECRET_NAME 2024-01-01T00:00:00Z" const presentSecrets = new Set(); if (listResult.exitCode === 0) { for (const line of listResult.stdout.split("\n")) { const trimmed = line.trim(); if (!trimmed) continue; // First column is the secret name const name = trimmed.split(/\s+/)[0]; if (name) presentSecrets.add(name); } } const secretChecks: SecretCheckResult[] = LAGEBILD_SECRET_NAMES.map((name) => ({ name, present: presentSecrets.has(name), })); const allSecretsPresent = secretChecks.every((s) => s.present); // Check .env for LAGEBILD_API_KEY presence let apiKeyInEnv = false; if (await context.io.exists(envPath)) { const rawEnv = await context.io.readFile(envPath); for (const line of rawEnv.split("\n")) { const trimmed = line.trim(); if (trimmed.startsWith("LAGEBILD_API_KEY=") && trimmed.length > "LAGEBILD_API_KEY=".length) { apiKeyInEnv = true; break; } } } // Read state for API URL const state = await readSystemStateSmart(workspaceRoot, systemId); const lagebildState = state.lagebild; // Best-effort GET /health (non-blocking) let apiReachable: HealthCheckResult = { reachable: false }; if (lagebildState?.apiUrl) { try { const healthUrl = lagebildState.apiUrl.endsWith("/") ? `${lagebildState.apiUrl}health` : `${lagebildState.apiUrl}/health`; const response = await fetch(healthUrl, { method: "GET", signal: AbortSignal.timeout(5000), }); apiReachable = { reachable: response.ok, statusCode: response.status, }; } catch (err) { apiReachable = { reachable: false, error: err instanceof Error ? err.message : String(err), }; } } if (!allSecretsPresent) { return { ...failResult("lagebild.validate", [ `Missing secrets: ${secretChecks .filter((s) => !s.present) .map((s) => s.name) .join(", ")}`, ]), data: { site: systemId, channel, workerName, secrets: secretChecks, allSecretsPresent: false, apiKeyInEnv, apiReachable, state: { connected: lagebildState?.connected ?? false, apiUrl: lagebildState?.apiUrl, channel: lagebildState?.channel, }, }, }; } return { data: { site: systemId, channel, workerName, secrets: secretChecks, allSecretsPresent: true, apiKeyInEnv, apiReachable, state: { connected: lagebildState?.connected ?? false, apiUrl: lagebildState?.apiUrl, channel: lagebildState?.channel, }, }, }; }