feat: add fableflow manifest generator
This commit is contained in:
parent
beb80490c8
commit
518a26fdd9
|
|
@ -360,6 +360,8 @@ fable-agent plinius godmode "improve explanation quality"
|
|||
- `--fallback <text>`
|
||||
- `--risk <risk>`
|
||||
- `--out <path>`
|
||||
- `fable5 fableflow`
|
||||
- `--out <path>`
|
||||
- `fable5 spec <task>`
|
||||
- `--repo <path>`
|
||||
- `--out <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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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 };
|
||||
}
|
||||
|
|
@ -99,6 +99,9 @@ export type { PlanPhase, PlanReceipt, PlanReceiptOptions } from "./plan-receipt.
|
|||
export { createGovernanceContract, writeGovernanceContract } 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 type { ContextLayer, ContextStageContract, ContextWorkspaceReceipt, ContextWorkspaceReceiptOptions } from "./context-workspace-receipt.js";
|
||||
|
||||
|
|
|
|||
13
src/index.ts
13
src/index.ts
|
|
@ -2298,6 +2298,19 @@ fable
|
|||
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
|
||||
.command("spec <task>")
|
||||
.description("Generate a six-section anti-hallucination spec with verifier gates")
|
||||
|
|
|
|||
Loading…
Reference in New Issue