feat: reweight defense story and presenter notes
This commit is contained in:
@@ -418,6 +418,7 @@ describe("useDemoTimeline", () => {
|
||||
|
||||
expect(result.current.state.phase).toBe("paused");
|
||||
expect(result.current.state.events[result.current.state.appliedCount - 1]?.stage).toBe("run_resume");
|
||||
expect(result.current.recordingId).toBe("lda-report-revision-v1");
|
||||
expect(result.current.output?.approved).toBe(false);
|
||||
expect(result.current.output?.created_issues).toHaveLength(0);
|
||||
expect(result.current.trace).toBeNull();
|
||||
|
||||
@@ -48,8 +48,10 @@ export type DemoTimelineController = {
|
||||
readonly primeReplayToStage: (stage: DemoEvent["stage"] | null) => void;
|
||||
};
|
||||
|
||||
const deriveRecordingId = (state: DemoTimelineState): string | null =>
|
||||
state.mode === "replay" ? "lda-report-success-v1" : null;
|
||||
const deriveRecordingId = (
|
||||
mode: DemoMode,
|
||||
activeRecording: DemoRecording | null,
|
||||
): string | null => mode === "replay" ? activeRecording?.recordingId ?? null : null;
|
||||
|
||||
const deriveMissingMessage = (mode: DemoMode, target: string | null): string | null => {
|
||||
if (mode !== "live" || target !== null) return null;
|
||||
@@ -349,7 +351,7 @@ export const useDemoTimeline = (
|
||||
output,
|
||||
trace,
|
||||
missingDeploymentMessage: deriveMissingMessage(state.mode, target),
|
||||
recordingId: deriveRecordingId(state),
|
||||
recordingId: deriveRecordingId(state.mode, activeRecording.current),
|
||||
canStart: state.mode === "replay" || target !== null,
|
||||
setMode,
|
||||
start,
|
||||
|
||||
@@ -122,6 +122,28 @@ describe("GuidedProductMoment", () => {
|
||||
expect(screen.getByLabelText("workflow.runs.resume operation")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("labels a replay revision as a separate prepared recording", () => {
|
||||
render(
|
||||
<GuidedProductMoment
|
||||
beat={findBeat("resume-output-evidence", "resume")!}
|
||||
demo={demoWithAppliedCount(6)}
|
||||
contract={contract}
|
||||
operation={resumeOperation}
|
||||
approvalActions={{
|
||||
state: "revision_requested",
|
||||
canSubmit: false,
|
||||
canRequestRevision: false,
|
||||
submit: vi.fn(async () => {}),
|
||||
requestRevision: vi.fn(async () => {}),
|
||||
}}
|
||||
openEvidence={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByText(/separate prepared recording/i)).toBeInTheDocument();
|
||||
expect(screen.queryByText(/same run/i)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps approval focused on input and decision without pre-resume output", () => {
|
||||
render(
|
||||
<GuidedProductMoment
|
||||
|
||||
@@ -38,13 +38,16 @@ const momentForBeat = (beatId: string): GuidedProductMoment => {
|
||||
const statusCopy = (
|
||||
moment: ReturnType<typeof momentForBeat>,
|
||||
approvalActions?: DemoApprovalActions,
|
||||
isReplay = false,
|
||||
): string => {
|
||||
if (moment === "interrupt") return "Run paused at the typed interrupt; inspect the context before deciding.";
|
||||
if (approvalActions?.state === "revision_requested") {
|
||||
return isReplay
|
||||
? "Revision requested. Separate prepared recording; no run-ID continuity is claimed."
|
||||
: "Revision requested. The live run resumes through its negative outcome branch.";
|
||||
}
|
||||
if (moment !== "approval") return "Same persisted run; inspect the proof below.";
|
||||
if (approvalActions?.state === "submitted") return "Submitted. Same run resumed.";
|
||||
if (approvalActions?.state === "revision_requested") {
|
||||
return "Revision requested. The same run resumed through its negative outcome branch.";
|
||||
}
|
||||
return "Run is paused. Submit resumes this same run.";
|
||||
};
|
||||
|
||||
@@ -76,8 +79,18 @@ export const GuidedProductMoment = ({
|
||||
const lens = demoBeatLensForBeat(beat.id);
|
||||
const facts = projectDemoRunFacts(demo);
|
||||
const runResume = demo.state.events.find((event) => event.stage === "run_resume");
|
||||
const headline = moment === "resume" && approvalActions?.state === "revision_requested"
|
||||
? "The revision request continues the persisted run"
|
||||
const revisionRequested = approvalActions?.state === "revision_requested";
|
||||
// Replay revision evidence has a separate recording identity; do not let
|
||||
// the normal submitted-branch lens imply continuity for that branch.
|
||||
const eyebrow = revisionRequested
|
||||
? demo.state.mode === "replay"
|
||||
? "Prepared branch"
|
||||
: "Live branch"
|
||||
: lens.eyebrow;
|
||||
const headline = revisionRequested
|
||||
? demo.state.mode === "replay"
|
||||
? "Separate prepared revision recording"
|
||||
: "Live revision branch resumes the run"
|
||||
: lens.headline;
|
||||
|
||||
return (
|
||||
@@ -91,9 +104,9 @@ export const GuidedProductMoment = ({
|
||||
data-continuation-focus={moment === "resume" ? "output" : moment === "trace" ? "trace" : undefined}
|
||||
>
|
||||
<header className="guided-product-moment__header">
|
||||
<span>{lens.eyebrow}</span>
|
||||
<span>{eyebrow}</span>
|
||||
<strong>{headline}</strong>
|
||||
<p>{statusCopy(moment, approvalActions)}</p>
|
||||
<p>{statusCopy(moment, approvalActions, demo.state.mode === "replay")}</p>
|
||||
</header>
|
||||
|
||||
<div className="guided-product-moment__primary">
|
||||
|
||||
@@ -615,7 +615,7 @@ describe("PresentationRoute", () => {
|
||||
|
||||
expect(window.location.hash).toBe("#scene/resume-output-evidence/resume");
|
||||
expect(screen.getByLabelText("workflow.runs.resume operation")).toBeInTheDocument();
|
||||
expect(screen.getByText(/Revision Requested/i)).toBeInTheDocument();
|
||||
expect(screen.getByText(/separate prepared recording; no run-ID continuity is claimed/i)).toBeInTheDocument();
|
||||
|
||||
window.location.hash = "#scene/resume-output-evidence/trace";
|
||||
window.dispatchEvent(new HashChangeEvent("hashchange"));
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { mainScenes } from "../storyboard.js";
|
||||
import {
|
||||
completeDeckTargetSeconds,
|
||||
discussionBranchForId,
|
||||
mainSpeechWordCount,
|
||||
presenterBeatNoteFor,
|
||||
presenterNotes,
|
||||
presenterSceneNotes,
|
||||
} from "./presenter-notes.js";
|
||||
|
||||
describe("presenter note catalog", () => {
|
||||
it("has exactly one note for every current storyboard beat", () => {
|
||||
const noteKeys = presenterNotes.map((note) => `${note.sceneId}/${note.beatId}`);
|
||||
|
||||
expect(new Set(noteKeys).size).toBe(noteKeys.length);
|
||||
for (const scene of mainScenes) {
|
||||
for (const beat of scene.beats) {
|
||||
const note = presenterBeatNoteFor(scene.id, beat.id);
|
||||
expect(note).toBeDefined();
|
||||
expect(note?.mustSay.trim().length).toBeGreaterThan(0);
|
||||
}
|
||||
}
|
||||
|
||||
expect(noteKeys).toHaveLength(mainScenes.flatMap((scene) => scene.beats).length);
|
||||
});
|
||||
|
||||
it("keeps the planned scene timing and complete-deck cap", () => {
|
||||
expect(mainScenes.map((scene) => presenterSceneNotes(scene.id).reduce((sum, note) => sum + note.targetSeconds, 0))).toEqual([
|
||||
45,
|
||||
45,
|
||||
45,
|
||||
55,
|
||||
45,
|
||||
55,
|
||||
40,
|
||||
20,
|
||||
45,
|
||||
35,
|
||||
30,
|
||||
50,
|
||||
120,
|
||||
75,
|
||||
]);
|
||||
expect(completeDeckTargetSeconds()).toBe(780);
|
||||
expect(completeDeckTargetSeconds()).toBeLessThanOrEqual(780);
|
||||
});
|
||||
|
||||
it("keeps the must-say speech within the defense word budget", () => {
|
||||
expect(mainSpeechWordCount()).toBeGreaterThanOrEqual(750);
|
||||
expect(mainSpeechWordCount()).toBeLessThanOrEqual(850);
|
||||
});
|
||||
|
||||
it("keeps the readable speech runbook synchronized with every must-say note", () => {
|
||||
const speech = readFileSync(
|
||||
join(import.meta.dirname, "../../../../../../docs/runbooks/defense-speech-and-claim-audit.md"),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
for (const note of presenterNotes) {
|
||||
expect(speech, `${note.sceneId}/${note.beatId}`).toContain(note.mustSay);
|
||||
}
|
||||
});
|
||||
|
||||
it("requires evidence and resolvable Q&A links without empty placeholders", () => {
|
||||
for (const note of presenterNotes) {
|
||||
expect(note.evidencePointers.length).toBeGreaterThan(0);
|
||||
for (const pointer of note.evidencePointers) {
|
||||
expect(pointer.trim().length).toBeGreaterThan(0);
|
||||
}
|
||||
for (const branchId of note.qnaBranchIds) {
|
||||
expect(discussionBranchForId(branchId)).toBeDefined();
|
||||
}
|
||||
for (const optionalField of [note.optionalDetail, note.warning, note.fallback]) {
|
||||
expect(optionalField === null || optionalField.trim().length > 0).toBe(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,418 @@
|
||||
import {
|
||||
findDiscussionBranch,
|
||||
mainScenes,
|
||||
type DiscussionBranchDefinition,
|
||||
type DiscussionBranchId,
|
||||
type MainSceneId,
|
||||
} from "../storyboard.js";
|
||||
|
||||
export type PresenterBeatNote = {
|
||||
readonly sceneId: MainSceneId;
|
||||
readonly beatId: string;
|
||||
readonly targetSeconds: number;
|
||||
readonly mustSay: string;
|
||||
readonly optionalDetail: string | null;
|
||||
readonly warning: string | null;
|
||||
readonly fallback: string | null;
|
||||
readonly evidencePointers: readonly [string, ...string[]];
|
||||
readonly qnaBranchIds: readonly DiscussionBranchId[];
|
||||
};
|
||||
|
||||
type PresenterBeatNoteOptions = {
|
||||
readonly optionalDetail?: string | null;
|
||||
readonly warning?: string | null;
|
||||
readonly fallback?: string | null;
|
||||
readonly qnaBranchIds?: readonly DiscussionBranchId[];
|
||||
};
|
||||
|
||||
const beatNote = <const EvidencePointers extends readonly [string, ...string[]]>(
|
||||
sceneId: MainSceneId,
|
||||
beatId: string,
|
||||
targetSeconds: number,
|
||||
mustSay: string,
|
||||
evidencePointers: EvidencePointers,
|
||||
options: PresenterBeatNoteOptions = {},
|
||||
): PresenterBeatNote => ({
|
||||
sceneId,
|
||||
beatId,
|
||||
targetSeconds,
|
||||
mustSay,
|
||||
optionalDetail: options.optionalDetail ?? null,
|
||||
warning: options.warning ?? null,
|
||||
fallback: options.fallback ?? null,
|
||||
evidencePointers,
|
||||
qnaBranchIds: options.qnaBranchIds ?? [],
|
||||
});
|
||||
|
||||
/**
|
||||
* Presenter-only speech and evidence source. The audience route remains in
|
||||
* storyboard.ts; this catalog is deliberately not rendered by scene content.
|
||||
*/
|
||||
export const presenterNotes = [
|
||||
beatNote(
|
||||
"thesis",
|
||||
"title",
|
||||
22,
|
||||
"This project began with the goal in the title: an AI agent for creating and automating workspace workflows. The difficult engineering problem became the system underneath the chat.",
|
||||
["Thesis Abstract and Introduction"],
|
||||
{
|
||||
warning: "Do not present the submitted system as a bundled autonomous planner.",
|
||||
qnaBranchIds: ["where-is-ai-agent", "title-ai-agent-wording"],
|
||||
},
|
||||
),
|
||||
beatNote(
|
||||
"thesis",
|
||||
"substrate",
|
||||
23,
|
||||
"The submitted contribution is a typed workflow substrate: an external planner can propose work while the platform owns definitions, validation, bindings, execution records, traces, and explicit resume boundaries.",
|
||||
["Thesis Abstract and Introduction", "Thesis Contributions"],
|
||||
{ qnaBranchIds: ["where-is-ai-agent", "not-just-cli"] },
|
||||
),
|
||||
beatNote(
|
||||
"problem",
|
||||
"direct-actions",
|
||||
22,
|
||||
"A model can call tools and complete one task, but a tool transcript is not reusable automation.",
|
||||
["Thesis Problem Statement and Requirements"],
|
||||
{ qnaBranchIds: ["direct-orchestration", "not-just-scripts"] },
|
||||
),
|
||||
beatNote(
|
||||
"problem",
|
||||
"missing-contracts",
|
||||
23,
|
||||
"Reuse needs schemas, source bindings, persistence, traces, and declared recovery boundaries, with planning kept separate from execution.",
|
||||
["Thesis Problem Statement and Requirements"],
|
||||
{ qnaBranchIds: ["why-schemas", "run-persistence"] },
|
||||
),
|
||||
beatNote(
|
||||
"positioning",
|
||||
"landscape",
|
||||
22,
|
||||
"Related systems have different centers of gravity: tool loops act now, scripts package code, hosted platforms operate workflows, agent graphs organize planners, and MCP exposes capabilities.",
|
||||
["Thesis Positioning and Related Systems"],
|
||||
{ qnaBranchIds: ["direct-orchestration", "generated-scripts", "hosted-automation", "durable-agent-graphs", "mcp-agent-scale"] },
|
||||
),
|
||||
beatNote(
|
||||
"positioning",
|
||||
"lda-position",
|
||||
23,
|
||||
"lda.chat takes a narrower position: a typed, provider-neutral lifecycle for workflows authored or operated by external agents, not a replacement or superiority claim.",
|
||||
["Thesis Positioning and Related Systems", "Thesis Source Model"],
|
||||
{ warning: "Provider neutrality is demonstrated for the implemented source families, not arbitrary future providers.", qnaBranchIds: ["not-just-scripts"] },
|
||||
),
|
||||
beatNote(
|
||||
"planner-runtime",
|
||||
"planner",
|
||||
18,
|
||||
"An external model or human proposes and revises workflow structure; this keeps planning outside the runtime.",
|
||||
["Thesis Architecture Overview"],
|
||||
{ qnaBranchIds: ["where-is-ai-agent", "not-just-cli"] },
|
||||
),
|
||||
beatNote(
|
||||
"planner-runtime",
|
||||
"runtime",
|
||||
18,
|
||||
"For fixed definitions and handler results, the runtime validates the graph, resolves sources, executes steps, records state and traces, and resumes only at declared boundaries.",
|
||||
["Thesis Workflow Core", "Thesis Architecture Overview"],
|
||||
{ warning: "Qualify determinism; provider code, resource reads, and external side effects can vary.", qnaBranchIds: ["run-persistence", "typed-interrupts"] },
|
||||
),
|
||||
beatNote(
|
||||
"planner-runtime",
|
||||
"boundary",
|
||||
19,
|
||||
"Typed CLI and JSON-RPC operations reach the same Workflow API, making schemas, diagnostics, and lifecycle state machine-readable without importing runtime internals.",
|
||||
["Thesis Architecture Overview", "docs/source_architecture.md"],
|
||||
{ qnaBranchIds: ["not-just-cli"] },
|
||||
),
|
||||
beatNote(
|
||||
"lifecycle",
|
||||
"draft",
|
||||
11,
|
||||
"Draft is mutable authoring state.",
|
||||
["Thesis Workflow Lifecycle"],
|
||||
{ optionalDetail: "Raw plans can also create artifacts without passing through a Draft." },
|
||||
),
|
||||
beatNote(
|
||||
"lifecycle",
|
||||
"artifact",
|
||||
11,
|
||||
"Artifact is an immutable workflow definition.",
|
||||
["Thesis Workflow Lifecycle"],
|
||||
),
|
||||
beatNote(
|
||||
"lifecycle",
|
||||
"deployment",
|
||||
11,
|
||||
"Deployment binds an artifact version to concrete sources and runtime context.",
|
||||
["Thesis Workflow Lifecycle"],
|
||||
),
|
||||
beatNote(
|
||||
"lifecycle",
|
||||
"run",
|
||||
12,
|
||||
"Run records one execution, including status, diagnostics, output, trace, and an explicit stopped or interrupted state.",
|
||||
["Thesis Workflow Lifecycle"],
|
||||
{ qnaBranchIds: ["lifecycle-states", "run-persistence"] },
|
||||
),
|
||||
beatNote(
|
||||
"architecture",
|
||||
"client",
|
||||
13,
|
||||
"Human and agent clients use the same public lifecycle operations.",
|
||||
["Thesis System Architecture", "docs/project_map.md"],
|
||||
),
|
||||
beatNote(
|
||||
"architecture",
|
||||
"api",
|
||||
13,
|
||||
"JSON-RPC handles transport concerns and delegates to WorkflowApi rather than owning domain behavior.",
|
||||
["Thesis System Architecture", "docs/source_architecture.md"],
|
||||
{ qnaBranchIds: ["not-just-cli"] },
|
||||
),
|
||||
beatNote(
|
||||
"architecture",
|
||||
"runtime",
|
||||
14,
|
||||
"Server composition supplies stores, provider projections, and the runtime while the core remains independent of MCP and Python behavior.",
|
||||
["Thesis System Architecture", "docs/source_architecture.md"],
|
||||
{ qnaBranchIds: ["provider-security"] },
|
||||
),
|
||||
beatNote(
|
||||
"architecture",
|
||||
"node-use",
|
||||
15,
|
||||
"A NodeUse validates input, invokes a projected capability, checks its declared outcome, reduces output into state, appends a trace frame, and routes to the next edge.",
|
||||
["Thesis Workflow Core Model"],
|
||||
),
|
||||
beatNote(
|
||||
"authoring",
|
||||
"discover",
|
||||
10,
|
||||
"Before authoring, a client can discover sources, capabilities, and schemas instead of guessing at hidden interfaces.",
|
||||
["CLI documentation", "Draft authoring API", "Challenge UX findings"],
|
||||
{ qnaBranchIds: ["why-schemas", "validation-diagnostics"] },
|
||||
),
|
||||
beatNote(
|
||||
"authoring",
|
||||
"author",
|
||||
10,
|
||||
"Focused operations let an external agent change a mutable Draft while preserving a clear lifecycle boundary.",
|
||||
["CLI documentation", "Draft authoring API"],
|
||||
{ qnaBranchIds: ["raw-plan-import"] },
|
||||
),
|
||||
beatNote(
|
||||
"authoring",
|
||||
"diagnose",
|
||||
10,
|
||||
"Validation returns structured diagnostics, affected paths, repair hints, and suggested next actions.",
|
||||
["Validation and diagnostics", "Challenge UX findings"],
|
||||
{ qnaBranchIds: ["validation-diagnostics"] },
|
||||
),
|
||||
beatNote(
|
||||
"authoring",
|
||||
"repair",
|
||||
10,
|
||||
"These surfaces make invalid intermediate drafts repairable; they support a loop, but no hint guarantees success.",
|
||||
["Validation and diagnostics", "Challenge UX findings"],
|
||||
{ warning: "Do not promise that diagnostics automatically repair every workflow.", qnaBranchIds: ["validation-diagnostics"] },
|
||||
),
|
||||
beatNote(
|
||||
"agent-handoff",
|
||||
"request",
|
||||
20,
|
||||
"I will now show a prepared demonstration built on this platform. The chat is a presentation interface, not the autonomous planner evaluated by the thesis. The chat translates a report request into the same public lifecycle operations an external agent could call. This prepared path demonstrates product behavior and recorded evidence, not a fresh model-performance result.",
|
||||
["Constrained demo agent and prepared replay recipe"],
|
||||
{
|
||||
fallback: "This is the reviewed recording, not a live model planning this workflow.",
|
||||
qnaBranchIds: ["prepared-replay-boundary", "demo-reliability"],
|
||||
},
|
||||
),
|
||||
beatNote(
|
||||
"prepared-lifecycle",
|
||||
"discover",
|
||||
9,
|
||||
"The later issue-review example first inspects configured local.lda_docs, report, and issue-board capabilities.",
|
||||
["examples/lda_report_workflow", "deployment inspect replay evidence"],
|
||||
{ qnaBranchIds: ["prepared-replay-boundary"] },
|
||||
),
|
||||
beatNote(
|
||||
"prepared-lifecycle",
|
||||
"draft",
|
||||
9,
|
||||
"It creates and edits a Draft for report generation, making the proposal visible before execution.",
|
||||
["examples/lda_report_workflow", "deployment inspect replay evidence"],
|
||||
),
|
||||
beatNote(
|
||||
"prepared-lifecycle",
|
||||
"validate",
|
||||
9,
|
||||
"It validates incomplete state, exposes a missing output binding, and applies a targeted repair.",
|
||||
["examples/lda_report_workflow", "deployment inspect replay evidence"],
|
||||
{ qnaBranchIds: ["validation-diagnostics"] },
|
||||
),
|
||||
beatNote(
|
||||
"prepared-lifecycle",
|
||||
"artifact",
|
||||
9,
|
||||
"It saves the validated plan as immutable artifact lda_report_case_study version 1.",
|
||||
["examples/lda_report_workflow", "deployment inspect replay evidence"],
|
||||
),
|
||||
beatNote(
|
||||
"prepared-lifecycle",
|
||||
"deployment",
|
||||
9,
|
||||
"This later issue-review example is richer than the thesis three-node deterministic report case study: it is an implementation extension built on the same platform. Deployment binds and validates a ready configuration but does not run the workflow.",
|
||||
["examples/lda_report_workflow", "deployment inspect replay evidence", "Thesis deterministic report case study"],
|
||||
{ warning: "Do not present issue-board output from this later example as the thesis case study.", qnaBranchIds: ["prepared-replay-boundary"] },
|
||||
),
|
||||
beatNote(
|
||||
"run-from-deployment",
|
||||
"input",
|
||||
11,
|
||||
"The deployment receives selected local documents and an issue-board path.",
|
||||
["workflow.runs.start replay evidence"],
|
||||
),
|
||||
beatNote(
|
||||
"run-from-deployment",
|
||||
"operation",
|
||||
12,
|
||||
"The public workflow.runs.start operation validates the deployment and input, creates a persisted Run, and begins the reusable graph.",
|
||||
["workflow.runs.start replay evidence"],
|
||||
{ qnaBranchIds: ["run-persistence"] },
|
||||
),
|
||||
beatNote(
|
||||
"run-from-deployment",
|
||||
"graph",
|
||||
12,
|
||||
"The graph reads documents, analyzes them, builds a report, drafts proposed issues, and pauses at a declared review interrupt before issue-board changes.",
|
||||
["workflow.runs.start replay evidence", "examples/lda_report_workflow"],
|
||||
{ fallback: "The operation view is replay-backed evidence of the prepared path, not a newly completed live run." },
|
||||
),
|
||||
beatNote(
|
||||
"typed-human-boundary",
|
||||
"interrupt",
|
||||
15,
|
||||
"Execution pauses at a typed issue_review interrupt exposing request data, allowed outcomes, request schema, and resume schema.",
|
||||
["Typed interrupt payload and resume contract"],
|
||||
{ qnaBranchIds: ["typed-interrupts", "why-schemas"] },
|
||||
),
|
||||
beatNote(
|
||||
"typed-human-boundary",
|
||||
"approval",
|
||||
15,
|
||||
"The operator chooses submitted or revision-requested; this is a typed interrupt and resume contract, not a production approval gate, role system, or policy engine.",
|
||||
["Typed interrupt payload and resume contract"],
|
||||
{ warning: "Both outcomes resume through declared workflow branches; this is not production approval governance.", qnaBranchIds: ["typed-interrupts", "security-production-boundary"] },
|
||||
),
|
||||
beatNote(
|
||||
"resume-output-evidence",
|
||||
"resume",
|
||||
16,
|
||||
"On the submitted path, workflow.runs.resume continues the recorded interrupted Run.",
|
||||
["workflow.runs.resume replay evidence", "Revision replay identity"],
|
||||
{ fallback: "The submitted replay demonstrates same-run continuation; the revision branch is recorded separately.", qnaBranchIds: ["replay-provenance", "prepared-replay-boundary"] },
|
||||
),
|
||||
beatNote(
|
||||
"resume-output-evidence",
|
||||
"output",
|
||||
16,
|
||||
"The workflow creates the report and issue-board changes, then records terminal output.",
|
||||
["workflow.runs.resume replay evidence", "examples/lda_report_workflow"],
|
||||
{ warning: "Identify these issue-board changes as later example evidence, not output from the thesis three-node case study." },
|
||||
),
|
||||
beatNote(
|
||||
"resume-output-evidence",
|
||||
"trace",
|
||||
18,
|
||||
"Trace frames and protocol evidence remain inspectable; this is declared-boundary resumability, not arbitrary crash recovery or exactly-once execution. The revision replay is a separate prepared recording.",
|
||||
["workflow.runs.resume replay evidence", "Revision replay identity"],
|
||||
{ warning: "Never claim run-ID continuity for the prepared revision recording.", qnaBranchIds: ["replay-provenance", "demo-reliability"] },
|
||||
),
|
||||
beatNote(
|
||||
"evaluation",
|
||||
"cohort",
|
||||
40,
|
||||
"The evaluation combines conformance tests, deterministic case studies, and a manually audited external-agent campaign: 36 trials across two challenges, two hosted models, three instruction profiles, and three waves, with three attempts per cell.",
|
||||
["Thesis Evaluation and Appendix C"],
|
||||
{ qnaBranchIds: ["evaluation-validity"] },
|
||||
),
|
||||
beatNote(
|
||||
"evaluation",
|
||||
"validity",
|
||||
40,
|
||||
"The author audit classified 27 trials as clean product-path passes, eight as invalid samples, and one as a failure. Invalid samples included contamination such as reading implementation files, prior artifacts, adjacent attempts, or evaluator state.",
|
||||
["Thesis Evaluation and Appendix C", "Author audit"],
|
||||
{ qnaBranchIds: ["evaluation-validity"] },
|
||||
),
|
||||
beatNote(
|
||||
"evaluation",
|
||||
"findings",
|
||||
40,
|
||||
"Because prompts, product snapshots, and hosted conditions changed across waves, these results are longitudinal engineering evidence. They expose authoring and diagnostic gaps, not a benchmark of model success, token reduction, retry reduction, or superiority.",
|
||||
["Thesis Evaluation and Appendix C", "Thesis Threats to Validity"],
|
||||
{ warning: "Use non-benchmark wording; do not report the counts as general model performance.", qnaBranchIds: ["evaluation-validity"] },
|
||||
),
|
||||
beatNote(
|
||||
"conclusion",
|
||||
"limits",
|
||||
18,
|
||||
"The prototype uses trusted in-process Python and file-backed stores; it does not provide production authentication, RBAC, sandboxing, scheduling, arbitrary crash recovery, or a bundled autonomous planner.",
|
||||
["Thesis Limitations"],
|
||||
{ qnaBranchIds: ["security-production-boundary", "production-readiness"] },
|
||||
),
|
||||
beatNote(
|
||||
"conclusion",
|
||||
"future",
|
||||
18,
|
||||
"A live agent interface, transactional storage, richer debugging, security hardening, scheduling, and controlled comparative evaluation remain future work.",
|
||||
["Thesis Future Work"],
|
||||
{ qnaBranchIds: ["production-readiness"] },
|
||||
),
|
||||
beatNote(
|
||||
"conclusion",
|
||||
"conclusion",
|
||||
20,
|
||||
"The contribution is architectural and implemented: external planners can propose workflows while a typed platform validates, binds, executes, persists, interrupts, resumes, and inspects them through public operations.",
|
||||
["Thesis Contributions", "Thesis Conclusion"],
|
||||
{ qnaBranchIds: ["where-is-ai-agent", "not-just-cli"] },
|
||||
),
|
||||
beatNote(
|
||||
"conclusion",
|
||||
"questions",
|
||||
19,
|
||||
"That boundary is the claim I will defend: reusable agent-operated automation is inspectable because planning and execution have explicit contracts. I welcome questions.",
|
||||
["Thesis Conclusion", "Defense Q&A index"],
|
||||
{ qnaBranchIds: ["where-is-ai-agent", "evaluation-validity", "production-readiness"] },
|
||||
),
|
||||
] satisfies readonly PresenterBeatNote[];
|
||||
|
||||
type PresenterRouteKey = `${MainSceneId}/${string}`;
|
||||
|
||||
const presenterNotesByRoute = new Map<PresenterRouteKey, PresenterBeatNote>(
|
||||
presenterNotes.map((note) => [`${note.sceneId}/${note.beatId}` as PresenterRouteKey, note]),
|
||||
);
|
||||
|
||||
/** Resolve a presenter note without giving audience components access to the catalog internals. */
|
||||
export const presenterBeatNoteFor = (sceneId: MainSceneId, beatId: string): PresenterBeatNote | undefined =>
|
||||
presenterNotesByRoute.get(`${sceneId}/${beatId}`);
|
||||
|
||||
export const presenterSceneNotes = (sceneId: MainSceneId): readonly PresenterBeatNote[] =>
|
||||
presenterNotes.filter((note) => note.sceneId === sceneId);
|
||||
|
||||
/** Keep the Q&A lookup in one place so note links cannot silently drift from storyboard branches. */
|
||||
export const discussionBranchForId = (branchId: DiscussionBranchId): DiscussionBranchDefinition | undefined =>
|
||||
findDiscussionBranch(branchId);
|
||||
|
||||
const NAVIGATION_BUFFER_SECONDS = 75;
|
||||
|
||||
export const completeDeckTargetSeconds = (): number =>
|
||||
presenterNotes.reduce((total, note) => total + note.targetSeconds, 0) + NAVIGATION_BUFFER_SECONDS;
|
||||
|
||||
/**
|
||||
* Counts only must-say text. Inline punctuation and identifiers are split into
|
||||
* readable word units, while hyphenated terms and contractions stay together.
|
||||
*/
|
||||
export const mainSpeechWordCount = (): number => {
|
||||
const wordPattern = /\b[\p{L}\p{N}]+(?:['-][\p{L}\p{N}]+)*\b/gu;
|
||||
return presenterNotes.reduce((total, note) => total + (note.mustSay.match(wordPattern)?.length ?? 0), 0);
|
||||
};
|
||||
@@ -79,6 +79,22 @@ describe("defense storyboard catalog", () => {
|
||||
expect(findBeat("prepared-lifecycle", "deployment")?.caption).toMatch(/deploy|bindings/i);
|
||||
});
|
||||
|
||||
it("keeps Scene 5 as vocabulary and moves applied lifecycle evidence to Scene 9", () => {
|
||||
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", "validate")?.caption).toMatch(/incomplete|repair/i);
|
||||
expect(findBeat("prepared-lifecycle", "deployment")?.caption).toMatch(/ready|does not run|Scene 10/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);
|
||||
expect(findBeat("evaluation", "validity")?.caption).toMatch(/27|8|1|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();
|
||||
|
||||
@@ -118,10 +118,10 @@ export const mainScenes = defineScenes([
|
||||
evidencePointer: "Thesis Workflow Lifecycle",
|
||||
view: "lifecycle",
|
||||
beats: [
|
||||
sceneBeat("draft", "Draft", "Draft, Artifact, Deployment, and Run are the durable vocabulary behind reusable automation."),
|
||||
sceneBeat("artifact", "Artifact", "An immutable artifact carries the durable workflow definition forward."),
|
||||
sceneBeat("deployment", "Deployment", "A deployment binds the durable workflow to concrete sources."),
|
||||
sceneBeat("run", "Run", "A run records durable execution, output, status, and trace."),
|
||||
sceneBeat("draft", "Draft", "Draft is mutable authoring state."),
|
||||
sceneBeat("artifact", "Artifact", "Artifact is an immutable workflow definition."),
|
||||
sceneBeat("deployment", "Deployment", "Deployment binds an artifact version to concrete sources."),
|
||||
sceneBeat("run", "Run", "Run records one execution, including output, status, and trace."),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -172,11 +172,11 @@ export const mainScenes = defineScenes([
|
||||
evidencePointer: "examples/lda_report_workflow; deployment inspect replay evidence",
|
||||
view: "demo-lifecycle",
|
||||
beats: [
|
||||
sceneBeat("discover", "Discover capabilities", "Inspect available sources, capabilities, and schemas before authoring.", { chatMode: "hidden", chatTheme: "light" }),
|
||||
sceneBeat("draft", "Author draft", "Create a workflow draft with report generation steps and routes.", { chatMode: "hidden", chatTheme: "light" }),
|
||||
sceneBeat("validate", "Validate and repair", "Bind sources and validate the draft; diagnose and repair issues.", { chatMode: "hidden", chatTheme: "light" }),
|
||||
sceneBeat("artifact", "Compile artifact", "Compile the validated draft into an immutable artifact.", { chatMode: "hidden", chatTheme: "light" }),
|
||||
sceneBeat("deployment", "Deploy and validate", "Save deployment bindings and validate readiness.", { chatMode: "hidden", chatTheme: "light" }),
|
||||
sceneBeat("discover", "Discover capabilities", "The later issue-review example inspects configured sources, capabilities, and schemas.", { chatMode: "hidden", chatTheme: "light" }),
|
||||
sceneBeat("draft", "Author draft", "Create and edit a Draft for report generation before execution.", { chatMode: "hidden", chatTheme: "light" }),
|
||||
sceneBeat("validate", "Validate and repair", "Validate incomplete state, diagnose a missing binding, and apply a targeted repair.", { chatMode: "hidden", chatTheme: "light" }),
|
||||
sceneBeat("artifact", "Compile artifact", "Save the validated plan as an immutable artifact.", { chatMode: "hidden", chatTheme: "light" }),
|
||||
sceneBeat("deployment", "Deploy and validate", "Bind and validate a ready Deployment; the run begins in Scene 10. This implementation extension is richer than the thesis three-node case study.", { chatMode: "hidden", chatTheme: "light" }),
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -231,8 +231,8 @@ export const mainScenes = defineScenes([
|
||||
view: "evaluation",
|
||||
beats: [
|
||||
sceneBeat("cohort", "36-trial cohort", "Two challenges, two hosted models, three profiles, and three waves.", { chatMode: "hidden" }),
|
||||
sceneBeat("validity", "Bounded validity", "Manual audit separates task completion from valid product-surface evidence.", { chatMode: "hidden" }),
|
||||
sceneBeat("findings", "Longitudinal findings", "Trials exposed concrete authoring and diagnostic UX gaps.", { chatMode: "hidden" }),
|
||||
sceneBeat("validity", "Bounded validity", "Author audit: 27 pass, 8 invalid, and 1 fail; task completion is separate from valid product-surface evidence.", { chatMode: "hidden" }),
|
||||
sceneBeat("findings", "Longitudinal findings", "Changing prompts and product snapshots make this engineering evidence, not a model benchmark.", { chatMode: "hidden" }),
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user