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}
selectNode={vi.fn()}
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 { projectDemoLifecycleFacts } from "./demo-lifecycle-facts.js";
const controller = (): DemoTimelineController => {
const controller = (eventMode: "recorded" | "empty" = "recorded"): DemoTimelineController => {
const recording = loadCanonicalDemoRecording();
return {
state: {
...initialDemoTimelineState,
mode: "replay",
phase: "paused",
events: recording.events,
appliedCount: recording.events.length,
events: eventMode === "recorded" ? recording.events : [],
appliedCount: eventMode === "recorded" ? recording.events.length : 0,
autoplay: false,
},
inFlight: false,
@@ -55,4 +55,16 @@ describe("projectDemoLifecycleFacts", () => {
expect(facts.run.id).toBe("run_recorded_lda_report");
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 { DemoEvent } from "../demo/timeline/models.js";
export type DemoLifecycleFacts = {
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");
const runStart = (demo: DemoTimelineController) =>
const runStart = (demo: DemoTimelineController): DemoEvent | undefined =>
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]> => {
if (!Array.isArray(value)) return [];
return value.flatMap((entry) => {
@@ -42,19 +56,9 @@ const readBindings = (value: unknown): ReadonlyArray<readonly [string, string]>
*/
export const projectDemoLifecycleFacts = (demo: DemoTimelineController): DemoLifecycleFacts => {
const deployment = deploymentInspect(demo);
const deploymentInterpreted = deployment?.interpreted as
| {
id?: string;
artifactId?: string;
artifactVersion?: number;
driftPolicy?: string;
bindings?: unknown;
}
| undefined;
const deploymentInterpreted = isRecord(deployment?.interpreted) ? deployment.interpreted : undefined;
const run = runStart(demo);
const runInterpreted = run?.interpreted as
| { runId?: string; status?: string }
| undefined;
const runInterpreted = isRecord(run?.interpreted) ? run.interpreted : undefined;
return {
draft: {
@@ -63,19 +67,17 @@ export const projectDemoLifecycleFacts = (demo: DemoTimelineController): DemoLif
status: "prepared context",
},
artifact: {
id: deploymentInterpreted?.artifactId ?? "unavailable",
version: typeof deploymentInterpreted?.artifactVersion === "number"
? deploymentInterpreted.artifactVersion
: null,
id: stringField(deploymentInterpreted, "artifactId") ?? "unavailable",
version: numberField(deploymentInterpreted, "artifactVersion") ?? null,
},
deployment: {
id: deploymentInterpreted?.id ?? "unavailable",
driftPolicy: deploymentInterpreted?.driftPolicy ?? "unavailable",
bindings: readBindings(deploymentInterpreted?.bindings),
id: stringField(deploymentInterpreted, "id") ?? "unavailable",
driftPolicy: stringField(deploymentInterpreted, "driftPolicy") ?? "unavailable",
bindings: readBindings(deploymentInterpreted?.["bindings"]),
},
run: {
id: runInterpreted?.runId ?? run?.resultingIds.runId ?? null,
status: runInterpreted?.status ?? "not started",
id: stringField(runInterpreted, "runId") ?? run?.resultingIds.runId ?? null,
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", () => {
const recording = loadCanonicalDemoRecording();
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 { LdaReportOutput } from "../demo/ldaReportDemoModels.js";
import type { TraceFrame } from "../lifecycle/models.js";
export type RunFactsInput = {
readonly selectedDocuments: ReadonlyArray<string>;
@@ -56,6 +62,47 @@ export type DemoRunFacts = {
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 => {
if (value === undefined || value === null) return absentLabel;
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 = (
events: ReadonlyArray<{ readonly stage: string }>,
stage: string,
): typeof events[number] | undefined =>
events: ReadonlyArray<DemoEvent>,
stage: DemoEventStage,
): DemoEvent | undefined =>
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 = (
events: ReadonlyArray<{ readonly stage: string; readonly params: unknown }>,
events: ReadonlyArray<DemoEvent>,
): RunFactsInput => {
const runStart = findEvent(events, "run_start") as
| { params: { workflow_input?: { selected_documents?: unknown; board_path?: unknown } } }
| undefined;
const wi = runStart?.params?.workflow_input;
const wi = recordField(eventParams(events, "run_start"), "workflow_input");
return {
selectedDocuments: Array.isArray(wi?.selected_documents)
? (wi.selected_documents as ReadonlyArray<string>)
: [],
boardPath: typeof wi?.board_path === "string" ? wi.board_path : "",
selectedDocuments: stringArrayField(wi, "selected_documents"),
boardPath: stringField(wi, "board_path") ?? "",
};
};
const readInterruptFacts = (
events: ReadonlyArray<{ readonly stage: string; readonly params: unknown; readonly interpreted: unknown }>,
events: ReadonlyArray<DemoEvent>,
interruptPayload: DemoTimelineController["interruptPayload"],
): RunFactsInterrupt => {
const runStart = findEvent(events, "run_start") as
| {
interpreted: {
interrupt?: {
kind?: string;
typed?: boolean;
outcomes?: unknown;
payload?: DemoTimelineController["interruptPayload"];
};
};
}
| 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>)
: [];
const runStartInterpreted = eventInterpreted(events, "run_start");
const interruptInterpreted = eventInterpreted(events, "interrupt");
const ri = recordField(runStartInterpreted, "interrupt");
const payload =
interruptPayload ??
parseInterruptPayload(interruptInterpreted?.["payload"]) ??
parseInterruptPayload(ri?.["payload"]);
const outcomes = stringArrayField(interruptInterpreted, "outcomes").length > 0
? stringArrayField(interruptInterpreted, "outcomes")
: stringArrayField(ri, "outcomes");
return {
kind: typeof ri?.kind === "string" ? ri.kind : "unknown",
typed: ri?.typed === true,
kind: stringField(ri, "kind") ?? "unknown",
typed: booleanField(ri, "typed") === true,
outcomes,
proposedIssues: payload?.proposed_issues ?? [],
reportMarkdownPreview: payload?.report_markdown ?? "",
@@ -121,41 +166,33 @@ const readInterruptFacts = (
};
const readResumeFacts = (
events: ReadonlyArray<{ readonly stage: string; readonly params: unknown }>,
events: ReadonlyArray<DemoEvent>,
): RunFactsResume => {
const runResume = findEvent(events, "run_resume") as
| { params: { resume_payload?: unknown; resume_outcome?: string } }
| undefined;
if (!runResume) return { outcome: null, payload: null };
const params = eventParams(events, "run_resume");
if (!params) return { outcome: null, payload: null };
const outcome =
runResume.params.resume_outcome === "submitted" ||
runResume.params.resume_outcome === "cancelled"
? runResume.params.resume_outcome
params.resume_outcome === "submitted" ||
params.resume_outcome === "cancelled"
? params.resume_outcome
: null;
const payload =
typeof runResume.params.resume_payload === "object" &&
runResume.params.resume_payload !== null
? (runResume.params.resume_payload as Record<string, unknown>)
isRecord(params.resume_payload)
? params.resume_payload
: null;
if (outcome === null) return { outcome: null, payload: null };
return { outcome, payload } as RunFactsResume;
return { outcome, payload: payload ?? {} };
};
const readOutputFacts = (
events: ReadonlyArray<{ readonly stage: string; readonly interpreted: unknown }>,
events: ReadonlyArray<DemoEvent>,
): RunFactsOutput => {
const resumeEvent = findEvent(events, "run_resume") as
| { interpreted: { output?: unknown } }
| undefined;
const completedEvent = findEvent(events, "completed") as
| { interpreted: { output?: unknown } }
| undefined;
const resumeInterpreted = eventInterpreted(events, "run_resume");
const completedInterpreted = eventInterpreted(events, "completed");
const raw = resumeEvent?.interpreted?.output ?? completedEvent?.interpreted?.output;
if (!raw || typeof raw !== "object") {
const output = parseReportOutput(resumeInterpreted?.["output"] ?? completedInterpreted?.["output"]);
if (!output) {
return { state: "not-created", message: "Output not created yet" };
}
const output = raw as LdaReportOutput;
return {
state: "created",
output,
@@ -168,30 +205,33 @@ const formatRecord = (record: Record<string, unknown>, absentLabel: string): str
formatFactValue(record, absentLabel);
const readTraceFacts = (
events: ReadonlyArray<{ readonly stage: string; readonly interpreted: unknown }>,
events: ReadonlyArray<DemoEvent>,
): RunFactsTrace => {
const traceEvent = findEvent(events, "trace_read") as
| { interpreted: { frames?: unknown } }
| undefined;
const completedEvent = findEvent(events, "completed") as
| { interpreted: { trace?: { frames?: unknown } } }
| undefined;
const traceInterpreted = eventInterpreted(events, "trace_read");
const completedInterpreted = eventInterpreted(events, "completed");
const completedTrace = recordField(completedInterpreted, "trace");
const rawFrames =
traceEvent?.interpreted?.frames ??
completedEvent?.interpreted?.trace?.frames;
traceInterpreted?.["frames"] ??
completedTrace?.["frames"];
if (!Array.isArray(rawFrames)) return { frames: [] };
return {
frames: rawFrames.map((frame: TraceFrame) => ({
nodeId: frame.nodeId,
stepType: frame.stepType,
outcome: frame.outcome,
resolvedInputLabel: formatRecord(frame.resolvedInput, "not captured in this recording"),
outputLabel: formatRecord(frame.output, "not captured in this recording"),
stateChangesLabel: formatRecord(frame.stateChanges, "not captured in this recording"),
})),
frames: rawFrames.flatMap((frame) => {
if (!isRecord(frame)) return [];
return [{
nodeId: stringField(frame, "nodeId") ?? "unknown",
stepType: stringField(frame, "stepType") ?? "unknown",
outcome: stringField(frame, "outcome") ?? "unknown",
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"),
}];
}),
};
};