feat: add fableflow manifest generator

This commit is contained in:
artale 2026-06-29 01:14:36 +02:00
parent beb80490c8
commit 518a26fdd9
5 changed files with 184 additions and 0 deletions

View File

@ -360,6 +360,8 @@ fable-agent plinius godmode "improve explanation quality"
- `--fallback <text>` - `--fallback <text>`
- `--risk <risk>` - `--risk <risk>`
- `--out <path>` - `--out <path>`
- `fable5 fableflow`
- `--out <path>`
- `fable5 spec <task>` - `fable5 spec <task>`
- `--repo <path>` - `--repo <path>`
- `--out <file>` - `--out <file>`

View File

@ -0,0 +1,64 @@
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { describe, expect, it } from "vitest";
import { createFableFlowManifest, writeFableFlowManifest } from "./fableflow-manifest.js";
describe("fableflow manifest", () => {
it("includes required Fable topology nodes", () => {
const manifest = createFableFlowManifest();
const ids = manifest.nodes.map((node) => node.id);
expect(ids).toEqual(expect.arrayContaining([
"forgejo",
"forgejo-runner",
"git-proxy-8099",
"deploy-webhook-8098",
"agent-site-8084",
"feedbackpilot-intake",
"work-inbox",
"forgejo-intake",
"forgejo-writeback",
"governance-contract",
"agent-chain-workers",
"zte-gate",
"factory-reconcile",
"model-access",
"rsi-canary",
]));
});
it("marks 8099 canonical and 8098 legacy/stale", () => {
const manifest = createFableFlowManifest();
const canonical = manifest.nodes.find((node) => node.id === "git-proxy-8099")!;
const legacy = manifest.nodes.find((node) => node.id === "deploy-webhook-8098")!;
expect(manifest.meta.deployAuthority).toBe("8099/deploy via git-proxy");
expect(manifest.meta.noDeploy).toBe(true);
expect(canonical.desc).toContain("Canonical guarded deploy route");
expect(legacy.desc).toContain("Legacy/stale visual-only");
expect(legacy.line).toBe("legacy");
});
it("wires intake through governance, verification, and canonical deploy", () => {
const manifest = createFableFlowManifest();
const edges = manifest.edges.map((edge) => `${edge.from}>${edge.to}`);
expect(edges).toContain("forgejo-intake>governance-contract");
expect(edges).toContain("governance-contract>agent-chain-workers");
expect(edges).toContain("agent-chain-workers>zte-gate");
expect(edges).toContain("factory-reconcile>git-proxy-8099");
expect(edges).toContain("deploy-webhook-8098>git-proxy-8099");
});
it("writes JSON manifest", () => {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "fableflow-"));
const file = path.join(dir, "manifest.json");
writeFableFlowManifest(file);
const manifest = JSON.parse(fs.readFileSync(file, "utf-8")) as ReturnType<typeof createFableFlowManifest>;
expect(manifest.meta.schema).toBe("fable.fableflow.manifest.v1");
expect(manifest.nodes.length).toBeGreaterThan(10);
});
});

View File

