feat: emit factory gate channel events
Add optional --run support to factory check, capabilities, rsi, and gate so factory verification steps can write channel JSONL events alongside existing receipts. Wire factory gate through fable5 verify with the same run id and document the new options in COMMANDS.md.
This commit is contained in:
parent
c3b1fda182
commit
e9d421a5bc
|
|
@ -168,12 +168,15 @@ fable-agent plinius godmode "improve explanation quality"
|
|||
### `factory`
|
||||
|
||||
- `factory check`
|
||||
- `--run <id>`
|
||||
- `factory capabilities`
|
||||
- `--file <path>`
|
||||
- `--run <id>`
|
||||
- `factory rsi`
|
||||
- `--host <host>`
|
||||
- `--port <n>`
|
||||
- `--out <path>`
|
||||
- `--run <id>`
|
||||
- `factory deploy <command>`
|
||||
- `--token <token>`
|
||||
- `--gate-receipt <path>`
|
||||
|
|
@ -186,6 +189,7 @@ fable-agent plinius godmode "improve explanation quality"
|
|||
- `--port <n>`
|
||||
- `--capabilities <path>`
|
||||
- `--diagnostic-only`
|
||||
- `--run <id>`
|
||||
|
||||
### `forgejo`
|
||||
|
||||
|
|
|
|||
44
src/index.ts
44
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<void> {
|
||||
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 <id>", "Emit factory check event to .runs/<id>/channel.jsonl")
|
||||
.action(async (opts: { run?: string }) => {
|
||||
await printFactoryCheck(opts.run);
|
||||
});
|
||||
|
||||
factory
|
||||
.command("capabilities")
|
||||
.description("Probe the factory capability registry")
|
||||
.option("--file <path>", "Capability registry path", "factory-capabilities.yaml")
|
||||
.action(async (opts: { file?: string }) => {
|
||||
.option("--run <id>", "Emit capability probe event to .runs/<id>/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 <host>", "Factory SSH host", "77.42.112.29")
|
||||
.option("--port <n>", "Factory SSH port", (v) => Number(v), 2222)
|
||||
.option("--out <path>", "Receipt output path")
|
||||
.action(async (opts: { host?: string; port?: number; out?: string }) => {
|
||||
.option("--run <id>", "Emit RSI receipt event to .runs/<id>/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 <n>", "Factory SSH port for cyber preflight", (v) => Number(v), 2222)
|
||||
.option("--capabilities <path>", "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 <id>", "Emit gate events to .runs/<id>/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(``);
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue