210 lines
9.4 KiB
TypeScript
210 lines
9.4 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import {
|
|
defaultMainLocation,
|
|
discussionBranches,
|
|
findBeat,
|
|
findScene,
|
|
mainScenes,
|
|
type DiscussionBranchDefinition,
|
|
} from "./storyboard.js";
|
|
|
|
describe("defense storyboard catalog", () => {
|
|
it("defines thirteen ordered main scenes with unique scene and beat ids", () => {
|
|
expect(mainScenes).toHaveLength(13);
|
|
expect(mainScenes.map((scene) => scene.id)).toEqual([
|
|
"thesis",
|
|
"problem",
|
|
"positioning",
|
|
"planner-runtime",
|
|
"lifecycle",
|
|
"architecture",
|
|
"agent-handoff",
|
|
"prepared-lifecycle",
|
|
"run-from-deployment",
|
|
"typed-human-boundary",
|
|
"resume-output-evidence",
|
|
"evaluation",
|
|
"conclusion",
|
|
]);
|
|
expect(findScene("architecture")?.beats.map((beat) => beat.id)).toEqual([
|
|
"overview",
|
|
"client",
|
|
"api",
|
|
"runtime",
|
|
]);
|
|
expect(findScene("prepared-lifecycle")?.beats.map((beat) => beat.id)).toEqual([
|
|
"discover",
|
|
"draft",
|
|
"diagnose",
|
|
"repair",
|
|
"artifact",
|
|
"deployment",
|
|
]);
|
|
expect(findScene("conclusion")?.number).toBe(13);
|
|
expect(findScene("authoring")).toBeUndefined();
|
|
for (const scene of mainScenes) {
|
|
expect(scene.beats.length).toBeGreaterThan(0);
|
|
expect(new Set(scene.beats.map((beat) => beat.id)).size).toBe(scene.beats.length);
|
|
expect(scene.claimClass.length).toBeGreaterThan(0);
|
|
expect(scene.evidencePointer.length).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
it("adds a hidden-chat questions beat after the conclusion beats", () => {
|
|
expect(findBeat("conclusion", "questions")).toBeDefined();
|
|
expect(findBeat("evaluation", "cohort")?.chatMode).toBe("hidden");
|
|
expect(findBeat("conclusion", "conclusion")?.chatMode).toBe("hidden");
|
|
expect(findBeat("conclusion", "questions")?.chatMode).toBe("hidden");
|
|
});
|
|
|
|
it("uses one editorial canvas theme and independent chat composition", () => {
|
|
expect(findScene("agent-handoff")?.beats).toHaveLength(1);
|
|
expect(findBeat("agent-handoff", "request")?.chatMode).toBe("hidden");
|
|
expect(findBeat("resume-output-evidence", "trace")?.chatMode).toBe("hidden");
|
|
});
|
|
|
|
it("keeps chat out of the way during proof-heavy demo beats", () => {
|
|
expect(findBeat("prepared-lifecycle", "discover")?.chatMode).toBe("hidden");
|
|
expect(findBeat("prepared-lifecycle", "draft")?.chatMode).toBe("hidden");
|
|
expect(findBeat("prepared-lifecycle", "diagnose")?.chatMode).toBe("hidden");
|
|
expect(findBeat("prepared-lifecycle", "repair")?.chatMode).toBe("hidden");
|
|
expect(findBeat("prepared-lifecycle", "artifact")?.chatMode).toBe("hidden");
|
|
expect(findBeat("prepared-lifecycle", "deployment")?.chatMode).toBe("hidden");
|
|
expect(findBeat("run-from-deployment", "input")?.chatMode).toBe("hidden");
|
|
expect(findBeat("run-from-deployment", "graph")?.chatMode).toBe("hidden");
|
|
expect(findBeat("typed-human-boundary", "approval")?.chatMode).toBe("hidden");
|
|
expect(findBeat("resume-output-evidence", "resume")?.chatMode).toBe("hidden");
|
|
expect(findBeat("resume-output-evidence", "output")?.chatMode).toBe("hidden");
|
|
expect(findBeat("resume-output-evidence", "trace")?.chatMode).toBe("hidden");
|
|
});
|
|
|
|
it("splits the demo climax into lifecycle, run, interrupt, and evidence scenes", () => {
|
|
expect(findScene("agent-handoff")?.number).toBe(7);
|
|
expect(findScene("prepared-lifecycle")?.number).toBe(8);
|
|
expect(findScene("run-from-deployment")?.number).toBe(9);
|
|
expect(findScene("typed-human-boundary")?.number).toBe(10);
|
|
expect(findScene("resume-output-evidence")?.number).toBe(11);
|
|
expect(findScene("evaluation")?.number).toBe(12);
|
|
expect(findScene("conclusion")?.number).toBe(13);
|
|
});
|
|
|
|
it("defines the lifecycle story beats before run evidence", () => {
|
|
expect(findBeat("prepared-lifecycle", "discover")?.caption).toMatch(/sources|capabilities|schemas/i);
|
|
expect(findBeat("prepared-lifecycle", "draft")?.caption).toMatch(/draft/i);
|
|
expect(findBeat("prepared-lifecycle", "diagnose")?.caption).toBe(
|
|
"Validation returns a structured diagnostic because analyze has no route for its ok outcome.",
|
|
);
|
|
expect(findBeat("prepared-lifecycle", "repair")?.caption).toBe(
|
|
"One route edit sends analyze.ok to __end__; the follow-up validation is valid.",
|
|
);
|
|
expect(findBeat("prepared-lifecycle", "artifact")?.caption).toMatch(/compile|artifact/i);
|
|
expect(findBeat("prepared-lifecycle", "deployment")?.caption).toMatch(/deploy|bindings/i);
|
|
});
|
|
|
|
it("keeps Scene 5 as vocabulary and moves applied lifecycle evidence to Scene 8", () => {
|
|
expect(findBeat("lifecycle", "draft")?.caption).toMatch(/^Draft is mutable authoring state\.$/);
|
|
expect(findBeat("lifecycle", "artifact")?.caption).toMatch(/^Artifact is an immutable workflow definition\.$/);
|
|
expect(findBeat("lifecycle", "deployment")?.caption).toMatch(/^Deployment binds an artifact version/);
|
|
expect(findBeat("lifecycle", "run")?.caption).toMatch(/^Run records one execution/);
|
|
expect(findBeat("prepared-lifecycle", "diagnose")?.caption).toContain("no route for its ok outcome");
|
|
expect(findBeat("prepared-lifecycle", "repair")?.caption).toContain("analyze.ok to __end__");
|
|
expect(findBeat("prepared-lifecycle", "deployment")?.caption).toMatch(/ready|does not run|Scene 9/i);
|
|
expect(findBeat("prepared-lifecycle", "deployment")?.caption).toMatch(/three-node|implementation extension/i);
|
|
});
|
|
|
|
it("makes the evaluation beat carry the audited counts and validity boundary", () => {
|
|
expect(findBeat("evaluation", "cohort")?.caption).toMatch(/36|two challenges|two hosted models|three waves/i);
|
|
const validityCaption = findBeat("evaluation", "validity")?.caption ?? "";
|
|
expect(validityCaption).toMatch(/\b27\b/);
|
|
expect(validityCaption).toMatch(/\b8\b/);
|
|
expect(validityCaption).toMatch(/\b1\b/);
|
|
expect(validityCaption).toMatch(/audit/i);
|
|
expect(findBeat("evaluation", "findings")?.caption).toMatch(/changing|longitudinal|benchmark/i);
|
|
});
|
|
|
|
it("defines focused run, interrupt, and evidence beats", () => {
|
|
expect(findBeat("run-from-deployment", "input")).toBeDefined();
|
|
expect(findBeat("run-from-deployment", "operation")).toBeDefined();
|
|
expect(findBeat("run-from-deployment", "graph")).toBeDefined();
|
|
expect(findBeat("typed-human-boundary", "interrupt")).toBeDefined();
|
|
expect(findBeat("typed-human-boundary", "approval")).toBeDefined();
|
|
expect(findBeat("resume-output-evidence", "resume")).toBeDefined();
|
|
expect(findBeat("resume-output-evidence", "output")).toBeDefined();
|
|
expect(findBeat("resume-output-evidence", "trace")).toBeDefined();
|
|
});
|
|
|
|
it("compresses Scene 11 into interrupt context and one operator decision beat", () => {
|
|
expect(findScene("typed-human-boundary")?.beats.map((beat) => beat.id)).toEqual([
|
|
"interrupt",
|
|
"approval",
|
|
]);
|
|
expect(findBeat("typed-human-boundary", "interrupt")?.caption).toMatch(/boundary|interrupt/i);
|
|
expect(findBeat("typed-human-boundary", "approval")?.caption).toMatch(/decision|resume/i);
|
|
expect(findBeat("typed-human-boundary", "cancel")).toBeUndefined();
|
|
});
|
|
|
|
it("defines discussion branches across multiple scenes", () => {
|
|
expect(discussionBranches.length).toBeGreaterThanOrEqual(5);
|
|
const positioningBranches = discussionBranches.filter((b) => b.parentSceneId === "positioning");
|
|
expect(positioningBranches.map((b) => b.id)).toEqual([
|
|
"direct-orchestration",
|
|
"generated-scripts",
|
|
"hosted-automation",
|
|
"durable-agent-graphs",
|
|
"mcp-agent-scale",
|
|
"not-just-scripts",
|
|
]);
|
|
expect(
|
|
discussionBranches
|
|
.filter((branch) => branch.parentSceneId === "prepared-lifecycle")
|
|
.map((branch) => branch.id),
|
|
).toEqual(expect.arrayContaining([
|
|
"raw-plan-import",
|
|
"validation-diagnostics",
|
|
"why-schemas",
|
|
]));
|
|
for (const branch of discussionBranches) {
|
|
expect(branch.title.length).toBeGreaterThan(0);
|
|
expect(branch.summary.length).toBeGreaterThan(0);
|
|
expect(branch.evidencePointer.length).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
it("defines core defense Q&A branches for expected examiner questions", () => {
|
|
expect(discussionBranches.map((branch) => branch.id)).toEqual(
|
|
expect.arrayContaining([
|
|
"where-is-ai-agent",
|
|
"title-ai-agent-wording",
|
|
"not-just-cli",
|
|
"not-just-scripts",
|
|
"evaluation-validity",
|
|
"security-production-boundary",
|
|
"demo-reliability",
|
|
"prepared-replay-boundary",
|
|
"why-schemas",
|
|
"production-readiness",
|
|
]),
|
|
);
|
|
});
|
|
|
|
it("keeps defense Q&A branches speaker-ready", () => {
|
|
const branches: readonly DiscussionBranchDefinition[] = discussionBranches;
|
|
const qnaBranches = branches.filter((branch) => branch.question);
|
|
expect(qnaBranches.length).toBeGreaterThanOrEqual(10);
|
|
for (const branch of qnaBranches) {
|
|
expect(branch.question).toMatch(/\?$/);
|
|
expect(branch.shortAnswer).toBeDefined();
|
|
expect(branch.shortAnswer!.length).toBeGreaterThan(40);
|
|
expect(branch.shortAnswer!.length).toBeLessThan(360);
|
|
expect(branch.expandedAnswer).toBeDefined();
|
|
expect(branch.expandedAnswer!.length).toBeGreaterThan(80);
|
|
expect(branch.evidencePointer.length).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
it("exposes a valid default location", () => {
|
|
expect(defaultMainLocation).toEqual({ kind: "main", sceneId: "thesis", beatId: "title", focusPath: [] });
|
|
expect(findScene(defaultMainLocation.sceneId)?.number).toBe(1);
|
|
});
|
|
});
|