fix: harden presentation demo fact projections

This commit is contained in:
lda
2026-07-09 22:00:49 +07:00 Verified
parent e8b5350b64
commit a85571d1cc
5 changed files with 207 additions and 106 deletions
@@ -90,7 +90,7 @@ describe("WorkflowGraphStage", () => {
selectedNodeId={null} selectedNodeId={null}
selectNode={vi.fn()} selectNode={vi.fn()}
variant="compact" variant="compact"
proof={{ runId: "run_recorded_lda_report", traceLabel: "5 workflow nodes", evidenceLabel: "JSON-RPC evidence" }} proof={{ runId: "run_recorded_lda_report", traceLabel: "9 workflow nodes", evidenceLabel: "JSON-RPC evidence" }}
/>, />,
); );
@@ -4,15 +4,15 @@ import { initialDemoTimelineState } from "../demo/timeline/reducer.js";
import type { DemoTimelineController } from "../demo/useDemoTimeline.js"; import type { DemoTimelineController } from "../demo/useDemoTimeline.js";
import { projectDemoLifecycleFacts } from "./demo-lifecycle-facts.js"; import { projectDemoLifecycleFacts } from "./demo-lifecycle-facts.js";
const controller = (): DemoTimelineController => { const controller = (eventMode: "recorded" | "empty" = "recorded"): DemoTimelineController => {
const recording = loadCanonicalDemoRecording(); const recording = loadCanonicalDemoRecording();
return { return {
state: { state: {
...initialDemoTimelineState, ...initialDemoTimelineState,
mode: "replay", mode: "replay",
phase: "paused", phase: "paused",
events: recording.events, events: eventMode === "recorded" ? recording.events : [],
appliedCount: recording.events.length, appliedCount: eventMode === "recorded" ? recording.events.length : 0,
autoplay: false, autoplay: false,
}, },
inFlight: false, inFlight: false,
@@ -55,4 +55,16 @@ describe("projectDemoLifecycleFacts", () => {
expect(facts.run.id).toBe("run_recorded_lda_report"); expect(facts.run.id).toBe("run_recorded_lda_report");
expect(facts.run.status).toBe("interrupted"); expect(facts.run.status).toBe("interrupted");
}); });
it("falls back honestly when replay evidence has not loaded yet", () => {
const facts = projectDemoLifecycleFacts(controller("empty"));
expect(facts.artifact).toEqual({ id: "unavailable", version: null });
expect(facts.deployment).toEqual({
id: "unavailable",
driftPolicy: "unavailable",
bindings: [],
});
expect(facts.run).toEqual({ id: null, status: "not started" });
});
}); });
@@ -1,4 +1,5 @@
import type { DemoTimelineController } from "../demo/useDemoTimeline.js"; import type { DemoTimelineController } from "../demo/useDemoTimeline.js";
import type { DemoEvent } from "../demo/timeline/models.js";
export type DemoLifecycleFacts = { export type DemoLifecycleFacts = {
readonly draft: { readonly draft: {
@@ -21,12 +22,25 @@ export type DemoLifecycleFacts = {
}; };
}; };
const deploymentInspect = (demo: DemoTimelineController) => const deploymentInspect = (demo: DemoTimelineController): DemoEvent | undefined =>
demo.state.events.find((event) => event.stage === "deployment_check"); demo.state.events.find((event) => event.stage === "deployment_check");
const runStart = (demo: DemoTimelineController) => const runStart = (demo: DemoTimelineController): DemoEvent | undefined =>
demo.state.events.find((event) => event.stage === "run_start"); demo.state.events.find((event) => event.stage === "run_start");
const isRecord = (value: unknown): value is Record<string, unknown> =>
typeof value === "object" && value !== null && !Array.isArray(value);
const stringField = (record: Record<string, unknown> | undefined, field: string): string | undefined => {
const value = record?.[field];
return typeof value === "string" ? value : undefined;
};
const numberField = (record: Record<string, unknown> | undefined, field: string): number | undefined => {
const value = record?.[field];
return typeof value === "number" ? value : undefined;
};
const readBindings = (value: unknown): ReadonlyArray<readonly [string, string]> => { const readBindings = (value: unknown): ReadonlyArray<readonly [string, string]> => {
if (!Array.isArray(value)) return []; if (!Array.isArray(value)) return [];
return value.flatMap((entry) => { return value.flatMap((entry) => {
@@ -42,19 +56,9 @@ const readBindings = (value: unknown): ReadonlyArray<readonly [string, string]>
*/ */
export const projectDemoLifecycleFacts = (demo: DemoTimelineController): DemoLifecycleFacts => { export const projectDemoLifecycleFacts = (demo: DemoTimelineController): DemoLifecycleFacts => {
const deployment = deploymentInspect(demo); const deployment = deploymentInspect(demo);
const deploymentInterpreted = deployment?.interpreted as const deploymentInterpreted = isRecord(deployment?.interpreted) ? deployment.interpreted : undefined;
| {
id?: string;
artifactId?: string;
artifactVersion?: number;
driftPolicy?: string;
bindings?: unknown;
}
| undefined;
const run = runStart(demo); const run = runStart(demo);
const runInterpreted = run?.interpreted as const runInterpreted = isRecord(run?.interpreted) ? run.interpreted : undefined;
| { runId?: string; status?: string }
| undefined;
return { return {
draft: { draft: {
@@ -63,19 +67,17 @@ export const projectDemoLifecycleFacts = (demo: DemoTimelineController): DemoLif
status: "prepared context", status: "prepared context",
}, },
artifact: { artifact: {
id: deploymentInterpreted?.artifactId ?? "unavailable", id: stringField(deploymentInterpreted, "artifactId") ?? "unavailable",
version: typeof deploymentInterpreted?.artifactVersion === "number" version: numberField(deploymentInterpreted, "artifactVersion") ?? null,
? deploymentInterpreted.artifactVersion
: null,
}, },
deployment: { deployment: {
id: deploymentInterpreted?.id ?? "unavailable", id: stringField(deploymentInterpreted, "id") ?? "unavailable",
driftPolicy: deploymentInterpreted?.driftPolicy ?? "unavailable", driftPolicy: stringField(deploymentInterpreted, "driftPolicy") ?? "unavailable",
bindings: readBindings(deploymentInterpreted?.bindings), bindings: readBindings(deploymentInterpreted?.["bindings"]),
}, },
run: { run: {
id: runInterpreted?.runId ?? run?.resultingIds.runId ?? null, id: stringField(runInterpreted, "runId") ?? run?.resultingIds.runId ?? null,
status: runInterpreted?.status ?? "not started", status: stringField(runInterpreted, "status") ?? "not started",
}, },
}; };
}; };
@@ -116,6 +116,53 @@ describe("demo-run-facts", () => {
} }
}); });
it("falls back honestly before any replay events have been applied", () => {
const facts = projectDemoRunFacts(controller({
state: {
...initialDemoTimelineState,
mode: "replay",
phase: "ready",
events: [],
appliedCount: 0,
autoplay: false,
},
interruptPayload: null,
}));
expect(facts.input).toEqual({ selectedDocuments: [], boardPath: "" });
expect(facts.interrupt).toMatchObject({
kind: "unknown",
typed: false,
outcomes: [],
proposedIssues: [],
reportMarkdownPreview: "",
});
expect(facts.resume).toEqual({ outcome: null, payload: null });
expect(facts.output.state).toBe("not-created");
expect(facts.trace.frames).toEqual([]);
});
it("ignores malformed output evidence instead of displaying partial data", () => {
const recording = loadCanonicalDemoRecording();
const malformedEvents = recording.events.map((event) =>
event.stage === "run_resume"
? { ...event, interpreted: { output: { markdown: 42 } } }
: event,
);
const facts = projectDemoRunFacts(controller({
state: {
...initialDemoTimelineState,
mode: "replay",
phase: "completed",
events: malformedEvents,
appliedCount: malformedEvents.length,
autoplay: false,
},
}));
expect(facts.output.state).toBe("not-created");
});
it("projects trace frames and empty object state accurately", () => { it("projects trace frames and empty object state accurately", () => {
const recording = loadCanonicalDemoRecording(); const recording = loadCanonicalDemoRecording();
const facts = projectDemoRunFacts(controller({ const facts = projectDemoRunFacts(controller({
@@ -1,6 +1,12 @@
import * as v from "valibot";
import {
LdaReportInterruptPayloadSchema,
LdaReportOutputSchema,
type LdaReportInterruptPayload,
type LdaReportOutput,
} from "../demo/ldaReportDemoModels.js";
import type { DemoEvent, DemoEventStage } from "../demo/timeline/models.js";
import type { DemoTimelineController } from "../demo/useDemoTimeline.js"; import type { DemoTimelineController } from "../demo/useDemoTimeline.js";
import type { LdaReportOutput } from "../demo/ldaReportDemoModels.js";
import type { TraceFrame } from "../lifecycle/models.js";
export type RunFactsInput = { export type RunFactsInput = {
readonly selectedDocuments: ReadonlyArray<string>; readonly selectedDocuments: ReadonlyArray<string>;
@@ -56,6 +62,47 @@ export type DemoRunFacts = {
const EMPTY_OBJECT_LABEL = "captured as empty object"; const EMPTY_OBJECT_LABEL = "captured as empty object";
const isRecord = (value: unknown): value is Record<string, unknown> =>
typeof value === "object" && value !== null && !Array.isArray(value);
const recordField = (
record: Record<string, unknown> | undefined,
field: string,
): Record<string, unknown> | undefined => {
const value = record?.[field];
return isRecord(value) ? value : undefined;
};
const stringField = (record: Record<string, unknown> | undefined, field: string): string | undefined => {
const value = record?.[field];
return typeof value === "string" ? value : undefined;
};
const booleanField = (record: Record<string, unknown> | undefined, field: string): boolean | undefined => {
const value = record?.[field];
return typeof value === "boolean" ? value : undefined;
};
const stringArrayField = (
record: Record<string, unknown> | undefined,
field: string,
): ReadonlyArray<string> => {
const value = record?.[field];
return Array.isArray(value) && value.every((item) => typeof item === "string")
? value
: [];
};
const parseInterruptPayload = (value: unknown): LdaReportInterruptPayload | null => {
const decoded = v.safeParse(LdaReportInterruptPayloadSchema, value);
return decoded.success ? decoded.output : null;
};
const parseReportOutput = (value: unknown): LdaReportOutput | null => {
const decoded = v.safeParse(LdaReportOutputSchema, value);
return decoded.success ? decoded.output : null;
};
export const formatFactValue = (value: unknown, absentLabel: string): string => { export const formatFactValue = (value: unknown, absentLabel: string): string => {
if (value === undefined || value === null) return absentLabel; if (value === undefined || value === null) return absentLabel;
if (typeof value === "object" && Object.keys(value).length === 0) return EMPTY_OBJECT_LABEL; if (typeof value === "object" && Object.keys(value).length === 0) return EMPTY_OBJECT_LABEL;
@@ -63,57 +110,55 @@ export const formatFactValue = (value: unknown, absentLabel: string): string =>
}; };
const findEvent = ( const findEvent = (
events: ReadonlyArray<{ readonly stage: string }>, events: ReadonlyArray<DemoEvent>,
stage: string, stage: DemoEventStage,
): typeof events[number] | undefined => ): DemoEvent | undefined =>
events.find((event) => event.stage === stage); events.find((event) => event.stage === stage);
const eventParams = (
events: ReadonlyArray<DemoEvent>,
stage: DemoEventStage,
): Record<string, unknown> | undefined => {
const params = findEvent(events, stage)?.params;
return isRecord(params) ? params : undefined;
};
const eventInterpreted = (
events: ReadonlyArray<DemoEvent>,
stage: DemoEventStage,
): Record<string, unknown> | undefined => {
const interpreted = findEvent(events, stage)?.interpreted;
return isRecord(interpreted) ? interpreted : undefined;
};
const readWorkflowInput = ( const readWorkflowInput = (
events: ReadonlyArray<{ readonly stage: string; readonly params: unknown }>, events: ReadonlyArray<DemoEvent>,
): RunFactsInput => { ): RunFactsInput => {
const runStart = findEvent(events, "run_start") as const wi = recordField(eventParams(events, "run_start"), "workflow_input");
| { params: { workflow_input?: { selected_documents?: unknown; board_path?: unknown } } }
| undefined;
const wi = runStart?.params?.workflow_input;
return { return {
selectedDocuments: Array.isArray(wi?.selected_documents) selectedDocuments: stringArrayField(wi, "selected_documents"),
? (wi.selected_documents as ReadonlyArray<string>) boardPath: stringField(wi, "board_path") ?? "",
: [],
boardPath: typeof wi?.board_path === "string" ? wi.board_path : "",
}; };
}; };
const readInterruptFacts = ( const readInterruptFacts = (
events: ReadonlyArray<{ readonly stage: string; readonly params: unknown; readonly interpreted: unknown }>, events: ReadonlyArray<DemoEvent>,
interruptPayload: DemoTimelineController["interruptPayload"], interruptPayload: DemoTimelineController["interruptPayload"],
): RunFactsInterrupt => { ): RunFactsInterrupt => {
const runStart = findEvent(events, "run_start") as const runStartInterpreted = eventInterpreted(events, "run_start");
| { const interruptInterpreted = eventInterpreted(events, "interrupt");
interpreted: { const ri = recordField(runStartInterpreted, "interrupt");
interrupt?: { const payload =
kind?: string; interruptPayload ??
typed?: boolean; parseInterruptPayload(interruptInterpreted?.["payload"]) ??
outcomes?: unknown; parseInterruptPayload(ri?.["payload"]);
payload?: DemoTimelineController["interruptPayload"]; const outcomes = stringArrayField(interruptInterpreted, "outcomes").length > 0
}; ? stringArrayField(interruptInterpreted, "outcomes")
}; : stringArrayField(ri, "outcomes");
}
| undefined;
const interruptEvent = findEvent(events, "interrupt") as
| { interpreted: { outcomes?: unknown; payload?: DemoTimelineController["interruptPayload"] } }
| undefined;
const ri = runStart?.interpreted?.interrupt;
const payload = interruptPayload ?? interruptEvent?.interpreted?.payload ?? ri?.payload ?? null;
const outcomes = Array.isArray(interruptEvent?.interpreted?.outcomes)
? (interruptEvent!.interpreted.outcomes as ReadonlyArray<string>)
: Array.isArray(ri?.outcomes)
? (ri.outcomes as ReadonlyArray<string>)
: [];
return { return {
kind: typeof ri?.kind === "string" ? ri.kind : "unknown", kind: stringField(ri, "kind") ?? "unknown",
typed: ri?.typed === true, typed: booleanField(ri, "typed") === true,
outcomes, outcomes,
proposedIssues: payload?.proposed_issues ?? [], proposedIssues: payload?.proposed_issues ?? [],
reportMarkdownPreview: payload?.report_markdown ?? "", reportMarkdownPreview: payload?.report_markdown ?? "",
@@ -121,41 +166,33 @@ const readInterruptFacts = (
}; };
const readResumeFacts = ( const readResumeFacts = (
events: ReadonlyArray<{ readonly stage: string; readonly params: unknown }>, events: ReadonlyArray<DemoEvent>,
): RunFactsResume => { ): RunFactsResume => {
const runResume = findEvent(events, "run_resume") as const params = eventParams(events, "run_resume");
| { params: { resume_payload?: unknown; resume_outcome?: string } } if (!params) return { outcome: null, payload: null };
| undefined;
if (!runResume) return { outcome: null, payload: null };
const outcome = const outcome =
runResume.params.resume_outcome === "submitted" || params.resume_outcome === "submitted" ||
runResume.params.resume_outcome === "cancelled" params.resume_outcome === "cancelled"
? runResume.params.resume_outcome ? params.resume_outcome
: null; : null;
const payload = const payload =
typeof runResume.params.resume_payload === "object" && isRecord(params.resume_payload)
runResume.params.resume_payload !== null ? params.resume_payload
? (runResume.params.resume_payload as Record<string, unknown>)
: null; : null;
if (outcome === null) return { outcome: null, payload: null }; if (outcome === null) return { outcome: null, payload: null };
return { outcome, payload } as RunFactsResume; return { outcome, payload: payload ?? {} };
}; };
const readOutputFacts = ( const readOutputFacts = (
events: ReadonlyArray<{ readonly stage: string; readonly interpreted: unknown }>, events: ReadonlyArray<DemoEvent>,
): RunFactsOutput => { ): RunFactsOutput => {
const resumeEvent = findEvent(events, "run_resume") as const resumeInterpreted = eventInterpreted(events, "run_resume");
| { interpreted: { output?: unknown } } const completedInterpreted = eventInterpreted(events, "completed");
| undefined;
const completedEvent = findEvent(events, "completed") as
| { interpreted: { output?: unknown } }
| undefined;
const raw = resumeEvent?.interpreted?.output ?? completedEvent?.interpreted?.output; const output = parseReportOutput(resumeInterpreted?.["output"] ?? completedInterpreted?.["output"]);
if (!raw || typeof raw !== "object") { if (!output) {
return { state: "not-created", message: "Output not created yet" }; return { state: "not-created", message: "Output not created yet" };
} }
const output = raw as LdaReportOutput;
return { return {
state: "created", state: "created",
output, output,
@@ -168,30 +205,33 @@ const formatRecord = (record: Record<string, unknown>, absentLabel: string): str
formatFactValue(record, absentLabel); formatFactValue(record, absentLabel);
const readTraceFacts = ( const readTraceFacts = (
events: ReadonlyArray<{ readonly stage: string; readonly interpreted: unknown }>, events: ReadonlyArray<DemoEvent>,
): RunFactsTrace => { ): RunFactsTrace => {
const traceEvent = findEvent(events, "trace_read") as const traceInterpreted = eventInterpreted(events, "trace_read");
| { interpreted: { frames?: unknown } } const completedInterpreted = eventInterpreted(events, "completed");
| undefined; const completedTrace = recordField(completedInterpreted, "trace");
const completedEvent = findEvent(events, "completed") as
| { interpreted: { trace?: { frames?: unknown } } }
| undefined;
const rawFrames = const rawFrames =
traceEvent?.interpreted?.frames ?? traceInterpreted?.["frames"] ??
completedEvent?.interpreted?.trace?.frames; completedTrace?.["frames"];
if (!Array.isArray(rawFrames)) return { frames: [] }; if (!Array.isArray(rawFrames)) return { frames: [] };
return { return {
frames: rawFrames.map((frame: TraceFrame) => ({ frames: rawFrames.flatMap((frame) => {
nodeId: frame.nodeId, if (!isRecord(frame)) return [];
stepType: frame.stepType, return [{
outcome: frame.outcome, nodeId: stringField(frame, "nodeId") ?? "unknown",
resolvedInputLabel: formatRecord(frame.resolvedInput, "not captured in this recording"), stepType: stringField(frame, "stepType") ?? "unknown",
outputLabel: formatRecord(frame.output, "not captured in this recording"), outcome: stringField(frame, "outcome") ?? "unknown",
stateChangesLabel: formatRecord(frame.stateChanges, "not captured in this recording"), resolvedInputLabel: formatRecord(
})), recordField(frame, "resolvedInput") ?? {},
"not captured in this recording",
),
outputLabel: formatRecord(recordField(frame, "output") ?? {}, "not captured in this recording"),
stateChangesLabel: formatRecord(recordField(frame, "stateChanges") ?? {}, "not captured in this recording"),
}];
}),
}; };
}; };