diff --git a/COMMANDS.md b/COMMANDS.md index 0038e1e..c4b0795 100644 --- a/COMMANDS.md +++ b/COMMANDS.md @@ -168,12 +168,15 @@ fable-agent plinius godmode "improve explanation quality" ### `factory` - `factory check` + - `--run ` - `factory capabilities` - `--file ` + - `--run ` - `factory rsi` - `--host ` - `--port ` - `--out ` + - `--run ` - `factory deploy ` - `--token ` - `--gate-receipt ` @@ -186,6 +189,7 @@ fable-agent plinius godmode "improve explanation quality" - `--port ` - `--capabilities ` - `--diagnostic-only` + - `--run ` ### `forgejo` diff --git a/src/index.ts b/src/index.ts index 6e34ec9..4dc7907 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1426,29 +1426,40 @@ const factory = program .command("factory") .description("Factory/VPS status and deployment guards"); -async function printFactoryCheck(): Promise<{ ready: boolean }> { +async function emitRunEvent(runId: string | undefined, event: { source: "agent" | "tool" | "workflow" | "gate" | "intake" | "mission"; type: "started" | "output" | "receipt" | "error" | "done"; data: unknown }): Promise { + if (!runId) return; + const { emitChannelEvent } = await import("./fable5/channel.js"); + emitChannelEvent(runId, event); +} + +async function printFactoryCheck(runId?: string): Promise<{ ready: boolean }> { const { checkFactory, factoryGateReady, formatFactoryCheck } = await import("./fable5/factory-status.js"); const result = await checkFactory(); + const ready = factoryGateReady(result); console.log(formatFactoryCheck(result.factory, result.deploy, result.errors)); - return { ready: factoryGateReady(result) }; + await emitRunEvent(runId, { source: "gate", type: ready ? "receipt" : "error", data: { kind: "factory-check", ready, ...result } }); + return { ready }; } factory .command("check") .description("Check factory status and deploy-webhook health") - .action(async () => { - await printFactoryCheck(); + .option("--run ", "Emit factory check event to .runs//channel.jsonl") + .action(async (opts: { run?: string }) => { + await printFactoryCheck(opts.run); }); factory .command("capabilities") .description("Probe the factory capability registry") .option("--file ", "Capability registry path", "factory-capabilities.yaml") - .action(async (opts: { file?: string }) => { + .option("--run ", "Emit capability probe event to .runs//channel.jsonl") + .action(async (opts: { file?: string; run?: string }) => { const { formatCapabilityReport, loadFactoryCapabilities, probeFactoryCapabilities } = await import("./fable5/factory-capabilities.js"); const services = loadFactoryCapabilities(path.resolve(opts.file ?? "factory-capabilities.yaml")); const report = await probeFactoryCapabilities(services); console.log(formatCapabilityReport(report)); + await emitRunEvent(opts.run, { source: "gate", type: report.requiredOk ? "receipt" : "error", data: { kind: "factory-capabilities", report } }); if (!report.requiredOk) process.exit(1); }); @@ -1458,12 +1469,14 @@ factory .option("--host ", "Factory SSH host", "77.42.112.29") .option("--port ", "Factory SSH port", (v) => Number(v), 2222) .option("--out ", "Receipt output path") - .action(async (opts: { host?: string; port?: number; out?: string }) => { + .option("--run ", "Emit RSI receipt event to .runs//channel.jsonl") + .action(async (opts: { host?: string; port?: number; out?: string; run?: string }) => { const { reconcileRsi } = await import("./fable5/rsi-reconcile.js"); const out = opts.out ?? path.join(".fable", "rsi", `${new Date().toISOString().replace(/[:.]/g, "-")}.json`); const receipt = reconcileRsi({ host: opts.host ?? "77.42.112.29", port: opts.port, out }); console.log(JSON.stringify(receipt, null, 2)); console.log(`\n Receipt: ${out}\n`); + await emitRunEvent(opts.run, { source: "gate", type: receipt.decision === "degraded" ? "error" : "receipt", data: { kind: "factory-rsi", receipt, path: out } }); if (receipt.decision === "degraded") process.exit(1); }); @@ -1499,14 +1512,17 @@ factory .option("--port ", "Factory SSH port for cyber preflight", (v) => Number(v), 2222) .option("--capabilities ", "Capability registry path", "factory-capabilities.yaml") .option("--diagnostic-only", "Bypass live factory/deploy readiness failures for non-deploy diagnostics") - .action(async (task: string, opts: { repo?: string; host?: string; port?: number; capabilities?: string; diagnosticOnly?: boolean }) => { + .option("--run ", "Emit gate events to .runs//channel.jsonl") + .action(async (task: string, opts: { repo?: string; host?: string; port?: number; capabilities?: string; diagnosticOnly?: boolean; run?: string }) => { const { spawnSync } = await import("node:child_process"); const repo = path.resolve(opts.repo ?? "."); - const status = await printFactoryCheck(); + await emitRunEvent(opts.run, { source: "gate", type: "started", data: { kind: "factory-gate", task, repo } }); + const status = await printFactoryCheck(opts.run); if (!status.ready && !opts.diagnosticOnly) { const { appendZteReceipt, createZteReceipt } = await import("./fable5/zte-protocol.js"); const reason = "live factory/deploy status is not ok"; appendZteReceipt(repo, createZteReceipt(task, repo, "blocked", ["factory check"], reason)); + await emitRunEvent(opts.run, { source: "gate", type: "error", data: { kind: "factory-gate", reason } }); console.error(` ✗ Factory gate failed: ${reason}; use --diagnostic-only for non-deploy diagnostics.`); process.exit(1); } @@ -1515,10 +1531,12 @@ factory const capabilitiesPath = path.resolve(opts.capabilities ?? "factory-capabilities.yaml"); const capabilities = await probeFactoryCapabilities(loadFactoryCapabilities(capabilitiesPath)); console.log(formatCapabilityReport(capabilities)); + await emitRunEvent(opts.run, { source: "gate", type: capabilities.requiredOk ? "receipt" : "error", data: { kind: "factory-capabilities", report: capabilities } }); if (!capabilities.requiredOk && !opts.diagnosticOnly) { const { appendZteReceipt, createZteReceipt } = await import("./fable5/zte-protocol.js"); const reason = "required factory capabilities are missing"; appendZteReceipt(repo, createZteReceipt(task, repo, "blocked", ["factory capabilities"], reason)); + await emitRunEvent(opts.run, { source: "gate", type: "error", data: { kind: "factory-gate", reason } }); console.error(` ✗ Factory gate failed: ${reason}; use --diagnostic-only for non-deploy diagnostics.`); process.exit(1); } @@ -1533,10 +1551,12 @@ factory writeCyberReconcileReceipt(reconcilePath, reconcile); console.log(` Cyber preflight: ${preflight.decision} (${preflightPath})`); console.log(` Cyber reconcile: ${reconcile.decision} (${reconcilePath})`); + await emitRunEvent(opts.run, { source: "gate", type: preflight.decision === "allow_read_only" && reconcile.decision === "allow_read_only" ? "receipt" : "error", data: { kind: "cyber-preflight", preflightPath, reconcilePath, preflight: preflight.decision, reconcile: reconcile.decision } }); if ((preflight.decision !== "allow_read_only" || reconcile.decision !== "allow_read_only") && !opts.diagnosticOnly) { const { appendZteReceipt, createZteReceipt } = await import("./fable5/zte-protocol.js"); const reason = "cyber preflight/reconcile did not pass"; appendZteReceipt(repo, createZteReceipt(task, repo, "blocked", ["cyber preflight", "cyber reconcile"], reason)); + await emitRunEvent(opts.run, { source: "gate", type: "error", data: { kind: "factory-gate", reason } }); console.error(` ✗ Factory gate failed: ${reason}; use --diagnostic-only for non-deploy diagnostics.`); process.exit(1); } @@ -1545,9 +1565,13 @@ factory console.log(` Repo: ${repo}`); console.log(``); - const args = [process.argv[1], "fable5", "verify", task, "--repo", repo]; + const args = [process.argv[1], "fable5", "verify", task, "--repo", repo, ...(opts.run ? ["--run", opts.run] : [])]; const check = spawnSync(process.execPath, args, { cwd: repo, stdio: "inherit" }); - if (check.status !== 0) process.exit(check.status ?? 1); + if (check.status !== 0) { + await emitRunEvent(opts.run, { source: "gate", type: "error", data: { kind: "factory-gate", reason: "repo verification failed", status: check.status } }); + process.exit(check.status ?? 1); + } + await emitRunEvent(opts.run, { source: "gate", type: "done", data: { kind: "factory-gate", task, repo } }); console.log(` ✓ Factory gate passed; deploy still requires explicit token/contract.`); console.log(``); });