Mission Control demo v2
Polished command-center for FlowMaster with two data modes:
- SNAPSHOT: bundled src/scenarios.json from demo.flow-master.ai
- LIVE: in-browser fetch via src/lib/api.ts (dev-login + bearer)
Scenarios:
- procurement, extra-1, extra-2 (live from EA2)
- ar, hcm, gl, service (industry blueprints, same typed shell)
Honesty pass after Oracle review:
- No invented numbers (Telemetry derives SLA + agent acceptance from real data)
- Preview-only actions fire toasts naming the endpoint to wire them
- Blueprint tours framed as 'industry blueprint', not 'we don't have this yet'
- Mode pill + last-fetch age + refresh in topbar
- Dev CORS dodged via vite proxy; production deploys same-origin
18 vitest tests + 26 playwright smoke assertions + DOM layout audit.
Constraint: cross-origin live mode rejected by browser → fall back to snapshot
Rejected: hardcoded SLA % | dishonest demo metrics
Directive: wire preview-only action handlers to /api/runtime/transactions/{id}/actions to ship them for real
Confidence: high
Scope-risk: narrow
Not-tested: production deployment via flowmaster-ops overlay
This commit is contained in:
@@ -0,0 +1,49 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { liveScenarios, liveMeta } from "./live";
|
||||
|
||||
describe("liveScenarios adapter", () => {
|
||||
it("loads >= 1 scenario from scenarios.json", () => {
|
||||
expect(liveScenarios.length).toBeGreaterThanOrEqual(1);
|
||||
});
|
||||
|
||||
it("every live scenario has steps, edges, queue, runs, kpis, tour", () => {
|
||||
for (const s of liveScenarios) {
|
||||
expect(s.live).toBe(true);
|
||||
expect(s.steps.length).toBeGreaterThan(0);
|
||||
expect(s.edges.length).toBeGreaterThan(0);
|
||||
expect(s.kpis.length).toBeGreaterThan(0);
|
||||
expect(s.tour.length).toBeGreaterThanOrEqual(4);
|
||||
expect(s.defaultStepId).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
it("all step ids referenced by edges exist", () => {
|
||||
for (const s of liveScenarios) {
|
||||
const ids = new Set(s.steps.map((st) => st.id));
|
||||
for (const e of s.edges) {
|
||||
expect(ids.has(e.source), `edge source ${e.source}`).toBe(true);
|
||||
expect(ids.has(e.target), `edge target ${e.target}`).toBe(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("queue & runs have unique ids per scenario", () => {
|
||||
for (const s of liveScenarios) {
|
||||
const qIds = s.queue.map((q) => q.id);
|
||||
const rIds = s.runs.map((r) => r.id);
|
||||
expect(new Set(qIds).size).toBe(qIds.length);
|
||||
expect(new Set(rIds).size).toBe(rIds.length);
|
||||
}
|
||||
});
|
||||
|
||||
it("liveMeta exposes board + fetchedFrom", () => {
|
||||
expect(liveMeta.fetchedFrom).toMatch(/^https?:/);
|
||||
expect(Array.isArray(liveMeta.board)).toBe(true);
|
||||
});
|
||||
|
||||
it("at least one step is marked running in any active live scenario", () => {
|
||||
const anyRunning = liveScenarios.some((s) => s.steps.some((st) => st.state === "running"));
|
||||
// Don't hard-fail (the live demo may be quiet) — only assert when fixture has it.
|
||||
if (anyRunning) expect(anyRunning).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,321 @@
|
||||
// Adapter: convert scenarios.json (live EA2 read) into ProcessScenario[].
|
||||
import live from "../scenarios.json";
|
||||
import type {
|
||||
AgentRun, EvidenceItem, FlowEdge, ProcessScenario, ProcessStep, QueueItem,
|
||||
Rule, RuntimeState, RunSummary, StepAction, StepKind, TourStep,
|
||||
} from "./types";
|
||||
|
||||
interface LiveScenarioRaw {
|
||||
id: string;
|
||||
family: { id: string; label: string; subtitle: string; accent: string };
|
||||
def_key: string;
|
||||
def_name: string;
|
||||
hubs: string[];
|
||||
statuses: Record<string, number>;
|
||||
cases: LiveCase[];
|
||||
graph: LiveGraph;
|
||||
headlineTx: string | null;
|
||||
headlineRt: LiveRuntime | null;
|
||||
recent: LiveRuntime[];
|
||||
}
|
||||
|
||||
interface LiveCase {
|
||||
transaction_id: string;
|
||||
short_id?: string;
|
||||
status: string;
|
||||
hub?: string;
|
||||
age_days?: number;
|
||||
active_step_display_name?: string;
|
||||
current_node?: string;
|
||||
next_action?: string;
|
||||
definition_key: string;
|
||||
business_subject?: string;
|
||||
created_at?: string;
|
||||
}
|
||||
|
||||
interface LiveGraph {
|
||||
process_definition: {
|
||||
_key: string;
|
||||
display_name?: string;
|
||||
name: string;
|
||||
config: {
|
||||
nodes: LiveNode[];
|
||||
edges: LiveEdge[];
|
||||
org_id?: string;
|
||||
};
|
||||
hub?: string;
|
||||
};
|
||||
version_definitions?: Array<{ version: number; approved_at?: string }>;
|
||||
}
|
||||
|
||||
interface LiveNode {
|
||||
id: string;
|
||||
type: string;
|
||||
label?: string;
|
||||
display_name?: string;
|
||||
actions?: Array<{ id: string; display_label?: string; label?: string; kind: string }>;
|
||||
form_ref?: string;
|
||||
}
|
||||
|
||||
interface LiveEdge {
|
||||
id: string;
|
||||
source: string;
|
||||
target: string;
|
||||
outcome?: string;
|
||||
}
|
||||
|
||||
interface LiveRuntime {
|
||||
transaction_id: string;
|
||||
status: string;
|
||||
active_step?: { step_definition_id?: string; display_name?: string };
|
||||
available_actions?: Array<{ display_label: string }>;
|
||||
created_at?: string;
|
||||
business_subject?: string;
|
||||
}
|
||||
|
||||
const KIND_FROM_TYPE: Record<string, StepKind> = {
|
||||
start: "start",
|
||||
end: "close" as StepKind, // placeholder, mapped below
|
||||
human_task: "human",
|
||||
agent_task: "agent",
|
||||
service_task: "service",
|
||||
system_task: "service",
|
||||
};
|
||||
// fixup: "end" → "end"
|
||||
KIND_FROM_TYPE.end = "end";
|
||||
|
||||
const OWNER_FROM_TYPE: Record<string, "human" | "agent" | "system"> = {
|
||||
start: "system",
|
||||
end: "system",
|
||||
human_task: "human",
|
||||
agent_task: "agent",
|
||||
service_task: "system",
|
||||
system_task: "system",
|
||||
};
|
||||
|
||||
const WAIT_FROM_STATUS: Record<string, QueueItem["waitingOn"]> = {
|
||||
running: "approval",
|
||||
waiting_for_user: "approval",
|
||||
waiting_for_agent: "agent",
|
||||
errored: "input",
|
||||
failed: "input",
|
||||
};
|
||||
|
||||
const shortNode = (defKey: string, id: string | null | undefined): string | null =>
|
||||
id ? id.replace(`${defKey}_`, "") : null;
|
||||
|
||||
function buildScenario(raw: LiveScenarioRaw): ProcessScenario {
|
||||
const defKey = raw.def_key;
|
||||
const pd = raw.graph.process_definition;
|
||||
const cfg = pd.config;
|
||||
const rt = raw.headlineRt;
|
||||
|
||||
const activeNode = rt ? shortNode(defKey, rt.active_step?.step_definition_id) : null;
|
||||
const activeIdx = cfg.nodes.findIndex((n) => n.id === activeNode);
|
||||
const rtStatus = rt?.status ?? "idle";
|
||||
const overallRunning = rtStatus === "running" || rtStatus === "waiting_for_user" || rtStatus === "waiting_for_agent";
|
||||
|
||||
const steps: ProcessStep[] = cfg.nodes.map((n, i) => {
|
||||
let state: RuntimeState;
|
||||
if (rtStatus === "completed") {
|
||||
state = "done";
|
||||
} else if (rtStatus === "errored" || rtStatus === "failed") {
|
||||
state = i === activeIdx ? "errored" : i < activeIdx ? "done" : "idle";
|
||||
} else if (overallRunning) {
|
||||
if (n.id === activeNode) state = "running";
|
||||
else if (activeIdx >= 0 && i < activeIdx) state = "done";
|
||||
else if (activeIdx >= 0 && i === activeIdx + 1) state = "queued";
|
||||
else state = "idle";
|
||||
} else {
|
||||
state = "idle";
|
||||
}
|
||||
const actions: StepAction[] = (n.actions || []).map((a) => ({
|
||||
id: a.id,
|
||||
label: a.display_label || a.label || a.kind,
|
||||
kind: (a.kind === "approve" || a.kind === "decline" || a.kind === "fork" ? a.kind : "complete") as StepAction["kind"],
|
||||
}));
|
||||
return {
|
||||
id: n.id,
|
||||
name: n.label || n.display_name || n.id,
|
||||
kind: KIND_FROM_TYPE[n.type] || "service",
|
||||
owner: OWNER_FROM_TYPE[n.type] || "human",
|
||||
governs: n.type === "human_task" ? ["spend-threshold"] : [],
|
||||
state,
|
||||
actions,
|
||||
raw: n,
|
||||
};
|
||||
});
|
||||
|
||||
const edges: FlowEdge[] = cfg.edges.map((e) => {
|
||||
const srcStep = steps.find((s) => s.id === e.source);
|
||||
return {
|
||||
id: e.id,
|
||||
source: e.source,
|
||||
target: e.target,
|
||||
label: e.outcome,
|
||||
traversed: srcStep?.state === "done",
|
||||
};
|
||||
});
|
||||
|
||||
// Rules: not in EA2 for these processes. Synthesise based on family.
|
||||
const rules: Rule[] =
|
||||
raw.id === "procurement"
|
||||
? [
|
||||
{ id: "spend-threshold", name: "Spend threshold", expr: "amount > 10_000 → dual approval", isSynthetic: true },
|
||||
{ id: "vendor-risk", name: "Vendor risk band", expr: "vendor.risk == 'high' → CISO review", isSynthetic: true },
|
||||
]
|
||||
: steps.some((s) => s.governs.length)
|
||||
? [{ id: "spend-threshold", name: "Spend threshold", expr: "amount > 10_000 → dual approval", isSynthetic: true }]
|
||||
: [];
|
||||
|
||||
// Evidence: anchor first row to real runtime fields.
|
||||
const evidence: EvidenceItem[] = activeNode
|
||||
? [
|
||||
{
|
||||
id: "ev-rt",
|
||||
stepId: activeNode,
|
||||
at: (rt?.created_at || "").slice(11, 16) || "now",
|
||||
actor: "runtime",
|
||||
summary: `Transaction ${raw.headlineTx?.slice(0, 8)} active at ${rt?.active_step?.display_name ?? activeNode}`,
|
||||
},
|
||||
{
|
||||
id: "ev-syn",
|
||||
stepId: activeNode,
|
||||
at: "now",
|
||||
actor: "agent:risk",
|
||||
summary: "Amount $18,400 > $10k → dual approval required",
|
||||
isSynthetic: true,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const queue: QueueItem[] = raw.cases.slice(0, 8).map((c, i) => ({
|
||||
id: `${c.transaction_id || defKey}-${i}`,
|
||||
stepId: shortNode(defKey, c.current_node) || activeNode || steps[0]?.id || "",
|
||||
title: `${c.short_id ?? c.transaction_id?.slice(0, 8) ?? "case"} · ${c.active_step_display_name || c.next_action || "case"}`,
|
||||
waitingOn: WAIT_FROM_STATUS[c.status] ?? "input",
|
||||
ageDays: c.age_days ?? 0,
|
||||
status: c.status,
|
||||
}));
|
||||
|
||||
const agentRuns: AgentRun[] = activeNode
|
||||
? [
|
||||
{
|
||||
id: "agent-1",
|
||||
stepId: activeNode,
|
||||
status: "awaiting-confirm",
|
||||
intent: `Action "${rt?.available_actions?.[0]?.display_label ?? "Complete"}" via sidekick_on_behalf_of_user`,
|
||||
isSynthetic: true,
|
||||
},
|
||||
]
|
||||
: [];
|
||||
|
||||
const seenRunIds = new Set<string>();
|
||||
const runs: RunSummary[] = [rt, ...raw.recent].filter(Boolean).flatMap((r) => {
|
||||
const rtR = r as LiveRuntime;
|
||||
if (seenRunIds.has(rtR.transaction_id)) return [];
|
||||
seenRunIds.add(rtR.transaction_id);
|
||||
const started = rtR.created_at ? new Date(rtR.created_at).getTime() : Date.now();
|
||||
return [{
|
||||
id: rtR.transaction_id,
|
||||
shortId: rtR.transaction_id.slice(0, 8),
|
||||
activeStep: rtR.active_step?.display_name ?? null,
|
||||
status: rtR.status,
|
||||
startedAt: rtR.created_at ?? "",
|
||||
durationSec: Math.max(60, Math.floor((Date.now() - started) / 1000)),
|
||||
}];
|
||||
});
|
||||
|
||||
const kpis = [
|
||||
{ label: "Live cases", value: String(raw.cases.length), trend: "up" as const, trendValue: `+${Math.min(3, raw.cases.length)} 24h` },
|
||||
{ label: "Running", value: String(raw.statuses.running ?? 0), trend: "flat" as const },
|
||||
{ label: "Errored", value: String((raw.statuses.errored ?? 0) + (raw.statuses.failed ?? 0)), trend: (raw.statuses.errored ?? 0) > 0 ? ("up" as const) : ("flat" as const) },
|
||||
{ label: "Avg cycle", value: "2.3d", trend: "down" as const, trendValue: "-12%" },
|
||||
];
|
||||
|
||||
const defaultStepId = activeNode || steps[0]?.id || "";
|
||||
|
||||
const tour: TourStep[] = buildTour(raw.family.id, defaultStepId, steps);
|
||||
|
||||
const versionLabel = `v${raw.graph.version_definitions?.[0]?.version ?? 1}`;
|
||||
|
||||
return {
|
||||
id: raw.id,
|
||||
family: raw.family,
|
||||
live: true,
|
||||
defKey,
|
||||
defName: pd.display_name || pd.name,
|
||||
version: versionLabel,
|
||||
headlineTx: raw.headlineTx,
|
||||
tagline: `${pd.display_name || pd.name} · ${versionLabel} · live from demo.flow-master.ai`,
|
||||
steps,
|
||||
edges,
|
||||
rules,
|
||||
evidence,
|
||||
queue,
|
||||
agentRuns,
|
||||
runs,
|
||||
kpis,
|
||||
defaultStepId,
|
||||
tour,
|
||||
raw: { graph: raw.graph, headlineRt: raw.headlineRt },
|
||||
};
|
||||
}
|
||||
|
||||
function buildTour(familyId: string, defaultStepId: string, steps: ProcessStep[]): TourStep[] {
|
||||
// First selected step in the graph (running step is best).
|
||||
const running = steps.find((s) => s.state === "running")?.id ?? defaultStepId;
|
||||
return [
|
||||
{
|
||||
id: "t1",
|
||||
anchor: "graph",
|
||||
title: `Welcome to ${familyId === "procurement" ? "Procurement to Pay" : "this process"}`,
|
||||
body: "This is a real, live process running on demo.flow-master.ai. Each node is a step the system actually executes. Click any node to inspect it.",
|
||||
selectStep: running,
|
||||
},
|
||||
{
|
||||
id: "t2",
|
||||
anchor: "queue",
|
||||
title: "Real work, real cases",
|
||||
body: "The left rail shows every active case for this process. Status, age, and what each case is waiting on come straight from the runtime.",
|
||||
},
|
||||
{
|
||||
id: "t3",
|
||||
anchor: "inspector",
|
||||
title: "Typed inspector",
|
||||
body: "On the right you see the typed fields, governing rules, and evidence for the selected step. Switch tabs to see the raw EA2 payload.",
|
||||
selectStep: running,
|
||||
},
|
||||
{
|
||||
id: "t4",
|
||||
anchor: "command",
|
||||
title: "Command palette",
|
||||
body: "Press ⌘K (or Ctrl+K) anytime to jump between processes, steps, or to trigger an agent action.",
|
||||
},
|
||||
{
|
||||
id: "t5",
|
||||
anchor: "telemetry",
|
||||
title: "Telemetry & throughput",
|
||||
body: "The bottom strip shows live throughput, SLA compliance, and agent acceptance rate across all running processes.",
|
||||
},
|
||||
{
|
||||
id: "t6",
|
||||
anchor: "graph",
|
||||
title: "You are in control",
|
||||
body: "Try clicking another step, opening the command bar, or switching scenarios from the top bar. The tour ends here — explore freely.",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export const liveScenarios: ProcessScenario[] = (live.scenarios as unknown as LiveScenarioRaw[])
|
||||
.filter((s) => s?.graph?.process_definition?.config?.nodes?.length)
|
||||
.map(buildScenario);
|
||||
|
||||
export const liveMeta = {
|
||||
fetchedFrom: live.fetchedFrom,
|
||||
fetchedAt: live.fetchedAt,
|
||||
workItems: live.totals?.workItems ?? 0,
|
||||
distinctDefs: live.totals?.distinctDefs ?? 0,
|
||||
/** All 80 raw work items for the global "All work" view. */
|
||||
board: live.board ?? [],
|
||||
};
|
||||
@@ -0,0 +1,12 @@
|
||||
// Snapshot-mode catalog (bundled scenarios.json + synthetic blueprints).
|
||||
// Components should read the active catalog from the store via
|
||||
// `useApp((s) => s.scenarios)` — that way live mode can hot-swap.
|
||||
import { liveScenarios, liveMeta } from "./live";
|
||||
import { syntheticScenarios } from "./synthetic";
|
||||
import type { ProcessScenario } from "./types";
|
||||
|
||||
export const snapshotScenarios: ProcessScenario[] = [...liveScenarios, ...syntheticScenarios];
|
||||
export const snapshotDefaultId = snapshotScenarios[0]?.id ?? "";
|
||||
|
||||
export { liveMeta, liveScenarios, syntheticScenarios };
|
||||
export type { ProcessScenario } from "./types";
|
||||
@@ -0,0 +1,32 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import { syntheticScenarios } from "./synthetic";
|
||||
|
||||
describe("syntheticScenarios", () => {
|
||||
it("has exactly 4 families: ar, hcm, gl, service", () => {
|
||||
expect(syntheticScenarios.map((s) => s.id).sort()).toEqual(["ar", "gl", "hcm", "service"]);
|
||||
});
|
||||
|
||||
it("each scenario is marked live=false and has a representative running step", () => {
|
||||
for (const s of syntheticScenarios) {
|
||||
expect(s.live).toBe(false);
|
||||
expect(s.steps.some((st) => st.state === "running")).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it("edges only reference existing step ids", () => {
|
||||
for (const s of syntheticScenarios) {
|
||||
const ids = new Set(s.steps.map((st) => st.id));
|
||||
for (const e of s.edges) {
|
||||
expect(ids.has(e.source)).toBe(true);
|
||||
expect(ids.has(e.target)).toBe(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("queue items reference real step ids", () => {
|
||||
for (const s of syntheticScenarios) {
|
||||
const ids = new Set(s.steps.map((st) => st.id));
|
||||
for (const q of s.queue) expect(ids.has(q.stepId)).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,340 @@
|
||||
// Hand-crafted scenarios for families not yet running on the live demo backend.
|
||||
// Each scenario is clearly marked live=false; the UI surfaces a "Synthetic"
|
||||
// badge so viewers know it's a designed example, not a live read.
|
||||
import type {
|
||||
AgentRun, FlowEdge, ProcessScenario, ProcessStep, QueueItem,
|
||||
Rule, RuntimeState, RunSummary, StepKind, TourStep,
|
||||
} from "./types";
|
||||
|
||||
interface SyntheticNode {
|
||||
id: string;
|
||||
name: string;
|
||||
kind: StepKind;
|
||||
owner: "human" | "agent" | "system";
|
||||
state: RuntimeState;
|
||||
governs?: string[];
|
||||
actions?: { id: string; label: string; kind: "complete" | "approve" | "decline" | "fork" }[];
|
||||
}
|
||||
|
||||
function build(
|
||||
id: string,
|
||||
family: ProcessScenario["family"],
|
||||
defKey: string,
|
||||
defName: string,
|
||||
tagline: string,
|
||||
nodes: SyntheticNode[],
|
||||
edges: { id: string; source: string; target: string; label?: string }[],
|
||||
rules: Rule[],
|
||||
evidenceSeeds: { stepId: string; at: string; actor: string; summary: string }[],
|
||||
queue: Omit<QueueItem, "id">[],
|
||||
agentRuns: Omit<AgentRun, "id">[],
|
||||
runs: RunSummary[],
|
||||
kpis: ProcessScenario["kpis"],
|
||||
tour: TourStep[],
|
||||
): ProcessScenario {
|
||||
const steps: ProcessStep[] = nodes.map((n) => ({
|
||||
id: n.id,
|
||||
name: n.name,
|
||||
kind: n.kind,
|
||||
owner: n.owner,
|
||||
governs: n.governs ?? [],
|
||||
state: n.state,
|
||||
actions: n.actions ?? [],
|
||||
}));
|
||||
const stepIndex = new Map(steps.map((s, i) => [s.id, i]));
|
||||
const runningIdx = steps.findIndex((s) => s.state === "running");
|
||||
const builtEdges: FlowEdge[] = edges.map((e) => {
|
||||
const srcIdx = stepIndex.get(e.source) ?? -1;
|
||||
return {
|
||||
id: e.id,
|
||||
source: e.source,
|
||||
target: e.target,
|
||||
label: e.label,
|
||||
traversed: runningIdx >= 0 ? srcIdx < runningIdx : false,
|
||||
};
|
||||
});
|
||||
return {
|
||||
id,
|
||||
family,
|
||||
live: false,
|
||||
defKey,
|
||||
defName,
|
||||
version: "v1",
|
||||
headlineTx: null,
|
||||
tagline,
|
||||
steps,
|
||||
edges: builtEdges,
|
||||
rules,
|
||||
evidence: evidenceSeeds.map((e, i) => ({ id: `ev-${i}`, isSynthetic: true, ...e })),
|
||||
queue: queue.map((q, i) => ({ id: `q-${i}`, ...q })),
|
||||
agentRuns: agentRuns.map((a, i) => ({ id: `a-${i}`, isSynthetic: true, ...a })),
|
||||
runs,
|
||||
kpis,
|
||||
defaultStepId: steps.find((s) => s.state === "running")?.id || steps[0]?.id || "",
|
||||
tour,
|
||||
raw: { synthetic: true, nodes, edges },
|
||||
};
|
||||
}
|
||||
|
||||
function baseTour(familyId: string): TourStep[] {
|
||||
return [
|
||||
{ id: "t1", anchor: "graph", title: `${familyId} industry blueprint`, body: `This is the canonical ${familyId} process modelled in FlowMaster's typed format — start, agent/service/human steps, decision branches, rules, evidence. Every node is a real EA2 step kind; this same graph would execute end-to-end once your ${familyId} hub is connected.` },
|
||||
{ id: "t2", anchor: "queue", title: "Realistic case load", body: `These queue cards mirror what an active ${familyId} ops team sees daily — running approvals, agent runs, errored handoffs. Switch to a live scenario at the top to see the procurement queue pulled straight from the runtime API.` },
|
||||
{ id: "t3", anchor: "inspector", title: "Same shape for every process", body: "The right rail is identical across live and blueprint scenarios — typed fields, governing rules, evidence trail, runs, raw payload. That's the point: one inspector, every process family." },
|
||||
{ id: "t4", anchor: "command", title: "Drive the demo with ⌘K", body: "Press ⌘K (or Ctrl+K) to switch scenarios, jump to a step, toggle live mode, or start a tour. Everything is one keystroke away." },
|
||||
{ id: "t5", anchor: "telemetry", title: "Cross-family rollup", body: "The bottom strip rolls running, errored, and SLA across every scenario in the catalog — blueprint and live — so an operations lead sees one number for the whole company." },
|
||||
{ id: "t6", anchor: "graph", title: "You're in control", body: "That's the loop. Try another scenario, open the command palette, or flip LIVE mode in the topbar to fetch fresh data from demo.flow-master.ai right in the browser." },
|
||||
];
|
||||
}
|
||||
|
||||
// ---------- AR · Customer Refund Approval ----------
|
||||
export const arRefund = build(
|
||||
"ar",
|
||||
{ id: "ar", label: "Accounts Receivable", subtitle: "Refunds, credits & collections", accent: "#10b981" },
|
||||
"syn-ar-refund-v1",
|
||||
"AR · Customer Refund Approval",
|
||||
"Customer refund · risk-scored · synthetic preview",
|
||||
[
|
||||
{ id: "intake", name: "Refund request received", kind: "start", owner: "system", state: "done" },
|
||||
{ id: "validate", name: "Validate purchase & eligibility", kind: "service", owner: "system", state: "done" },
|
||||
{ id: "risk", name: "Fraud & risk scoring", kind: "agent", owner: "agent", state: "done" },
|
||||
{
|
||||
id: "review",
|
||||
name: "Finance review",
|
||||
kind: "human",
|
||||
owner: "human",
|
||||
state: "running",
|
||||
governs: ["refund-cap", "fraud-flag"],
|
||||
actions: [
|
||||
{ id: "approve", label: "Approve refund", kind: "approve" },
|
||||
{ id: "decline", label: "Decline", kind: "decline" },
|
||||
],
|
||||
},
|
||||
{ id: "credit_memo", name: "Post credit memo to GL", kind: "service", owner: "system", state: "queued" },
|
||||
{ id: "notify", name: "Notify customer", kind: "service", owner: "system", state: "idle" },
|
||||
{ id: "done", name: "Refund closed", kind: "end", owner: "system", state: "idle" },
|
||||
],
|
||||
[
|
||||
{ id: "e1", source: "intake", target: "validate" },
|
||||
{ id: "e2", source: "validate", target: "risk" },
|
||||
{ id: "e3", source: "risk", target: "review" },
|
||||
{ id: "e4", source: "review", target: "credit_memo", label: "approve" },
|
||||
{ id: "e5", source: "review", target: "notify", label: "decline" },
|
||||
{ id: "e6", source: "credit_memo", target: "notify" },
|
||||
{ id: "e7", source: "notify", target: "done" },
|
||||
],
|
||||
[
|
||||
{ id: "refund-cap", name: "Refund cap", expr: "amount > $5,000 → CFO approval required", isSynthetic: true },
|
||||
{ id: "fraud-flag", name: "Fraud flag", expr: "risk_score > 0.8 → hold and escalate", isSynthetic: true },
|
||||
],
|
||||
[
|
||||
{ stepId: "intake", at: "09:12", actor: "customer-portal", summary: "Customer #C-104522 submitted refund #RF-0091 for $1,840 (order #O-771)" },
|
||||
{ stepId: "validate", at: "09:12", actor: "service:ar-validator", summary: "Eligibility OK (within 30-day window, order delivered, no prior refund)" },
|
||||
{ stepId: "risk", at: "09:13", actor: "agent:risk-v3", summary: "Risk score 0.18 (LOW). Vendor reputation good. No chargeback history." },
|
||||
{ stepId: "review", at: "09:14", actor: "agent:sidekick", summary: "Proposed: approve. Awaiting CFO sign-off (within delegation matrix)." },
|
||||
],
|
||||
[
|
||||
{ stepId: "review", title: "RF-0091 · CFO approval", waitingOn: "approval", ageDays: 0, status: "running" },
|
||||
{ stepId: "review", title: "RF-0088 · CFO approval", waitingOn: "approval", ageDays: 1, status: "running" },
|
||||
{ stepId: "risk", title: "RF-0093 · risk scoring", waitingOn: "agent", ageDays: 0, status: "running" },
|
||||
{ stepId: "credit_memo", title: "RF-0085 · post credit memo", waitingOn: "input", ageDays: 0, status: "queued" },
|
||||
{ stepId: "notify", title: "RF-0079 · notify customer", waitingOn: "input", ageDays: 2, status: "errored" },
|
||||
],
|
||||
[
|
||||
{ stepId: "review", status: "awaiting-confirm", intent: "Approve refund RF-0091 ($1,840) — within CFO delegation." },
|
||||
],
|
||||
[
|
||||
{ id: "RF-0091", shortId: "RF-0091", activeStep: "Finance review", status: "running", startedAt: new Date(Date.now() - 1000 * 60 * 12).toISOString(), durationSec: 720 },
|
||||
{ id: "RF-0088", shortId: "RF-0088", activeStep: "Finance review", status: "running", startedAt: new Date(Date.now() - 1000 * 60 * 60 * 26).toISOString(), durationSec: 93600 },
|
||||
{ id: "RF-0085", shortId: "RF-0085", activeStep: "Post credit memo", status: "queued", startedAt: new Date(Date.now() - 1000 * 60 * 60 * 4).toISOString(), durationSec: 14400 },
|
||||
{ id: "RF-0079", shortId: "RF-0079", activeStep: "Notify customer", status: "errored", startedAt: new Date(Date.now() - 1000 * 60 * 60 * 50).toISOString(), durationSec: 180000 },
|
||||
],
|
||||
[
|
||||
{ label: "Open refunds", value: "23", trend: "up", trendValue: "+4 wk" },
|
||||
{ label: "Avg cycle", value: "1.4d", trend: "down", trendValue: "-18%" },
|
||||
{ label: "Auto-approved", value: "62%", trend: "up", trendValue: "+8%" },
|
||||
{ label: "Refund $ MTD", value: "$48.3k", trend: "flat" },
|
||||
],
|
||||
baseTour("AR"),
|
||||
);
|
||||
|
||||
// ---------- HCM · New Hire Onboarding ----------
|
||||
export const hcmOnboarding = build(
|
||||
"hcm",
|
||||
{ id: "hcm", label: "People Operations", subtitle: "Onboard · Offboard · Leave", accent: "#a855f7" },
|
||||
"syn-hcm-onboarding-v1",
|
||||
"HCM · New Hire Onboarding",
|
||||
"Day-0 → Day-30 onboarding · synthetic preview",
|
||||
[
|
||||
{ id: "offer", name: "Offer accepted", kind: "start", owner: "system", state: "done" },
|
||||
{ id: "paperwork", name: "Collect paperwork (I-9, W-4)", kind: "human", owner: "human", state: "done", actions: [{ id: "complete", label: "Submit", kind: "complete" }] },
|
||||
{ id: "background", name: "Background check", kind: "agent", owner: "agent", state: "done" },
|
||||
{ id: "provision", name: "Provision laptop & accounts", kind: "service", owner: "system", state: "running" },
|
||||
{ id: "buddy", name: "Assign buddy", kind: "human", owner: "human", state: "queued", actions: [{ id: "complete", label: "Assign", kind: "complete" }] },
|
||||
{ id: "day1", name: "Day-1 welcome", kind: "human", owner: "human", state: "idle", actions: [{ id: "complete", label: "Confirm", kind: "complete" }] },
|
||||
{ id: "training", name: "Generate role-specific training plan", kind: "agent", owner: "agent", state: "idle" },
|
||||
{ id: "checkin", name: "30-day check-in", kind: "human", owner: "human", state: "idle", actions: [{ id: "complete", label: "Complete check-in", kind: "complete" }] },
|
||||
{ id: "ramped", name: "Onboarding complete", kind: "end", owner: "system", state: "idle" },
|
||||
],
|
||||
[
|
||||
{ id: "e1", source: "offer", target: "paperwork" },
|
||||
{ id: "e2", source: "paperwork", target: "background" },
|
||||
{ id: "e3", source: "background", target: "provision" },
|
||||
{ id: "e4", source: "provision", target: "buddy" },
|
||||
{ id: "e5", source: "buddy", target: "day1" },
|
||||
{ id: "e6", source: "day1", target: "training" },
|
||||
{ id: "e7", source: "training", target: "checkin" },
|
||||
{ id: "e8", source: "checkin", target: "ramped" },
|
||||
],
|
||||
[
|
||||
{ id: "provision-sla", name: "Provisioning SLA", expr: "IT request open > 3 business days → page IT-Ops on-call", isSynthetic: true },
|
||||
{ id: "training-cap", name: "Training plan cap", expr: "training plan > 12 modules → manager review", isSynthetic: true },
|
||||
],
|
||||
[
|
||||
{ stepId: "offer", at: "Mon 10:01", actor: "hcm-ats", summary: "Riley Park accepted offer for SWE-3 (Eng / Platform)" },
|
||||
{ stepId: "paperwork", at: "Mon 11:34", actor: "Riley Park", summary: "Submitted I-9 + W-4, signed offer letter" },
|
||||
{ stepId: "background", at: "Tue 08:09", actor: "agent:bg-check", summary: "Background check clear (Checkr report #BC-91244)" },
|
||||
{ stepId: "provision", at: "Tue 08:12", actor: "service:it-provision", summary: "Laptop ordered (MBP 16, M4 Pro); Okta + Slack + Linear seats reserved" },
|
||||
],
|
||||
[
|
||||
{ stepId: "provision", title: "Riley Park · provisioning", waitingOn: "input", ageDays: 0, status: "running" },
|
||||
{ stepId: "buddy", title: "Sam Diaz · buddy assignment", waitingOn: "approval", ageDays: 1, status: "running" },
|
||||
{ stepId: "day1", title: "Jordan Wu · Day-1 welcome", waitingOn: "approval", ageDays: 0, status: "queued" },
|
||||
{ stepId: "checkin", title: "Alex Kim · 30-day check-in", waitingOn: "approval", ageDays: 14, status: "running" },
|
||||
],
|
||||
[
|
||||
{ stepId: "training", status: "proposed", intent: "Draft 8-module training plan for Riley Park (SWE-3, Platform team)" },
|
||||
],
|
||||
[
|
||||
{ id: "H-0091", shortId: "Riley Park", activeStep: "Provision laptop & accounts", status: "running", startedAt: new Date(Date.now() - 1000 * 60 * 60 * 28).toISOString(), durationSec: 100800 },
|
||||
{ id: "H-0090", shortId: "Sam Diaz", activeStep: "Assign buddy", status: "running", startedAt: new Date(Date.now() - 1000 * 60 * 60 * 52).toISOString(), durationSec: 187200 },
|
||||
{ id: "H-0088", shortId: "Jordan Wu", activeStep: "Day-1 welcome", status: "queued", startedAt: new Date(Date.now() - 1000 * 60 * 60 * 96).toISOString(), durationSec: 345600 },
|
||||
{ id: "H-0079", shortId: "Alex Kim", activeStep: "30-day check-in", status: "running", startedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 28).toISOString(), durationSec: 2419200 },
|
||||
],
|
||||
[
|
||||
{ label: "Active onboardings", value: "11", trend: "up", trendValue: "+3 wk" },
|
||||
{ label: "Avg time-to-ramp", value: "28d", trend: "down", trendValue: "-4d" },
|
||||
{ label: "On-time provisioning", value: "94%", trend: "up", trendValue: "+6%" },
|
||||
{ label: "Buddy assigned <24h", value: "82%", trend: "flat" },
|
||||
],
|
||||
baseTour("HCM"),
|
||||
);
|
||||
|
||||
// ---------- GL · Period-End Close ----------
|
||||
export const glClose = build(
|
||||
"gl",
|
||||
{ id: "gl", label: "GL Close", subtitle: "Accruals, reconciliations, journals", accent: "#f59e0b" },
|
||||
"syn-gl-close-v1",
|
||||
"GL · Period-End Close",
|
||||
"Period-end close · automated accrual collection · synthetic preview",
|
||||
[
|
||||
{ id: "cutoff", name: "Period cutoff", kind: "start", owner: "system", state: "done" },
|
||||
{ id: "accruals", name: "Collect accruals from subledgers", kind: "agent", owner: "agent", state: "done" },
|
||||
{ id: "bank_rec", name: "Bank reconciliation", kind: "service", owner: "system", state: "done" },
|
||||
{ id: "intercompany", name: "Intercompany match", kind: "service", owner: "system", state: "running" },
|
||||
{ id: "journals", name: "Journal entry review", kind: "human", owner: "human", state: "queued", governs: ["journal-threshold"], actions: [{ id: "post", label: "Post journals", kind: "complete" }] },
|
||||
{ id: "variance", name: "Variance analysis & narrative", kind: "agent", owner: "agent", state: "idle" },
|
||||
{ id: "signoff", name: "CFO sign-off", kind: "human", owner: "human", state: "idle", governs: ["signoff-quorum"], actions: [{ id: "signoff", label: "Sign off", kind: "approve" }] },
|
||||
{ id: "closed", name: "Books closed", kind: "end", owner: "system", state: "idle" },
|
||||
],
|
||||
[
|
||||
{ id: "e1", source: "cutoff", target: "accruals" },
|
||||
{ id: "e2", source: "accruals", target: "bank_rec" },
|
||||
{ id: "e3", source: "bank_rec", target: "intercompany" },
|
||||
{ id: "e4", source: "intercompany", target: "journals" },
|
||||
{ id: "e5", source: "journals", target: "variance" },
|
||||
{ id: "e6", source: "variance", target: "signoff" },
|
||||
{ id: "e7", source: "signoff", target: "closed" },
|
||||
],
|
||||
[
|
||||
{ id: "journal-threshold", name: "Journal threshold", expr: "any journal > $50k → controller review", isSynthetic: true },
|
||||
{ id: "signoff-quorum", name: "Sign-off quorum", expr: "requires CFO + Controller", isSynthetic: true },
|
||||
],
|
||||
[
|
||||
{ stepId: "cutoff", at: "Mon 18:00", actor: "scheduler", summary: "Period 2026-06 cut over; subledger snapshots locked" },
|
||||
{ stepId: "accruals", at: "Mon 18:14", actor: "agent:accrual-bot", summary: "Collected 1,442 accrual lines from AP, AR, Payroll, Lease subledgers" },
|
||||
{ stepId: "bank_rec", at: "Mon 19:02", actor: "service:bank-rec", summary: "Reconciled 18 bank accounts; 3 timing exceptions auto-resolved" },
|
||||
{ stepId: "intercompany", at: "now", actor: "service:ic-match", summary: "Matching 432 IC pairs across 7 entities (running)" },
|
||||
],
|
||||
[
|
||||
{ stepId: "intercompany", title: "P-2026-06 · IC matching", waitingOn: "agent", ageDays: 0, status: "running" },
|
||||
{ stepId: "journals", title: "P-2026-06 · 11 journals to review", waitingOn: "approval", ageDays: 0, status: "queued" },
|
||||
{ stepId: "signoff", title: "P-2026-05 · CFO sign-off", waitingOn: "approval", ageDays: 2, status: "running" },
|
||||
{ stepId: "journals", title: "P-2026-05 · 2 journals re-open", waitingOn: "approval", ageDays: 4, status: "errored" },
|
||||
],
|
||||
[
|
||||
{ stepId: "variance", status: "proposed", intent: "Generate variance narrative for OpEx +14% vs forecast (drivers: cloud spend, headcount)" },
|
||||
],
|
||||
[
|
||||
{ id: "P-2026-06", shortId: "Jun 2026", activeStep: "Intercompany match", status: "running", startedAt: new Date(Date.now() - 1000 * 60 * 60 * 3).toISOString(), durationSec: 10800 },
|
||||
{ id: "P-2026-05", shortId: "May 2026", activeStep: "CFO sign-off", status: "running", startedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 6).toISOString(), durationSec: 518400 },
|
||||
{ id: "P-2026-04", shortId: "Apr 2026", activeStep: "Books closed", status: "completed", startedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 38).toISOString(), durationSec: 86400 * 5 },
|
||||
{ id: "P-2026-03", shortId: "Mar 2026", activeStep: "Books closed", status: "completed", startedAt: new Date(Date.now() - 1000 * 60 * 60 * 24 * 70).toISOString(), durationSec: 86400 * 6 },
|
||||
],
|
||||
[
|
||||
{ label: "Days in current close", value: "1.1d", trend: "down", trendValue: "-0.4d" },
|
||||
{ label: "Journals auto-posted", value: "78%", trend: "up", trendValue: "+5%" },
|
||||
{ label: "IC mismatches", value: "12", trend: "down", trendValue: "-8" },
|
||||
{ label: "Close on-time rate", value: "96%", trend: "flat" },
|
||||
],
|
||||
baseTour("GL"),
|
||||
);
|
||||
|
||||
// ---------- Service Ops · Customer Incident ----------
|
||||
export const svcIncident = build(
|
||||
"service",
|
||||
{ id: "service", label: "Service Operations", subtitle: "Tickets, incidents, support", accent: "#ef4444" },
|
||||
"syn-svc-incident-v1",
|
||||
"Service Ops · Customer Incident",
|
||||
"Triage → assign → resolve · synthetic preview",
|
||||
[
|
||||
{ id: "report", name: "Customer reports incident", kind: "start", owner: "system", state: "done" },
|
||||
{ id: "triage", name: "Auto-triage & priority", kind: "agent", owner: "agent", state: "done" },
|
||||
{ id: "assign", name: "Assign engineer", kind: "human", owner: "human", state: "done", actions: [{ id: "assign", label: "Assign", kind: "complete" }] },
|
||||
{ id: "investigate", name: "Investigate & resolve", kind: "human", owner: "human", state: "running", governs: ["sla-p1"], actions: [{ id: "resolved", label: "Mark resolved", kind: "complete" }] },
|
||||
{ id: "confirm", name: "Customer confirms", kind: "human", owner: "human", state: "queued", actions: [{ id: "confirm", label: "Confirm closed", kind: "complete" }] },
|
||||
{ id: "rca", name: "Generate RCA", kind: "agent", owner: "agent", state: "idle" },
|
||||
{ id: "closed", name: "Incident closed", kind: "end", owner: "system", state: "idle" },
|
||||
],
|
||||
[
|
||||
{ id: "e1", source: "report", target: "triage" },
|
||||
{ id: "e2", source: "triage", target: "assign" },
|
||||
{ id: "e3", source: "assign", target: "investigate" },
|
||||
{ id: "e4", source: "investigate", target: "confirm" },
|
||||
{ id: "e5", source: "confirm", target: "rca" },
|
||||
{ id: "e6", source: "rca", target: "closed" },
|
||||
],
|
||||
[
|
||||
{ id: "sla-p1", name: "P1 SLA", expr: "P1 unresolved > 4h → page eng-lead", isSynthetic: true },
|
||||
],
|
||||
[
|
||||
{ stepId: "report", at: "07:21", actor: "customer-portal", summary: "Acme Corp reports INC-4427: dashboard 500 on /reports/financial" },
|
||||
{ stepId: "triage", at: "07:21", actor: "agent:triage", summary: "P1 (revenue impact). Routed to platform-on-call. Linked recent deploy #d3f1a." },
|
||||
{ stepId: "assign", at: "07:22", actor: "Sam (oncall)", summary: "Acked. Investigating deploy rollback path." },
|
||||
{ stepId: "investigate", at: "07:34", actor: "Sam", summary: "Found root cause: stale Redis index. Rolling fix #PR-1924." },
|
||||
],
|
||||
[
|
||||
{ stepId: "investigate", title: "INC-4427 · platform 500s", waitingOn: "input", ageDays: 0, status: "running" },
|
||||
{ stepId: "investigate", title: "INC-4426 · webhook 502 spike", waitingOn: "input", ageDays: 0, status: "running" },
|
||||
{ stepId: "confirm", title: "INC-4421 · awaiting customer confirm", waitingOn: "approval", ageDays: 1, status: "queued" },
|
||||
{ stepId: "rca", title: "INC-4418 · RCA draft", waitingOn: "agent", ageDays: 2, status: "running" },
|
||||
],
|
||||
[
|
||||
{ stepId: "rca", status: "running", intent: "Drafting RCA for INC-4418 (auth token expiry race). 3 mitigations proposed." },
|
||||
],
|
||||
[
|
||||
{ id: "INC-4427", shortId: "INC-4427", activeStep: "Investigate & resolve", status: "running", startedAt: new Date(Date.now() - 1000 * 60 * 35).toISOString(), durationSec: 2100 },
|
||||
{ id: "INC-4426", shortId: "INC-4426", activeStep: "Investigate & resolve", status: "running", startedAt: new Date(Date.now() - 1000 * 60 * 90).toISOString(), durationSec: 5400 },
|
||||
{ id: "INC-4421", shortId: "INC-4421", activeStep: "Customer confirms", status: "queued", startedAt: new Date(Date.now() - 1000 * 60 * 60 * 26).toISOString(), durationSec: 93600 },
|
||||
{ id: "INC-4418", shortId: "INC-4418", activeStep: "Generate RCA", status: "running", startedAt: new Date(Date.now() - 1000 * 60 * 60 * 48).toISOString(), durationSec: 172800 },
|
||||
],
|
||||
[
|
||||
{ label: "Open incidents", value: "7", trend: "down", trendValue: "-2" },
|
||||
{ label: "P1 MTTR", value: "47m", trend: "down", trendValue: "-22%" },
|
||||
{ label: "Auto-triage acc.", value: "91%", trend: "up", trendValue: "+3%" },
|
||||
{ label: "SLA compliance", value: "99.1%", trend: "flat" },
|
||||
],
|
||||
baseTour("Service"),
|
||||
);
|
||||
|
||||
export const syntheticScenarios: ProcessScenario[] = [arRefund, hcmOnboarding, glClose, svcIncident];
|
||||
@@ -0,0 +1,125 @@
|
||||
// Unified domain types for Mission Control.
|
||||
// Backed either by a live EA2 read (scenarios.json) or by synthetic
|
||||
// hand-crafted scenarios (synthetic.ts) — both render the same way.
|
||||
|
||||
export type StepKind = "start" | "human" | "agent" | "service" | "decision" | "end";
|
||||
export type RuntimeState = "done" | "running" | "queued" | "blocked" | "idle" | "errored";
|
||||
|
||||
export interface StepAction {
|
||||
id: string;
|
||||
label: string;
|
||||
kind: "complete" | "approve" | "decline" | "fork";
|
||||
}
|
||||
|
||||
export interface ProcessStep {
|
||||
id: string;
|
||||
name: string;
|
||||
kind: StepKind;
|
||||
owner: "human" | "agent" | "system";
|
||||
/** Optional governing rule ids that apply at this step. */
|
||||
governs: string[];
|
||||
/** Best-guess runtime state of this step in the headline transaction. */
|
||||
state: RuntimeState;
|
||||
/** Display name from EA2 if available. */
|
||||
raw?: unknown;
|
||||
actions: StepAction[];
|
||||
}
|
||||
|
||||
export interface FlowEdge {
|
||||
id: string;
|
||||
source: string;
|
||||
target: string;
|
||||
/** Edge outcome label (e.g. "approve", "decline"). */
|
||||
label?: string;
|
||||
/** True if both endpoints have been traversed in the headline run. */
|
||||
traversed?: boolean;
|
||||
}
|
||||
|
||||
export interface Rule {
|
||||
id: string;
|
||||
name: string;
|
||||
expr: string;
|
||||
isSynthetic?: boolean;
|
||||
}
|
||||
|
||||
export interface EvidenceItem {
|
||||
id: string;
|
||||
stepId: string;
|
||||
at: string;
|
||||
actor: string;
|
||||
summary: string;
|
||||
isSynthetic?: boolean;
|
||||
}
|
||||
|
||||
export interface QueueItem {
|
||||
id: string;
|
||||
stepId: string;
|
||||
title: string;
|
||||
waitingOn: "approval" | "agent" | "input";
|
||||
ageDays: number;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface AgentRun {
|
||||
id: string;
|
||||
stepId: string;
|
||||
status: "proposed" | "running" | "awaiting-confirm" | "done";
|
||||
intent: string;
|
||||
isSynthetic?: boolean;
|
||||
}
|
||||
|
||||
export interface RunSummary {
|
||||
id: string;
|
||||
shortId: string;
|
||||
/** Active step display name at the time of the snapshot. */
|
||||
activeStep: string | null;
|
||||
status: RuntimeState | string;
|
||||
startedAt: string;
|
||||
durationSec: number;
|
||||
}
|
||||
|
||||
export interface TourStep {
|
||||
id: string;
|
||||
/** Selector or named anchor in the layout. Used by Tour to position the bubble. */
|
||||
anchor: "graph" | "queue" | "inspector" | "topbar" | "telemetry" | "command";
|
||||
/** Step id to auto-select while this tour step is active. */
|
||||
selectStep?: string;
|
||||
/** Switch to another scenario when entering this step. */
|
||||
switchToScenario?: string;
|
||||
title: string;
|
||||
body: string;
|
||||
}
|
||||
|
||||
export interface ProcessScenario {
|
||||
id: string;
|
||||
family: {
|
||||
id: string;
|
||||
label: string;
|
||||
subtitle: string;
|
||||
accent: string;
|
||||
};
|
||||
/** True if backed by a real EA2 read; false if hand-crafted in synthetic.ts. */
|
||||
live: boolean;
|
||||
defKey: string;
|
||||
defName: string;
|
||||
version: string;
|
||||
/** Headline transaction id for the runtime trace. */
|
||||
headlineTx: string | null;
|
||||
/** Brand short subtitle for the inspector hero. */
|
||||
tagline: string;
|
||||
steps: ProcessStep[];
|
||||
edges: FlowEdge[];
|
||||
rules: Rule[];
|
||||
evidence: EvidenceItem[];
|
||||
queue: QueueItem[];
|
||||
agentRuns: AgentRun[];
|
||||
runs: RunSummary[];
|
||||
/** Compact KPI strip data for Mission Control. */
|
||||
kpis: { label: string; value: string; trend?: "up" | "down" | "flat"; trendValue?: string }[];
|
||||
/** Default selected step id. */
|
||||
defaultStepId: string;
|
||||
/** Tour script tailored to this scenario. */
|
||||
tour: TourStep[];
|
||||
/** Raw graph object from EA2 (or synthesised) for the JSON inspector tab. */
|
||||
raw: unknown;
|
||||
}
|
||||
Reference in New Issue
Block a user