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>`
|
- `--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>`
|
||||||
|
|
|
||||||
|
|
@ -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 { 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";
|
||||||
|
|
||||||
|
|
|
||||||
13
src/index.ts
13
src/index.ts
|
|
@ -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")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue