diff --git a/COMMANDS.md b/COMMANDS.md index e0225fa..342290b 100644 --- a/COMMANDS.md +++ b/COMMANDS.md @@ -172,6 +172,9 @@ fable-agent plinius godmode "improve explanation quality" - `--file ` - `factory gate ` - `--repo ` + - `--host ` + - `--port ` + - `--capabilities ` - `--diagnostic-only` ### `forgejo` diff --git a/src/index.ts b/src/index.ts index d303469..31bbb29 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1456,8 +1456,11 @@ factory .command("gate ") .description("Run factory check plus local ZTE/repo verification gate") .option("--repo ", "Repo path to verify", ".") + .option("--host ", "Factory SSH host for cyber preflight", "77.42.112.29") + .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; diagnosticOnly?: boolean }) => { + .action(async (task: string, opts: { repo?: string; host?: string; port?: number; capabilities?: string; diagnosticOnly?: boolean }) => { const { spawnSync } = await import("node:child_process"); const repo = path.resolve(opts.repo ?? "."); const status = await printFactoryCheck(); @@ -1469,6 +1472,36 @@ factory process.exit(1); } + const { formatCapabilityReport, loadFactoryCapabilities, probeFactoryCapabilities } = await import("./fable5/factory-capabilities.js"); + const capabilitiesPath = path.resolve(opts.capabilities ?? "factory-capabilities.yaml"); + const capabilities = await probeFactoryCapabilities(loadFactoryCapabilities(capabilitiesPath)); + console.log(formatCapabilityReport(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)); + console.error(` ✗ Factory gate failed: ${reason}; use --diagnostic-only for non-deploy diagnostics.`); + process.exit(1); + } + + const { runCyberPreflight } = await import("./fable5/cyber-preflight.js"); + const { reconcileCyberPreflight, writeCyberReconcileReceipt } = await import("./fable5/cyber-reconcile.js"); + const stamp = new Date().toISOString().replace(/[:.]/g, "-"); + const preflightPath = path.join(repo, ".fable", "cyber-preflight", `factory-gate-${stamp}.json`); + const reconcilePath = path.join(repo, ".fable", "cyber-preflight", `factory-gate-${stamp}-reconcile.json`); + const preflight = runCyberPreflight({ host: opts.host ?? "77.42.112.29", port: opts.port, out: preflightPath }); + const reconcile = reconcileCyberPreflight(preflightPath, capabilitiesPath); + writeCyberReconcileReceipt(reconcilePath, reconcile); + console.log(` Cyber preflight: ${preflight.decision} (${preflightPath})`); + console.log(` Cyber reconcile: ${reconcile.decision} (${reconcilePath})`); + 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)); + console.error(` ✗ Factory gate failed: ${reason}; use --diagnostic-only for non-deploy diagnostics.`); + process.exit(1); + } + console.log(` ZTE Gate: ${task}`); console.log(` Repo: ${repo}`); console.log(``);