From 9b0eb551f49afd992772d54e2f968e17049e236a Mon Sep 17 00:00:00 2001 From: Ryan Dombrowski Date: Thu, 2 Jul 2026 12:56:12 -0400 Subject: [PATCH] =?UTF-8?q?feat:=20flagship=20demo=20=E2=80=94=20governed?= =?UTF-8?q?=20Generate=20view=20(M1=20PR-7,=20demo=20side)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit demo Generate view driven by `dspack-gen serve` (localhost NDJSON): - input bar: prompt / intent / model (ollama: | anthropic:) / fake-pipeline toggle (the deterministic scripted adapter the Playwright gate drives) - per-attempt panels: gate strip S1 surface-schema / S2 contract-vocabulary / S3 governance; findings with rule id, location, message, and the contract's rationale VERBATIM; on a clean attempt the S3 card lists every applicable rule as satisfied — verified by the linter, not assumed from prompt steering - the exact repair message sent (rendered from the same findings object) - emitter gate strip A1/A2/A3 per A2UI version - outcome panel: the emitted surface rendered off the generated catalog via @a2ui/react v0.9.1 (dspack tokens driving the theme), audit report v1 download; failures render honestly with no surface Verified in-browser: fake pipeline shows violation → repair → all gates green → AlertDialog renders and opens with cancel-before-confirm. Co-Authored-By: Claude Fable 5 --- demo/src/App.tsx | 29 +++- demo/src/GenerateView.tsx | 298 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 324 insertions(+), 3 deletions(-) create mode 100644 demo/src/GenerateView.tsx diff --git a/demo/src/App.tsx b/demo/src/App.tsx index 29f6dbe..064dcbc 100644 --- a/demo/src/App.tsx +++ b/demo/src/App.tsx @@ -3,6 +3,7 @@ import { MessageProcessor, type A2uiClientAction } from "@a2ui/web_core/v0_9"; import { A2uiSurface, MarkdownContext, type ReactComponentImplementation } from "@a2ui/react/v0_9"; import { renderMarkdown } from "@a2ui/markdown-it"; import { buildCatalog, registry } from "./ingest"; +import { GenerateView } from "./GenerateView"; // The exact same artifacts the transformer produced — imported, not duplicated. import settingsSurface from "../../surface/settings-card.surface.json"; @@ -68,6 +69,7 @@ function a2uiThemeVars(cat: AnyCatalog): React.CSSProperties { } export function App() { + const [view, setView] = useState<"surfaces" | "generate">("surfaces"); const [surfaceKey, setSurfaceKey] = useState("access"); const [surface, setSurface] = useState(null); const [actions, setActions] = useState([]); @@ -107,22 +109,42 @@ export function App() { {Object.entries(SURFACES).map(([key, s]) => ( ))} + + {view === "generate" ? ( + + ) : (
{/* Rendered surface */}
@@ -230,6 +252,7 @@ export function App() {
+ )} ); } diff --git a/demo/src/GenerateView.tsx b/demo/src/GenerateView.tsx new file mode 100644 index 0000000..58d6d59 --- /dev/null +++ b/demo/src/GenerateView.tsx @@ -0,0 +1,298 @@ +/** + * The flagship governed-generation view (M1 PR-7). + * + * Talks to `dspack-gen serve` (localhost NDJSON) and replays the pipeline on + * screen: attempt panels with the surface gates S1/S2/S3 (findings shown with + * the contract's rationale verbatim), the exact repair message sent, the + * emitter gates A1/A2/A3 per A2UI version, the rendered surface (off the + * generated catalog, as everywhere in this demo), and the downloadable audit + * report v1 — the full trail: prompt → violation → repair → validated output. + * + * "Fake pipeline" mode runs the server's deterministic scripted adapter (the + * golden violating fixture, then the contract's worked example) — the same + * backend the Playwright gate drives. Live mode needs Ollama (or a hosted + * model) behind the serve process; the UI is identical. + */ +import { useMemo, useState } from "react"; +import { MessageProcessor } from "@a2ui/web_core/v0_9"; +import { A2uiSurface, MarkdownContext, type ReactComponentImplementation } from "@a2ui/react/v0_9"; +import { renderMarkdown } from "@a2ui/markdown-it"; +import type { buildCatalog } from "./ingest"; + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +interface GateReport { + gate: "S1" | "S2" | "S3"; + name: string; + status: "PASS" | "FAIL" | "SKIPPED"; + errors?: string[]; +} +interface Finding { + ruleId: string; + type: string; + requirement: string; + level: string; + message: string; + rationale: string; + location: { path: string; component: string; nodeId?: string }; + exampleIds: string[]; +} +type PipelineEvent = + | { type: "start"; intent: string; prompt: string; adapterId: string; ruleIds: string[] } + | { type: "attempt"; index: number; model?: string; surface: unknown; gates: GateReport[]; findings: Finding[] } + | { type: "repair"; index: number; message: string } + | { type: "emitted"; validations: Array<{ a2uiVersion: string; gates: Array<{ gate: string; name: string; pass: boolean }> }>; warnings: Array<{ code: string; message: string }> } + | { type: "done"; outcome: string; exitCode: number; report: unknown; surfaceMessages?: { messages: unknown[] } } + | { type: "error"; exitCode: number; message: string }; + +const GATE_LABEL: Record = { + S1: "surface schema", + S2: "contract vocabulary", + S3: "governance", +}; + +export function GenerateView({ + ingested, + themeVars, +}: { + ingested: ReturnType; + themeVars: React.CSSProperties; +}) { + const [serveUrl, setServeUrl] = useState("http://127.0.0.1:8787"); + const [prompt, setPrompt] = useState("a screen to delete my account"); + const [model, setModel] = useState("ollama:qwen3:8b"); + const [fake, setFake] = useState(false); + const [running, setRunning] = useState(false); + const [error, setError] = useState(null); + const [events, setEvents] = useState([]); + + const done = events.find((e): e is Extract => e.type === "done"); + const start = events.find((e): e is Extract => e.type === "start"); + + const rendered = useMemo(() => { + if (!done?.surfaceMessages) return null; + const processor = new MessageProcessor([ingested.catalog], async () => {}); + processor.processMessages(structuredClone(done.surfaceMessages.messages) as any); + const model = Array.from(processor.model.surfacesMap.values())[0]; + return model ? processor.model.getSurface(model.id) : null; + }, [done, ingested]); + + async function run() { + setRunning(true); + setError(null); + setEvents([]); + try { + const response = await fetch(`${serveUrl}/run`, { + method: "POST", + headers: { "content-type": "application/json" }, + body: JSON.stringify(fake ? { prompt, fake: true } : { prompt, model }), + }); + if (!response.ok || !response.body) throw new Error(`serve responded ${response.status}`); + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let buffer = ""; + for (;;) { + const { done: eof, value } = await reader.read(); + if (value) buffer += decoder.decode(value, { stream: true }); + const lines = buffer.split("\n"); + buffer = lines.pop() ?? ""; + for (const line of lines) { + if (line.trim()) setEvents((prev) => [...prev, JSON.parse(line) as PipelineEvent]); + } + if (eof) break; + } + } catch (e) { + setError(e instanceof Error ? e.message : String(e)); + } finally { + setRunning(false); + } + } + + const attempts = events.filter((e): e is Extract => e.type === "attempt"); + const repairs = events.filter((e): e is Extract => e.type === "repair"); + const emitted = events.find((e): e is Extract => e.type === "emitted"); + const pipelineError = events.find((e): e is Extract => e.type === "error"); + + return ( +
+
+

Governed generation

+

+ The prompt goes to dspack-gen serve: schema-constrained generation → surface gates + (S1 schema · S2 vocabulary · S3 governance) → bounded repair → A2UI emission → emitter gates + (A1–A3) → audit report. The rules and rationales on screen come verbatim from the dspack contract. +

+
+ +
+ + + + + +
+
+ {error && ( +

+ {error} — is dspack-gen serve running? +

+ )} + {pipelineError && ( +

+ pipeline error (exit {pipelineError.exitCode}): {pipelineError.message} +

+ )} +
+ + {attempts.map((attempt) => ( +
+

+ Attempt {attempt.index + 1} + {attempt.model ? · {attempt.model} : null} +

+
+ {attempt.gates.map((gate) => ( + + {gate.gate} {GATE_LABEL[gate.gate]}: {gate.status} + + ))} +
+ + {attempt.findings.length > 0 ? ( + attempt.findings.map((finding, i) => ( +
+
+ ✖ {finding.level} [{finding.requirement}] {finding.ruleId}{" "} + [{finding.type}] +
+
+ at {finding.location.path} (component: {finding.location.component} + {finding.location.nodeId ? `, id: "${finding.location.nodeId}"` : ""}) +
+
{finding.message}
+
Rationale: {finding.rationale}
+
+ )) + ) : ( +
+ {(start?.ruleIds ?? []).map((ruleId) => ( +
+ ✓ {ruleId} satisfied — verified by the governance linter, not assumed from + prompt steering. +
+ ))} +
+ )} + + {repairs.find((r) => r.index === attempt.index) && ( +
+ + Repair feedback sent to the model (rendered from the same findings object) + +
{repairs.find((r) => r.index === attempt.index)!.message}
+
+ )} +
+ ))} + + {emitted && ( +
+

Emitted to A2UI

+
+ {emitted.validations.flatMap((validation) => + validation.gates.map((gate) => ( + + [{validation.a2uiVersion}] {gate.gate} {gate.name}: {gate.pass ? "PASS" : "FAIL"} + + )), + )} +
+ {emitted.warnings.length > 0 && ( +

+ Emitter notes (every synthesis is recorded — nothing silent):{" "} + {emitted.warnings.map((w) => w.code).join(", ")} +

+ )} +
+ )} + + {done && ( +
+

+ Outcome:{" "} + {done.outcome} +

+ {rendered && ( +
+ + + +
+ )} +

+ {rendered + ? "Rendered off the generated catalog through @a2ui/react v0.9.1 — dspack tokens drive the theme." + : "No surface rendered — the pipeline reports failure honestly instead of degrading."} +

+ +
+ )} +
+ ); +} + +const s: Record = { + card: { border: "1px solid #e2e8f0", borderRadius: 12, padding: 20, background: "#fff", marginBottom: 16 }, + h2: { fontSize: 16, marginTop: 0 }, + note: { fontSize: 12.5, color: "#64748b", lineHeight: 1.5 }, + dim: { color: "#64748b", fontWeight: 400, fontSize: 12.5 }, + label: { display: "flex", flexDirection: "column", gap: 4, fontSize: 12, color: "#475569", flex: 1, minWidth: 160 }, + input: { border: "1px solid #cbd5e1", borderRadius: 6, padding: "6px 8px", fontSize: 13, fontFamily: "inherit" }, + runBtn: { background: "#0f172a", color: "#fff", border: "none", borderRadius: 6, padding: "8px 16px", cursor: "pointer", fontSize: 13 }, + chip: { borderRadius: 999, padding: "3px 10px", fontSize: 12, fontWeight: 600 }, + chipPass: { background: "#dcfce7", color: "#166534" }, + chipFail: { background: "#fee2e2", color: "#991b1b" }, + chipSkip: { background: "#f1f5f9", color: "#64748b" }, + finding: { borderLeft: "3px solid #dc2626", padding: "6px 10px", margin: "6px 0", fontSize: 13, background: "#fafafa" }, + rationale: { color: "#64748b", fontSize: 12.5, marginTop: 2 }, + pre: { background: "#0f172a", color: "#e2e8f0", padding: 12, borderRadius: 8, fontSize: 11.5, whiteSpace: "pre-wrap", maxHeight: 260, overflow: "auto" }, + surfaceFrame: { border: "1px dashed #cbd5e1", borderRadius: 8, padding: 16, minHeight: 160, background: "#f8fafc", marginTop: 8 }, +};