@ -0,0 +1,102 @@
import * as fs from "node:fs";
import * as path from "node:path";
export interface FableFlowNode {
id: string;
label: string;
kind: string;
line: string;
zone: string;
sub: string;
desc: string;
watched: string;
records: string;
icon: string;
x: number;
y: number;
}
export interface FableFlowEdge {
id: string;
from: string;
to: string;
line: string;
label: string;
}
export interface FableFlowManifest {
nodes: FableFlowNode[];
edges: FableFlowEdge[];
lines: Record<string, { label: string; color: string }>;
meta: {
schema: "fable.fableflow.manifest.v1";
generatedFrom: "committed-receipts";
deployAuthority: "8099/deploy via git-proxy";
legacyDeploy: "8098/deploy-webhook visual-only legacy/stale";
noDeploy: true;
};
}
export function createFableFlowManifest(): FableFlowManifest {
return {
meta: {
schema: "fable.fableflow.manifest.v1",
generatedFrom: "committed-receipts",
deployAuthority: "8099/deploy via git-proxy",
legacyDeploy: "8098/deploy-webhook visual-only legacy/stale",
noDeploy: true,
},
lines: {
intake: { label: "Intake", color: "#58a6ff" },
governance: { label: "Governance", color: "#bc8cff" },
verify: { label: "Verify / receipts", color: "#3fb950" },
deploy: { label: "Guarded deploy", color: "#f0883e" },
legacy: { label: "Legacy / stale", color: "#8b949e" },
},
nodes: [
node("forgejo", "Forgejo", "service", "intake", "source", "issues / PRs / wiki", "Forgejo replaces GitHub/Jira as work source", "FORGEJO_URL", ".fable/tasks/forgejo-*.json", "FG", 0, 0),
node("forgejo-runner", "Forgejo Runner", "service", "verify", "ci", "runner", "Runner status must be refreshed live before claims", "live docker/runner receipt", ".fable/cyber-preflight/*.json", "CI", 240, 0),
node("git-proxy-8099", "git-proxy 8099", "service", "deploy", "deploy", "canonical", "Canonical guarded deploy route: 8099/deploy via git-proxy", "http://77.42.112.29:8099/health", ".fable/factory/reconciliation-live.json", "DP", 480, 0),
node("deploy-webhook-8098", "deploy-webhook 8098", "legacy", "legacy", "deploy", "legacy/stale", "Legacy/stale visual-only deploy-webhook; not a trusted decision path", "legacy only", ".fable/cyber-preflight/*8098*", "LG", 720, 0),
node("agent-site-8084", "agent-site 8084", "service", "intake", "ui", "agent site", "Agent/site surface; refresh live before availability claims", "http://77.42.112.29:8084", ".fable/cyber-preflight/*.json", "UI", 960, 0),
node("feedbackpilot-intake", "FeedbackPilot Intake", "receipt", "intake", "wp", "user feedback", "FeedbackPilot JSON to receipt / optional Forgejo issue", "src/fable5/feedbackpilot-intake.ts", "fable.feedbackpilot.intake.v1", "FP", 0, 180),
node("work-inbox", "Work Inbox", "receipt", "intake", "native work", "slack/discord/email/chat", "Where-you-work messages become scanned async receipts", "src/fable5/work-inbox.ts", "fable.work_inbox.receipt.v1", "IN", 240, 180),
node("forgejo-intake", "Forgejo Intake", "receipt", "intake", "work", "issues/PR/wiki", "Forgejo item to scanned task receipt", "src/fable5/forgejo-intake.ts", "fable.tasks/forgejo-*.json", "FI", 480, 180),
node("forgejo-writeback", "Forgejo Writeback", "receipt", "intake", "work", "comments/labels", "Receipt-backed comments/labels only; no deploy authority", "src/fable5/forgejo-writeback.ts", "fable.forgejo.writeback.v1", "FW", 720, 180),
node("governance-contract", "Governance Contract", "receipt", "governance", "control", "quorum", "Proposer/approver/quorum/tie-break/fallback receipt", "src/fable5/governance-contract.ts", "fable.governance.contract.v1", "GV", 240, 360),
node("agent-chain-workers", "Agent Chain Workers", "receipt", "verify", "workers", "scout/plan/build/review", "Receipt-backed local worker stages", "src/fable5/agent-chains.ts", "fable.agent_chain.stage.v1", "CH", 480, 360),
node("zte-gate", "ZTE Gate", "receipt", "verify", "gate", "plan/gate", "Verifier gate before deploy decisions", "src/fable5/zte-protocol.ts", ".fable/zte-receipts.jsonl", "ZT", 720, 360),
node("factory-reconcile", "Factory Reconcile", "receipt", "verify", "factory", "live receipt", "Live reconciliation overrides memory", "src/fable5/factory-reconciliation.ts", ".fable/factory/reconciliation-live.json", "RC", 960, 360),
node("model-access", "Model Access", "receipt", "verify", "models", "access check", "Model access claims require fresh receipt", "src/fable5/model-access-receipt.ts", "fable.model_access.receipt.v1", "MA", 0, 540),
node("rsi-canary", "RSI Canary", "receipt", "verify", "rsi", "canary only", "Canary repair proven; broad autopatch remains unproven without fresh receipt", "fable rsi receipts", ".fable/rsi/canary-live-*.json", "RS", 240, 540),
],
edges: [
edge("feedbackpilot-intake", "forgejo-intake", "intake", "opens issue"),
edge("work-inbox", "forgejo-intake", "intake", "captures work"),
edge("forgejo", "forgejo-intake", "intake", "fetches"),
edge("forgejo-intake", "governance-contract", "governance", "routes"),
edge("governance-contract", "agent-chain-workers", "verify", "authorizes work"),
edge("agent-chain-workers", "zte-gate", "verify", "produces evidence"),
edge("zte-gate", "factory-reconcile", "verify", "checks live state"),
edge("factory-reconcile", "git-proxy-8099", "deploy", "canonical gated path"),
edge("deploy-webhook-8098", "git-proxy-8099", "legacy", "superseded by"),
edge("forgejo-writeback", "forgejo", "intake", "comments/labels"),
edge("model-access", "agent-chain-workers", "verify", "provider evidence"),
edge("rsi-canary", "zte-gate", "verify", "canary proof only"),
],
};
}
export function writeFableFlowManifest(file: string, manifest = createFableFlowManifest()): string {
fs.mkdirSync(path.dirname(file), { recursive: true });
fs.writeFileSync(file, `${JSON.stringify(manifest, null, 2)}\n`);
return file;
}
function node(id: string, label: string, kind: string, line: string, zone: string, sub: string, desc: string, watched: string, records: string, icon: string, x: number, y: number): FableFlowNode {
return { id, label, kind, line, zone, sub, desc, watched, records, icon, x, y };
}
function edge(from: string, to: string, line: string, label: string): FableFlowEdge {
return { id: `${from}>${to}`, from, to, line, label };
}

