diff --git a/.changeset/resumable-petrinaut-workpiece.md b/.changeset/resumable-petrinaut-workpiece.md new file mode 100644 index 00000000000..4387d484672 --- /dev/null +++ b/.changeset/resumable-petrinaut-workpiece.md @@ -0,0 +1,7 @@ +--- +"@hashintel/petrinaut": patch +"@hashintel/petrinaut-core": patch +--- + +Report duplicate AI mutations as no-ops so hosts can distinguish an applied document change from +an already-present state when resuming a correlated browser tool call. diff --git a/apps/brunch-agent/src/conversation/client-tools.ts b/apps/brunch-agent/src/conversation/client-tools.ts index c238c9e3215..68b48d43b5d 100644 --- a/apps/brunch-agent/src/conversation/client-tools.ts +++ b/apps/brunch-agent/src/conversation/client-tools.ts @@ -1,6 +1,9 @@ /** Flue-side client-tool signal contract: awaiting sentinel, result signal, tool names. */ -import { READ_PETRINAUT_DOC_TOOL_NAME } from "@hashintel/brunch-agent-plugin-sdcpn/flue"; +import { + petrinautFixtureToolNames, + READ_PETRINAUT_DOC_TOOL_NAME, +} from "@hashintel/brunch-agent-plugin-sdcpn/flue"; import { CLIENT_TOOL_RESULT_SIGNAL } from "@hashintel/brunch-agent-transport-aisdk"; import { AWAITING_CLIENT } from "@hashintel/brunch-agent/client-tools"; @@ -9,6 +12,7 @@ export { CLIENT_TOOL_RESULT_SIGNAL }; export const clientToolNames: ReadonlySet = new Set([ READ_PETRINAUT_DOC_TOOL_NAME, + ...petrinautFixtureToolNames, ]); const isRecord = (value: unknown): value is Record => diff --git a/apps/brunch-agent/src/conversation/workpiece.ts b/apps/brunch-agent/src/conversation/workpiece.ts new file mode 100644 index 00000000000..d8ac8ae9b69 --- /dev/null +++ b/apps/brunch-agent/src/conversation/workpiece.ts @@ -0,0 +1,47 @@ +/** Recover the current Markdown workpiece from canonical Flue history. */ + +import { createHash } from "node:crypto"; + +import { selectRunbookWorkpiece } from "@hashintel/brunch-agent/workpiece"; + +import type { FlueConversationSnapshot } from "@flue/sdk"; + +const sha256 = (value: string): string => + createHash("sha256").update(value).digest("hex"); + +export interface RecoveredRunbookWorkpiece { + readonly authorship: "model-produced" | "test-authored"; + readonly content: string; + readonly fixtureId?: string; + readonly sha256: string; + readonly sourceKind: "assistant" | "prepared-signal"; + readonly sourceMessageId: string; + readonly sourceMessageSha256: string; + readonly sourceSubmissionId?: string; +} + +/** + * Add content and source hashes to the substrate-neutral current-workpiece + * selection used by both evaluations and the browser fixture. + */ +export const recoverRunbookWorkpiece = ( + snapshot: FlueConversationSnapshot, +): RecoveredRunbookWorkpiece | undefined => { + const selected = selectRunbookWorkpiece(snapshot); + if (selected === undefined) return undefined; + + return { + authorship: selected.authorship, + content: selected.content, + ...(selected.fixtureId === undefined + ? {} + : { fixtureId: selected.fixtureId }), + sha256: sha256(selected.content), + sourceKind: selected.sourceKind, + sourceMessageId: selected.sourceMessageId, + sourceMessageSha256: sha256(JSON.stringify(selected.sourceMessage)), + ...(selected.sourceSubmissionId === undefined + ? {} + : { sourceSubmissionId: selected.sourceSubmissionId }), + }; +}; diff --git a/apps/brunch-agent/src/evaluations/persona/proof-artifacts.ts b/apps/brunch-agent/src/evaluations/persona/proof-artifacts.ts index 0c3c71409e7..8d5d32c67a7 100644 --- a/apps/brunch-agent/src/evaluations/persona/proof-artifacts.ts +++ b/apps/brunch-agent/src/evaluations/persona/proof-artifacts.ts @@ -9,7 +9,7 @@ import { import { isAwaitingClient } from "../../conversation/client-tools.ts"; import { formatFlueTranscript } from "../../conversation/transcript.ts"; -import { recoverRunbookWorkpiece } from "../runbook/artifacts.ts"; +import { recoverRunbookWorkpiece } from "../../conversation/workpiece.ts"; interface ProofEventBase { readonly sequence: number; diff --git a/apps/brunch-agent/src/evaluations/runbook/artifacts.ts b/apps/brunch-agent/src/evaluations/runbook/artifacts.ts index 05a50b46847..fb1923706d2 100644 --- a/apps/brunch-agent/src/evaluations/runbook/artifacts.ts +++ b/apps/brunch-agent/src/evaluations/runbook/artifacts.ts @@ -2,56 +2,16 @@ import { basename } from "node:path"; -import { sha256 } from "./campaign-integrity.ts"; +import { runbookIrFence } from "@hashintel/brunch-agent/workpiece"; -import type { FlueConversationPart, FlueConversationSnapshot } from "@flue/sdk"; +import { recoverRunbookWorkpiece } from "../../conversation/workpiece.ts"; -export const RUNBOOK_IR_FENCE = "runbook-ir"; - -const runbookIrFencePattern = /```runbook-ir\s*\n([\s\S]*?)```/g; - -export const latestRunbookIrBlock = (text: string): string | undefined => { - const matches = [...text.matchAll(runbookIrFencePattern)]; - const last = matches.at(-1)?.[1]; - return last === undefined ? undefined : last.trim(); -}; +import type { FlueConversationSnapshot } from "@flue/sdk"; export const recoverRunbookIr = ( snapshot: FlueConversationSnapshot, ): string | undefined => recoverRunbookWorkpiece(snapshot)?.content; -export interface RecoveredRunbookWorkpiece { - readonly content: string; - readonly sha256: string; - readonly sourceMessageId: string; - readonly sourceMessageSha256: string; -} - -export const recoverRunbookWorkpiece = ( - snapshot: FlueConversationSnapshot, -): RecoveredRunbookWorkpiece | undefined => { - let recovered: RecoveredRunbookWorkpiece | undefined; - for (const message of snapshot.messages) { - if (message.purpose !== "assistant") continue; - const text = message.parts - .filter( - (part): part is Extract => - part.type === "text", - ) - .map((part) => part.text) - .join("\n"); - const content = latestRunbookIrBlock(text); - if (content === undefined) continue; - recovered = { - content, - sha256: sha256(content), - sourceMessageId: message.id, - sourceMessageSha256: sha256(JSON.stringify(message)), - }; - } - return recovered; -}; - export const interviewerToolNamesFrom = ( snapshot: FlueConversationSnapshot, ): readonly string[] => [ @@ -153,7 +113,7 @@ export const ordinaryElicitationViolationsFrom = ( } if ( firstWorkpiecePosition === undefined && - part.text.includes(`\`\`\`${RUNBOOK_IR_FENCE}`) + part.text.includes(`\`\`\`${runbookIrFence}`) ) { firstWorkpiecePosition = position; } diff --git a/apps/brunch-agent/src/evaluations/runbook/headless-petrinaut-client.ts b/apps/brunch-agent/src/evaluations/runbook/headless-petrinaut-client.ts index 54cce1b64ee..b654e17eebf 100644 --- a/apps/brunch-agent/src/evaluations/runbook/headless-petrinaut-client.ts +++ b/apps/brunch-agent/src/evaluations/runbook/headless-petrinaut-client.ts @@ -12,7 +12,7 @@ import { getLatestNetDefinitionToolName, } from "@hashintel/petrinaut-core/ai"; -import type { Petrinaut } from "@hashintel/petrinaut-core"; +import type { Petrinaut, SDCPN } from "@hashintel/petrinaut-core"; export interface HeadlessPetrinautToolCall { readonly toolCallId: string; @@ -45,15 +45,18 @@ const constructionToolNames = new Set( const errorMessageFrom = (error: unknown): string => error instanceof Error ? error.message : String(error); -export const createHeadlessPetrinautClient = (title: string) => { +export const createHeadlessPetrinautClient = ( + title: string, + initial: SDCPN = { + places: [], + transitions: [], + types: [], + parameters: [], + differentialEquations: [], + }, +) => { const handle = createJsonDocHandle({ - initial: { - places: [], - transitions: [], - types: [], - parameters: [], - differentialEquations: [], - }, + initial, }); const instance = createPetrinaut({ document: handle }); const writableCallbacks = createPetrinautAiWritableCallbacks( diff --git a/apps/brunch-agent/test/architecture/boundaries.integration.ts b/apps/brunch-agent/test/architecture/boundaries.integration.ts index 5e4d4355c84..5aa1feea1fa 100644 --- a/apps/brunch-agent/test/architecture/boundaries.integration.ts +++ b/apps/brunch-agent/test/architecture/boundaries.integration.ts @@ -379,13 +379,14 @@ describe("recorded Flue constraints hold by construction (spec §10)", () => { }); describe("core auxiliary subpaths stay in their assigned lanes", () => { - test("core exposes Flue composition, browser contracts, and storage support as explicit subpaths", () => { + test("core exposes Flue composition, browser, storage, and workpiece contracts as explicit subpaths", () => { const core = PACKAGES.find((pkg) => pkg.name === CORE)!; expect(Object.keys(core.manifest.exports ?? {})).toEqual([ ".", "./client-tools", "./flue", "./storage", + "./workpiece", ]); }); @@ -429,6 +430,8 @@ describe("the HASH smoke is runnable without a model key or a network (spec §12 "Types Flue's public conversation snapshot so the transcript projector can be unit-tested; the import is type-only — no provider key, no socket, no model call, no runtime boot.", "apps/brunch-agent/test/petrinaut-chat.integration.ts": "Boots the plain Flue chat agent on Flue's node runtime with pi-ai's faux provider, drives the browser ChatTransport against the mounted Flue route over app.fetch, and proves streamed reasoning/text, server tools, client-tool resume, SDK history ownership, SQLite restart, and harness-side idempotent apply-sweep — no provider key, no socket, no extraction model call. Run as a child process by petrinaut-chat.test.ts.", + "apps/brunch-agent/test/prepared-workpiece.integration.ts": + "Boots the built Flue ChatAgent with pi-ai's faux provider, creates a prepared fixture through one tagged public signal with fixture-scoped initial data, retries its deterministic idempotency key, and proves prepared/model workpiece selection from canonical history — no provider key, socket, or network model call.", "apps/brunch-agent/test/proof-artifacts.test.ts": "Types Flue's public conversation snapshot so canonical trace derivation, workpiece binding, and atomic evidence retention can be unit-tested against an in-memory fixture — no provider key, no socket, no model call, no runtime boot.", "apps/brunch-agent/test/runbook-artifacts.test.ts": @@ -437,6 +440,8 @@ describe("the HASH smoke is runnable without a model key or a network (spec §12 "Defines the scripted pi-ai faux provider loaded only by the hermetic prospective-runner test — no provider key, no socket, and no network model call.", "apps/brunch-agent/test/runbook-headless.integration.ts": "Boots the built Flue ChatAgent with pi-ai's faux provider and a headless Petrinaut client to prove validated construct-only tool flow without a provider key, socket, or network model call.", + "apps/brunch-agent/test/workpiece.test.ts": + "Types Flue's public conversation snapshot so the substrate-neutral workpiece selector and app-owned SHA-256 projection can be unit-tested against in-memory messages — no provider key, no socket, no model call, no runtime boot.", "libs/@hashintel/brunch-agent/packages/transport-aisdk/test/chat-transport.test.ts": "Types a stubbed public Flue client and stream chunks to prove finite AI SDK projection and client-tool signal admission — no runtime boot, provider key, socket, or model call.", "libs/@hashintel/brunch-agent/packages/transport-aisdk/test/transcript.test.ts": diff --git a/apps/brunch-agent/test/architecture/boundaries.test.ts b/apps/brunch-agent/test/architecture/boundaries.test.ts new file mode 100644 index 00000000000..712624bb2b6 --- /dev/null +++ b/apps/brunch-agent/test/architecture/boundaries.test.ts @@ -0,0 +1,5 @@ +/** + * Keep the filesystem-wide architecture suite runnable by Vitest while its + * implementation remains a non-test entry point for boundary self-inspection. + */ +import "./boundaries.integration.ts"; diff --git a/apps/brunch-agent/test/prepared-workpiece.integration.test.ts b/apps/brunch-agent/test/prepared-workpiece.integration.test.ts new file mode 100644 index 00000000000..25f523efb82 --- /dev/null +++ b/apps/brunch-agent/test/prepared-workpiece.integration.test.ts @@ -0,0 +1,70 @@ +import { mkdtemp, rm } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +import { expect, test } from "vitest"; + +import { runNodeScript } from "./run-node-script"; + +test("the built ChatAgent preserves prepared and model workpiece provenance", async () => { + const databaseDirectory = await mkdtemp( + join(tmpdir(), "brunch-prepared-workpiece-"), + ); + try { + const { exitCode, stdout, stderr } = await runNodeScript( + join(import.meta.dirname, "prepared-workpiece.integration.ts"), + join(import.meta.dirname, "../../.."), + { + BRUNCH_CHAT_DB_PATH: join(databaseDirectory, "conversations.db"), + }, + ); + expect(exitCode, stderr || stdout).toBe(0); + const resultLine = stdout + .split("\n") + .find((line) => line.startsWith("PREPARED_WORKPIECE_HERMETIC ")); + expect(resultLine, stdout).toBeDefined(); + const result = JSON.parse( + resultLine!.slice("PREPARED_WORKPIECE_HERMETIC ".length), + ) as { + readonly clientToolCallIds: string[]; + readonly messageCountStableAcrossRetry: boolean; + readonly prepared: { + readonly authorship: string; + readonly content: string; + readonly sourceKind: string; + }; + readonly preparedDispatchCount: number; + readonly preparationSubmissionId: string; + readonly retryDeduplicated: boolean; + readonly retrySubmissionId: string; + readonly targetArcAdded: boolean; + readonly revision: { + readonly authorship: string; + readonly content: string; + readonly sourceKind: string; + }; + }; + + expect(result.retryDeduplicated).toBe(true); + expect(result.retrySubmissionId).toBe(result.preparationSubmissionId); + expect(result.messageCountStableAcrossRetry).toBe(true); + expect(result.preparedDispatchCount).toBe(1); + expect(result.clientToolCallIds).toEqual([ + "fixture-read-before-mutation", + "fixture-add-reservation-arc", + ]); + expect(result.targetArcAdded).toBe(true); + expect(result.prepared).toMatchObject({ + authorship: "test-authored", + content: "# Prepared revision\n\nTiming and recovery remain unresolved.", + sourceKind: "prepared-signal", + }); + expect(result.revision).toMatchObject({ + authorship: "model-produced", + sourceKind: "assistant", + }); + expect(result.revision.content).toContain("# Model revision one"); + } finally { + await rm(databaseDirectory, { recursive: true, force: true }); + } +}); diff --git a/apps/brunch-agent/test/prepared-workpiece.integration.ts b/apps/brunch-agent/test/prepared-workpiece.integration.ts new file mode 100644 index 00000000000..3c6ea36699f --- /dev/null +++ b/apps/brunch-agent/test/prepared-workpiece.integration.ts @@ -0,0 +1,274 @@ +import { tmpdir } from "node:os"; +import { join } from "node:path"; + +import { + fauxAssistantMessage, + fauxProvider, + fauxText, + fauxToolCall, +} from "@earendil-works/pi-ai"; +import { setProvider } from "@flue/runtime"; +import { createFlueClient } from "@flue/sdk"; + +import { + petrinautFixtureToolNames, + validatedFixtureMutationMode, +} from "@hashintel/brunch-agent-plugin-sdcpn/flue"; +import { createPreparedWorkpieceDelivery } from "@hashintel/brunch-agent/workpiece"; + +import { + CLIENT_TOOL_RESULT_SIGNAL, + isAwaitingClient, +} from "../src/conversation/client-tools.ts"; +import { + agentOwnershipHeaders, + flueConversationIdFrom, +} from "../src/conversation/identity.ts"; +import { recoverRunbookWorkpiece } from "../src/conversation/workpiece.ts"; +import { createHeadlessPetrinautClient } from "../src/evaluations/runbook/headless-petrinaut-client.ts"; +import { loadBuiltBrunchApplication } from "../src/evaluations/runbook/load-built-application.ts"; +import { CHAT_AGENT_ROUTE } from "../src/http/routes.ts"; + +const modelId = "claude-haiku-4-5"; +const dispatchCrewPlaceId = "dispatch_crew_available"; +const startFinalInspectionTransitionId = "start_final_inspection"; +const preparedBody = [ + "Fixture authorship: test-authored.", + "```runbook-ir", + "# Prepared revision", + "", + "Timing and recovery remain unresolved.", + "```", +].join("\n"); +const preparedDelivery = createPreparedWorkpieceDelivery({ + body: preparedBody, + fixtureId: "crew-reservation-v1", + revision: 0, +}); + +process.env.BRUNCH_CHAT_MODEL = modelId; +process.env.BRUNCH_DEV_DB_PATH = + process.env.BRUNCH_CHAT_DB_PATH ?? + join(tmpdir(), `brunch-prepared-workpiece-${crypto.randomUUID()}.db`); + +const provider = fauxProvider({ + provider: "anthropic", + models: [{ id: modelId, reasoning: true }], +}); +setProvider(provider.provider); +provider.setResponses([ + fauxAssistantMessage([ + fauxText( + [ + "Preparation acknowledged.", + "```runbook-ir", + "# Echo that must not become a model revision", + "```", + ].join("\n"), + ), + ]), + fauxAssistantMessage( + [ + fauxToolCall( + "getLatestNetDefinition", + {}, + { id: "fixture-read-before-mutation" }, + ), + ], + { stopReason: "toolUse" }, + ), + fauxAssistantMessage( + [ + fauxToolCall( + "addArc", + { + transitionId: startFinalInspectionTransitionId, + arcDirection: "input", + placeId: dispatchCrewPlaceId, + weight: 1, + }, + { id: "fixture-add-reservation-arc" }, + ), + ], + { stopReason: "toolUse" }, + ), + fauxAssistantMessage([ + fauxText( + [ + "Confirmation incorporated while retaining the unknown.", + "```runbook-ir", + "# Model revision one", + "", + "The sole crew is reserved for final inspection and returned by sign-off.", + "", + "Timing and recovery remain unresolved.", + "```", + ].join("\n"), + ), + ]), +]); + +const identity = { + principalKey: "prepared-workpiece-test", + conversationId: "prepared-workpiece-test", +}; +const application = await loadBuiltBrunchApplication(); +const petrinautClient = createHeadlessPetrinautClient( + "Prepared crew reservation", + { + types: [], + parameters: [], + places: [ + { + id: dispatchCrewPlaceId, + name: "Dispatch crew available", + colorId: null, + dynamicsEnabled: false, + differentialEquationId: null, + x: 0, + y: 0, + }, + ], + transitions: [ + { + id: startFinalInspectionTransitionId, + name: "Start final inspection", + inputArcs: [], + outputArcs: [], + lambdaType: "predicate", + lambdaCode: "", + transitionKernelCode: "", + x: 180, + y: 0, + }, + ], + differentialEquations: [], + }, +); + +try { + const transport: typeof fetch = async (input, init) => + application.fetch( + input instanceof Request ? input : new Request(input, init), + ); + const client = createFlueClient({ + url: `http://brunch.local/agents/${CHAT_AGENT_ROUTE}/${flueConversationIdFrom(identity)}`, + fetch: transport, + headers: agentOwnershipHeaders(identity), + }); + const preparationPrompt = { + uid: null, + initialData: { mode: validatedFixtureMutationMode }, + ...preparedDelivery, + } as const; + const preparation = await client.send(preparationPrompt); + await client.wait(preparation); + const preparedSnapshot = await client.history(); + const recoveredPrepared = recoverRunbookWorkpiece(preparedSnapshot); + + const retry = await client.send(preparationPrompt); + const afterRetry = await client.history(); + + const confirmation = await client.send({ + message: { + kind: "user", + body: "Final inspection consumes the sole crew; sign-off returns it.", + }, + }); + await client.wait(confirmation); + const completedCallIds = new Set(); + const serviceClientCalls = async (clientRound: number): Promise => { + if (clientRound >= 5) { + throw new Error("Prepared fixture exceeded five client-tool rounds."); + } + const snapshot = await client.history(); + const pendingCalls = snapshot.messages.flatMap((message) => + message.parts.flatMap((part) => { + if ( + part.type !== "dynamic-tool" || + !petrinautFixtureToolNames.includes( + part.toolName as (typeof petrinautFixtureToolNames)[number], + ) || + completedCallIds.has(part.toolCallId) || + part.state !== "output-available" || + !isAwaitingClient(part.output) + ) { + return []; + } + return [ + { + toolCallId: part.toolCallId, + toolName: part.toolName, + input: part.input, + }, + ]; + }), + ); + if (pendingCalls.length === 0) return; + + const results = await Promise.all( + pendingCalls.map((pendingCall) => petrinautClient.execute(pendingCall)), + ); + for (const result of results) completedCallIds.add(result.toolCallId); + const continuation = await client.send({ + message: { + kind: "signal", + type: CLIENT_TOOL_RESULT_SIGNAL, + tagName: CLIENT_TOOL_RESULT_SIGNAL, + body: JSON.stringify(results), + }, + }); + await client.wait(continuation); + await serviceClientCalls(clientRound + 1); + }; + await serviceClientCalls(0); + const revisedSnapshot = await client.history(); + const recoveredRevision = recoverRunbookWorkpiece(revisedSnapshot); + + process.stdout.write( + `PREPARED_WORKPIECE_HERMETIC ${JSON.stringify({ + preparationSubmissionId: preparation.submissionId, + retrySubmissionId: retry.submissionId, + retryDeduplicated: retry.deduplicated === true, + messageCountStableAcrossRetry: + preparedSnapshot.messages.length === afterRetry.messages.length, + prepared: recoveredPrepared, + revision: recoveredRevision, + clientToolCallIds: [...completedCallIds], + dynamicTools: revisedSnapshot.messages.flatMap((message) => + message.parts.flatMap((part) => + part.type === "dynamic-tool" + ? [ + { + toolCallId: part.toolCallId, + toolName: part.toolName, + state: part.state, + output: part.output, + errorText: part.errorText, + }, + ] + : [], + ), + ), + targetArcAdded: + petrinautClient + .definition() + .transitions.find(({ id }) => id === startFinalInspectionTransitionId) + ?.inputArcs.some( + (arc) => + arc.placeId === dispatchCrewPlaceId && + arc.type === "standard" && + arc.weight === 1, + ) === true, + preparedDispatchCount: revisedSnapshot.messages.filter( + (message) => + message.role === "system" && + message.purpose === "dispatch" && + message.signal?.tagName === "prepared-fixture", + ).length, + })}\n`, + ); +} finally { + petrinautClient.dispose(); + await application.stop(); +} diff --git a/apps/brunch-agent/test/runbook-artifacts.test.ts b/apps/brunch-agent/test/runbook-artifacts.test.ts index ec065240c0c..1203e25f189 100644 --- a/apps/brunch-agent/test/runbook-artifacts.test.ts +++ b/apps/brunch-agent/test/runbook-artifacts.test.ts @@ -2,10 +2,13 @@ import { describe, expect, test } from "vitest"; import { latestRunbookIrBlock, + runbookIrFence, +} from "@hashintel/brunch-agent/workpiece"; + +import { recoverRunbookWorkpiece } from "../src/conversation/workpiece.ts"; +import { ordinaryElicitationViolationsFrom, recoverRunbookIr, - recoverRunbookWorkpiece, - RUNBOOK_IR_FENCE, skillResourcePathsFrom, } from "../src/evaluations/runbook/artifacts.ts"; @@ -27,11 +30,11 @@ const snapshotWithAssistantText = (text: string): FlueConversationSnapshot => describe("runbook artifact recovery", () => { test("takes the last fenced IR block", () => { const text = [ - "```" + RUNBOOK_IR_FENCE, + "```" + runbookIrFence, "# first", "```", "later", - "```" + RUNBOOK_IR_FENCE, + "```" + runbookIrFence, "# second", "```", ].join("\n"); @@ -41,7 +44,7 @@ describe("runbook artifact recovery", () => { test("recovers an IR from assistant history", () => { const snapshot = snapshotWithAssistantText( [ - "```" + RUNBOOK_IR_FENCE, + "```" + runbookIrFence, "# Runbook IR", "## Purpose and outcome", "```", diff --git a/apps/brunch-agent/test/workpiece.test.ts b/apps/brunch-agent/test/workpiece.test.ts new file mode 100644 index 00000000000..490a18a7eec --- /dev/null +++ b/apps/brunch-agent/test/workpiece.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, test } from "vitest"; + +import { recoverRunbookWorkpiece } from "../src/conversation/workpiece.ts"; + +import type { + FlueConversationMessage, + FlueConversationSnapshot, +} from "@flue/sdk"; + +const revisionMessage: FlueConversationMessage = { + id: "revision", + role: "assistant", + purpose: "assistant", + display: "visible", + submissionId: "turn-1", + parts: [ + { type: "text", text: "```runbook-ir\n# Revision\n```", state: "done" }, + ], +}; + +const snapshot: FlueConversationSnapshot = { + v: 1, + conversationId: "conversation", + offset: "1", + messages: [revisionMessage], + settlements: [], +}; + +describe("recoverRunbookWorkpiece", () => { + test("accepts a Flue snapshot and adds stable content and source hashes", () => { + expect(recoverRunbookWorkpiece(snapshot)).toEqual({ + authorship: "model-produced", + content: "# Revision", + sha256: + "330eeebe84d31400de2dad6ea1783ed1a0d0c5487ab32e63e58a6fffe201c4cb", + sourceKind: "assistant", + sourceMessageId: "revision", + sourceMessageSha256: + "eced072a0cecc954fa63a7f9664e1dffea1a685f71d2b835a9af971a115718e5", + sourceSubmissionId: "turn-1", + }); + }); +}); diff --git a/apps/petrinaut-website/package.json b/apps/petrinaut-website/package.json index 37df3a79f40..8603d255e9f 100644 --- a/apps/petrinaut-website/package.json +++ b/apps/petrinaut-website/package.json @@ -27,6 +27,7 @@ "@hashintel/petrinaut-core": "workspace:*", "@local/petrinaut-optimizer-client": "workspace:*", "@mantine/hooks": "8.3.5", + "@noble/hashes": "2.0.1", "@pandacss/dev": "1.11.1", "@sentry/react": "10.64.0", "@tanstack/react-router": "1.170.31", diff --git a/apps/petrinaut-website/src/main/app/local-storage-demo/brunch-panel-transport.test.ts b/apps/petrinaut-website/src/main/app/local-storage-demo/brunch-panel-transport.test.ts index 6df757d8614..78d0abc3b90 100644 --- a/apps/petrinaut-website/src/main/app/local-storage-demo/brunch-panel-transport.test.ts +++ b/apps/petrinaut-website/src/main/app/local-storage-demo/brunch-panel-transport.test.ts @@ -3,6 +3,7 @@ import { expect, test, vi } from "vitest"; import { BrunchPanelConversationTracker, createBrunchPanelTransport, + createUnavailableBrunchPanelTransport, } from "./brunch-panel-transport"; import type { AgentSendResult, FlueClient } from "@flue/sdk"; @@ -71,6 +72,7 @@ test("delegates one typed message to the supplied Flue conversation", async () = expect(send).toHaveBeenCalledOnce(); expect(send).toHaveBeenCalledWith({ + idempotencyKey: "ai-sdk:user:user-1", message: { kind: "user", body: "Typed tracer." }, signal: undefined, }); @@ -175,3 +177,80 @@ test("settles in-flight submissions before a durable abort can target them", asy await expect(rejected).rejects.toThrow("rejected admission"); await expect(tracker.settleInFlightSubmissions()).resolves.toBeUndefined(); }); + +test("returns a fixture-scoped mutation result through the same Flue client", async () => { + const admission: AgentSendResult = { + streamUrl: "http://brunch.test/stream", + offset: "offset-2", + submissionId: "submission-2", + uid: "uid-2", + }; + const send = vi.fn(async () => admission); + const wait = vi.fn(async () => {}); + const client = { + send, + wait, + } as Pick as FlueClient; + const transport = createBrunchPanelTransport( + Promise.resolve(client), + new BrunchPanelConversationTracker(), + { clientToolNames: new Set(["addArc"]) }, + ); + const stream = await transport.sendMessages({ + trigger: "submit-message", + chatId: "conversation-stable", + messageId: "assistant-1", + messages: [ + { + id: "assistant-1", + role: "assistant", + parts: [ + { + type: "dynamic-tool", + toolName: "addArc", + toolCallId: "add-arc-1", + state: "output-available", + input: {}, + output: { applied: true }, + }, + ], + }, + ], + abortSignal: undefined, + }); + await stream.pipeTo(new WritableStream()); + + expect(send).toHaveBeenCalledWith({ + idempotencyKey: "ai-sdk:client-tools:assistant-1:add-arc-1", + message: { + kind: "signal", + type: "client-tool-result", + tagName: "client-tool-result", + body: JSON.stringify([ + { + toolCallId: "add-arc-1", + toolName: "addArc", + output: { applied: true }, + }, + ]), + attributes: { toolCallIds: "add-arc-1" }, + }, + signal: undefined, + }); +}); + +test("refuses fixture traffic when the mounted Flue route is unavailable", async () => { + const transport = createUnavailableBrunchPanelTransport( + "Fixture route unavailable.", + ); + + await expect( + transport.sendMessages({ + trigger: "submit-message", + chatId: "conversation-stable", + messageId: undefined, + messages: [], + abortSignal: undefined, + }), + ).rejects.toThrow("Fixture route unavailable."); +}); diff --git a/apps/petrinaut-website/src/main/app/local-storage-demo/brunch-panel-transport.ts b/apps/petrinaut-website/src/main/app/local-storage-demo/brunch-panel-transport.ts index 973075debb3..3e289ff7bcb 100644 --- a/apps/petrinaut-website/src/main/app/local-storage-demo/brunch-panel-transport.ts +++ b/apps/petrinaut-website/src/main/app/local-storage-demo/brunch-panel-transport.ts @@ -233,7 +233,13 @@ const decorateBrunchStream = ( export const createBrunchPanelTransport = ( clientPromise: Promise, tracker: BrunchPanelConversationTracker, - hooks?: { + options?: { + /** Fixture-scoped client tools; defaults to the Petrinaut docs reader alone. */ + readonly clientToolNames?: ReadonlySet; + readonly mapClientToolInput?: (input: { + readonly input: unknown; + readonly toolName: string; + }) => unknown; readonly onAdmission?: (admission: AgentSendResult) => void; }, ): PetrinautAiChatTransport => ({ @@ -244,10 +250,13 @@ export const createBrunchPanelTransport = ( const client = await clientPromise; const transport = createFlueChatTransport({ client, - clientToolNames: brunchClientToolNames, + clientToolNames: options?.clientToolNames ?? brunchClientToolNames, + ...(options?.mapClientToolInput === undefined + ? {} + : { mapClientToolInput: options.mapClientToolInput }), onAdmission: (event) => { tracker.recordAdmission(event); - hooks?.onAdmission?.(event.admission); + options?.onAdmission?.(event.admission); }, onResponseMessage: ({ messageId, submissionId }) => tracker.recordResponse(messageId, submissionId), @@ -256,3 +265,12 @@ export const createBrunchPanelTransport = ( })(), ), }); + +export const createUnavailableBrunchPanelTransport = ( + reason: string, +): PetrinautAiChatTransport => ({ + reconnectToStream: async () => null, + sendMessages: async () => { + throw new Error(reason); + }, +}); diff --git a/apps/petrinaut-website/src/main/app/local-storage-demo/crew-reservation-history.ts b/apps/petrinaut-website/src/main/app/local-storage-demo/crew-reservation-history.ts new file mode 100644 index 00000000000..f5030832123 --- /dev/null +++ b/apps/petrinaut-website/src/main/app/local-storage-demo/crew-reservation-history.ts @@ -0,0 +1,14 @@ +import type { WorkpieceHistory } from "@hashintel/brunch-agent/workpiece"; + +/** + * Canonical Flue history plus the durable offset observed by the browser. + * Preparation and settlement share this shape rather than independently + * extending the workpiece projection. + */ +export type CrewReservationHistory = WorkpieceHistory & { + readonly offset: string; + readonly settlements: readonly { + readonly outcome: string; + readonly submissionId: string; + }[]; +}; diff --git a/apps/petrinaut-website/src/main/app/local-storage-demo/crew-reservation-settled-manifest.test.ts b/apps/petrinaut-website/src/main/app/local-storage-demo/crew-reservation-settled-manifest.test.ts new file mode 100644 index 00000000000..ddb0f5396c9 --- /dev/null +++ b/apps/petrinaut-website/src/main/app/local-storage-demo/crew-reservation-settled-manifest.test.ts @@ -0,0 +1,395 @@ +import { describe, expect, test } from "vitest"; + +import { + preparedWorkpieceAuthorship, + preparedWorkpieceClaimBoundary, + preparedWorkpieceSignalTag, + type WorkpieceHistoryMessage, +} from "@hashintel/brunch-agent/workpiece"; + +import { + hasCrewReservationTargetArc, + settleCrewReservationManifest, +} from "./crew-reservation-settled-manifest"; +import { + crewReservationFixtureId, + dispatchCrewPlaceId, + preparedCrewReservationNet, + preparedCrewReservationWorkpiece, + startFinalInspectionTransitionId, +} from "./prepared-crew-reservation-fixture"; + +const preparedMessage: WorkpieceHistoryMessage = { + id: "prepared-message", + role: "system", + purpose: "dispatch", + submissionId: "prepare-submission", + signal: { + tagName: preparedWorkpieceSignalTag, + attributes: { + fixtureId: crewReservationFixtureId, + authorship: preparedWorkpieceAuthorship, + claimBoundary: preparedWorkpieceClaimBoundary, + }, + }, + parts: [ + { + type: "text", + text: preparedCrewReservationWorkpiece, + }, + ], +}; + +const settledHistory = ( + messages: readonly WorkpieceHistoryMessage[] = [preparedMessage], +) => ({ + conversationId: "canonical-flue-conversation", + offset: "10", + messages, + settlements: [{ submissionId: "prepare-submission", outcome: "completed" }], +}); + +const targetMutationMessages = ( + toolCallId = "target-arc-call", +): readonly [WorkpieceHistoryMessage, WorkpieceHistoryMessage] => [ + { + id: "target-mutation-request", + role: "assistant", + purpose: "assistant", + submissionId: "confirmation-turn", + parts: [ + { + type: "dynamic-tool", + toolCallId, + toolName: "addArc", + input: { + transitionId: startFinalInspectionTransitionId, + arcDirection: "input", + placeId: dispatchCrewPlaceId, + weight: 1, + }, + }, + ], + }, + { + id: "target-mutation-result", + role: "system", + purpose: "dispatch", + submissionId: "mutation-continuation", + signal: { + tagName: "client-tool-result", + }, + parts: [ + { + type: "text", + text: JSON.stringify([ + { + toolCallId, + toolName: "addArc", + output: { applied: true }, + }, + ]), + }, + ], + }, +]; + +describe("crew-reservation settled manifest", () => { + test("records a coherent prepared bundle without inventing the target arc", async () => { + const result = await settleCrewReservationManifest({ + definition: preparedCrewReservationNet, + history: settledHistory(), + settledAt: "2026-09-03T12:00:00.000Z", + }); + + expect(result).toMatchObject({ + status: "settled", + manifest: { + fixtureId: crewReservationFixtureId, + revision: 0, + conversation: { + canonicalId: "canonical-flue-conversation", + }, + latestWorkpiece: { + authorship: "test-authored", + sourceMessageId: "prepared-message", + }, + document: { + targetArc: "absent", + }, + }, + }); + }); + + test("advances only after a completed model revision and document change", async () => { + const initial = await settleCrewReservationManifest({ + definition: preparedCrewReservationNet, + history: settledHistory(), + settledAt: "2026-09-03T12:00:00.000Z", + }); + if (initial.status !== "settled") { + throw new Error("Expected the prepared fixture to settle"); + } + + const revisedMessage: WorkpieceHistoryMessage = { + id: "revised-workpiece", + role: "assistant", + purpose: "assistant", + submissionId: "confirmation-turn", + parts: [ + { + type: "text", + text: preparedCrewReservationWorkpiece.replace( + "It deliberately lacks", + "The confirmation resolves", + ), + }, + ], + }; + const revisedDefinition = structuredClone(preparedCrewReservationNet); + const startInspection = revisedDefinition.transitions.find( + ({ id }) => id === startFinalInspectionTransitionId, + ); + if (startInspection === undefined) { + throw new Error("Missing prepared start-inspection transition"); + } + startInspection.inputArcs.push({ + placeId: dispatchCrewPlaceId, + type: "standard", + weight: 1, + }); + + const result = await settleCrewReservationManifest({ + definition: revisedDefinition, + history: { + ...settledHistory([ + preparedMessage, + ...targetMutationMessages(), + revisedMessage, + ]), + offset: "20", + settlements: [ + { submissionId: "prepare-submission", outcome: "completed" }, + { submissionId: "confirmation-turn", outcome: "completed" }, + ], + }, + previous: initial.manifest, + settledAt: "2026-09-03T12:05:00.000Z", + }); + + expect(hasCrewReservationTargetArc(revisedDefinition)).toBe(true); + expect(result).toMatchObject({ + status: "settled", + manifest: { + revision: 1, + latestWorkpiece: { authorship: "model-produced" }, + document: { targetArc: "present" }, + }, + }); + }); + + test("refuses a model revision without one successful correlated target mutation", async () => { + const revisedMessage: WorkpieceHistoryMessage = { + id: "revised-workpiece", + role: "assistant", + purpose: "assistant", + submissionId: "confirmation-turn", + parts: [{ type: "text", text: preparedCrewReservationWorkpiece }], + }; + const revisedDefinition = structuredClone(preparedCrewReservationNet); + const startInspection = revisedDefinition.transitions.find( + ({ id }) => id === startFinalInspectionTransitionId, + ); + if (startInspection === undefined) { + throw new Error("Missing prepared start-inspection transition"); + } + startInspection.inputArcs.push({ + placeId: dispatchCrewPlaceId, + type: "standard", + weight: 1, + }); + const history = { + ...settledHistory([preparedMessage, revisedMessage]), + settlements: [ + { submissionId: "prepare-submission", outcome: "completed" }, + { submissionId: "confirmation-turn", outcome: "completed" }, + ], + }; + + await expect( + settleCrewReservationManifest({ + definition: revisedDefinition, + history, + settledAt: "2026-09-03T12:05:00.000Z", + }), + ).resolves.toEqual({ + status: "refused", + reason: "missing-correlated-mutation", + }); + + const [targetCall, targetResult] = targetMutationMessages(); + await expect( + settleCrewReservationManifest({ + definition: revisedDefinition, + history: { + ...history, + messages: [ + preparedMessage, + targetCall, + { + ...targetResult, + parts: [ + { + type: "text", + text: JSON.stringify([ + { + toolCallId: "target-arc-call", + toolName: "addArc", + output: { applied: false, reason: "no-op" }, + }, + ]), + }, + ], + }, + revisedMessage, + ], + }, + settledAt: "2026-09-03T12:05:00.000Z", + }), + ).resolves.toEqual({ + status: "refused", + reason: "missing-correlated-mutation", + }); + + await expect( + settleCrewReservationManifest({ + definition: revisedDefinition, + history: { + ...history, + messages: [ + preparedMessage, + ...targetMutationMessages(), + targetMutationMessages()[1], + revisedMessage, + ], + }, + settledAt: "2026-09-03T12:05:00.000Z", + }), + ).resolves.toMatchObject({ status: "settled" }); + + await expect( + settleCrewReservationManifest({ + definition: revisedDefinition, + history: { + ...history, + messages: [ + preparedMessage, + ...targetMutationMessages(), + ...targetMutationMessages("second-target-arc-call"), + revisedMessage, + ], + }, + settledAt: "2026-09-03T12:05:00.000Z", + }), + ).resolves.toEqual({ + status: "refused", + reason: "missing-correlated-mutation", + }); + }); + + test("retains the previous bundle when recovery is partial", async () => { + const initial = await settleCrewReservationManifest({ + definition: preparedCrewReservationNet, + history: settledHistory(), + settledAt: "2026-09-03T12:00:00.000Z", + }); + if (initial.status !== "settled") { + throw new Error("Expected the prepared fixture to settle"); + } + + await expect( + settleCrewReservationManifest({ + definition: preparedCrewReservationNet, + history: { + ...settledHistory(), + conversationId: "different-conversation", + }, + previous: initial.manifest, + settledAt: "2026-09-03T12:05:00.000Z", + }), + ).resolves.toEqual({ + status: "refused", + reason: "conversation-mismatch", + }); + + await expect( + settleCrewReservationManifest({ + definition: preparedCrewReservationNet, + history: { + ...settledHistory(), + settlements: [], + }, + previous: initial.manifest, + settledAt: "2026-09-03T12:05:00.000Z", + }), + ).resolves.toEqual({ + status: "refused", + reason: "missing-completed-settlement", + }); + }); + + test("does not publish a new revision for an unchanged coherent bundle", async () => { + const initial = await settleCrewReservationManifest({ + definition: preparedCrewReservationNet, + history: settledHistory(), + settledAt: "2026-09-03T12:00:00.000Z", + }); + if (initial.status !== "settled") { + throw new Error("Expected the prepared fixture to settle"); + } + + await expect( + settleCrewReservationManifest({ + definition: preparedCrewReservationNet, + history: { ...settledHistory(), offset: "11" }, + previous: initial.manifest, + settledAt: "2026-09-03T12:05:00.000Z", + }), + ).resolves.toEqual(initial); + }); + + test("keeps the prepared bundle selected when the document changes first", async () => { + const initial = await settleCrewReservationManifest({ + definition: preparedCrewReservationNet, + history: settledHistory(), + settledAt: "2026-09-03T12:00:00.000Z", + }); + if (initial.status !== "settled") { + throw new Error("Expected the prepared fixture to settle"); + } + const partialDefinition = structuredClone(preparedCrewReservationNet); + const startInspection = partialDefinition.transitions.find( + ({ id }) => id === startFinalInspectionTransitionId, + ); + if (startInspection === undefined) { + throw new Error("Missing prepared start-inspection transition"); + } + startInspection.inputArcs.push({ + placeId: dispatchCrewPlaceId, + type: "standard", + weight: 1, + }); + + await expect( + settleCrewReservationManifest({ + definition: partialDefinition, + history: settledHistory(), + previous: initial.manifest, + settledAt: "2026-09-03T12:05:00.000Z", + }), + ).resolves.toEqual({ + status: "refused", + reason: "bundle-mismatch", + }); + }); +}); diff --git a/apps/petrinaut-website/src/main/app/local-storage-demo/crew-reservation-settled-manifest.ts b/apps/petrinaut-website/src/main/app/local-storage-demo/crew-reservation-settled-manifest.ts new file mode 100644 index 00000000000..b157dfef5a5 --- /dev/null +++ b/apps/petrinaut-website/src/main/app/local-storage-demo/crew-reservation-settled-manifest.ts @@ -0,0 +1,275 @@ +import { sha256 as sha256Bytes } from "@noble/hashes/sha2.js"; +import { bytesToHex } from "@noble/hashes/utils.js"; + +import { clientToolHistoryFrom } from "@hashintel/brunch-agent-transport-aisdk"; +import { selectRunbookWorkpiece } from "@hashintel/brunch-agent/workpiece"; +import { isSDCPNEqual, type SDCPN } from "@hashintel/petrinaut-core"; +import { normalizePetrinautAiToolInput } from "@hashintel/petrinaut-core/ai"; + +import { + crewReservationConversationId, + crewReservationDocumentId, + crewReservationFixtureId, + dispatchCrewPlaceId, + preparedCrewReservationNet, + startFinalInspectionTransitionId, +} from "./prepared-crew-reservation-fixture"; + +import type { CrewReservationHistory } from "./crew-reservation-history"; + +export const crewReservationSettledManifestStorageKey = + "brunch:prepared-fixture:crew-reservation-v1:settled"; + +declare const manifestValueBrand: unique symbol; +type ManifestValue = string & { + readonly [manifestValueBrand]: Kind; +}; + +export type CanonicalConversationId = ManifestValue<"canonical-conversation">; +export type ConversationOffset = ManifestValue<"conversation-offset">; +export type FlueMessageId = ManifestValue<"flue-message">; +export type FlueSubmissionId = ManifestValue<"flue-submission">; +export type ManifestId = ManifestValue<"manifest">; +export type Sha256Digest = ManifestValue<"sha256">; + +export const asCanonicalConversationId = ( + value: string, +): CanonicalConversationId => value as CanonicalConversationId; +export const asConversationOffset = (value: string): ConversationOffset => + value as ConversationOffset; +export const asFlueMessageId = (value: string): FlueMessageId => + value as FlueMessageId; +export const asFlueSubmissionId = (value: string): FlueSubmissionId => + value as FlueSubmissionId; +export const asManifestId = (value: string): ManifestId => value as ManifestId; +export const asSha256Digest = (value: string): Sha256Digest => + value as Sha256Digest; + +const isRecord = (value: unknown): value is Record => + typeof value === "object" && value !== null; + +export interface CrewReservationSettledManifest { + readonly conversation: { + readonly canonicalId: CanonicalConversationId; + readonly logicalId: typeof crewReservationConversationId; + readonly offset: ConversationOffset; + }; + readonly document: { + readonly id: typeof crewReservationDocumentId; + readonly sha256: Sha256Digest; + readonly targetArc: "absent" | "present"; + }; + readonly fixtureId: typeof crewReservationFixtureId; + readonly latestWorkpiece: { + readonly authorship: "model-produced" | "test-authored"; + readonly contentSha256: Sha256Digest; + readonly sourceKind: "assistant" | "prepared-signal"; + readonly sourceMessageId: FlueMessageId; + readonly sourceMessageSha256: Sha256Digest; + readonly sourceSubmissionId: FlueSubmissionId; + }; + readonly manifestId: ManifestId; + readonly revision: number; + readonly settledAt: string; + readonly version: 1; +} + +export type CrewReservationSettlementResult = + | { + readonly manifest: CrewReservationSettledManifest; + readonly status: "settled"; + } + | { + readonly reason: + | "bundle-mismatch" + | "conversation-mismatch" + | "missing-correlated-mutation" + | "missing-completed-settlement" + | "missing-workpiece"; + readonly status: "refused"; + }; + +const targetMutationCallIds = ( + history: CrewReservationHistory, +): readonly string[] => { + const { calls } = clientToolHistoryFrom(history.messages); + return calls.flatMap(({ input, toolCallId, toolName }) => { + if (toolName !== "addArc") return []; + const normalizedInput = normalizePetrinautAiToolInput("addArc", input); + return isRecord(normalizedInput) && + normalizedInput.transitionId === startFinalInspectionTransitionId && + normalizedInput.arcDirection === "input" && + normalizedInput.placeId === dispatchCrewPlaceId && + normalizedInput.weight === 1 + ? [toolCallId] + : []; + }); +}; + +const successfulMutationResultIds = ( + history: CrewReservationHistory, +): readonly string[] => { + const { results } = clientToolHistoryFrom(history.messages); + return results.flatMap(({ output, toolCallId, toolName }) => + toolName === "addArc" && + typeof output === "object" && + output !== null && + "applied" in output && + output.applied === true + ? [toolCallId] + : [], + ); +}; + +const hasOneCorrelatedTargetMutation = ( + history: CrewReservationHistory, +): boolean => { + const successfulResultIds = new Set(successfulMutationResultIds(history)); + const correlatedTargetCallIds = new Set( + targetMutationCallIds(history).filter((toolCallId) => + successfulResultIds.has(toolCallId), + ), + ); + return correlatedTargetCallIds.size === 1; +}; + +export const sha256Digest = (value: string): Sha256Digest => + asSha256Digest(bytesToHex(sha256Bytes(new TextEncoder().encode(value)))); + +export const hasCrewReservationTargetArc = (definition: SDCPN): boolean => { + const transition = definition.transitions.find( + ({ id }) => id === startFinalInspectionTransitionId, + ); + return ( + transition?.inputArcs.some( + (arc) => + arc.placeId === dispatchCrewPlaceId && + arc.type === "standard" && + arc.weight === 1, + ) ?? false + ); +}; + +const preparedCrewReservationNetWithTargetArc = (): SDCPN => { + const definition = structuredClone(preparedCrewReservationNet); + const transition = definition.transitions.find( + ({ id }) => id === startFinalInspectionTransitionId, + ); + if (transition === undefined) { + throw new Error("The prepared fixture has no start-inspection transition."); + } + transition.inputArcs.push({ + placeId: dispatchCrewPlaceId, + type: "standard", + weight: 1, + }); + return definition; +}; + +export const settleCrewReservationManifest = async (input: { + readonly definition: SDCPN; + readonly history: CrewReservationHistory; + readonly previous?: CrewReservationSettledManifest; + readonly settledAt: string; +}): Promise => { + if ( + input.previous !== undefined && + input.previous.conversation.canonicalId !== input.history.conversationId + ) { + return { status: "refused", reason: "conversation-mismatch" }; + } + + const workpiece = selectRunbookWorkpiece(input.history); + if (workpiece === undefined || workpiece.sourceSubmissionId === undefined) { + return { status: "refused", reason: "missing-workpiece" }; + } + const sourceSettlement = input.history.settlements.find( + ({ submissionId }) => submissionId === workpiece.sourceSubmissionId, + ); + if (sourceSettlement?.outcome !== "completed") { + return { + status: "refused", + reason: "missing-completed-settlement", + }; + } + const targetArcPresent = hasCrewReservationTargetArc(input.definition); + if ( + workpiece.authorship === "model-produced" && + (!targetArcPresent || + !isSDCPNEqual( + input.definition, + preparedCrewReservationNetWithTargetArc(), + ) || + !hasOneCorrelatedTargetMutation(input.history)) + ) { + return { + status: "refused", + reason: "missing-correlated-mutation", + }; + } + + const contentSha256 = sha256Digest(workpiece.content); + const documentSha256 = sha256Digest(JSON.stringify(input.definition)); + const sourceMessageSha256 = sha256Digest( + JSON.stringify(workpiece.sourceMessage), + ); + if (workpiece.authorship === "test-authored") { + const preparedDocumentSha256 = sha256Digest( + JSON.stringify(preparedCrewReservationNet), + ); + if ( + targetArcPresent || + documentSha256 !== preparedDocumentSha256 || + !isSDCPNEqual(input.definition, preparedCrewReservationNet) + ) { + return { status: "refused", reason: "bundle-mismatch" }; + } + } + if ( + input.previous?.latestWorkpiece.sourceMessageId === + workpiece.sourceMessageId + ) { + if ( + input.previous.document.sha256 !== documentSha256 || + input.previous.latestWorkpiece.sourceMessageSha256 !== + sourceMessageSha256 || + input.previous.latestWorkpiece.contentSha256 !== contentSha256 + ) { + return { status: "refused", reason: "bundle-mismatch" }; + } + return { status: "settled", manifest: input.previous }; + } + const revision = (input.previous?.revision ?? -1) + 1; + const withoutId = { + version: 1 as const, + fixtureId: crewReservationFixtureId, + revision, + settledAt: input.settledAt, + conversation: { + logicalId: crewReservationConversationId, + canonicalId: asCanonicalConversationId(input.history.conversationId), + offset: asConversationOffset(input.history.offset), + }, + latestWorkpiece: { + authorship: workpiece.authorship, + contentSha256, + sourceKind: workpiece.sourceKind, + sourceMessageId: asFlueMessageId(workpiece.sourceMessageId), + sourceMessageSha256, + sourceSubmissionId: asFlueSubmissionId(workpiece.sourceSubmissionId), + }, + document: { + id: crewReservationDocumentId, + sha256: documentSha256, + targetArc: targetArcPresent ? ("present" as const) : ("absent" as const), + }, + } satisfies Omit; + + return { + status: "settled", + manifest: { + ...withoutId, + manifestId: asManifestId(sha256Digest(JSON.stringify(withoutId))), + }, + }; +}; diff --git a/apps/petrinaut-website/src/main/app/local-storage-demo/local-storage-demo-app.tsx b/apps/petrinaut-website/src/main/app/local-storage-demo/local-storage-demo-app.tsx index 39e4b466fa3..bac3cd5a72c 100644 --- a/apps/petrinaut-website/src/main/app/local-storage-demo/local-storage-demo-app.tsx +++ b/apps/petrinaut-website/src/main/app/local-storage-demo/local-storage-demo-app.tsx @@ -5,7 +5,7 @@ import { createFlueClient, type FlueConversationSettlement } from "@flue/sdk"; import { produce } from "immer"; -import { useEffect, useMemo, useState } from "react"; +import { useCallback, useEffect, useMemo, useState } from "react"; import { agentOwnershipHeaders, @@ -50,9 +50,25 @@ import { getOrCreateBrunchConversationId } from "./brunch-conversation-id"; import { BrunchPanelConversationTracker, createBrunchPanelTransport, + createUnavailableBrunchPanelTransport, } from "./brunch-panel-transport"; import { resolveBrunchPreviewConfig } from "./brunch-preview-config"; import { getOrCreateBrunchPrincipal } from "./brunch-principal"; +import { + crewReservationDocumentId, + isCrewReservationFixtureSelected, + preparedCrewReservationNet, +} from "./prepared-crew-reservation-fixture"; +import { + PreparedFixtureBanner, + PreparedFixtureSelector, +} from "./prepared-fixture-banner"; +import { resolveCrewReservationBundle } from "./resolve-crew-reservation-bundle"; +import { + crewReservationFixtureConfiguration, + useCrewReservationFixtureSession, +} from "./use-crew-reservation-fixture-session"; +import { useCrewReservationSettledManifestStorage } from "./use-crew-reservation-settled-manifest"; import { useFlueChatHistory } from "./use-flue-chat-history"; import { useLocalStorageAiMessages } from "./use-local-storage-ai-messages"; import { @@ -89,6 +105,13 @@ const createDefaultStoredSDCPN = (): SDCPNInLocalStorage => ({ lastUpdated: new Date(0).toISOString(), }); +const preparedCrewReservationStoredSDCPN: SDCPNInLocalStorage = { + id: crewReservationDocumentId, + title: "Prepared final inspection and dispatch", + sdcpn: preparedCrewReservationNet, + lastUpdated: new Date(0).toISOString(), +}; + /** * Creates the localStorage record for a newly created net, keeping the generated * id and last-updated timestamp in sync. @@ -201,7 +224,14 @@ const createConversationTrackerFor = ( const getStoredSDCPNsForDisplay = ( storedSDCPNs: Record, + crewReservationDocument: SDCPNInLocalStorage | undefined, ): Record => { + if (crewReservationDocument !== undefined) { + return { + ...storedSDCPNs, + [crewReservationDocument.id]: crewReservationDocument, + }; + } if (Object.values(storedSDCPNs).length > 0) { return storedSDCPNs; } @@ -351,7 +381,55 @@ export const LocalStorageDemoApp = ({ const { aiMessagesByNetId, setAiMessagesByNetId } = useLocalStorageAiMessages(); const { storedSDCPNs, setStoredSDCPNs } = useLocalStorageSDCPNs(); - const storedSDCPNsForDisplay = getStoredSDCPNsForDisplay(storedSDCPNs); + const { settledManifest, setSettledManifest } = + useCrewReservationSettledManifestStorage(); + const crewReservationFixtureSelected = isCrewReservationFixtureSelected( + window.location.search, + ); + const crewReservationBundle = crewReservationFixtureSelected + ? resolveCrewReservationBundle({ + fallbackDocument: preparedCrewReservationStoredSDCPN, + manifest: settledManifest, + storedDocument: storedSDCPNs[crewReservationDocumentId], + }) + : undefined; + const storedSDCPNsForDisplay = getStoredSDCPNsForDisplay( + storedSDCPNs, + crewReservationBundle?.selectedDocument, + ); + + useEffect(() => { + if ( + !crewReservationFixtureSelected || + storedSDCPNs[crewReservationDocumentId] !== undefined + ) { + return; + } + setStoredSDCPNs((previous) => ({ + ...previous, + [crewReservationDocumentId]: preparedCrewReservationStoredSDCPN, + })); + }, [crewReservationFixtureSelected, setStoredSDCPNs, storedSDCPNs]); + + const persistCrewReservationSnapshot = useCallback( + (sha256: string, definition: SDCPN) => { + setStoredSDCPNs((previous) => + produce(previous, (draft) => { + const document = + draft[crewReservationDocumentId] ?? + preparedCrewReservationStoredSDCPN; + draft[crewReservationDocumentId] = { + ...document, + coherentSnapshots: { + ...document.coherentSnapshots, + [sha256]: structuredClone(definition), + }, + }; + }), + ); + }, + [setStoredSDCPNs], + ); useEffect(() => { if (!brunchPreviewConfig.isBrunchConfigured) { @@ -377,10 +455,13 @@ export const LocalStorageDemoApp = ({ (a, b) => new Date(b.lastUpdated).getTime() - new Date(a.lastUpdated).getTime(), )[0] ?? null; + const initiallySelectedNet = crewReservationFixtureSelected + ? storedSDCPNsForDisplay[crewReservationDocumentId] + : mostRecentlyModifiedNet; // The net currently selected in the UI. const [currentNetId, setCurrentNetId] = useState( - () => mostRecentlyModifiedNet?.id ?? null, + () => initiallySelectedNet?.id ?? null, ); // Metadata and persisted SDCPN snapshot for the selected net. @@ -390,9 +471,7 @@ export const LocalStorageDemoApp = ({ // Live editable document handle for the selected net only. const [activeHandle, setActiveHandle] = useState(() => - mostRecentlyModifiedNet - ? createActiveHandle(mostRecentlyModifiedNet) - : null, + initiallySelectedNet ? createActiveHandle(initiallySelectedNet) : null, ); useEffect(() => { @@ -419,7 +498,9 @@ export const LocalStorageDemoApp = ({ }); }, [activeHandle, setStoredSDCPNs]); - const existingNets: MinimalNetMetadata[] = Object.values(storedSDCPNs) + const existingNets: MinimalNetMetadata[] = Object.values( + storedSDCPNsForDisplay, + ) .map((net) => ({ netId: net.id, title: net.title, @@ -507,9 +588,17 @@ export const LocalStorageDemoApp = ({ ); }; - const conversationId = currentNetId - ? getOrCreateBrunchConversationId(currentNetId) - : null; + const preparedFixtureIsCurrent = + crewReservationFixtureSelected && + currentNetId === crewReservationDocumentId; + const fixtureConfiguration = preparedFixtureIsCurrent + ? crewReservationFixtureConfiguration + : undefined; + const conversationId = + currentNetId === null + ? null + : (fixtureConfiguration?.conversationId ?? + getOrCreateBrunchConversationId(currentNetId)); const flueClientPromise = useMemo( () => brunchPreviewConfig.isBrunchConfigured && conversationId !== null @@ -525,6 +614,8 @@ export const LocalStorageDemoApp = ({ const flueHistory = useFlueChatHistory( flueClientPromise, conversationId ?? "", + fixtureConfiguration?.clientToolNames, + fixtureConfiguration?.mapClientToolInput, ); const brunchVoiceMode = useMemo( () => @@ -535,15 +626,50 @@ export const LocalStorageDemoApp = ({ ), [conversationTracker, flueHistory.settlements, openAIVoiceConfig], ); - const petrinautAiChatTransport = useMemo( - () => - flueClientPromise === null - ? stockChatTransport - : createBrunchPanelTransport(flueClientPromise, conversationTracker, { - onAdmission: flueHistory.refresh, - }), - [conversationTracker, flueClientPromise, flueHistory.refresh], - ); + const crewReservationSession = useCrewReservationFixtureSession({ + clientPromise: flueClientPromise, + definition: storedSDCPNs[crewReservationDocumentId]?.sdcpn, + enabled: fixtureConfiguration !== undefined, + history: flueHistory.snapshot, + historyError: flueHistory.error?.message, + persistCoherentSnapshot: persistCrewReservationSnapshot, + refreshHistory: flueHistory.refresh, + setSettledManifest, + settledManifest, + snapshotMissing: crewReservationBundle?.snapshotMissing ?? false, + }); + const transportClientPromise = + fixtureConfiguration === undefined + ? flueClientPromise + : crewReservationSession.transportClientPromise; + const petrinautAiChatTransport = useMemo(() => { + if (transportClientPromise !== null) { + return createBrunchPanelTransport( + transportClientPromise, + conversationTracker, + { + ...(fixtureConfiguration === undefined + ? {} + : { + clientToolNames: fixtureConfiguration.clientToolNames, + mapClientToolInput: fixtureConfiguration.mapClientToolInput, + }), + onAdmission: flueHistory.refresh, + }, + ); + } + return fixtureConfiguration !== undefined + ? createUnavailableBrunchPanelTransport( + crewReservationSession.transportUnavailableReason, + ) + : stockChatTransport; + }, [ + conversationTracker, + crewReservationSession.transportUnavailableReason, + fixtureConfiguration, + flueHistory.refresh, + transportClientPromise, + ]); const aiAssistant = useMemo( () => ({ @@ -628,7 +754,21 @@ export const LocalStorageDemoApp = ({ } return ( -
+
+ {preparedFixtureIsCurrent && ( + + )} + {!preparedFixtureIsCurrent && } { + test("recovers an existing prepared conversation without resubmitting", async () => { + const send = vi.fn(); + const wait = vi.fn(); + + await expect( + prepareCrewReservationConversation({ + history: vi.fn().mockResolvedValue(preparedHistory), + send, + wait, + }), + ).resolves.toEqual(preparedHistory); + expect(send).not.toHaveBeenCalled(); + expect(wait).not.toHaveBeenCalled(); + }); + + test("creates revision zero once through the tagged signal delivery", async () => { + const history = vi + .fn() + .mockRejectedValueOnce({ status: 404 }) + .mockResolvedValueOnce(preparedHistory); + const admission = { submissionId: "prepare-submission" }; + const send = vi.fn().mockResolvedValue(admission); + const wait = vi.fn().mockResolvedValue(undefined); + + await expect( + prepareCrewReservationConversation({ history, send, wait }), + ).resolves.toEqual(preparedHistory); + expect(send).toHaveBeenCalledWith({ + uid: null, + initialData: { mode: preparedWorkpieceInitialDataMode }, + ...preparedCrewReservationDelivery, + }); + expect(wait).toHaveBeenCalledWith(admission); + expect(history).toHaveBeenCalledTimes(2); + }); + + test("refuses an existing conversation without this fixture source", async () => { + await expect( + prepareCrewReservationConversation({ + history: vi.fn().mockResolvedValue({ + ...preparedHistory, + messages: [], + }), + send: vi.fn(), + wait: vi.fn(), + }), + ).rejects.toThrow(/no recoverable workpiece/u); + }); +}); diff --git a/apps/petrinaut-website/src/main/app/local-storage-demo/prepare-crew-reservation-conversation.ts b/apps/petrinaut-website/src/main/app/local-storage-demo/prepare-crew-reservation-conversation.ts new file mode 100644 index 00000000000..7c8625c06b7 --- /dev/null +++ b/apps/petrinaut-website/src/main/app/local-storage-demo/prepare-crew-reservation-conversation.ts @@ -0,0 +1,81 @@ +import { + preparedWorkpieceInitialDataMode, + selectRunbookWorkpiece, +} from "@hashintel/brunch-agent/workpiece"; + +import { + crewReservationFixtureId, + preparedCrewReservationDelivery, +} from "./prepared-crew-reservation-fixture"; + +import type { CrewReservationHistory } from "./crew-reservation-history"; +import type { AgentSendResult } from "@flue/sdk"; + +export interface PreparedFixtureConversationClient { + readonly history: () => Promise; + readonly send: (input: { + readonly idempotencyKey: string; + readonly initialData: { + readonly mode: typeof preparedWorkpieceInitialDataMode; + }; + readonly message: typeof preparedCrewReservationDelivery.message; + readonly uid: null; + }) => Promise; + readonly wait: (admission: AgentSendResult) => Promise; +} + +const isNotFound = (error: unknown): boolean => + typeof error === "object" && + error !== null && + "status" in error && + error.status === 404; + +const assertPreparedFixtureHistory = ( + history: CrewReservationHistory, +): CrewReservationHistory => { + const currentWorkpiece = selectRunbookWorkpiece(history); + if ( + currentWorkpiece?.sourceKind !== "prepared-signal" && + currentWorkpiece?.sourceKind !== "assistant" + ) { + throw new Error( + "The prepared fixture conversation has no recoverable workpiece.", + ); + } + const preparedSource = history.messages.find( + (message) => + message.signal?.tagName === + preparedCrewReservationDelivery.message.tagName, + ); + if ( + preparedSource?.signal?.attributes?.fixtureId !== crewReservationFixtureId + ) { + throw new Error( + "The prepared fixture conversation belongs to a different fixture.", + ); + } + return history; +}; + +/** + * Create revision zero through Flue's public signal delivery, or recover the + * already-created append-only conversation. Concurrent tabs converge through + * the delivery's deterministic idempotency key. + */ +export const prepareCrewReservationConversation = async ( + client: PreparedFixtureConversationClient, +): Promise => { + try { + return assertPreparedFixtureHistory(await client.history()); + } catch (error) { + if (!isNotFound(error)) throw error; + } + + const admission = await client.send({ + uid: null, + initialData: { mode: preparedWorkpieceInitialDataMode }, + ...preparedCrewReservationDelivery, + }); + await client.wait(admission); + return assertPreparedFixtureHistory(await client.history()); +}; diff --git a/apps/petrinaut-website/src/main/app/local-storage-demo/prepared-crew-reservation-fixture.test.ts b/apps/petrinaut-website/src/main/app/local-storage-demo/prepared-crew-reservation-fixture.test.ts new file mode 100644 index 00000000000..9374b8df606 --- /dev/null +++ b/apps/petrinaut-website/src/main/app/local-storage-demo/prepared-crew-reservation-fixture.test.ts @@ -0,0 +1,80 @@ +import { describe, expect, test } from "vitest"; + +import { + crewReservationFixtureClientToolNames, + crewReservationFixtureId, + dispatchCrewPlaceId, + isCrewReservationFixtureSelected, + preparedCrewReservationDelivery, + preparedCrewReservationNet, + preparedCrewReservationWorkpiece, + startFinalInspectionTransitionId, +} from "./prepared-crew-reservation-fixture"; + +const transitionById = (transitionId: string) => { + const transition = preparedCrewReservationNet.transitions.find( + (candidate) => candidate.id === transitionId, + ); + if (transition === undefined) { + throw new Error(`Missing prepared transition ${transitionId}`); + } + return transition; +}; + +describe("prepared crew-reservation fixture", () => { + test("advertises only the selected canonical read and mutation", () => { + expect(crewReservationFixtureClientToolNames).toEqual([ + "getLatestNetDefinition", + "addArc", + ]); + }); + + test("has the batch flow and crew return but omits the target input arc", () => { + const startInspection = transitionById(startFinalInspectionTransitionId); + const signOff = transitionById("sign-off"); + + expect(startInspection.inputArcs).toContainEqual({ + placeId: "batch-ready", + type: "standard", + weight: 1, + }); + expect(startInspection.inputArcs).not.toContainEqual( + expect.objectContaining({ placeId: dispatchCrewPlaceId }), + ); + expect(signOff.outputArcs).toEqual( + expect.arrayContaining([ + { placeId: "ready-for-dispatch", weight: 1 }, + { placeId: dispatchCrewPlaceId, weight: 1 }, + ]), + ); + }); + + test("carries the quantity, unknowns, and honest claim boundary", () => { + expect(preparedCrewReservationWorkpiece).toContain( + "Exactly one dispatch crew", + ); + expect(preparedCrewReservationWorkpiece).toContain( + "timing, failure modes, and recovery behavior remain unresolved", + ); + expect(preparedCrewReservationWorkpiece).toContain( + "not model-produced evidence", + ); + expect(preparedCrewReservationDelivery.message.attributes).toEqual({ + fixtureId: crewReservationFixtureId, + authorship: "test-authored", + claimBoundary: "prepared-not-model-produced", + }); + }); + + test("selects only the explicit stable query value", () => { + expect( + isCrewReservationFixtureSelected( + `?brunch-fixture=${crewReservationFixtureId}`, + ), + ).toBe(true); + expect( + isCrewReservationFixtureSelected("?brunch-fixture=another-fixture"), + ).toBe(false); + expect(isCrewReservationFixtureSelected("")).toBe(false); + }); +}); diff --git a/apps/petrinaut-website/src/main/app/local-storage-demo/prepared-crew-reservation-fixture.ts b/apps/petrinaut-website/src/main/app/local-storage-demo/prepared-crew-reservation-fixture.ts new file mode 100644 index 00000000000..92be1cc9a9c --- /dev/null +++ b/apps/petrinaut-website/src/main/app/local-storage-demo/prepared-crew-reservation-fixture.ts @@ -0,0 +1,154 @@ +import { createPreparedWorkpieceDelivery } from "@hashintel/brunch-agent/workpiece"; +import { + getLatestNetDefinitionToolName, + type PetrinautAiToolName, +} from "@hashintel/petrinaut-core/ai"; + +import type { SDCPN } from "@hashintel/petrinaut-core"; + +export const crewReservationFixtureId = "crew-reservation-v1"; +export const crewReservationDocumentId = + "mission-6-crew-reservation-document-v1"; +export const crewReservationConversationId = + "mission-6-crew-reservation-conversation-v1"; +export const crewReservationFixtureQuery = "brunch-fixture"; +export const crewReservationFixtureClientToolNames = [ + getLatestNetDefinitionToolName, + "addArc", +] as const satisfies readonly PetrinautAiToolName[]; + +export const dispatchCrewPlaceId = "dispatch-crew-available"; +export const startFinalInspectionTransitionId = "start-final-inspection"; + +export const preparedCrewReservationWorkpiece = [ + "Fixture authorship: test-authored preparation for Mission 6.", + "Non-claims: not a Mission 4 candidate, not model-produced evidence, not capture-backed provenance, and not proof of automatic full-net projection.", + "", + "```runbook-ir", + "# Final inspection and dispatch workpiece", + "", + "## Purpose and posture", + "Maintain the narrow batch path from final inspection to dispatch readiness and test one evidence-backed correction against the live Petrinaut document.", + "", + "## Operational account", + "- A batch that is ready enters final inspection.", + "- Final inspection reserves the sole available dispatch crew.", + "- Sign-off releases that crew and makes the batch ready for dispatch.", + "", + "## Quantity and resource policy", + "Exactly one dispatch crew is available in this fixture. Starting final inspection consumes that one available crew; sign-off returns it.", + "", + "## Current Petrinaut correspondence", + "The prepared non-empty net contains the batch path and the crew return from sign-off. It deliberately lacks the standard weight-1 input arc from `Dispatch crew available` to `Start final inspection`.", + "", + "## Explicit unknowns", + "Inspection and sign-off timing, failure modes, and recovery behavior remain unresolved.", + "", + "## Claim boundary", + "This prepared revision is test-authored diagnostic material. It is not model-produced evidence and does not establish capture provenance, behavioral execution, or broad projection quality.", + "```", +].join("\n"); + +export const preparedCrewReservationDelivery = createPreparedWorkpieceDelivery({ + body: preparedCrewReservationWorkpiece, + fixtureId: crewReservationFixtureId, + revision: 0, +}); + +export const preparedCrewReservationNet: SDCPN = { + places: [ + { + id: "batch-ready", + name: "Batch ready", + colorId: null, + dynamicsEnabled: false, + differentialEquationId: null, + x: 80, + y: 100, + }, + { + id: "under-final-inspection", + name: "Under final inspection", + colorId: null, + dynamicsEnabled: false, + differentialEquationId: null, + x: 420, + y: 100, + }, + { + id: "ready-for-dispatch", + name: "Ready for dispatch", + colorId: null, + dynamicsEnabled: false, + differentialEquationId: null, + x: 760, + y: 100, + }, + { + id: dispatchCrewPlaceId, + name: "Dispatch crew available", + colorId: null, + dynamicsEnabled: false, + differentialEquationId: null, + x: 420, + y: 360, + }, + ], + transitions: [ + { + id: startFinalInspectionTransitionId, + name: "Start final inspection", + inputArcs: [ + { + placeId: "batch-ready", + type: "standard", + weight: 1, + }, + ], + outputArcs: [ + { + placeId: "under-final-inspection", + weight: 1, + }, + ], + lambdaType: "predicate", + lambdaCode: "", + transitionKernelCode: "", + x: 250, + y: 100, + }, + { + id: "sign-off", + name: "Sign-off", + inputArcs: [ + { + placeId: "under-final-inspection", + type: "standard", + weight: 1, + }, + ], + outputArcs: [ + { + placeId: "ready-for-dispatch", + weight: 1, + }, + { + placeId: dispatchCrewPlaceId, + weight: 1, + }, + ], + lambdaType: "predicate", + lambdaCode: "", + transitionKernelCode: "", + x: 590, + y: 100, + }, + ], + types: [], + parameters: [], + differentialEquations: [], +}; + +export const isCrewReservationFixtureSelected = (search: string): boolean => + new URLSearchParams(search).get(crewReservationFixtureQuery) === + crewReservationFixtureId; diff --git a/apps/petrinaut-website/src/main/app/local-storage-demo/prepared-fixture-banner.test.tsx b/apps/petrinaut-website/src/main/app/local-storage-demo/prepared-fixture-banner.test.tsx new file mode 100644 index 00000000000..28ad5065b61 --- /dev/null +++ b/apps/petrinaut-website/src/main/app/local-storage-demo/prepared-fixture-banner.test.tsx @@ -0,0 +1,84 @@ +import { renderToStaticMarkup } from "react-dom/server"; +import { describe, expect, test } from "vitest"; + +import { + asCanonicalConversationId, + asConversationOffset, + asFlueMessageId, + asFlueSubmissionId, + asManifestId, + asSha256Digest, +} from "./crew-reservation-settled-manifest"; +import { + crewReservationConversationId, + crewReservationDocumentId, + crewReservationFixtureId, +} from "./prepared-crew-reservation-fixture"; +import { + PreparedFixtureBanner, + PreparedFixtureSelector, +} from "./prepared-fixture-banner"; + +describe("PreparedFixtureBanner", () => { + test("offers a stable labelled fixture selector", () => { + const markup = renderToStaticMarkup(); + + expect(markup).toContain("Prepared fixture selector"); + expect(markup).toContain("Open the labelled crew-reservation fixture"); + expect(markup).toContain("?brunch-fixture=crew-reservation-v1"); + }); + + test("visibly states authorship, non-claims, and automatic settlement", () => { + const markup = renderToStaticMarkup( + , + ); + + expect(markup).toContain("Test-authored prepared fixture"); + expect(markup).toContain("not model-produced evidence"); + expect(markup).toContain("does not claim capture provenance"); + expect(markup).toContain("automatically mirrored document"); + expect(markup).toContain("Current Markdown workpiece"); + expect(markup).toContain("Final inspection and dispatch workpiece"); + }); + + test("visibly retains the prior bundle when settlement is refused", () => { + const markup = renderToStaticMarkup( + , + ); + + expect(markup).toContain( + "Settlement refused (missing-correlated-mutation)", + ); + expect(markup).toContain("bundle revision 3 remains selected"); + }); +}); diff --git a/apps/petrinaut-website/src/main/app/local-storage-demo/prepared-fixture-banner.tsx b/apps/petrinaut-website/src/main/app/local-storage-demo/prepared-fixture-banner.tsx new file mode 100644 index 00000000000..77e60581d2d --- /dev/null +++ b/apps/petrinaut-website/src/main/app/local-storage-demo/prepared-fixture-banner.tsx @@ -0,0 +1,90 @@ +import { latestRunbookIrBlock } from "@hashintel/brunch-agent/workpiece"; + +import { + crewReservationFixtureId, + crewReservationFixtureQuery, + preparedCrewReservationWorkpiece, +} from "./prepared-crew-reservation-fixture"; + +import type { CrewReservationSettledManifest } from "./crew-reservation-settled-manifest"; +import type { CrewReservationSettlementStatus } from "./use-crew-reservation-settled-manifest"; + +const fixturePanelStyle = { + background: "rgba(255, 255, 255, 0.96)", + border: "1px solid #c9d2df", + borderRadius: 8, + boxShadow: "0 2px 8px rgba(20, 33, 50, 0.12)", + left: 16, + maxWidth: 520, + padding: "10px 12px", + position: "absolute", + top: 16, + zIndex: 20, +} as const; + +export const PreparedFixtureSelector = () => ( + +); + +export const PreparedFixtureBanner = ({ + currentWorkpiece, + settledManifest, + settlementStatus = { state: "preparing" }, +}: { + readonly currentWorkpiece?: string; + readonly settledManifest: CrewReservationSettledManifest | null; + readonly settlementStatus?: CrewReservationSettlementStatus; +}) => { + const displayedWorkpiece = + currentWorkpiece ?? + (settledManifest === null + ? latestRunbookIrBlock(preparedCrewReservationWorkpiece) + : undefined); + + return ( + + ); +}; diff --git a/apps/petrinaut-website/src/main/app/local-storage-demo/resolve-crew-reservation-bundle.test.ts b/apps/petrinaut-website/src/main/app/local-storage-demo/resolve-crew-reservation-bundle.test.ts new file mode 100644 index 00000000000..7da06ba9129 --- /dev/null +++ b/apps/petrinaut-website/src/main/app/local-storage-demo/resolve-crew-reservation-bundle.test.ts @@ -0,0 +1,192 @@ +import { describe, expect, test } from "vitest"; + +import { + latestRunbookIrBlock, + preparedWorkpieceAuthorship, + preparedWorkpieceClaimBoundary, + preparedWorkpieceSignalTag, +} from "@hashintel/brunch-agent/workpiece"; + +import { + asCanonicalConversationId, + asConversationOffset, + asFlueMessageId, + asFlueSubmissionId, + asManifestId, + sha256Digest, + type CrewReservationSettledManifest, +} from "./crew-reservation-settled-manifest"; +import { + crewReservationConversationId, + crewReservationDocumentId, + crewReservationFixtureId, + preparedCrewReservationNet, + preparedCrewReservationWorkpiece, +} from "./prepared-crew-reservation-fixture"; +import { + resolveCrewReservationBundle, + workpieceForCrewReservationBundle, +} from "./resolve-crew-reservation-bundle"; + +import type { CrewReservationHistory } from "./crew-reservation-history"; +import type { SDCPNInLocalStorage } from "./use-local-storage-sdcpns"; + +const preparedMessage = { + id: "prepared-message", + role: "system", + purpose: "dispatch", + submissionId: "prepare-submission", + signal: { + tagName: preparedWorkpieceSignalTag, + attributes: { + fixtureId: crewReservationFixtureId, + authorship: preparedWorkpieceAuthorship, + claimBoundary: preparedWorkpieceClaimBoundary, + }, + }, + parts: [{ type: "text", text: preparedCrewReservationWorkpiece }], +} as const; + +const preparedContent = latestRunbookIrBlock(preparedCrewReservationWorkpiece); +if (preparedContent === undefined) { + throw new Error("The prepared fixture has no runbook-ir workpiece."); +} + +const manifest: CrewReservationSettledManifest = { + version: 1, + fixtureId: crewReservationFixtureId, + manifestId: asManifestId("manifest"), + revision: 0, + settledAt: "2026-09-04T08:00:00.000Z", + conversation: { + canonicalId: asCanonicalConversationId("canonical"), + logicalId: crewReservationConversationId, + offset: asConversationOffset("2"), + }, + document: { + id: crewReservationDocumentId, + sha256: sha256Digest(JSON.stringify(preparedCrewReservationNet)), + targetArc: "absent", + }, + latestWorkpiece: { + authorship: "test-authored", + contentSha256: sha256Digest(preparedContent), + sourceKind: "prepared-signal", + sourceMessageId: asFlueMessageId(preparedMessage.id), + sourceMessageSha256: sha256Digest(JSON.stringify(preparedMessage)), + sourceSubmissionId: asFlueSubmissionId(preparedMessage.submissionId), + }, +}; + +const fallbackDocument: SDCPNInLocalStorage = { + id: crewReservationDocumentId, + title: "Prepared", + sdcpn: preparedCrewReservationNet, + lastUpdated: "1970-01-01T00:00:00.000Z", +}; + +const history: CrewReservationHistory = { + conversationId: "canonical", + offset: "3", + settlements: [], + messages: [ + preparedMessage, + { + id: "newer-message", + role: "assistant", + purpose: "assistant", + parts: [ + { + type: "text", + text: "```runbook-ir\n# Unsettled newer workpiece\n```", + }, + ], + }, + ], +}; + +describe("resolveCrewReservationBundle", () => { + test("selects a coherent document whose content matches the manifest digest", () => { + const partialDefinition = structuredClone(preparedCrewReservationNet); + const firstPlace = partialDefinition.places.at(0); + if (firstPlace === undefined) { + throw new Error("The prepared fixture has no places."); + } + firstPlace.name = "Partial write"; + + const selection = resolveCrewReservationBundle({ + fallbackDocument, + manifest, + storedDocument: { + ...fallbackDocument, + sdcpn: partialDefinition, + coherentSnapshots: { + [manifest.document.sha256]: preparedCrewReservationNet, + }, + }, + }); + + expect(selection.snapshotMissing).toBe(false); + expect(selection.selectedDocument.sdcpn).toEqual( + preparedCrewReservationNet, + ); + expect(selection.selectedDocument.sdcpn).not.toEqual(partialDefinition); + }); + + test("refuses a snapshot stored under a digest that its content does not match", () => { + const corruptedSnapshot = structuredClone(preparedCrewReservationNet); + const firstPlace = corruptedSnapshot.places.at(0); + if (firstPlace === undefined) { + throw new Error("The prepared fixture has no places."); + } + firstPlace.name = "Corrupted snapshot"; + + const selection = resolveCrewReservationBundle({ + fallbackDocument, + manifest, + storedDocument: { + ...fallbackDocument, + coherentSnapshots: { + [manifest.document.sha256]: corruptedSnapshot, + }, + }, + }); + + expect(selection.snapshotMissing).toBe(true); + expect(selection.selectedDocument.sdcpn).toEqual(fallbackDocument.sdcpn); + expect(selection.selectedDocument.sdcpn).not.toEqual(corruptedSnapshot); + }); + + test("keeps a missing snapshot diagnosable without inventing a revision", () => { + expect( + resolveCrewReservationBundle({ + fallbackDocument, + manifest, + storedDocument: fallbackDocument, + }), + ).toEqual({ + selectedDocument: fallbackDocument, + snapshotMissing: true, + }); + }); +}); + +describe("workpieceForCrewReservationBundle", () => { + test("selects the source whose content and record match the manifest hashes", () => { + expect(workpieceForCrewReservationBundle(history, manifest)).toContain( + "# Final inspection and dispatch workpiece", + ); + }); + + test("refuses a selected source whose manifest hash does not match", () => { + expect( + workpieceForCrewReservationBundle(history, { + ...manifest, + latestWorkpiece: { + ...manifest.latestWorkpiece, + sourceMessageSha256: sha256Digest("mismatched source"), + }, + }), + ).toBeUndefined(); + }); +}); diff --git a/apps/petrinaut-website/src/main/app/local-storage-demo/resolve-crew-reservation-bundle.ts b/apps/petrinaut-website/src/main/app/local-storage-demo/resolve-crew-reservation-bundle.ts new file mode 100644 index 00000000000..9a601aa517a --- /dev/null +++ b/apps/petrinaut-website/src/main/app/local-storage-demo/resolve-crew-reservation-bundle.ts @@ -0,0 +1,98 @@ +import { + latestRunbookIrBlock, + selectRunbookWorkpiece, +} from "@hashintel/brunch-agent/workpiece"; + +import { + sha256Digest, + type CrewReservationSettledManifest, +} from "./crew-reservation-settled-manifest"; + +import type { CrewReservationHistory } from "./crew-reservation-history"; +import type { SDCPNInLocalStorage } from "./use-local-storage-sdcpns"; + +export interface CrewReservationBundleSelection { + readonly selectedDocument: SDCPNInLocalStorage; + readonly snapshotMissing: boolean; +} + +export const resolveCrewReservationBundle = (input: { + readonly fallbackDocument: SDCPNInLocalStorage; + readonly manifest: CrewReservationSettledManifest | null; + readonly storedDocument: SDCPNInLocalStorage | undefined; +}): CrewReservationBundleSelection => { + const liveDocument = input.storedDocument ?? input.fallbackDocument; + if (input.manifest === null) { + return { + selectedDocument: liveDocument, + snapshotMissing: false, + }; + } + + const coherentDefinition = + liveDocument.coherentSnapshots?.[input.manifest.document.sha256]; + if ( + coherentDefinition === undefined || + sha256Digest(JSON.stringify(coherentDefinition)) !== + input.manifest.document.sha256 + ) { + return { + selectedDocument: liveDocument, + snapshotMissing: true, + }; + } + + return { + selectedDocument: { + ...liveDocument, + sdcpn: coherentDefinition, + }, + snapshotMissing: false, + }; +}; + +export const workpieceForCrewReservationBundle = ( + history: CrewReservationHistory | undefined, + manifest: CrewReservationSettledManifest | null, +): string | undefined => { + if (history === undefined) return undefined; + if (manifest === null) return selectRunbookWorkpiece(history)?.content; + + const selectedMessageIndex = history.messages.findIndex( + ({ id }) => id === manifest.latestWorkpiece.sourceMessageId, + ); + if (selectedMessageIndex === -1) return undefined; + const selectedMessage = history.messages[selectedMessageIndex]; + if (selectedMessage === undefined) return undefined; + + const content = latestRunbookIrBlock( + selectedMessage.parts + .flatMap((part) => (part.type === "text" ? [part.text] : [])) + .join("\n"), + ); + if ( + content === undefined || + sha256Digest(content) !== manifest.latestWorkpiece.contentSha256 || + sha256Digest(JSON.stringify(selectedMessage)) !== + manifest.latestWorkpiece.sourceMessageSha256 + ) { + return undefined; + } + + const selectedWorkpiece = selectRunbookWorkpiece({ + ...history, + messages: history.messages.slice(0, selectedMessageIndex + 1), + }); + if ( + selectedWorkpiece?.sourceMessageId !== + manifest.latestWorkpiece.sourceMessageId || + selectedWorkpiece.sourceSubmissionId !== + manifest.latestWorkpiece.sourceSubmissionId || + selectedWorkpiece.authorship !== manifest.latestWorkpiece.authorship || + selectedWorkpiece.sourceKind !== manifest.latestWorkpiece.sourceKind + ) { + return undefined; + } + + return content; +}; diff --git a/apps/petrinaut-website/src/main/app/local-storage-demo/use-crew-reservation-fixture-session.ts b/apps/petrinaut-website/src/main/app/local-storage-demo/use-crew-reservation-fixture-session.ts new file mode 100644 index 00000000000..415c58f9e5a --- /dev/null +++ b/apps/petrinaut-website/src/main/app/local-storage-demo/use-crew-reservation-fixture-session.ts @@ -0,0 +1,119 @@ +import { useEffect, useMemo } from "react"; + +import { + getLatestNetDefinitionToolName, + normalizePetrinautAiToolInput, +} from "@hashintel/petrinaut-core/ai"; + +import { + crewReservationConversationId, + crewReservationFixtureClientToolNames, +} from "./prepared-crew-reservation-fixture"; +import { workpieceForCrewReservationBundle } from "./resolve-crew-reservation-bundle"; +import { useCrewReservationSettlement } from "./use-crew-reservation-settled-manifest"; +import { usePrepareCrewReservationConversation } from "./use-prepare-crew-reservation-conversation"; + +import type { CrewReservationHistory } from "./crew-reservation-history"; +import type { CrewReservationSettledManifest } from "./crew-reservation-settled-manifest"; +import type { FlueClient } from "@flue/sdk"; +import type { SDCPN } from "@hashintel/petrinaut-core"; + +const clientToolNames: ReadonlySet = new Set( + crewReservationFixtureClientToolNames, +); + +export const crewReservationFixtureConfiguration = { + clientToolNames, + conversationId: crewReservationConversationId, + mapClientToolInput: ({ + input, + toolName, + }: { + readonly input: unknown; + readonly toolName: string; + }) => + toolName === "addArc" || toolName === getLatestNetDefinitionToolName + ? normalizePetrinautAiToolInput(toolName, input) + : input, +} as const; + +export const useCrewReservationFixtureSession = (input: { + readonly clientPromise: Promise | null; + readonly definition: SDCPN | undefined; + readonly enabled: boolean; + readonly history: CrewReservationHistory | undefined; + readonly historyError: string | undefined; + readonly persistCoherentSnapshot: (sha256: string, definition: SDCPN) => void; + readonly refreshHistory: () => void; + readonly setSettledManifest: ( + value: + | CrewReservationSettledManifest + | null + | (( + previous: CrewReservationSettledManifest | null, + ) => CrewReservationSettledManifest | null), + ) => void; + readonly settledManifest: CrewReservationSettledManifest | null; + readonly snapshotMissing: boolean; +}) => { + const { + clientPromise, + definition, + enabled, + history, + historyError, + persistCoherentSnapshot, + refreshHistory, + setSettledManifest, + settledManifest, + snapshotMissing, + } = input; + const preparation = usePrepareCrewReservationConversation( + clientPromise, + enabled, + ); + const preparationStatus = preparation.status; + + useEffect(() => { + if ( + preparationStatus.state === "ready" || + preparationStatus.state === "failed" + ) { + refreshHistory(); + } + }, [preparationStatus.state, refreshHistory]); + + const settlementStatus = useCrewReservationSettlement({ + definition: enabled ? definition : undefined, + enabled, + history: enabled ? history : undefined, + historyError: enabled ? historyError : undefined, + persistCoherentSnapshot, + preparationError: + preparationStatus.state === "failed" + ? preparationStatus.error + : undefined, + setSettledManifest, + settledManifest, + snapshotMissing, + }); + + const currentWorkpiece = useMemo(() => { + try { + return workpieceForCrewReservationBundle(history, settledManifest); + } catch { + return undefined; + } + }, [history, settledManifest]); + + return { + currentWorkpiece, + preparationStatus, + settlementStatus, + transportClientPromise: preparation.clientPromise, + transportUnavailableReason: + preparationStatus.state === "failed" + ? preparationStatus.error + : "The prepared fixture conversation is still being prepared.", + }; +}; diff --git a/apps/petrinaut-website/src/main/app/local-storage-demo/use-crew-reservation-settled-manifest.test.ts b/apps/petrinaut-website/src/main/app/local-storage-demo/use-crew-reservation-settled-manifest.test.ts new file mode 100644 index 00000000000..2d199f9577c --- /dev/null +++ b/apps/petrinaut-website/src/main/app/local-storage-demo/use-crew-reservation-settled-manifest.test.ts @@ -0,0 +1,162 @@ +/** + * @vitest-environment jsdom + */ +import { cleanup, renderHook, waitFor } from "@testing-library/react"; +import { afterEach, beforeEach, expect, test, vi } from "vitest"; + +import { + preparedWorkpieceAuthorship, + preparedWorkpieceClaimBoundary, + preparedWorkpieceSignalTag, +} from "@hashintel/brunch-agent/workpiece"; + +import { crewReservationSettledManifestStorageKey } from "./crew-reservation-settled-manifest"; +import { + crewReservationFixtureId, + dispatchCrewPlaceId, + preparedCrewReservationNet, + preparedCrewReservationWorkpiece, + startFinalInspectionTransitionId, +} from "./prepared-crew-reservation-fixture"; +import { + useCrewReservationSettlement, + useCrewReservationSettledManifestStorage, +} from "./use-crew-reservation-settled-manifest"; + +const preparedHistory = { + conversationId: "canonical-conversation", + offset: "2", + settlements: [{ submissionId: "prepare-submission", outcome: "completed" }], + messages: [ + { + id: "prepared-message", + role: "system", + purpose: "dispatch", + submissionId: "prepare-submission", + signal: { + tagName: preparedWorkpieceSignalTag, + attributes: { + fixtureId: crewReservationFixtureId, + authorship: preparedWorkpieceAuthorship, + claimBoundary: preparedWorkpieceClaimBoundary, + }, + }, + parts: [{ type: "text", text: preparedCrewReservationWorkpiece }], + }, + ], +}; + +beforeEach(() => { + window.localStorage.clear(); +}); + +afterEach(() => { + cleanup(); + window.localStorage.clear(); +}); + +test("keeps the prior runtime bundle selected while a document write is partial", async () => { + const persistCoherentSnapshot = vi.fn(); + const { result, rerender } = renderHook( + ({ definition }: { definition: typeof preparedCrewReservationNet }) => { + const storage = useCrewReservationSettledManifestStorage(); + const status = useCrewReservationSettlement({ + definition, + enabled: true, + history: preparedHistory, + historyError: undefined, + persistCoherentSnapshot, + preparationError: undefined, + setSettledManifest: storage.setSettledManifest, + settledManifest: storage.settledManifest, + snapshotMissing: false, + }); + return { ...storage, status }; + }, + { initialProps: { definition: preparedCrewReservationNet } }, + ); + + await waitFor(() => expect(result.current.status.state).toBe("settled")); + const settledManifest = result.current.settledManifest; + expect(settledManifest?.revision).toBe(0); + expect(persistCoherentSnapshot).toHaveBeenCalledWith( + settledManifest?.document.sha256, + preparedCrewReservationNet, + ); + + const partialDefinition = structuredClone(preparedCrewReservationNet); + const startInspection = partialDefinition.transitions.find( + ({ id }) => id === startFinalInspectionTransitionId, + ); + if (startInspection === undefined) { + throw new Error("Missing prepared start-inspection transition"); + } + startInspection.inputArcs.push({ + placeId: dispatchCrewPlaceId, + type: "standard", + weight: 1, + }); + rerender({ definition: partialDefinition }); + + await waitFor(() => expect(result.current.status.state).toBe("refused")); + expect(result.current.settledManifest).toEqual(settledManifest); + expect( + JSON.parse( + window.localStorage.getItem(crewReservationSettledManifestStorageKey) ?? + "null", + ), + ).toEqual(settledManifest); +}); + +test("surfaces canonical history failure without publishing a bundle", async () => { + const persistCoherentSnapshot = vi.fn(); + const { result } = renderHook(() => { + const storage = useCrewReservationSettledManifestStorage(); + const status = useCrewReservationSettlement({ + definition: preparedCrewReservationNet, + enabled: true, + history: undefined, + historyError: "History unavailable.", + persistCoherentSnapshot, + preparationError: undefined, + setSettledManifest: storage.setSettledManifest, + settledManifest: storage.settledManifest, + snapshotMissing: false, + }); + return { ...storage, status }; + }); + + await waitFor(() => expect(result.current.status.state).toBe("refused")); + expect(result.current.status).toEqual({ + state: "refused", + reason: "history-unavailable", + detail: "History unavailable.", + }); + expect(result.current.settledManifest).toBeNull(); +}); + +test("distinguishes preparation failure from unavailable history", async () => { + const persistCoherentSnapshot = vi.fn(); + const { result } = renderHook(() => { + const storage = useCrewReservationSettledManifestStorage(); + const status = useCrewReservationSettlement({ + definition: preparedCrewReservationNet, + enabled: true, + history: undefined, + historyError: undefined, + persistCoherentSnapshot, + preparationError: "Provider authentication failed.", + setSettledManifest: storage.setSettledManifest, + settledManifest: storage.settledManifest, + snapshotMissing: false, + }); + return { ...storage, status }; + }); + + expect(result.current.status).toEqual({ + state: "refused", + reason: "preparation-failed", + detail: "Provider authentication failed.", + }); + expect(result.current.settledManifest).toBeNull(); +}); diff --git a/apps/petrinaut-website/src/main/app/local-storage-demo/use-crew-reservation-settled-manifest.ts b/apps/petrinaut-website/src/main/app/local-storage-demo/use-crew-reservation-settled-manifest.ts new file mode 100644 index 00000000000..510dd78d5f4 --- /dev/null +++ b/apps/petrinaut-website/src/main/app/local-storage-demo/use-crew-reservation-settled-manifest.ts @@ -0,0 +1,163 @@ +import { useLocalStorage } from "@mantine/hooks"; +import { useEffect, useState } from "react"; + +import { + crewReservationSettledManifestStorageKey, + settleCrewReservationManifest, + type CrewReservationSettledManifest, + type CrewReservationSettlementResult, +} from "./crew-reservation-settled-manifest"; + +import type { CrewReservationHistory } from "./crew-reservation-history"; +import type { SDCPN } from "@hashintel/petrinaut-core"; + +export type CrewReservationSettlementStatus = + | { readonly state: "idle" | "preparing" } + | { readonly state: "settled" } + | { + readonly detail?: string; + readonly reason: + | Extract< + CrewReservationSettlementResult, + { status: "refused" } + >["reason"] + | "bundle-snapshot-unavailable" + | "history-unavailable" + | "preparation-failed" + | "settlement-failed"; + readonly state: "refused"; + }; + +export const useCrewReservationSettledManifestStorage = () => { + const [settledManifest, setSettledManifest] = + useLocalStorage({ + key: crewReservationSettledManifestStorageKey, + defaultValue: null, + getInitialValueInEffect: false, + }); + return { settledManifest, setSettledManifest }; +}; + +export const useCrewReservationSettlement = (input: { + readonly definition: SDCPN | undefined; + readonly enabled: boolean; + readonly history: CrewReservationHistory | undefined; + readonly historyError: string | undefined; + readonly persistCoherentSnapshot: (sha256: string, definition: SDCPN) => void; + readonly preparationError: string | undefined; + readonly setSettledManifest: ( + value: + | CrewReservationSettledManifest + | null + | (( + previous: CrewReservationSettledManifest | null, + ) => CrewReservationSettledManifest | null), + ) => void; + readonly settledManifest: CrewReservationSettledManifest | null; + readonly snapshotMissing: boolean; +}) => { + const { + definition, + enabled, + history, + historyError, + persistCoherentSnapshot, + preparationError, + setSettledManifest, + settledManifest, + snapshotMissing, + } = input; + const [observedStatus, setObservedStatus] = + useState({ state: "preparing" }); + + useEffect(() => { + if ( + !enabled || + historyError !== undefined || + definition === undefined || + history === undefined + ) { + return; + } + + let cancelled = false; + const definitionSnapshot = structuredClone(definition); + const settle = async (): Promise => { + let result: CrewReservationSettlementResult; + try { + result = await settleCrewReservationManifest({ + definition: definitionSnapshot, + history, + ...(settledManifest === null ? {} : { previous: settledManifest }), + settledAt: new Date().toISOString(), + }); + } catch (error) { + if (!cancelled) { + setObservedStatus({ + state: "refused", + reason: "settlement-failed", + detail: + error instanceof Error + ? error.message + : "The coherent bundle could not be inspected.", + }); + } + return; + } + if (cancelled) return; + if (result.status === "refused") { + setObservedStatus({ + state: "refused", + reason: result.reason, + }); + return; + } + persistCoherentSnapshot( + result.manifest.document.sha256, + definitionSnapshot, + ); + if (result.manifest.manifestId !== settledManifest?.manifestId) { + setSettledManifest(result.manifest); + } + setObservedStatus({ state: "settled" }); + }; + void settle(); + + return () => { + cancelled = true; + }; + }, [ + definition, + enabled, + history, + historyError, + persistCoherentSnapshot, + setSettledManifest, + settledManifest, + ]); + + const status: CrewReservationSettlementStatus = !enabled + ? { state: "idle" } + : historyError !== undefined + ? { + state: "refused", + reason: "history-unavailable", + detail: historyError, + } + : snapshotMissing + ? { + state: "refused", + reason: "bundle-snapshot-unavailable", + } + : preparationError !== undefined && + (definition === undefined || history === undefined) + ? { + state: "refused", + reason: "preparation-failed", + detail: preparationError, + } + : definition === undefined || history === undefined + ? { state: "preparing" } + : observedStatus; + return status; +}; diff --git a/apps/petrinaut-website/src/main/app/local-storage-demo/use-flue-chat-history.test.ts b/apps/petrinaut-website/src/main/app/local-storage-demo/use-flue-chat-history.test.ts index f21c7a30033..e361451207c 100644 --- a/apps/petrinaut-website/src/main/app/local-storage-demo/use-flue-chat-history.test.ts +++ b/apps/petrinaut-website/src/main/app/local-storage-demo/use-flue-chat-history.test.ts @@ -2,7 +2,7 @@ import { FlueApiError } from "@flue/sdk"; /** * @vitest-environment jsdom */ -import { cleanup, renderHook, waitFor } from "@testing-library/react"; +import { act, cleanup, renderHook, waitFor } from "@testing-library/react"; import { afterEach, expect, test, vi } from "vitest"; import { useFlueChatHistory } from "./use-flue-chat-history"; @@ -207,3 +207,82 @@ test("closes the SDK observation on unmount", async () => { expect(harness.close).toHaveBeenCalledOnce(); }); + +test("projects fixture client-tool results from canonical signal history", async () => { + const harness = createObservationHarness({ + conversation: { + conversationId: "conversation-1", + settlements: [], + messages: [ + { + id: "assistant-1", + role: "assistant", + purpose: "assistant", + display: "visible", + parts: [ + { + type: "dynamic-tool", + toolName: "addArc", + toolCallId: "arc-1", + state: "output-available", + input: { placeId: "crew" }, + output: { awaiting: "client" }, + }, + ], + }, + { + id: "result-1", + role: "system", + purpose: "dispatch", + display: "diagnostic", + signal: { tagName: "client-tool-result" }, + parts: [ + { + type: "text", + text: JSON.stringify([ + { + toolCallId: "arc-1", + toolName: "addArc", + output: { applied: true }, + }, + ]), + state: "done", + }, + ], + }, + ], + }, + offset: "offset-2", + phase: "live", + error: undefined, + }); + const clientToolNames = new Set(["addArc"]); + const { result } = renderHook(() => + useFlueChatHistory( + harness.clientPromise, + "conversation-1", + clientToolNames, + ), + ); + + await waitFor(() => expect(result.current.ready).toBe(true)); + expect(result.current.messages?.[0]?.parts).toEqual([ + { + type: "tool-addArc", + toolCallId: "arc-1", + state: "output-available", + input: { placeId: "crew" }, + output: { applied: true }, + }, + ]); + expect(result.current.snapshot?.offset).toBe("offset-2"); + expect(result.current.snapshot?.messages.map(({ id }) => id)).toEqual([ + "assistant-1", + "result-1", + ]); + + act(() => { + result.current.refresh(); + }); + expect(harness.refresh).toHaveBeenCalledTimes(1); +}); diff --git a/apps/petrinaut-website/src/main/app/local-storage-demo/use-flue-chat-history.ts b/apps/petrinaut-website/src/main/app/local-storage-demo/use-flue-chat-history.ts index 7e3bf752055..075545ea8e6 100644 --- a/apps/petrinaut-website/src/main/app/local-storage-demo/use-flue-chat-history.ts +++ b/apps/petrinaut-website/src/main/app/local-storage-demo/use-flue-chat-history.ts @@ -16,18 +16,40 @@ import type { PetrinautAiMessage } from "@hashintel/petrinaut/ui"; const noSettlements: readonly FlueConversationSettlement[] = []; +/** + * The observed canonical conversation together with the durable-stream offset + * it was read at. Fixture consumers use the offset to tell a settled bundle + * from a stale one; they never interpret it. + */ +export type FlueHistorySnapshot = FlueConversationState & { + readonly offset: string; +}; + const projectPetrinautMessages = ( conversation: FlueConversationState, + clientToolNames: ReadonlySet, + mapClientToolInput: + | ((input: { + readonly input: unknown; + readonly toolName: string; + }) => unknown) + | undefined, ): PetrinautAiMessage[] => // The host owns this narrowing: its configured client-tool catalog is the // same catalog Petrinaut's message type exposes. snapshotToUiMessages(conversation, { - clientToolNames: brunchClientToolNames, + clientToolNames, + ...(mapClientToolInput === undefined ? {} : { mapClientToolInput }), }) as PetrinautAiMessage[]; export const useFlueChatHistory = ( clientPromise: Promise | null, conversationId: string, + clientToolNames: ReadonlySet = brunchClientToolNames, + mapClientToolInput?: (input: { + readonly input: unknown; + readonly toolName: string; + }) => unknown, ): { readonly error: Error | undefined; readonly latestSettlement: FlueConversationSettlement | undefined; @@ -36,6 +58,7 @@ export const useFlueChatHistory = ( readonly ready: boolean; readonly refresh: () => void; readonly settlements: readonly FlueConversationSettlement[]; + readonly snapshot: FlueHistorySnapshot | undefined; } => { const observationRef = useRef(null); const [observed, setObserved] = useState<{ @@ -93,23 +116,31 @@ export const useFlueChatHistory = ( }; }, [clientPromise, conversationId]); - const snapshot = + const observation = observed?.conversationId === conversationId ? observed.snapshot : undefined; - const conversation = snapshot?.conversation; - const absent = snapshot?.phase === "absent"; + const conversation = observation?.conversation; + const absent = observation?.phase === "absent"; const ready = absent || conversation !== undefined; return { - error: snapshot?.error, + error: observation?.error, latestSettlement: conversation?.settlements.at(-1), messages: conversation === undefined ? absent ? [] : undefined - : projectPetrinautMessages(conversation), - phase: snapshot?.phase, + : projectPetrinautMessages( + conversation, + clientToolNames, + mapClientToolInput, + ), + phase: observation?.phase, ready, refresh, settlements: conversation?.settlements ?? noSettlements, + snapshot: + conversation === undefined || observation?.offset === undefined + ? undefined + : { ...conversation, offset: observation.offset }, }; }; diff --git a/apps/petrinaut-website/src/main/app/local-storage-demo/use-local-storage-sdcpns.ts b/apps/petrinaut-website/src/main/app/local-storage-demo/use-local-storage-sdcpns.ts index 796a72af1d6..dad4583d4c5 100644 --- a/apps/petrinaut-website/src/main/app/local-storage-demo/use-local-storage-sdcpns.ts +++ b/apps/petrinaut-website/src/main/app/local-storage-demo/use-local-storage-sdcpns.ts @@ -5,6 +5,12 @@ import type { SDCPN } from "@hashintel/petrinaut-core"; const rootLocalStorageKey = "petrinaut-sdcpn"; export type SDCPNInLocalStorage = { + /** + * Content-addressed coherent revisions retained by prepared fixtures. The + * live `sdcpn` remains the automatic mirror; these snapshots give a settled + * manifest a concrete document revision to select after a partial write. + */ + coherentSnapshots?: Record; id: string; lastUpdated: string; // ISO timestamp sdcpn: SDCPN; diff --git a/apps/petrinaut-website/src/main/app/local-storage-demo/use-prepare-crew-reservation-conversation.test.ts b/apps/petrinaut-website/src/main/app/local-storage-demo/use-prepare-crew-reservation-conversation.test.ts new file mode 100644 index 00000000000..1d71f49b9b8 --- /dev/null +++ b/apps/petrinaut-website/src/main/app/local-storage-demo/use-prepare-crew-reservation-conversation.test.ts @@ -0,0 +1,34 @@ +/** + * @vitest-environment jsdom + */ +import { renderHook, waitFor } from "@testing-library/react"; +import { expect, test, vi } from "vitest"; + +import { usePrepareCrewReservationConversation } from "./use-prepare-crew-reservation-conversation"; + +import type { FlueClient } from "@flue/sdk"; + +test("reports preparation failure without rejecting the shared client", async () => { + const client = { + history: vi.fn().mockRejectedValue({ status: 404 }), + send: vi.fn().mockResolvedValue({ submissionId: "preparation" }), + wait: vi + .fn() + .mockRejectedValue(new Error("Provider authentication failed")), + } as unknown as FlueClient; + const clientPromise = Promise.resolve(client); + + const { result } = renderHook(() => + usePrepareCrewReservationConversation(clientPromise, true), + ); + + await waitFor(() => expect(result.current.status.state).toBe("failed")); + expect(result.current.status).toEqual({ + state: "failed", + error: "Provider authentication failed", + }); + await expect(clientPromise).resolves.toBe(client); + await expect(result.current.clientPromise).rejects.toThrow( + "Provider authentication failed", + ); +}); diff --git a/apps/petrinaut-website/src/main/app/local-storage-demo/use-prepare-crew-reservation-conversation.ts b/apps/petrinaut-website/src/main/app/local-storage-demo/use-prepare-crew-reservation-conversation.ts new file mode 100644 index 00000000000..6b66fa2de3a --- /dev/null +++ b/apps/petrinaut-website/src/main/app/local-storage-demo/use-prepare-crew-reservation-conversation.ts @@ -0,0 +1,69 @@ +import { useEffect, useMemo, useState } from "react"; + +import { prepareCrewReservationConversation } from "./prepare-crew-reservation-conversation"; + +import type { FlueClient } from "@flue/sdk"; + +export type CrewReservationPreparationStatus = + | { readonly state: "idle" | "preparing" | "ready" } + | { readonly error: string; readonly state: "failed" }; + +export const usePrepareCrewReservationConversation = ( + clientPromise: Promise | null, + enabled: boolean, +): { + readonly clientPromise: Promise | null; + readonly status: CrewReservationPreparationStatus; +} => { + const preparedClientPromise = useMemo(() => { + if (!enabled || clientPromise === null) return clientPromise; + return clientPromise.then(async (client) => { + await prepareCrewReservationConversation(client); + return client; + }); + }, [clientPromise, enabled]); + const [observed, setObserved] = useState<{ + readonly clientPromise: Promise; + readonly status: CrewReservationPreparationStatus; + }>(); + + useEffect(() => { + if (!enabled || preparedClientPromise === null) return; + + let cancelled = false; + const prepare = async (): Promise => { + try { + await preparedClientPromise; + if (!cancelled) { + setObserved({ + clientPromise: preparedClientPromise, + status: { state: "ready" }, + }); + } + } catch (error) { + if (cancelled) return; + setObserved({ + clientPromise: preparedClientPromise, + status: { + state: "failed", + error: + error instanceof Error + ? error.message + : "The prepared conversation could not be initialized.", + }, + }); + } + }; + void prepare(); + return () => { + cancelled = true; + }; + }, [enabled, preparedClientPromise]); + + const status: CrewReservationPreparationStatus = !enabled + ? { state: "idle" } + : observed?.clientPromise === preparedClientPromise + ? observed.status + : { state: "preparing" }; + return { clientPromise: preparedClientPromise, status }; +}; diff --git a/apps/petrinaut-website/src/main/app/voice-interview/voice-preview.integration.test.ts b/apps/petrinaut-website/src/main/app/voice-interview/voice-preview.integration.test.ts index 6e4a0a63095..ec153c77af7 100644 --- a/apps/petrinaut-website/src/main/app/voice-interview/voice-preview.integration.test.ts +++ b/apps/petrinaut-website/src/main/app/voice-interview/voice-preview.integration.test.ts @@ -522,6 +522,7 @@ describe("controlled voice preview", () => { expect.objectContaining({ type: "submission-accepted" }), ); expect(send).toHaveBeenCalledWith({ + idempotencyKey: "ai-sdk:user:voice-realtime:1:call-1", message: { kind: "user", body: "The supervisor approves it." }, signal: undefined, }); diff --git a/libs/@hashintel/brunch-agent/MISSION.md b/libs/@hashintel/brunch-agent/MISSION.md index 2d09a6215ad..4ceaa2f6e88 100644 --- a/libs/@hashintel/brunch-agent/MISSION.md +++ b/libs/@hashintel/brunch-agent/MISSION.md @@ -1,163 +1,161 @@ -# Mission 5 — one Flue conversation route for Voice and the typed panel +# Mission 6 — resume one prepared workpiece and Petrinaut document ## Status -**Live as of 2026-09-03** for [FE-1574](https://linear.app/hash/issue/FE-1574/let-voice-speak-through-canonical-brunch-conversations) on `ln/fe-1574-direct-voice-flue`, stacked directly on the closed Mission 4 branch. This is the sole execution authority for the branch. The builder implementation now routes typed panel and finalized Voice turns through one browser `FlueClient` at `/agents/chat/:instanceId`, projects canonical replies into Petrinaut and TTS, uses durable Flue abort for explicit Stop, and rehydrates canonical conversation state through SDK observation. The former Brunch `/api/chat` handler and projector are deleted. Mission acceptance remains open until the required human demo and proof-leaf-8 bundle exist. +**Live on `ln/fe-1575-resumable-workpiece-petrinaut`.** [FE-1575](https://linear.app/hash/issue/FE-1575/resume-one-brunch-workpiece-and-petrinaut-document-across-tabs) remains in progress. The implementation and outer mechanical witness are complete; cold-reader semantic adjudication and the product-manager demo remain the final acceptance gates. See the [retained implementation and witness evidence](docs/evidence/implementations/fe-1575-resumable-workpiece-petrinaut.md). -The accepted departure base remains Mission 4's package-composed `ChatAgent`: `useBrunchAgent()` mounts core's independent `elicitation` capability and `useSdcpnPlugin()` mounts the SDCPN job contribution. `@hashintel/brunch-agent-transport-aisdk` is now the browser adapter over public `@flue/sdk`, not a server handler. The current external Voice evidence remains PR [#9496](https://github.com/hashintel/hash/pull/9496) at `c7fe8a2e68e8fdc37018b21ec2e9daf4e9ef7c82`, PR [#9507](https://github.com/hashintel/hash/pull/9507) at `252b9dbb0c77fae8cee45a506f09cac3e20c381c`, and PR [#9512](https://github.com/hashintel/hash/pull/9512) at `d13535d1077b3a78d6a1411031b7d0a0a78e3144`. They are read-only source evidence, not branches to rewrite or wholesale architecture to restore. +## Imperative -Cold-start reads are [`docs/evidence/implementations/mission-4-voice-integration-handoff.md`](docs/evidence/implementations/mission-4-voice-integration-handoff.md), [`packages/transport-aisdk/src/index.ts`](packages/transport-aisdk/src/index.ts), [`packages/transport-aisdk/src/ui-stream.ts`](packages/transport-aisdk/src/ui-stream.ts), [`packages/transport-aisdk/src/transcript.ts`](packages/transport-aisdk/src/transcript.ts), [`apps/brunch-agent/src/app.ts`](../../../apps/brunch-agent/src/app.ts), [`apps/brunch-agent/src/http/ownership.ts`](../../../apps/brunch-agent/src/http/ownership.ts), [`apps/brunch-agent/test/petrinaut-chat.integration.ts`](../../../apps/brunch-agent/test/petrinaut-chat.integration.ts), [`apps/brunch-agent/test/architecture/boundaries.integration.ts`](../../../apps/brunch-agent/test/architecture/boundaries.integration.ts), [`apps/petrinaut-website/src/main/app/local-storage-demo/`](../../../apps/petrinaut-website/src/main/app/local-storage-demo/), [`apps/petrinaut-website/src/main/app/voice-interview/`](../../../apps/petrinaut-website/src/main/app/voice-interview/), and the installed Flue 2.0.3 documentation at `node_modules/@flue/sdk/docs/sdk/flue-client.md`, `node_modules/@flue/sdk/docs/reference/streaming-protocol.md`, and `node_modules/@flue/sdk/docs/guide/react.md`. +Determine whether one canonical Brunch conversation can maintain a useful Markdown workpiece and drive a meaningful change to a real Petrinaut document through the browser without reviving a comprehensive typed domain IR. -## Imperative +Mission 3 separately showed a recoverable Markdown workpiece and hermetic canonical Petrinaut callbacks, but its paid model could not carry a nested construction schema and no product path joined the two results. Mission 4 accepted the independent core `elicitation` capability and SDCPN job-skill composition but produced no full-run candidate. This mission must retire the join and resume uncertainty honestly with one deliberately prepared fixture rather than treating either historical result as an integrated product. + +### Visible product advance -Make the mounted Flue conversation route the only product door into a Brunch conversation, and make Voice a faithful audio projection of that one canonical conversation. One finalized spoken answer and one typed panel message must both enter the owning Flue conversation through `@flue/sdk` against `/agents/chat/:instanceId`, and the corresponding canonical Brunch response must reach visible text and TTS without another model rewriting the text. Do this now because Mission 4 established the canonical agent composition while two transports still exist to the same conversation: the Voice preview's AI SDK composer path and the server-side `/api/chat` adapter, which admits through a different code path than the SDK does. Routing Voice onto Flue while keeping `/api/chat` for typed text would harden the split into two routes, two ownership rules, and two protocols; the least mechanism is one route, with the AI SDK reduced to the panel's rendering contract behind a host-supplied browser `ChatTransport`. +**Release note:** Brunch edits the Petrinaut net you are looking at from the conversation, and your work survives closing the tab. -### Product-manager litmus +**Demo script (no engineer present):** with the local Brunch/Petrinaut development stack running, open the stable demo fixture selector for the labelled prepared crew-reservation case. Its canonical Brunch conversation, current Markdown workpiece, and non-empty Petrinaut net come back together. The fixture visibly states that it is test-authored and prepared, and what it does not claim. Tell Brunch the one new realistic fact the fixture is prepared for: final inspection uses the single dispatch crew, and sign-off releases it. Watch the workpiece update and a new arc appear in the live net from `Dispatch crew available` to `Start final inspection`. Wait until the fixture reports that the conversation, workpiece, and automatically mirrored document are settled. Open the same fixture in a second tab, submit one follow-up message, and receive Brunch's response in the same conversation without duplicate submission or identity drift. -Adopted on restack onto the parent spine's 2026-09-03 litmus reframing. A product manager who did not watch the work must be able to notice the advance; the single-route consolidation, the browser `ChatTransport`, the deleted `/api/chat` door, and the repurposed transport package are internal sequencing and must not be presented as the advance. +**Previously impossible:** Brunch only produced off-canvas net JSON for manual load; nothing it did touched the live document or survived a reload. -**Release note:** in the Petrinaut Brunch panel you can type or speak to Brunch in one conversation; what you hear is exactly what Brunch wrote; **Stop** really stops Brunch rather than just hiding its answer; and reopening the panel shows the same conversation you left, without re-sending or replaying anything. +**Deployment posture:** the demo runs against the locally run Petrinaut website and Brunch agent (`yarn dev:brunch`). Mission 8 stopped before remote deployment, so no product-manager-noticeable claim here depends on remote infrastructure; remote durability stays with Mission 8. -**Demo script (no engineer present), on the deployment posture available at cut time — the local `yarn dev:brunch` pair with the Brunch preview selected:** open the panel and type one message; read the reply. Start Voice mode and speak one answer; see exactly one new user message appear, then see Brunch's reply appear as text and hear the same words read aloud. Speak over it once; playback stops and the text stays. Ask a second question and press **Stop** while Brunch is still working; the conversation shows that turn as stopped, not as an answer. Close the panel and reopen the same conversation: the typed turn, the spoken turn, and the stopped turn are all there exactly as you saw them, nothing replays, and nothing is sent again. +**Completion:** the mission is done when a product manager can run that demo script end to end for this fixture and every readiness-gate obligation in [Proof](#proof) is closed. The first green pass through the two-tab path is an internal milestone inside the mission, not its completion. -**Previously impossible:** Stop only cancelled the browser request while Brunch kept working, so reopening the panel showed a full answer you had stopped; typed and spoken turns entered Brunch through different doors, so a spoken turn could be held or ordered differently from a typed one. +## Throughline -**Completion:** the mission is complete at the contract stratum below — when a product manager can run this demo script end to end and proof leaf 8's witness bundle records it — not when the first typed or spoken turn crosses the route. The first green typed-panel tracer and the first green Voice tracer are internal milestones. +### Observed departure point and first unproved boundary -### Recut rationale +The production browser already has most local pieces: -Inspected at the real boundary on 2026-09-03 (`node_modules/@flue/sdk/docs/reference/streaming-protocol.md`, `packages/transport-aisdk/src/index.ts`, `packages/transport-aisdk/src/ui-stream.ts`, `apps/petrinaut-website/src/main/app/local-storage-demo/brunch-panel-transport.ts`, `node_modules/ai/dist/index.d.ts` `ChatTransport`): +- `apps/petrinaut-website`'s local-storage demo owns an editable `PetrinautDocHandle`, automatically writes handle changes to `petrinaut-sdcpn`, and maps each net to a persistent Brunch conversation id; +- Petrinaut's stock AI panel already validates and executes canonical read, mutation, and command tools against the active browser document and returns the original tool-call id; +- live Mission 5 is replacing the server-side `GET`/`POST /api/chat` adapter with one browser `FlueClient` plus host-supplied AI SDK `ChatTransport` over the mounted `/agents/chat/:instanceId` route; that route carries typed turns, `history()` hydration, and correlated client-tool-result signals, and Mission 6 must consume rather than duplicate or reverse that transport; and +- the SDCPN skill already emits a full recoverable `runbook-ir` block and requires construction to consume that workpiece rather than transcript archaeology. -- Flue's SSE does not remove the need for translation while the panel speaks `useChat`: Flue admits one `DeliveredMessage` with a 202 and streams `ConversationStreamChunk` batches on a separate, never-ending offset-resumed read; the AI SDK posts the whole `messages` array and expects one finite `UIMessageChunk` stream per turn. Request shape, vocabulary, and lifecycle all differ, and `@flue/*` ships no AI SDK adapter. -- At the recut, the adapter was one translation cut across two homes by the transport topology gate: AI SDK request framing, CORS, and principal parsing in `transport-aisdk` (then Flue-free), and the load-bearing `ConversationStreamChunk → UIMessageChunk` projection plus snapshot → UI messages in `apps/brunch-agent/src/conversation/`. -- A Hono-level relocation would have re-expressed the same admit → follow → project → terminate join as a server handler; it would have removed nothing and kept two routes. It was rejected. -- AI SDK `ChatTransport` is a client-side interface, and Petrinaut already accepts and wraps host-supplied transports. The landed browser transport over the same `createFlueClient()` that Voice uses owns the projector, removes `/api/chat` and its handler, and makes typed and spoken turns share one admission path, one ownership guard, and one protocol. +The first unproved boundary is ordinary Brunch conversation over Mission 5's browser Flue transport → mounted canonical document read / least mutation → browser execution → correlated Flue continuation. Today ordinary SDCPN conversations mount only the Petrinaut documentation reader as a browser tool; the validated construction subset is headless-only, and no stable fixture selector or coherent cross-tab witness joins the conversation, workpiece, and document lifecycles. Mission 6 may develop independent fixture/workpiece/document logic while Mission 5 proceeds, but it must consume Mission 5's landed browser transport before integrated or outer proof and must not add another conversation route in the interim. -## Throughline +### Accepted fixture and boundary crossings + +Prepare the existing final-inspection / dispatch-crew case as an explicitly test-authored fixture. Its starting workpiece and non-empty net preserve this narrow account: one crew is reserved during final inspection, sign-off releases it, the batch then becomes dispatch-ready, and timing plus failure/recovery remain unresolved. Its prepared material must identify its authorship and must not be presented as a Mission 4 candidate or model-produced evidence. -The real boundary is the local Petrinaut surface driven by `yarn dev:brunch` — both its typed panel and its Voice control — through one same-origin-proxied Flue route into the mounted production `ChatAgent`, back to visible Petrinaut text and, for Voice, audible playback: +Deliver the prepared starting workpiece exactly once through Flue's public dispatch surface as a tagged structured signal. Its canonical record must remain `role: system`, `purpose: dispatch`, carry the fixture id, `test-authored` authorship, and non-claims as signal attributes, and preserve the exact Markdown body. This record is prepared revision zero. Later full `runbook-ir` blocks emitted in genuine assistant responses are model-produced revisions; the workpiece resolver selects the latest eligible revision without rewriting Flue's append-only history. This is analogous to last-one-wins selection of extension-contributed artifacts in a Pi raw session log, not permission to overwrite either log. + +The disposable production-route probe established the carrier facts: the tagged signal retained its exact body and attributes, an exact idempotent retry converged on the original submission without adding messages, and the snapshot survived process reopen unchanged. The current `ChatAgent` rejected fixture authorship supplied as `initialData` with `400` and created no history; `initialData` is not a substitute for the public workpiece source. A user delivery would impersonate the person, while faux-provider output, hand-authored assistant records, private canonical record types, direct database writes, and a second history store are not preparation routes. The probe's configured Anthropic credential was rejected with `401`, so its separately classified faux assistant response established no model-behavior claim. + +Prepare the net with `Batch ready`, `Under final inspection`, `Ready for dispatch`, and `Dispatch crew available` places plus `Start final inspection` and `Sign-off` transitions. Preserve the batch-flow arcs and the return of the crew from sign-off, but deliberately omit the standard input arc that reserves the sole crew when final inspection starts. Use one realistic confirming answer: final inspection consumes the sole available dispatch crew, sign-off returns it, and timing plus recovery remain unknown. The least candidate mutation is one canonical weight-1 standard input arc from `Dispatch crew available` to `Start final inspection`. The exact before/after edge makes the semantic oracle discriminating while avoiding Mission 3's deeply nested schema failure. If that shallow mutation still cannot cross Flue faithfully, stop with the carrier blocker rather than weakening the claim. ```text - Petrinaut typed panel (`useChat`) Petrinaut microphone - → host-supplied browser `ChatTransport` → OpenAI Realtime provisional STT + turn detection - (`sendMessages` → one user text or → one validated finalized `continue_interview` answer - one client-tool-result signal) - └──────────────┬────────────────────────────┘ -→ one browser `createFlueClient()` per selected principal + logical conversation id -→ one supported `send()` admission at the same-origin proxied `/agents/chat/:instanceId` route - (Voice may enter via the panel's transport — preferred, one visible store — or call `send()` directly; see fog-line) -→ `agentOwnershipGuard` (the only ownership check) → `createAgentRouter(ChatAgent)` -→ current `ChatAgent` with `useBrunchAgent()` + `useSdcpnPlugin()` -→ SDK reads: `wait(admission, { onEvent })` for the panel's finite per-turn stream, - `observe({ live: "sse" })` for canonical state and reopen -→ response parts correlated by server-issued `submissionId` -→ panel: existing `ConversationStreamChunk → UIMessageChunk` projector, terminated on `submission-settled` -→ Voice: canonical completed Brunch text displayed and passed unchanged as TTS input -→ local playback/observation cancellation or explicit conversation-wide `abort()` -→ observation rehydration after reopening the same logical conversation +stable prepared-fixture selector +→ resolve distinct fixture, Petrinaut document, and Flue conversation identities +→ open the prepared non-empty browser document and use the browser Flue client to idempotently deliver or recover the tagged revision-zero signal +→ hydrate canonical Flue history through `history()` on the mounted route +→ recover prepared revision zero from the tagged dispatch record, or the latest eligible assistant revision, by source message id plus content hash +→ submit the realistic crew-reservation confirmation through Mission 5's production browser Flue transport +→ Brunch emits an inspectable full workpiece revision without erasing prior meaning or the remaining unknown +→ SDCPN construction reads that current workpiece and the live browser document +→ Brunch requests the least canonical meaningful mutation +→ Petrinaut validates and executes it against the bound document +→ the original tool-call id and result return as one correlated signal through the same browser Flue transport and resume the same conversation +→ inspect the canonical non-empty document and advance the runtime settled manifest only after conversation/workpiece/document state is observable +→ a second tab opens the same fixture selector, resolves the same identities and settled hashes, submits one follow-up, and receives Brunch's response ``` -The `/api/chat` route, `createPetrinautChatHandler`, the in-process `init().dispatch()/read()` admission path, the `GET ?id=` history door, and the `/api/chat` Vite proxy are removed from the Brunch app; the local launcher proxies `/agents/chat/*` instead. `@hashintel/brunch-agent-transport-aisdk` is repurposed as the browser-side adapter: it exports the projector, the snapshot → UI-message projection, the header names, and a `ChatTransport` factory over a caller-supplied `FlueClient`, and depends on `ai` and the public `@flue/sdk` client only. The Petrinaut panel itself stays on `useChat`; it is not rewritten onto `@flue/react`. +The settled fixture manifest is runtime local product state, not merely retained evaluation evidence: the stable selector uses it to choose the latest coherent observed bundle of distinct fixture, Flue conversation, workpiece source/hash, and Petrinaut document/hash or revision identities across reopen. It is a small viability pointer, not a new event log, independent workpiece store, or distributed transaction. A failed history load, workpiece recovery, rejected/no-op mutation, or missing result correlation must leave the prior settled bundle selected while partial state and failure remain visible for diagnosis. Retained witness artifacts copy and inspect this runtime state but do not select the product bundle. The existing automatic localStorage mirror is the only document-save mechanism unless a real failure proves it insufficient; this mission adds no explicit Save affordance. -`conversationId` is the stable logical reference selected by the Petrinaut host. The current principal plus that id mechanically derives the Flue instance path and ownership headers. `submissionId` correlates one admitted answer and its settlement. Flue offsets remain opaque. `uid` identifies one current incarnation and may guard a known continuation, but it must not replace the logical conversation id or be presented as durable user identity. +### Expected touched paths -For maintained Voice state beyond what the panel already holds, use the SDK's materialized `observe()` surface, or `useFlueAgent()` over the same memoized client if the React seam earns it. Use `readSubmissionReply()`, `read()`, or `wait({ onEvent })` only for submission-scoped extraction, not as a parallel transcript reducer; the panel transport's per-submission projection into `useChat`'s own store is that submission-scoped use, not a second transcript. Do not parse SSE, calculate offsets, retry stream chunks, or hand-pick the latest message. The tracer may stream canonical text visibly through the observation, but TTS begins only from completed speakable segments and may remain settlement-gated; token-by-token speech is not part of this claim. +This manifest is provisional and may shrink or move when the first real probe exposes the deeper existing boundary: -The first tracer is text-turn-only at the Brunch boundary. It does not require the temporary `brunch_ask` client-tool shim: a finalized spoken answer is a direct Flue user message, and canonical plain assistant text is sufficient to prove the transport. If the real tracer cannot preserve answer correlation without structured questions, stop and present that observed strain before mounting the suspended capability. +```text +libs/@hashintel/brunch-agent/ +├── MISSION.md ~ live authority and eventual close evidence +├── MISSION.next.md ~ future joins and carried flags only +├── packages/plugin-sdcpn/ ~ mount only the read/mutation capability earned by this tracer +└── docs/evidence/ + prepared fixture manifest and browser witness +apps/brunch-agent/ +├── src/agents/chat-agent/ and src/conversation/ ? only if fixture-scoped mounting or workpiece recovery belongs outside the landed browser transport +└── test/ + real Flue/client-tool fixture integration +apps/petrinaut-website/ +└── src/main/app/local-storage-demo/ ~ consume Mission 5 transport; fixture selection, prepared signal, runtime settled manifest, and cross-tab continuation +libs/@hashintel/brunch-agent/packages/transport-aisdk/ ? consume the landed Mission 5 public surface; do not duplicate its implementation here +libs/@hashintel/petrinaut-core/ or libs/@hashintel/petrinaut/ ? only for an observed canonical contract or browser-host defect +``` -### Contract stratum and readiness gate +## Proof -Close the **one-route conversation transport stratum**: one typed turn and one finalized Voice turn each admitted once through `@flue/sdk` at the mounted route, canonical output, client-tool follow-up as a signal, local playback cancellation, durable abort, visible failure, and same-conversation reopen — with no second server-side door remaining. +The visible advance is the demo script in the imperative, run by a product manager against the named local posture. The evidence that backs the claim is one stable local demo URL or fixture selector plus its labelled prepared manifest, exact before/after Flue snapshots, recovered Markdown workpiece revisions, canonical Petrinaut document states, and two-tab witness; those are oracles for the builder and adjudicator, not the advance itself. Together they establish single-fixture browser-backed viability. They do **not** establish automatic full-net projection, capture-backed or selected-pair provenance, behavioral execution, broad scenario coverage, remote replacement durability, concurrent editing, Mission 3/4 quality superiority, or a promoted reusable product seed. -Order the tracers so the cheaper one proves the route first: the typed panel over the browser transport (it reuses the existing projector and has an existing integration scenario to re-express), then Voice. After each end-to-end turn works, enumerate the lateral obligations it exposes and close those required to make the visible claim true: duplicate finalization, ambiguous admission, submission/reply correlation, client-tool resume correlation, reconnect and replay, local cancellation versus durable abort races, fatal ownership errors, and canonical text/TTS-input correspondence. Carry broader speech ergonomics, multi-turn barge-in tuning, structured questions, and remote identity and exposure only to the named deferred owners below. +### Internal milestone: first green throughline -## Proof +The first internal milestone is one pass through the throughline for the prepared fixture: a cold reader can reconstruct the spine and distinguish supplied evidence, inference, and the explicit unknown in the workpiece; one realistic turn produces an inspectable workpiece revision without erasing the unknown; Brunch reads the live document and applies the one supported arc through the real browser client-tool boundary; the canonical net is non-empty and visibly corresponds to the confirmed meaning; and a second tab observes the same settled conversation, workpiece, and document revision and continues without duplicate submission or identity drift. Reaching this milestone authorizes the readiness work below; it does not close the mission. -This proof establishes that one real local typed turn and one real local Voice turn each cross the supported Flue conversation protocol at the single mounted route into the current canonical Brunch agent, that the typed turn returns as one finite AI SDK stream and the Voice turn as one visible and spoken canonical response, both with bounded cancellation and recovery semantics, and that no server-side AI SDK door remains. It does **not** establish trusted production authentication, remote deployment, broad Voice UX, structured-question transport, Petrinaut client-tool mutation, workpiece viability, or that the `useChat` panel itself is removable. +### Readiness gate: completion bar -1. **Typed panel over the browser Flue transport.** A typed panel submission calls `send()` exactly once with one `kind: "user"` message; a completed client-tool follow-up calls `send()` exactly once with one `kind: "signal"` `client-tool-result` message and resumes the same assistant message id; the returned `UIMessageChunk` stream carries the same start/step/part/finish sequence the former `/api/chat` integration asserted, and terminates on that submission's `submission-settled`. Reopen hydration comes from `observe({ live: "sse" })` through `snapshotToUiMessages`. Oracle: the current `apps/brunch-agent/test/petrinaut-chat.integration.ts` scenario re-expressed through the browser transport against the in-process `app.fetch` of the real `app.ts` (Flue route, ownership guard, faux provider), preserving its text, reasoning, server-tool, and client-tool-resume assertions; the relocated projector and transcript unit tests; and the outer witness typing one message in the real panel with the network ledger showing only `/agents/chat/:instanceId` traffic. -2. **Direct finalized admission.** A completed Realtime `continue_interview` call invokes Flue `send()` exactly once with one `kind: "user"` message; provisional transcript events, duplicated provider terminal events, stale epochs, and repeated tool-call delivery never enter history. The admitted server `submissionId` becomes the turn correlation key. A lost or ambiguous admission is surfaced and never blindly resent. Oracle: named cases in `apps/petrinaut-website/src/main/app/voice-interview/realtime-brunch-bridge.test.ts`, including `admits one finalized Realtime answer through Flue once`, plus canonical snapshot inspection showing exactly one matching visible user message. -3. **No server-side AI SDK door.** The Brunch app no longer mounts `/api/chat`; `createPetrinautChatHandler`, `PETRINAUT_CHAT_ROUTE`, the in-process `init()` admission path, and the `/api/chat` proxy are deleted; the repurposed transport package contains no `Request`/`Response` handler. The real Voice turn reaches the mounted route through `@flue/sdk` — either through the panel's Flue transport or by a direct `send()` — and makes no submission over any non-Flue protocol. Oracle: `apps/brunch-agent/test/build-artifact.test.ts` asserting the built server answers `/api/chat` with Hono's 404 and still serves the Flue route; the retained browser network ledger from the outer witness; and a focused integration case in `apps/petrinaut-website/src/main/app/voice-interview/voice-preview.integration.test.ts` named `admits a Voice turn only through the Flue route`. -4. **Canonical visible and TTS output.** The materialized response selected for the admitted `submissionId` is the source for visible text and TTS input. Completed visible assistant text is preserved in part order; reasoning and non-speech parts are not promoted to spoken text. The exact string array sent for canonical speech equals the selected Brunch text, and no response-preparation or simplification generation call occurs. Oracle: `apps/petrinaut-website/src/main/app/voice-interview/canonical-speech.test.ts`, `openai-realtime-session.test.ts`, and the outer artifact comparison of canonical snapshot text to the recorded TTS request payload; the human witness confirms that playback begins but does not claim synthesized audio is a verbatim recording. -5. **Cancellation and abort stay distinct.** Barge-in, pause, or panel closure stops local playback/observation according to the existing Voice contract without rewriting or deleting canonical history. An explicit durable Stop action uses Flue's conversation-wide `abort()` when work is unsettled, and history/observation exposes an `aborted` settlement; an abort that loses the race to completion remains completed. Oracle: named local-versus-durable cancellation cases in `voice-turn-controller.test.ts` and `voice-preview.integration.test.ts`, plus the retained Voice event ledger and Flue settlement. -6. **Reopen resumes without replay.** Reopening the same selected conversation rehydrates its canonical messages and settlements from Flue, does not submit another user message, and does not automatically replay settled audio. An absent conversation, fatal 401/403, reconnecting stream, and settled conversation are visibly distinguishable. Oracle: a named `rehydrates the settled Voice turn without resubmission or playback` case over the SDK observation in `realtime-brunch-bridge.test.ts`, followed by the outer witness reopening the panel and comparing the second view with the same snapshot. -7. **Architecture and boundary integrity.** The built app still mounts `useBrunchAgent()` plus `useSdcpnPlugin()`, excludes the obsolete app-local stub agent, derives ownership from principal plus logical conversation id in exactly one guard, and reaches Flue locally through a same-origin protocol-preserving proxy rather than a newly public route. The transport package's runtime dependencies are exactly `ai` and `@flue/sdk`; it imports no `@flue/runtime`, core, plugin, or binding module. Oracle: `apps/brunch-agent/test/build-artifact.test.ts`, `apps/brunch-agent/test/agent-ownership.test.ts`, the transport case in `apps/brunch-agent/test/architecture/boundaries.integration.ts` (`transports consume their wire encoder and the public Flue client only — never core, a binding, or the runtime`), SDCPN packaging tests, and browser inspection of the claimed local route and headers. -8. **Real Voice witness and retained proof bundle.** With `yarn dev:brunch`, a human speaks one answer, sees exactly one matching user message, sees and hears the canonical Brunch response begin, interrupts playback once, exercises Stop on one unsettled turn, and reopens the original settled turn. Retain under `docs/evidence/implementations/mission-5-direct-voice-flue/` the witness record, sanitized Voice event ledger, network route summary, canonical Flue snapshot, settlement outcomes, source/build commit, and hashes. Oracle: human adjudication against that bundle; mocked browser or server-only evidence cannot satisfy this leaf. -9. **Focused repository verification and truthful docs.** Brunch app, website, core/plugin, transport, and Petrinaut checks pass; end-user and operator prose describes the single route that actually shipped and preserves the distinction between canonical text and generated audio; no surviving prose or comment names `/api/chat` as a Brunch door. Oracle: `yarn exec turbo run lint:tsc lint:eslint test:unit build --filter @apps/brunch-agent --filter @apps/petrinaut-website --filter @hashintel/petrinaut --filter @hashintel/brunch-agent --filter @hashintel/brunch-agent-plugin-sdcpn --filter @hashintel/brunch-agent-transport-aisdk`, `yarn workspace @local/petrinaut-arch-docs lint:arch-docs` if a Petrinaut architectural boundary changes, `rg -n "api/chat" apps/brunch-agent libs/@hashintel/brunch-agent/packages apps/petrinaut-website/src/main/app/local-storage-demo` returning only the stock Petrinaut route and frozen Mission 4 evidence, inspection of `apps/petrinaut-website/README.md` and `libs/@hashintel/petrinaut/docs/ai-assistant.md`, and a patch changeset if the published Petrinaut package changes. +The mission completes only when the demo script works for this fixture and these obligations are closed: stale fixture/workpiece/document revision refusal, duplicate tool delivery, read/write failure visibility, unsupported meaning, no-op mutation honesty, partial-save behavior, second-tab rehydration, separate identity integrity, and one negative mutation case. Do not close every consequential-element provenance link, remote task replacement, broad scenario coverage, or repeated automatic projection here; those become Mission 7 or Mission 9 obligations only after this tracer exposes a finite peer set and load-bearing seams. -## Constraints +Every final leaf has a discriminating oracle: -- Preserve Mission 4's current core/plugin/app composition and authored skill packaging. Voice reconciliation must never restore the deleted app-local `ChatAgent`, concise stub prompt, YAML plugin machinery, or a second model-facing agent. -- Flue history is the sole canonical conversation record. Voice owns media capture, provisional display, turn finalization, TTS, playback, and local interaction state; it owns no durable transcript and may not splice into stock-assistant history. -- Use `@flue/sdk`/`@flue/react` directly for shell-facing conversation transport. No adapter may re-specify Flue offsets, retries, materialization, settlement, or recovery. The browser `ChatTransport` is a projection over the public `FlueClient` (`send()`, `wait()`/`observe()`, `history()`), never a second HTTP client; it reads chunks only through the SDK's `onEvent`/observation surfaces. -- One product route. `/agents/chat/:instanceId` behind `agentOwnershipGuard` is the sole door for typed, Voice, diagnostic, and evaluation traffic; no route, handler, or package may accept a conversation turn over another protocol. The stock Petrinaut `/api/chat` (the website's own OpenAI function) is untouched and must not be borrowed. -- The Petrinaut panel remains on `useChat`; the AI SDK is its rendering contract, supplied a transport by the host. Do not rewrite the panel onto `@flue/react`, and do not add a second transcript store beside `useChat`'s messages for the typed panel. -- Submit only the validated finalized answer. Provisional transcription and audio remain ephemeral. One Voice finalization causes at most one `send()` call; because Flue 2.0.3 does not accept a caller idempotency key on `send()`, ambiguous admission must remain visible and must not trigger an automatic retry. -- Brunch owns canonical response content. TTS may synthesize audio from exact selected text, but no second model may summarize, shorten, paraphrase, or select replacement wording for the tracer. -- Local playback cancellation, local observation cancellation, HTTP request cancellation, and Flue's conversation-wide durable abort are distinct operations and must remain distinguishable in code, UI state, evidence, and tests. -- The current browser-minted local principal is an ownership discriminator, not trusted authentication. The outer proof is local and same-origin; it must not expose `/agents/chat/:id` publicly or claim production identity, authorization, CORS, deployment, or recovery. -- `@hashintel/brunch-agent-transport-aisdk` survives only as the browser-side adapter and the home of the projector, snapshot projection, and header names. Its runtime dependencies are `ai` and `@flue/sdk`; it never imports `@flue/runtime`, core, a plugin, or a binding, and client-tool names reach it as caller-supplied options. The boundary test's transport gate is amended to say exactly that — this is the one accepted topology-gate change of the recut. -- Do not mount the suspended `brunch_ask` capability merely to preserve the divergent preview stack. Re-entry requires observed plain-turn correlation strain and an owner decision consistent with the structured-question planning contract. -- External Voice branches and their issues/PRs remain read-only evidence. Port only behavior that serves this mission, preserve relevant provenance in commits, and do not rewrite, close, or represent those records as accepted wholesale. -- Record admission, first canonical text, first TTS request/audio, and settlement latency without transcript, prompt, tool, SDP, audio, credential, or response-body content in ordinary telemetry. -- No implementation begins until this authority cut is committed separately. Material changes to this contract require owner review and another focused authority commit before dependent implementation. +1. **The prepared fixture is honest and sufficient for this narrow test.** The committed fixture manifest, raw Flue snapshot, and a cold-reader adjudication identify the prepared workpiece's tagged system/dispatch source, exact test-authored Markdown, process spine, constrained crew, release policy, quantity context, explicit unknown, prepared net meaning, and non-claims. The same inspection distinguishes every later assistant revision as model-produced and must not require transcript archaeology. +2. **One evidence turn maintains the Markdown workpiece.** A production-agent fixture integration mechanically recovers prepared revision zero from the tagged dispatch record, then selects the latest eligible assistant `runbook-ir` block after the confirming turn, retaining each source message id and SHA-256. Before/after adjudication must find the supplied contextual quantity, retained crew/release meaning, retained unsupported context, and no invented fact or hardened unknown. +3. **The real browser executes a correlated Petrinaut read and write.** Focused plugin/transport tests prove that `getLatestNetDefinition` and the selected `addArc` schema come mechanically from Petrinaut's canonical contracts, fixture mode advertises only the selected operations, duplicate result delivery does not apply the mutation twice, rejected input remains visible, and a mutation that would change nothing is reported as a no-op rather than as a change. The browser witness must retain tool name, call id, parsed input, execution output, resumed signal, and resulting canonical definition; a headless callback alone does not pass. +4. **The document change is meaningful rather than merely accepted.** A structural comparison proves there was no standard input arc from `Dispatch crew available` to `Start final inspection` before the turn and exactly one weight-1 arc afterward, while `Sign-off` still returns the crew and the prepared net remains non-empty. The changed workpiece retains the reservation/release meaning and unresolved timing/recovery. Parser/schema acceptance or a disconnected convenience element fails. +5. **The runtime settled manifest cannot bless partial state.** A focused failure test injects history/workpiece-recovery failure, rejected `addArc`, or missing/duplicate result correlation and shows that the prior coherent runtime bundle remains selected while the failure and any partial state are inspectable. A retained evidence manifest alone does not pass this leaf, and no localStorage failure interface is invented solely to satisfy it. +6. **A second tab resumes and continues the same fixture.** With `yarn dev:brunch` running, the recorded browser protocol opens the stable selector in Tab A, performs and settles the turn, then opens it in Tab B. The witness compares fixture id, document id and canonical definition hash, Flue conversation id and history, latest workpiece source/hash, runtime settled-manifest identity, and absence of duplicate submission; Tab B must then submit one follow-up message and receive its correlated Brunch response in that same conversation. A read alone does not pass. Tab B opened against a stale or mismatched revision must refuse visibly rather than silently select older artifacts. +7. **The cut has not smuggled in the later architecture.** Public-schema and dependency inspection finds only fixture identity/revision links, Markdown recovery metadata, and canonical Petrinaut payloads—no closed process ontology, typed capture-to-workpiece reducer, graph database, second conversation log, or general projection engine. -### Expected touched paths +Verification proceeds inside-out but closure requires the outer boundary: -```text -~ libs/@hashintel/brunch-agent/packages/transport-aisdk/src/ delete the HTTP handler; add ChatTransport factory over FlueClient; receive ui-stream + snapshotToUiMessages + headers -~ libs/@hashintel/brunch-agent/packages/transport-aisdk/package.json deps become ai + @flue/sdk; drop valibot if unused -~ libs/@hashintel/brunch-agent/packages/transport-aisdk/test/ replace chat-handler/golden with transport + projector + snapshot tests -~ apps/brunch-agent/test/architecture/boundaries.integration.ts amend the transport gate -- apps/brunch-agent/src/http/petrinaut-chat.ts server-side door removed -- apps/brunch-agent/src/conversation/ui-stream.ts moves into the transport package -~ apps/brunch-agent/src/conversation/transcript.ts snapshotToUiMessages moves out; formatFlueTranscript stays for the CLI -~ apps/brunch-agent/src/http/routes.ts, local-origins.ts drop PETRINAUT_CHAT_ROUTE and the /api/chat proxy; proxy /agents/chat/* -~ apps/brunch-agent/src/app.ts remove the /api/chat mount and app-transport closure -~ apps/brunch-agent/package.json drop the `ai` devDependency if nothing else uses it -- apps/brunch-agent/test/petrinaut-chat.test.ts, petrinaut-chat.integration.ts, petrinaut-chat-result.ts, flue-ui-stream.test.ts re-expressed against the browser transport / relocated -~ apps/brunch-agent/test/build-artifact.test.ts, local-dev-origins.test.ts single-route assertions -~ apps/brunch-agent/petrinaut-local.vite.config.ts same-origin Flue-route proxy for the local real surface -~ apps/petrinaut-website/src/main/app/local-storage-demo/ createFlueClient composition, browser transport, delete use-flue-chat-history -~ apps/petrinaut-website/src/main/app/voice-interview/ direct Flue admission, materialized response, cancellation, reopen -~ apps/petrinaut-website/package.json add @flue/sdk -~ yarn.lock workspace dependency update -? apps/brunch-agent/src/http/ownership.ts, src/conversation/identity*.ts only if the identity-contract home (fog-line) moves -? libs/@hashintel/petrinaut/src/ui/ smallest public panel seam only if host composition cannot remain local -~ apps/petrinaut-website/README.md operator-facing route and preview behavior -~ libs/@hashintel/petrinaut/docs/ai-assistant.md user-visible Voice behavior -~ libs/@hashintel/brunch-agent/MISSION.next.md reconcile the production-door, restricted-ingress, and adapter-removal statements -+ libs/@hashintel/brunch-agent/docs/evidence/implementations/mission-5-direct-voice-flue/ retained proof bundle -? .changeset/ one patch changeset only if the published Petrinaut package changes -``` +- **Inner:** fixture parsing and prepared-label checks; identity separation; workpiece recovery/hash; canonical `addArc` schema and exact before/after edge assertion; idempotent client-tool result handling; runtime-manifest refusal. +- **Middle:** the built production `ChatAgent` and Mission 5 browser `FlueClient`/`ChatTransport` path at `/agents/chat/:instanceId` hydrate the prepared conversation, accept the evidence turn, recover the revised workpiece, and carry actual browser-tool calls/results. No `GET` or `POST /api/chat` evidence passes. Run the focused workspaces through root Turbo (`test:unit`, `lint:tsc`, `lint:eslint`, and `build` where changed). +- **Outer:** the two-tab `yarn dev:brunch` witness above, with retained before/after artifacts. +- **Semantic:** a cold human accepts fixture/workpiece honesty and the workpiece-to-document correspondence. The oracle may falsify those claims; it may not rewrite the interaction or architecture policy. +- **Product:** a product manager who did not watch the work runs the demo script from the imperative without an engineer and notices the advance. This is the last check before close, after the readiness gate; it is not a substitute for the oracles above. -## Fog-line +## Constraints -- How a finalized Voice answer enters Flue. Two shapes are admissible: **(B, preferred)** Voice submits through the panel's own Flue `ChatTransport` (`useChat.sendMessage` → `send()`), so the panel's `useChat` messages remain the single visible store and Voice's `observe()` shrinks to selecting completed canonical text for TTS — or reads the panel's completed assistant message and drops `observe()` entirely; **(A)** Voice calls `send()` directly and keeps its own `observe()` state, with the panel rehydrating. Start with B; fall back to A only if the existing hold-while-streaming, epoch, or TTS-correlation semantics demonstrably strain under the panel's transport, and record the observed strain. Either way, one finalization is one `send()`, and if the chosen shape would create two mutable transcript stores or a second custom reducer, stop and reorient at the panel boundary. -- The smallest honest home for the browser-safe principal + logical-conversation-id → Flue-instance-id contract and the two ownership header names. `apps/brunch-agent/src/conversation/identity-web.ts` proves the algorithm; the website must not gain an app-to-app source import, and core must not own HTTP header names. The leading candidate is the repurposed transport package, which already exports the principal header; a website-local copy pinned by an equality test against the app is the fallback. Do not create a new package to hold two strings and a hash. -- Whether `wait(admission, { onEvent })` alone gives the browser transport a clean finite per-turn stream, or whether the panel needs `observe()` for reconnect during a turn. `wait()` rejects on failed/aborted settlement and on `terminal_event_missing`; the transport must map those to `error`/`abort` chunks rather than throwing past `useChat`. The first real disconnect mid-turn decides; do not pre-build reconnect machinery. -- Whether `reconnectToStream` should return `null` (observation-only rehydration, the current behavior) or resume an unsettled submission after reload. Start with `null` plus SDK observation; re-enter only if the witness observes a lost in-flight turn. -- The exact subset of PRs #9496, #9507, and #9512 to port after semantic comparison with the current branch. Their useful Voice state-machine behavior is evidence; their app-local agent topology, temporary ask shim, and generative preparation are not presumed requirements. -- The bounded speech-selection policy if the exercised Brunch response contains multiple completed text blocks or an interactive part. Begin with canonical completed visible text in order; if this produces duplicate, misleading, or unspeakable output, retain the mismatch and seek a Brunch-owned deterministic presentation rule rather than another generator. -- Whether the existing Stop affordance can express both local Voice interruption and explicit conversation-wide durable abort without misleading the user. The first real race decides the smallest UI distinction. -- Whether a same-origin local proxy can carry every SDK history/SSE/send/abort route unchanged. A crisp protocol or middleware blocker permits the thinnest route correction; it does not permit rebuilding the AI SDK adapter under another name. +- Keep fixture id, Flue conversation id, latest workpiece source/revision, and Petrinaut document id/revision distinct and explicitly linked. One id must not impersonate all lifecycles. +- Flue history remains the canonical conversation log. Browser message caches and fixture artifacts are projections or evidence, never a second authority. +- Consume Mission 5's browser `FlueClient` plus host-supplied AI SDK `ChatTransport`; typed turns, prepared signals, history hydration, and client-tool results all cross `/agents/chat/:instanceId`. Do not keep, restore, or add another product conversation route. +- The tagged prepared signal is the only test-authored workpiece source admitted by this fixture. It remains a diagnostic system/dispatch record; latest-revision selection may supersede it with a genuine assistant workpiece but may not mutate, relabel, or hide its authorship. +- Markdown remains the semantic workpiece. Recover its full latest version; do not introduce a comprehensive typed domain IR to make fixture lookup convenient. +- Projection consumes the current workpiece. The transcript may establish provenance and help recover that artifact but may not become the primary construction IR. +- Petrinaut owns canonical schemas, browser validation, mutations, and document state. Brunch imports or mechanically derives those contracts and does not hand-copy their field shapes. +- Client tools execute against the active bound browser document and return the original tool-call id. Stale, duplicate, cross-document, malformed, failed, and no-op outcomes fail visibly. +- Advance the runtime settled manifest only after the claimed Flue snapshot, workpiece revision, and document state can all be inspected. It selects the coherent local bundle but does not make the browser and Flue stores transactional. Automatic localStorage mirroring remains the only save behavior; do not invent an explicit Save affordance or cross-store transaction machinery without an observed recovery failure requiring it. +- Preserve the accepted Mission 4 `useBrunchAgent()` plus `useSdcpnPlugin()` architecture. The app composes; the plugin owns SDCPN operation semantics; the transport carries results; the UI executes them. +- Keep construction tools unavailable to unrelated ordinary conversations unless the real path proves the smallest safe selection can be scoped to this fixture/mode. Stock-assistant behavior must remain unchanged when Brunch is absent or unselected. +- The fixture is local and deliberately prepared. Make no remote durability, capture provenance, automatic projection, behavioral execution, or concurrent collaboration claim. +- No HASH Graph, Temporal, Redis, new database, observer, workflow engine, second agent, second event log, or closed workpiece schema. +- Update the affected Petrinaut user guide in the same change if the selector, save/resume behavior, or panel behavior becomes user-facing; add one Petrinaut changeset only if a published Petrinaut package changes. -## Stop or reorient +## Fog-line -Stop and surface the evidence if the implementation creates a second conversation authority, keeps or re-adds any server-side route that accepts a conversation turn over a non-Flue protocol, submits provisional STT, automatically retries an ambiguous admission, rewrites canonical text through another model, hand-rolls stream recovery, computes offsets, restores the old stub agent, or activates `brunch_ask` without observed need and owner approval. +- Whether the selected shallow `addArc` schema survives the provider-visible Flue carrier and results in exactly one browser mutation without reopening the broader nested-schema problem. +- The least safe way to expose canonical `getLatestNetDefinition` plus `addArc` in a fixture conversation while retaining the headless-only guard for broader construction. +- Whether the latest `runbook-ir` message id and hash are sufficient workpiece revision identity or the two-tab consumer exposes a need for a separate persisted workpiece artifact. +- Whether Mantine/localStorage synchronization plus the active `PetrinautDocHandle` is sufficient for the same-browser two-tab witness, and which document hash/revision signal best distinguishes settled from stale state. +- Whether the known provider-visible nested-schema failure is absent for the selected flat mutation. Do not generalize one success to nested construction classes. +- Which of history recovery, invalid `addArc`, or duplicate result delivery is the cheapest discriminating failure for the settled-witness rule after the first real path reveals the ordering. -Stop if the browser transport cannot preserve the current client-tool resume semantics (completed client-tool parts on the referenced assistant message → one signal send → continuation of the same assistant message id) without a server-side helper; that is evidence the resume contract needs redesign, not permission to reintroduce `/api/chat`. +Resolve these at the named production/browser boundaries. Clarifying prose alone does not clear them. If a choice changes the accepted interaction policy, architectural ownership, or proof claim, return it to the owner and amend this authority before implementation continues. + +## Stop or reorient -Stop at the boundary if direct Flue state cannot reach the existing visible panel without duplicated mutable history; decide the UI ownership seam before adding synchronization machinery. Stop if local cancellation accidentally aborts durable work, explicit Stop only cancels a browser request while the provider keeps spending, a stale response is spoken after conversation/epoch change, or reopen resubmits or replays a settled turn. +Stop and surface evidence if: -Stop rather than widen if the real route requires public unauthenticated exposure, production identity work, remote deployment, Petrinaut mutation tools, workpiece/projection state, or a whole assistant rewrite. Those are not hidden prerequisites to this transport tracer. +- fixture preparation requires pretending a Mission 4 candidate exists, placing prepared text in a user or assistant record, accepting an untagged preparation signal, or otherwise hiding test-authored/model-authored boundaries; +- the path conflates fixture, conversation, workpiece, and document identities or creates a second canonical conversation history; +- typed traffic, prepared signals, history, or client-tool results cross a product route other than Mission 5's mounted browser Flue route; +- the agent rereads transcript prose as its primary projection input because the current Markdown workpiece cannot carry the needed meaning; +- parser/schema acceptance, document non-emptiness, or a disconnected convenience element is offered as semantic correspondence; +- client-tool results lose their original call id, can target the wrong document, or duplicate execution on retry/reload; +- a partial or failed write advances the runtime settled manifest, second-tab reopening silently selects stale/mismatched artifacts, or Tab B proves only a read without a real continuation; +- exposing one browser mutation requires mounting an unrestricted construction surface for every ordinary conversation; +- the selected provider/Flue schema cannot faithfully carry the least meaningful mutation—record the crisp blocker rather than hand-copying Petrinaut schemas or widening into Mission 9; +- the tracer needs a closed ontology, typed claim ledger, general projection engine, distributed transaction, or new durable service before a concrete failure demonstrates that need; or +- work widens into capture-backed why/provenance, automatic projection breadth, remote deployment durability, concurrent collaboration, or broad scenario readiness. ## Deferred -- **`useChat` panel removal:** with the server-side door gone, the AI SDK survives only as the Petrinaut panel's rendering contract behind a host-supplied transport. Whether Petrinaut ever drops `useChat` is a Petrinaut product decision, not a Brunch transport question; Brunch carries no further obligation here. -- **Restricted-ingress rule for the Flue route:** Mission 8's landed contract denied `/agents/chat/:id` publicly and routed restricted traffic through `/api/chat`. This recut makes the Flue route the only product route, so that rule must be re-expressed as the FE-1423 gates applying directly to `/agents/chat/:id`. Record the re-expression in `MISSION.next.md`; the release/deployment gate owns its enforcement. -- **Structured questions:** core-owned question semantics, binding, rendering, correlated reply, and resumed tool execution remain in the shared future-planning record. Re-enter when plain Voice turns demonstrably cannot preserve a required interaction. -- **Broader Voice quality:** multi-turn barge-in tuning, long-response ergonomics, optional deterministic spoken presentation, accessibility breadth, and response optimisation re-enter after measured strain on the direct canonical route. -- **Remote/public operation:** trusted identity and authorization, origin policy, hosted Flue reachability, rate/spend controls, replacement recovery, and remote observability remain with the Mission 8 release/deployment gate or a separately cut successor. -- **Product-data work:** prepared workpiece/Petrinaut viability remains Mission 6; capture-backed review remains Mission 7; automatic traceable projection remains Mission 9. This mission carries no document mutation or provenance claim beyond canonical conversation history. -- **Host breadth:** stock/Brunch picker behavior, session switching beyond the selected local Brunch conversation, and HASH embed parity wait for the first visible consumer that makes them load-bearing. +Mission 7 still owns capture-backed visible why/provenance and broken-link refusal after this fixture is viable. Mission 9 still owns repeatable automatic projection, broader nested provider-schema classes, stable generated-element derivations, repeated/changed-input behavior, and the breadth of semantic correspondence. Remote replacement durability and release infrastructure remain in the historical Mission 8 handoff. Multi-tab concurrent editing, a durable cross-store commit protocol, explicit localStorage failure injection and refusal of a concurrent write from a tab holding an older revision (distinct from the readiness-gate refusal to reopen onto stale or mismatched artifacts), and promotion of this prepared fixture into a reusable product seed re-enter only if the automatic mirror loses or overwrites state, a later consumer requires atomic bundle identity, or this mission otherwise exposes concrete strain; their current planning home and re-entry conditions remain in [`MISSION.next.md`](MISSION.next.md) and the linked Mission 7/9 drafts. diff --git a/libs/@hashintel/brunch-agent/MISSION.next.md b/libs/@hashintel/brunch-agent/MISSION.next.md index c3b7df518b9..ecaa6fb4d54 100644 --- a/libs/@hashintel/brunch-agent/MISSION.next.md +++ b/libs/@hashintel/brunch-agent/MISSION.next.md @@ -1,8 +1,8 @@ # Brunch future mission spine -> Canonical future-planning spine, shared frame, and backlog index only. This file is not execution authority and authorizes no implementation. [`MISSION.md`](MISSION.md) is the live Mission 5 authority on this branch. Detailed provisional clusters are context repositories, not missions; re-evaluate and convert one into `MISSION.md` on its own branch before acting. +> Canonical future-planning spine, shared frame, and backlog index only. This file is not execution authority and authorizes no implementation. [`MISSION.md`](MISSION.md) is the sole live Mission 6 authority on this branch, which is stacked on the live Mission 5 authority of the FE-1574 branch. Detailed provisional clusters are context repositories, not missions; re-evaluate and convert one into `MISSION.md` on its own branch before acting. -This spine and its five linked drafts form one future-planning record. Keep each consequential meaning in one authoritative planning home: shared contracts and unallocated concerns live here; mission-specific detail lives in its draft. A spine pointer is not a second contract. Material omitted from a future cut returns to this record at full fidelity, and the consumed draft is removed. +This spine and its four linked drafts form one future-planning record. Keep each consequential meaning in one authoritative planning home: shared contracts and unallocated concerns live here; mission-specific detail lives in its draft. A spine pointer is not a second contract. Material omitted from a future cut returns to this record at full fidelity, and the consumed draft is removed. ## Current authority and accepted spine @@ -10,15 +10,15 @@ Mission 4 closed on this branch by owner adjudication on 2026-09-03. The accepte A future Mission 4 close-out addendum requires its own issue, branch, PR, and mission authority. It may stack on this closed branch and own broader reliability/hardening if warranted, browser parity, fixture/seed promotion contracts, topology-neutral case allocation, contract/readiness sweeps, archive subtraction, and Mission 8 preparation. It also owns the observed S4 report-versus-immediate-ask decision unless a later numbered mission first makes it load-bearing: re-enter only when a real review must continue immediately or repeated gap-only reports create visible friction; preserve S3 restraint while testing S4 activation and asking under a fresh instrument. Its exact issue/name and minimum scope remain owner decisions; do not create another Mission 4 draft. -Mission 5 is now live on this branch under FE-1574 and owns the direct Voice/Flue transport cut; its full contract lives only in [`MISSION.md`](MISSION.md). Mission 6 remains independently cuttable from Mission 4 under its own issue, branch, PR, worktree, and mission authority: one deliberately prepared, honestly labelled fixture joins canonical conversation, session history, Markdown workpiece, and Petrinaut document; one browser-backed read/write change saves and resumes across tabs. Neither mission requires a Mission 4 full-run candidate, and neither is the other's prerequisite. +Mission 6 is now live on the FE-1575 branch under root [`MISSION.md`](MISSION.md): one deliberately prepared, honestly labelled fixture must join canonical conversation, session history, Markdown workpiece, and Petrinaut document through a browser-backed read/write change and cross-tab resume. Its consumed draft has been removed; its product-manager litmus, demo script, and readiness-gate completion bar live only in that authority. Mission 5 is live on the FE-1574 branch directly beneath this one and owns the direct Voice/Flue transport cut; its full contract lives only in that branch's root `MISSION.md`. Neither tracer requires a Mission 4 full-run candidate. The two were cut as independent siblings, but Mission 5's recut made the browser Flue `ChatTransport` the only door into a Brunch conversation and removed the `/api/chat` path Mission 6 had named as its departure point; the owner therefore corrected Mission 6 to consume Mission 5's landed transport, and this branch now stacks on Mission 5's committed typed-panel transport tracer. The earlier capture-backed provenance, automatic-projection, revision, and optimisation drafts remain later readiness/product advances. They are renumbered around the historical Mission 8 deployment track rather than forcing the two uncertainty-retiring tracers to inherit its unproved remote boundary. ```text M4 closed — core/plugin elicitation pattern accepted; S4 transition and full-run candidate deferred M4+ optional successor — broader hardening or source promotion only under separate authority -M5 live on this branch — direct Voice/Flue turn, canonical streamed reply, cancellation, and reopen -M6 resumable fixture tracer — conversation → Markdown workpiece → Petrinaut read/write → cross-tab resume +M5 live on FE-1574, beneath this branch — direct Voice/Flue turn, canonical streamed reply, cancellation, and reopen +M6 live on FE-1575 — conversation → Markdown workpiece → Petrinaut read/write → cross-tab resume over Mission 5's transport M7 capture-backed review — close selected-pair provenance breadth and visible why/refusal M8 deployment handoff — historical branch stopped after local application proof, before infrastructure deployment M9 automatic projection — broaden the proved fixture seam to repeatable traceable projection of one meaningful region @@ -26,16 +26,10 @@ M10 revision — ship bounded authorized reviewer revision and a scoped patch M11 optimisation — ship an accepted optimisation handoff after its consumer contract exists ``` -Every numbered product mission after the proof-of-life exception must pass the **product-manager litmus**: a product manager who did not watch the work must be able to notice that the product materially moved forward. Each mission therefore states, in its draft's visible-product-advance section, a release-note sentence, a demo script a product manager can run without an engineer, and the thing that was impossible before. Snapshots, manifests, event ledgers, and negative controls are oracles that belong in the evidence sections; they are not the visible advance. A mission is complete at its readiness gate, when the demo script works for the named scenario, not at the first green throughline tracer, which is an internal milestone inside the mission. Mission 5 names the Petrinaut Brunch panel's typed and Voice surface over one Flue route, with its litmus stated in the live [`MISSION.md`](MISSION.md#product-manager-litmus); Mission 6 names the stable fixture and browser Petrinaut document; Missions 7, 9, and 10 name the Petrinaut Brunch panel. Because Mission 8 stopped before remote deployment, those panel missions must name the deployment posture available at cut time, and a locally run panel is acceptable for the demo; a product-manager-noticeable claim must never depend on infrastructure that does not exist, while remote durability obligations stay in their readiness gates. Architecture, schema repair, fixtures, evaluation, rehearsal, and spikes may support the advance but cannot be the sole outcome. Parallel work means separate issue, branch, PR, worktree, and mission authority; it never means multiple live missions here. +Every numbered product mission after the proof-of-life exception must pass the **product-manager litmus**: a product manager who did not watch the work must be able to notice that the product materially moved forward. Each mission therefore states, in its draft's visible-product-advance section and then in its cut `MISSION.md` imperative, a release-note sentence, a demo script a product manager can run without an engineer, and the thing that was impossible before. Snapshots, manifests, event ledgers, and negative controls are oracles that belong in the evidence sections; they are not the visible advance. A mission is complete at its readiness gate, when the demo script works for the named scenario, not at the first green throughline tracer, which is an internal milestone inside the mission. Live Mission 5 names the Petrinaut Brunch panel's typed and Voice surface over one Flue route, with its litmus stated in the FE-1574 branch's live `MISSION.md`; live Mission 6 names the stable fixture and browser Petrinaut document, with its litmus stated in this branch's [`MISSION.md`](MISSION.md#visible-product-advance); Missions 7, 9, and 10 name the Petrinaut Brunch panel. Because Mission 8 stopped before remote deployment, those panel missions must name the deployment posture available at cut time, and a locally run panel is acceptable for the demo; a product-manager-noticeable claim must never depend on infrastructure that does not exist, while remote durability obligations stay in their readiness gates. Architecture, schema repair, fixtures, evaluation, rehearsal, and spikes may support the advance but cannot be the sole outcome. Parallel work means separate issue, branch, PR, worktree, and mission authority; it never means multiple live missions here. ## Successor mission précis -### M6 — Prove the Markdown/workpiece/Petrinaut loop - -Tracker projection: [FE-1575](https://linear.app/hash/issue/FE-1575/resume-one-brunch-workpiece-and-petrinaut-document-across-tabs). - -One honestly prepared fixture links a canonical conversation, session history, Markdown workpiece, and Petrinaut document; Brunch updates the workpiece, performs one meaningful browser-backed document change, saves, and resumes from a second tab. This can be cut immediately and independently of Voice. **Product-manager litmus:** Brunch edits the net you are looking at from the conversation, and the work survives closing the tab. Demo: open the demo fixture, say one new thing about the process, watch the net change, save, reopen in a second tab and continue. Previously impossible: Brunch only produced off-canvas net JSON for manual load. Complete at the [readiness gate](docs/mission-drafts/6-resumable-workpiece-petrinaut-fixture.md#readiness-gate-after-the-new-throughline), not at the first green mutation; see the [visible product advance](docs/mission-drafts/6-resumable-workpiece-petrinaut-fixture.md#visible-product-advance). - ### M7 — Make the demo net genuinely explainable Tracker projection: [FE-1573](https://linear.app/hash/issue/FE-1573/explain-one-prepared-petrinaut-net-from-exact-conversation-evidence), advancing stakeholder outcome [FE-1478](https://linear.app/hash/issue/FE-1478/provide-provenance-from-a-generated-net-back-to-the-requirements-graph). @@ -231,7 +225,7 @@ These tracks may start only under their own issue, branch, PR, and mission autho | Inferential observer fold | Decide before Mission 10 whether observed foreground strain earns promotion; otherwise retain phase-boundary synthesis | Missions 4–9 | | Provider-visible nested schema | Mission 6 tests only the least mutation needed by its meaningful fixture; Mission 9 closes the broader canonical projection classes after a crisp blocker or success | Mission 5 and non-construction Voice work | | Provenance interaction fixture | Mission 6 establishes minimal fixture identity; freeze the broader derivation fixture before Mission 7 why and Mission 9 automatic projection diverge | Voice work and prepared-fixture viability | -| Host choice/session lifecycle | Mission 5 proves Flue conversation reopen; Mission 6 proves fixture save/reopen; later host/picker breadth waits for its visible consumer | Either independent tracer | +| Host choice/session lifecycle | Mission 5 proves Flue conversation reopen; Mission 6 proves fixture save/reopen over Mission 5's landed browser transport; later host/picker breadth waits for its visible consumer | Mission 5 first, then Mission 6 | | Optimisation handoff contract | Chris/Yannis accept input/output contract and one fixture before Mission 11 is cut | Missions 4–10 | | Simulation-backed semantic check | Promote only if cheap and discriminating for the selected revision | First provenance tracer | @@ -239,15 +233,14 @@ The deliberately provisional shared-interface names remain `EvidenceBackedWorkpi ## Detailed provisional clusters -Detailed mission-specific boundaries, tracer floors, readiness ratchets, risks, oracles, and stop conditions live only in these five context repositories: +Detailed mission-specific boundaries, tracer floors, readiness ratchets, risks, oracles, and stop conditions live only in these four context repositories: -- [Draft Mission 6 — resumable workpiece-to-Petrinaut fixture tracer](docs/mission-drafts/6-resumable-workpiece-petrinaut-fixture.md) - [Draft Mission 7 — capture-backed review](docs/mission-drafts/7-capture-backed-review.md) - [Draft Mission 9 — automatic traceable projection](docs/mission-drafts/9-traceable-projection.md) - [Draft Mission 10 — bounded reviewer revision](docs/mission-drafts/10-bounded-reviewer-revision.md) - [Draft Mission 11 — optimisation handoff](docs/mission-drafts/11-optimisation-handoff.md) -Do not create Mission 4 or Mission 8 drafts. Mission 5 is live here; Mission 6 remains an independent next cut that must become the sole live root `MISSION.md` in its own worktree. Mission 11 stays deliberately shallow until Chris and Yannis accept input artifacts, one optimisation question, scenario/parameter representation, execution boundary, expected result, and minimum credibility checks. +Do not create Mission 4 or Mission 8 drafts. Mission 5 is live on the FE-1574 branch beneath this one and is Mission 6's transport prerequisite; Mission 6 is live here and its authority exists only in root `MISSION.md`. Mission 11 stays deliberately shallow until Chris and Yannis accept input artifacts, one optimisation question, scenario/parameter representation, execution boundary, expected result, and minimum credibility checks. ## Unallocated backlog @@ -487,3 +480,9 @@ The owner subsequently changed the integration premise: Voice should use canonic ## 2026-09-03 product-manager litmus reframing Later on 2026-09-03 the owner replaced the "visible/usable proof" completion criterion with the product-manager litmus defined in the accepted spine above. The observed problem was that each précis pinned completion to an evidence bundle at the first green throughline tracer, which convinces a builder but is invisible to a product manager, and that Draft Mission 9 carried engineering internals in its visible-advance section. The change re-pins completion to each mission's readiness gate for the named demo scenario, moves oracles out of the visible-advance sections, expands Mission 7 from one element to every consequential element of the demo net, and names the deployment posture problem for Missions 7, 9, and 10. Mission 5 was live on its own branch and was not touched by that commit; on restack, the live branch adopted the litmus in [`MISSION.md`](MISSION.md#product-manager-litmus), naming Stop-that-really-stops and one shared typed/spoken conversation as its product-manager-noticeable advance and its single-route consolidation as internal sequencing. Mission-specific detail lives in the affected drafts' `Visible product advance` and `Throughline proof floor` sections and in the [draft README](docs/mission-drafts/README.md). + +Mission 6 had been cut into root [`MISSION.md`](MISSION.md) on the FE-1575 branch from the pre-litmus draft earlier the same day. That cut was recut on restack rather than left as it stood: its proof section had named the evidence bundle (selector, manifest, snapshots, revisions) as the visible proof artifact and read as if the first green two-tab pass were completion. The recut moves the release note, demo script, and previously-impossible statement into the imperative, names the readiness gate as the completion bar, keeps the seven discriminating oracles as builder evidence, and records the local-only demo posture explicitly. No Mission 6 draft remains here. + +## 2026-09-03 Mission 5 becomes Mission 6's transport prerequisite + +Missions 5 and 6 were cut as independent siblings on this spine. Mission 5's recut then made one browser Flue `ChatTransport` the only door into a Brunch conversation and removed the server-side `/api/chat` route, `createPetrinautChatHandler`, the `GET ?id=` history door, and the app-side UI-stream projector that Mission 6's throughline had named as its departure point. The owner corrected Mission 6 to consume Mission 5's landed transport rather than wire against the route being deleted or copy Mission 5's in-progress implementation. Mission 6 implemented and verified its transport-independent substrate (prepared-signal recovery, fixture-scoped read and `addArc`, correlated-result settlement refusal, stable selector, document seed, runtime manifest) and then stopped at that boundary. The owner first chose to keep Mission 6 a sibling branch and wait for Mission 5 to land, because Mission 5's committed state was then documentation only and every line Mission 6 needed sat uncommitted. Mission 5 then committed its typed-panel transport tracer as three commits (`Expose the Flue browser chat transport`, `Wire the Petrinaut panel to the Flue route`, `Remove the legacy Petrinaut chat route`) ahead of its Voice work, and the owner reversed the wait: this branch was moved onto `ln/fe-1574-direct-voice-flue` so Mission 6 consumes only Mission 5's committed public surface. Splitting the transport consolidation into a shared substrate branch was rejected because it would sever half of Mission 5's imperative from its authority. Consequences accepted with the move: this branch's root `MISSION.md` is Mission 6's, so Mission 5's live authority is visible only on its own branch; Mission 6 cannot merge before Mission 5; and when Mission 5 closes and archives its authority this branch will need another restack. The independence claims in the spine above were corrected to match. diff --git a/libs/@hashintel/brunch-agent/docs/evidence/design/provenance-and-tooling-decision-log-2026-09-04.md b/libs/@hashintel/brunch-agent/docs/evidence/design/provenance-and-tooling-decision-log-2026-09-04.md new file mode 100644 index 00000000000..7638119b3cb --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/design/provenance-and-tooling-decision-log-2026-09-04.md @@ -0,0 +1,93 @@ +# Provenance, workpiece, and tooling decision log — 2026-09-04 + +> Design evidence, not execution authority. Compiled on 2026-09-04 from the owner conversation that reviewed the Mission 6 tie-off and the Mission 7 departure point. Every terrain claim below was checked against the checked-out code or the installed Flue 2.0.3 types during that conversation; each entry names what was inspected. Owner-settled items are policy accepted in conversation and must still be promoted into mission authority at the named cut before implementation. Recommendations are the agent's and remain open until the owner accepts them. The projection of this log into a reviewable design is [`provenance-by-lineage-mini-spec-2026-09-04.md`](provenance-by-lineage-mini-spec-2026-09-04.md). + +Legend: **Settled** = owner accepted in conversation. **Recommended** = agent recommendation, not yet accepted. **Open** = fog; a probe is named. + +## A. Mission 6 tie-off + +**A1. Mission 6 deterministic layers are closed; the outer witness was blocked by an environment fault, not a product defect.** Settled as observation. Inspected: `docs/evidence/implementations/fe-1575-resumable-workpiece-petrinaut.md`; the shell's `ANTHROPIC_API_KEY` was the five-character placeholder `dummy`, which explains the recorded HTTP 401. Consequence: the outer two-tab witness, the cold-reader adjudication, and the product-manager demo remain open; the two human gates depend on the outer rerun because the model-produced revision does not yet exist. The Mission 6 builder is performing the rerun in a parallel session (untracked `fe-1575-outer-browser-witness-2026-09-04-r2/` observed in the worktree). + +**A2. Mission 6 stacks on unmerged Mission 5.** Observation. `gt log short` shows `ln/fe-1574-direct-voice-flue` beneath this branch; GitHub PR 9528 is open awaiting review; Mission 5's human Voice witness is unrun per its evidence README. No PR exists yet for FE-1575. Consequence: Mission 6 cannot merge before Mission 5, and its close report needs a PR. + +**A3. The prepared fixture's "Current Petrinaut correspondence" section is fixture-rigging.** Settled. Inspected: `prepared-crew-reservation-fixture.ts` versus `plugin-sdcpn/src/skills/sdcpn-modelling/templates/workpiece.md`; no template heading or skill directive produces such a section. Consequence: the Mission 6 fixture is a viability proof of transport, mutation, and resume, and must not be promoted into the provenance pair. The close report must say so. + +**A4. The fenced-block workpiece source is a Mission 6 contract that Mission 7 will change.** Settled. The Mission 6 close report names the move from fenced `runbook-ir` blocks to an `update_workpiece` tool as a carried change so nobody treats latest-block selection as settled. Mission 6's implementation is not retrofitted mid-tie-off. + +## B. Terrain: what exists between conversation, workpiece, and net + +**B1. Flue history to workpiece revision: exists and is tested.** Inspected: `packages/core/src/workpiece.ts`. The resolver selects the tagged prepared signal or the latest assistant `runbook-ir` block, identified by source message id plus SHA-256. + +**B2. Flue history to capture store: exists as a stub that re-indexes user utterances.** Inspected: `apps/brunch-agent/src/capture/apply-sweep.ts`, `packages/core/src/evidence/capture-store.ts`, `session-log.ts`. One envelope per user entry, excerpt equals the whole utterance, payload `{}`; excerpts resolve to a pointer of session id plus entry ordinal by substring search over an archived copy of history; the store adds an owner key, dedup, and idempotent retry. Everything but the owner key duplicates what Flue history already carries, under a second identity scheme. The store is a JSON file beside the sqlite database (`db-path.ts`). + +**B3. Capture store to workpiece: does not exist.** Inspected: the workpiece template asks for "exact expert wording" as prose beside each claim and never names capture ids, message ids, or ordinals; Markdown has no passage identity. Settled: this is the central unresolved design tension, and it has been deferred as "later" without being stated in the spine. + +**B4. Workpiece to net: one prose sentence in the fixture; no derivation record anywhere.** Inspected: fixture and template as in A3. Settled: a hand-authored derivation fixture, as the Mission 7 draft proposes, is useless and rejected. + +**B5. Workpiece visibility: the workpiece is a black box during a conversation.** Inspected: the `runbook-ir` block streams as a fenced code block inside the assistant message; the only current view is the Mission 6 fixture banner's collapsed `
` element (`prepared-fixture-banner.tsx`). No pane, revision list, or diff exists. + +**B6. Retained persona runs contain no revision series.** Inspected: every run under `docs/evidence/evaluations/` has at most one `runbook-ir` block; `vestera-runbook-headless` emits once at the end; Mission 4 v2 runs stopped before substance. Consequence: the model's revision cadence is unmeasured. + +**B7. Petrinaut elements have no metadata slot; the file wrapper has only `title` and `meta.generator`.** Inspected: `petrinaut-core/src/schemas/entity-schemas.ts` (strict objects), `file-format/types.ts`. + +**B8. Flue offers four typed ways to put something in the canonical log.** Inspected: `@flue/runtime` and `@flue/sdk` 2.0.3 `.d.mts`. Tool call records (input, output, call id; surfaced as tool parts on the assistant message with `turnId` and `submissionId`); `useDataWriter` data parts on the current response; `usePersistentState` `state_write` records, atomic with the tool batch, server-side only, not in `history()`; and signals via external `dispatch()` or `ctx.append` in finish hooks, surfaced as system-role messages with `tagName` and string attributes. There is no arbitrary custom entry and no tool-context `append`. + +**B9. The construction tool schema carrier is still the one Mission 3 falsified.** Inspected: `plugin-sdcpn/src/tools/petrinaut-construction.ts` declares input as `v.looseObject({})` with a `rawTransform` that re-parses against Petrinaut's Zod schema, and pastes the canonical JSON Schema into the description text. Flue accepts Valibot only (`ToolInputSchema = v.GenericSchema`), converts via `@valibot/to-json-schema`, and rejects other Standard Schema vendors by checking `~standard.vendor === "valibot"`. The provider therefore sees an object with no fields. Flat `addArc` can survive on the description; nested `addType.elements` failed nine of nine in Mission 3. The spine already recorded "Flue Standard Schema support or a mechanical shape-preserving conversion" as the accepted next move; neither has been done. + +**B10. Mounted tools today.** Inspected: `chat-agent/agent.ts`, `plugin-sdcpn/src/flue.ts`, `core/src/flue.ts`, `core/src/client-tools.ts`, website `local-storage-demo/`, persona `client-tool-hosts.ts`. Server: `ping`, Flue's `activate_skill`, `readPetrinautDoc` (browser-deferred, all SDCPN conversations), six construction tools (headless mode only), two fixture tools (Mission 6 mode only). Core owns no model-facing tool by stated rule. Client: the Petrinaut panel client-tool host; an `ask` interactive tool and `sweep` result handling that no mounted server tool ever produces; the Voice bridge, which is a transport rather than a tool; persona hosts `none`, `mock`, `real-headless`. The stock Petrinaut assistant exposes about fifty canonical tools. + +## C. Design decisions + +**C1. Provenance is recovered from lineage in the canonical log, not stored in a typed IR, a capture store, or a hand-authored derivation.** Settled in principle. Rationale: the typed comprehensive IR chased a receding horizon and degraded model performance; the structural swing left no seam; both treated provenance as a property of the domain model when it is a property of who changed what, in response to what, when. Every such moment is already recorded in Flue history once workpiece revisions and net mutations are tool calls. + +**C2. The honest shape of a why answer is one creating call, one workpiece passage, one introducing revision, then either a quoted line or a short turn range.** Settled. The owner's correction: tool calls do not occur every turn and workpiece revisions do not occur every turn, so ranges enter at exactly one hop, the last. The verbatim quote check narrows a range to a line where the model quoted the expert. Elements changed several times show introducing and last-changing calls separately. + +**C3. The why tool returns structured ranges; the assistant interprets.** Settled. The user asking "why" implies an assistant interpretation anyway, so the tool never authors prose and never invents a link. It must accept multiple element ids and return potentially several ranges per element. + +**C4. Workpiece updates are tool calls, not fenced blocks in assistant text.** Settled. Tool: `update_workpiece`, input one Markdown string, `durable: true`, validates and hashes, writes the current-revision pointer with `usePersistentState` inside the same tool batch, returns revision and hash. Core owns the tool; plugins own the template. Rationale: structural revision identity (call id), write-time validation, agent access to its own current revision without model echo, lineage shared with mutation calls through `turnId`, and a clean UI split. Caveats recorded: token cost is unchanged and a structured-patch input is the later absorber; a model may call a tool less readily than it emits text, so cadence must be measured either way. + +**C5. The generic lookup is a core `query_workpiece` tool; the element lookup is plugin-owned.** Settled for the split, name provisional. Core knows revisions and history, not Petri nets; it takes a revision pointer or passage locator and returns turn ranges with user text. Plugin-sdcpn owns a `locate_elements`-style lookup because only it knows which calls are mutations and where ids sit in inputs. + +**C6. Net revisions join to workpiece revisions through the client-tool result, not through Petrinaut metadata.** Recommended. The browser returns the post-mutation document hash inside the client-tool result (Mission 6 already computes it for the settled manifest). A document hash no tool result explains is honestly "changed outside the conversation." A file-level pointer in Petrinaut `meta` is deferred until Mission 11 has a real consumer for a self-describing export. + +**C7. The workpiece becomes a visible, revisioned document in its own pane.** Settled. Chat projects `update_workpiece` parts out of assistant messages, leaving a one-line marker; the pane shows current revision, revision list, and diff, driven from Flue history through the Mission 5 transport. Rationale: the chat pane is too small for artifacts; a why answer resolves to a passage the reviewer must be able to see; per-turn emission for the pane is the same behaviour that gives blame its grain. Projection lives in the app or transport layer, not the Petrinaut library. + +**C8. The capture store and sweep earn no place in Mission 7.** Recommended. Flue history already carries message ids and exact text. Owner-key enforcement on evidence reads can be a check at answer time. The store re-enters only if verification strains under compaction or ownership. + +**C9. The `ask` and `sweep` client handling is retired from code.** Settled. Surfaces: the two names in core `client-tools.ts` and the suspended ask contract behind them; the website's ask interactive tool and its test; the sweep filter in the panel transport and the sweep output module; the Voice references in `canonical-speech.ts` and `interview-coverage.ts`. Archived mission records remain the design reference. Vehicle: a subtraction commit under Mission 7 authority. + +**C10. Petrinaut mutation tools are wired into ordinary conversation now, and admission is by evidence rather than by an inherited six-tool subset.** Settled as policy. The owner's judgement: continuing to defer tool wiring is a strategic mistake; the six were an unexamined side-quest set; the skill teaches concepts but not tool use. Terrain supports it: the subset lacks every `update*` and `remove*`, `getNetCompilationErrors`, `addScenario`, `applyAutoLayout`, metrics, subnets, and differential equations. Parity with the stock modeller is still not the goal. Mission 6's live constraint that construction tools stay out of ordinary conversations must be amended at the Mission 7 cut, not in the Mission 6 tie-off. + +**C11. The schema carrier must be fixed before tools are admitted.** Recommended as the first act of the next cut. Options: a mechanical JSON Schema to Valibot interpreter for the subset Petrinaut uses (local, reversible, satisfies "mechanically derived"), or upstream Flue Standard Schema support. Petrinaut's canonical tool descriptions were written for the stock assistant and become useful field-level guidance once the carrier carries fields. + +**C12. The skill must add construction posture, not just concepts.** Settled in principle. Read the definition first, mutate in small steps, check compilation errors, record each decision in Construction notes, call `update_workpiece` before and after construction. + +**C13. Mission 7's release note narrows to the honest framing.** Settled. "Ask why about any element and see the workpiece passage, who prepared it, and the exact conversation line it rests on, or an explicit refusal." Rejected: inventing a longer conversation to make "what the expert actually said" true for prepared elements. + +**C14. Mission 7 names local deployment posture; remote durability returns to Mission 8.** Settled. The owner reports the Postgres persistence move is slow and unresolved for local versus remote; it will not exist for Mission 7, so the sequence lines up with the original numbering. + +**C15. The workpiece passage-identity question is decided by probe, not in a draft.** Recommended. Candidates: heading path (readable, breaks on rename), Markdown anchors (stable, changes the surface), companion manifest (clean Markdown, second-artifact drift). Probe: prepare references for the elements of one real pair, revise one non-semantic line, observe which scheme survives. + +## D. Real honest fixtures through persona interviews + +**D1. Real conversations and real workpieces replace prepared fixtures as the provenance pair.** Settled. The Mission 6 fixture stays a viability proof. Multiple persona runs can proceed in parallel; six cases already exist under `evaluations/cases/`. + +**D2. Persistence during a run is the Flue store; retention is the harness's per-run evidence directory.** Observation. The harness's `--brunch-evidence-dir` already refreshes a canonical `snapshot.json` plus deterministic projections on every settled read. With `update_workpiece`, revisions become tool parts in that snapshot and the harness's workpiece recovery must read tool parts instead of fenced blocks. + +**D3. How a retained real conversation becomes a live fixture is the first fog item of the persona programme.** Open. Options: keep the genuine conversation live in a durable store shipped with the demo; restore retained genuine records into a fresh store through Flue's storage adapter (records are genuine, only relocated, but the adapter's record types are private and the routing doc warns against consuming them); or replay the materialized snapshot as prepared signals, which turns genuine history into a prepared projection and would make the why route fixture-only. Probe: whether Flue 2.0.3 exposes or tolerates a conversation export and restore at the storage boundary. + +**D4. Stop rule: a turn cap as budget plus a Brunch-side completion signal; ledger coverage grades afterwards.** Recommended. Today the turn budget lives only in the launch prompt and the persona is told never to end the interview. Proposed: cap per run (larger than the 6–10 used so far, cost accepted), stop early when Brunch itself declares construction handoff or delivery in its status section, and grade coverage against the hidden oracle ledger after the run rather than using it to stop. + +**D5. Runs go to construction, because the fixture must contain lineage.** Recommended. Sequencing follows: carrier fix and tool admission and `update_workpiece` land before construction runs. An elicitation-only campaign can run earlier to measure revision cadence. + +## E. Consequences for the planning record + +**E1.** State the provenance tension in the spine as an open design decision with C1 as the current hypothesis, the typed IR and hand-authored derivation as rejected with reasons, revision cadence as the named strain, and the visible workpiece as the precondition. + +**E2.** Re-cut the Mission 7 draft: drop the capture-store chain and Mission 2 inherited closure; make the carrier fix, orphan retirement, `update_workpiece`, the workpiece pane, tool admission and teaching, and the persona programme the mission's body; make the why route the last step over real lineage; name local posture. + +**E3.** Adjust the Mission 9 and 10 drafts: they inherit the seam from lineage (revision id equals call id, passage identity per C15, element id, document hash per C6) and no longer assume a derivation fixture or prebuilt pair. + +**E4.** Mission 6 close report: record A3 and A4 plainly, plus the credential cause in A1. + +**E5.** Decision-integrity: every Settled item here is an owner decision expressed in conversation. It becomes authority only when written into the cut `MISSION.md` for Mission 7; this log and the mini spec are neither authority nor a substitute for it. diff --git a/libs/@hashintel/brunch-agent/docs/evidence/design/provenance-by-lineage-independent-review-2026-09-04.md b/libs/@hashintel/brunch-agent/docs/evidence/design/provenance-by-lineage-independent-review-2026-09-04.md new file mode 100644 index 00000000000..c96ae7fc06e --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/design/provenance-by-lineage-independent-review-2026-09-04.md @@ -0,0 +1,392 @@ +# Independent review of provenance-by-lineage replanning — 2026-09-04 + +> Review evidence, not execution authority. This document evaluates [`provenance-and-tooling-decision-log-2026-09-04.md`](provenance-and-tooling-decision-log-2026-09-04.md) and [`provenance-by-lineage-mini-spec-2026-09-04.md`](provenance-by-lineage-mini-spec-2026-09-04.md) against the checked-out Brunch and Petrinaut code, the installed Flue 2.0.3 documentation and types, the current future mission record, and three independent adversarial reviews. It identifies factual corrections, semantic gaps, and strategic blind spots. It changes no owner-settled policy and authorizes no implementation. + +## Executive verdict + +The replanning is directionally strong. Making the workpiece visible, making revisions first-class tool calls, refusing to turn the capture store into semantic IR, repairing the provider schema carrier before trusting construction, and deriving audit information from the canonical conversation record are all sound moves. + +The central claim is nevertheless overstated. The proposed mechanism establishes **temporal audit lineage**, not yet **motivational or causal provenance**. It can show which workpiece revision was current when an assistant requested a mutation and which conversation context preceded that revision. It cannot deterministically establish that a particular workpiece passage motivated a particular element. Passage selection, element-effect attribution, actor identity, and durable exact evidence each require information or contracts that the described lineage does not contain. + +The defensible near-term claim is: + +> Show the logged assistant mutation request, its later browser outcome, the workpiece revision temporally current for that request, and the intervening authorized conversation context, with explicit warnings where motivation, causal derivation, actor attribution, or exact historical evidence is not established. + +Anything stronger requires reopening C1–C3, C5, C6, C8, C10, C13, and C15 before Mission 7 authority is cut. The proposed Mission 7 also absorbs the central automatic-construction advance currently assigned to Mission 9 and is too large to remain a bounded explainability mission without an explicit recut. + +## Critical semantic gaps + +### 1. “Latest workpiece before mutation” is correlation, not causation + +The decisive unsupported traversal is: + +```text +element id + → mutation call + → latest update_workpiece at or before that call + → the passage in that revision +``` + +The conversation log contains no deterministic relation from a mutation to the passage that motivated it. A workpiece can contain many claims; one call can realize several claims; several mutations can be made while the same multi-topic workpiece is current; and a mutation can arise from formalism constraints, model inference, an external source, or a mistake. A post-construction workpiece update may contain the actual rationale but is excluded by the backward-selection rule. + +The proposed `locate_elements` returns mutation calls and the workpiece revision current at each. The proposed `query_workpiece` begins from a passage locator. No mechanism produces that locator from the mutation. Therefore the mini spec's statement that “every hop is a lookup in the canonical log; nothing is stored elsewhere and nothing is inferred” is false as written: selecting the relevant passage necessarily requires semantic inference or an explicit relation. + +This is not repaired merely by sharing a `turnId`. Shared temporal correlation still does not establish motivation, and the current client-tool result does not in fact share the mutation request's turn, as described below. + +**Required decision:** choose explicitly between: + +1. **Temporal-lineage claim:** report the workpiece revision current for the mutation and a broader conversation range, without saying that one passage motivated the element; or +2. **Small creation-time association:** carry a revision identifier and one or more passage locators with the mutation request or with a thin construction-operation envelope. + +A small creation-time relation is not the comprehensive typed IR previously rejected, and it differs materially from a retrospective hand-authored derivation fixture. If neither is acceptable, the system must return a broader range or refuse rather than invent a point passage. + +### 2. Passage identity and blame are prerequisites, not downstream fog + +The intent promises one workpiece passage, the revision that introduced it, and blame across revisions while passage identity remains undecided. Heading paths fail under rename or movement; exact text fails under edits; ordinary line blame fails under reformatting; anchors remain stable only if their lifecycle is defined; and a companion manifest can drift from the Markdown. + +The proposed C15 probe changes one non-semantic line. That is too weak to establish the promised semantics. It does not test: + +- heading rename or movement; +- passage split or merge; +- paraphrase that preserves meaning; +- deletion and reintroduction; +- a passage accumulating evidence from several non-adjacent periods; +- duplicate quotations or repeated headings; or +- correction, qualification, contextual coexistence, and conflict. + +For split, merge, paraphrase, and reintroduction there may be no unique mechanically discoverable “introducing revision” without explicit successor/predecessor semantics. + +**Required decision:** either define durable passage identifiers and their edit lifecycle before promising blame, or narrow the first claim to revision-local text and refuse cross-revision “introduced by” answers. The passage-identity probe should cover rename, move, paraphrase, split, merge, deletion, and reintroduction, not only a non-semantic line edit. + +### 3. User-turn ranges are not attribution and do not establish “who prepared it” + +The range between two workpiece revisions can contain unrelated statements and can omit older evidence reused in the new revision. A verbatim match proves string occurrence, not endorsement, origin, authority, or causation. A user may quote another person, reject the quoted proposition, or repeat wording supplied by the assistant. Non-verbatim synthesis, declared defaults, formalism constraints, assumptions, external sources, and construction-opened losses require distinct treatment. + +The proposed answer also collapses several different actors: + +- the assistant that authored the workpiece revision; +- the expert whose evidence supports a claim; +- the person who requested construction; +- the browser principal that applied the mutation; +- a later reviewer; and +- the owner or authority that permits canonical change. + +A Flue `role: "user"` is not sufficient actor identity. The release note's promise to show “who prepared it” is therefore unsupported by the described records. + +**Required contract:** represent and report workpiece author, source/evidence actor, mutation actor, requesting principal, and authorization context separately. Conversation ranges may be supporting context; they are not, by themselves, authorship or authority. + +### 4. Element IDs in tool inputs do not establish actual mutation effects + +Seeing an element ID in a mutation input does not prove that the call created or changed that element. Calls can fail, no-op, apply against stale state, affect several entities, create derived entities, delete an entity, replace or recreate an ID, or be successfully applied while their result delivery is lost. Commands such as layout can affect many elements without identifying them individually in the input. + +The current `clientToolHistoryFrom` projection treats results as opaque correlated outputs. It does not establish canonical effect semantics or reconcile conflicting and repeated results. + +**Minimum settled mutation evidence:** + +- mutation call or operation identity; +- target document identity or incarnation; +- expected base document hash; +- applied, no-op, failed, stale, or unknown outcome; +- confirmed post-document hash; +- exact created, updated, deleted, and derived element IDs, or retained canonical pre/post definitions from which those effects are mechanically derived; and +- one authoritative result per call with duplicate-result rules. + +Without this, the honest statement is “the assistant attempted this mutation,” not “this call created this element.” Deleted and recreated elements also need explicit lifecycle semantics; “creating call and last-changing call” is not sufficient when identity can be retired and reused. + +### 5. The browser mutation and workpiece update do not share one atomic boundary + +`update_workpiece` would be a server-side Flue tool. Its `usePersistentState` setter can commit atomically with that server tool's unit of work. Petrinaut mutation executes later in the browser and its result returns as a separate `client-tool-result` system dispatch. The workpiece update and the browser mutation therefore do not share durability or a transaction. + +A workpiece update can settle while a browser mutation fails, times out, applies against stale state, or is manually superseded. A browser mutation can apply while its result is lost. Two tabs or two conversations can target the same document. A later result can arrive after another mutation or hand edit. + +Atomicity across these boundaries is not necessarily required, but observable incomplete states and deterministic reconciliation are. The design should model an operation protocol rather than imply a shared batch: + +```text +mutation requested(callId, documentId, baseDocumentHash, workpieceRevision, intended scope) + → browser outcome(applied | no-op | failed | stale, postDocumentHash, effects) + → reconciled | incomplete | unknown +``` + +This protocol must define retry identity, duplicate delivery, stale-base refusal, lost-result behavior, and what provenance is safe to report for an incomplete or unknown operation. + +### 6. A post-document hash is not an adequate join + +A whole-document SHA-256 is a content fingerprint, not a mutation lineage record. It does not identify the pre-state, the transition, the actor, or the affected elements. Identical hashes can recur after revert and reapply. A hand edit followed by a logged mutation can yield a post-hash that appears “explained” by the tool result even though the resulting document includes an outside-conversation change. Serialization changes or a missing client-tool result can produce the opposite false classification. + +Consequently, “a document hash that no tool result explains is changed outside the conversation” is too strong. The safe classification is “not attributable from the recorded transitions.” Reliable attribution needs at least document identity, expected base hash, serialized operation order, result hash, operation ID, and confirmed effects. + +After an unexplained transition, the system should refuse provenance for the affected state—or for the entire document when no trustworthy diff can isolate the effects—and begin a new explicitly imported external revision if lineage is to continue. + +## Critical durability and access gaps + +### 7. Flue's supported history projection does not preserve exact lineage across compaction + +The design conflates Flue's append-only canonical storage with the public `history()` projection. Flue's underlying `ConversationStreamStore` is an ordered append-only canonical record, but its record types are not the supported application read surface. Brunch reads `createFlueClient(...).history()`, which returns a materialized conversation snapshot. + +Flue 2.0.3 explicitly compacts older messages into a summary while retaining only recent history verbatim. After compaction, the supported materialized history may no longer contain: + +- exact old user lines; +- old `update_workpiece` inputs; +- old mutation tool parts and results; +- the full workpiece revision series; or +- the exact text needed for quote verification. + +Brunch's current Flue history reader consumes only `snapshot.messages`; it has no supported access to the private pre-compaction records. This breaks the exact-line answer, workpiece revision pane, passage blame, and mutation lookup at once. + +C8 therefore understates the re-entry condition for independent retention. Compaction is already a known behavior, not merely hypothetical future strain. + +**Required decision:** either scope Mission 7 explicitly to uncompacted local conversations and visibly refuse once required evidence has compacted, or retain an immutable, authorized, compaction-independent lineage projection before compaction. That projection need not resurrect the current one-envelope-per-user-utterance capture design, but some stable retained evidence is required for the longer Mission 9 and Mission 10 story. + +Retention, export, audit, revocation, and migration are additional reasons a canonical operational conversation store may not be sufficient as the product's evidentiary archive. + +### 8. `query_workpiece` and `locate_elements` have no specified executable history-access boundary + +The ownership split is conceptually plausible but omits the runtime boundary. Inside a Flue agent there is no history hook. The supported read path is `createFlueClient(...).history()`, which requires a host-resolved conversation URL and transport. The current Brunch architecture deliberately places this absorption in the binding and app layer. + +Therefore “core knows revisions and history” is not currently true as an executable capability. A core server tool cannot silently self-HTTP without host composition, and a plugin-owned server tool cannot inspect Flue history without acquiring a substrate dependency or an injected history service. + +**Recommended boundary:** + +- core owns pure formalism-independent workpiece revision and query semantics; +- plugin-sdcpn owns interpretation of Petrinaut mutation names, inputs, outputs, and element effects; +- the binding/app owns authorized acquisition of the Flue materialized history or retained lineage projection; and +- the app composes the model-facing query tool from those capabilities. + +Whether there is one composed why tool or two model-facing tools should follow the minimum useful product interaction; package ownership does not require exposing package seams to the model. + +### 9. The proposed persistent-state pointer does not give the model the current workpiece + +The proposed state contains only `{ callId, sha256, revision }`. It contains no Markdown, and `usePersistentState` values are server-side; they are not automatically shown to the model. The statement that “the next render reads the current pointer without the model echoing it” does not explain how the model obtains the current workpiece content. + +Reading the content back from `history()` reintroduces the unavailable-history and compaction problems. A pointer alone also cannot validate or recover the current document after old tool parts disappear from the materialized projection. + +**Required mechanism:** persist the current Markdown with the pointer, or provide a retrieval capability backed by a compaction-independent store. The design must also distinguish generic core validation from plugin-specific validation. Core can check non-empty Markdown and generic size/integrity constraints, but it cannot validate conformance to the SDCPN workpiece template without plugin participation. + +### 10. Authorization, disclosure, retention, and untrusted-history handling are absent + +Returning coarse ranges “with the user text” can expose unrelated material and supplies old conversation content to the model as untrusted input. Answer-time equality with one owner key is insufficient for: + +- a second authorized reviewer; +- partial disclosure; +- revocation; +- restored fixtures; +- cross-conversation document access; +- a document whose owner changes; +- sensitive or deleted evidence; and +- later Mission 10 authority distinctions. + +The lineage must bind principal or actor identity, conversation identity or incarnation, document identity, and authorization context. Restoring or relocating a fixture must preserve those identities or explicitly establish replacements. Reads must fail without leaking the existence or content of unauthorized evidence. + +Retrieved conversation text should be treated as untrusted evidence rather than fresh instructions. The why operation should return only the smallest authorized range necessary for the answer and preserve a clear boundary between quoted evidence and model-authored interpretation. + +## Factual corrections to the mini spec and decision log + +### 11. The depicted client-tool turn topology is incorrect + +The mini spec depicts a user message, assistant response, mutation request, and mutation result as sharing one `turnId`. In current Flue history: + +- user messages commonly carry a `submissionId` but no `turnId`; +- the assistant mutation request carries the model turn's `turnId`; +- the construction tool's immediate Flue output is only `{ awaiting: "client" }` and terminates that response; +- the browser's actual output arrives later in a separate `client-tool-result` system dispatch under another submission; and +- the assistant continuation has a new model turn. + +Correlation is by `toolCallId`, plus submission and record order where needed—not by assigning the same `turnId` to the original user message and later browser result. Conversation-range resolution must join user deliveries, assistant turns, client-result signals, and continuations through their actual submission/order semantics. + +The retained Mission 6 witness also shows cumulative client-tool-result signals that repeat earlier call IDs. The lineage reader must deduplicate and reconcile by call ID rather than treating every signal occurrence as a new result. + +### 12. Current browser mutation results do not contain `documentSha256` + +The current construction tool settles server-side with `{ awaiting: "client" }`. The retained Mission 6 `addArc` browser result contains title, detail, target, and `applied`, but no document hash. Mission 6 computes the document hash separately in its settled manifest. + +Accordingly, the sentence “the browser returns the post-mutation document SHA-256 inside each client-tool result” describes proposed work, not observed current behavior. The browser result contract, its caller, and the production routing must be changed and tested before C6 exists. + +### 13. `usePersistentState` cannot be called inside the tool's `run` + +Flue hooks must be called while the agent function renders. The implementable pattern is: + +1. call `usePersistentState` during agent render; +2. capture the returned setter in the tool closure; and +3. invoke that setter from the tool callback, where `toolCallId` is available in the tool context. + +The mechanism table's pseudocode is therefore technically wrong or materially ambiguous. It should describe a render-time hook and tool closure, including how the revision number is computed with updater semantics and how the returned revision is kept consistent with the buffered state write. + +### 14. The Petrinaut file-wrapper description is imprecise + +The versioned Petrinaut file wrapper carries more than `title` and `meta.generator`: it also includes `version`, the SDCPN document arrays, and optional generator-version metadata. Runtime entity schemas use strict objects in important places, but the file import schemas are not uniformly strict. + +The narrower conclusion remains sound: there is no currently supported provenance metadata slot on individual elements, and no established file-level provenance field. The documents should state that directly rather than claiming that the wrapper has only two fields or that every relevant file-level object is strict. + +### 15. “Durable tool” does not make the browser side effect exactly once + +`durable: true` can protect the server-side tool attempt and its recorded Flue state effects. It does not make an external browser mutation exactly once. The browser can apply a mutation and lose its result; a retry can encounter changed state; signal admission can be deduplicated while the external side effect has already happened more than once. + +The durability statement must be scoped to the server workpiece tool. Browser mutation idempotency, base-state checking, and unknown outcomes require their own contract. + +## Strategic gaps in the mission sequence + +### 16. The proposed Mission 7 absorbs Mission 9's central product advance + +The recut Mission 7 now includes: + +- provider schema-carrier repair; +- broad ordinary-conversation mutation-tool admission; +- construction teaching; +- `update_workpiece` and persistent revision mechanics; +- a workpiece revision/diff pane; +- retirement of old client-tool surfaces; +- several real persona interviews through construction; +- production of a real constructed net; and +- the final why route over that lineage. + +Mission 9's current release note is that Brunch constructs a recognizable net region itself. If Mission 7's real persona run constructs the provenance pair, that advance has already been crossed. M7 becomes a large multi-front construction, UI, evaluation, persistence, and explainability mission whose user value arrives last, while M9 later repeats the same terrain under a different completion bar. + +**Required recut:** choose explicitly between: + +1. consolidate the real construction and explanation advance, then give the following mission projection breadth, repeat/change behavior, and readiness closure; or +2. make the visible revisioned workpiece a smaller precursor mission, followed by one mission that constructs and explains a real bounded region. + +At minimum, carrier repair plus one canonical production-path mutation should be an early go/no-go tracer before committing to the pane, persona campaign, and whole-net coverage. + +### 17. M7 is not bounded around one coherent visible advance + +Even if one branch may contain several tracer bullets, the proposed M7 has too many independent failure fronts: schema conversion, model tool selection, workpiece cadence, history access, passage identity, browser mutation effects, new UI, fixture restoration, persona quality, and reviewer usefulness. Several can invalidate the architecture after substantial unrelated implementation has landed. + +The first working line should answer the disputed semantic question with the least mechanism: one genuine conversation, at least two distinguishable workpiece passages, at least two mutations, one failed or no-op mutation, one correction, and one hand edit. If the why resolver cannot distinguish those cases without guessing, the architecture should stop before whole-net breadth. + +### 18. Fixture restoration is unresolved but lies on the critical path + +The mini spec accurately marks a `???` between a genuine persona run and a live demo fixture. Keeping a dev store, importing private Flue records, and replaying a materialized snapshot each make a different product claim. Replaying signals creates a prepared projection rather than preserving the original live lineage. + +Running several paid persona construction campaigns before proving that one minimal genuine conversation can be exported or retained, relocated, reopened, authorized, and queried risks producing evidence that cannot power the demo. + +**Recommended order:** run the export/restore/reopen probe on one tiny genuine conversation before the broader persona programme. Record whether the product fixture is a retained live store, a supported relocation of genuine records, or an honestly labelled prepared projection. + +### 19. Broad canonical-tool admission contradicts the stated restraint + +The mini spec says to admit Petrinaut's canonical mutation, query, and command tools by default and scope down only after observed misbehavior, while saying stock-modeller parity remains a non-goal. That is parity-first admission in practice. It also conflicts with the current Mission 9 record's rejection of broad tool parity and risks a large provider tool-definition cost, selection ambiguity, inappropriate commands, and a much larger failure surface before any scenario requires metrics, subnets, differential equations, scenarios, removals, and layout together. + +Rejecting the inherited arbitrary six-tool subset does not require admitting everything. Replace it with a **scenario-derived canonical subset**, mechanically generated from Petrinaut's authority, and expand it when the selected scenario or observed failure requires another mutation class. Measure schema size, prompt-cache effects, tool-selection behavior, latency, correction behavior, and provider errors before making a broad bundle the ordinary default. + +### 20. The completion criterion remains underdefined + +“Any element” and “consequential element” lack a mechanical inventory rule. The old M7 draft at least required a frozen element inventory and explicit supported or unsupported dispositions. That obligation should survive the mechanism change. + +Before a run is graded, freeze: + +- the selected document and workpiece revisions; +- the element inventory; +- the rule distinguishing consequential elements from presentation-only artifacts; +- expected supported, partially supported, externally changed, and refused dispositions; and +- what happens to deleted or recreated elements. + +Without this, whole-net completion can be gamed by excluding difficult elements after the fact. + +### 21. Migration and rollback are missing + +The plan changes persisted workpiece representation from fenced assistant text to tool parts and state, changes ordinary-conversation tool admission, removes `ask` and `sweep` handling, and changes the lineage expected by future missions. Existing conversations and retained runs contain only the old representation. Rollback after new state records and new tool calls is unspecified. + +A migration matrix should cover: + +- old history with new code; +- new history with rolled-back code; +- conversations containing both fenced and tool revisions; +- mixed-version browser and server; +- Mission 6 fixture mode; +- retained evidence restoration; and +- tool-manifest rollback. + +Given the prototype posture, a permanent compatibility layer is not warranted, but the transition crosses persisted data and separately deployed browser/server boundaries. An additive introduction with a bounded dual-read period may therefore be the least safe mechanism. The bridge should have an explicit removal gate. + +### 22. Deployment ordering is inconsistent + +C14 moves remote durability back to Mission 8, but current Mission 9 and Mission 10 drafts still require deployed or replacement-safe inherited state. Historical Mission 8 stopped before remote deployment, and no new executable M8 is yet scheduled. This leaves later missions either blocked on an unscheduled dependency or tempted to silently weaken “deployed.” + +Choose explicitly between: + +- scheduling a real Mission 8 persistence/deployment mission before the first remote claim; or +- making M9 and M10 explicitly local-product missions and naming a pre-M11 remote release gate. + +“Locally run panel,” “locally verified application image,” and “remote replacement-safe service” must remain distinct claims. + +### 23. Consumer discovery may be too late + +Deferring self-describing export and optimization-consumer discovery until Mission 11 risks choosing an M9 region and M10 correction that do not exercise the scenario, parameter, metric, executable, or behavioral semantics Chris and Yannis actually need. Mission 11 could then become an unexpectedly large rebuild rather than a handoff. + +Do lightweight, non-binding consumer discovery before selecting the M9 region: one optimization question, minimum scenario and parameter semantics, expected execution boundary, required outputs, and minimum credibility checks. Keep implementation in M11, but use the real consumer to select a representative proving case and decide whether file-level provenance is load-bearing earlier. + +### 24. Semantic and behavioral proof is too visual until the optimization handoff + +A visually plausible, parser-valid SDCPN can still have incorrect enabling, resource conservation, timing, scenarios, or stochastic behavior. Human semantic review is necessary but insufficient for operational behavior. + +When selecting the first meaningful generated region, require at least one executable discriminator derived from the workpiece—for example resource reservation and release, reachability, token conservation, or one scenario outcome—and carry it unchanged through the reviewer-revision mission. This need not become broad simulation coverage; it should be the cheapest check capable of catching a plausible but behaviorally wrong projection. + +## Items that should be reopened before Mission 7 authority + +### Reopen C1–C3: lineage and the honest why answer + +Decide whether the product promises temporal audit context or causal provenance. If causal provenance remains the intent, identify the smallest explicit mutation-to-passage relation and separate authorship, evidence, rationale, and authority. + +### Reopen C5: lookup ownership and execution + +Keep semantic package ownership, but assign supported Flue history acquisition, authorization, and tool composition to the binding/app boundary. Decide whether the model needs two tools or one composed why operation based on interaction quality rather than package topology. + +### Reopen C6: hash-only net/workpiece join + +Replace the post-hash-only proposal with a transition and effect contract, or narrow the claim to unattributed state correlation. + +### Reopen C8: excluding capture or another retained evidence projection + +Known Flue compaction already threatens exact evidence and revision recovery. Decide the uncompacted limitation or the minimal compaction-independent retention mechanism before promising exact historical lines. + +### Reopen C10: full-bundle default admission + +Retire the inherited arbitrary six, but choose a scenario-derived canonical subset and expand from observed need rather than mounting near-parity by default. + +### Reopen C13: release wording + +“Who prepared it” and “the exact conversation line it rests on” exceed the represented identity and causality. Narrow the release note until actor identity, evidence retention, and causal association are established. + +### Reopen C15: passage identity + +Treat passage identity as a prerequisite to point-passage provenance and blame. Strengthen the probe to semantic edits, or defer blame and report only revision-local text. + +### Reopen D1, D3, and D5: real fixtures and construction sequencing + +Prove one genuine conversation can become an authorized live fixture before the persona campaign, and resolve whether construction in M7 intentionally consumes M9's visible advance. + +## Recommended next sequence + +1. **Adjudicate the product claim.** Decide causal provenance versus temporal audit lineage and amend the release wording accordingly. +2. **Build one adversarial tracer on paper or in the smallest executable harness.** Use one conversation with two workpiece passages, two mutations, one failed or no-op mutation, one correction, and one hand edit. Require deterministic answers or explicit refusals. +3. **Correct the Flue topology model.** Represent user delivery, assistant mutation request, later client-result signal, continuation, duplicate result delivery, and server-state writes using their real identities. +4. **Define the minimum mutation transition/effect record.** Include document identity, base state, outcome, post state, affected elements, and unknown-result behavior. +5. **Force compaction.** Verify which exact user text, workpiece revisions, and tool records remain available through the supported public surface; choose an explicit limitation or retained projection. +6. **Export or retain and reopen the tracer conversation.** Exercise authorization and why resolution after relocation or restart before launching paid persona breadth. +7. **Probe passage identity under semantic edits.** Include rename, move, paraphrase, split, merge, deletion, and reintroduction. +8. **Repair the provider schema carrier for the smallest scenario-derived mutation subset.** Prove one real nested call before broad admission. +9. **Re-cut the mission topology from the observed results.** Explicitly resolve the M7/M9 overlap, deployment order, migration boundary, and consumer-driven proving scenario. +10. **Only then run the broader persona construction campaign.** Freeze the consequential-element inventory and behavioral oracle before grading. + +## Evidence consulted + +- [`provenance-and-tooling-decision-log-2026-09-04.md`](provenance-and-tooling-decision-log-2026-09-04.md) +- [`provenance-by-lineage-mini-spec-2026-09-04.md`](provenance-by-lineage-mini-spec-2026-09-04.md) +- [`../../../MISSION.next.md`](../../../MISSION.next.md) +- [`../../mission-drafts/7-capture-backed-review.md`](../../mission-drafts/7-capture-backed-review.md) +- [`../../mission-drafts/9-traceable-projection.md`](../../mission-drafts/9-traceable-projection.md) +- [`../../mission-drafts/10-bounded-reviewer-revision.md`](../../mission-drafts/10-bounded-reviewer-revision.md) +- [`../../specs/petrinaut-batched-construction-tools.md`](../../specs/petrinaut-batched-construction-tools.md) +- [`../../../packages/core/src/workpiece.ts`](../../../packages/core/src/workpiece.ts) +- [`../../../packages/plugin-sdcpn/src/flue.ts`](../../../packages/plugin-sdcpn/src/flue.ts) +- [`../../../packages/plugin-sdcpn/src/tools/petrinaut-construction.ts`](../../../packages/plugin-sdcpn/src/tools/petrinaut-construction.ts) +- [`../../../packages/binding-flue/src/history-reader.ts`](../../../packages/binding-flue/src/history-reader.ts) +- [`../../../packages/transport-aisdk/src/client-tool-history.ts`](../../../packages/transport-aisdk/src/client-tool-history.ts) +- [`../../../../../../apps/brunch-agent/src/agents/chat-agent/agent.ts`](../../../../../../apps/brunch-agent/src/agents/chat-agent/agent.ts) +- [`../implementations/fe-1575-outer-browser-witness-2026-09-04-r2/witness.md`](../implementations/fe-1575-outer-browser-witness-2026-09-04-r2/witness.md) +- Installed Flue 2.0.3 documentation for agent hooks, public conversation history, compaction, streaming, and conversation persistence under `node_modules/@flue/runtime/docs/` and `node_modules/@flue/sdk/docs/` +- Petrinaut canonical AI, action, entity, and file-format schemas under `libs/@hashintel/petrinaut-core/src/` + +## Review disposition + +The design should not be discarded. Its useful core is a first-class revisioned workpiece plus mutation-call audit history. The correction is to stop calling temporal adjacency a complete provenance relation, then add only the smallest identities, effects, retention, and authorization contracts that the real why answer requires. The mission sequence should be recut after those disputed seams are probed, not before. diff --git a/libs/@hashintel/brunch-agent/docs/evidence/design/provenance-by-lineage-mini-spec-2026-09-04.md b/libs/@hashintel/brunch-agent/docs/evidence/design/provenance-by-lineage-mini-spec-2026-09-04.md new file mode 100644 index 00000000000..259beb1dc6e --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/design/provenance-by-lineage-mini-spec-2026-09-04.md @@ -0,0 +1,135 @@ +# Provenance by lineage — mini spec for review, 2026-09-04 + +> Design evidence, not execution authority. This document projects the [decision log of 2026-09-04](provenance-and-tooling-decision-log-2026-09-04.md) into one reviewable statement of intent, design, and consequences for the Brunch mission spine. It is written for an independent reviewer who has not seen the originating conversation. Entry references such as C4 point into the log. Nothing here may be implemented until it is re-evaluated and cut into a live `MISSION.md`; the intended vehicle is the Mission 7 cut. + +## 1. Intent + +Brunch must be able to say, for any consequential element of a Petri net it helped build, where that element came from: the workpiece passage that motivated it, the revision that introduced the passage, and the conversation turns behind that revision, or an honest refusal. It must do this without a comprehensive typed domain model, without a second conversation log, and without anyone hand-authoring links. + +Two earlier approaches failed in opposite directions. A comprehensive typed intermediate representation tried to make provenance a property of the domain model; the typology receded as it grew and the model worked worse with it. The structural Markdown workpiece that replaced it is legible and cheap but has no seam to either the conversation or the net, and provenance was deferred as "later" without the tension being named in the planning record (B3). + +The resolving observation: provenance is not a domain fact. It is who changed what, in response to what, when. Three actors know a link at the moment it is created, and each moment is already recorded in Flue's append-only canonical log once the artifacts they produce are tool calls (C1). + +## 2. The lineage model + +```text +turn N: user says X (user message, turnId T) + └─ assistant response (assistant message, turnId T) + ├─ text + ├─ tool call update_workpiece { markdown } revision R, identity = call id + ├─ tool result { revision, sha256 } + ├─ tool call addArc { ... } net mutation, same turnId + └─ tool result { ..., documentSha256 } net revision, same turnId + state_write workpiece = { callId, sha256, revision } same batch, same durability +``` + +Resolution walks backwards from an element and is a point at every hop but the last: + +```text +element id + → mutation call that created it, and calls that changed it point(s) + → latest update_workpiece at or before that call point + → the passage in that revision point + → blame: the revision that introduced the passage point + → user turns between that revision and the previous one RANGE + └─ narrowed to one line where the passage quotes the expert verbatim +``` + +The honest why answer is therefore: one creating call, one workpiece passage, one introducing revision, and then either a quoted line or a short turn range (C2). The range is where workpiece cadence is coarser than turn cadence. Elements changed several times report introducing and last-changing calls separately. Every hop is a lookup in the canonical log; nothing is stored elsewhere and nothing is inferred. + +What the model does: it calls the tools and interprets structured ranges in prose. What it may not do: author a link, reread the transcript as provenance, or explain an element the tools mark unsupported (C3). + +## 3. Mechanisms + +### 3.1 `update_workpiece` (core, server-side) — C4 + +| Aspect | Decision | +| --- | --- | +| Input | one Markdown string, the full current workpiece | +| Run | validate non-empty and well-formed; SHA-256; `usePersistentState('workpiece', { callId, sha256, revision })`; return `{ revision, sha256 }` | +| Durability | `durable: true`, so an interrupted call replays rather than settling unknown | +| Ownership | core owns the tool because the mechanism is formalism-independent; plugins own the template | +| Replaces | the fenced `runbook-ir` block in assistant text; the prepared-signal route stays for test-authored revision zero | + +Why a tool rather than a fenced block: the revision identity is a call id instead of a regex over prose; the tool can refuse a truncated document; the next render reads the current pointer without the model echoing it; mutations and revisions share `turnId`; the UI can project the part into its own pane. Costs acknowledged: the full document still crosses the wire each revision, and a model may call a tool less readily than it emits text. A structured-patch input is the later absorber for token cost; cadence must be measured for either shape (B6). + +### 3.2 `query_workpiece` (core) and `locate_elements` (plugin-sdcpn) — C5 + +`query_workpiece` takes a revision pointer or a passage locator and returns the turn ranges behind it with the user text, plus verbatim-quote matches where the passage quotes. `locate_elements` takes element ids and returns, per id, the mutation calls with their turn ids and document hashes, and the workpiece revision current at each. The split follows knowledge: core knows revisions and history; only the plugin knows which calls are mutations and where ids sit in inputs. Names are provisional. + +### 3.3 Net revision identity through the client-tool result — C6 + +The browser returns the post-mutation document SHA-256 inside each client-tool result. Mission 6 already computes this hash for its settled manifest. A document hash that no tool result explains is reported as "changed outside the conversation." Petrinaut's schema is not extended: elements are strict objects with no metadata slot and the file wrapper carries only `title` and a generator (B7). A file-level provenance pointer in `meta` is deferred until Mission 11 has a consumer for a self-describing export. + +### 3.4 The visible workpiece — C7 + +The chat rendering projects `update_workpiece` parts out of assistant messages and leaves a one-line "workpiece updated" marker. A pane in the Petrinaut Brunch panel shows the current revision, the revision list, and a diff between any two, all derived from Flue history through the Mission 5 transport. The why answer renders into the same pane because it resolves to a passage the reviewer must see. This projection lives in the app or transport layer, not in the Petrinaut library. The pane is the surface every later provenance and revision mission assumes and none has built; it is also the product-manager-visible advance in its own right. + +### 3.5 Schema carrier repair — B9, C11 + +Precondition for admitting any tool beyond the flat ones. Flue accepts Valibot schemas only and rejects other Standard Schema vendors; the construction factory therefore declares an empty loose object and pastes Petrinaut's JSON Schema into the description. Fix by a mechanical JSON Schema to Valibot interpreter covering the subset Petrinaut uses (objects, strings, numbers, enums, arrays, nullable, optional, unions), or by upstream Flue Standard Schema support. The local interpreter is derived rather than hand-copied and is reversible if upstream support arrives. + +### 3.6 Tool admission and teaching — C10, C12 + +Petrinaut's canonical mutation, query, and command tools are admitted to ordinary SDCPN conversations, mechanically from Petrinaut's AI tool bundle, and scoped down only from observed misbehaviour. The inherited six-tool subset is retired as a product surface. The skill gains construction posture: read the definition first, mutate in small steps, check compilation errors, record decisions in Construction notes, call `update_workpiece` before and after construction. Parity with the stock modeller remains a non-goal; the change is the direction of the default. + +### 3.7 Subtraction — C8, C9 + +Retired from code under Mission 7 authority: the `ask` and `sweep` names in core, the suspended ask contract, the website ask interactive tool and test, the sweep filter and output module in the panel transport, and the two Voice references. The capture store and sweep are not consumed by Mission 7; the store re-enters only if answer-time verification strains under compaction or ownership. + +## 4. Tool inventory after this design + +| Tool | Owner | Executes | Status | +| --- | --- | --- | --- | +| `ping` | app | server | keep, diagnostic | +| `activate_skill` | Flue | server | keep | +| `readPetrinautDoc` | plugin-sdcpn | browser | keep | +| Petrinaut canonical bundle | plugin-sdcpn, derived | browser | admit to ordinary conversation after 3.5 | +| `update_workpiece` | core | server | new | +| `query_workpiece` | core | server | new | +| `locate_elements` | plugin-sdcpn | server | new | +| `ask`, `sweep` client handling | core, website | browser | retire | +| six-tool and two-tool subsets | plugin-sdcpn | browser | retire as product surfaces; Mission 6 fixture mode stays until Mission 6 archives | + +## 5. Real honest fixtures — D1 to D5 + +The provenance pair is a real conversation with a real revisioned workpiece and a real constructed net, produced by persona interviews against the production agent. The Mission 6 prepared fixture remains a viability proof and is not promoted (A3). + +```text +persona run (Pi harness, production ChatAgent, real-headless host) + → Flue store holds the genuine conversation, revisions, mutations + → harness retains snapshot.json + projections per settled read + → grade coverage against the hidden oracle ledger afterwards + → ??? → live fixture the demo opens (D3, open) +``` + +Settled: run several cases in parallel; six cases exist. Recommended: a turn cap larger than the 6–10 used so far as budget, early stop when Brunch itself declares construction handoff or delivery, ledger coverage as the post-hoc grade rather than the stop rule (D4); runs go to construction so the fixture contains lineage, which sequences the carrier fix and tool admission before the construction campaign, with an earlier elicitation-only campaign to measure revision cadence (D5). + +Open: how a retained genuine conversation becomes a live fixture in a fresh store. Keeping the dev store as shipped data, restoring genuine records through Flue's storage adapter, and replaying the snapshot as prepared signals each have a named cost in D3; the first probe is whether Flue 2.0.3 tolerates export and restore at its storage boundary. + +## 6. What this changes in the mission spine + +1. **Name the tension.** The spine states provenance-by-lineage as the current hypothesis, the typed IR and hand-authored derivation as rejected with reasons, revision cadence as the named strain, and the visible workpiece as the precondition (E1). +2. **Re-cut Mission 7.** Body: carrier fix, orphan retirement, `update_workpiece`, workpiece pane, tool admission and teaching, persona programme to construction. Last step: the why route over real lineage. Release note narrowed to the honest framing (C13). Local posture; remote durability to Mission 8 (C14). Capture-store chain and Mission 2 inherited closure dropped (E2). +3. **Adjust Missions 9 and 10.** They inherit the seam from lineage: revision id equals call id, passage identity per the probe in C15, stable element ids, document hash per C6. They no longer assume a derivation fixture or a prebuilt pair (E3). +4. **Mission 6 close report.** Record the fixture-rigging admission, the carried fenced-block-to-tool change, and the credential cause of the blocked witness (E4). +5. **Authority.** Every settled item is an owner decision expressed in conversation. It becomes authority only when written into the cut Mission 7 `MISSION.md`, with Mission 6's construction-tool constraint amended there and not in the Mission 6 tie-off (E5). + +## 7. Fog-line + +- Revision cadence: whether the model calls `update_workpiece` often enough for blame to have grain; unmeasured (B6). +- Passage identity: heading path, Markdown anchor, or companion manifest; decided by the probe in C15. +- Carrier repair route: local interpreter versus upstream Flue support (3.5). +- Fixture materialization from a genuine conversation (D3). +- Token cost of full-document emission on long interviews, and when a structured patch earns its place. +- Which canonical tools misbehave at the provider boundary once the carrier carries fields; only observed failure scopes admission down. +- Whether the why answer over a real pair is useful to a reviewer, not merely correct; a human judges, per the Mission 7 draft's existing risk row. + +## 8. Questions for the independent reviewer + +1. Is provenance-by-lineage a sufficient answer to the stated intent, or does it smuggle an assumption the log did not check? +2. Is the tool split in 3.2 the right ownership boundary, or should one tool serve both lookups? +3. Is returning the document hash in the client-tool result an adequate join between net and workpiece revisions, given hand edits in Petrinaut? +4. Does anything in sections 3 and 4 reintroduce a mechanism the spine's rejected-mechanisms list has already refused? +5. Is the persona programme's stop rule and fixture-materialization plan honest about what a "real" fixture is? +6. Which settled items should be re-opened before they enter the Mission 7 authority, and why? diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/SHA256SUMS b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/SHA256SUMS new file mode 100644 index 00000000000..f97c0ae270f --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/SHA256SUMS @@ -0,0 +1,21 @@ +6807ecb0e76a2e90193281fb48c3c5c69158f45e04ad55055b75600533fce367 call-result-correlation.json +183ea07ea977ed9a121da7a38f04cdb165d87533ba94de0034d85c07a4efa5db cold-reader-gate.md +c6c49657c8b40f552a4d829f8089027ac4538cff1fe0e5f1f0f58c533138748e cold-reader-records.json +c17ae80cc22939f0af6c2675f6bcc20b025b17a5b7bfb9e8029813bcef9ad736 definition-after.json +0ec2b0c9bb82787602c359b41e6c1663001f8e42425ad2ab7f84ac019944766b definition-before.json +c17ae80cc22939f0af6c2675f6bcc20b025b17a5b7bfb9e8029813bcef9ad736 definition-tab-b.json +64d50737a22eed36ca5d5605a414ba80d30c5bc776e6d2a0d90b454ecd2b7d9b flue-snapshot-after.json +1a8ed1a7463511807bc2f6b796a272bcc279b412fc4306701b9a12b7f9d15f81 flue-snapshot-before.json +b05b8def1ff005df8131f6a486c2f9f68d5e41455c2732fe83d36187dc28e824 flue-snapshot-tab-b.json +6df15e3e1f95a1df9bb0567a84e6808abfebcd089e9b2c0d95b5d3c39b0412f1 latest-workpiece.md +17f99f53f8cf93285a7344f5e9687d94e5f59011e6895ce11a9722fde2e77511 prepared-workpiece.md +9f7148397ddde657157199428147971cbde59a751d8ffc13e5ce9624b74e6bf7 route-evidence.json +9ac14c680bbf962e528fb18826737a61174a3e42aaaeab85745fee2aa923d7ec run-metadata.json +8d5c9ccde97fa2058ba12adf9b37ab911ed02b24ffa4f8175a3633b3d6a42c25 screenshot-tab-a-after.png +ae6811c466fa0f918dc74166f155ad0239ffe37b1ce9c7d27beec5d1ed92a72d screenshot-tab-a-before.png +772ad1314906a2fc67f34b1ca5ccf7c179c8b76751a03b1a3544b7ce289d756d screenshot-tab-b-after.png +ec0af35e2018e72dfb2b10df4be944bdb645c649dcda66685d868c1d33d71f60 settled-manifest-after.json +a29c4a8d3453298285e1b210eb816a2a5636587ecb3fe367a8646d3c02da4e86 settled-manifest-before.json +ec0af35e2018e72dfb2b10df4be944bdb645c649dcda66685d868c1d33d71f60 settled-manifest-tab-b.json +ac10a63dc246af73c123ca81fb24ba1d5b680ff33d21efda930156a36f16e02a tab-b-correlation.json +d5d8211d376ccb334879c52bc7fff6360696c3166ca8903695d99bbe0b75d26d witness.md diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/call-result-correlation.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/call-result-correlation.json new file mode 100644 index 00000000000..04bad3673e9 --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/call-result-correlation.json @@ -0,0 +1,84 @@ +{ + "calls": [ + { + "messageId": "entry_01M1NV6ARKVMJTM74GVBKS6VW4", + "submissionId": "sub_ik_a38b1133b7c7c65bf8f07814d23bcbcb", + "toolCallId": "toolu_01BQukCZTAhJ64VNE7oC1CWG", + "state": "output-available", + "rawInput": { + "transitionId": "start-final-inspection", + "arcDirection": "input", + "placeId": "dispatch-crew-available", + "weight": "1", + "type": "standard" + }, + "parsedInput": { + "transitionId": "start-final-inspection", + "arcDirection": "input", + "placeId": "dispatch-crew-available", + "weight": 1, + "type": "standard" + } + } + ], + "resultDeliveries": [ + { + "messageId": "entry_direct_c3ViX2lrX2FmOWNjY2IxZTk1NzI5ZjkyNDk0NDA2NTE0NTE3Y2Nl", + "submissionId": "sub_ik_af9cccb1e95729f92494406514517cce", + "result": { + "toolCallId": "toolu_01BQukCZTAhJ64VNE7oC1CWG", + "toolName": "addArc", + "output": { + "title": "Added input arc", + "detail": "Dispatch crew available <-> Start final inspection", + "target": { + "kind": "selection", + "item": { + "type": "arc", + "id": "$A_place:dispatch-crew-available___start-final-inspection" + } + }, + "applied": true + } + } + }, + { + "messageId": "entry_direct_c3ViX2lrX2Q3OTdiNGM2MDUzZTVkMGExNDRlYzE1NDdhMjJlMGNh", + "submissionId": "sub_ik_d797b4c6053e5d0a144ec1547a22e0ca", + "result": { + "toolCallId": "toolu_01BQukCZTAhJ64VNE7oC1CWG", + "toolName": "addArc", + "output": { + "title": "Added input arc", + "detail": "Dispatch crew available <-> Start final inspection", + "target": { + "kind": "selection", + "item": { + "type": "arc", + "id": "$A_place:dispatch-crew-available___start-final-inspection" + } + }, + "applied": true + } + } + } + ], + "uniqueResults": [ + { + "toolCallId": "toolu_01BQukCZTAhJ64VNE7oC1CWG", + "toolName": "addArc", + "output": { + "title": "Added input arc", + "detail": "Dispatch crew available <-> Start final inspection", + "target": { + "kind": "selection", + "item": { + "type": "arc", + "id": "$A_place:dispatch-crew-available___start-final-inspection" + } + }, + "applied": true + } + } + ] +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/cold-reader-gate.md b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/cold-reader-gate.md new file mode 100644 index 00000000000..eb90497ed0e --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/cold-reader-gate.md @@ -0,0 +1,31 @@ +# Mission 6 cold-reader gate + +Reviewer qualification: a human who did not watch the implementation or outer witness. Do not give the reviewer `witness.md`, screenshots, implementation files, or this mission's expected conclusions. + +Give the reviewer only: + +- [prepared-workpiece.md](prepared-workpiece.md) +- [latest-workpiece.md](latest-workpiece.md) +- [cold-reader-records.json](cold-reader-records.json) + +Ask for a written response to these questions: + +1. Who authored each workpiece revision, and what in the supplied Flue records establishes that attribution? +2. What exact dispatch-crew quantity or resource policy is recorded? +3. What operation reserves the crew, and what operation returns it? +4. List every unresolved timing, failure, and recovery point. +5. What behavioral execution or operational-provenance claims, if any, can be made from these materials alone? + +Acceptance requires the human response to: + +- attribute revision zero to test preparation, not the model; +- attribute the later assistant record to model production; +- recover the exactly-one-crew policy and the intended reservation/return; +- preserve every timing, failure, and recovery unknown; and +- avoid inferring behavioral execution or capture provenance. + +Record the reviewer's identity, date, verbatim response, and explicit accept/reject decision below only after the review occurs. + +## Human verdict + +Pending. diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/cold-reader-records.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/cold-reader-records.json new file mode 100644 index 00000000000..75fd835682f --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/cold-reader-records.json @@ -0,0 +1,46 @@ +{ + "records": [ + { + "id": "entry_direct_c3ViX2lrXzI4ODYzMWVhYWQ3OThjYzY5MDcwZTMzMTNlOTUxYmNi", + "role": "system", + "purpose": "dispatch", + "display": "diagnostic", + "submissionId": "sub_ik_288631eaad798cc69070e3313e951bcb", + "signal": { + "tagName": "prepared-fixture", + "attributes": { + "fixtureId": "crew-reservation-v1", + "authorship": "test-authored", + "claimBoundary": "prepared-not-model-produced" + } + }, + "parts": [ + { + "type": "text", + "text": "Fixture authorship: test-authored preparation for Mission 6.\nNon-claims: not a Mission 4 candidate, not model-produced evidence, not capture-backed provenance, and not proof of automatic full-net projection.\n\n```runbook-ir\n# Final inspection and dispatch workpiece\n\n## Purpose and posture\nMaintain the narrow batch path from final inspection to dispatch readiness and test one evidence-backed correction against the live Petrinaut document.\n\n## Operational account\n- A batch that is ready enters final inspection.\n- Final inspection reserves the sole available dispatch crew.\n- Sign-off releases that crew and makes the batch ready for dispatch.\n\n## Quantity and resource policy\nExactly one dispatch crew is available in this fixture. Starting final inspection consumes that one available crew; sign-off returns it.\n\n## Current Petrinaut correspondence\nThe prepared non-empty net contains the batch path and the crew return from sign-off. It deliberately lacks the standard weight-1 input arc from `Dispatch crew available` to `Start final inspection`.\n\n## Explicit unknowns\nInspection and sign-off timing, failure modes, and recovery behavior remain unresolved.\n\n## Claim boundary\nThis prepared revision is test-authored diagnostic material. It is not model-produced evidence and does not establish capture provenance, behavioral execution, or broad projection quality.\n```", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NV6M4SBXB2WA46SM1B8QM3", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_d797b4c6053e5d0a144ec1547a22e0ca", + "turnId": "turn_01M1NV6KB8F7QJTRYHX0WT8AG5", + "parts": [ + { + "type": "reasoning", + "text": "Good! The arc has been successfully added. Looking at the final net definition, I can confirm:\n\n1. The arc was added successfully: `addArc` returned `\"applied\":true` with detail \"Dispatch crew available <-> Start final inspection\"\n\n2. In the final `getLatestNetDefinition` result, I can see that the `start-final-inspection` transition now has TWO input arcs:\n - `{\"placeId\":\"batch-ready\",\"weight\":1,\"type\":\"standard\"}`\n - `{\"type\":\"standard\",\"placeId\":\"dispatch-crew-available\",\"weight\":1\"}`\n\nThis confirms the missing arc from `Dispatch crew available` (weight 1, standard type) to `Start final inspection` is now in place.\n\nThe net structure is now:\n- **Places**: batch-ready, under-final-inspection, ready-for-dispatch, dispatch-crew-available\n- **Transitions**:\n - `start-final-inspection`: Takes a batch from batch-ready AND a crew token from dispatch-crew-available, produces under-final-inspection\n - `sign-off`: Takes under-final-inspection, produces both ready-for-dispatch AND returns dispatch-crew-available (crew token)\n\nThis correctly models the resource constraint: the single dispatch crew is reserved during inspection (consumed by start-final-inspection) and released at sign-off (produced by sign-off).\n\nNow I need to emit the revised runbook-ir workpiece, marking the revision as model-produced from test-authored revision zero.", + "state": "done" + }, + { + "type": "text", + "text": "✓ **Arc added successfully.** The Petrinaut tool confirms: `\"applied\":true` with detail \"Dispatch crew available ↔ Start final inspection\".\n\n**Verification of revised net structure:**\n- `Start final inspection` transition now has two standard input arcs (weight 1 each):\n 1. From `Batch ready` (original)\n 2. From `Dispatch crew available` (added, evidence-backed correction)\n- `Sign-off` transition correctly returns the crew token to `Dispatch crew available` (weight 1, output arc)\n\nThis enforces the resource constraint: the single dispatch crew is consumed when final inspection begins and restored at sign-off.\n\n---\n\n## Revised Workpiece (Model-Produced from Test-Authored Revision Zero)\n\n```runbook-ir\n# Final inspection and dispatch batch path\n\n## Purpose and posture\nModel the narrow batch path from final inspection to dispatch readiness with single-crew resource constraint. This revision incorporates the evidence-backed correction of the missing crew-availability input arc.\n\n## Operational account\n- A batch that is ready enters final inspection.\n- Final inspection requires and reserves the sole available dispatch crew.\n- Sign-off releases that crew and marks the batch ready for dispatch.\n\n## Quantity and resource policy\nExactly one dispatch crew is available in this system. Starting final inspection consumes that one available crew token; sign-off returns it. At most one batch can be under final inspection at a time because the sole crew is needed and cannot be in two places simultaneously.\n\n## Net structure (model-produced)\n**Places:**\n- `Batch ready`: batches waiting for inspection\n- `Under final inspection`: batches currently being inspected\n- `Ready for dispatch`: batches completed and awaiting dispatch\n- `Dispatch crew available`: availability token for the single crew (capacity 1)\n\n**Transitions:**\n- `Start final inspection`: consumes 1 batch token from `Batch ready` AND 1 crew token from `Dispatch crew available`; produces 1 token in `Under final inspection`\n- `Sign-off`: consumes 1 token from `Under final inspection`; produces 1 token each in `Ready for dispatch` and `Dispatch crew available` (returns the crew)\n\n**Arcs:**\n- `Batch ready` → `Start final inspection` (standard, weight 1) — original\n- `Dispatch crew available` → `Start final inspection` (standard, weight 1) — added in revision\n- `Start final inspection` → `Under final inspection` (standard, weight 1) — original\n- `Under final inspection` → `Sign-off` (standard, weight 1) — original\n- `Sign-off` → `Ready for dispatch` (standard, weight 1) — original\n- `Sign-off` → `Dispatch crew available` (standard, weight 1) — original\n\n## Explicit unknowns\n- Inspection and sign-off timing and duration\n- Failure modes, defect outcomes, and recovery behavior\n- Initial batch population and crew availability state\n- Repeat or recycling scenarios\n\n## Revision record\n**Revision 0** (test-authored): Prepared fixture identified missing standard weight-1 input arc from `Dispatch crew available` to `Start final inspection`.\n\n**Revision 1** (model-produced): Arc added via Petrinaut `addArc` tool and verified in net definition. Crew-availability constraint now enforced at net semantics level.\n\n## Claim boundary\nThis revision is model-produced evidence of arc correction applied to the test-authored source. It does not establish timing behavior, failure recovery, execution performance, or projection beyond this narrow batch-crew-inspection path.\n```", + "state": "done" + } + ] + } + ] +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/definition-after.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/definition-after.json new file mode 100644 index 00000000000..c92ed16c454 --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/definition-after.json @@ -0,0 +1,98 @@ +{ + "places": [ + { + "id": "batch-ready", + "name": "Batch ready", + "colorId": null, + "dynamicsEnabled": false, + "differentialEquationId": null, + "x": 80, + "y": 100 + }, + { + "id": "under-final-inspection", + "name": "Under final inspection", + "colorId": null, + "dynamicsEnabled": false, + "differentialEquationId": null, + "x": 420, + "y": 100 + }, + { + "id": "ready-for-dispatch", + "name": "Ready for dispatch", + "colorId": null, + "dynamicsEnabled": false, + "differentialEquationId": null, + "x": 760, + "y": 100 + }, + { + "id": "dispatch-crew-available", + "name": "Dispatch crew available", + "colorId": null, + "dynamicsEnabled": false, + "differentialEquationId": null, + "x": 420, + "y": 360 + } + ], + "transitions": [ + { + "id": "start-final-inspection", + "name": "Start final inspection", + "inputArcs": [ + { + "placeId": "batch-ready", + "weight": 1, + "type": "standard" + }, + { + "type": "standard", + "placeId": "dispatch-crew-available", + "weight": 1 + } + ], + "outputArcs": [ + { + "placeId": "under-final-inspection", + "weight": 1 + } + ], + "lambdaType": "predicate", + "lambdaCode": "", + "transitionKernelCode": "", + "x": 250, + "y": 100 + }, + { + "id": "sign-off", + "name": "Sign-off", + "inputArcs": [ + { + "placeId": "under-final-inspection", + "weight": 1, + "type": "standard" + } + ], + "outputArcs": [ + { + "placeId": "ready-for-dispatch", + "weight": 1 + }, + { + "placeId": "dispatch-crew-available", + "weight": 1 + } + ], + "lambdaType": "predicate", + "lambdaCode": "", + "transitionKernelCode": "", + "x": 590, + "y": 100 + } + ], + "types": [], + "differentialEquations": [], + "parameters": [] +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/definition-before.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/definition-before.json new file mode 100644 index 00000000000..5803556bfe8 --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/definition-before.json @@ -0,0 +1,93 @@ +{ + "places": [ + { + "id": "batch-ready", + "name": "Batch ready", + "colorId": null, + "dynamicsEnabled": false, + "differentialEquationId": null, + "x": 80, + "y": 100 + }, + { + "id": "under-final-inspection", + "name": "Under final inspection", + "colorId": null, + "dynamicsEnabled": false, + "differentialEquationId": null, + "x": 420, + "y": 100 + }, + { + "id": "ready-for-dispatch", + "name": "Ready for dispatch", + "colorId": null, + "dynamicsEnabled": false, + "differentialEquationId": null, + "x": 760, + "y": 100 + }, + { + "id": "dispatch-crew-available", + "name": "Dispatch crew available", + "colorId": null, + "dynamicsEnabled": false, + "differentialEquationId": null, + "x": 420, + "y": 360 + } + ], + "transitions": [ + { + "id": "start-final-inspection", + "name": "Start final inspection", + "inputArcs": [ + { + "placeId": "batch-ready", + "type": "standard", + "weight": 1 + } + ], + "outputArcs": [ + { + "placeId": "under-final-inspection", + "weight": 1 + } + ], + "lambdaType": "predicate", + "lambdaCode": "", + "transitionKernelCode": "", + "x": 250, + "y": 100 + }, + { + "id": "sign-off", + "name": "Sign-off", + "inputArcs": [ + { + "placeId": "under-final-inspection", + "type": "standard", + "weight": 1 + } + ], + "outputArcs": [ + { + "placeId": "ready-for-dispatch", + "weight": 1 + }, + { + "placeId": "dispatch-crew-available", + "weight": 1 + } + ], + "lambdaType": "predicate", + "lambdaCode": "", + "transitionKernelCode": "", + "x": 590, + "y": 100 + } + ], + "types": [], + "parameters": [], + "differentialEquations": [] +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/definition-tab-b.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/definition-tab-b.json new file mode 100644 index 00000000000..c92ed16c454 --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/definition-tab-b.json @@ -0,0 +1,98 @@ +{ + "places": [ + { + "id": "batch-ready", + "name": "Batch ready", + "colorId": null, + "dynamicsEnabled": false, + "differentialEquationId": null, + "x": 80, + "y": 100 + }, + { + "id": "under-final-inspection", + "name": "Under final inspection", + "colorId": null, + "dynamicsEnabled": false, + "differentialEquationId": null, + "x": 420, + "y": 100 + }, + { + "id": "ready-for-dispatch", + "name": "Ready for dispatch", + "colorId": null, + "dynamicsEnabled": false, + "differentialEquationId": null, + "x": 760, + "y": 100 + }, + { + "id": "dispatch-crew-available", + "name": "Dispatch crew available", + "colorId": null, + "dynamicsEnabled": false, + "differentialEquationId": null, + "x": 420, + "y": 360 + } + ], + "transitions": [ + { + "id": "start-final-inspection", + "name": "Start final inspection", + "inputArcs": [ + { + "placeId": "batch-ready", + "weight": 1, + "type": "standard" + }, + { + "type": "standard", + "placeId": "dispatch-crew-available", + "weight": 1 + } + ], + "outputArcs": [ + { + "placeId": "under-final-inspection", + "weight": 1 + } + ], + "lambdaType": "predicate", + "lambdaCode": "", + "transitionKernelCode": "", + "x": 250, + "y": 100 + }, + { + "id": "sign-off", + "name": "Sign-off", + "inputArcs": [ + { + "placeId": "under-final-inspection", + "weight": 1, + "type": "standard" + } + ], + "outputArcs": [ + { + "placeId": "ready-for-dispatch", + "weight": 1 + }, + { + "placeId": "dispatch-crew-available", + "weight": 1 + } + ], + "lambdaType": "predicate", + "lambdaCode": "", + "transitionKernelCode": "", + "x": 590, + "y": 100 + } + ], + "types": [], + "differentialEquations": [], + "parameters": [] +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/flue-snapshot-after.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/flue-snapshot-after.json new file mode 100644 index 00000000000..b35e84df234 --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/flue-snapshot-after.json @@ -0,0 +1,287 @@ +{ + "v": 1, + "conversationId": "conv_01M1NV5WZETMYEGGMFXNYDSTRS", + "offset": "0000000000000000_0000000000000092", + "messages": [ + { + "id": "entry_direct_c3ViX2lrXzI4ODYzMWVhYWQ3OThjYzY5MDcwZTMzMTNlOTUxYmNi", + "role": "system", + "purpose": "dispatch", + "display": "diagnostic", + "submissionId": "sub_ik_288631eaad798cc69070e3313e951bcb", + "signal": { + "tagName": "prepared-fixture", + "attributes": { + "fixtureId": "crew-reservation-v1", + "authorship": "test-authored", + "claimBoundary": "prepared-not-model-produced" + } + }, + "parts": [ + { + "type": "text", + "text": "Fixture authorship: test-authored preparation for Mission 6.\nNon-claims: not a Mission 4 candidate, not model-produced evidence, not capture-backed provenance, and not proof of automatic full-net projection.\n\n```runbook-ir\n# Final inspection and dispatch workpiece\n\n## Purpose and posture\nMaintain the narrow batch path from final inspection to dispatch readiness and test one evidence-backed correction against the live Petrinaut document.\n\n## Operational account\n- A batch that is ready enters final inspection.\n- Final inspection reserves the sole available dispatch crew.\n- Sign-off releases that crew and makes the batch ready for dispatch.\n\n## Quantity and resource policy\nExactly one dispatch crew is available in this fixture. Starting final inspection consumes that one available crew; sign-off returns it.\n\n## Current Petrinaut correspondence\nThe prepared non-empty net contains the batch path and the crew return from sign-off. It deliberately lacks the standard weight-1 input arc from `Dispatch crew available` to `Start final inspection`.\n\n## Explicit unknowns\nInspection and sign-off timing, failure modes, and recovery behavior remain unresolved.\n\n## Claim boundary\nThis prepared revision is test-authored diagnostic material. It is not model-produced evidence and does not establish capture provenance, behavioral execution, or broad projection quality.\n```", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NV5XQCBVP3FC4JW5FH1WZJ", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_288631eaad798cc69070e3313e951bcb", + "turnId": "turn_01M1NV5WZH2J0S74S1Q24556EP", + "parts": [ + { + "type": "reasoning", + "text": "This is a prepared fixture for a conversation about operational process modeling using SDCPN (stochastic dynamic coloured Petri nets) in Petrinaut. Let me understand what's being presented:\n\nThe fixture describes a batch processing system with final inspection and dispatch:\n- Batches enter final inspection when ready\n- Final inspection reserves the sole available dispatch crew\n- Sign-off releases the crew and marks the batch as ready for dispatch\n- There's exactly one dispatch crew available\n\nThe fixture notes that the current Petrinaut document has a deliberate gap: it's missing the standard weight-1 input arc from \"Dispatch crew available\" to \"Start final inspection\".\n\nThe task seems to be to:\n1. Maintain this narrow batch path from final inspection to dispatch readiness\n2. Test one evidence-backed correction against the live Petrinaut document\n3. This means likely adding that missing arc\n\nThe explicit unknowns are:\n- Inspection and sign-off timing\n- Failure modes and recovery behavior\n\nThis is a test-authored diagnostic material, not model-produced evidence.\n\nBefore I proceed, I should:\n1. Activate the `sdcpn-modelling` skill since this is about SDCPN modeling\n2. Get the current Petrinaut net definition to see what we're working with\n3. Understand the current state and the specific correction needed\n\nLet me start by activating the skill and getting the current net state.", + "state": "done" + }, + { + "type": "text", + "text": "I'll help you maintain and test this final inspection and dispatch workpiece. Let me start by activating the SDCPN modelling skill and examining the current Petrinaut net definition.", + "state": "done" + }, + { + "type": "dynamic-tool", + "toolName": "activate_skill", + "toolCallId": "toolu_01Y9HuAi2cvi16UD9bTvcvfS", + "state": "output-available", + "input": { + "name": "sdcpn-modelling" + }, + "output": "Run the skill named \"sdcpn-modelling\".\n\n\n# Capability-aware lifecycle\n\nUse one conceptual lifecycle: orient, elicit or revise, maintain the workpiece, construct when supported, check, and deliver. The current conversation may expose only one branch of that lifecycle. Do not claim that an unavailable transition occurred.\n\n## Select the runtime branch\n\n### Interactive elicitation or revision\n\nInterview in the person's operational vocabulary. Activate the `elicitation` skill and read `references/profile.md` before substantive questions or revision. Read `templates/workpiece.md` when creating or materially revising the shared workpiece. Construct only when the mounted capabilities actually permit construction in this conversation.\n\n### Construct-only execution\n\nUse the supplied workpiece as the complete modelling input. Do not interview. Read `references/pn-construction.md` and `references/checks.md`, then use the mounted construction tools. If a consequential workpiece gap prevents faithful construction, report the gap and the smallest question a later interactive elicitation must answer; do not ask it or invent an answer in this conversation.\n\n## Procedure\n\n### Orient\n\nEstablish enough purpose and context to select one focused next action: the intended question or decision, audience, boundary, horizon, accuracy need, and available time. Orientation need not settle every concern before elicitation begins.\n\n### Elicit or revise\n\nFor a new account, follow one concrete case and re-evaluate the active gap after each useful answer. For an existing account, first locate the disputed or changed material and its consequence for the objective. Use the `elicitation` skill's universal guidance and `references/profile.md` for detailed operations and coverage; do not turn their register order into question order.\n\n### Maintain the workpiece\n\nTreat the workpiece as the recoverable account construction will consume. Update it after a useful stretch rather than waiting until the end. Preserve unrelated material unless new evidence affects it.\n\nWhenever the workpiece changes substantially, emit the full current document in a fenced block whose language tag is exactly `runbook-ir`. Emit it again before construction and before workpiece-only delivery. A delta or prose promise is not a recoverable workpiece.\n\n### Construct\n\nConstruct only from the current workpiece. Read `references/pn-construction.md` and `references/checks.md` before beginning. Use mounted Petrinaut tools for every net change and inspect the resulting definition rather than emitting free-form net JSON. If the required tools are absent, limit the result to the workpiece and construction-ready notes.\n\nConstruction may infer a representation from recorded operational meaning; it may not invent operational facts. Record construction inferences, approximations, defaults, and target losses in the workpiece.\n\n### Check and deliver\n\nApply `references/checks.md` whenever construction is prepared or attempted. Deliver the current workpiece in every branch. Deliver a net only when the mounted tool path has produced and checked one. State what the result can support, what remains open, what was assumed or simplified, and what the target or current tools could not represent.\n\nAn explicit stop opens no new topic. In an interactive conversation, emit the best current workpiece and any already-checked net with limitations visible. In construct-only execution, report a blocking gap rather than opening an interview.\n\n## Resource discipline\n\nRead resources directly from this skill's advertised resource list, using the exact `/.flue/packaged-skills/...` path shown in the activation briefing; the relative name is a label only. Do not treat Markdown links as includes, follow references recursively, or read construction material merely to frame ordinary interview questions.\n\n\nSupporting skill resources are available but are not loaded into context unless needed:\n\n- references/checks.md → read_skill_resource /.flue/packaged-skills/skill%3Asdcpn-modelling%3A99a447ec4bcbf6c0/references/checks.md\n- references/pn-construction.md → read_skill_resource /.flue/packaged-skills/skill%3Asdcpn-modelling%3A99a447ec4bcbf6c0/references/pn-construction.md\n- references/profile.md → read_skill_resource /.flue/packaged-skills/skill%3Asdcpn-modelling%3A99a447ec4bcbf6c0/references/profile.md\n- templates/workpiece.md → read_skill_resource /.flue/packaged-skills/skill%3Asdcpn-modelling%3A99a447ec4bcbf6c0/templates/workpiece.md\n", + "durationMs": 1 + }, + { + "type": "dynamic-tool", + "toolName": "getLatestNetDefinition", + "toolCallId": "toolu_018ayLcg31aA5nZjvPe3VLEz", + "state": "output-available", + "input": {}, + "output": { + "awaiting": "client" + }, + "durationMs": 1 + }, + { + "type": "reasoning", + "text": "The getLatestNetDefinition returned awaiting client signal, meaning the client needs to load the current document. I should wait for that and then proceed. But let me think about what to do next.\n\nThe skill has been activated successfully. Now I need to:\n1. Wait for the net definition from the client\n2. Read the workpiece template to understand how to structure the workpiece\n3. Understand the current gaps and what needs to be corrected\n\nBased on the prepared fixture, the key issue is:\n- The current Petrinaut document is missing the weight-1 input arc from \"Dispatch crew available\" to \"Start final inspection\"\n- This is a deliberate gap that we need to test correcting\n\nLet me wait for the client signal and then proceed with getting the live document and understanding what needs to be corrected.", + "state": "done" + }, + { + "type": "text", + "text": "The skill is now active. I'm awaiting the current Petrinaut net definition from the client. Once that arrives, I'll examine the current state and identify the specific gap mentioned in the fixture—the missing input arc from `Dispatch crew available` to `Start final inspection`—and we can verify the evidence-backed correction.\n\nThe fixture establishes a clear scope:\n- **Operational focus**: The narrow batch path from final inspection through dispatch readiness\n- **Resource constraint**: Exactly one dispatch crew available, reserved during inspection, returned at sign-off\n- **Known gap**: Missing standard weight-1 arc from crew availability to the start of inspection\n- **Boundary**: Timing, failure modes, and recovery are explicitly unresolved\n\nI'm ready to proceed once the net definition loads. What would you like to examine or correct first?", + "state": "done" + } + ] + }, + { + "id": "entry_direct_c3ViX2lrXzIzMzcwMWRlZDcxMDVkNWQ0M2M0YjhhZTI1MTRlYzhi", + "role": "user", + "purpose": "user", + "display": "visible", + "submissionId": "sub_ik_233701ded7105d5d43c4b8ae2514ec8b", + "parts": [ + { + "type": "text", + "text": "Confirmed: final inspection uses the single dispatch crew and sign-off releases it; timing, failure, and recovery remain unknown. Read the live Petrinaut definition, add the missing standard weight-1 input arc from Dispatch crew available to Start final inspection, verify it, and emit the full revised runbook-ir workpiece. In that revised workpiece, identify the revision itself as model-produced from test-authored revision zero; do not call the revised workpiece test-authored.", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NV68Q36Q2ZMVFVMR3A2J1D", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_233701ded7105d5d43c4b8ae2514ec8b", + "turnId": "turn_01M1NV67W787NJDB98B4DVTMA1", + "parts": [ + { + "type": "reasoning", + "text": "The user wants me to:\n1. Read the live Petrinaut net definition\n2. Add the missing standard weight-1 input arc from \"Dispatch crew available\" to \"Start final inspection\"\n3. Verify the change\n4. Emit the full revised runbook-ir workpiece\n5. Label the revised workpiece as model-produced from test-authored revision zero (not test-authored itself)\n\nLet me start by reading the live definition, then adding the arc, then verifying it, and finally emitting the revised workpiece.", + "state": "done" + }, + { + "type": "dynamic-tool", + "toolName": "getLatestNetDefinition", + "toolCallId": "toolu_016Jkt4shRUg8hmtq6YyfcFB", + "state": "output-available", + "input": {}, + "output": { + "awaiting": "client" + }, + "durationMs": 1 + } + ] + }, + { + "id": "entry_direct_c3ViX2lrX2EzOGIxMTMzYjdjN2M2NWJmOGYwNzgxNGQyM2JjYmNi", + "role": "system", + "purpose": "dispatch", + "display": "diagnostic", + "submissionId": "sub_ik_a38b1133b7c7c65bf8f07814d23bcbcb", + "signal": { + "tagName": "client-tool-result", + "attributes": { + "toolCallIds": "toolu_016Jkt4shRUg8hmtq6YyfcFB" + } + }, + "parts": [ + { + "type": "text", + "text": "[{\"toolCallId\":\"toolu_016Jkt4shRUg8hmtq6YyfcFB\",\"toolName\":\"getLatestNetDefinition\",\"output\":{\"title\":\"Prepared final inspection and dispatch\",\"definition\":{\"places\":[{\"id\":\"batch-ready\",\"name\":\"Batch ready\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":80,\"y\":100},{\"id\":\"under-final-inspection\",\"name\":\"Under final inspection\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":100},{\"id\":\"ready-for-dispatch\",\"name\":\"Ready for dispatch\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":760,\"y\":100},{\"id\":\"dispatch-crew-available\",\"name\":\"Dispatch crew available\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":360}],\"transitions\":[{\"id\":\"start-final-inspection\",\"name\":\"Start final inspection\",\"inputArcs\":[{\"placeId\":\"batch-ready\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":250,\"y\":100},{\"id\":\"sign-off\",\"name\":\"Sign-off\",\"inputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"ready-for-dispatch\",\"weight\":1},{\"placeId\":\"dispatch-crew-available\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":590,\"y\":100}],\"types\":[],\"differentialEquations\":[],\"parameters\":[]},\"extensions\":{\"colors\":true,\"stochasticity\":true,\"dynamics\":true,\"parameters\":true,\"subnets\":true}}}]", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NV6ARKVMJTM74GVBKS6VW4", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_a38b1133b7c7c65bf8f07814d23bcbcb", + "turnId": "turn_01M1NV6A1766CSNYQR9H1HEBKH", + "parts": [ + { + "type": "reasoning", + "text": "The net definition has been loaded. Let me analyze the current state:\n\n**Current state:**\n- Places:\n - `batch-ready`: Batch waiting for final inspection\n - `under-final-inspection`: Batch undergoing final inspection\n - `ready-for-dispatch`: Batch ready for dispatch\n - `dispatch-crew-available`: Dispatch crew available (resource)\n\n- Transitions:\n - `start-final-inspection`: Takes a batch from `batch-ready` to `under-final-inspection`. It has one input arc from `batch-ready` with weight 1.\n - `sign-off`: Takes a batch from `under-final-inspection` to `ready-for-dispatch` and returns the crew to `dispatch-crew-available`. Has input arc from `under-final-inspection` (weight 1) and output arcs to both `ready-for-dispatch` and `dispatch-crew-available`.\n\n**The missing arc:**\nThe fixture states that the missing arc is: standard weight-1 input arc from `Dispatch crew available` to `Start final inspection`.\n\nCurrently, `start-final-inspection` only has one input arc from `batch-ready`. It needs an additional input arc from `dispatch-crew-available` with weight 1 and type \"standard\".\n\n**What needs to happen:**\n1. Add an input arc to the `start-final-inspection` transition from the `dispatch-crew-available` place with weight 1 and type \"standard\"\n2. Verify the result\n3. Emit the full revised workpiece with the identification that this is model-produced from test-authored revision zero\n\nLet me use the addArc function to add this arc.\n\nThe parameters for addArc based on the schema:\n- transitionId: \"start-final-inspection\"\n- arcDirection: \"input\"\n- placeId: \"dispatch-crew-available\" (since we're using the legacy shorthand)\n- weight: 1\n- type: \"standard\"\n- targetSubnetId omitted, since this operates on the root net", + "state": "done" + }, + { + "type": "dynamic-tool", + "toolName": "addArc", + "toolCallId": "toolu_01BQukCZTAhJ64VNE7oC1CWG", + "state": "output-available", + "input": { + "transitionId": "start-final-inspection", + "arcDirection": "input", + "placeId": "dispatch-crew-available", + "weight": "1", + "type": "standard" + }, + "output": { + "awaiting": "client" + }, + "durationMs": 0 + } + ] + }, + { + "id": "entry_direct_c3ViX2lrX2FmOWNjY2IxZTk1NzI5ZjkyNDk0NDA2NTE0NTE3Y2Nl", + "role": "system", + "purpose": "dispatch", + "display": "diagnostic", + "submissionId": "sub_ik_af9cccb1e95729f92494406514517cce", + "signal": { + "tagName": "client-tool-result", + "attributes": { + "toolCallIds": "toolu_016Jkt4shRUg8hmtq6YyfcFB,toolu_01BQukCZTAhJ64VNE7oC1CWG" + } + }, + "parts": [ + { + "type": "text", + "text": "[{\"toolCallId\":\"toolu_016Jkt4shRUg8hmtq6YyfcFB\",\"toolName\":\"getLatestNetDefinition\",\"output\":{\"title\":\"Prepared final inspection and dispatch\",\"definition\":{\"places\":[{\"id\":\"batch-ready\",\"name\":\"Batch ready\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":80,\"y\":100},{\"id\":\"under-final-inspection\",\"name\":\"Under final inspection\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":100},{\"id\":\"ready-for-dispatch\",\"name\":\"Ready for dispatch\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":760,\"y\":100},{\"id\":\"dispatch-crew-available\",\"name\":\"Dispatch crew available\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":360}],\"transitions\":[{\"id\":\"start-final-inspection\",\"name\":\"Start final inspection\",\"inputArcs\":[{\"placeId\":\"batch-ready\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":250,\"y\":100},{\"id\":\"sign-off\",\"name\":\"Sign-off\",\"inputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"ready-for-dispatch\",\"weight\":1},{\"placeId\":\"dispatch-crew-available\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":590,\"y\":100}],\"types\":[],\"differentialEquations\":[],\"parameters\":[]},\"extensions\":{\"colors\":true,\"stochasticity\":true,\"dynamics\":true,\"parameters\":true,\"subnets\":true}}},{\"toolCallId\":\"toolu_01BQukCZTAhJ64VNE7oC1CWG\",\"toolName\":\"addArc\",\"output\":{\"title\":\"Added input arc\",\"detail\":\"Dispatch crew available <-> Start final inspection\",\"target\":{\"kind\":\"selection\",\"item\":{\"type\":\"arc\",\"id\":\"$A_place:dispatch-crew-available___start-final-inspection\"}},\"applied\":true}}]", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NV6HRWJHSZ3QD2V8RZEWV3", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_af9cccb1e95729f92494406514517cce", + "turnId": "turn_01M1NV6GV7Q0BAMD0S09EEAY1E", + "parts": [ + { + "type": "reasoning", + "text": "Good! The arc has been added successfully. The tool output shows:\n- \"Applied: true\" - the arc was successfully added\n- Detail: \"Dispatch crew available <-> Start final inspection\"\n- The arc ID is \"$A_place:dispatch-crew-available___start-final-inspection\"\n\nNow I need to read the live net definition again to verify the change was applied correctly, then emit the revised workpiece with the full Markdown document showing the state of the net with the arc now in place.", + "state": "done" + }, + { + "type": "dynamic-tool", + "toolName": "getLatestNetDefinition", + "toolCallId": "toolu_017Ze5BnyBGXdaw7DJSGxZQb", + "state": "output-available", + "input": {}, + "output": { + "awaiting": "client" + }, + "durationMs": 0 + } + ] + }, + { + "id": "entry_direct_c3ViX2lrX2Q3OTdiNGM2MDUzZTVkMGExNDRlYzE1NDdhMjJlMGNh", + "role": "system", + "purpose": "dispatch", + "display": "diagnostic", + "submissionId": "sub_ik_d797b4c6053e5d0a144ec1547a22e0ca", + "signal": { + "tagName": "client-tool-result", + "attributes": { + "toolCallIds": "toolu_016Jkt4shRUg8hmtq6YyfcFB,toolu_01BQukCZTAhJ64VNE7oC1CWG,toolu_017Ze5BnyBGXdaw7DJSGxZQb" + } + }, + "parts": [ + { + "type": "text", + "text": "[{\"toolCallId\":\"toolu_016Jkt4shRUg8hmtq6YyfcFB\",\"toolName\":\"getLatestNetDefinition\",\"output\":{\"title\":\"Prepared final inspection and dispatch\",\"definition\":{\"places\":[{\"id\":\"batch-ready\",\"name\":\"Batch ready\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":80,\"y\":100},{\"id\":\"under-final-inspection\",\"name\":\"Under final inspection\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":100},{\"id\":\"ready-for-dispatch\",\"name\":\"Ready for dispatch\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":760,\"y\":100},{\"id\":\"dispatch-crew-available\",\"name\":\"Dispatch crew available\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":360}],\"transitions\":[{\"id\":\"start-final-inspection\",\"name\":\"Start final inspection\",\"inputArcs\":[{\"placeId\":\"batch-ready\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":250,\"y\":100},{\"id\":\"sign-off\",\"name\":\"Sign-off\",\"inputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"ready-for-dispatch\",\"weight\":1},{\"placeId\":\"dispatch-crew-available\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":590,\"y\":100}],\"types\":[],\"differentialEquations\":[],\"parameters\":[]},\"extensions\":{\"colors\":true,\"stochasticity\":true,\"dynamics\":true,\"parameters\":true,\"subnets\":true}}},{\"toolCallId\":\"toolu_01BQukCZTAhJ64VNE7oC1CWG\",\"toolName\":\"addArc\",\"output\":{\"title\":\"Added input arc\",\"detail\":\"Dispatch crew available <-> Start final inspection\",\"target\":{\"kind\":\"selection\",\"item\":{\"type\":\"arc\",\"id\":\"$A_place:dispatch-crew-available___start-final-inspection\"}},\"applied\":true}},{\"toolCallId\":\"toolu_017Ze5BnyBGXdaw7DJSGxZQb\",\"toolName\":\"getLatestNetDefinition\",\"output\":{\"title\":\"Prepared final inspection and dispatch\",\"definition\":{\"places\":[{\"id\":\"batch-ready\",\"name\":\"Batch ready\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":80,\"y\":100},{\"id\":\"under-final-inspection\",\"name\":\"Under final inspection\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":100},{\"id\":\"ready-for-dispatch\",\"name\":\"Ready for dispatch\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":760,\"y\":100},{\"id\":\"dispatch-crew-available\",\"name\":\"Dispatch crew available\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":360}],\"transitions\":[{\"id\":\"start-final-inspection\",\"name\":\"Start final inspection\",\"inputArcs\":[{\"placeId\":\"batch-ready\",\"weight\":1,\"type\":\"standard\"},{\"type\":\"standard\",\"placeId\":\"dispatch-crew-available\",\"weight\":1}],\"outputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":250,\"y\":100},{\"id\":\"sign-off\",\"name\":\"Sign-off\",\"inputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"ready-for-dispatch\",\"weight\":1},{\"placeId\":\"dispatch-crew-available\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":590,\"y\":100}],\"types\":[],\"differentialEquations\":[],\"parameters\":[]},\"extensions\":{\"colors\":true,\"stochasticity\":true,\"dynamics\":true,\"parameters\":true,\"subnets\":true}}}]", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NV6M4SBXB2WA46SM1B8QM3", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_d797b4c6053e5d0a144ec1547a22e0ca", + "turnId": "turn_01M1NV6KB8F7QJTRYHX0WT8AG5", + "parts": [ + { + "type": "reasoning", + "text": "Good! The arc has been successfully added. Looking at the final net definition, I can confirm:\n\n1. The arc was added successfully: `addArc` returned `\"applied\":true` with detail \"Dispatch crew available <-> Start final inspection\"\n\n2. In the final `getLatestNetDefinition` result, I can see that the `start-final-inspection` transition now has TWO input arcs:\n - `{\"placeId\":\"batch-ready\",\"weight\":1,\"type\":\"standard\"}`\n - `{\"type\":\"standard\",\"placeId\":\"dispatch-crew-available\",\"weight\":1\"}`\n\nThis confirms the missing arc from `Dispatch crew available` (weight 1, standard type) to `Start final inspection` is now in place.\n\nThe net structure is now:\n- **Places**: batch-ready, under-final-inspection, ready-for-dispatch, dispatch-crew-available\n- **Transitions**:\n - `start-final-inspection`: Takes a batch from batch-ready AND a crew token from dispatch-crew-available, produces under-final-inspection\n - `sign-off`: Takes under-final-inspection, produces both ready-for-dispatch AND returns dispatch-crew-available (crew token)\n\nThis correctly models the resource constraint: the single dispatch crew is reserved during inspection (consumed by start-final-inspection) and released at sign-off (produced by sign-off).\n\nNow I need to emit the revised runbook-ir workpiece, marking the revision as model-produced from test-authored revision zero.", + "state": "done" + }, + { + "type": "text", + "text": "✓ **Arc added successfully.** The Petrinaut tool confirms: `\"applied\":true` with detail \"Dispatch crew available ↔ Start final inspection\".\n\n**Verification of revised net structure:**\n- `Start final inspection` transition now has two standard input arcs (weight 1 each):\n 1. From `Batch ready` (original)\n 2. From `Dispatch crew available` (added, evidence-backed correction)\n- `Sign-off` transition correctly returns the crew token to `Dispatch crew available` (weight 1, output arc)\n\nThis enforces the resource constraint: the single dispatch crew is consumed when final inspection begins and restored at sign-off.\n\n---\n\n## Revised Workpiece (Model-Produced from Test-Authored Revision Zero)\n\n```runbook-ir\n# Final inspection and dispatch batch path\n\n## Purpose and posture\nModel the narrow batch path from final inspection to dispatch readiness with single-crew resource constraint. This revision incorporates the evidence-backed correction of the missing crew-availability input arc.\n\n## Operational account\n- A batch that is ready enters final inspection.\n- Final inspection requires and reserves the sole available dispatch crew.\n- Sign-off releases that crew and marks the batch ready for dispatch.\n\n## Quantity and resource policy\nExactly one dispatch crew is available in this system. Starting final inspection consumes that one available crew token; sign-off returns it. At most one batch can be under final inspection at a time because the sole crew is needed and cannot be in two places simultaneously.\n\n## Net structure (model-produced)\n**Places:**\n- `Batch ready`: batches waiting for inspection\n- `Under final inspection`: batches currently being inspected\n- `Ready for dispatch`: batches completed and awaiting dispatch\n- `Dispatch crew available`: availability token for the single crew (capacity 1)\n\n**Transitions:**\n- `Start final inspection`: consumes 1 batch token from `Batch ready` AND 1 crew token from `Dispatch crew available`; produces 1 token in `Under final inspection`\n- `Sign-off`: consumes 1 token from `Under final inspection`; produces 1 token each in `Ready for dispatch` and `Dispatch crew available` (returns the crew)\n\n**Arcs:**\n- `Batch ready` → `Start final inspection` (standard, weight 1) — original\n- `Dispatch crew available` → `Start final inspection` (standard, weight 1) — added in revision\n- `Start final inspection` → `Under final inspection` (standard, weight 1) — original\n- `Under final inspection` → `Sign-off` (standard, weight 1) — original\n- `Sign-off` → `Ready for dispatch` (standard, weight 1) — original\n- `Sign-off` → `Dispatch crew available` (standard, weight 1) — original\n\n## Explicit unknowns\n- Inspection and sign-off timing and duration\n- Failure modes, defect outcomes, and recovery behavior\n- Initial batch population and crew availability state\n- Repeat or recycling scenarios\n\n## Revision record\n**Revision 0** (test-authored): Prepared fixture identified missing standard weight-1 input arc from `Dispatch crew available` to `Start final inspection`.\n\n**Revision 1** (model-produced): Arc added via Petrinaut `addArc` tool and verified in net definition. Crew-availability constraint now enforced at net semantics level.\n\n## Claim boundary\nThis revision is model-produced evidence of arc correction applied to the test-authored source. It does not establish timing behavior, failure recovery, execution performance, or projection beyond this narrow batch-crew-inspection path.\n```", + "state": "done" + } + ] + } + ], + "settlements": [ + { + "submissionId": "sub_ik_288631eaad798cc69070e3313e951bcb", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_288631eaad798cc69070e3313e951bcb" + }, + { + "submissionId": "sub_ik_233701ded7105d5d43c4b8ae2514ec8b", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_233701ded7105d5d43c4b8ae2514ec8b" + }, + { + "submissionId": "sub_ik_a38b1133b7c7c65bf8f07814d23bcbcb", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_a38b1133b7c7c65bf8f07814d23bcbcb" + }, + { + "submissionId": "sub_ik_af9cccb1e95729f92494406514517cce", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_af9cccb1e95729f92494406514517cce" + }, + { + "submissionId": "sub_ik_d797b4c6053e5d0a144ec1547a22e0ca", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_d797b4c6053e5d0a144ec1547a22e0ca" + } + ], + "incarnation": "inc_01M1NV5WZD6VQ76Z7C3JJRFFBA" +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/flue-snapshot-before.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/flue-snapshot-before.json new file mode 100644 index 00000000000..ab6482d39be --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/flue-snapshot-before.json @@ -0,0 +1,89 @@ +{ + "v": 1, + "conversationId": "conv_01M1NV5WZETMYEGGMFXNYDSTRS", + "offset": "0000000000000000_0000000000000032", + "messages": [ + { + "id": "entry_direct_c3ViX2lrXzI4ODYzMWVhYWQ3OThjYzY5MDcwZTMzMTNlOTUxYmNi", + "role": "system", + "purpose": "dispatch", + "display": "diagnostic", + "submissionId": "sub_ik_288631eaad798cc69070e3313e951bcb", + "signal": { + "tagName": "prepared-fixture", + "attributes": { + "fixtureId": "crew-reservation-v1", + "authorship": "test-authored", + "claimBoundary": "prepared-not-model-produced" + } + }, + "parts": [ + { + "type": "text", + "text": "Fixture authorship: test-authored preparation for Mission 6.\nNon-claims: not a Mission 4 candidate, not model-produced evidence, not capture-backed provenance, and not proof of automatic full-net projection.\n\n```runbook-ir\n# Final inspection and dispatch workpiece\n\n## Purpose and posture\nMaintain the narrow batch path from final inspection to dispatch readiness and test one evidence-backed correction against the live Petrinaut document.\n\n## Operational account\n- A batch that is ready enters final inspection.\n- Final inspection reserves the sole available dispatch crew.\n- Sign-off releases that crew and makes the batch ready for dispatch.\n\n## Quantity and resource policy\nExactly one dispatch crew is available in this fixture. Starting final inspection consumes that one available crew; sign-off returns it.\n\n## Current Petrinaut correspondence\nThe prepared non-empty net contains the batch path and the crew return from sign-off. It deliberately lacks the standard weight-1 input arc from `Dispatch crew available` to `Start final inspection`.\n\n## Explicit unknowns\nInspection and sign-off timing, failure modes, and recovery behavior remain unresolved.\n\n## Claim boundary\nThis prepared revision is test-authored diagnostic material. It is not model-produced evidence and does not establish capture provenance, behavioral execution, or broad projection quality.\n```", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NV5XQCBVP3FC4JW5FH1WZJ", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_288631eaad798cc69070e3313e951bcb", + "turnId": "turn_01M1NV5WZH2J0S74S1Q24556EP", + "parts": [ + { + "type": "reasoning", + "text": "This is a prepared fixture for a conversation about operational process modeling using SDCPN (stochastic dynamic coloured Petri nets) in Petrinaut. Let me understand what's being presented:\n\nThe fixture describes a batch processing system with final inspection and dispatch:\n- Batches enter final inspection when ready\n- Final inspection reserves the sole available dispatch crew\n- Sign-off releases the crew and marks the batch as ready for dispatch\n- There's exactly one dispatch crew available\n\nThe fixture notes that the current Petrinaut document has a deliberate gap: it's missing the standard weight-1 input arc from \"Dispatch crew available\" to \"Start final inspection\".\n\nThe task seems to be to:\n1. Maintain this narrow batch path from final inspection to dispatch readiness\n2. Test one evidence-backed correction against the live Petrinaut document\n3. This means likely adding that missing arc\n\nThe explicit unknowns are:\n- Inspection and sign-off timing\n- Failure modes and recovery behavior\n\nThis is a test-authored diagnostic material, not model-produced evidence.\n\nBefore I proceed, I should:\n1. Activate the `sdcpn-modelling` skill since this is about SDCPN modeling\n2. Get the current Petrinaut net definition to see what we're working with\n3. Understand the current state and the specific correction needed\n\nLet me start by activating the skill and getting the current net state.", + "state": "done" + }, + { + "type": "text", + "text": "I'll help you maintain and test this final inspection and dispatch workpiece. Let me start by activating the SDCPN modelling skill and examining the current Petrinaut net definition.", + "state": "done" + }, + { + "type": "dynamic-tool", + "toolName": "activate_skill", + "toolCallId": "toolu_01Y9HuAi2cvi16UD9bTvcvfS", + "state": "output-available", + "input": { + "name": "sdcpn-modelling" + }, + "output": "Run the skill named \"sdcpn-modelling\".\n\n\n# Capability-aware lifecycle\n\nUse one conceptual lifecycle: orient, elicit or revise, maintain the workpiece, construct when supported, check, and deliver. The current conversation may expose only one branch of that lifecycle. Do not claim that an unavailable transition occurred.\n\n## Select the runtime branch\n\n### Interactive elicitation or revision\n\nInterview in the person's operational vocabulary. Activate the `elicitation` skill and read `references/profile.md` before substantive questions or revision. Read `templates/workpiece.md` when creating or materially revising the shared workpiece. Construct only when the mounted capabilities actually permit construction in this conversation.\n\n### Construct-only execution\n\nUse the supplied workpiece as the complete modelling input. Do not interview. Read `references/pn-construction.md` and `references/checks.md`, then use the mounted construction tools. If a consequential workpiece gap prevents faithful construction, report the gap and the smallest question a later interactive elicitation must answer; do not ask it or invent an answer in this conversation.\n\n## Procedure\n\n### Orient\n\nEstablish enough purpose and context to select one focused next action: the intended question or decision, audience, boundary, horizon, accuracy need, and available time. Orientation need not settle every concern before elicitation begins.\n\n### Elicit or revise\n\nFor a new account, follow one concrete case and re-evaluate the active gap after each useful answer. For an existing account, first locate the disputed or changed material and its consequence for the objective. Use the `elicitation` skill's universal guidance and `references/profile.md` for detailed operations and coverage; do not turn their register order into question order.\n\n### Maintain the workpiece\n\nTreat the workpiece as the recoverable account construction will consume. Update it after a useful stretch rather than waiting until the end. Preserve unrelated material unless new evidence affects it.\n\nWhenever the workpiece changes substantially, emit the full current document in a fenced block whose language tag is exactly `runbook-ir`. Emit it again before construction and before workpiece-only delivery. A delta or prose promise is not a recoverable workpiece.\n\n### Construct\n\nConstruct only from the current workpiece. Read `references/pn-construction.md` and `references/checks.md` before beginning. Use mounted Petrinaut tools for every net change and inspect the resulting definition rather than emitting free-form net JSON. If the required tools are absent, limit the result to the workpiece and construction-ready notes.\n\nConstruction may infer a representation from recorded operational meaning; it may not invent operational facts. Record construction inferences, approximations, defaults, and target losses in the workpiece.\n\n### Check and deliver\n\nApply `references/checks.md` whenever construction is prepared or attempted. Deliver the current workpiece in every branch. Deliver a net only when the mounted tool path has produced and checked one. State what the result can support, what remains open, what was assumed or simplified, and what the target or current tools could not represent.\n\nAn explicit stop opens no new topic. In an interactive conversation, emit the best current workpiece and any already-checked net with limitations visible. In construct-only execution, report a blocking gap rather than opening an interview.\n\n## Resource discipline\n\nRead resources directly from this skill's advertised resource list, using the exact `/.flue/packaged-skills/...` path shown in the activation briefing; the relative name is a label only. Do not treat Markdown links as includes, follow references recursively, or read construction material merely to frame ordinary interview questions.\n\n\nSupporting skill resources are available but are not loaded into context unless needed:\n\n- references/checks.md → read_skill_resource /.flue/packaged-skills/skill%3Asdcpn-modelling%3A99a447ec4bcbf6c0/references/checks.md\n- references/pn-construction.md → read_skill_resource /.flue/packaged-skills/skill%3Asdcpn-modelling%3A99a447ec4bcbf6c0/references/pn-construction.md\n- references/profile.md → read_skill_resource /.flue/packaged-skills/skill%3Asdcpn-modelling%3A99a447ec4bcbf6c0/references/profile.md\n- templates/workpiece.md → read_skill_resource /.flue/packaged-skills/skill%3Asdcpn-modelling%3A99a447ec4bcbf6c0/templates/workpiece.md\n", + "durationMs": 1 + }, + { + "type": "dynamic-tool", + "toolName": "getLatestNetDefinition", + "toolCallId": "toolu_018ayLcg31aA5nZjvPe3VLEz", + "state": "output-available", + "input": {}, + "output": { + "awaiting": "client" + }, + "durationMs": 1 + }, + { + "type": "reasoning", + "text": "The getLatestNetDefinition returned awaiting client signal, meaning the client needs to load the current document. I should wait for that and then proceed. But let me think about what to do next.\n\nThe skill has been activated successfully. Now I need to:\n1. Wait for the net definition from the client\n2. Read the workpiece template to understand how to structure the workpiece\n3. Understand the current gaps and what needs to be corrected\n\nBased on the prepared fixture, the key issue is:\n- The current Petrinaut document is missing the weight-1 input arc from \"Dispatch crew available\" to \"Start final inspection\"\n- This is a deliberate gap that we need to test correcting\n\nLet me wait for the client signal and then proceed with getting the live document and understanding what needs to be corrected.", + "state": "done" + }, + { + "type": "text", + "text": "The skill is now active. I'm awaiting the current Petrinaut net definition from the client. Once that arrives, I'll examine the current state and identify the specific gap mentioned in the fixture—the missing input arc from `Dispatch crew available` to `Start final inspection`—and we can verify the evidence-backed correction.\n\nThe fixture establishes a clear scope:\n- **Operational focus**: The narrow batch path from final inspection through dispatch readiness\n- **Resource constraint**: Exactly one dispatch crew available, reserved during inspection, returned at sign-off\n- **Known gap**: Missing standard weight-1 arc from crew availability to the start of inspection\n- **Boundary**: Timing, failure modes, and recovery are explicitly unresolved\n\nI'm ready to proceed once the net definition loads. What would you like to examine or correct first?", + "state": "done" + } + ] + } + ], + "settlements": [ + { + "submissionId": "sub_ik_288631eaad798cc69070e3313e951bcb", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_288631eaad798cc69070e3313e951bcb" + } + ], + "incarnation": "inc_01M1NV5WZD6VQ76Z7C3JJRFFBA" +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/flue-snapshot-tab-b.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/flue-snapshot-tab-b.json new file mode 100644 index 00000000000..c0f8e5b5153 --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/flue-snapshot-tab-b.json @@ -0,0 +1,326 @@ +{ + "v": 1, + "conversationId": "conv_01M1NV5WZETMYEGGMFXNYDSTRS", + "offset": "0000000000000000_0000000000000110", + "messages": [ + { + "id": "entry_direct_c3ViX2lrXzI4ODYzMWVhYWQ3OThjYzY5MDcwZTMzMTNlOTUxYmNi", + "role": "system", + "purpose": "dispatch", + "display": "diagnostic", + "submissionId": "sub_ik_288631eaad798cc69070e3313e951bcb", + "signal": { + "tagName": "prepared-fixture", + "attributes": { + "fixtureId": "crew-reservation-v1", + "authorship": "test-authored", + "claimBoundary": "prepared-not-model-produced" + } + }, + "parts": [ + { + "type": "text", + "text": "Fixture authorship: test-authored preparation for Mission 6.\nNon-claims: not a Mission 4 candidate, not model-produced evidence, not capture-backed provenance, and not proof of automatic full-net projection.\n\n```runbook-ir\n# Final inspection and dispatch workpiece\n\n## Purpose and posture\nMaintain the narrow batch path from final inspection to dispatch readiness and test one evidence-backed correction against the live Petrinaut document.\n\n## Operational account\n- A batch that is ready enters final inspection.\n- Final inspection reserves the sole available dispatch crew.\n- Sign-off releases that crew and makes the batch ready for dispatch.\n\n## Quantity and resource policy\nExactly one dispatch crew is available in this fixture. Starting final inspection consumes that one available crew; sign-off returns it.\n\n## Current Petrinaut correspondence\nThe prepared non-empty net contains the batch path and the crew return from sign-off. It deliberately lacks the standard weight-1 input arc from `Dispatch crew available` to `Start final inspection`.\n\n## Explicit unknowns\nInspection and sign-off timing, failure modes, and recovery behavior remain unresolved.\n\n## Claim boundary\nThis prepared revision is test-authored diagnostic material. It is not model-produced evidence and does not establish capture provenance, behavioral execution, or broad projection quality.\n```", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NV5XQCBVP3FC4JW5FH1WZJ", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_288631eaad798cc69070e3313e951bcb", + "turnId": "turn_01M1NV5WZH2J0S74S1Q24556EP", + "parts": [ + { + "type": "reasoning", + "text": "This is a prepared fixture for a conversation about operational process modeling using SDCPN (stochastic dynamic coloured Petri nets) in Petrinaut. Let me understand what's being presented:\n\nThe fixture describes a batch processing system with final inspection and dispatch:\n- Batches enter final inspection when ready\n- Final inspection reserves the sole available dispatch crew\n- Sign-off releases the crew and marks the batch as ready for dispatch\n- There's exactly one dispatch crew available\n\nThe fixture notes that the current Petrinaut document has a deliberate gap: it's missing the standard weight-1 input arc from \"Dispatch crew available\" to \"Start final inspection\".\n\nThe task seems to be to:\n1. Maintain this narrow batch path from final inspection to dispatch readiness\n2. Test one evidence-backed correction against the live Petrinaut document\n3. This means likely adding that missing arc\n\nThe explicit unknowns are:\n- Inspection and sign-off timing\n- Failure modes and recovery behavior\n\nThis is a test-authored diagnostic material, not model-produced evidence.\n\nBefore I proceed, I should:\n1. Activate the `sdcpn-modelling` skill since this is about SDCPN modeling\n2. Get the current Petrinaut net definition to see what we're working with\n3. Understand the current state and the specific correction needed\n\nLet me start by activating the skill and getting the current net state.", + "state": "done" + }, + { + "type": "text", + "text": "I'll help you maintain and test this final inspection and dispatch workpiece. Let me start by activating the SDCPN modelling skill and examining the current Petrinaut net definition.", + "state": "done" + }, + { + "type": "dynamic-tool", + "toolName": "activate_skill", + "toolCallId": "toolu_01Y9HuAi2cvi16UD9bTvcvfS", + "state": "output-available", + "input": { + "name": "sdcpn-modelling" + }, + "output": "Run the skill named \"sdcpn-modelling\".\n\n\n# Capability-aware lifecycle\n\nUse one conceptual lifecycle: orient, elicit or revise, maintain the workpiece, construct when supported, check, and deliver. The current conversation may expose only one branch of that lifecycle. Do not claim that an unavailable transition occurred.\n\n## Select the runtime branch\n\n### Interactive elicitation or revision\n\nInterview in the person's operational vocabulary. Activate the `elicitation` skill and read `references/profile.md` before substantive questions or revision. Read `templates/workpiece.md` when creating or materially revising the shared workpiece. Construct only when the mounted capabilities actually permit construction in this conversation.\n\n### Construct-only execution\n\nUse the supplied workpiece as the complete modelling input. Do not interview. Read `references/pn-construction.md` and `references/checks.md`, then use the mounted construction tools. If a consequential workpiece gap prevents faithful construction, report the gap and the smallest question a later interactive elicitation must answer; do not ask it or invent an answer in this conversation.\n\n## Procedure\n\n### Orient\n\nEstablish enough purpose and context to select one focused next action: the intended question or decision, audience, boundary, horizon, accuracy need, and available time. Orientation need not settle every concern before elicitation begins.\n\n### Elicit or revise\n\nFor a new account, follow one concrete case and re-evaluate the active gap after each useful answer. For an existing account, first locate the disputed or changed material and its consequence for the objective. Use the `elicitation` skill's universal guidance and `references/profile.md` for detailed operations and coverage; do not turn their register order into question order.\n\n### Maintain the workpiece\n\nTreat the workpiece as the recoverable account construction will consume. Update it after a useful stretch rather than waiting until the end. Preserve unrelated material unless new evidence affects it.\n\nWhenever the workpiece changes substantially, emit the full current document in a fenced block whose language tag is exactly `runbook-ir`. Emit it again before construction and before workpiece-only delivery. A delta or prose promise is not a recoverable workpiece.\n\n### Construct\n\nConstruct only from the current workpiece. Read `references/pn-construction.md` and `references/checks.md` before beginning. Use mounted Petrinaut tools for every net change and inspect the resulting definition rather than emitting free-form net JSON. If the required tools are absent, limit the result to the workpiece and construction-ready notes.\n\nConstruction may infer a representation from recorded operational meaning; it may not invent operational facts. Record construction inferences, approximations, defaults, and target losses in the workpiece.\n\n### Check and deliver\n\nApply `references/checks.md` whenever construction is prepared or attempted. Deliver the current workpiece in every branch. Deliver a net only when the mounted tool path has produced and checked one. State what the result can support, what remains open, what was assumed or simplified, and what the target or current tools could not represent.\n\nAn explicit stop opens no new topic. In an interactive conversation, emit the best current workpiece and any already-checked net with limitations visible. In construct-only execution, report a blocking gap rather than opening an interview.\n\n## Resource discipline\n\nRead resources directly from this skill's advertised resource list, using the exact `/.flue/packaged-skills/...` path shown in the activation briefing; the relative name is a label only. Do not treat Markdown links as includes, follow references recursively, or read construction material merely to frame ordinary interview questions.\n\n\nSupporting skill resources are available but are not loaded into context unless needed:\n\n- references/checks.md → read_skill_resource /.flue/packaged-skills/skill%3Asdcpn-modelling%3A99a447ec4bcbf6c0/references/checks.md\n- references/pn-construction.md → read_skill_resource /.flue/packaged-skills/skill%3Asdcpn-modelling%3A99a447ec4bcbf6c0/references/pn-construction.md\n- references/profile.md → read_skill_resource /.flue/packaged-skills/skill%3Asdcpn-modelling%3A99a447ec4bcbf6c0/references/profile.md\n- templates/workpiece.md → read_skill_resource /.flue/packaged-skills/skill%3Asdcpn-modelling%3A99a447ec4bcbf6c0/templates/workpiece.md\n", + "durationMs": 1 + }, + { + "type": "dynamic-tool", + "toolName": "getLatestNetDefinition", + "toolCallId": "toolu_018ayLcg31aA5nZjvPe3VLEz", + "state": "output-available", + "input": {}, + "output": { + "awaiting": "client" + }, + "durationMs": 1 + }, + { + "type": "reasoning", + "text": "The getLatestNetDefinition returned awaiting client signal, meaning the client needs to load the current document. I should wait for that and then proceed. But let me think about what to do next.\n\nThe skill has been activated successfully. Now I need to:\n1. Wait for the net definition from the client\n2. Read the workpiece template to understand how to structure the workpiece\n3. Understand the current gaps and what needs to be corrected\n\nBased on the prepared fixture, the key issue is:\n- The current Petrinaut document is missing the weight-1 input arc from \"Dispatch crew available\" to \"Start final inspection\"\n- This is a deliberate gap that we need to test correcting\n\nLet me wait for the client signal and then proceed with getting the live document and understanding what needs to be corrected.", + "state": "done" + }, + { + "type": "text", + "text": "The skill is now active. I'm awaiting the current Petrinaut net definition from the client. Once that arrives, I'll examine the current state and identify the specific gap mentioned in the fixture—the missing input arc from `Dispatch crew available` to `Start final inspection`—and we can verify the evidence-backed correction.\n\nThe fixture establishes a clear scope:\n- **Operational focus**: The narrow batch path from final inspection through dispatch readiness\n- **Resource constraint**: Exactly one dispatch crew available, reserved during inspection, returned at sign-off\n- **Known gap**: Missing standard weight-1 arc from crew availability to the start of inspection\n- **Boundary**: Timing, failure modes, and recovery are explicitly unresolved\n\nI'm ready to proceed once the net definition loads. What would you like to examine or correct first?", + "state": "done" + } + ] + }, + { + "id": "entry_direct_c3ViX2lrXzIzMzcwMWRlZDcxMDVkNWQ0M2M0YjhhZTI1MTRlYzhi", + "role": "user", + "purpose": "user", + "display": "visible", + "submissionId": "sub_ik_233701ded7105d5d43c4b8ae2514ec8b", + "parts": [ + { + "type": "text", + "text": "Confirmed: final inspection uses the single dispatch crew and sign-off releases it; timing, failure, and recovery remain unknown. Read the live Petrinaut definition, add the missing standard weight-1 input arc from Dispatch crew available to Start final inspection, verify it, and emit the full revised runbook-ir workpiece. In that revised workpiece, identify the revision itself as model-produced from test-authored revision zero; do not call the revised workpiece test-authored.", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NV68Q36Q2ZMVFVMR3A2J1D", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_233701ded7105d5d43c4b8ae2514ec8b", + "turnId": "turn_01M1NV67W787NJDB98B4DVTMA1", + "parts": [ + { + "type": "reasoning", + "text": "The user wants me to:\n1. Read the live Petrinaut net definition\n2. Add the missing standard weight-1 input arc from \"Dispatch crew available\" to \"Start final inspection\"\n3. Verify the change\n4. Emit the full revised runbook-ir workpiece\n5. Label the revised workpiece as model-produced from test-authored revision zero (not test-authored itself)\n\nLet me start by reading the live definition, then adding the arc, then verifying it, and finally emitting the revised workpiece.", + "state": "done" + }, + { + "type": "dynamic-tool", + "toolName": "getLatestNetDefinition", + "toolCallId": "toolu_016Jkt4shRUg8hmtq6YyfcFB", + "state": "output-available", + "input": {}, + "output": { + "awaiting": "client" + }, + "durationMs": 1 + } + ] + }, + { + "id": "entry_direct_c3ViX2lrX2EzOGIxMTMzYjdjN2M2NWJmOGYwNzgxNGQyM2JjYmNi", + "role": "system", + "purpose": "dispatch", + "display": "diagnostic", + "submissionId": "sub_ik_a38b1133b7c7c65bf8f07814d23bcbcb", + "signal": { + "tagName": "client-tool-result", + "attributes": { + "toolCallIds": "toolu_016Jkt4shRUg8hmtq6YyfcFB" + } + }, + "parts": [ + { + "type": "text", + "text": "[{\"toolCallId\":\"toolu_016Jkt4shRUg8hmtq6YyfcFB\",\"toolName\":\"getLatestNetDefinition\",\"output\":{\"title\":\"Prepared final inspection and dispatch\",\"definition\":{\"places\":[{\"id\":\"batch-ready\",\"name\":\"Batch ready\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":80,\"y\":100},{\"id\":\"under-final-inspection\",\"name\":\"Under final inspection\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":100},{\"id\":\"ready-for-dispatch\",\"name\":\"Ready for dispatch\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":760,\"y\":100},{\"id\":\"dispatch-crew-available\",\"name\":\"Dispatch crew available\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":360}],\"transitions\":[{\"id\":\"start-final-inspection\",\"name\":\"Start final inspection\",\"inputArcs\":[{\"placeId\":\"batch-ready\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":250,\"y\":100},{\"id\":\"sign-off\",\"name\":\"Sign-off\",\"inputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"ready-for-dispatch\",\"weight\":1},{\"placeId\":\"dispatch-crew-available\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":590,\"y\":100}],\"types\":[],\"differentialEquations\":[],\"parameters\":[]},\"extensions\":{\"colors\":true,\"stochasticity\":true,\"dynamics\":true,\"parameters\":true,\"subnets\":true}}}]", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NV6ARKVMJTM74GVBKS6VW4", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_a38b1133b7c7c65bf8f07814d23bcbcb", + "turnId": "turn_01M1NV6A1766CSNYQR9H1HEBKH", + "parts": [ + { + "type": "reasoning", + "text": "The net definition has been loaded. Let me analyze the current state:\n\n**Current state:**\n- Places:\n - `batch-ready`: Batch waiting for final inspection\n - `under-final-inspection`: Batch undergoing final inspection\n - `ready-for-dispatch`: Batch ready for dispatch\n - `dispatch-crew-available`: Dispatch crew available (resource)\n\n- Transitions:\n - `start-final-inspection`: Takes a batch from `batch-ready` to `under-final-inspection`. It has one input arc from `batch-ready` with weight 1.\n - `sign-off`: Takes a batch from `under-final-inspection` to `ready-for-dispatch` and returns the crew to `dispatch-crew-available`. Has input arc from `under-final-inspection` (weight 1) and output arcs to both `ready-for-dispatch` and `dispatch-crew-available`.\n\n**The missing arc:**\nThe fixture states that the missing arc is: standard weight-1 input arc from `Dispatch crew available` to `Start final inspection`.\n\nCurrently, `start-final-inspection` only has one input arc from `batch-ready`. It needs an additional input arc from `dispatch-crew-available` with weight 1 and type \"standard\".\n\n**What needs to happen:**\n1. Add an input arc to the `start-final-inspection` transition from the `dispatch-crew-available` place with weight 1 and type \"standard\"\n2. Verify the result\n3. Emit the full revised workpiece with the identification that this is model-produced from test-authored revision zero\n\nLet me use the addArc function to add this arc.\n\nThe parameters for addArc based on the schema:\n- transitionId: \"start-final-inspection\"\n- arcDirection: \"input\"\n- placeId: \"dispatch-crew-available\" (since we're using the legacy shorthand)\n- weight: 1\n- type: \"standard\"\n- targetSubnetId omitted, since this operates on the root net", + "state": "done" + }, + { + "type": "dynamic-tool", + "toolName": "addArc", + "toolCallId": "toolu_01BQukCZTAhJ64VNE7oC1CWG", + "state": "output-available", + "input": { + "transitionId": "start-final-inspection", + "arcDirection": "input", + "placeId": "dispatch-crew-available", + "weight": "1", + "type": "standard" + }, + "output": { + "awaiting": "client" + }, + "durationMs": 0 + } + ] + }, + { + "id": "entry_direct_c3ViX2lrX2FmOWNjY2IxZTk1NzI5ZjkyNDk0NDA2NTE0NTE3Y2Nl", + "role": "system", + "purpose": "dispatch", + "display": "diagnostic", + "submissionId": "sub_ik_af9cccb1e95729f92494406514517cce", + "signal": { + "tagName": "client-tool-result", + "attributes": { + "toolCallIds": "toolu_016Jkt4shRUg8hmtq6YyfcFB,toolu_01BQukCZTAhJ64VNE7oC1CWG" + } + }, + "parts": [ + { + "type": "text", + "text": "[{\"toolCallId\":\"toolu_016Jkt4shRUg8hmtq6YyfcFB\",\"toolName\":\"getLatestNetDefinition\",\"output\":{\"title\":\"Prepared final inspection and dispatch\",\"definition\":{\"places\":[{\"id\":\"batch-ready\",\"name\":\"Batch ready\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":80,\"y\":100},{\"id\":\"under-final-inspection\",\"name\":\"Under final inspection\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":100},{\"id\":\"ready-for-dispatch\",\"name\":\"Ready for dispatch\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":760,\"y\":100},{\"id\":\"dispatch-crew-available\",\"name\":\"Dispatch crew available\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":360}],\"transitions\":[{\"id\":\"start-final-inspection\",\"name\":\"Start final inspection\",\"inputArcs\":[{\"placeId\":\"batch-ready\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":250,\"y\":100},{\"id\":\"sign-off\",\"name\":\"Sign-off\",\"inputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"ready-for-dispatch\",\"weight\":1},{\"placeId\":\"dispatch-crew-available\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":590,\"y\":100}],\"types\":[],\"differentialEquations\":[],\"parameters\":[]},\"extensions\":{\"colors\":true,\"stochasticity\":true,\"dynamics\":true,\"parameters\":true,\"subnets\":true}}},{\"toolCallId\":\"toolu_01BQukCZTAhJ64VNE7oC1CWG\",\"toolName\":\"addArc\",\"output\":{\"title\":\"Added input arc\",\"detail\":\"Dispatch crew available <-> Start final inspection\",\"target\":{\"kind\":\"selection\",\"item\":{\"type\":\"arc\",\"id\":\"$A_place:dispatch-crew-available___start-final-inspection\"}},\"applied\":true}}]", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NV6HRWJHSZ3QD2V8RZEWV3", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_af9cccb1e95729f92494406514517cce", + "turnId": "turn_01M1NV6GV7Q0BAMD0S09EEAY1E", + "parts": [ + { + "type": "reasoning", + "text": "Good! The arc has been added successfully. The tool output shows:\n- \"Applied: true\" - the arc was successfully added\n- Detail: \"Dispatch crew available <-> Start final inspection\"\n- The arc ID is \"$A_place:dispatch-crew-available___start-final-inspection\"\n\nNow I need to read the live net definition again to verify the change was applied correctly, then emit the revised workpiece with the full Markdown document showing the state of the net with the arc now in place.", + "state": "done" + }, + { + "type": "dynamic-tool", + "toolName": "getLatestNetDefinition", + "toolCallId": "toolu_017Ze5BnyBGXdaw7DJSGxZQb", + "state": "output-available", + "input": {}, + "output": { + "awaiting": "client" + }, + "durationMs": 0 + } + ] + }, + { + "id": "entry_direct_c3ViX2lrX2Q3OTdiNGM2MDUzZTVkMGExNDRlYzE1NDdhMjJlMGNh", + "role": "system", + "purpose": "dispatch", + "display": "diagnostic", + "submissionId": "sub_ik_d797b4c6053e5d0a144ec1547a22e0ca", + "signal": { + "tagName": "client-tool-result", + "attributes": { + "toolCallIds": "toolu_016Jkt4shRUg8hmtq6YyfcFB,toolu_01BQukCZTAhJ64VNE7oC1CWG,toolu_017Ze5BnyBGXdaw7DJSGxZQb" + } + }, + "parts": [ + { + "type": "text", + "text": "[{\"toolCallId\":\"toolu_016Jkt4shRUg8hmtq6YyfcFB\",\"toolName\":\"getLatestNetDefinition\",\"output\":{\"title\":\"Prepared final inspection and dispatch\",\"definition\":{\"places\":[{\"id\":\"batch-ready\",\"name\":\"Batch ready\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":80,\"y\":100},{\"id\":\"under-final-inspection\",\"name\":\"Under final inspection\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":100},{\"id\":\"ready-for-dispatch\",\"name\":\"Ready for dispatch\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":760,\"y\":100},{\"id\":\"dispatch-crew-available\",\"name\":\"Dispatch crew available\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":360}],\"transitions\":[{\"id\":\"start-final-inspection\",\"name\":\"Start final inspection\",\"inputArcs\":[{\"placeId\":\"batch-ready\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":250,\"y\":100},{\"id\":\"sign-off\",\"name\":\"Sign-off\",\"inputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"ready-for-dispatch\",\"weight\":1},{\"placeId\":\"dispatch-crew-available\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":590,\"y\":100}],\"types\":[],\"differentialEquations\":[],\"parameters\":[]},\"extensions\":{\"colors\":true,\"stochasticity\":true,\"dynamics\":true,\"parameters\":true,\"subnets\":true}}},{\"toolCallId\":\"toolu_01BQukCZTAhJ64VNE7oC1CWG\",\"toolName\":\"addArc\",\"output\":{\"title\":\"Added input arc\",\"detail\":\"Dispatch crew available <-> Start final inspection\",\"target\":{\"kind\":\"selection\",\"item\":{\"type\":\"arc\",\"id\":\"$A_place:dispatch-crew-available___start-final-inspection\"}},\"applied\":true}},{\"toolCallId\":\"toolu_017Ze5BnyBGXdaw7DJSGxZQb\",\"toolName\":\"getLatestNetDefinition\",\"output\":{\"title\":\"Prepared final inspection and dispatch\",\"definition\":{\"places\":[{\"id\":\"batch-ready\",\"name\":\"Batch ready\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":80,\"y\":100},{\"id\":\"under-final-inspection\",\"name\":\"Under final inspection\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":100},{\"id\":\"ready-for-dispatch\",\"name\":\"Ready for dispatch\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":760,\"y\":100},{\"id\":\"dispatch-crew-available\",\"name\":\"Dispatch crew available\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":360}],\"transitions\":[{\"id\":\"start-final-inspection\",\"name\":\"Start final inspection\",\"inputArcs\":[{\"placeId\":\"batch-ready\",\"weight\":1,\"type\":\"standard\"},{\"type\":\"standard\",\"placeId\":\"dispatch-crew-available\",\"weight\":1}],\"outputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":250,\"y\":100},{\"id\":\"sign-off\",\"name\":\"Sign-off\",\"inputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"ready-for-dispatch\",\"weight\":1},{\"placeId\":\"dispatch-crew-available\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":590,\"y\":100}],\"types\":[],\"differentialEquations\":[],\"parameters\":[]},\"extensions\":{\"colors\":true,\"stochasticity\":true,\"dynamics\":true,\"parameters\":true,\"subnets\":true}}}]", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NV6M4SBXB2WA46SM1B8QM3", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_d797b4c6053e5d0a144ec1547a22e0ca", + "turnId": "turn_01M1NV6KB8F7QJTRYHX0WT8AG5", + "parts": [ + { + "type": "reasoning", + "text": "Good! The arc has been successfully added. Looking at the final net definition, I can confirm:\n\n1. The arc was added successfully: `addArc` returned `\"applied\":true` with detail \"Dispatch crew available <-> Start final inspection\"\n\n2. In the final `getLatestNetDefinition` result, I can see that the `start-final-inspection` transition now has TWO input arcs:\n - `{\"placeId\":\"batch-ready\",\"weight\":1,\"type\":\"standard\"}`\n - `{\"type\":\"standard\",\"placeId\":\"dispatch-crew-available\",\"weight\":1\"}`\n\nThis confirms the missing arc from `Dispatch crew available` (weight 1, standard type) to `Start final inspection` is now in place.\n\nThe net structure is now:\n- **Places**: batch-ready, under-final-inspection, ready-for-dispatch, dispatch-crew-available\n- **Transitions**:\n - `start-final-inspection`: Takes a batch from batch-ready AND a crew token from dispatch-crew-available, produces under-final-inspection\n - `sign-off`: Takes under-final-inspection, produces both ready-for-dispatch AND returns dispatch-crew-available (crew token)\n\nThis correctly models the resource constraint: the single dispatch crew is reserved during inspection (consumed by start-final-inspection) and released at sign-off (produced by sign-off).\n\nNow I need to emit the revised runbook-ir workpiece, marking the revision as model-produced from test-authored revision zero.", + "state": "done" + }, + { + "type": "text", + "text": "✓ **Arc added successfully.** The Petrinaut tool confirms: `\"applied\":true` with detail \"Dispatch crew available ↔ Start final inspection\".\n\n**Verification of revised net structure:**\n- `Start final inspection` transition now has two standard input arcs (weight 1 each):\n 1. From `Batch ready` (original)\n 2. From `Dispatch crew available` (added, evidence-backed correction)\n- `Sign-off` transition correctly returns the crew token to `Dispatch crew available` (weight 1, output arc)\n\nThis enforces the resource constraint: the single dispatch crew is consumed when final inspection begins and restored at sign-off.\n\n---\n\n## Revised Workpiece (Model-Produced from Test-Authored Revision Zero)\n\n```runbook-ir\n# Final inspection and dispatch batch path\n\n## Purpose and posture\nModel the narrow batch path from final inspection to dispatch readiness with single-crew resource constraint. This revision incorporates the evidence-backed correction of the missing crew-availability input arc.\n\n## Operational account\n- A batch that is ready enters final inspection.\n- Final inspection requires and reserves the sole available dispatch crew.\n- Sign-off releases that crew and marks the batch ready for dispatch.\n\n## Quantity and resource policy\nExactly one dispatch crew is available in this system. Starting final inspection consumes that one available crew token; sign-off returns it. At most one batch can be under final inspection at a time because the sole crew is needed and cannot be in two places simultaneously.\n\n## Net structure (model-produced)\n**Places:**\n- `Batch ready`: batches waiting for inspection\n- `Under final inspection`: batches currently being inspected\n- `Ready for dispatch`: batches completed and awaiting dispatch\n- `Dispatch crew available`: availability token for the single crew (capacity 1)\n\n**Transitions:**\n- `Start final inspection`: consumes 1 batch token from `Batch ready` AND 1 crew token from `Dispatch crew available`; produces 1 token in `Under final inspection`\n- `Sign-off`: consumes 1 token from `Under final inspection`; produces 1 token each in `Ready for dispatch` and `Dispatch crew available` (returns the crew)\n\n**Arcs:**\n- `Batch ready` → `Start final inspection` (standard, weight 1) — original\n- `Dispatch crew available` → `Start final inspection` (standard, weight 1) — added in revision\n- `Start final inspection` → `Under final inspection` (standard, weight 1) — original\n- `Under final inspection` → `Sign-off` (standard, weight 1) — original\n- `Sign-off` → `Ready for dispatch` (standard, weight 1) — original\n- `Sign-off` → `Dispatch crew available` (standard, weight 1) — original\n\n## Explicit unknowns\n- Inspection and sign-off timing and duration\n- Failure modes, defect outcomes, and recovery behavior\n- Initial batch population and crew availability state\n- Repeat or recycling scenarios\n\n## Revision record\n**Revision 0** (test-authored): Prepared fixture identified missing standard weight-1 input arc from `Dispatch crew available` to `Start final inspection`.\n\n**Revision 1** (model-produced): Arc added via Petrinaut `addArc` tool and verified in net definition. Crew-availability constraint now enforced at net semantics level.\n\n## Claim boundary\nThis revision is model-produced evidence of arc correction applied to the test-authored source. It does not establish timing behavior, failure recovery, execution performance, or projection beyond this narrow batch-crew-inspection path.\n```", + "state": "done" + } + ] + }, + { + "id": "entry_direct_c3ViX2lrX2IyZjk2ZmQ2MmI4MGE2ZGNmOTgwMzI5YWQ2MWY3MGU5", + "role": "user", + "purpose": "user", + "display": "visible", + "submissionId": "sub_ik_b2f96fd62b80a6dcf980329ad61f70e9", + "parts": [ + { + "type": "text", + "text": "From the resumed workpiece, list the unresolved timing, failure, and recovery questions. Do not change the Petrinaut net.", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NV73Z110CY393GEB8T02SH", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_b2f96fd62b80a6dcf980329ad61f70e9", + "turnId": "turn_01M1NV7343YW19DZKMTGEDCWTF", + "parts": [ + { + "type": "reasoning", + "text": "The user is asking me to list the unresolved timing, failure, and recovery questions from the workpiece I just provided. They explicitly say not to change the Petrinaut net, so this is purely an analytical question about what remains open in the model.\n\nLet me look at the \"Explicit unknowns\" section I provided:\n- Inspection and sign-off timing and duration\n- Failure modes, defect outcomes, and recovery behavior\n- Initial batch population and crew availability state\n- Repeat or recycling scenarios\n\nBut the user wants me to be more specific about the timing, failure, and recovery questions. I should expand these into actual concrete questions that would need to be answered to make the model complete. Let me think about what operational questions are unresolved:\n\n**Timing questions:**\n- How long does final inspection take?\n- How long does sign-off take?\n- Are inspection and sign-off separate durations or combined?\n- Can inspection occur in parallel for multiple batches if crew were available?\n\n**Failure questions:**\n- What happens if a batch fails inspection?\n- Can inspection fail entirely or does sign-off always succeed?\n- Are there defect categories (reject, rework, accept)?\n- Does a failed batch consume the crew resource?\n\n**Recovery questions:**\n- If a batch fails inspection, where does it go? (Back to ready? To a separate fail queue? Removed entirely?)\n- Does failed inspection release the crew immediately, or after some remediation?\n- Can a reworked batch re-enter the inspection process?\n- What is the relationship between failed batches and crew availability?\n\nLet me provide a structured list of these unresolved questions organized by category.", + "state": "done" + }, + { + "type": "text", + "text": "## Unresolved Timing, Failure, and Recovery Questions\n\n### Timing\n1. **Inspection duration**: What is the stochastic or deterministic duration of the final inspection phase (from \"Start final inspection\" to \"Sign-off\")?\n2. **Sign-off duration**: Does \"Sign-off\" occur instantaneously, or does it have its own duration separate from inspection?\n3. **Crew engagement horizon**: Is the crew occupied only during the inspection phase, or does it remain engaged during batches' time in \"Ready for dispatch\"?\n4. **Parallel inspection capacity**: If additional crews became available, could multiple batches be inspected concurrently, or is there a single-batch constraint in the process?\n\n### Failure and Defect Outcomes\n1. **Inspection failure mode**: Can a batch fail inspection? If so, under what conditions?\n2. **Failure rate**: Is there a known or modeled probability that inspection produces a defect or rejection?\n3. **Defect categories**: Are there multiple outcomes (accept, reject, rework required)? If so, what distinguishes them?\n4. **Sign-off contingency**: Can sign-off always succeed after inspection, or can sign-off itself reject a batch?\n5. **Scrap or return**: What happens to batches that fail? Are they removed from the system, returned to \"Batch ready,\" sent to a separate rework queue, or diverted elsewhere?\n\n### Recovery and Rework Behavior\n1. **Failed batch crew release**: When a batch fails inspection, is the crew released immediately, or must it complete additional work before returning to availability?\n2. **Rework entry point**: If a batch requires rework, does it re-enter \"Batch ready,\" or is there a separate rework-staging place?\n3. **Rework limit**: Is there a maximum number of rework cycles, or can a batch cycle indefinitely?\n4. **Crew allocation to rework**: Does rework use the same dispatch crew, a different crew, or no dedicated crew?\n5. **Initial state**: How many batches start in \"Batch ready\" and how is \"Dispatch crew available\" initialized (1 token, or variable)?", + "state": "done" + } + ] + } + ], + "settlements": [ + { + "submissionId": "sub_ik_288631eaad798cc69070e3313e951bcb", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_288631eaad798cc69070e3313e951bcb" + }, + { + "submissionId": "sub_ik_233701ded7105d5d43c4b8ae2514ec8b", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_233701ded7105d5d43c4b8ae2514ec8b" + }, + { + "submissionId": "sub_ik_a38b1133b7c7c65bf8f07814d23bcbcb", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_a38b1133b7c7c65bf8f07814d23bcbcb" + }, + { + "submissionId": "sub_ik_af9cccb1e95729f92494406514517cce", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_af9cccb1e95729f92494406514517cce" + }, + { + "submissionId": "sub_ik_d797b4c6053e5d0a144ec1547a22e0ca", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_d797b4c6053e5d0a144ec1547a22e0ca" + }, + { + "submissionId": "sub_ik_b2f96fd62b80a6dcf980329ad61f70e9", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_b2f96fd62b80a6dcf980329ad61f70e9" + } + ], + "incarnation": "inc_01M1NV5WZD6VQ76Z7C3JJRFFBA" +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/latest-workpiece.md b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/latest-workpiece.md new file mode 100644 index 00000000000..4c89055d2b5 --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/latest-workpiece.md @@ -0,0 +1,45 @@ +# Final inspection and dispatch batch path + +## Purpose and posture +Model the narrow batch path from final inspection to dispatch readiness with single-crew resource constraint. This revision incorporates the evidence-backed correction of the missing crew-availability input arc. + +## Operational account +- A batch that is ready enters final inspection. +- Final inspection requires and reserves the sole available dispatch crew. +- Sign-off releases that crew and marks the batch ready for dispatch. + +## Quantity and resource policy +Exactly one dispatch crew is available in this system. Starting final inspection consumes that one available crew token; sign-off returns it. At most one batch can be under final inspection at a time because the sole crew is needed and cannot be in two places simultaneously. + +## Net structure (model-produced) +**Places:** +- `Batch ready`: batches waiting for inspection +- `Under final inspection`: batches currently being inspected +- `Ready for dispatch`: batches completed and awaiting dispatch +- `Dispatch crew available`: availability token for the single crew (capacity 1) + +**Transitions:** +- `Start final inspection`: consumes 1 batch token from `Batch ready` AND 1 crew token from `Dispatch crew available`; produces 1 token in `Under final inspection` +- `Sign-off`: consumes 1 token from `Under final inspection`; produces 1 token each in `Ready for dispatch` and `Dispatch crew available` (returns the crew) + +**Arcs:** +- `Batch ready` → `Start final inspection` (standard, weight 1) — original +- `Dispatch crew available` → `Start final inspection` (standard, weight 1) — added in revision +- `Start final inspection` → `Under final inspection` (standard, weight 1) — original +- `Under final inspection` → `Sign-off` (standard, weight 1) — original +- `Sign-off` → `Ready for dispatch` (standard, weight 1) — original +- `Sign-off` → `Dispatch crew available` (standard, weight 1) — original + +## Explicit unknowns +- Inspection and sign-off timing and duration +- Failure modes, defect outcomes, and recovery behavior +- Initial batch population and crew availability state +- Repeat or recycling scenarios + +## Revision record +**Revision 0** (test-authored): Prepared fixture identified missing standard weight-1 input arc from `Dispatch crew available` to `Start final inspection`. + +**Revision 1** (model-produced): Arc added via Petrinaut `addArc` tool and verified in net definition. Crew-availability constraint now enforced at net semantics level. + +## Claim boundary +This revision is model-produced evidence of arc correction applied to the test-authored source. It does not establish timing behavior, failure recovery, execution performance, or projection beyond this narrow batch-crew-inspection path. diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/prepared-workpiece.md b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/prepared-workpiece.md new file mode 100644 index 00000000000..fa01def8c11 --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/prepared-workpiece.md @@ -0,0 +1,21 @@ +# Final inspection and dispatch workpiece + +## Purpose and posture +Maintain the narrow batch path from final inspection to dispatch readiness and test one evidence-backed correction against the live Petrinaut document. + +## Operational account +- A batch that is ready enters final inspection. +- Final inspection reserves the sole available dispatch crew. +- Sign-off releases that crew and makes the batch ready for dispatch. + +## Quantity and resource policy +Exactly one dispatch crew is available in this fixture. Starting final inspection consumes that one available crew; sign-off returns it. + +## Current Petrinaut correspondence +The prepared non-empty net contains the batch path and the crew return from sign-off. It deliberately lacks the standard weight-1 input arc from `Dispatch crew available` to `Start final inspection`. + +## Explicit unknowns +Inspection and sign-off timing, failure modes, and recovery behavior remain unresolved. + +## Claim boundary +This prepared revision is test-authored diagnostic material. It is not model-produced evidence and does not establish capture provenance, behavioral execution, or broad projection quality. diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/route-evidence.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/route-evidence.json new file mode 100644 index 00000000000..85b3c90305b --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/route-evidence.json @@ -0,0 +1,6 @@ +{ + "fixtureUrl": "http://127.0.0.1:4915/?brunch-fixture=crew-reservation-v1", + "mountedRoute": "/agents/chat/", + "sameMountedInstanceAcrossTabs": true, + "browserErrors": [] +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/run-metadata.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/run-metadata.json new file mode 100644 index 00000000000..28615dac8f1 --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/run-metadata.json @@ -0,0 +1,8 @@ +{ + "implementationCommit": "8ef9cd967d", + "prompt": "Confirmed: final inspection uses the single dispatch crew and sign-off releases it; timing, failure, and recovery remain unknown. Read the live Petrinaut definition, add the missing standard weight-1 input arc from Dispatch crew available to Start final inspection, verify it, and emit the full revised runbook-ir workpiece. In that revised workpiece, identify the revision itself as model-produced from test-authored revision zero; do not call the revised workpiece test-authored.", + "followup": "From the resumed workpiece, list the unresolved timing, failure, and recovery questions. Do not change the Petrinaut net.", + "beforeOffset": "0000000000000000_0000000000000032", + "afterOffset": "0000000000000000_0000000000000092", + "tabBOffset": "0000000000000000_0000000000000110" +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/screenshot-tab-a-after.png b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/screenshot-tab-a-after.png new file mode 100644 index 00000000000..4e06ff085e7 Binary files /dev/null and b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/screenshot-tab-a-after.png differ diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/screenshot-tab-a-before.png b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/screenshot-tab-a-before.png new file mode 100644 index 00000000000..082e16eb0be Binary files /dev/null and b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/screenshot-tab-a-before.png differ diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/screenshot-tab-b-after.png b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/screenshot-tab-b-after.png new file mode 100644 index 00000000000..6e3f3f75b88 Binary files /dev/null and b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/screenshot-tab-b-after.png differ diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/settled-manifest-after.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/settled-manifest-after.json new file mode 100644 index 00000000000..435e88a58ad --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/settled-manifest-after.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "fixtureId": "crew-reservation-v1", + "revision": 1, + "settledAt": "2026-09-04T09:15:22.339Z", + "conversation": { + "logicalId": "mission-6-crew-reservation-conversation-v1", + "canonicalId": "conv_01M1NV5WZETMYEGGMFXNYDSTRS", + "offset": "0000000000000000_0000000000000092" + }, + "latestWorkpiece": { + "authorship": "model-produced", + "contentSha256": "1d250465b7c9ee930c21581c2b6715ad01915e50e5a66b4348ea7970eac9f78c", + "sourceKind": "assistant", + "sourceMessageId": "entry_01M1NV6M4SBXB2WA46SM1B8QM3", + "sourceMessageSha256": "56a6b415da3c596af168165c1f95eb2c586a758ab9f37dc63dffcd0869a84815", + "sourceSubmissionId": "sub_ik_d797b4c6053e5d0a144ec1547a22e0ca" + }, + "document": { + "id": "mission-6-crew-reservation-document-v1", + "sha256": "3c47961d02296c00131644d1aea0dac16a017f470a66aea919fcf324a2bc9e37", + "targetArc": "present" + }, + "manifestId": "d16b26c12d81a2f961d428d8062fce2a7755c3a6342715f8c85b054546d330b7" +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/settled-manifest-before.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/settled-manifest-before.json new file mode 100644 index 00000000000..af88d6180b3 --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/settled-manifest-before.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "fixtureId": "crew-reservation-v1", + "revision": 0, + "settledAt": "2026-09-04T09:14:55.934Z", + "conversation": { + "logicalId": "mission-6-crew-reservation-conversation-v1", + "canonicalId": "conv_01M1NV5WZETMYEGGMFXNYDSTRS", + "offset": "0000000000000000_0000000000000032" + }, + "latestWorkpiece": { + "authorship": "test-authored", + "contentSha256": "1cc7a1b5d961f9f6327b458cf8292703ced5627fe26ee3f7b878f6375e51501a", + "sourceKind": "prepared-signal", + "sourceMessageId": "entry_direct_c3ViX2lrXzI4ODYzMWVhYWQ3OThjYzY5MDcwZTMzMTNlOTUxYmNi", + "sourceMessageSha256": "bfff373d94057ad6715fafff05a58db24c6c47c583497bfb965cb37ce3e5879e", + "sourceSubmissionId": "sub_ik_288631eaad798cc69070e3313e951bcb" + }, + "document": { + "id": "mission-6-crew-reservation-document-v1", + "sha256": "8dfa723b8dabadad790d2552de8e191e4227b07fe6c4d4e9d8e2d365e6ec4abd", + "targetArc": "absent" + }, + "manifestId": "45662458cb5d01dd3ecb4daeefed12f1fd699fa972cc6101565da10330f421f5" +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/settled-manifest-tab-b.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/settled-manifest-tab-b.json new file mode 100644 index 00000000000..435e88a58ad --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/settled-manifest-tab-b.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "fixtureId": "crew-reservation-v1", + "revision": 1, + "settledAt": "2026-09-04T09:15:22.339Z", + "conversation": { + "logicalId": "mission-6-crew-reservation-conversation-v1", + "canonicalId": "conv_01M1NV5WZETMYEGGMFXNYDSTRS", + "offset": "0000000000000000_0000000000000092" + }, + "latestWorkpiece": { + "authorship": "model-produced", + "contentSha256": "1d250465b7c9ee930c21581c2b6715ad01915e50e5a66b4348ea7970eac9f78c", + "sourceKind": "assistant", + "sourceMessageId": "entry_01M1NV6M4SBXB2WA46SM1B8QM3", + "sourceMessageSha256": "56a6b415da3c596af168165c1f95eb2c586a758ab9f37dc63dffcd0869a84815", + "sourceSubmissionId": "sub_ik_d797b4c6053e5d0a144ec1547a22e0ca" + }, + "document": { + "id": "mission-6-crew-reservation-document-v1", + "sha256": "3c47961d02296c00131644d1aea0dac16a017f470a66aea919fcf324a2bc9e37", + "targetArc": "present" + }, + "manifestId": "d16b26c12d81a2f961d428d8062fce2a7755c3a6342715f8c85b054546d330b7" +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/tab-b-correlation.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/tab-b-correlation.json new file mode 100644 index 00000000000..ade0492b67c --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/tab-b-correlation.json @@ -0,0 +1,21 @@ +{ + "sameCanonicalConversation": true, + "sameManifestId": true, + "sameDocumentHash": true, + "sameWorkpieceHash": true, + "preparedSourceCountAfter": 1, + "preparedSourceCountTabB": 1, + "addArcCallCountAfter": 1, + "addArcCallCountTabB": 1, + "newMessageIds": [ + "entry_direct_c3ViX2lrX2IyZjk2ZmQ2MmI4MGE2ZGNmOTgwMzI5YWQ2MWY3MGU5", + "entry_01M1NV73Z110CY393GEB8T02SH" + ], + "newSettlements": [ + { + "submissionId": "sub_ik_b2f96fd62b80a6dcf980329ad61f70e9", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_b2f96fd62b80a6dcf980329ad61f70e9" + } + ] +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/witness.md b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/witness.md new file mode 100644 index 00000000000..d9eed05f6f4 --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04-r2/witness.md @@ -0,0 +1,48 @@ +# FE-1575 corrected outer browser witness — 2026-09-04 + +## Scope + +This is the retained outer mechanical witness for Mission 6 at implementation commit `8ef9cd967d`. It supersedes the first witness for acceptance because that run's model-produced revision incorrectly described itself as test-authored. This run used a fresh Playwright browser context, the stable `crew-reservation-v1` fixture route, the local Brunch Flue mount, and a real configured provider credential. Credentials, authorization headers, the browser principal, the Flue instance route component, and provider request payloads are not retained. + +The provider serialized the `addArc` weight as `"1"`. The witnessed build normalized only that observed finite numeric string at the Petrinaut tool boundary before canonical validation and browser execution. [`call-result-correlation.json`](call-result-correlation.json) retains both the raw provider input and parsed canonical input; the resulting definition retains numeric weight `1`. + +## Protocol and result + +1. Started the production dev processes underlying `yarn dev:brunch` after loading `.env.local` without printing it. The root wrapper's prerequisite build could not run in this sandbox because `tsx` was denied its `/tmp` IPC socket; all affected package builds had already passed, so the Brunch server and Petrinaut panel processes were started directly with their normal Vite entrypoints. +2. Opened `http://127.0.0.1:4915/?brunch-fixture=crew-reservation-v1` in a fresh browser context and waited for settled revision zero. +3. Retained the before Flue snapshot, canonical definition, runtime manifest, and Tab A screenshot. +4. Submitted one confirmation/construction turn instructing Brunch to preserve timing/failure/recovery unknowns, add the missing standard weight-1 input arc, and identify the new assistant workpiece as model-produced from test-authored revision zero. +5. Observed one `addArc` call and one unique correlated successful client-tool result, `toolu_01BQukCZTAhJ64VNE7oC1CWG`. Flue history materialized that result in two cumulative client-tool-result signal deliveries as later read verification completed; the repeated call ID remained one logical result and the browser retained exactly one arc. +6. Verified that the only semantic definition delta was one standard weight-1 input arc from `dispatch-crew-available` to `start-final-inspection`. +7. Observed runtime manifest revision 1 selecting the model-produced workpiece and changed document, with target arc `present`. +8. Opened Tab B in the same browser context. It selected the same manifest, workpiece hash, document hash, and canonical conversation, with exactly one prepared source and one `addArc` call. +9. Submitted a non-mutating follow-up in Tab B asking for the unresolved timing, failure, and recovery questions without changing the net. +10. Observed completed submission `sub_ik_b2f96fd62b80a6dcf980329ad61f70e9` and correlated response `entry_01M1NV73Z110CY393GEB8T02SH`. The document and settled manifest remained unchanged. + +## Retained identities and invariants + +- Canonical conversation: `conv_01M1NV5WZETMYEGGMFXNYDSTRS` +- Before/after/Tab-B offsets: `0000000000000000_0000000000000032`, `0000000000000000_0000000000000092`, `0000000000000000_0000000000000110` +- Settled manifest revision: `1` +- Settled manifest ID: `d16b26c12d81a2f961d428d8062fce2a7755c3a6342715f8c85b054546d330b7` +- Document SHA-256: `3c47961d02296c00131644d1aea0dac16a017f470a66aea919fcf324a2bc9e37` +- Workpiece SHA-256: `1d250465b7c9ee930c21581c2b6715ad01915e50e5a66b4348ea7970eac9f78c` +- Prepared source count after Tab B: `1` +- `addArc` call count after Tab B: `1` +- Unique successful `addArc` result count: `1` across `2` cumulative signal deliveries +- Tab B follow-up outcome: `completed` +- The selected model-produced workpiece explicitly distinguishes itself from test-authored revision zero. +- `definition-after.json` and `definition-tab-b.json` have the same SHA-256. +- `settled-manifest-after.json` and `settled-manifest-tab-b.json` have the same SHA-256. + +## Artifacts + +- Before state: [Flue](flue-snapshot-before.json), [definition](definition-before.json), [manifest](settled-manifest-before.json), [screenshot](screenshot-tab-a-before.png) +- Settled Tab A state: [Flue](flue-snapshot-after.json), [definition](definition-after.json), [manifest](settled-manifest-after.json), [call/result correlation with parsed input](call-result-correlation.json), [screenshot](screenshot-tab-a-after.png) +- Tab B continuation: [Flue](flue-snapshot-tab-b.json), [definition](definition-tab-b.json), [manifest](settled-manifest-tab-b.json), [correlation](tab-b-correlation.json), [screenshot](screenshot-tab-b-after.png) +- Semantic inputs: [prepared workpiece](prepared-workpiece.md), [latest model-produced workpiece](latest-workpiece.md), [cold-reader records](cold-reader-records.json) +- Redacted route observation: [route evidence](route-evidence.json) +- Run metadata: [run-metadata.json](run-metadata.json) +- Integrity: [SHA256SUMS](SHA256SUMS) + +This witness proves the bounded browser protocol above. It does not establish capture provenance, timing behavior, failure/recovery behavior, simulation validity, or broad automatic projection quality. Cold-reader adjudication and the product-manager demo remain human-owned gates. diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/SHA256SUMS b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/SHA256SUMS new file mode 100644 index 00000000000..1d9c9bd912c --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/SHA256SUMS @@ -0,0 +1,20 @@ +6c3a075636a5df23667c58c4ca005cc7da6b1927e13bfc54939f5e4416e5059f call-result-correlation.json +1d37d87803edd25905098bc957a1557ea7250a243b689c62c1d58ebc463135d0 cold-reader-gate.md +e5393930c436fac7bcb5f30b610ca4d28174a027f0b3fe8eab0045076379ce29 cold-reader-records.json +c17ae80cc22939f0af6c2675f6bcc20b025b17a5b7bfb9e8029813bcef9ad736 definition-after.json +0ec2b0c9bb82787602c359b41e6c1663001f8e42425ad2ab7f84ac019944766b definition-before.json +c17ae80cc22939f0af6c2675f6bcc20b025b17a5b7bfb9e8029813bcef9ad736 definition-tab-b.json +4fe368ef206e71e63173c1876ac9ca54e8b0d0971066be74dae5f78d4b612b0a flue-snapshot-after.json +ea20487150d8a329f4c2a6e0ac32e22de39d0d655646581ad2c56768a3591d1a flue-snapshot-before.json +7aaa993b395bc4ac84625fb2f472645ba13428bc39faac1f0f4ba948bfb3d9f5 flue-snapshot-tab-b.json +a384aa803085d3ff350a347e18080503a78305f87eb5d91eec1ad1141b941eb3 latest-workpiece.md +17f99f53f8cf93285a7344f5e9687d94e5f59011e6895ce11a9722fde2e77511 prepared-workpiece.md +cfb79c8f52fc8f1758b1a61f67e520c67a31d2f19f561017899d0724b776100a route-evidence.json +a9bd4a4a18f52ebd5c0f0e9c7c5d80b1c37e85823bfa6a03bd9c1d329b4b4267 screenshot-tab-a-after.png +eaf49081a6ab7acba746f337fdb85e3829d473818dc7c96f83a40acd3cb6f383 screenshot-tab-a-before.png +d39ea91fc69fa049af169c6ebcfe1a09c5b085d350d5eb6f9020c31095549943 screenshot-tab-b-after.png +caf83c79b2526587794faf4ed8c59c0d3f4bb9ed88e3d547679efe429197797c settled-manifest-after.json +5b302df73ccd6ac0cec777c9e4ade15ac7318dca7023ed79e3bceaa0604c3ec0 settled-manifest-before.json +caf83c79b2526587794faf4ed8c59c0d3f4bb9ed88e3d547679efe429197797c settled-manifest-tab-b.json +08afa5150f33b8b19641118524880eca9b13f4e7a94a55eca7b76775cfa30770 tab-b-correlation.json +935fa33c714b6441622cb1c50f2ff5e7f8e56c434a5365f5857c2eb6a1ffd830 witness.md diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/call-result-correlation.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/call-result-correlation.json new file mode 100644 index 00000000000..bdc346ad17c --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/call-result-correlation.json @@ -0,0 +1,51 @@ +{ + "calls": [ + { + "messageId": "entry_01M1NQGD1NRR36CBAX78EA666J", + "submissionId": "sub_ik_dd2eb7be21980998b89ae9a9da085fb2", + "toolCallId": "toolu_01KLHzRE7gbPbFfPaXe3RTry", + "state": "output-available", + "input": { + "transitionId": "start-final-inspection", + "arcDirection": "input", + "placeId": "dispatch-crew-available", + "weight": "1", + "type": "standard" + } + } + ], + "results": [ + { + "toolCallId": "toolu_01KLHzRE7gbPbFfPaXe3RTry", + "toolName": "addArc", + "output": { + "title": "Added input arc", + "detail": "Dispatch crew available <-> Start final inspection", + "target": { + "kind": "selection", + "item": { + "type": "arc", + "id": "$A_place:dispatch-crew-available___start-final-inspection" + } + }, + "applied": true + } + }, + { + "toolCallId": "toolu_01KLHzRE7gbPbFfPaXe3RTry", + "toolName": "addArc", + "output": { + "title": "Added input arc", + "detail": "Dispatch crew available <-> Start final inspection", + "target": { + "kind": "selection", + "item": { + "type": "arc", + "id": "$A_place:dispatch-crew-available___start-final-inspection" + } + }, + "applied": true + } + } + ] +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/cold-reader-gate.md b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/cold-reader-gate.md new file mode 100644 index 00000000000..1d00d3e13f8 --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/cold-reader-gate.md @@ -0,0 +1,36 @@ +# Mission 6 cold-reader gate + +Reviewer qualification: a human who did not watch the implementation or outer +witness. Do not give the reviewer `witness.md`, screenshots, implementation +files, or this mission's expected conclusions. + +Give the reviewer only: + +- [prepared-workpiece.md](prepared-workpiece.md) +- [latest-workpiece.md](latest-workpiece.md) +- [cold-reader-records.json](cold-reader-records.json) + +Ask for a written response to these questions: + +1. Who authored each workpiece revision, and what in the supplied Flue records + establishes that attribution? +2. What exact dispatch-crew quantity or resource policy is recorded? +3. What operation reserves the crew, and what operation returns it? +4. List every unresolved timing, failure, and recovery point. +5. What behavioral execution or operational-provenance claims, if any, can be + made from these materials alone? + +Acceptance requires the human response to: + +- attribute revision zero to test preparation, not the model; +- attribute the later assistant record to model production; +- recover the exactly-one-crew policy and the intended reservation/return; +- preserve every timing, failure, and recovery unknown; and +- avoid inferring behavioral execution or capture provenance. + +Record the reviewer's identity, date, verbatim response, and explicit +accept/reject decision below only after the review occurs. + +## Human verdict + +Pending. diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/cold-reader-records.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/cold-reader-records.json new file mode 100644 index 00000000000..a5efc6ff2eb --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/cold-reader-records.json @@ -0,0 +1,39 @@ +{ + "preparedRecord": { + "id": "entry_direct_c3ViX2lrX2YyNDkzOWJlYmI4NDFmZmUxNDY5YTdmM2E0YTA2OTE1", + "role": "system", + "purpose": "dispatch", + "display": "diagnostic", + "submissionId": "sub_ik_f24939bebb841ffe1469a7f3a4a06915", + "signal": { + "tagName": "prepared-fixture", + "attributes": { + "fixtureId": "crew-reservation-v1", + "authorship": "test-authored", + "claimBoundary": "prepared-not-model-produced" + } + }, + "parts": [ + { + "type": "text", + "text": "Fixture authorship: test-authored preparation for Mission 6.\nNon-claims: not a Mission 4 candidate, not model-produced evidence, not capture-backed provenance, and not proof of automatic full-net projection.\n\n```runbook-ir\n# Final inspection and dispatch workpiece\n\n## Purpose and posture\nMaintain the narrow batch path from final inspection to dispatch readiness and test one evidence-backed correction against the live Petrinaut document.\n\n## Operational account\n- A batch that is ready enters final inspection.\n- Final inspection reserves the sole available dispatch crew.\n- Sign-off releases that crew and makes the batch ready for dispatch.\n\n## Quantity and resource policy\nExactly one dispatch crew is available in this fixture. Starting final inspection consumes that one available crew; sign-off returns it.\n\n## Current Petrinaut correspondence\nThe prepared non-empty net contains the batch path and the crew return from sign-off. It deliberately lacks the standard weight-1 input arc from `Dispatch crew available` to `Start final inspection`.\n\n## Explicit unknowns\nInspection and sign-off timing, failure modes, and recovery behavior remain unresolved.\n\n## Claim boundary\nThis prepared revision is test-authored diagnostic material. It is not model-produced evidence and does not establish capture provenance, behavioral execution, or broad projection quality.\n```", + "state": "done" + } + ] + }, + "revisedRecord": { + "id": "entry_01M1NQGRAKWG69MWYMPYTCFXCZ", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_6dbc187e4e72793b7539c02387d04715", + "turnId": "turn_01M1NQGQH38A3MMGGEWKN4VCDP", + "parts": [ + { + "type": "text", + "text": "**Verification successful.** The `start-final-inspection` transition now contains the corrected input arcs:\n```json\n\"inputArcs\":[\n {\"placeId\":\"batch-ready\",\"weight\":1,\"type\":\"standard\"},\n {\"type\":\"standard\",\"placeId\":\"dispatch-crew-available\",\"weight\":1}\n]\n```\n\nThe resource constraint is now satisfied: firing `start-final-inspection` requires both a ready batch AND an available crew token. The `sign-off` transition releases that crew back to `dispatch-crew-available`.\n\n---\n\n## Revised workpiece (runbook-ir)\n\n```runbook-ir\n# Final inspection and dispatch workpiece — Revised\n\n## Purpose and posture\nModel the batch path from final inspection to dispatch readiness with explicit crew resource management. This revision adds the missing crew-consumption arc to enforce the resource constraint.\n\n## Operational account\n- A batch that is ready enters final inspection.\n- Starting final inspection requires both a ready batch AND the sole available dispatch crew.\n- Starting final inspection consumes that crew token.\n- Sign-off releases that crew and makes the batch ready for dispatch.\n\n## Quantity and resource policy\nExactly one dispatch crew is available. Starting final inspection consumes that one available crew; sign-off returns it. No transition fires without sufficient tokens in all input places.\n\n## Petrinaut correspondence\nThe net contains four places:\n- `batch-ready`: batches awaiting final inspection\n- `under-final-inspection`: batches currently in inspection\n- `ready-for-dispatch`: batches cleared for dispatch\n- `dispatch-crew-available`: crew availability (1 token when free)\n\nTransitions:\n- `start-final-inspection`: requires 1 token from batch-ready AND 1 token from dispatch-crew-available; produces 1 token to under-final-inspection\n- `sign-off`: requires 1 token from under-final-inspection; produces 1 token to ready-for-dispatch AND 1 token to dispatch-crew-available\n\nThe missing standard weight-1 input arc from `dispatch-crew-available` to `start-final-inspection` has been **added and verified** in the live Petrinaut definition.\n\n## Explicit unknowns\n- Inspection timing: duration, stochasticity, or determinism remain unresolved\n- Sign-off timing: duration, stochasticity, or determinism remain unresolved\n- Failure modes: whether inspection can fail, halt, or reject a batch\n- Recovery behavior: how failures or rejections affect batch state or crew availability\n\n## Claim boundary\nThis workpiece and the corrected net establish only:\n- The crew-consumption constraint at start of inspection\n- The crew-release at sign-off completion\n- Token flow from ready batch through inspection to dispatch readiness\n\nThis is test-authored diagnostic material for the narrow final-inspection–to-dispatch path. It does not establish capture provenance, behavioral execution, failure handling, full process projection, or integration with upstream or downstream operations.\n```\n\nThe correction is complete and verified in the live Petrinaut definition.", + "state": "done" + } + ] + } +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/definition-after.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/definition-after.json new file mode 100644 index 00000000000..c92ed16c454 --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/definition-after.json @@ -0,0 +1,98 @@ +{ + "places": [ + { + "id": "batch-ready", + "name": "Batch ready", + "colorId": null, + "dynamicsEnabled": false, + "differentialEquationId": null, + "x": 80, + "y": 100 + }, + { + "id": "under-final-inspection", + "name": "Under final inspection", + "colorId": null, + "dynamicsEnabled": false, + "differentialEquationId": null, + "x": 420, + "y": 100 + }, + { + "id": "ready-for-dispatch", + "name": "Ready for dispatch", + "colorId": null, + "dynamicsEnabled": false, + "differentialEquationId": null, + "x": 760, + "y": 100 + }, + { + "id": "dispatch-crew-available", + "name": "Dispatch crew available", + "colorId": null, + "dynamicsEnabled": false, + "differentialEquationId": null, + "x": 420, + "y": 360 + } + ], + "transitions": [ + { + "id": "start-final-inspection", + "name": "Start final inspection", + "inputArcs": [ + { + "placeId": "batch-ready", + "weight": 1, + "type": "standard" + }, + { + "type": "standard", + "placeId": "dispatch-crew-available", + "weight": 1 + } + ], + "outputArcs": [ + { + "placeId": "under-final-inspection", + "weight": 1 + } + ], + "lambdaType": "predicate", + "lambdaCode": "", + "transitionKernelCode": "", + "x": 250, + "y": 100 + }, + { + "id": "sign-off", + "name": "Sign-off", + "inputArcs": [ + { + "placeId": "under-final-inspection", + "weight": 1, + "type": "standard" + } + ], + "outputArcs": [ + { + "placeId": "ready-for-dispatch", + "weight": 1 + }, + { + "placeId": "dispatch-crew-available", + "weight": 1 + } + ], + "lambdaType": "predicate", + "lambdaCode": "", + "transitionKernelCode": "", + "x": 590, + "y": 100 + } + ], + "types": [], + "differentialEquations": [], + "parameters": [] +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/definition-before.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/definition-before.json new file mode 100644 index 00000000000..5803556bfe8 --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/definition-before.json @@ -0,0 +1,93 @@ +{ + "places": [ + { + "id": "batch-ready", + "name": "Batch ready", + "colorId": null, + "dynamicsEnabled": false, + "differentialEquationId": null, + "x": 80, + "y": 100 + }, + { + "id": "under-final-inspection", + "name": "Under final inspection", + "colorId": null, + "dynamicsEnabled": false, + "differentialEquationId": null, + "x": 420, + "y": 100 + }, + { + "id": "ready-for-dispatch", + "name": "Ready for dispatch", + "colorId": null, + "dynamicsEnabled": false, + "differentialEquationId": null, + "x": 760, + "y": 100 + }, + { + "id": "dispatch-crew-available", + "name": "Dispatch crew available", + "colorId": null, + "dynamicsEnabled": false, + "differentialEquationId": null, + "x": 420, + "y": 360 + } + ], + "transitions": [ + { + "id": "start-final-inspection", + "name": "Start final inspection", + "inputArcs": [ + { + "placeId": "batch-ready", + "type": "standard", + "weight": 1 + } + ], + "outputArcs": [ + { + "placeId": "under-final-inspection", + "weight": 1 + } + ], + "lambdaType": "predicate", + "lambdaCode": "", + "transitionKernelCode": "", + "x": 250, + "y": 100 + }, + { + "id": "sign-off", + "name": "Sign-off", + "inputArcs": [ + { + "placeId": "under-final-inspection", + "type": "standard", + "weight": 1 + } + ], + "outputArcs": [ + { + "placeId": "ready-for-dispatch", + "weight": 1 + }, + { + "placeId": "dispatch-crew-available", + "weight": 1 + } + ], + "lambdaType": "predicate", + "lambdaCode": "", + "transitionKernelCode": "", + "x": 590, + "y": 100 + } + ], + "types": [], + "parameters": [], + "differentialEquations": [] +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/definition-tab-b.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/definition-tab-b.json new file mode 100644 index 00000000000..c92ed16c454 --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/definition-tab-b.json @@ -0,0 +1,98 @@ +{ + "places": [ + { + "id": "batch-ready", + "name": "Batch ready", + "colorId": null, + "dynamicsEnabled": false, + "differentialEquationId": null, + "x": 80, + "y": 100 + }, + { + "id": "under-final-inspection", + "name": "Under final inspection", + "colorId": null, + "dynamicsEnabled": false, + "differentialEquationId": null, + "x": 420, + "y": 100 + }, + { + "id": "ready-for-dispatch", + "name": "Ready for dispatch", + "colorId": null, + "dynamicsEnabled": false, + "differentialEquationId": null, + "x": 760, + "y": 100 + }, + { + "id": "dispatch-crew-available", + "name": "Dispatch crew available", + "colorId": null, + "dynamicsEnabled": false, + "differentialEquationId": null, + "x": 420, + "y": 360 + } + ], + "transitions": [ + { + "id": "start-final-inspection", + "name": "Start final inspection", + "inputArcs": [ + { + "placeId": "batch-ready", + "weight": 1, + "type": "standard" + }, + { + "type": "standard", + "placeId": "dispatch-crew-available", + "weight": 1 + } + ], + "outputArcs": [ + { + "placeId": "under-final-inspection", + "weight": 1 + } + ], + "lambdaType": "predicate", + "lambdaCode": "", + "transitionKernelCode": "", + "x": 250, + "y": 100 + }, + { + "id": "sign-off", + "name": "Sign-off", + "inputArcs": [ + { + "placeId": "under-final-inspection", + "weight": 1, + "type": "standard" + } + ], + "outputArcs": [ + { + "placeId": "ready-for-dispatch", + "weight": 1 + }, + { + "placeId": "dispatch-crew-available", + "weight": 1 + } + ], + "lambdaType": "predicate", + "lambdaCode": "", + "transitionKernelCode": "", + "x": 590, + "y": 100 + } + ], + "types": [], + "differentialEquations": [], + "parameters": [] +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/flue-snapshot-after.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/flue-snapshot-after.json new file mode 100644 index 00000000000..ba4e176112e --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/flue-snapshot-after.json @@ -0,0 +1,321 @@ +{ + "v": 1, + "conversationId": "conv_01M1NQEXM3CAPPTXM33ZE1YSRG", + "offset": "0000000000000000_0000000000000108", + "messages": [ + { + "id": "entry_direct_c3ViX2lrX2YyNDkzOWJlYmI4NDFmZmUxNDY5YTdmM2E0YTA2OTE1", + "role": "system", + "purpose": "dispatch", + "display": "diagnostic", + "submissionId": "sub_ik_f24939bebb841ffe1469a7f3a4a06915", + "signal": { + "tagName": "prepared-fixture", + "attributes": { + "fixtureId": "crew-reservation-v1", + "authorship": "test-authored", + "claimBoundary": "prepared-not-model-produced" + } + }, + "parts": [ + { + "type": "text", + "text": "Fixture authorship: test-authored preparation for Mission 6.\nNon-claims: not a Mission 4 candidate, not model-produced evidence, not capture-backed provenance, and not proof of automatic full-net projection.\n\n```runbook-ir\n# Final inspection and dispatch workpiece\n\n## Purpose and posture\nMaintain the narrow batch path from final inspection to dispatch readiness and test one evidence-backed correction against the live Petrinaut document.\n\n## Operational account\n- A batch that is ready enters final inspection.\n- Final inspection reserves the sole available dispatch crew.\n- Sign-off releases that crew and makes the batch ready for dispatch.\n\n## Quantity and resource policy\nExactly one dispatch crew is available in this fixture. Starting final inspection consumes that one available crew; sign-off returns it.\n\n## Current Petrinaut correspondence\nThe prepared non-empty net contains the batch path and the crew return from sign-off. It deliberately lacks the standard weight-1 input arc from `Dispatch crew available` to `Start final inspection`.\n\n## Explicit unknowns\nInspection and sign-off timing, failure modes, and recovery behavior remain unresolved.\n\n## Claim boundary\nThis prepared revision is test-authored diagnostic material. It is not model-produced evidence and does not establish capture provenance, behavioral execution, or broad projection quality.\n```", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NQEYF9W60J2JVDYB2BJXK1", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_f24939bebb841ffe1469a7f3a4a06915", + "turnId": "turn_01M1NQEXM7MHJ94PF52Q54VAY4", + "parts": [ + { + "type": "reasoning", + "text": "The user has provided a prepared-fixture for SDCPN modelling work. This is test-authored material that is:\n- Part of a Mission 6 exercise\n- Focused on a narrow batch path from final inspection to dispatch readiness\n- Aimed at testing one evidence-backed correction against the live Petrinaut document\n- Not model-produced evidence, not a capture-backed provenance, not proof of automatic full-net projection\n\nThe operational account describes:\n1. A batch that is ready enters final inspection\n2. Final inspection reserves the sole available dispatch crew\n3. Sign-off releases that crew and makes the batch ready for dispatch\n\nThe key policy: Exactly one dispatch crew is available. Starting final inspection consumes it; sign-off returns it.\n\nThe current state: The prepared non-empty net contains the batch path and the crew return from sign-off, but deliberately LACKS the standard weight-1 input arc from \"Dispatch crew available\" to \"Start final inspection\".\n\nThe purpose is to test one evidence-backed correction against the live Petrinaut document.\n\nMy role here is:\n1. First, activate the sdcpn-modelling skill since this is a process-modelling task\n2. Understand the current state of the Petrinaut net\n3. Help verify the operational account against the net\n4. Work with the user to identify and potentially implement the missing arc correction\n5. Maintain the workpiece with clear documentation of what is known, what is missing, and what has been tested\n\nLet me start by activating the skill and getting the current net state.", + "state": "done" + }, + { + "type": "text", + "text": "I'll help you with this prepared-fixture exercise. Let me start by activating the SDCPN modelling skill and then retrieving the current state of the Petrinaut net to understand what we're working with.", + "state": "done" + }, + { + "type": "dynamic-tool", + "toolName": "activate_skill", + "toolCallId": "toolu_01WQaZveys8joHtboYvGQiuA", + "state": "output-available", + "input": { + "name": "sdcpn-modelling" + }, + "output": "Run the skill named \"sdcpn-modelling\".\n\n\n# Capability-aware lifecycle\n\nUse one conceptual lifecycle: orient, elicit or revise, maintain the workpiece, construct when supported, check, and deliver. The current conversation may expose only one branch of that lifecycle. Do not claim that an unavailable transition occurred.\n\n## Select the runtime branch\n\n### Interactive elicitation or revision\n\nInterview in the person's operational vocabulary. Activate the `elicitation` skill and read `references/profile.md` before substantive questions or revision. Read `templates/workpiece.md` when creating or materially revising the shared workpiece. Construct only when the mounted capabilities actually permit construction in this conversation.\n\n### Construct-only execution\n\nUse the supplied workpiece as the complete modelling input. Do not interview. Read `references/pn-construction.md` and `references/checks.md`, then use the mounted construction tools. If a consequential workpiece gap prevents faithful construction, report the gap and the smallest question a later interactive elicitation must answer; do not ask it or invent an answer in this conversation.\n\n## Procedure\n\n### Orient\n\nEstablish enough purpose and context to select one focused next action: the intended question or decision, audience, boundary, horizon, accuracy need, and available time. Orientation need not settle every concern before elicitation begins.\n\n### Elicit or revise\n\nFor a new account, follow one concrete case and re-evaluate the active gap after each useful answer. For an existing account, first locate the disputed or changed material and its consequence for the objective. Use the `elicitation` skill's universal guidance and `references/profile.md` for detailed operations and coverage; do not turn their register order into question order.\n\n### Maintain the workpiece\n\nTreat the workpiece as the recoverable account construction will consume. Update it after a useful stretch rather than waiting until the end. Preserve unrelated material unless new evidence affects it.\n\nWhenever the workpiece changes substantially, emit the full current document in a fenced block whose language tag is exactly `runbook-ir`. Emit it again before construction and before workpiece-only delivery. A delta or prose promise is not a recoverable workpiece.\n\n### Construct\n\nConstruct only from the current workpiece. Read `references/pn-construction.md` and `references/checks.md` before beginning. Use mounted Petrinaut tools for every net change and inspect the resulting definition rather than emitting free-form net JSON. If the required tools are absent, limit the result to the workpiece and construction-ready notes.\n\nConstruction may infer a representation from recorded operational meaning; it may not invent operational facts. Record construction inferences, approximations, defaults, and target losses in the workpiece.\n\n### Check and deliver\n\nApply `references/checks.md` whenever construction is prepared or attempted. Deliver the current workpiece in every branch. Deliver a net only when the mounted tool path has produced and checked one. State what the result can support, what remains open, what was assumed or simplified, and what the target or current tools could not represent.\n\nAn explicit stop opens no new topic. In an interactive conversation, emit the best current workpiece and any already-checked net with limitations visible. In construct-only execution, report a blocking gap rather than opening an interview.\n\n## Resource discipline\n\nRead resources directly from this skill's advertised resource list, using the exact `/.flue/packaged-skills/...` path shown in the activation briefing; the relative name is a label only. Do not treat Markdown links as includes, follow references recursively, or read construction material merely to frame ordinary interview questions.\n\n\nSupporting skill resources are available but are not loaded into context unless needed:\n\n- references/checks.md → read_skill_resource /.flue/packaged-skills/skill%3Asdcpn-modelling%3A99a447ec4bcbf6c0/references/checks.md\n- references/pn-construction.md → read_skill_resource /.flue/packaged-skills/skill%3Asdcpn-modelling%3A99a447ec4bcbf6c0/references/pn-construction.md\n- references/profile.md → read_skill_resource /.flue/packaged-skills/skill%3Asdcpn-modelling%3A99a447ec4bcbf6c0/references/profile.md\n- templates/workpiece.md → read_skill_resource /.flue/packaged-skills/skill%3Asdcpn-modelling%3A99a447ec4bcbf6c0/templates/workpiece.md\n", + "durationMs": 3 + }, + { + "type": "dynamic-tool", + "toolName": "getLatestNetDefinition", + "toolCallId": "toolu_01FjSrPHir7RNkmV1PUpZ1qu", + "state": "output-available", + "input": {}, + "output": { + "awaiting": "client" + }, + "durationMs": 3 + }, + { + "type": "reasoning", + "text": "The skill is activated. The net definition call shows `{\"awaiting\":\"client\"}` which means the browser needs to fetch it. Let me wait a moment for that result to come through via the client-tool-result signal, or I should ask the user about the current state. \n\nActually, I should ping first to confirm the server is in the loop, then proceed to understand what we have.", + "state": "done" + }, + { + "type": "text", + "text": "Let me confirm the server connection and then retrieve the current net definition:", + "state": "done" + }, + { + "type": "dynamic-tool", + "toolName": "ping", + "toolCallId": "toolu_01NpMVdLYpu7PeSDCfJqjwqA", + "state": "output-available", + "input": { + "note": "Confirming server ready for crew-reservation-v1 prepared-fixture work" + }, + "output": { + "ok": true, + "note": "Confirming server ready for crew-reservation-v1 prepared-fixture work" + }, + "durationMs": 1 + }, + { + "type": "reasoning", + "text": "Good, the server is ready. Now I should wait for the net definition from the browser. The getLatestNetDefinition returned `{\"awaiting\":\"client\"}` which means it's waiting for a client tool result. I should describe what I'm going to do next and wait for that result.", + "state": "done" + }, + { + "type": "text", + "text": "Good. Now let me work with the prepared-fixture material you've provided. \n\n## Understanding the prepared fixture\n\nYou've provided a **test-authored diagnostic workpiece** focused on the **final inspection → dispatch path** with one specific correction to test:\n\n**Operational account:**\n- A batch ready for dispatch enters final inspection\n- Starting final inspection *reserves* the single available dispatch crew\n- Sign-off *releases* that crew and marks the batch ready for dispatch\n\n**Current net state:**\n- Contains the batch path and the crew return from sign-off\n- **Deliberately omits** the weight-1 input arc from \"Dispatch crew available\" to \"Start final inspection\" \n\n**Purpose:** Test one evidence-backed correction against the live Petrinaut document.\n\n---\n\n## Next step\n\nThe Petrinaut net definition is being retrieved. Once I see the current structure, I can:\n\n1. **Verify** the operational account against what's in the net\n2. **Identify** the missing arc precisely \n3. **Test the correction** by adding that arc and confirming it satisfies the resource constraint (exactly one crew consumed at start, returned at sign-off)\n\nI'm waiting for the live net definition to come through. What I'll be looking for:\n- The places representing the batch states (ready, in-inspection, ready-for-dispatch)\n- The places for crew availability\n- The transitions for \"Start final inspection\" and \"Sign-off\"\n- Which arcs are already present and which are missing\n\nDoes this match your intention for this exercise?", + "state": "done" + } + ] + }, + { + "id": "entry_direct_c3ViX2lrX2I3YTI3NGI3YzI4MjVkNGMwOTE0ZGE0NDBlMjQxY2I2", + "role": "user", + "purpose": "user", + "display": "visible", + "submissionId": "sub_ik_b7a274b7c2825d4c0914da440e241cb6", + "parts": [ + { + "type": "text", + "text": "Confirmed: final inspection uses the single dispatch crew and sign-off releases it; timing, failure, and recovery remain unknown. Read the live Petrinaut definition, add the missing standard weight-1 input arc from Dispatch crew available to Start final inspection, verify it, and emit the full revised runbook-ir workpiece.", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NQGAVZ00RTNPHG4MM610XE", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_b7a274b7c2825d4c0914da440e241cb6", + "turnId": "turn_01M1NQGA4WJ01NC2MS1JJB0QWJ", + "parts": [ + { + "type": "reasoning", + "text": "The user has confirmed the operational intent and is asking me to:\n1. Read the live Petrinaut definition\n2. Add the missing standard weight-1 input arc from \"Dispatch crew available\" to \"Start final inspection\"\n3. Verify it\n4. Emit the full revised runbook-ir workpiece\n\nI need to call getLatestNetDefinition again to get the actual net definition since the previous call returned {\"awaiting\":\"client\"}.", + "state": "done" + }, + { + "type": "dynamic-tool", + "toolName": "getLatestNetDefinition", + "toolCallId": "toolu_01BqoDEZeq7HTqu3S8p6ZLA6", + "state": "output-available", + "input": {}, + "output": { + "awaiting": "client" + }, + "durationMs": 0 + } + ] + }, + { + "id": "entry_direct_c3ViX2lrX2RkMmViN2JlMjE5ODA5OThiODlhZTlhOWRhMDg1ZmIy", + "role": "system", + "purpose": "dispatch", + "display": "diagnostic", + "submissionId": "sub_ik_dd2eb7be21980998b89ae9a9da085fb2", + "signal": { + "tagName": "client-tool-result", + "attributes": { + "toolCallIds": "toolu_01BqoDEZeq7HTqu3S8p6ZLA6" + } + }, + "parts": [ + { + "type": "text", + "text": "[{\"toolCallId\":\"toolu_01BqoDEZeq7HTqu3S8p6ZLA6\",\"toolName\":\"getLatestNetDefinition\",\"output\":{\"title\":\"Prepared final inspection and dispatch\",\"definition\":{\"places\":[{\"id\":\"batch-ready\",\"name\":\"Batch ready\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":80,\"y\":100},{\"id\":\"under-final-inspection\",\"name\":\"Under final inspection\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":100},{\"id\":\"ready-for-dispatch\",\"name\":\"Ready for dispatch\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":760,\"y\":100},{\"id\":\"dispatch-crew-available\",\"name\":\"Dispatch crew available\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":360}],\"transitions\":[{\"id\":\"start-final-inspection\",\"name\":\"Start final inspection\",\"inputArcs\":[{\"placeId\":\"batch-ready\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":250,\"y\":100},{\"id\":\"sign-off\",\"name\":\"Sign-off\",\"inputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"ready-for-dispatch\",\"weight\":1},{\"placeId\":\"dispatch-crew-available\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":590,\"y\":100}],\"types\":[],\"differentialEquations\":[],\"parameters\":[]},\"extensions\":{\"colors\":true,\"stochasticity\":true,\"dynamics\":true,\"parameters\":true,\"subnets\":true}}}]", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NQGD1NRR36CBAX78EA666J", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_dd2eb7be21980998b89ae9a9da085fb2", + "turnId": "turn_01M1NQGC2ZFFFC5MFXN2949H93", + "parts": [ + { + "type": "reasoning", + "text": "Good! Now I have the live Petrinaut definition. Let me analyze it:\n\n**Current places:**\n1. `batch-ready` - batch waiting to enter inspection\n2. `under-final-inspection` - batch in inspection\n3. `ready-for-dispatch` - batch ready for dispatch (output state)\n4. `dispatch-crew-available` - represents availability of the single dispatch crew\n\n**Current transitions:**\n1. `start-final-inspection` - takes 1 batch from `batch-ready`, outputs 1 to `under-final-inspection`\n - Input arcs: `batch-ready` (weight 1, standard)\n - Output arcs: `under-final-inspection` (weight 1)\n\n2. `sign-off` - takes 1 batch from `under-final-inspection`, outputs 1 to `ready-for-dispatch` AND 1 to `dispatch-crew-available`\n - Input arcs: `under-final-inspection` (weight 1, standard)\n - Output arcs: `ready-for-dispatch` (weight 1), `dispatch-crew-available` (weight 1)\n\n**What's missing:**\nThe `start-final-inspection` transition is missing an input arc from `dispatch-crew-available`. When a batch starts final inspection, it should consume one crew token.\n\nAccording to the prepared fixture, this should be:\n- Arc direction: **input**\n- Source place: `dispatch-crew-available`\n- Weight: 1\n- Type: standard\n\nSo I need to add an input arc to `start-final-inspection` with:\n- placeId: `dispatch-crew-available`\n- weight: 1\n- type: `standard`\n\nLet me use the `addArc` function to add this arc.", + "state": "done" + }, + { + "type": "text", + "text": "Perfect. I can see the live net structure. Let me analyze it:\n\n**Current structure:**\n- **Places:** Batch-ready → Under-final-inspection → Ready-for-dispatch, plus Dispatch-crew-available\n- **start-final-inspection transition:** Currently has only one input arc (from batch-ready)\n- **sign-off transition:** Takes batch from inspection and outputs both the batch (to ready-for-dispatch) AND returns 1 crew token to dispatch-crew-available\n\n**Missing element:** The `start-final-inspection` transition lacks the input arc from `dispatch-crew-available`. This means the model currently allows inspection to begin without consuming the crew resource—violating the \"exactly one crew reserved\" constraint.\n\nNow I'll add the missing standard weight-1 input arc from `dispatch-crew-available` to `start-final-inspection`:", + "state": "done" + }, + { + "type": "dynamic-tool", + "toolName": "addArc", + "toolCallId": "toolu_01KLHzRE7gbPbFfPaXe3RTry", + "state": "output-available", + "input": { + "transitionId": "start-final-inspection", + "arcDirection": "input", + "placeId": "dispatch-crew-available", + "weight": "1", + "type": "standard" + }, + "output": { + "awaiting": "client" + }, + "durationMs": 0 + } + ] + }, + { + "id": "entry_direct_c3ViX2lrXzAwOGZhNmQ3MzU0NzQ5YTFjYjNmNjc0MTg0ZTdjYjFi", + "role": "system", + "purpose": "dispatch", + "display": "diagnostic", + "submissionId": "sub_ik_008fa6d7354749a1cb3f674184e7cb1b", + "signal": { + "tagName": "client-tool-result", + "attributes": { + "toolCallIds": "toolu_01BqoDEZeq7HTqu3S8p6ZLA6,toolu_01KLHzRE7gbPbFfPaXe3RTry" + } + }, + "parts": [ + { + "type": "text", + "text": "[{\"toolCallId\":\"toolu_01BqoDEZeq7HTqu3S8p6ZLA6\",\"toolName\":\"getLatestNetDefinition\",\"output\":{\"title\":\"Prepared final inspection and dispatch\",\"definition\":{\"places\":[{\"id\":\"batch-ready\",\"name\":\"Batch ready\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":80,\"y\":100},{\"id\":\"under-final-inspection\",\"name\":\"Under final inspection\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":100},{\"id\":\"ready-for-dispatch\",\"name\":\"Ready for dispatch\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":760,\"y\":100},{\"id\":\"dispatch-crew-available\",\"name\":\"Dispatch crew available\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":360}],\"transitions\":[{\"id\":\"start-final-inspection\",\"name\":\"Start final inspection\",\"inputArcs\":[{\"placeId\":\"batch-ready\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":250,\"y\":100},{\"id\":\"sign-off\",\"name\":\"Sign-off\",\"inputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"ready-for-dispatch\",\"weight\":1},{\"placeId\":\"dispatch-crew-available\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":590,\"y\":100}],\"types\":[],\"differentialEquations\":[],\"parameters\":[]},\"extensions\":{\"colors\":true,\"stochasticity\":true,\"dynamics\":true,\"parameters\":true,\"subnets\":true}}},{\"toolCallId\":\"toolu_01KLHzRE7gbPbFfPaXe3RTry\",\"toolName\":\"addArc\",\"output\":{\"title\":\"Added input arc\",\"detail\":\"Dispatch crew available <-> Start final inspection\",\"target\":{\"kind\":\"selection\",\"item\":{\"type\":\"arc\",\"id\":\"$A_place:dispatch-crew-available___start-final-inspection\"}},\"applied\":true}}]", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NQGMV82KXETG8X23QZHPM3", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_008fa6d7354749a1cb3f674184e7cb1b", + "turnId": "turn_01M1NQGKXT0H69NP5JFRYQJ2J7", + "parts": [ + { + "type": "reasoning", + "text": "Good! The arc has been added successfully. Let me verify by checking the live definition again to confirm the arc is now present in the start-final-inspection transition.\n\nLooking at the addArc output, it says \"Applied: true\" and created an arc with ID \"$A_place:dispatch-crew-available___start-final-inspection\". This is the confirmation.\n\nHowever, I notice that the getLatestNetDefinition result that came back still shows the old structure—the start-final-inspection transition still shows only one inputArc (from batch-ready). This might be a timing issue in how the results came back, or the definition might not have refreshed yet.\n\nLet me read the live definition one more time to verify the arc was actually applied and is visible in the current state.", + "state": "done" + }, + { + "type": "text", + "text": "Excellent! The arc addition was successful: `\"applied\":true`. Let me verify the updated net definition:", + "state": "done" + }, + { + "type": "dynamic-tool", + "toolName": "getLatestNetDefinition", + "toolCallId": "toolu_01LuBH9HSYSzvkCMq5E3PN57", + "state": "output-available", + "input": {}, + "output": { + "awaiting": "client" + }, + "durationMs": 0 + } + ] + }, + { + "id": "entry_direct_c3ViX2lrXzZkYmMxODdlNGU3Mjc5M2I3NTM5YzAyMzg3ZDA0NzE1", + "role": "system", + "purpose": "dispatch", + "display": "diagnostic", + "submissionId": "sub_ik_6dbc187e4e72793b7539c02387d04715", + "signal": { + "tagName": "client-tool-result", + "attributes": { + "toolCallIds": "toolu_01BqoDEZeq7HTqu3S8p6ZLA6,toolu_01KLHzRE7gbPbFfPaXe3RTry,toolu_01LuBH9HSYSzvkCMq5E3PN57" + } + }, + "parts": [ + { + "type": "text", + "text": "[{\"toolCallId\":\"toolu_01BqoDEZeq7HTqu3S8p6ZLA6\",\"toolName\":\"getLatestNetDefinition\",\"output\":{\"title\":\"Prepared final inspection and dispatch\",\"definition\":{\"places\":[{\"id\":\"batch-ready\",\"name\":\"Batch ready\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":80,\"y\":100},{\"id\":\"under-final-inspection\",\"name\":\"Under final inspection\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":100},{\"id\":\"ready-for-dispatch\",\"name\":\"Ready for dispatch\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":760,\"y\":100},{\"id\":\"dispatch-crew-available\",\"name\":\"Dispatch crew available\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":360}],\"transitions\":[{\"id\":\"start-final-inspection\",\"name\":\"Start final inspection\",\"inputArcs\":[{\"placeId\":\"batch-ready\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":250,\"y\":100},{\"id\":\"sign-off\",\"name\":\"Sign-off\",\"inputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"ready-for-dispatch\",\"weight\":1},{\"placeId\":\"dispatch-crew-available\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":590,\"y\":100}],\"types\":[],\"differentialEquations\":[],\"parameters\":[]},\"extensions\":{\"colors\":true,\"stochasticity\":true,\"dynamics\":true,\"parameters\":true,\"subnets\":true}}},{\"toolCallId\":\"toolu_01KLHzRE7gbPbFfPaXe3RTry\",\"toolName\":\"addArc\",\"output\":{\"title\":\"Added input arc\",\"detail\":\"Dispatch crew available <-> Start final inspection\",\"target\":{\"kind\":\"selection\",\"item\":{\"type\":\"arc\",\"id\":\"$A_place:dispatch-crew-available___start-final-inspection\"}},\"applied\":true}},{\"toolCallId\":\"toolu_01LuBH9HSYSzvkCMq5E3PN57\",\"toolName\":\"getLatestNetDefinition\",\"output\":{\"title\":\"Prepared final inspection and dispatch\",\"definition\":{\"places\":[{\"id\":\"batch-ready\",\"name\":\"Batch ready\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":80,\"y\":100},{\"id\":\"under-final-inspection\",\"name\":\"Under final inspection\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":100},{\"id\":\"ready-for-dispatch\",\"name\":\"Ready for dispatch\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":760,\"y\":100},{\"id\":\"dispatch-crew-available\",\"name\":\"Dispatch crew available\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":360}],\"transitions\":[{\"id\":\"start-final-inspection\",\"name\":\"Start final inspection\",\"inputArcs\":[{\"placeId\":\"batch-ready\",\"weight\":1,\"type\":\"standard\"},{\"type\":\"standard\",\"placeId\":\"dispatch-crew-available\",\"weight\":1}],\"outputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":250,\"y\":100},{\"id\":\"sign-off\",\"name\":\"Sign-off\",\"inputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"ready-for-dispatch\",\"weight\":1},{\"placeId\":\"dispatch-crew-available\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":590,\"y\":100}],\"types\":[],\"differentialEquations\":[],\"parameters\":[]},\"extensions\":{\"colors\":true,\"stochasticity\":true,\"dynamics\":true,\"parameters\":true,\"subnets\":true}}}]", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NQGRAKWG69MWYMPYTCFXCZ", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_6dbc187e4e72793b7539c02387d04715", + "turnId": "turn_01M1NQGQH38A3MMGGEWKN4VCDP", + "parts": [ + { + "type": "reasoning", + "text": "Perfect! The verification confirms the arc has been successfully added. Looking at the final definition:\n\nThe `start-final-inspection` transition now has two input arcs:\n1. `{\"placeId\":\"batch-ready\",\"weight\":1,\"type\":\"standard\"}`\n2. `{\"type\":\"standard\",\"placeId\":\"dispatch-crew-available\",\"weight\":1}`\n\nThis means:\n- Starting final inspection requires 1 token from \"batch-ready\" AND 1 token from \"dispatch-crew-available\"\n- It produces 1 token to \"under-final-inspection\"\n- The sign-off transition consumes the batch from inspection and returns 1 crew token\n\nThis satisfies the operational constraint: exactly one dispatch crew is consumed when inspection begins and released when sign-off completes.\n\nNow I need to emit the full revised runbook-ir workpiece as requested. I should update the prepared fixture material with:\n1. The operational account (confirmed)\n2. The current Petrinaut correspondence (UPDATED - the missing arc has been added)\n3. Explicit unknowns (unchanged)\n4. The claim boundary (adjusted to reflect what this correction establishes)", + "state": "done" + }, + { + "type": "text", + "text": "**Verification successful.** The `start-final-inspection` transition now contains the corrected input arcs:\n```json\n\"inputArcs\":[\n {\"placeId\":\"batch-ready\",\"weight\":1,\"type\":\"standard\"},\n {\"type\":\"standard\",\"placeId\":\"dispatch-crew-available\",\"weight\":1}\n]\n```\n\nThe resource constraint is now satisfied: firing `start-final-inspection` requires both a ready batch AND an available crew token. The `sign-off` transition releases that crew back to `dispatch-crew-available`.\n\n---\n\n## Revised workpiece (runbook-ir)\n\n```runbook-ir\n# Final inspection and dispatch workpiece — Revised\n\n## Purpose and posture\nModel the batch path from final inspection to dispatch readiness with explicit crew resource management. This revision adds the missing crew-consumption arc to enforce the resource constraint.\n\n## Operational account\n- A batch that is ready enters final inspection.\n- Starting final inspection requires both a ready batch AND the sole available dispatch crew.\n- Starting final inspection consumes that crew token.\n- Sign-off releases that crew and makes the batch ready for dispatch.\n\n## Quantity and resource policy\nExactly one dispatch crew is available. Starting final inspection consumes that one available crew; sign-off returns it. No transition fires without sufficient tokens in all input places.\n\n## Petrinaut correspondence\nThe net contains four places:\n- `batch-ready`: batches awaiting final inspection\n- `under-final-inspection`: batches currently in inspection\n- `ready-for-dispatch`: batches cleared for dispatch\n- `dispatch-crew-available`: crew availability (1 token when free)\n\nTransitions:\n- `start-final-inspection`: requires 1 token from batch-ready AND 1 token from dispatch-crew-available; produces 1 token to under-final-inspection\n- `sign-off`: requires 1 token from under-final-inspection; produces 1 token to ready-for-dispatch AND 1 token to dispatch-crew-available\n\nThe missing standard weight-1 input arc from `dispatch-crew-available` to `start-final-inspection` has been **added and verified** in the live Petrinaut definition.\n\n## Explicit unknowns\n- Inspection timing: duration, stochasticity, or determinism remain unresolved\n- Sign-off timing: duration, stochasticity, or determinism remain unresolved\n- Failure modes: whether inspection can fail, halt, or reject a batch\n- Recovery behavior: how failures or rejections affect batch state or crew availability\n\n## Claim boundary\nThis workpiece and the corrected net establish only:\n- The crew-consumption constraint at start of inspection\n- The crew-release at sign-off completion\n- Token flow from ready batch through inspection to dispatch readiness\n\nThis is test-authored diagnostic material for the narrow final-inspection–to-dispatch path. It does not establish capture provenance, behavioral execution, failure handling, full process projection, or integration with upstream or downstream operations.\n```\n\nThe correction is complete and verified in the live Petrinaut definition.", + "state": "done" + } + ] + } + ], + "settlements": [ + { + "submissionId": "sub_ik_f24939bebb841ffe1469a7f3a4a06915", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_f24939bebb841ffe1469a7f3a4a06915" + }, + { + "submissionId": "sub_ik_b7a274b7c2825d4c0914da440e241cb6", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_b7a274b7c2825d4c0914da440e241cb6" + }, + { + "submissionId": "sub_ik_dd2eb7be21980998b89ae9a9da085fb2", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_dd2eb7be21980998b89ae9a9da085fb2" + }, + { + "submissionId": "sub_ik_008fa6d7354749a1cb3f674184e7cb1b", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_008fa6d7354749a1cb3f674184e7cb1b" + }, + { + "submissionId": "sub_ik_6dbc187e4e72793b7539c02387d04715", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_6dbc187e4e72793b7539c02387d04715" + } + ], + "incarnation": "inc_01M1NQEXM24JHNYN40Q8RA5DH5" +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/flue-snapshot-before.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/flue-snapshot-before.json new file mode 100644 index 00000000000..a584f125feb --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/flue-snapshot-before.json @@ -0,0 +1,113 @@ +{ + "v": 1, + "conversationId": "conv_01M1NQEXM3CAPPTXM33ZE1YSRG", + "offset": "0000000000000000_0000000000000043", + "messages": [ + { + "id": "entry_direct_c3ViX2lrX2YyNDkzOWJlYmI4NDFmZmUxNDY5YTdmM2E0YTA2OTE1", + "role": "system", + "purpose": "dispatch", + "display": "diagnostic", + "submissionId": "sub_ik_f24939bebb841ffe1469a7f3a4a06915", + "signal": { + "tagName": "prepared-fixture", + "attributes": { + "fixtureId": "crew-reservation-v1", + "authorship": "test-authored", + "claimBoundary": "prepared-not-model-produced" + } + }, + "parts": [ + { + "type": "text", + "text": "Fixture authorship: test-authored preparation for Mission 6.\nNon-claims: not a Mission 4 candidate, not model-produced evidence, not capture-backed provenance, and not proof of automatic full-net projection.\n\n```runbook-ir\n# Final inspection and dispatch workpiece\n\n## Purpose and posture\nMaintain the narrow batch path from final inspection to dispatch readiness and test one evidence-backed correction against the live Petrinaut document.\n\n## Operational account\n- A batch that is ready enters final inspection.\n- Final inspection reserves the sole available dispatch crew.\n- Sign-off releases that crew and makes the batch ready for dispatch.\n\n## Quantity and resource policy\nExactly one dispatch crew is available in this fixture. Starting final inspection consumes that one available crew; sign-off returns it.\n\n## Current Petrinaut correspondence\nThe prepared non-empty net contains the batch path and the crew return from sign-off. It deliberately lacks the standard weight-1 input arc from `Dispatch crew available` to `Start final inspection`.\n\n## Explicit unknowns\nInspection and sign-off timing, failure modes, and recovery behavior remain unresolved.\n\n## Claim boundary\nThis prepared revision is test-authored diagnostic material. It is not model-produced evidence and does not establish capture provenance, behavioral execution, or broad projection quality.\n```", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NQEYF9W60J2JVDYB2BJXK1", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_f24939bebb841ffe1469a7f3a4a06915", + "turnId": "turn_01M1NQEXM7MHJ94PF52Q54VAY4", + "parts": [ + { + "type": "reasoning", + "text": "The user has provided a prepared-fixture for SDCPN modelling work. This is test-authored material that is:\n- Part of a Mission 6 exercise\n- Focused on a narrow batch path from final inspection to dispatch readiness\n- Aimed at testing one evidence-backed correction against the live Petrinaut document\n- Not model-produced evidence, not a capture-backed provenance, not proof of automatic full-net projection\n\nThe operational account describes:\n1. A batch that is ready enters final inspection\n2. Final inspection reserves the sole available dispatch crew\n3. Sign-off releases that crew and makes the batch ready for dispatch\n\nThe key policy: Exactly one dispatch crew is available. Starting final inspection consumes it; sign-off returns it.\n\nThe current state: The prepared non-empty net contains the batch path and the crew return from sign-off, but deliberately LACKS the standard weight-1 input arc from \"Dispatch crew available\" to \"Start final inspection\".\n\nThe purpose is to test one evidence-backed correction against the live Petrinaut document.\n\nMy role here is:\n1. First, activate the sdcpn-modelling skill since this is a process-modelling task\n2. Understand the current state of the Petrinaut net\n3. Help verify the operational account against the net\n4. Work with the user to identify and potentially implement the missing arc correction\n5. Maintain the workpiece with clear documentation of what is known, what is missing, and what has been tested\n\nLet me start by activating the skill and getting the current net state.", + "state": "done" + }, + { + "type": "text", + "text": "I'll help you with this prepared-fixture exercise. Let me start by activating the SDCPN modelling skill and then retrieving the current state of the Petrinaut net to understand what we're working with.", + "state": "done" + }, + { + "type": "dynamic-tool", + "toolName": "activate_skill", + "toolCallId": "toolu_01WQaZveys8joHtboYvGQiuA", + "state": "output-available", + "input": { + "name": "sdcpn-modelling" + }, + "output": "Run the skill named \"sdcpn-modelling\".\n\n\n# Capability-aware lifecycle\n\nUse one conceptual lifecycle: orient, elicit or revise, maintain the workpiece, construct when supported, check, and deliver. The current conversation may expose only one branch of that lifecycle. Do not claim that an unavailable transition occurred.\n\n## Select the runtime branch\n\n### Interactive elicitation or revision\n\nInterview in the person's operational vocabulary. Activate the `elicitation` skill and read `references/profile.md` before substantive questions or revision. Read `templates/workpiece.md` when creating or materially revising the shared workpiece. Construct only when the mounted capabilities actually permit construction in this conversation.\n\n### Construct-only execution\n\nUse the supplied workpiece as the complete modelling input. Do not interview. Read `references/pn-construction.md` and `references/checks.md`, then use the mounted construction tools. If a consequential workpiece gap prevents faithful construction, report the gap and the smallest question a later interactive elicitation must answer; do not ask it or invent an answer in this conversation.\n\n## Procedure\n\n### Orient\n\nEstablish enough purpose and context to select one focused next action: the intended question or decision, audience, boundary, horizon, accuracy need, and available time. Orientation need not settle every concern before elicitation begins.\n\n### Elicit or revise\n\nFor a new account, follow one concrete case and re-evaluate the active gap after each useful answer. For an existing account, first locate the disputed or changed material and its consequence for the objective. Use the `elicitation` skill's universal guidance and `references/profile.md` for detailed operations and coverage; do not turn their register order into question order.\n\n### Maintain the workpiece\n\nTreat the workpiece as the recoverable account construction will consume. Update it after a useful stretch rather than waiting until the end. Preserve unrelated material unless new evidence affects it.\n\nWhenever the workpiece changes substantially, emit the full current document in a fenced block whose language tag is exactly `runbook-ir`. Emit it again before construction and before workpiece-only delivery. A delta or prose promise is not a recoverable workpiece.\n\n### Construct\n\nConstruct only from the current workpiece. Read `references/pn-construction.md` and `references/checks.md` before beginning. Use mounted Petrinaut tools for every net change and inspect the resulting definition rather than emitting free-form net JSON. If the required tools are absent, limit the result to the workpiece and construction-ready notes.\n\nConstruction may infer a representation from recorded operational meaning; it may not invent operational facts. Record construction inferences, approximations, defaults, and target losses in the workpiece.\n\n### Check and deliver\n\nApply `references/checks.md` whenever construction is prepared or attempted. Deliver the current workpiece in every branch. Deliver a net only when the mounted tool path has produced and checked one. State what the result can support, what remains open, what was assumed or simplified, and what the target or current tools could not represent.\n\nAn explicit stop opens no new topic. In an interactive conversation, emit the best current workpiece and any already-checked net with limitations visible. In construct-only execution, report a blocking gap rather than opening an interview.\n\n## Resource discipline\n\nRead resources directly from this skill's advertised resource list, using the exact `/.flue/packaged-skills/...` path shown in the activation briefing; the relative name is a label only. Do not treat Markdown links as includes, follow references recursively, or read construction material merely to frame ordinary interview questions.\n\n\nSupporting skill resources are available but are not loaded into context unless needed:\n\n- references/checks.md → read_skill_resource /.flue/packaged-skills/skill%3Asdcpn-modelling%3A99a447ec4bcbf6c0/references/checks.md\n- references/pn-construction.md → read_skill_resource /.flue/packaged-skills/skill%3Asdcpn-modelling%3A99a447ec4bcbf6c0/references/pn-construction.md\n- references/profile.md → read_skill_resource /.flue/packaged-skills/skill%3Asdcpn-modelling%3A99a447ec4bcbf6c0/references/profile.md\n- templates/workpiece.md → read_skill_resource /.flue/packaged-skills/skill%3Asdcpn-modelling%3A99a447ec4bcbf6c0/templates/workpiece.md\n", + "durationMs": 3 + }, + { + "type": "dynamic-tool", + "toolName": "getLatestNetDefinition", + "toolCallId": "toolu_01FjSrPHir7RNkmV1PUpZ1qu", + "state": "output-available", + "input": {}, + "output": { + "awaiting": "client" + }, + "durationMs": 3 + }, + { + "type": "reasoning", + "text": "The skill is activated. The net definition call shows `{\"awaiting\":\"client\"}` which means the browser needs to fetch it. Let me wait a moment for that result to come through via the client-tool-result signal, or I should ask the user about the current state. \n\nActually, I should ping first to confirm the server is in the loop, then proceed to understand what we have.", + "state": "done" + }, + { + "type": "text", + "text": "Let me confirm the server connection and then retrieve the current net definition:", + "state": "done" + }, + { + "type": "dynamic-tool", + "toolName": "ping", + "toolCallId": "toolu_01NpMVdLYpu7PeSDCfJqjwqA", + "state": "output-available", + "input": { + "note": "Confirming server ready for crew-reservation-v1 prepared-fixture work" + }, + "output": { + "ok": true, + "note": "Confirming server ready for crew-reservation-v1 prepared-fixture work" + }, + "durationMs": 1 + }, + { + "type": "reasoning", + "text": "Good, the server is ready. Now I should wait for the net definition from the browser. The getLatestNetDefinition returned `{\"awaiting\":\"client\"}` which means it's waiting for a client tool result. I should describe what I'm going to do next and wait for that result.", + "state": "done" + }, + { + "type": "text", + "text": "Good. Now let me work with the prepared-fixture material you've provided. \n\n## Understanding the prepared fixture\n\nYou've provided a **test-authored diagnostic workpiece** focused on the **final inspection → dispatch path** with one specific correction to test:\n\n**Operational account:**\n- A batch ready for dispatch enters final inspection\n- Starting final inspection *reserves* the single available dispatch crew\n- Sign-off *releases* that crew and marks the batch ready for dispatch\n\n**Current net state:**\n- Contains the batch path and the crew return from sign-off\n- **Deliberately omits** the weight-1 input arc from \"Dispatch crew available\" to \"Start final inspection\" \n\n**Purpose:** Test one evidence-backed correction against the live Petrinaut document.\n\n---\n\n## Next step\n\nThe Petrinaut net definition is being retrieved. Once I see the current structure, I can:\n\n1. **Verify** the operational account against what's in the net\n2. **Identify** the missing arc precisely \n3. **Test the correction** by adding that arc and confirming it satisfies the resource constraint (exactly one crew consumed at start, returned at sign-off)\n\nI'm waiting for the live net definition to come through. What I'll be looking for:\n- The places representing the batch states (ready, in-inspection, ready-for-dispatch)\n- The places for crew availability\n- The transitions for \"Start final inspection\" and \"Sign-off\"\n- Which arcs are already present and which are missing\n\nDoes this match your intention for this exercise?", + "state": "done" + } + ] + } + ], + "settlements": [ + { + "submissionId": "sub_ik_f24939bebb841ffe1469a7f3a4a06915", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_f24939bebb841ffe1469a7f3a4a06915" + } + ], + "incarnation": "inc_01M1NQEXM24JHNYN40Q8RA5DH5" +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/flue-snapshot-tab-b.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/flue-snapshot-tab-b.json new file mode 100644 index 00000000000..4cb95e7516d --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/flue-snapshot-tab-b.json @@ -0,0 +1,360 @@ +{ + "v": 1, + "conversationId": "conv_01M1NQEXM3CAPPTXM33ZE1YSRG", + "offset": "0000000000000000_0000000000000123", + "messages": [ + { + "id": "entry_direct_c3ViX2lrX2YyNDkzOWJlYmI4NDFmZmUxNDY5YTdmM2E0YTA2OTE1", + "role": "system", + "purpose": "dispatch", + "display": "diagnostic", + "submissionId": "sub_ik_f24939bebb841ffe1469a7f3a4a06915", + "signal": { + "tagName": "prepared-fixture", + "attributes": { + "fixtureId": "crew-reservation-v1", + "authorship": "test-authored", + "claimBoundary": "prepared-not-model-produced" + } + }, + "parts": [ + { + "type": "text", + "text": "Fixture authorship: test-authored preparation for Mission 6.\nNon-claims: not a Mission 4 candidate, not model-produced evidence, not capture-backed provenance, and not proof of automatic full-net projection.\n\n```runbook-ir\n# Final inspection and dispatch workpiece\n\n## Purpose and posture\nMaintain the narrow batch path from final inspection to dispatch readiness and test one evidence-backed correction against the live Petrinaut document.\n\n## Operational account\n- A batch that is ready enters final inspection.\n- Final inspection reserves the sole available dispatch crew.\n- Sign-off releases that crew and makes the batch ready for dispatch.\n\n## Quantity and resource policy\nExactly one dispatch crew is available in this fixture. Starting final inspection consumes that one available crew; sign-off returns it.\n\n## Current Petrinaut correspondence\nThe prepared non-empty net contains the batch path and the crew return from sign-off. It deliberately lacks the standard weight-1 input arc from `Dispatch crew available` to `Start final inspection`.\n\n## Explicit unknowns\nInspection and sign-off timing, failure modes, and recovery behavior remain unresolved.\n\n## Claim boundary\nThis prepared revision is test-authored diagnostic material. It is not model-produced evidence and does not establish capture provenance, behavioral execution, or broad projection quality.\n```", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NQEYF9W60J2JVDYB2BJXK1", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_f24939bebb841ffe1469a7f3a4a06915", + "turnId": "turn_01M1NQEXM7MHJ94PF52Q54VAY4", + "parts": [ + { + "type": "reasoning", + "text": "The user has provided a prepared-fixture for SDCPN modelling work. This is test-authored material that is:\n- Part of a Mission 6 exercise\n- Focused on a narrow batch path from final inspection to dispatch readiness\n- Aimed at testing one evidence-backed correction against the live Petrinaut document\n- Not model-produced evidence, not a capture-backed provenance, not proof of automatic full-net projection\n\nThe operational account describes:\n1. A batch that is ready enters final inspection\n2. Final inspection reserves the sole available dispatch crew\n3. Sign-off releases that crew and makes the batch ready for dispatch\n\nThe key policy: Exactly one dispatch crew is available. Starting final inspection consumes it; sign-off returns it.\n\nThe current state: The prepared non-empty net contains the batch path and the crew return from sign-off, but deliberately LACKS the standard weight-1 input arc from \"Dispatch crew available\" to \"Start final inspection\".\n\nThe purpose is to test one evidence-backed correction against the live Petrinaut document.\n\nMy role here is:\n1. First, activate the sdcpn-modelling skill since this is a process-modelling task\n2. Understand the current state of the Petrinaut net\n3. Help verify the operational account against the net\n4. Work with the user to identify and potentially implement the missing arc correction\n5. Maintain the workpiece with clear documentation of what is known, what is missing, and what has been tested\n\nLet me start by activating the skill and getting the current net state.", + "state": "done" + }, + { + "type": "text", + "text": "I'll help you with this prepared-fixture exercise. Let me start by activating the SDCPN modelling skill and then retrieving the current state of the Petrinaut net to understand what we're working with.", + "state": "done" + }, + { + "type": "dynamic-tool", + "toolName": "activate_skill", + "toolCallId": "toolu_01WQaZveys8joHtboYvGQiuA", + "state": "output-available", + "input": { + "name": "sdcpn-modelling" + }, + "output": "Run the skill named \"sdcpn-modelling\".\n\n\n# Capability-aware lifecycle\n\nUse one conceptual lifecycle: orient, elicit or revise, maintain the workpiece, construct when supported, check, and deliver. The current conversation may expose only one branch of that lifecycle. Do not claim that an unavailable transition occurred.\n\n## Select the runtime branch\n\n### Interactive elicitation or revision\n\nInterview in the person's operational vocabulary. Activate the `elicitation` skill and read `references/profile.md` before substantive questions or revision. Read `templates/workpiece.md` when creating or materially revising the shared workpiece. Construct only when the mounted capabilities actually permit construction in this conversation.\n\n### Construct-only execution\n\nUse the supplied workpiece as the complete modelling input. Do not interview. Read `references/pn-construction.md` and `references/checks.md`, then use the mounted construction tools. If a consequential workpiece gap prevents faithful construction, report the gap and the smallest question a later interactive elicitation must answer; do not ask it or invent an answer in this conversation.\n\n## Procedure\n\n### Orient\n\nEstablish enough purpose and context to select one focused next action: the intended question or decision, audience, boundary, horizon, accuracy need, and available time. Orientation need not settle every concern before elicitation begins.\n\n### Elicit or revise\n\nFor a new account, follow one concrete case and re-evaluate the active gap after each useful answer. For an existing account, first locate the disputed or changed material and its consequence for the objective. Use the `elicitation` skill's universal guidance and `references/profile.md` for detailed operations and coverage; do not turn their register order into question order.\n\n### Maintain the workpiece\n\nTreat the workpiece as the recoverable account construction will consume. Update it after a useful stretch rather than waiting until the end. Preserve unrelated material unless new evidence affects it.\n\nWhenever the workpiece changes substantially, emit the full current document in a fenced block whose language tag is exactly `runbook-ir`. Emit it again before construction and before workpiece-only delivery. A delta or prose promise is not a recoverable workpiece.\n\n### Construct\n\nConstruct only from the current workpiece. Read `references/pn-construction.md` and `references/checks.md` before beginning. Use mounted Petrinaut tools for every net change and inspect the resulting definition rather than emitting free-form net JSON. If the required tools are absent, limit the result to the workpiece and construction-ready notes.\n\nConstruction may infer a representation from recorded operational meaning; it may not invent operational facts. Record construction inferences, approximations, defaults, and target losses in the workpiece.\n\n### Check and deliver\n\nApply `references/checks.md` whenever construction is prepared or attempted. Deliver the current workpiece in every branch. Deliver a net only when the mounted tool path has produced and checked one. State what the result can support, what remains open, what was assumed or simplified, and what the target or current tools could not represent.\n\nAn explicit stop opens no new topic. In an interactive conversation, emit the best current workpiece and any already-checked net with limitations visible. In construct-only execution, report a blocking gap rather than opening an interview.\n\n## Resource discipline\n\nRead resources directly from this skill's advertised resource list, using the exact `/.flue/packaged-skills/...` path shown in the activation briefing; the relative name is a label only. Do not treat Markdown links as includes, follow references recursively, or read construction material merely to frame ordinary interview questions.\n\n\nSupporting skill resources are available but are not loaded into context unless needed:\n\n- references/checks.md → read_skill_resource /.flue/packaged-skills/skill%3Asdcpn-modelling%3A99a447ec4bcbf6c0/references/checks.md\n- references/pn-construction.md → read_skill_resource /.flue/packaged-skills/skill%3Asdcpn-modelling%3A99a447ec4bcbf6c0/references/pn-construction.md\n- references/profile.md → read_skill_resource /.flue/packaged-skills/skill%3Asdcpn-modelling%3A99a447ec4bcbf6c0/references/profile.md\n- templates/workpiece.md → read_skill_resource /.flue/packaged-skills/skill%3Asdcpn-modelling%3A99a447ec4bcbf6c0/templates/workpiece.md\n", + "durationMs": 3 + }, + { + "type": "dynamic-tool", + "toolName": "getLatestNetDefinition", + "toolCallId": "toolu_01FjSrPHir7RNkmV1PUpZ1qu", + "state": "output-available", + "input": {}, + "output": { + "awaiting": "client" + }, + "durationMs": 3 + }, + { + "type": "reasoning", + "text": "The skill is activated. The net definition call shows `{\"awaiting\":\"client\"}` which means the browser needs to fetch it. Let me wait a moment for that result to come through via the client-tool-result signal, or I should ask the user about the current state. \n\nActually, I should ping first to confirm the server is in the loop, then proceed to understand what we have.", + "state": "done" + }, + { + "type": "text", + "text": "Let me confirm the server connection and then retrieve the current net definition:", + "state": "done" + }, + { + "type": "dynamic-tool", + "toolName": "ping", + "toolCallId": "toolu_01NpMVdLYpu7PeSDCfJqjwqA", + "state": "output-available", + "input": { + "note": "Confirming server ready for crew-reservation-v1 prepared-fixture work" + }, + "output": { + "ok": true, + "note": "Confirming server ready for crew-reservation-v1 prepared-fixture work" + }, + "durationMs": 1 + }, + { + "type": "reasoning", + "text": "Good, the server is ready. Now I should wait for the net definition from the browser. The getLatestNetDefinition returned `{\"awaiting\":\"client\"}` which means it's waiting for a client tool result. I should describe what I'm going to do next and wait for that result.", + "state": "done" + }, + { + "type": "text", + "text": "Good. Now let me work with the prepared-fixture material you've provided. \n\n## Understanding the prepared fixture\n\nYou've provided a **test-authored diagnostic workpiece** focused on the **final inspection → dispatch path** with one specific correction to test:\n\n**Operational account:**\n- A batch ready for dispatch enters final inspection\n- Starting final inspection *reserves* the single available dispatch crew\n- Sign-off *releases* that crew and marks the batch ready for dispatch\n\n**Current net state:**\n- Contains the batch path and the crew return from sign-off\n- **Deliberately omits** the weight-1 input arc from \"Dispatch crew available\" to \"Start final inspection\" \n\n**Purpose:** Test one evidence-backed correction against the live Petrinaut document.\n\n---\n\n## Next step\n\nThe Petrinaut net definition is being retrieved. Once I see the current structure, I can:\n\n1. **Verify** the operational account against what's in the net\n2. **Identify** the missing arc precisely \n3. **Test the correction** by adding that arc and confirming it satisfies the resource constraint (exactly one crew consumed at start, returned at sign-off)\n\nI'm waiting for the live net definition to come through. What I'll be looking for:\n- The places representing the batch states (ready, in-inspection, ready-for-dispatch)\n- The places for crew availability\n- The transitions for \"Start final inspection\" and \"Sign-off\"\n- Which arcs are already present and which are missing\n\nDoes this match your intention for this exercise?", + "state": "done" + } + ] + }, + { + "id": "entry_direct_c3ViX2lrX2I3YTI3NGI3YzI4MjVkNGMwOTE0ZGE0NDBlMjQxY2I2", + "role": "user", + "purpose": "user", + "display": "visible", + "submissionId": "sub_ik_b7a274b7c2825d4c0914da440e241cb6", + "parts": [ + { + "type": "text", + "text": "Confirmed: final inspection uses the single dispatch crew and sign-off releases it; timing, failure, and recovery remain unknown. Read the live Petrinaut definition, add the missing standard weight-1 input arc from Dispatch crew available to Start final inspection, verify it, and emit the full revised runbook-ir workpiece.", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NQGAVZ00RTNPHG4MM610XE", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_b7a274b7c2825d4c0914da440e241cb6", + "turnId": "turn_01M1NQGA4WJ01NC2MS1JJB0QWJ", + "parts": [ + { + "type": "reasoning", + "text": "The user has confirmed the operational intent and is asking me to:\n1. Read the live Petrinaut definition\n2. Add the missing standard weight-1 input arc from \"Dispatch crew available\" to \"Start final inspection\"\n3. Verify it\n4. Emit the full revised runbook-ir workpiece\n\nI need to call getLatestNetDefinition again to get the actual net definition since the previous call returned {\"awaiting\":\"client\"}.", + "state": "done" + }, + { + "type": "dynamic-tool", + "toolName": "getLatestNetDefinition", + "toolCallId": "toolu_01BqoDEZeq7HTqu3S8p6ZLA6", + "state": "output-available", + "input": {}, + "output": { + "awaiting": "client" + }, + "durationMs": 0 + } + ] + }, + { + "id": "entry_direct_c3ViX2lrX2RkMmViN2JlMjE5ODA5OThiODlhZTlhOWRhMDg1ZmIy", + "role": "system", + "purpose": "dispatch", + "display": "diagnostic", + "submissionId": "sub_ik_dd2eb7be21980998b89ae9a9da085fb2", + "signal": { + "tagName": "client-tool-result", + "attributes": { + "toolCallIds": "toolu_01BqoDEZeq7HTqu3S8p6ZLA6" + } + }, + "parts": [ + { + "type": "text", + "text": "[{\"toolCallId\":\"toolu_01BqoDEZeq7HTqu3S8p6ZLA6\",\"toolName\":\"getLatestNetDefinition\",\"output\":{\"title\":\"Prepared final inspection and dispatch\",\"definition\":{\"places\":[{\"id\":\"batch-ready\",\"name\":\"Batch ready\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":80,\"y\":100},{\"id\":\"under-final-inspection\",\"name\":\"Under final inspection\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":100},{\"id\":\"ready-for-dispatch\",\"name\":\"Ready for dispatch\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":760,\"y\":100},{\"id\":\"dispatch-crew-available\",\"name\":\"Dispatch crew available\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":360}],\"transitions\":[{\"id\":\"start-final-inspection\",\"name\":\"Start final inspection\",\"inputArcs\":[{\"placeId\":\"batch-ready\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":250,\"y\":100},{\"id\":\"sign-off\",\"name\":\"Sign-off\",\"inputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"ready-for-dispatch\",\"weight\":1},{\"placeId\":\"dispatch-crew-available\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":590,\"y\":100}],\"types\":[],\"differentialEquations\":[],\"parameters\":[]},\"extensions\":{\"colors\":true,\"stochasticity\":true,\"dynamics\":true,\"parameters\":true,\"subnets\":true}}}]", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NQGD1NRR36CBAX78EA666J", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_dd2eb7be21980998b89ae9a9da085fb2", + "turnId": "turn_01M1NQGC2ZFFFC5MFXN2949H93", + "parts": [ + { + "type": "reasoning", + "text": "Good! Now I have the live Petrinaut definition. Let me analyze it:\n\n**Current places:**\n1. `batch-ready` - batch waiting to enter inspection\n2. `under-final-inspection` - batch in inspection\n3. `ready-for-dispatch` - batch ready for dispatch (output state)\n4. `dispatch-crew-available` - represents availability of the single dispatch crew\n\n**Current transitions:**\n1. `start-final-inspection` - takes 1 batch from `batch-ready`, outputs 1 to `under-final-inspection`\n - Input arcs: `batch-ready` (weight 1, standard)\n - Output arcs: `under-final-inspection` (weight 1)\n\n2. `sign-off` - takes 1 batch from `under-final-inspection`, outputs 1 to `ready-for-dispatch` AND 1 to `dispatch-crew-available`\n - Input arcs: `under-final-inspection` (weight 1, standard)\n - Output arcs: `ready-for-dispatch` (weight 1), `dispatch-crew-available` (weight 1)\n\n**What's missing:**\nThe `start-final-inspection` transition is missing an input arc from `dispatch-crew-available`. When a batch starts final inspection, it should consume one crew token.\n\nAccording to the prepared fixture, this should be:\n- Arc direction: **input**\n- Source place: `dispatch-crew-available`\n- Weight: 1\n- Type: standard\n\nSo I need to add an input arc to `start-final-inspection` with:\n- placeId: `dispatch-crew-available`\n- weight: 1\n- type: `standard`\n\nLet me use the `addArc` function to add this arc.", + "state": "done" + }, + { + "type": "text", + "text": "Perfect. I can see the live net structure. Let me analyze it:\n\n**Current structure:**\n- **Places:** Batch-ready → Under-final-inspection → Ready-for-dispatch, plus Dispatch-crew-available\n- **start-final-inspection transition:** Currently has only one input arc (from batch-ready)\n- **sign-off transition:** Takes batch from inspection and outputs both the batch (to ready-for-dispatch) AND returns 1 crew token to dispatch-crew-available\n\n**Missing element:** The `start-final-inspection` transition lacks the input arc from `dispatch-crew-available`. This means the model currently allows inspection to begin without consuming the crew resource—violating the \"exactly one crew reserved\" constraint.\n\nNow I'll add the missing standard weight-1 input arc from `dispatch-crew-available` to `start-final-inspection`:", + "state": "done" + }, + { + "type": "dynamic-tool", + "toolName": "addArc", + "toolCallId": "toolu_01KLHzRE7gbPbFfPaXe3RTry", + "state": "output-available", + "input": { + "transitionId": "start-final-inspection", + "arcDirection": "input", + "placeId": "dispatch-crew-available", + "weight": "1", + "type": "standard" + }, + "output": { + "awaiting": "client" + }, + "durationMs": 0 + } + ] + }, + { + "id": "entry_direct_c3ViX2lrXzAwOGZhNmQ3MzU0NzQ5YTFjYjNmNjc0MTg0ZTdjYjFi", + "role": "system", + "purpose": "dispatch", + "display": "diagnostic", + "submissionId": "sub_ik_008fa6d7354749a1cb3f674184e7cb1b", + "signal": { + "tagName": "client-tool-result", + "attributes": { + "toolCallIds": "toolu_01BqoDEZeq7HTqu3S8p6ZLA6,toolu_01KLHzRE7gbPbFfPaXe3RTry" + } + }, + "parts": [ + { + "type": "text", + "text": "[{\"toolCallId\":\"toolu_01BqoDEZeq7HTqu3S8p6ZLA6\",\"toolName\":\"getLatestNetDefinition\",\"output\":{\"title\":\"Prepared final inspection and dispatch\",\"definition\":{\"places\":[{\"id\":\"batch-ready\",\"name\":\"Batch ready\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":80,\"y\":100},{\"id\":\"under-final-inspection\",\"name\":\"Under final inspection\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":100},{\"id\":\"ready-for-dispatch\",\"name\":\"Ready for dispatch\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":760,\"y\":100},{\"id\":\"dispatch-crew-available\",\"name\":\"Dispatch crew available\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":360}],\"transitions\":[{\"id\":\"start-final-inspection\",\"name\":\"Start final inspection\",\"inputArcs\":[{\"placeId\":\"batch-ready\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":250,\"y\":100},{\"id\":\"sign-off\",\"name\":\"Sign-off\",\"inputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"ready-for-dispatch\",\"weight\":1},{\"placeId\":\"dispatch-crew-available\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":590,\"y\":100}],\"types\":[],\"differentialEquations\":[],\"parameters\":[]},\"extensions\":{\"colors\":true,\"stochasticity\":true,\"dynamics\":true,\"parameters\":true,\"subnets\":true}}},{\"toolCallId\":\"toolu_01KLHzRE7gbPbFfPaXe3RTry\",\"toolName\":\"addArc\",\"output\":{\"title\":\"Added input arc\",\"detail\":\"Dispatch crew available <-> Start final inspection\",\"target\":{\"kind\":\"selection\",\"item\":{\"type\":\"arc\",\"id\":\"$A_place:dispatch-crew-available___start-final-inspection\"}},\"applied\":true}}]", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NQGMV82KXETG8X23QZHPM3", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_008fa6d7354749a1cb3f674184e7cb1b", + "turnId": "turn_01M1NQGKXT0H69NP5JFRYQJ2J7", + "parts": [ + { + "type": "reasoning", + "text": "Good! The arc has been added successfully. Let me verify by checking the live definition again to confirm the arc is now present in the start-final-inspection transition.\n\nLooking at the addArc output, it says \"Applied: true\" and created an arc with ID \"$A_place:dispatch-crew-available___start-final-inspection\". This is the confirmation.\n\nHowever, I notice that the getLatestNetDefinition result that came back still shows the old structure—the start-final-inspection transition still shows only one inputArc (from batch-ready). This might be a timing issue in how the results came back, or the definition might not have refreshed yet.\n\nLet me read the live definition one more time to verify the arc was actually applied and is visible in the current state.", + "state": "done" + }, + { + "type": "text", + "text": "Excellent! The arc addition was successful: `\"applied\":true`. Let me verify the updated net definition:", + "state": "done" + }, + { + "type": "dynamic-tool", + "toolName": "getLatestNetDefinition", + "toolCallId": "toolu_01LuBH9HSYSzvkCMq5E3PN57", + "state": "output-available", + "input": {}, + "output": { + "awaiting": "client" + }, + "durationMs": 0 + } + ] + }, + { + "id": "entry_direct_c3ViX2lrXzZkYmMxODdlNGU3Mjc5M2I3NTM5YzAyMzg3ZDA0NzE1", + "role": "system", + "purpose": "dispatch", + "display": "diagnostic", + "submissionId": "sub_ik_6dbc187e4e72793b7539c02387d04715", + "signal": { + "tagName": "client-tool-result", + "attributes": { + "toolCallIds": "toolu_01BqoDEZeq7HTqu3S8p6ZLA6,toolu_01KLHzRE7gbPbFfPaXe3RTry,toolu_01LuBH9HSYSzvkCMq5E3PN57" + } + }, + "parts": [ + { + "type": "text", + "text": "[{\"toolCallId\":\"toolu_01BqoDEZeq7HTqu3S8p6ZLA6\",\"toolName\":\"getLatestNetDefinition\",\"output\":{\"title\":\"Prepared final inspection and dispatch\",\"definition\":{\"places\":[{\"id\":\"batch-ready\",\"name\":\"Batch ready\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":80,\"y\":100},{\"id\":\"under-final-inspection\",\"name\":\"Under final inspection\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":100},{\"id\":\"ready-for-dispatch\",\"name\":\"Ready for dispatch\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":760,\"y\":100},{\"id\":\"dispatch-crew-available\",\"name\":\"Dispatch crew available\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":360}],\"transitions\":[{\"id\":\"start-final-inspection\",\"name\":\"Start final inspection\",\"inputArcs\":[{\"placeId\":\"batch-ready\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":250,\"y\":100},{\"id\":\"sign-off\",\"name\":\"Sign-off\",\"inputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"ready-for-dispatch\",\"weight\":1},{\"placeId\":\"dispatch-crew-available\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":590,\"y\":100}],\"types\":[],\"differentialEquations\":[],\"parameters\":[]},\"extensions\":{\"colors\":true,\"stochasticity\":true,\"dynamics\":true,\"parameters\":true,\"subnets\":true}}},{\"toolCallId\":\"toolu_01KLHzRE7gbPbFfPaXe3RTry\",\"toolName\":\"addArc\",\"output\":{\"title\":\"Added input arc\",\"detail\":\"Dispatch crew available <-> Start final inspection\",\"target\":{\"kind\":\"selection\",\"item\":{\"type\":\"arc\",\"id\":\"$A_place:dispatch-crew-available___start-final-inspection\"}},\"applied\":true}},{\"toolCallId\":\"toolu_01LuBH9HSYSzvkCMq5E3PN57\",\"toolName\":\"getLatestNetDefinition\",\"output\":{\"title\":\"Prepared final inspection and dispatch\",\"definition\":{\"places\":[{\"id\":\"batch-ready\",\"name\":\"Batch ready\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":80,\"y\":100},{\"id\":\"under-final-inspection\",\"name\":\"Under final inspection\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":100},{\"id\":\"ready-for-dispatch\",\"name\":\"Ready for dispatch\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":760,\"y\":100},{\"id\":\"dispatch-crew-available\",\"name\":\"Dispatch crew available\",\"colorId\":null,\"dynamicsEnabled\":false,\"differentialEquationId\":null,\"x\":420,\"y\":360}],\"transitions\":[{\"id\":\"start-final-inspection\",\"name\":\"Start final inspection\",\"inputArcs\":[{\"placeId\":\"batch-ready\",\"weight\":1,\"type\":\"standard\"},{\"type\":\"standard\",\"placeId\":\"dispatch-crew-available\",\"weight\":1}],\"outputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":250,\"y\":100},{\"id\":\"sign-off\",\"name\":\"Sign-off\",\"inputArcs\":[{\"placeId\":\"under-final-inspection\",\"weight\":1,\"type\":\"standard\"}],\"outputArcs\":[{\"placeId\":\"ready-for-dispatch\",\"weight\":1},{\"placeId\":\"dispatch-crew-available\",\"weight\":1}],\"lambdaType\":\"predicate\",\"lambdaCode\":\"\",\"transitionKernelCode\":\"\",\"x\":590,\"y\":100}],\"types\":[],\"differentialEquations\":[],\"parameters\":[]},\"extensions\":{\"colors\":true,\"stochasticity\":true,\"dynamics\":true,\"parameters\":true,\"subnets\":true}}}]", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NQGRAKWG69MWYMPYTCFXCZ", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_6dbc187e4e72793b7539c02387d04715", + "turnId": "turn_01M1NQGQH38A3MMGGEWKN4VCDP", + "parts": [ + { + "type": "reasoning", + "text": "Perfect! The verification confirms the arc has been successfully added. Looking at the final definition:\n\nThe `start-final-inspection` transition now has two input arcs:\n1. `{\"placeId\":\"batch-ready\",\"weight\":1,\"type\":\"standard\"}`\n2. `{\"type\":\"standard\",\"placeId\":\"dispatch-crew-available\",\"weight\":1}`\n\nThis means:\n- Starting final inspection requires 1 token from \"batch-ready\" AND 1 token from \"dispatch-crew-available\"\n- It produces 1 token to \"under-final-inspection\"\n- The sign-off transition consumes the batch from inspection and returns 1 crew token\n\nThis satisfies the operational constraint: exactly one dispatch crew is consumed when inspection begins and released when sign-off completes.\n\nNow I need to emit the full revised runbook-ir workpiece as requested. I should update the prepared fixture material with:\n1. The operational account (confirmed)\n2. The current Petrinaut correspondence (UPDATED - the missing arc has been added)\n3. Explicit unknowns (unchanged)\n4. The claim boundary (adjusted to reflect what this correction establishes)", + "state": "done" + }, + { + "type": "text", + "text": "**Verification successful.** The `start-final-inspection` transition now contains the corrected input arcs:\n```json\n\"inputArcs\":[\n {\"placeId\":\"batch-ready\",\"weight\":1,\"type\":\"standard\"},\n {\"type\":\"standard\",\"placeId\":\"dispatch-crew-available\",\"weight\":1}\n]\n```\n\nThe resource constraint is now satisfied: firing `start-final-inspection` requires both a ready batch AND an available crew token. The `sign-off` transition releases that crew back to `dispatch-crew-available`.\n\n---\n\n## Revised workpiece (runbook-ir)\n\n```runbook-ir\n# Final inspection and dispatch workpiece — Revised\n\n## Purpose and posture\nModel the batch path from final inspection to dispatch readiness with explicit crew resource management. This revision adds the missing crew-consumption arc to enforce the resource constraint.\n\n## Operational account\n- A batch that is ready enters final inspection.\n- Starting final inspection requires both a ready batch AND the sole available dispatch crew.\n- Starting final inspection consumes that crew token.\n- Sign-off releases that crew and makes the batch ready for dispatch.\n\n## Quantity and resource policy\nExactly one dispatch crew is available. Starting final inspection consumes that one available crew; sign-off returns it. No transition fires without sufficient tokens in all input places.\n\n## Petrinaut correspondence\nThe net contains four places:\n- `batch-ready`: batches awaiting final inspection\n- `under-final-inspection`: batches currently in inspection\n- `ready-for-dispatch`: batches cleared for dispatch\n- `dispatch-crew-available`: crew availability (1 token when free)\n\nTransitions:\n- `start-final-inspection`: requires 1 token from batch-ready AND 1 token from dispatch-crew-available; produces 1 token to under-final-inspection\n- `sign-off`: requires 1 token from under-final-inspection; produces 1 token to ready-for-dispatch AND 1 token to dispatch-crew-available\n\nThe missing standard weight-1 input arc from `dispatch-crew-available` to `start-final-inspection` has been **added and verified** in the live Petrinaut definition.\n\n## Explicit unknowns\n- Inspection timing: duration, stochasticity, or determinism remain unresolved\n- Sign-off timing: duration, stochasticity, or determinism remain unresolved\n- Failure modes: whether inspection can fail, halt, or reject a batch\n- Recovery behavior: how failures or rejections affect batch state or crew availability\n\n## Claim boundary\nThis workpiece and the corrected net establish only:\n- The crew-consumption constraint at start of inspection\n- The crew-release at sign-off completion\n- Token flow from ready batch through inspection to dispatch readiness\n\nThis is test-authored diagnostic material for the narrow final-inspection–to-dispatch path. It does not establish capture provenance, behavioral execution, failure handling, full process projection, or integration with upstream or downstream operations.\n```\n\nThe correction is complete and verified in the live Petrinaut definition.", + "state": "done" + } + ] + }, + { + "id": "entry_direct_c3ViX2lrXzg4NDFkMzZmMWUyZTRiYTlmMzk4NTJiZTU0ZDJlMTc0", + "role": "user", + "purpose": "user", + "display": "visible", + "submissionId": "sub_ik_8841d36f1e2e4ba9f39852be54d2e174", + "parts": [ + { + "type": "text", + "text": "From the resumed workpiece, list the unresolved timing, failure, and recovery questions. Do not change the Petrinaut net.", + "state": "done" + } + ] + }, + { + "id": "entry_01M1NQJG7GRPC479PRF826T6F3", + "role": "assistant", + "purpose": "assistant", + "display": "visible", + "submissionId": "sub_ik_8841d36f1e2e4ba9f39852be54d2e174", + "turnId": "turn_01M1NQJFBBXYRZE472GFGHQF4K", + "parts": [ + { + "type": "reasoning", + "text": "The user wants me to extract and list the unresolved questions from the \"Explicit unknowns\" and \"Claim boundary\" sections of the workpiece I just delivered. They explicitly don't want me to change the Petrinaut net, just to articulate the open questions.\n\nLet me pull out the specific questions that remain unresolved:\n\nFrom \"Explicit unknowns\":\n1. Inspection timing: duration, stochasticity, or determinism remain unresolved\n2. Sign-off timing: duration, stochasticity, or determinism remain unresolved\n3. Failure modes: whether inspection can fail, halt, or reject a batch\n4. Recovery behavior: how failures or rejections affect batch state or crew availability\n\nI should unpack these into concrete questions that would need answering in a real operational investigation.", + "state": "done" + }, + { + "type": "text", + "text": "## Unresolved questions from the workpiece\n\n### Inspection timing\n1. How long does final inspection take—is it a fixed duration or variable?\n2. If variable, what distribution describes inspection duration (exponential, log-normal, empirical)?\n3. Does inspection timing depend on batch characteristics (size, composition, prior history)?\n4. Is inspection deterministic or does it include randomness?\n\n### Sign-off timing\n1. How long does sign-off take after inspection completes—is it immediate, fixed, or variable?\n2. If variable, what distribution describes sign-off duration?\n3. Does sign-off timing depend on inspection outcome or batch properties?\n4. Is sign-off deterministic or does it include randomness?\n\n### Failure modes\n1. Can a batch fail final inspection? If so, what triggers failure?\n2. Can inspection halt or pause without completing (e.g., awaiting additional information)?\n3. Can a batch be rejected at sign-off after passing inspection?\n4. Can the crew become unavailable during inspection (unavoidable absence, equipment failure, reassignment)?\n\n### Recovery behavior\n1. If a batch fails inspection, where does it go—back to ready, to a separate rework queue, or discarded?\n2. If a batch fails, does the crew become available immediately or does recovery consume crew time?\n3. If inspection halts mid-process, does the crew remain locked or become available?\n4. If rejected at sign-off, does the batch cycle back through inspection or follow a different path?\n5. If the crew becomes unavailable mid-inspection, what happens to the batch and the crew state?\n\nThese questions determine whether timing needs stochastic transitions, whether the batch path branches on failure, and how crew availability and batch state interact under exceptions. The current net cannot represent any of these behaviors.", + "state": "done" + } + ] + } + ], + "settlements": [ + { + "submissionId": "sub_ik_f24939bebb841ffe1469a7f3a4a06915", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_f24939bebb841ffe1469a7f3a4a06915" + }, + { + "submissionId": "sub_ik_b7a274b7c2825d4c0914da440e241cb6", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_b7a274b7c2825d4c0914da440e241cb6" + }, + { + "submissionId": "sub_ik_dd2eb7be21980998b89ae9a9da085fb2", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_dd2eb7be21980998b89ae9a9da085fb2" + }, + { + "submissionId": "sub_ik_008fa6d7354749a1cb3f674184e7cb1b", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_008fa6d7354749a1cb3f674184e7cb1b" + }, + { + "submissionId": "sub_ik_6dbc187e4e72793b7539c02387d04715", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_6dbc187e4e72793b7539c02387d04715" + }, + { + "submissionId": "sub_ik_8841d36f1e2e4ba9f39852be54d2e174", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_8841d36f1e2e4ba9f39852be54d2e174" + } + ], + "incarnation": "inc_01M1NQEXM24JHNYN40Q8RA5DH5" +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/latest-workpiece.md b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/latest-workpiece.md new file mode 100644 index 00000000000..be2a95f9fc4 --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/latest-workpiece.md @@ -0,0 +1,40 @@ +# Final inspection and dispatch workpiece — Revised + +## Purpose and posture +Model the batch path from final inspection to dispatch readiness with explicit crew resource management. This revision adds the missing crew-consumption arc to enforce the resource constraint. + +## Operational account +- A batch that is ready enters final inspection. +- Starting final inspection requires both a ready batch AND the sole available dispatch crew. +- Starting final inspection consumes that crew token. +- Sign-off releases that crew and makes the batch ready for dispatch. + +## Quantity and resource policy +Exactly one dispatch crew is available. Starting final inspection consumes that one available crew; sign-off returns it. No transition fires without sufficient tokens in all input places. + +## Petrinaut correspondence +The net contains four places: +- `batch-ready`: batches awaiting final inspection +- `under-final-inspection`: batches currently in inspection +- `ready-for-dispatch`: batches cleared for dispatch +- `dispatch-crew-available`: crew availability (1 token when free) + +Transitions: +- `start-final-inspection`: requires 1 token from batch-ready AND 1 token from dispatch-crew-available; produces 1 token to under-final-inspection +- `sign-off`: requires 1 token from under-final-inspection; produces 1 token to ready-for-dispatch AND 1 token to dispatch-crew-available + +The missing standard weight-1 input arc from `dispatch-crew-available` to `start-final-inspection` has been **added and verified** in the live Petrinaut definition. + +## Explicit unknowns +- Inspection timing: duration, stochasticity, or determinism remain unresolved +- Sign-off timing: duration, stochasticity, or determinism remain unresolved +- Failure modes: whether inspection can fail, halt, or reject a batch +- Recovery behavior: how failures or rejections affect batch state or crew availability + +## Claim boundary +This workpiece and the corrected net establish only: +- The crew-consumption constraint at start of inspection +- The crew-release at sign-off completion +- Token flow from ready batch through inspection to dispatch readiness + +This is test-authored diagnostic material for the narrow final-inspection–to-dispatch path. It does not establish capture provenance, behavioral execution, failure handling, full process projection, or integration with upstream or downstream operations. diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/prepared-workpiece.md b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/prepared-workpiece.md new file mode 100644 index 00000000000..fa01def8c11 --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/prepared-workpiece.md @@ -0,0 +1,21 @@ +# Final inspection and dispatch workpiece + +## Purpose and posture +Maintain the narrow batch path from final inspection to dispatch readiness and test one evidence-backed correction against the live Petrinaut document. + +## Operational account +- A batch that is ready enters final inspection. +- Final inspection reserves the sole available dispatch crew. +- Sign-off releases that crew and makes the batch ready for dispatch. + +## Quantity and resource policy +Exactly one dispatch crew is available in this fixture. Starting final inspection consumes that one available crew; sign-off returns it. + +## Current Petrinaut correspondence +The prepared non-empty net contains the batch path and the crew return from sign-off. It deliberately lacks the standard weight-1 input arc from `Dispatch crew available` to `Start final inspection`. + +## Explicit unknowns +Inspection and sign-off timing, failure modes, and recovery behavior remain unresolved. + +## Claim boundary +This prepared revision is test-authored diagnostic material. It is not model-produced evidence and does not establish capture provenance, behavioral execution, or broad projection quality. diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/route-evidence.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/route-evidence.json new file mode 100644 index 00000000000..da12d3d008a --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/route-evidence.json @@ -0,0 +1,7 @@ +{ + "origin": "http://127.0.0.1:4915", + "historyRoute": "/agents/chat/?view=history", + "status": 200, + "retainedAuthorizationHeaders": false, + "retainedProviderPayloads": false +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/screenshot-tab-a-after.png b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/screenshot-tab-a-after.png new file mode 100644 index 00000000000..0f42bfeaebf Binary files /dev/null and b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/screenshot-tab-a-after.png differ diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/screenshot-tab-a-before.png b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/screenshot-tab-a-before.png new file mode 100644 index 00000000000..142e160b12b Binary files /dev/null and b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/screenshot-tab-a-before.png differ diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/screenshot-tab-b-after.png b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/screenshot-tab-b-after.png new file mode 100644 index 00000000000..5527acbca57 Binary files /dev/null and b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/screenshot-tab-b-after.png differ diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/settled-manifest-after.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/settled-manifest-after.json new file mode 100644 index 00000000000..cd937a8620a --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/settled-manifest-after.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "fixtureId": "crew-reservation-v1", + "revision": 1, + "settledAt": "2026-09-04T08:10:58.162Z", + "conversation": { + "logicalId": "mission-6-crew-reservation-conversation-v1", + "canonicalId": "conv_01M1NQEXM3CAPPTXM33ZE1YSRG", + "offset": "0000000000000000_0000000000000108" + }, + "latestWorkpiece": { + "authorship": "model-produced", + "contentSha256": "785135be03f8cbe9156b835f34d463bf7111fc109dd90dbe9db55955670c050e", + "sourceKind": "assistant", + "sourceMessageId": "entry_01M1NQGRAKWG69MWYMPYTCFXCZ", + "sourceMessageSha256": "5c5645d0f63c9792ece099228cbc021e117853108a1501c0cc230334eb2a6af8", + "sourceSubmissionId": "sub_ik_6dbc187e4e72793b7539c02387d04715" + }, + "document": { + "id": "mission-6-crew-reservation-document-v1", + "sha256": "3c47961d02296c00131644d1aea0dac16a017f470a66aea919fcf324a2bc9e37", + "targetArc": "present" + }, + "manifestId": "a00f05964afc87b1ef34b50c96711232ac44c5b60eb6e8ce60a540af3b5dab49" +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/settled-manifest-before.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/settled-manifest-before.json new file mode 100644 index 00000000000..48e8013a09f --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/settled-manifest-before.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "fixtureId": "crew-reservation-v1", + "revision": 0, + "settledAt": "2026-09-04T08:10:01.220Z", + "conversation": { + "logicalId": "mission-6-crew-reservation-conversation-v1", + "canonicalId": "conv_01M1NQEXM3CAPPTXM33ZE1YSRG", + "offset": "0000000000000000_0000000000000043" + }, + "latestWorkpiece": { + "authorship": "test-authored", + "contentSha256": "1cc7a1b5d961f9f6327b458cf8292703ced5627fe26ee3f7b878f6375e51501a", + "sourceKind": "prepared-signal", + "sourceMessageId": "entry_direct_c3ViX2lrX2YyNDkzOWJlYmI4NDFmZmUxNDY5YTdmM2E0YTA2OTE1", + "sourceMessageSha256": "05003ea859f0658266d92b24eeaca0103aa75d2c4a3ec4eddaffe606b6775fc6", + "sourceSubmissionId": "sub_ik_f24939bebb841ffe1469a7f3a4a06915" + }, + "document": { + "id": "mission-6-crew-reservation-document-v1", + "sha256": "8dfa723b8dabadad790d2552de8e191e4227b07fe6c4d4e9d8e2d365e6ec4abd", + "targetArc": "absent" + }, + "manifestId": "fef5b371de498d5c2e6bb0456878c21aeb05a0f68957cde4a859f194b5122fab" +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/settled-manifest-tab-b.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/settled-manifest-tab-b.json new file mode 100644 index 00000000000..cd937a8620a --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/settled-manifest-tab-b.json @@ -0,0 +1,25 @@ +{ + "version": 1, + "fixtureId": "crew-reservation-v1", + "revision": 1, + "settledAt": "2026-09-04T08:10:58.162Z", + "conversation": { + "logicalId": "mission-6-crew-reservation-conversation-v1", + "canonicalId": "conv_01M1NQEXM3CAPPTXM33ZE1YSRG", + "offset": "0000000000000000_0000000000000108" + }, + "latestWorkpiece": { + "authorship": "model-produced", + "contentSha256": "785135be03f8cbe9156b835f34d463bf7111fc109dd90dbe9db55955670c050e", + "sourceKind": "assistant", + "sourceMessageId": "entry_01M1NQGRAKWG69MWYMPYTCFXCZ", + "sourceMessageSha256": "5c5645d0f63c9792ece099228cbc021e117853108a1501c0cc230334eb2a6af8", + "sourceSubmissionId": "sub_ik_6dbc187e4e72793b7539c02387d04715" + }, + "document": { + "id": "mission-6-crew-reservation-document-v1", + "sha256": "3c47961d02296c00131644d1aea0dac16a017f470a66aea919fcf324a2bc9e37", + "targetArc": "present" + }, + "manifestId": "a00f05964afc87b1ef34b50c96711232ac44c5b60eb6e8ce60a540af3b5dab49" +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/tab-b-correlation.json b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/tab-b-correlation.json new file mode 100644 index 00000000000..d5c02471672 --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/tab-b-correlation.json @@ -0,0 +1,18 @@ +{ + "conversationId": "conv_01M1NQEXM3CAPPTXM33ZE1YSRG", + "offset": "0000000000000000_0000000000000123", + "manifestId": "a00f05964afc87b1ef34b50c96711232ac44c5b60eb6e8ce60a540af3b5dab49", + "documentSha256": "3c47961d02296c00131644d1aea0dac16a017f470a66aea919fcf324a2bc9e37", + "workpieceSha256": "785135be03f8cbe9156b835f34d463bf7111fc109dd90dbe9db55955670c050e", + "preparedSourceCount": 1, + "addArcCallCount": 1, + "followUp": { + "submissionId": "sub_ik_8841d36f1e2e4ba9f39852be54d2e174", + "responseMessageId": "entry_01M1NQJG7GRPC479PRF826T6F3", + "settlement": { + "submissionId": "sub_ik_8841d36f1e2e4ba9f39852be54d2e174", + "outcome": "completed", + "answeredBySubmissionId": "sub_ik_8841d36f1e2e4ba9f39852be54d2e174" + } + } +} diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/witness.md b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/witness.md new file mode 100644 index 00000000000..87c7d8d2fc5 --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-outer-browser-witness-2026-09-04/witness.md @@ -0,0 +1,90 @@ +# FE-1575 outer browser witness — 2026-09-04 + +## Scope + +This is the retained outer mechanical witness for Mission 6 at commit +`ace2968`. It used a clean browser principal in one Playwright context, the +stable `crew-reservation-v1` fixture route, the local Brunch Flue mount, and a +real configured provider credential. Credentials, authorization headers, the +browser principal, the Flue instance route component, and provider request +payloads are not retained. + +The provider serialized the `addArc` weight as `"1"`. The witnessed build +normalized that finite numeric string at the Petrinaut tool boundary before +canonical validation and browser execution. The retained raw Flue snapshot +preserves the provider-supplied input; the resulting Petrinaut definition +preserves the canonical numeric weight `1`. + +## Protocol and result + +1. Started `yarn dev:brunch` after loading `.env.local` without printing it. +2. Cleared browser local storage, opened + `http://127.0.0.1:4915/?brunch-fixture=crew-reservation-v1`, and waited for + settled revision zero. +3. Retained the before Flue snapshot, canonical definition, runtime manifest, + and Tab A screenshot. +4. Submitted one confirmation/construction turn: + + > Confirmed: final inspection uses the single dispatch crew and sign-off + > releases it; timing, failure, and recovery remain unknown. Read the live + > Petrinaut definition, add the missing standard weight-1 input arc from + > Dispatch crew available to Start final inspection, verify it, and emit + > the full revised runbook-ir workpiece. + +5. Observed one `addArc` call and one correlated successful client-tool result: + `toolu_01KLHzRE7gbPbFfPaXe3RTry`. +6. Verified that the only semantic definition delta was one standard, + weight-1 input arc from `dispatch-crew-available` to + `start-final-inspection`. +7. Observed runtime manifest revision 1 selecting the model-produced workpiece + and changed document, with target arc `present`. +8. Opened Tab B in the same browser context. It selected the same manifest, + workpiece hash, document hash, and canonical conversation, with exactly one + prepared source and one `addArc` call. +9. Submitted a non-mutating follow-up in Tab B: + + > From the resumed workpiece, list the unresolved timing, failure, and + > recovery questions. Do not change the Petrinaut net. + +10. Observed completed submission + `sub_ik_8841d36f1e2e4ba9f39852be54d2e174` and correlated response message + `entry_01M1NQJG7GRPC479PRF826T6F3`. The document and settled manifest were + unchanged. + +## Retained identities and invariants + +- Canonical conversation: `conv_01M1NQEXM3CAPPTXM33ZE1YSRG` +- Settled manifest revision: `1` +- Settled manifest ID: + `a00f05964afc87b1ef34b50c96711232ac44c5b60eb6e8ce60a540af3b5dab49` +- Prepared source count after Tab B: `1` +- `addArc` call count after Tab B: `1` +- Tab B follow-up outcome: `completed` +- `definition-after.json` and `definition-tab-b.json` have the same SHA-256. +- `settled-manifest-after.json` and `settled-manifest-tab-b.json` have the same + SHA-256. + +## Artifacts + +- Before state: [Flue](flue-snapshot-before.json), + [definition](definition-before.json), + [manifest](settled-manifest-before.json), + [screenshot](screenshot-tab-a-before.png) +- Settled Tab A state: [Flue](flue-snapshot-after.json), + [definition](definition-after.json), + [manifest](settled-manifest-after.json), + [call/result correlation](call-result-correlation.json), + [screenshot](screenshot-tab-a-after.png) +- Tab B continuation: [Flue](flue-snapshot-tab-b.json), + [definition](definition-tab-b.json), + [manifest](settled-manifest-tab-b.json), + [correlation](tab-b-correlation.json), + [screenshot](screenshot-tab-b-after.png) +- Semantic inputs: [prepared workpiece](prepared-workpiece.md), + [latest workpiece](latest-workpiece.md) +- Redacted route observation: [route evidence](route-evidence.json) +- Integrity: [SHA256SUMS](SHA256SUMS) + +This witness proves the bounded browser protocol above. It does not establish +capture provenance, timing behavior, failure/recovery behavior, simulation +validity, or broad automatic projection quality. diff --git a/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-resumable-workpiece-petrinaut.md b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-resumable-workpiece-petrinaut.md new file mode 100644 index 00000000000..dc09d5ccdf6 --- /dev/null +++ b/libs/@hashintel/brunch-agent/docs/evidence/implementations/fe-1575-resumable-workpiece-petrinaut.md @@ -0,0 +1,63 @@ +# FE-1575 — resumable workpiece and Petrinaut document + +## Deterministic implementation evidence + +The prepared crew-reservation fixture uses distinct fixture, logical +conversation, canonical Flue conversation, workpiece-source, and Petrinaut +document identities. Revision zero is delivered through the public mounted +Flue route as one `prepared-fixture` system/dispatch signal with a deterministic +idempotency key. The browser transport derives stable keys for typed messages +and correlated client-tool-result signals. + +Focused tests cover: + +- exact prepared-signal retry and append-only workpiece selection; +- fixture-only `getLatestNetDefinition` and `addArc` advertisement; +- the built agent's read, mutation, original call-id result, and continuation; +- rejected and duplicate/no-op canonical browser mutations; +- exact prepared and revised document structure; +- history, workpiece, mutation-result, and document mismatch refusal; and +- content-addressed selection of the prior coherent document revision while a + partial mirrored value remains inspectable. + +The affected Brunch, transport, plugin, Petrinaut, and website builds, type +checks, and lint checks passed on 2026-09-04. The app-wide lint checks retain +pre-existing warning-only findings; no persona suite was run. + +## Live two-tab browser witness + +The corrected 2026-09-04 witness is retained in [fe-1575-outer-browser-witness-2026-09-04-r2](fe-1575-outer-browser-witness-2026-09-04-r2/witness.md). It used the production dev processes underlying `yarn dev:brunch`, one fresh Playwright browser context, the mounted `/agents/chat/:instanceId` route, a real configured provider credential, and the stable fixture URL: + +```text +http://127.0.0.1:4915/?brunch-fixture=crew-reservation-v1 +``` + +The clean run created canonical conversation `conv_01M1NV5WZETMYEGGMFXNYDSTRS` and exactly one tagged prepared source. Tab A advanced from settled revision zero with the target arc absent to revision 1 with a model-produced workpiece and the target arc present. It retained one `addArc` call and one unique correlated successful result, `toolu_01BQukCZTAhJ64VNE7oC1CWG`, materialized in two cumulative signal deliveries without applying a second arc. Mechanical comparison found exactly one semantic document change: a standard weight-1 input arc from `dispatch-crew-available` to `start-final-inspection`. + +Tab B reopened the same manifest, workpiece hash, document hash, and canonical conversation. It submitted a non-mutating follow-up and received completed correlated response `entry_01M1NV73Z110CY393GEB8T02SH` without another prepared source or `addArc` call. The post-Tab-A and Tab-B definitions and manifests have identical hashes. + +The provider serialized the arc weight as `"1"`. The corrected correlation artifact retains that raw input and the post-normalization parsed input with numeric weight `1`; no broader nested input normalization remains. The selected assistant workpiece explicitly labels revision 1 as model-produced from test-authored revision zero and preserves the fixture's non-claims. The earlier HTTP 401 remains historical authentication/environment evidence only, not a carrier/schema conclusion. + +The first [2026-09-04 witness](fe-1575-outer-browser-witness-2026-09-04/witness.md) remains immutable historical evidence but is superseded for acceptance: its model-produced workpiece incorrectly called itself test-authored and its correlation artifact omitted the parsed canonical input. + +## Remaining human checks + +Cold-reader semantic adjudication and the product-manager demo remain separate +human gates. They have not been replaced with persona or agent testing. + +### Cold-reader handoff + +Give the reviewer only the prepared and model-revised workpieces plus their +tagged Flue records. Ask them to identify authorship, the exactly-one-crew +policy, the intended reservation and return, and every unresolved timing, +failure, and recovery point. Fail the check if the reader attributes revision +zero to the model or infers unsupported behavioral execution. + +### Product-manager handoff + +With a valid live provider credential, have the reviewer open the labelled +fixture in Tab A, confirm the visible non-claims, submit the crew-reservation +fact once, and wait for a settled bundle. They must inspect the exact added +weight-1 input arc, open the same fixture in Tab B, verify matching identities +and hashes, submit a follow-up, and receive its correlated Brunch response +without a duplicate prepared submission. A read-only Tab B does not pass. diff --git a/libs/@hashintel/brunch-agent/docs/mission-drafts/6-resumable-workpiece-petrinaut-fixture.md b/libs/@hashintel/brunch-agent/docs/mission-drafts/6-resumable-workpiece-petrinaut-fixture.md deleted file mode 100644 index ce5541ecd89..00000000000 --- a/libs/@hashintel/brunch-agent/docs/mission-drafts/6-resumable-workpiece-petrinaut-fixture.md +++ /dev/null @@ -1,164 +0,0 @@ -# Draft Mission 6 — Resumable workpiece-to-Petrinaut fixture tracer - -> Draft cluster only. Not execution authority. Do not implement until this cluster is re-evaluated and cut into `MISSION.md`. - -## Cold-start reads - -- [FE-1575](https://linear.app/hash/issue/FE-1575/resume-one-brunch-workpiece-and-petrinaut-document-across-tabs) — tracker projection for this future branch mission; the eventual branch `MISSION.md` remains execution authority. -- [`../../MISSION.md`](../../MISSION.md) — live Mission 5 Voice authority on this branch; it supplies no Mission 6 execution authority and does not change Mission 4's explicit absence of a full-run candidate. -- [`../../MISSION.next.md`](../../MISSION.next.md) — shared workpiece, projection, evidence, and product constraints. -- [`../mission-archive/3-structurally-typed-runbook-to-headless-pn.md`](../mission-archive/3-structurally-typed-runbook-to-headless-pn.md) and [`../evidence/implementations/fe-1525-headless-runbook-pn.md`](../evidence/implementations/fe-1525-headless-runbook-pn.md) — accepted workpiece viability, hermetic callback route, and failed real-model nested-schema carrier. -- [`../mission-archive/4-core-plugin-elicitation-proof-of-life.md`](../mission-archive/4-core-plugin-elicitation-proof-of-life.md) — accepted core/plugin architecture and exact proof exclusions. -- [`../../packages/plugin-sdcpn/src/flue.ts`](../../packages/plugin-sdcpn/src/flue.ts), [`../../packages/plugin-sdcpn/src/tools/petrinaut-construction.ts`](../../packages/plugin-sdcpn/src/tools/petrinaut-construction.ts), and Petrinaut's canonical AI/action schemas — current read and bounded construction capabilities. -- [`../../../../../apps/brunch-agent/src/conversation/client-tools.ts`](../../../../../apps/brunch-agent/src/conversation/client-tools.ts), [`../../../../../apps/brunch-agent/src/conversation/ui-stream.ts`](../../../../../apps/brunch-agent/src/conversation/ui-stream.ts), and the current browser host — existing client-tool carriage and result correlation; reuse semantics without requiring the old chat UI. -- Flue `FlueClient` documentation for stable conversation history, observation, submission reattachment, and tool-part materialization. - -## Visible product advance - -**Release note:** Brunch edits the Petrinaut net you are looking at from the conversation, and your work survives closing the tab. - -**Demo script (no engineer present):** open the stable demo fixture; the canonical Brunch conversation, current Markdown workpiece, and associated Petrinaut document come back together. Tell Brunch one new realistic thing about the process. Watch the workpiece update and a meaningful change appear in the live net. Save. Open the same fixture in a second tab and continue the conversation from the saved state. - -**Previously impossible:** Brunch only produced off-canvas net JSON for manual load; nothing it did touched the live document or survived a reload. - -The fixture may be deliberately prepared. It need not be a complete persona-produced Mission 4 artifact, a promoted quality baseline, or proof of comprehensive provenance. Prepared status, authored material, limitations, and any model-produced updates remain explicit, and the demo script says so out loud. - -**Completion:** the mission is done when a product manager can run the demo script end to end at the readiness gate below, not when the first browser mutation lands. - -## Contract stratum - -Close the **single-fixture viability stratum** for these two transformations and their resumable product boundary: - -```text -canonical conversation evidence → maintained Markdown workpiece -maintained Markdown workpiece → meaningful Petrinaut read/write change -``` - -The fixture has separate stable identities for the demo case, conversation, current workpiece revision, and Petrinaut document revision. One id must not impersonate all four lifecycles. A small manifest records their relationships, exact prepared inputs, current coherent bundle revision, and hashes or revision tokens needed to detect stale state. - -The minimum fixture contains one process spine, one shared or constrained resource, one decision/policy, one contextual quantity, one explicit unknown, and enough meaning to change a small non-empty net region. It does not require a comprehensive typed domain IR, assertion-card ontology, graph database, or full provenance ledger. - -## Boundary crossings and current throughline hypothesis - -```text -stable demo fixture id -→ resolve conversation id + workpiece revision + Petrinaut document id/revision -→ hydrate canonical Flue history and current Markdown workpiece -→ one realistic evidence turn updates the workpiece at an explicit phase boundary -→ Brunch reads current Petrinaut state through a browser-owned client tool -→ SDCPN skill interprets the current workpiece, not the transcript as projection IR -→ Brunch requests the least canonical Petrinaut mutation(s) -→ browser validates and executes against the bound document -→ correlated client-tool result resumes the same Flue conversation -→ verify meaningful non-empty state -→ publish a new coherent fixture revision only after workpiece and document saves succeed -→ second tab resolves and resumes that revision -``` - -A partial save remains visible and does not advance the fixture's current coherent revision. The first tracer does not require simultaneous multi-tab collaboration or a distributed transaction service. - -## Throughline proof floor - -For one deliberately prepared fixture: - -1. a cold reader can reconstruct the selected operational spine and distinguish supplied evidence, agent inference/assumption, and the explicit unknown in the current Markdown workpiece; -2. one new realistic conversation turn produces an inspectable workpiece revision without erasing the unknown or unsupported meaning; -3. through the real browser client-tool boundary, Brunch reads the associated Petrinaut document and applies one meaningful supported change derived from the current workpiece; -4. canonical Petrinaut state is non-empty and visibly corresponds to the selected meaning; and -5. after save, a second tab opens the same fixture id, observes the same settled conversation/workpiece/document revision, and successfully continues or reads it without duplicate submission or identity drift. - -One pass through those five steps is the first internal milestone, not mission completion. The retained oracles are the stable demo URL or fixture selector plus the before/after fixture manifest, exact Flue snapshot, Markdown workpiece revisions, and canonical Petrinaut document revisions; they are evidence for the builder, not the visible advance. This proves viability, not automatic full-net projection, selected-pair provenance breadth, remote replacement durability, concurrent editing, or Mission 3/4 quality superiority. - -## Readiness ratchet - -### Inherited stratum closure - -- Consume Mission 4's accepted `useBrunchAgent()` + `useSdcpnPlugin()` architecture and no broader quality claim. -- Consume Petrinaut-owned schemas/mutations mechanically; parser acceptance alone remains vacuous. -- Treat Flue history as canonical and client-tool results as correlated execution evidence. -- Preserve the Mission 3 failure: provider-visible nested mutation shapes are unproved and may force a smaller first mutation or a crisp upstream blocker. - -### Readiness gate after the new throughline - -This gate is the mission's completion bar: the demo script above must work for the named fixture. Before accepting this single-fixture capability, close stale fixture/workpiece/document revision refusal, duplicate tool delivery, read/write failure visibility, unsupported meaning, no-op mutation honesty, partial-save behavior, second-tab rehydration, separate identity integrity, and one negative mutation case. Do not close every consequential-element provenance link, remote task replacement, broad scenario coverage, or repeated automatic projection here; those become Mission 7 or Mission 9 obligations only after this tracer exposes a finite peer set and load-bearing seams. - -## Candidate evidence and oracles - -| Claim leaf | Candidate oracle | -| --- | --- | -| Prepared fixture is honest and minimally sufficient | Frozen manifest plus cold-reader adjudication identifies the spine, resource, policy, quantity, unknown, authorship, and preparation route. | -| Conversation evidence can maintain Markdown | Before/after workpiece inspection against the exact Flue snapshot detects invention, hardening, lost prior meaning, and lost unknowns. | -| Browser executes real Petrinaut read/write tools | Production-boundary integration records tool call ids, canonical parsed inputs, execution outcomes, correlated result signals, and current document state. | -| Change is meaningful | Human comparison binds one workpiece meaning to a visible canonical type/parameter/place/transition/arc change appropriate to the fixture; non-empty/parser-valid alone fails. | -| Save is coherent | Injected workpiece-save or document-save failure leaves the prior current bundle revision selected and exposes the partial result for recovery. | -| Second tab resumes stable state | Open the same stable fixture selector after save and compare conversation id/history, workpiece revision/hash, document id/revision, and canonical definition before continuing. | -| No typed domain IR was smuggled in | Public-schema and dependency inspection finds only fixture identity/revision links and canonical Petrinaut payloads, not a closed process ontology or typed capture-to-workpiece model. | - -## Verification approach - -- **Inner:** fixture-manifest parse/version/stale checks, explicit identity separation, coherent-revision publication, idempotent client-tool result handling, and canonical Petrinaut mutation tests. -- **Middle:** drive the production Brunch agent through Flue, update the Markdown artifact, execute actual browser callbacks against the fixture-bound Petrinaut instance, and retain before/after artifacts plus one injected failure. -- **Outer:** from the real demo route or equivalent product selector, perform the update/save in Tab A and reopen/continue from the same fixture in Tab B. A headless callback alone does not establish this mission. -- **Semantic:** a cold human judges whether the workpiece remained honest and the changed net region corresponds to it. - -## Inputs and joins - -- This mission may cut directly from Mission 4 and run independently of the direct Voice mission. Typed text is sufficient for its first tracer; Voice can later become another input modality to the same canonical conversation. -- An owner selects one deliberately prepared fixture and records its non-claims. No full Mission 4 candidate or new persona campaign is prerequisite. -- Mission 7 inherits this fixture only if it needs to close capture-backed why/provenance breadth. Mission 9 inherits the browser mutation and semantic-projection seam only if the tracer proves them viable. -- Mission 8's local deployment artifact is terrain, not a prerequisite for a local two-tab viability proof and not evidence of remote durability. - -## Risks and assumptions - -- If a realistic prepared conversation/workpiece cannot support one meaningful mutation without richer typed structure, record the exact lookup, identity, or ambiguity strain before adding any schema. -- If existing per-action provider schemas cannot carry the required nested mutation, reduce to the smallest meaningful supported action only if semantic correspondence survives; otherwise stop with the crisp provider/Flue schema blocker. -- If coherent save cannot span existing workpiece/document stores, the least fixture-scoped commit marker may publish only after both writes; do not invent distributed transactions before a failure demonstrates the need. -- If direct browser tool servicing needs a transport abstraction, extract only tool-call/result correlation from the browser `ChatTransport` in `transport-aisdk` (recut Mission 5); do not require the chat UI or duplicate Flue observation. - -## Accepted constraints and guarded invariants - -- Separate demo, conversation, workpiece, and document identities with explicit links. -- One canonical Flue history; fixture log projections do not become another authority. -- Markdown is the semantic workpiece; no comprehensive typed domain IR. -- Projection consumes the current workpiece, not the transcript as primary IR. -- Petrinaut owns canonical schemas and mutation execution. -- Client tools execute in the browser against the bound document and return the original tool call id. -- Current coherent revision advances only after all required saves succeed. -- Prepared fixture status and unsupported meaning remain visible. -- No claim of remote replacement durability, automatic full projection, provenance breadth, or concurrent collaboration. - -## Cross-cutting obligations - -Preserve exact evidence attribution, visible failure, stable identity, workpiece sufficiency, semantic correspondence, stock-assistant isolation if it remains present, and same-change user documentation for the demo selector/save/resume behavior. - -## Expected touched paths - -```text -libs/@hashintel/brunch-agent/ -├── evaluations or docs/evidence fixture area + one prepared fixture and adjudication -├── packages/core/ ? only minimal workpiece/fixture contracts with a real second consumer -└── packages/plugin-sdcpn/ ~ least read/write capability and guidance needed by the tracer -apps/brunch-agent/ -├── src/agents/chat-agent/ ~ mount accepted fixture read/write path -├── src/conversation/ ~ client-tool correlation without UI-message authority -└── test/ + real Flue/browser-tool integration -libs/@hashintel/petrinaut-core/ ~ canonical contracts only if a source defect is found -libs/@hashintel/petrinaut/src/ ~ stable fixture resolution, save/resume, browser tool execution -``` - -## Fog-line - -- The exact prepared scenario and smallest meaningful document mutation. -- Where the fixture manifest and current coherent-revision marker belong. -- The current Petrinaut document persistence/revision API and whether the demo route already has a stable selector. -- Whether workpiece Markdown is a file, Flue data part, or product document for this tracer; choose the least real persistence boundary that supports two-tab reopen. -- The least browser host for direct Flue client-tool servicing after removal or bypass of the old assistant UI. -- The exact negative save/mutation case and acceptable local-only durability claim. - -## Stop or reorient - -Stop if the tracer requires pretending a Mission 4 candidate exists, makes a prepared fixture look model-produced, conflates all ids, treats parser validity as meaning, submits client-tool results without original correlation, advances the current bundle after a partial save, uses transcript text as the projection IR, or introduces a closed domain ontology before observed strain. Stop at provider-schema or host-persistence blockers rather than widening into Mission 7/9 readiness work. - -## Carried evidence and rejected alternatives - -Mission 3 showed that a Markdown workpiece can be useful and that a hermetic callback can build canonical non-empty state, while falsifying the exercised real-model nested-schema carrier. Mission 4 established capability composition but produced no full-run candidate. This mission intentionally joins those facts with a prepared fixture to test viability before requiring promoted-source quality, complete provenance, or a generalized semantic model. diff --git a/libs/@hashintel/brunch-agent/docs/mission-drafts/7-capture-backed-review.md b/libs/@hashintel/brunch-agent/docs/mission-drafts/7-capture-backed-review.md index 11a1e320719..07ab3f6ca0c 100644 --- a/libs/@hashintel/brunch-agent/docs/mission-drafts/7-capture-backed-review.md +++ b/libs/@hashintel/brunch-agent/docs/mission-drafts/7-capture-backed-review.md @@ -8,8 +8,8 @@ A fresh builder must resolve the current repository and the deployment handoff rather than treating this draft as a specification: -- [`../../MISSION.md`](../../MISSION.md) — live Mission 5 Voice authority on this branch; it supplies no Mission 7 execution authority or conversation/workpiece candidate, and Mission 4 produced no full-run candidate. -- [`6-resumable-workpiece-petrinaut-fixture.md`](6-resumable-workpiece-petrinaut-fixture.md) — independent viability predecessor. If accepted, its deliberately prepared and honestly labelled fixture may become this mission's selected pair after a separate provenance-suitability decision; a complete persona workpiece is not intrinsically required. +- [`../../MISSION.md`](../../MISSION.md) — live Mission 6 viability authority on this branch, stacked on the live Mission 5 transport authority of [FE-1574](https://linear.app/hash/issue/FE-1574/let-voice-speak-through-canonical-brunch-conversations); neither supplies Mission 7 execution authority or a conversation/workpiece candidate, and Mission 4 produced no full-run candidate. +- [FE-1575](https://linear.app/hash/issue/FE-1575/resume-one-brunch-workpiece-and-petrinaut-document-across-tabs) — live Mission 6 viability predecessor. If accepted, its deliberately prepared and honestly labelled fixture may become this mission's selected pair after a separate provenance-suitability decision; at cut time replace this live pointer with the accepted archive/evidence. A complete persona workpiece is not intrinsically required. - [`../../MISSION.next.md`](../../MISSION.next.md) — compact future spine, FE-1476 product frame, shared proof obligations, standing locks, and any later evidence admitted after this draft was written. - [`../mission-archive/2-mechanical-capture-sweep.md`](../mission-archive/2-mechanical-capture-sweep.md) — accepted mechanical capture throughline, exact close evidence, empty-payload boundary, conversation identity, and carried flags. - [`../mission-archive/3-structurally-typed-runbook-to-headless-pn.md`](../mission-archive/3-structurally-typed-runbook-to-headless-pn.md) and [`../evidence/implementations/fe-1525-headless-runbook-pn.md`](../evidence/implementations/fe-1525-headless-runbook-pn.md) — accepted runbook/workpiece leg, falsified real-model construction leg, and the distinction between a hermetic non-empty fixture and vacuous empty-net parser success. diff --git a/libs/@hashintel/brunch-agent/docs/mission-drafts/9-traceable-projection.md b/libs/@hashintel/brunch-agent/docs/mission-drafts/9-traceable-projection.md index 82b84336fc9..8412c4b41dd 100644 --- a/libs/@hashintel/brunch-agent/docs/mission-drafts/9-traceable-projection.md +++ b/libs/@hashintel/brunch-agent/docs/mission-drafts/9-traceable-projection.md @@ -10,7 +10,7 @@ A fresh builder must resolve these authorities and evidence before choosing a me - [`../../MISSION.md`](../../MISSION.md) — live Mission 5 Voice authority on this branch. Mission 9 may be cut only after Mission 7 validly closes its accepted join and a new owner-authorized mission replaces the then-current branch authority. - [`../../MISSION.next.md`](../../MISSION.next.md) — compact future spine, FE-1476 floor, cross-mission obligations, standing locks, and current Mission 10 handoff. -- [`6-resumable-workpiece-petrinaut-fixture.md`](6-resumable-workpiece-petrinaut-fixture.md) and [`7-capture-backed-review.md`](7-capture-backed-review.md) — provisional viability and provenance predecessors. At cut time replace assumptions with their accepted evidence, exact current workpiece/derivation seam, and real browser mutation behavior. +- [`../../MISSION.md`](../../MISSION.md), [FE-1575](https://linear.app/hash/issue/FE-1575/resume-one-brunch-workpiece-and-petrinaut-document-across-tabs), and [`7-capture-backed-review.md`](7-capture-backed-review.md) — live Mission 6 viability authority plus the provisional provenance predecessor. At cut time replace the live pointer and assumptions with accepted evidence, the exact current workpiece/derivation seam, and observed browser mutation behavior. - [`../mission-archive/3-structurally-typed-runbook-to-headless-pn.md`](../mission-archive/3-structurally-typed-runbook-to-headless-pn.md) and [`../evidence/implementations/fe-1525-headless-runbook-pn.md`](../evidence/implementations/fe-1525-headless-runbook-pn.md) — accepted workpiece leg, canonical callback fixture, provider-visible nested-schema failure, vacuous empty-net result, and explicit next-boundary decision. - [`../specs/petrinaut-batched-construction-tools.md`](../specs/petrinaut-batched-construction-tools.md) — candidate `pn_read`/`pn_edit` design input and its corrected transaction, outcome, identity, carrier, and ownership constraints. It does not select batching; this mission repairs the known single-action carrier first and admits a batch only if subsequent probes establish it as the least sufficient mechanism. - [`apps/brunch-agent/src/evaluations/runbook/headless-petrinaut-client.ts`](../../../../../apps/brunch-agent/src/evaluations/runbook/headless-petrinaut-client.ts), [`apps/brunch-agent/test/headless-petrinaut-client.test.ts`](../../../../../apps/brunch-agent/test/headless-petrinaut-client.test.ts), and [`../../packages/plugin-sdcpn/test/construction-tools.test.ts`](../../packages/plugin-sdcpn/test/construction-tools.test.ts) — current bounded six-tool callback route and its limits. diff --git a/libs/@hashintel/brunch-agent/packages/core/package.json b/libs/@hashintel/brunch-agent/packages/core/package.json index ca72a1d986e..1643df67d78 100644 --- a/libs/@hashintel/brunch-agent/packages/core/package.json +++ b/libs/@hashintel/brunch-agent/packages/core/package.json @@ -21,6 +21,10 @@ "./storage": { "types": "./src/storage.ts", "import": "./dist/storage.js" + }, + "./workpiece": { + "types": "./src/workpiece.ts", + "import": "./dist/workpiece.js" } }, "scripts": { diff --git a/libs/@hashintel/brunch-agent/packages/core/src/workpiece.ts b/libs/@hashintel/brunch-agent/packages/core/src/workpiece.ts new file mode 100644 index 00000000000..ab1430aa1dc --- /dev/null +++ b/libs/@hashintel/brunch-agent/packages/core/src/workpiece.ts @@ -0,0 +1,212 @@ +/** + * Substrate-neutral selection of the current Markdown workpiece from an + * append-only conversation projection. + */ + +export const preparedWorkpieceSignalType = "brunch.fixture.prepared"; +export const preparedWorkpieceSignalTag = "prepared-fixture"; +export const preparedWorkpieceAuthorship = "test-authored"; +export const preparedWorkpieceClaimBoundary = "prepared-not-model-produced"; +export const preparedWorkpieceInitialDataMode = "validated-fixture-mutation"; +export const runbookIrFence = "runbook-ir"; + +type WorkpieceTextPart = { + readonly text: string; + readonly type: "text"; +}; + +export interface WorkpieceHistoryMessage { + readonly body?: string; + readonly id: string; + readonly parts: readonly ( + | WorkpieceTextPart + | { readonly type: string; readonly [key: string]: unknown } + )[]; + readonly purpose: string; + readonly role: string; + readonly signal?: { + readonly attributes?: Readonly>; + readonly tagName?: string; + readonly type?: string; + }; + readonly submissionId?: string; +} + +export interface WorkpieceHistory { + readonly conversationId: string; + readonly messages: readonly WorkpieceHistoryMessage[]; +} + +export interface PreparedWorkpieceDelivery { + readonly idempotencyKey: string; + readonly message: { + readonly attributes: { + readonly authorship: typeof preparedWorkpieceAuthorship; + readonly claimBoundary: typeof preparedWorkpieceClaimBoundary; + readonly fixtureId: string; + }; + readonly body: string; + readonly kind: "signal"; + readonly tagName: typeof preparedWorkpieceSignalTag; + readonly type: typeof preparedWorkpieceSignalType; + }; +} + +export interface SelectedRunbookWorkpiece { + readonly authorship: "model-produced" | "test-authored"; + readonly content: string; + readonly fixtureId?: string; + readonly sourceKind: "assistant" | "prepared-signal"; + readonly sourceMessage: WorkpieceHistoryMessage; + readonly sourceMessageId: string; + readonly sourceSubmissionId?: string; +} + +const runbookIrFencePattern = /```runbook-ir\s*\n([\s\S]*?)```/gu; + +export const latestRunbookIrBlock = (text: string): string | undefined => { + const matches = [...text.matchAll(runbookIrFencePattern)]; + const last = matches.at(-1)?.[1]; + return last === undefined ? undefined : last.trim(); +}; + +const textFrom = (message: WorkpieceHistoryMessage): string => + message.parts + .filter((part): part is WorkpieceTextPart => part.type === "text") + .map((part) => part.text) + .join("\n"); + +const preparedFixtureIdFrom = ( + message: WorkpieceHistoryMessage, +): string | undefined => { + const fixtureId = message.signal?.attributes?.fixtureId; + return typeof fixtureId === "string" && fixtureId.length > 0 + ? fixtureId + : undefined; +}; + +const isPreparedWorkpieceMessage = ( + message: WorkpieceHistoryMessage, +): boolean => + message.role === "system" && + message.purpose === "dispatch" && + message.signal?.tagName === preparedWorkpieceSignalTag && + message.signal.attributes?.authorship === preparedWorkpieceAuthorship && + message.signal.attributes.claimBoundary === preparedWorkpieceClaimBoundary && + preparedFixtureIdFrom(message) !== undefined; + +const selectedFrom = ( + message: WorkpieceHistoryMessage, + source: Pick, +): SelectedRunbookWorkpiece | undefined => { + const content = latestRunbookIrBlock(textFrom(message)); + if (content === undefined) return undefined; + + return { + ...source, + content, + sourceMessage: message, + sourceMessageId: message.id, + ...(message.submissionId === undefined + ? {} + : { sourceSubmissionId: message.submissionId }), + }; +}; + +export const createPreparedWorkpieceDelivery = (input: { + readonly body: string; + readonly fixtureId: string; + readonly revision: number; +}): PreparedWorkpieceDelivery => { + if (input.fixtureId.length === 0) { + throw new Error("A prepared workpiece delivery requires a fixture id."); + } + if (latestRunbookIrBlock(input.body) === undefined) { + throw new Error( + "A prepared workpiece delivery requires a full runbook-ir block.", + ); + } + + return { + idempotencyKey: `${preparedWorkpieceSignalTag}:${input.fixtureId}:revision-${input.revision}`, + message: { + kind: "signal", + type: preparedWorkpieceSignalType, + tagName: preparedWorkpieceSignalTag, + body: input.body, + attributes: { + fixtureId: input.fixtureId, + authorship: preparedWorkpieceAuthorship, + claimBoundary: preparedWorkpieceClaimBoundary, + }, + }, + }; +}; + +/** + * Prepared revision zero is a tagged dispatch record. Later assistant + * workpieces win in log order, except for the assistant reply produced by the + * preparation submission itself. + */ +export const selectRunbookWorkpiece = ( + history: WorkpieceHistory, +): SelectedRunbookWorkpiece | undefined => { + const preparedCandidates = history.messages.filter( + (message) => message.signal?.tagName === preparedWorkpieceSignalTag, + ); + if (preparedCandidates.length > 1) { + throw new Error( + `Conversation ${history.conversationId} has more than one prepared workpiece source.`, + ); + } + + const preparedMessage = preparedCandidates.at(0); + if ( + preparedMessage !== undefined && + !isPreparedWorkpieceMessage(preparedMessage) + ) { + throw new Error( + `Conversation ${history.conversationId} has a malformed prepared workpiece source.`, + ); + } + + const preparationSubmissionId = preparedMessage?.submissionId; + let selected: SelectedRunbookWorkpiece | undefined; + + for (const message of history.messages) { + if (message === preparedMessage) { + const preparedWorkpiece = selectedFrom(message, { + authorship: preparedWorkpieceAuthorship, + sourceKind: "prepared-signal", + }); + if (preparedWorkpiece === undefined) { + throw new Error( + `Conversation ${history.conversationId} has a prepared source without a runbook-ir block.`, + ); + } + const fixtureId = preparedFixtureIdFrom(message); + if (fixtureId === undefined) { + throw new Error( + `Conversation ${history.conversationId} has a malformed prepared workpiece source.`, + ); + } + selected = { ...preparedWorkpiece, fixtureId }; + continue; + } + if ( + message.purpose !== "assistant" || + message.role !== "assistant" || + (preparationSubmissionId !== undefined && + message.submissionId === preparationSubmissionId) + ) { + continue; + } + const assistantWorkpiece = selectedFrom(message, { + authorship: "model-produced", + sourceKind: "assistant", + }); + if (assistantWorkpiece !== undefined) selected = assistantWorkpiece; + } + + return selected; +}; diff --git a/libs/@hashintel/brunch-agent/packages/core/test/workpiece.test.ts b/libs/@hashintel/brunch-agent/packages/core/test/workpiece.test.ts new file mode 100644 index 00000000000..bb4ff0ffea0 --- /dev/null +++ b/libs/@hashintel/brunch-agent/packages/core/test/workpiece.test.ts @@ -0,0 +1,174 @@ +import { describe, expect, test } from "vitest"; + +import { + createPreparedWorkpieceDelivery, + preparedWorkpieceAuthorship, + preparedWorkpieceClaimBoundary, + preparedWorkpieceSignalTag, + selectRunbookWorkpiece, + type WorkpieceHistory, + type WorkpieceHistoryMessage, +} from "../src/workpiece"; + +const workpiece = (name: string): string => + `\`\`\`runbook-ir\n# ${name}\n\`\`\``; + +const preparedMessage = ( + id = "prepared", + submissionId = "prepare-submission", +): WorkpieceHistoryMessage => ({ + id, + role: "system", + purpose: "dispatch", + submissionId, + signal: { + tagName: preparedWorkpieceSignalTag, + attributes: { + authorship: preparedWorkpieceAuthorship, + claimBoundary: preparedWorkpieceClaimBoundary, + fixtureId: "crew-reservation-v1", + }, + }, + parts: [{ type: "text", text: workpiece("Prepared") }], +}); + +const assistantMessage = ( + id: string, + submissionId: string, + name: string, +): WorkpieceHistoryMessage => ({ + id, + role: "assistant", + purpose: "assistant", + submissionId, + parts: [{ type: "text", text: workpiece(name) }], +}); + +const history = ( + messages: readonly WorkpieceHistoryMessage[], +): WorkpieceHistory => ({ + conversationId: "conversation", + messages, +}); + +describe("prepared workpiece delivery", () => { + test("carries explicit authorship and a revision-stable idempotency key", () => { + expect( + createPreparedWorkpieceDelivery({ + fixtureId: "crew-reservation-v1", + revision: 0, + body: workpiece("Prepared"), + }), + ).toEqual({ + idempotencyKey: "prepared-fixture:crew-reservation-v1:revision-0", + message: { + kind: "signal", + type: "brunch.fixture.prepared", + tagName: "prepared-fixture", + body: workpiece("Prepared"), + attributes: { + fixtureId: "crew-reservation-v1", + authorship: "test-authored", + claimBoundary: "prepared-not-model-produced", + }, + }, + }); + }); + + test("refuses prepared content without a runbook-ir block", () => { + expect(() => + createPreparedWorkpieceDelivery({ + fixtureId: "crew-reservation-v1", + revision: 0, + body: "# Not fenced", + }), + ).toThrow(/requires a full runbook-ir block/u); + }); +}); + +describe("selectRunbookWorkpiece", () => { + test("selects prepared revision zero with its honest authorship", () => { + expect(selectRunbookWorkpiece(history([preparedMessage()]))).toMatchObject({ + authorship: "test-authored", + content: "# Prepared", + fixtureId: "crew-reservation-v1", + sourceKind: "prepared-signal", + sourceMessageId: "prepared", + }); + }); + + test("ignores the assistant response to preparation", () => { + expect( + selectRunbookWorkpiece( + history([ + preparedMessage(), + assistantMessage( + "preparation-response", + "prepare-submission", + "Echo", + ), + ]), + ), + ).toMatchObject({ + authorship: "test-authored", + content: "# Prepared", + }); + }); + + test("selects the latest genuine assistant revision", () => { + expect( + selectRunbookWorkpiece( + history([ + preparedMessage(), + assistantMessage("revision-1", "turn-1", "Revision one"), + assistantMessage("revision-2", "turn-2", "Revision two"), + ]), + ), + ).toMatchObject({ + authorship: "model-produced", + content: "# Revision two", + sourceKind: "assistant", + sourceMessageId: "revision-2", + }); + }); + + test("uses canonical log order when the prepared source follows older assistant text", () => { + expect( + selectRunbookWorkpiece( + history([ + assistantMessage("older", "older-turn", "Older assistant text"), + preparedMessage(), + ]), + ), + ).toMatchObject({ + authorship: "test-authored", + content: "# Prepared", + sourceMessageId: "prepared", + }); + }); + + test("refuses malformed and duplicate prepared sources", () => { + expect(() => + selectRunbookWorkpiece( + history([ + { + ...preparedMessage(), + signal: { + tagName: preparedWorkpieceSignalTag, + attributes: { authorship: "model-produced" }, + }, + }, + ]), + ), + ).toThrow(/malformed prepared workpiece source/u); + + expect(() => + selectRunbookWorkpiece( + history([ + preparedMessage("prepared-1"), + preparedMessage("prepared-2", "prepare-submission-2"), + ]), + ), + ).toThrow(/more than one prepared workpiece source/u); + }); +}); diff --git a/libs/@hashintel/brunch-agent/packages/core/vite.config.ts b/libs/@hashintel/brunch-agent/packages/core/vite.config.ts index 291e5b9c4bf..986fb63fdc4 100644 --- a/libs/@hashintel/brunch-agent/packages/core/vite.config.ts +++ b/libs/@hashintel/brunch-agent/packages/core/vite.config.ts @@ -14,6 +14,7 @@ export default defineConfig({ flue: fileURLToPath(new URL("src/flue.ts", import.meta.url)), index: fileURLToPath(new URL("src/index.ts", import.meta.url)), storage: fileURLToPath(new URL("src/storage.ts", import.meta.url)), + workpiece: fileURLToPath(new URL("src/workpiece.ts", import.meta.url)), }, fileName: (_format, entryName) => `${entryName}.js`, formats: ["es"], diff --git a/libs/@hashintel/brunch-agent/packages/plugin-sdcpn/src/flue.ts b/libs/@hashintel/brunch-agent/packages/plugin-sdcpn/src/flue.ts index 3c48ef1a56f..06705585906 100644 --- a/libs/@hashintel/brunch-agent/packages/plugin-sdcpn/src/flue.ts +++ b/libs/@hashintel/brunch-agent/packages/plugin-sdcpn/src/flue.ts @@ -6,22 +6,31 @@ import { } from "@flue/runtime"; import * as v from "valibot"; +import { preparedWorkpieceInitialDataMode } from "@hashintel/brunch-agent/workpiece"; + import sdcpnAppend from "./prompts/APPEND_SYSTEM.md?raw"; import { SDCPN_MODELLING_SKILL_NAME, sdcpnModellingSkill, } from "./skills/sdcpn-modelling/skill"; -import { petrinautConstructionTools } from "./tools/petrinaut-construction"; +import { + petrinautConstructionTools, + petrinautFixtureTools, +} from "./tools/petrinaut-construction"; import { READ_PETRINAUT_DOC_TOOL_NAME, readPetrinautDoc, } from "./tools/read-petrinaut-doc"; export const VALIDATED_CONSTRUCTION_MODE = "validated-construction"; +export const validatedFixtureMutationMode = preparedWorkpieceInitialDataMode; export const sdcpnInitialDataSchema = v.optional( v.object({ - mode: v.literal(VALIDATED_CONSTRUCTION_MODE), + mode: v.picklist([ + VALIDATED_CONSTRUCTION_MODE, + validatedFixtureMutationMode, + ]), }), ); @@ -44,6 +53,15 @@ This is a construct-only headless conversation. Use only the supplied runbook IR for (const constructionTool of petrinautConstructionTools) { useTool(constructionTool); } + } else if (initialData?.mode === validatedFixtureMutationMode) { + useInstruction( + ` +This is a visibly labelled prepared-fixture conversation. Treat its tagged prepared runbook-ir dispatch as test-authored revision zero, maintain the full Markdown workpiece in later responses, preserve explicit unknowns, and do not relabel prepared material as model-produced. Every later assistant-authored workpiece is model-produced: label that revision accordingly and do not copy revision zero's claim that the current revision is test-authored. Use only the mounted canonical Petrinaut read and least arc mutation when confirmed evidence calls for that change. Read the live document before mutating it, report rejected or no-op outcomes honestly, and do not construct unrelated net content. +`.replace(/^\s+|\s+$/gu, ""), + ); + for (const fixtureTool of petrinautFixtureTools) { + useTool(fixtureTool); + } } } @@ -51,6 +69,8 @@ export { READ_PETRINAUT_DOC_TOOL_NAME, readPetrinautDoc }; export { SDCPN_MODELLING_SKILL_NAME }; export { PETRINAUT_CONSTRUCTION_TOOL_NAMES, + petrinautFixtureToolNames, petrinautConstructionTools, + petrinautFixtureTools, type PetrinautConstructionToolName, } from "./tools/petrinaut-construction"; diff --git a/libs/@hashintel/brunch-agent/packages/plugin-sdcpn/src/tools/petrinaut-construction.ts b/libs/@hashintel/brunch-agent/packages/plugin-sdcpn/src/tools/petrinaut-construction.ts index 9f45f05ad1f..78c4b1f606b 100644 --- a/libs/@hashintel/brunch-agent/packages/plugin-sdcpn/src/tools/petrinaut-construction.ts +++ b/libs/@hashintel/brunch-agent/packages/plugin-sdcpn/src/tools/petrinaut-construction.ts @@ -2,7 +2,10 @@ import { defineTool } from "@flue/runtime"; import * as v from "valibot"; import { AWAITING_CLIENT } from "@hashintel/brunch-agent/client-tools"; -import { petrinautAiTools } from "@hashintel/petrinaut-core/ai"; +import { + normalizePetrinautAiToolInput, + petrinautAiTools, +} from "@hashintel/petrinaut-core/ai"; export const PETRINAUT_CONSTRUCTION_TOOL_NAMES = [ "getLatestNetDefinition", @@ -13,8 +16,14 @@ export const PETRINAUT_CONSTRUCTION_TOOL_NAMES = [ "addArc", ] as const satisfies readonly (keyof typeof petrinautAiTools)[]; +export const petrinautFixtureToolNames = [ + "getLatestNetDefinition", + "addArc", +] as const satisfies readonly (keyof typeof petrinautAiTools)[]; + export type PetrinautConstructionToolName = (typeof PETRINAUT_CONSTRUCTION_TOOL_NAMES)[number]; +type PetrinautFixtureToolName = (typeof petrinautFixtureToolNames)[number]; const issuePathFrom = ( input: Record, @@ -46,15 +55,22 @@ const canonicalInputFor = (toolName: PetrinautConstructionToolName) => { return { description: [ canonicalTool.description, + ...(toolName === "addArc" + ? [ + "A finite numeric-string weight is normalized to a number before canonical validation.", + ] + : []), "Canonical Petrinaut input JSON Schema:", JSON.stringify(jsonSchema), ].join("\n"), schema: v.pipe( v.looseObject({}), v.rawTransform((context) => { - const parsed = canonicalTool.inputSchema.safeParse( + const normalizedInput = normalizePetrinautAiToolInput( + toolName, context.dataset.value, ); + const parsed = canonicalTool.inputSchema.safeParse(normalizedInput); if (parsed.success) return parsed.data; for (const issue of parsed.error.issues) { @@ -91,3 +107,16 @@ const definePetrinautConstructionTool = ( export const petrinautConstructionTools = PETRINAUT_CONSTRUCTION_TOOL_NAMES.map( definePetrinautConstructionTool, ); + +const isPetrinautFixtureTool = ( + tool: (typeof petrinautConstructionTools)[number], +): tool is (typeof petrinautConstructionTools)[number] & { + readonly name: PetrinautFixtureToolName; +} => + petrinautFixtureToolNames.some((fixtureToolName) => { + return fixtureToolName === tool.name; + }); + +export const petrinautFixtureTools = petrinautConstructionTools.filter( + isPetrinautFixtureTool, +); diff --git a/libs/@hashintel/brunch-agent/packages/plugin-sdcpn/test/construction-tools.test.ts b/libs/@hashintel/brunch-agent/packages/plugin-sdcpn/test/construction-tools.test.ts index 1ca9cb0e53a..ce2da8f9260 100644 --- a/libs/@hashintel/brunch-agent/packages/plugin-sdcpn/test/construction-tools.test.ts +++ b/libs/@hashintel/brunch-agent/packages/plugin-sdcpn/test/construction-tools.test.ts @@ -3,9 +3,16 @@ import { describe, expect, test } from "vitest"; import { petrinautAiTools } from "@hashintel/petrinaut-core/ai"; +import { + sdcpnInitialDataSchema, + VALIDATED_CONSTRUCTION_MODE, + validatedFixtureMutationMode, +} from "../src/flue"; import { PETRINAUT_CONSTRUCTION_TOOL_NAMES, + petrinautFixtureToolNames, petrinautConstructionTools, + petrinautFixtureTools, } from "../src/tools/petrinaut-construction"; const toolByName = (toolName: string) => { @@ -18,12 +25,37 @@ const toolByName = (toolName: string) => { }; describe("Petrinaut construction tools", () => { + test("accepts only the ordinary headless and prepared-fixture modes", () => { + expect(v.parse(sdcpnInitialDataSchema, undefined)).toBeUndefined(); + expect( + v.parse(sdcpnInitialDataSchema, { + mode: VALIDATED_CONSTRUCTION_MODE, + }), + ).toEqual({ mode: VALIDATED_CONSTRUCTION_MODE }); + expect( + v.parse(sdcpnInitialDataSchema, { + mode: validatedFixtureMutationMode, + }), + ).toEqual({ mode: validatedFixtureMutationMode }); + expect(() => + v.parse(sdcpnInitialDataSchema, { + mode: "unrestricted-construction", + }), + ).toThrow(/Invalid type/u); + }); + test("exposes exactly the bounded canonical subset", () => { expect(petrinautConstructionTools.map((tool) => tool.name)).toEqual([ ...PETRINAUT_CONSTRUCTION_TOOL_NAMES, ]); }); + test("limits prepared fixtures to one canonical read and arc mutation", () => { + expect(petrinautFixtureTools.map((tool) => tool.name)).toEqual([ + ...petrinautFixtureToolNames, + ]); + }); + test("mechanically carries the canonical input contract", () => { for (const toolName of PETRINAUT_CONSTRUCTION_TOOL_NAMES) { const constructionTool = toolByName(toolName); @@ -55,6 +87,19 @@ describe("Petrinaut construction tools", () => { ); }); + test("normalizes a finite provider numeric-string arc weight", () => { + const addArc = toolByName("addArc"); + const result = v.parse(addArc.input!, { + transitionId: "transition", + arcDirection: "input", + placeId: "place", + weight: "1", + type: "standard", + }); + + expect(result).toMatchObject({ weight: 1 }); + }); + test("retains nested values in canonical validation paths", () => { const addType = toolByName("addType"); const invalidElement = { diff --git a/libs/@hashintel/brunch-agent/packages/transport-aisdk/src/client-tool-history.ts b/libs/@hashintel/brunch-agent/packages/transport-aisdk/src/client-tool-history.ts new file mode 100644 index 00000000000..114150c2f83 --- /dev/null +++ b/libs/@hashintel/brunch-agent/packages/transport-aisdk/src/client-tool-history.ts @@ -0,0 +1,104 @@ +import { CLIENT_TOOL_RESULT_SIGNAL } from "./client-tool-result"; + +export interface ClientToolHistoryCall { + readonly input: Readonly>; + readonly toolCallId: string; + readonly toolName: string; +} + +export interface ClientToolHistoryResult { + readonly output: unknown; + readonly toolCallId: string; + readonly toolName: string; +} + +export interface ClientToolHistory { + readonly calls: readonly ClientToolHistoryCall[]; + readonly results: readonly ClientToolHistoryResult[]; +} + +export interface ClientToolHistoryMessage { + readonly parts: readonly unknown[]; + readonly signal?: { + readonly tagName?: string; + readonly type?: string; + }; +} + +const isRecord = (value: unknown): value is Record => + typeof value === "object" && value !== null; + +const callsFrom = ( + messages: readonly ClientToolHistoryMessage[], +): readonly ClientToolHistoryCall[] => + messages.flatMap((message) => + message.parts.flatMap((part) => { + if ( + !isRecord(part) || + part.type !== "dynamic-tool" || + typeof part.toolName !== "string" || + typeof part.toolCallId !== "string" || + !isRecord(part.input) + ) { + return []; + } + return [ + { + input: part.input, + toolCallId: part.toolCallId, + toolName: part.toolName, + }, + ]; + }), + ); + +const resultsFrom = ( + messages: readonly ClientToolHistoryMessage[], +): readonly ClientToolHistoryResult[] => + messages.flatMap((message) => { + if ( + message.signal?.tagName !== CLIENT_TOOL_RESULT_SIGNAL && + message.signal?.type !== CLIENT_TOOL_RESULT_SIGNAL + ) { + return []; + } + + const body = message.parts + .flatMap((part) => + isRecord(part) && part.type === "text" && typeof part.text === "string" + ? [part.text] + : [], + ) + .join(""); + + try { + const parsed: unknown = JSON.parse(body); + if (!Array.isArray(parsed)) return []; + return parsed.flatMap((result) => { + if ( + !isRecord(result) || + typeof result.toolName !== "string" || + typeof result.toolCallId !== "string" || + !("output" in result) + ) { + return []; + } + return [ + { + output: result.output, + toolCallId: result.toolCallId, + toolName: result.toolName, + }, + ]; + }); + } catch { + return []; + } + }); + +export const clientToolHistoryFrom = ( + messages: readonly ClientToolHistoryMessage[], +): ClientToolHistory => ({ + calls: callsFrom(messages), + results: resultsFrom(messages), +}); diff --git a/libs/@hashintel/brunch-agent/packages/transport-aisdk/src/index.ts b/libs/@hashintel/brunch-agent/packages/transport-aisdk/src/index.ts index 93b19f1e1be..e28d4e6ef75 100644 --- a/libs/@hashintel/brunch-agent/packages/transport-aisdk/src/index.ts +++ b/libs/@hashintel/brunch-agent/packages/transport-aisdk/src/index.ts @@ -7,6 +7,13 @@ import { createFlueUiStream } from "./ui-stream"; import type { AgentSendResult, DeliveredMessage, FlueClient } from "@flue/sdk"; import type { ChatTransport, UIMessage, UIMessageChunk } from "ai"; +export { + clientToolHistoryFrom, + type ClientToolHistory, + type ClientToolHistoryCall, + type ClientToolHistoryMessage, + type ClientToolHistoryResult, +} from "./client-tool-history"; export { BRUNCH_CONVERSATION_HEADER, BRUNCH_PRINCIPAL_HEADER } from "./headers"; export { CLIENT_TOOL_RESULT_SIGNAL } from "./client-tool-result"; export { @@ -31,6 +38,10 @@ export interface ClientToolResult { export interface FlueChatTransportOptions { readonly client: FlueClient; readonly clientToolNames: ReadonlySet; + readonly mapClientToolInput?: (input: { + readonly input: unknown; + readonly toolName: string; + }) => unknown; readonly onAdmission?: (event: { readonly admission: AgentSendResult; readonly kind: "client-tool-result" | "user"; @@ -182,6 +193,7 @@ const streamSubmission = ( const projector = createFlueUiStream({ submissionId: admission.submissionId, clientToolNames: options.clientToolNames, + mapClientToolInput: options.mapClientToolInput, write, }); @@ -265,10 +277,18 @@ export const createFlueChatTransport = < }, }; })(); + const idempotencyKey = + messageId === undefined + ? `ai-sdk:user:${userMessage!.id}` + : `ai-sdk:client-tools:${messageId}:${toolResults + .map(({ toolCallId }) => toolCallId) + .sort() + .join(",")}`; let admission: AgentSendResult; try { admission = await options.client.send({ + idempotencyKey, message, signal: abortSignal, }); diff --git a/libs/@hashintel/brunch-agent/packages/transport-aisdk/src/transcript.ts b/libs/@hashintel/brunch-agent/packages/transport-aisdk/src/transcript.ts index 089fc3dd4bc..49ca746eb7a 100644 --- a/libs/@hashintel/brunch-agent/packages/transport-aisdk/src/transcript.ts +++ b/libs/@hashintel/brunch-agent/packages/transport-aisdk/src/transcript.ts @@ -19,6 +19,10 @@ export type UiHistoryMessage = Omit< export interface SnapshotToUiMessagesOptions { readonly clientToolNames: ReadonlySet; + readonly mapClientToolInput?: (input: { + readonly input: unknown; + readonly toolName: string; + }) => unknown; } const unhandledConversationPart = (part: never): never => { @@ -71,17 +75,24 @@ const clientToolResultsFrom = ( const toolPartFrom = ( part: Extract, - clientToolNames: ReadonlySet, + options: SnapshotToUiMessagesOptions, clientOutputs: ReadonlyMap, ): UiMessagePart => { - const isClientTool = clientToolNames.has(part.toolName); + const isClientTool = options.clientToolNames.has(part.toolName); const hasClientOutput = clientOutputs.has(part.toolCallId); + const input = + isClientTool && options.mapClientToolInput !== undefined + ? options.mapClientToolInput({ + input: part.input, + toolName: part.toolName, + }) + : part.input; if (part.state === "output-error") { return { type: `tool-${part.toolName}`, toolCallId: part.toolCallId, state: "output-error", - input: part.input, + input, errorText: part.errorText, ...(isClientTool ? {} : { providerExecuted: true }), }; @@ -91,7 +102,7 @@ const toolPartFrom = ( type: `tool-${part.toolName}`, toolCallId: part.toolCallId, state: "input-available", - input: part.input, + input, }; } const output = isClientTool @@ -104,7 +115,7 @@ const toolPartFrom = ( type: `tool-${part.toolName}`, toolCallId: part.toolCallId, state: "output-available", - input: part.input, + input, output, ...(isClientTool ? {} : { providerExecuted: true }), }; @@ -113,7 +124,7 @@ const toolPartFrom = ( type: `tool-${part.toolName}`, toolCallId: part.toolCallId, state: "input-available", - input: part.input, + input, ...(isClientTool ? {} : { providerExecuted: true }), }; }; @@ -134,7 +145,7 @@ const partsFrom = ( continue; } if (part.type === "dynamic-tool") { - parts.push(toolPartFrom(part, options.clientToolNames, clientOutputs)); + parts.push(toolPartFrom(part, options, clientOutputs)); continue; } if (part.type === "file") { diff --git a/libs/@hashintel/brunch-agent/packages/transport-aisdk/src/ui-stream.ts b/libs/@hashintel/brunch-agent/packages/transport-aisdk/src/ui-stream.ts index 7270be320b2..a24f3398868 100644 --- a/libs/@hashintel/brunch-agent/packages/transport-aisdk/src/ui-stream.ts +++ b/libs/@hashintel/brunch-agent/packages/transport-aisdk/src/ui-stream.ts @@ -4,6 +4,10 @@ import type { UIMessageChunk } from "ai"; export interface FlueUiStreamOptions { readonly submissionId: AgentSendResult["submissionId"]; readonly clientToolNames: ReadonlySet; + readonly mapClientToolInput?: (input: { + readonly input: unknown; + readonly toolName: string; + }) => unknown; readonly write: (chunk: UIMessageChunk) => void; } @@ -129,7 +133,13 @@ export const createFlueUiStream = ( type: "tool-input-available", toolCallId: chunk.toolCallId, toolName: chunk.toolName, - input: chunk.input, + input: + isClientTool && options.mapClientToolInput !== undefined + ? options.mapClientToolInput({ + input: chunk.input, + toolName: chunk.toolName, + }) + : chunk.input, ...(isClientTool ? {} : { providerExecuted: true }), }); return; diff --git a/libs/@hashintel/brunch-agent/packages/transport-aisdk/test/chat-transport.test.ts b/libs/@hashintel/brunch-agent/packages/transport-aisdk/test/chat-transport.test.ts index 3b982b101be..ffcef517172 100644 --- a/libs/@hashintel/brunch-agent/packages/transport-aisdk/test/chat-transport.test.ts +++ b/libs/@hashintel/brunch-agent/packages/transport-aisdk/test/chat-transport.test.ts @@ -114,6 +114,7 @@ test("admits one user message and projects a finite per-turn stream", async () = expect(send).toHaveBeenCalledOnce(); expect(send).toHaveBeenCalledWith({ + idempotencyKey: "ai-sdk:user:user-1", message: { kind: "user", body: "Run the transport tracer." }, signal: undefined, }); @@ -158,6 +159,7 @@ test("admits one client-tool result signal and resumes its assistant id", async ); expect(send).toHaveBeenCalledWith({ + idempotencyKey: "ai-sdk:client-tools:assistant-original:tool-1", message: { kind: "signal", type: "client-tool-result", @@ -179,6 +181,30 @@ test("admits one client-tool result signal and resumes its assistant id", async }); }); +test("derives the same idempotency key for exact AI SDK retries", async () => { + const { client, send } = clientWith(completedEvents); + const transport = createFlueChatTransport({ + client, + clientToolNames: new Set(), + }); + const options = sendOptions([ + { + id: "stable-user-message", + role: "user", + parts: [{ type: "text", text: "Admit this once." }], + }, + ]); + + await transport.sendMessages(options); + await transport.sendMessages(options); + + expect(send).toHaveBeenCalledTimes(2); + expect(send.mock.calls.map(([input]) => input.idempotencyKey)).toEqual([ + "ai-sdk:user:stable-user-message", + "ai-sdk:user:stable-user-message", + ]); +}); + test("starts with history-only reconnection", async () => { const { client } = clientWith([]); const transport = createFlueChatTransport({ diff --git a/libs/@hashintel/brunch-agent/packages/transport-aisdk/test/client-tool-history.test.ts b/libs/@hashintel/brunch-agent/packages/transport-aisdk/test/client-tool-history.test.ts new file mode 100644 index 00000000000..bfc3abd64c8 --- /dev/null +++ b/libs/@hashintel/brunch-agent/packages/transport-aisdk/test/client-tool-history.test.ts @@ -0,0 +1,64 @@ +import { describe, expect, test } from "vitest"; + +import { CLIENT_TOOL_RESULT_SIGNAL, clientToolHistoryFrom } from "../src/index"; + +describe("clientToolHistoryFrom", () => { + test("projects generic calls and correlated client result envelopes", () => { + expect( + clientToolHistoryFrom([ + { + parts: [ + { + type: "dynamic-tool", + toolName: "addArc", + toolCallId: "call-1", + input: { placeId: "place-1" }, + }, + ], + }, + { + signal: { tagName: CLIENT_TOOL_RESULT_SIGNAL }, + parts: [ + { + type: "text", + text: JSON.stringify([ + { + toolName: "addArc", + toolCallId: "call-1", + output: { applied: true }, + }, + ]), + }, + ], + }, + ]), + ).toEqual({ + calls: [ + { + input: { placeId: "place-1" }, + toolCallId: "call-1", + toolName: "addArc", + }, + ], + results: [ + { + output: { applied: true }, + toolCallId: "call-1", + toolName: "addArc", + }, + ], + }); + }); + + test("ignores malformed calls and result bodies", () => { + expect( + clientToolHistoryFrom([ + { parts: [{ type: "dynamic-tool", toolName: "addArc" }] }, + { + signal: { type: CLIENT_TOOL_RESULT_SIGNAL }, + parts: [{ type: "text", text: "not-json" }], + }, + ]), + ).toEqual({ calls: [], results: [] }); + }); +}); diff --git a/libs/@hashintel/brunch-agent/packages/transport-aisdk/test/ui-stream.test.ts b/libs/@hashintel/brunch-agent/packages/transport-aisdk/test/ui-stream.test.ts index c567fcd9b38..ce905e9dce1 100644 --- a/libs/@hashintel/brunch-agent/packages/transport-aisdk/test/ui-stream.test.ts +++ b/libs/@hashintel/brunch-agent/packages/transport-aisdk/test/ui-stream.test.ts @@ -118,3 +118,37 @@ test("ignores observation catch-up chunks in a submission stream", () => { "finish", ]); }); + +test("maps client-tool input before exposing it to the AI SDK", () => { + const written: UIMessageChunk[] = []; + const projector = createFlueUiStream({ + submissionId: "submission-1", + clientToolNames: new Set(["addArc"]), + mapClientToolInput: ({ input }) => ({ ...(input as object), weight: 1 }), + write: (chunk) => written.push(chunk), + }); + projector.accept({ + type: "message-started", + conversationId: "conversation-1", + messageId: "message-1", + submissionId: "submission-1", + turnId: "turn-1", + position: position(0), + }); + projector.accept({ + type: "tool-input", + conversationId: "conversation-1", + messageId: "message-1", + toolCallId: "call-1", + toolName: "addArc", + input: { weight: "1" }, + position: position(1), + }); + + expect(written).toContainEqual({ + type: "tool-input-available", + toolCallId: "call-1", + toolName: "addArc", + input: { weight: 1 }, + }); +}); diff --git a/libs/@hashintel/petrinaut-core/src/ai.test.ts b/libs/@hashintel/petrinaut-core/src/ai.test.ts index 26f38c743e1..06f9ac185f0 100644 --- a/libs/@hashintel/petrinaut-core/src/ai.test.ts +++ b/libs/@hashintel/petrinaut-core/src/ai.test.ts @@ -5,6 +5,7 @@ import { aiCommandActionInputSchemas, createPetrinautAiWritableCallbacks, getLatestNetDefinitionToolName, + normalizePetrinautAiToolInput, petrinautAiPrompt, petrinautAiToolInputSchemas, petrinautAiTools, @@ -46,6 +47,17 @@ describe("Petrinaut AI core exports", () => { expect(petrinautAiTools).toHaveProperty("applyAutoLayout"); }); + test("normalizes an addArc weight serialized as text", () => { + expect( + normalizePetrinautAiToolInput("addArc", { + transitionId: "transition", + arcDirection: "input", + weight: "1", + type: "standard", + }), + ).toMatchObject({ weight: 1 }); + }); + test("latest net definition tool documents extension settings", () => { expect( petrinautAiTools[getLatestNetDefinitionToolName].description, diff --git a/libs/@hashintel/petrinaut-core/src/ai.ts b/libs/@hashintel/petrinaut-core/src/ai.ts index 61310b70a83..05778c0745c 100644 --- a/libs/@hashintel/petrinaut-core/src/ai.ts +++ b/libs/@hashintel/petrinaut-core/src/ai.ts @@ -210,6 +210,27 @@ export type PetrinautAiToolInput = z.input< (typeof petrinautAiTools)[Name]["inputSchema"] >; +const isRecord = (value: unknown): value is Record => + typeof value === "object" && value !== null; + +/** + * Normalize the narrow structured values that text-oriented providers may + * serialize before applying the canonical Petrinaut tool schema. + */ +export const normalizePetrinautAiToolInput = ( + toolName: PetrinautAiToolName, + input: unknown, +): unknown => { + if (toolName !== "addArc" || !isRecord(input)) return input; + let normalized = input; + + if (typeof normalized.weight === "string") { + const weight = Number(normalized.weight); + if (Number.isFinite(weight)) normalized = { ...normalized, weight }; + } + return normalized; +}; + /** * Writable tool callbacks exposed to the AI: every mutation, plus the subset * of commands registered in {@link aiCommandActionInputSchemas}. Read-only diff --git a/libs/@hashintel/petrinaut/docs/ai-assistant.md b/libs/@hashintel/petrinaut/docs/ai-assistant.md index 6d56acb96e8..fe8c249e26b 100644 --- a/libs/@hashintel/petrinaut/docs/ai-assistant.md +++ b/libs/@hashintel/petrinaut/docs/ai-assistant.md @@ -26,6 +26,18 @@ If an assistant request fails, Petrinaut shows the error in a brief toast rather Hosts may provide canonical conversation rehydration. In that case, reopening the same assistant shows its settled and stopped turns without resubmitting a message or replaying Voice audio. +### Prepared local demo fixture + +The local Petrinaut development demo offers a labelled crew-reservation fixture when Brunch is +configured. Opening it restores a test-authored Markdown workpiece, a non-empty final-inspection +net, and their canonical Brunch conversation. The status panel distinguishes prepared text from +model-produced revisions and states the fixture's non-claims. + +The document is mirrored to browser local storage automatically; there is no separate Save action. +Wait for the status panel to report a settled bundle before reopening the same fixture in another +tab. A refused status leaves the previous coherent bundle selected and names the failed history, +workpiece, mutation-correlation, or document check instead of claiming that partial state settled. + When the Brunch voice preview is enabled and available, an empty composer shows a waveform action titled **Start voice mode**. Typing non-whitespace text replaces it with **Send**. The same dynamic action appears in the first-run prompt and the assistant panel; if voice is unavailable, the empty diff --git a/libs/@hashintel/petrinaut/src/ui/views/Editor/panels/ai-assistant-panel.tsx b/libs/@hashintel/petrinaut/src/ui/views/Editor/panels/ai-assistant-panel.tsx index 6c515cc2adf..d58a7f77c46 100644 --- a/libs/@hashintel/petrinaut/src/ui/views/Editor/panels/ai-assistant-panel.tsx +++ b/libs/@hashintel/petrinaut/src/ui/views/Editor/panels/ai-assistant-panel.tsx @@ -5,7 +5,6 @@ import { use, useCallback, useEffect, useRef, useState } from "react"; import { aiCommandActionInputSchemas, type AiCommandActionName, - createPetrinautAiWritableCallbacks, getLatestNetDefinitionToolName, getNetCompilationErrorsToolName, mutationActionInputSchemas as petrinautAiMutationToolInputSchemas, @@ -37,6 +36,7 @@ import { REVIEW_CHIPS, STARTER_CHIPS, } from "./ai-assistant-panel/ai-assistant-contents/prompt-chips"; +import { applyPetrinautAiMutation } from "./ai-assistant-panel/apply-petrinaut-ai-mutation"; import { createDiagnosticsAwareAiTransport } from "./ai-assistant-panel/create-diagnostics-aware-ai-transport"; import { createReasoningTimingAwareAiTransport } from "./ai-assistant-panel/create-reasoning-timing-aware-ai-transport"; import { finalizeStreamingMessageParts } from "./ai-assistant-panel/finalize-streaming-message-parts"; @@ -51,7 +51,6 @@ import { type AiToolCall, type AiToolTarget, summarizeApplyAutoLayout, - summarizePetrinautAiToolCall, toPetrinautAiToolOutput, } from "./ai-assistant-panel/tool-summaries"; @@ -256,25 +255,6 @@ const waitForDiagnosticsRefresh = async ({ }); }; -const applyPetrinautAiMutation = ({ - aiToolCall, - instance, -}: { - aiToolCall: Extract; - instance: Petrinaut; -}): AiToolOutput => { - const definition = instance.definition.get(); - const toolCallbacks = createPetrinautAiWritableCallbacks(instance); - const summary = summarizePetrinautAiToolCall(aiToolCall, { definition }); - const callback = toolCallbacks[aiToolCall.toolName] as ( - input: typeof aiToolCall.input, - ) => void; - - callback(aiToolCall.input); - - return toPetrinautAiToolOutput(summary); -}; - const applyPetrinautAiCommand = async ({ aiToolCall, instance, diff --git a/libs/@hashintel/petrinaut/src/ui/views/Editor/panels/ai-assistant-panel/apply-petrinaut-ai-mutation.test.ts b/libs/@hashintel/petrinaut/src/ui/views/Editor/panels/ai-assistant-panel/apply-petrinaut-ai-mutation.test.ts new file mode 100644 index 00000000000..8b221aad305 --- /dev/null +++ b/libs/@hashintel/petrinaut/src/ui/views/Editor/panels/ai-assistant-panel/apply-petrinaut-ai-mutation.test.ts @@ -0,0 +1,102 @@ +import { describe, expect, test } from "vitest"; + +import { + createJsonDocHandle, + createPetrinaut, + type SDCPN, +} from "@hashintel/petrinaut-core"; + +import { applyPetrinautAiMutation } from "./apply-petrinaut-ai-mutation"; + +const definition: SDCPN = { + places: [ + { + id: "crew", + name: "Crew", + colorId: null, + dynamicsEnabled: false, + differentialEquationId: null, + x: 0, + y: 0, + }, + ], + transitions: [ + { + id: "start", + name: "Start", + inputArcs: [], + outputArcs: [], + lambdaType: "predicate", + lambdaCode: "", + transitionKernelCode: "", + x: 100, + y: 0, + }, + ], + types: [], + parameters: [], + differentialEquations: [], +}; + +describe("applyPetrinautAiMutation", () => { + test("reports a duplicate canonical arc as a no-op", () => { + const instance = createPetrinaut({ + document: createJsonDocHandle({ + id: "document", + initial: definition, + capabilities: { disabledExtensions: [] }, + }), + }); + const call = { + toolName: "addArc" as const, + input: { + transitionId: "start", + arcDirection: "input" as const, + placeId: "crew", + weight: 1, + type: "standard" as const, + }, + }; + + expect(applyPetrinautAiMutation({ aiToolCall: call, instance })).toEqual( + expect.objectContaining({ applied: true }), + ); + expect(applyPetrinautAiMutation({ aiToolCall: call, instance })).toEqual({ + applied: false, + reason: + "Added input arc was a no-op because the document already had that state.", + }); + expect(instance.definition.get().transitions[0]?.inputArcs).toHaveLength(1); + + instance.dispose(); + }); + + test("leaves the document unchanged when a canonical arc is rejected", () => { + const instance = createPetrinaut({ + document: createJsonDocHandle({ + id: "document", + initial: definition, + capabilities: { disabledExtensions: [] }, + }), + }); + + expect(() => + applyPetrinautAiMutation({ + aiToolCall: { + toolName: "addArc", + input: { + transitionId: "start", + arcDirection: "input", + placeId: "missing-place", + weight: 1, + type: "standard", + }, + }, + instance, + }), + ).toThrow(/missing-place/u); + expect(instance.definition.get().transitions[0]?.inputArcs).toEqual([]); + + instance.dispose(); + }); +}); diff --git a/libs/@hashintel/petrinaut/src/ui/views/Editor/panels/ai-assistant-panel/apply-petrinaut-ai-mutation.ts b/libs/@hashintel/petrinaut/src/ui/views/Editor/panels/ai-assistant-panel/apply-petrinaut-ai-mutation.ts new file mode 100644 index 00000000000..3d475821387 --- /dev/null +++ b/libs/@hashintel/petrinaut/src/ui/views/Editor/panels/ai-assistant-panel/apply-petrinaut-ai-mutation.ts @@ -0,0 +1,39 @@ +import { + createPetrinautAiWritableCallbacks, + isSDCPNEqual, + type Petrinaut, + type PetrinautAiMutationToolName, +} from "@hashintel/petrinaut-core"; + +import { + type AiToolCall, + type AiToolOutput, + summarizePetrinautAiToolCall, + toPetrinautAiToolOutput, +} from "./tool-summaries"; + +export const applyPetrinautAiMutation = ({ + aiToolCall, + instance, +}: { + aiToolCall: Extract; + instance: Petrinaut; +}): AiToolOutput => { + const definition = instance.definition.get(); + const toolCallbacks = createPetrinautAiWritableCallbacks(instance); + const summary = summarizePetrinautAiToolCall(aiToolCall, { definition }); + const callback = toolCallbacks[aiToolCall.toolName] as ( + input: typeof aiToolCall.input, + ) => void; + + callback(aiToolCall.input); + + if (isSDCPNEqual(definition, instance.definition.get())) { + return { + applied: false, + reason: `${summary.title} was a no-op because the document already had that state.`, + }; + } + + return toPetrinautAiToolOutput(summary); +}; diff --git a/yarn.lock b/yarn.lock index 8d1c736db42..c50ba1aa15e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -933,6 +933,7 @@ __metadata: "@hashintel/petrinaut-core": "workspace:*" "@local/petrinaut-optimizer-client": "workspace:*" "@mantine/hooks": "npm:8.3.5" + "@noble/hashes": "npm:2.0.1" "@pandacss/dev": "npm:1.11.1" "@sentry/react": "npm:10.64.0" "@tanstack/react-router": "npm:1.170.31"