View File

@ -99,6 +99,9 @@ export type { PlanPhase, PlanReceipt, PlanReceiptOptions } from "./plan-receipt.
export { createGovernanceContract, writeGovernanceContract } from "./governance-contract.js"; export { createGovernanceContract, writeGovernanceContract } from "./governance-contract.js";
export type { GovernanceContractOptions, GovernanceContractReceipt, GovernanceDecision } from "./governance-contract.js"; export type { GovernanceContractOptions, GovernanceContractReceipt, GovernanceDecision } from "./governance-contract.js";
export { createFableFlowManifest, writeFableFlowManifest } from "./fableflow-manifest.js";
export type { FableFlowEdge, FableFlowManifest, FableFlowNode } from "./fableflow-manifest.js";
export { createContextWorkspaceReceipt, writeContextWorkspaceReceipt } from "./context-workspace-receipt.js"; export { createContextWorkspaceReceipt, writeContextWorkspaceReceipt } from "./context-workspace-receipt.js";
export type { ContextLayer, ContextStageContract, ContextWorkspaceReceipt, ContextWorkspaceReceiptOptions } from "./context-workspace-receipt.js"; export type { ContextLayer, ContextStageContract, ContextWorkspaceReceipt, ContextWorkspaceReceiptOptions } from "./context-workspace-receipt.js";

View File

@ -2298,6 +2298,19 @@ fable
if (receipt.decision === "blocked") process.exit(1); if (receipt.decision === "blocked") process.exit(1);
}); });
fable
.command("fableflow")
.description("Generate a JamFlow-compatible FableFlow manifest from receipt-backed topology")
.option("--out <path>", "Manifest output path", path.join("TEMP", "fable-manifest.json"))
.action(async (opts: { out?: string }) => {
const { createFableFlowManifest, writeFableFlowManifest } = await import("./fable5/fableflow-manifest.js");
const manifest = createFableFlowManifest();
const out = opts.out ?? path.join("TEMP", "fable-manifest.json");
writeFableFlowManifest(out, manifest);
console.log(JSON.stringify(manifest, null, 2));
console.log("\n FableFlow manifest: " + out + "\n");
});
fable fable
.command("spec <task>") .command("spec <task>")
.description("Generate a six-section anti-hallucination spec with verifier gates") .description("Generate a six-section anti-hallucination spec with verifier gates")