From e5e2bdfa7acb8fa8fdaa55501b3a98834b2f2352 Mon Sep 17 00:00:00 2001 From: Puma Date: Sat, 3 Oct 2026 01:07:21 -0700 Subject: [PATCH 01/21] fix(ui): own artifact download anchors and URL release lifecycle --- .../2026-10-03-artifact-download-lifecycle.md | 22 ++++ .../workbench/IoInspectorPage.svelte | 48 +++++--- .../artifactDownloadLifecycle.test.ts | 107 ++++++++++++++++++ .../workbench/artifactDownloadLifecycle.ts | 91 +++++++++++++++ 4 files changed, 253 insertions(+), 15 deletions(-) create mode 100644 docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-artifact-download-lifecycle.md create mode 100644 src/components/workbench/artifactDownloadLifecycle.test.ts create mode 100644 src/components/workbench/artifactDownloadLifecycle.ts diff --git a/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-artifact-download-lifecycle.md b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-artifact-download-lifecycle.md new file mode 100644 index 000000000..70dabe050 --- /dev/null +++ b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-artifact-download-lifecycle.md @@ -0,0 +1,22 @@ +# Artifact download anchor and URL lifecycle + +## Decision + +The critical anti-pattern gate identified imperative insertion of a temporary download anchor in IoInspectorPage. Render pending anchors through Svelte and activate them after mounting. A small download lifecycle owner retains each object URL until its own 30-second release or activation failure. Successful activations retain their grace timer after component disposal; unactivated URLs are reclaimed immediately. Pending completion promises settle on disposal, late URL admission is rejected and reclaimed, and activation after disposal cannot click a detached anchor. The original click error reaches the existing artifact error formatter. + +Concurrent requests retain distinct IDs, URLs, and filenames. Existing filename/media derivation, read authorization, loading state, and rel protection remain. Lifecycle-generation checks prevent late read completion from creating a download after unmount. The scheduler and artifact backend are unchanged. + +## Verification + +- Six direct lifecycle tests cover concurrent downloads and independent delayed release; original activation error; unmount with pending and active downloads; late admission; and unmount during activation. +- The lifecycle and existing inspector presenter suite pass 32 tests; all frontend tests pass 546/546. +- TypeScript, full ESLint, critical anti-pattern gate, and Svelte client compilation (zero warnings) pass locally. +- This is controller execution plus component compilation, not a real-browser download receipt. Browser download policy and actual saved bytes remain outside this local qualification. +- Independent review and exact-head hosted qualification are pending. The two a11y suppression findings, Clippy, and dependency audit remain separate. + + +## Independent review repair + +Review of initial tree a367c62b8b30729e1d5fa4a95d6e247337ca6756 found that immediate revocation of already-clicked URLs on unmount shortened the pre-existing browser grace period. The revised owner keeps those timers after disposal and clears component publication/completion callbacks. Tests distinguish clicked-then-unmount and successful versus failed activation that synchronously unmounts. Browser timer/revocation defaults live in the controller, so retained timers do not need component callbacks. Narrow re-review is pending. + +Independent narrow re-review accepted repair tree 1521cf361523f32ab5a127955cfed3fb87f8ee0c. The grace timer confirms URL release only; it cannot confirm that the browser saved a file. Hosted and real-browser qualification remain distinct. diff --git a/src/components/workbench/IoInspectorPage.svelte b/src/components/workbench/IoInspectorPage.svelte index 1d6c1a516..234b96653 100644 --- a/src/components/workbench/IoInspectorPage.svelte +++ b/src/components/workbench/IoInspectorPage.svelte @@ -1,5 +1,6 @@ +{#each pendingDownloads as request (request.id)} + +{/each} +
diff --git a/src/components/workbench/artifactDownloadLifecycle.test.ts b/src/components/workbench/artifactDownloadLifecycle.test.ts new file mode 100644 index 000000000..b68104f22 --- /dev/null +++ b/src/components/workbench/artifactDownloadLifecycle.test.ts @@ -0,0 +1,107 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { createArtifactDownloadLifecycle, type PendingArtifactDownload } from './artifactDownloadLifecycle.ts'; + +function setup() { + let pending: PendingArtifactDownload[] = []; + const revoked: string[] = []; + const timers = new Map void>(); + const delays: number[] = []; + let timerId = 0; + const lifecycle = createArtifactDownloadLifecycle({ + publish: (value) => { pending = value; }, + revoke: (url) => { revoked.push(url); }, + schedule: (callback, delay) => { delays.push(delay); timers.set(++timerId, callback); return timerId; }, + cancel: (id) => { timers.delete(id); }, + }); + return { lifecycle, revoked, timers, delays, pending: () => pending }; +} + +test('concurrent downloads keep distinct filenames and URLs until their own delayed release', async () => { + const h = setup(); + const first = h.lifecycle.enqueue('blob:first', 'first.png'); + const second = h.lifecycle.enqueue('blob:second', 'second.wav'); + const [a, b] = h.pending(); + assert.deepEqual([a.filename, b.filename], ['first.png', 'second.wav']); + let clicks = 0; + h.lifecycle.activate(b.id, () => { clicks += 1; }); + await second; + assert.deepEqual(h.pending(), [a]); + h.lifecycle.activate(a.id, () => { clicks += 1; }); + await first; + h.lifecycle.activate(a.id, () => { clicks += 1; }); + assert.equal(clicks, 2); + assert.deepEqual(h.pending(), []); + assert.deepEqual(h.revoked, []); + assert.deepEqual(h.delays, [30_000, 30_000]); + h.timers.get(1)!(); + assert.deepEqual(h.revoked, ['blob:second']); + h.timers.get(2)!(); + assert.deepEqual(h.revoked, ['blob:second', 'blob:first']); + h.lifecycle.dispose(); + assert.equal(h.revoked.length, 2); +}); + +test('click failure preserves the original error and immediately releases its URL', async () => { + const h = setup(); + const result = h.lifecycle.enqueue('blob:failed', 'failed.bin'); + const original = new Error('activation rejected'); + const rejection = assert.rejects(result, (error) => error === original); + h.lifecycle.activate(h.pending()[0].id, () => { throw original; }); + await rejection; + assert.deepEqual(h.revoked, ['blob:failed']); + assert.equal(h.timers.size, 0); + assert.deepEqual(h.pending(), []); +}); + +test('unmount settles pending work but preserves the clicked URL grace period', async () => { + const h = setup(); + const active = h.lifecycle.enqueue('blob:active', 'active.bin'); + h.lifecycle.activate(h.pending()[0].id, () => {}); + await active; + const pending = h.lifecycle.enqueue('blob:pending', 'pending.bin'); + const id = h.pending()[0].id; + const rejection = assert.rejects(pending, /disposed before activation/); + h.lifecycle.dispose(); + await rejection; + h.lifecycle.activate(id, () => assert.fail('disposed download clicked')); + h.lifecycle.dispose(); + assert.deepEqual(h.revoked, ['blob:pending']); + assert.equal(h.timers.size, 1); + h.timers.get(1)!(); + assert.deepEqual(h.revoked, ['blob:pending', 'blob:active']); + assert.equal(h.timers.size, 0); + assert.deepEqual(h.pending(), []); +}); + +test('a late URL handed to an unmounted owner is reclaimed and cannot hang', async () => { + const h = setup(); + h.lifecycle.dispose(); + await assert.rejects(h.lifecycle.enqueue('blob:late', 'late.bin'), /disposed/); + assert.deepEqual(h.revoked, ['blob:late']); + assert.deepEqual(h.pending(), []); +}); + + +test('successful click that unmounts still retains its URL for the grace period', async () => { + const h = setup(); + const result = h.lifecycle.enqueue('blob:unmount', 'unmount.bin'); + h.lifecycle.activate(h.pending()[0].id, () => h.lifecycle.dispose()); + await result; + assert.deepEqual(h.revoked, []); + assert.deepEqual(h.delays, [30_000]); + h.timers.get(1)!(); + assert.deepEqual(h.revoked, ['blob:unmount']); + assert.equal(h.timers.size, 0); +}); + +test('failed click that unmounts preserves the error and reclaims immediately', async () => { + const h = setup(); + const result = h.lifecycle.enqueue('blob:failed-unmount', 'failed.bin'); + const original = new Error('click failed after navigation'); + const rejection = assert.rejects(result, (error) => error === original); + h.lifecycle.activate(h.pending()[0].id, () => { h.lifecycle.dispose(); throw original; }); + await rejection; + assert.deepEqual(h.revoked, ['blob:failed-unmount']); + assert.equal(h.timers.size, 0); +}); diff --git a/src/components/workbench/artifactDownloadLifecycle.ts b/src/components/workbench/artifactDownloadLifecycle.ts new file mode 100644 index 000000000..8d8c7555f --- /dev/null +++ b/src/components/workbench/artifactDownloadLifecycle.ts @@ -0,0 +1,91 @@ +export interface PendingArtifactDownload { + id: number; + objectUrl: string; + filename: string; +} + +interface DownloadEntry { + request: PendingArtifactDownload; + resolve?: () => void; + reject?: (error: unknown) => void; + timer?: number; + activated: boolean; +} + +interface DownloadLifecycleOptions { + publish: (pending: PendingArtifactDownload[]) => void; + revoke?: (objectUrl: string) => void; + schedule?: (callback: () => void, delay: number) => number; + cancel?: (timer: number) => void; +} + +const REVOKE_DELAY_MS = 30_000; + +/** Own each download URL from admission through click, delayed release, or disposal. */ +export function createArtifactDownloadLifecycle(options: DownloadLifecycleOptions) { + const { + revoke = (objectUrl: string) => URL.revokeObjectURL(objectUrl), + schedule = (callback: () => void, delay: number) => window.setTimeout(callback, delay), + cancel = (timer: number) => window.clearTimeout(timer), + } = options; + let publishPending: DownloadLifecycleOptions['publish'] | undefined = options.publish; + const entries = new Map(); + let serial = 0; + let disposed = false; + + function publish(): void { + publishPending?.([...entries.values()].filter((entry) => !entry.activated).map((entry) => entry.request)); + } + + function release(entry: DownloadEntry): void { + if (!entries.delete(entry.request.id)) return; + if (entry.timer !== undefined) cancel(entry.timer); + revoke(entry.request.objectUrl); + } + + return { + enqueue(objectUrl: string, filename: string): Promise { + if (disposed) { + revoke(objectUrl); + return Promise.reject(new Error('Artifact download owner is disposed.')); + } + return new Promise((resolve, reject) => { + const request = { id: ++serial, objectUrl, filename }; + entries.set(request.id, { request, resolve, reject, activated: false }); + publish(); + }); + }, + activate(id: number, click: () => void): void { + const entry = entries.get(id); + if (!entry || entry.activated || disposed) return; + entry.activated = true; + try { + click(); + entry.timer = schedule(() => release(entry), REVOKE_DELAY_MS); + entry.resolve?.(); + } catch (error) { + release(entry); + entry.reject?.(error); + } + entry.resolve = undefined; + entry.reject = undefined; + publish(); + }, + dispose(): void { + if (disposed) return; + disposed = true; + for (const entry of entries.values()) { + // A successful click keeps its grace timer even when the UI goes away. + // In-progress activation finishes its success/error path after click returns. + if (!entry.activated) { + release(entry); + entry.reject?.(new Error('Artifact download owner was disposed before activation.')); + entry.resolve = undefined; + entry.reject = undefined; + } + } + publishPending?.([]); + publishPending = undefined; + }, + }; +} From e6160c068164c878c95fe148aea6714df08681d4 Mon Sep 17 00:00:00 2001 From: Puma Date: Sat, 3 Oct 2026 01:22:42 -0700 Subject: [PATCH 02/21] fix(a11y): disclose video caption limits and document graph activation --- .../reports/2026-10-03-preview-a11y-limitations.md | 9 +++++++++ src/components/workbench/IoInspectorPage.svelte | 6 ++++++ src/components/workbench/RunGraphSnapshot.svelte | 1 + .../workbench/ioInspectorPresenters.test.ts | 12 ++++++++++++ src/components/workbench/ioInspectorPresenters.ts | 4 +++- 5 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-preview-a11y-limitations.md diff --git a/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-preview-a11y-limitations.md b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-preview-a11y-limitations.md new file mode 100644 index 000000000..e8805a559 --- /dev/null +++ b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-preview-a11y-limitations.md @@ -0,0 +1,9 @@ +# Preview accessibility review and disclosure + +The run graph's conditional button role and tabindex share the same onSelectNode guard. Existing click and Enter/Space activation remain unchanged; the adjacent review reason explains the static compiler limitation without weakening keyboard behavior. + +The artifact projection currently has no caption-track association. Video renderer summaries now disclose that no caption track is provided for this preview; the video preview displays that notice. This does not assert that arbitrary source media has no embedded or burned-in captions, or claim full accessibility. No dummy track is created. Tests cover explicit and inferred video media and non-video families. Current track-association state is absent by contract; adding supported association and validating real accessible playback remain future work. + +Local qualification: 27 presenter tests and 547 full frontend tests pass; the eight parser regressions and full accessibility scan pass; TypeScript and full ESLint pass; both changed Svelte components compile without warnings. This is static/controller evidence, not a real-browser accessibility audit. Independent source review and hosted qualification are pending. + +Independent source review accepted tree bc2ae922fb12ec0776d7bc6be0a52ff3f6297861. The notice describes this preview's absent track association only, and the role/tabindex rationale matches existing activation behavior. This is not a blanket WCAG conformance claim. diff --git a/src/components/workbench/IoInspectorPage.svelte b/src/components/workbench/IoInspectorPage.svelte index 234b96653..6c84f3bf4 100644 --- a/src/components/workbench/IoInspectorPage.svelte +++ b/src/components/workbench/IoInspectorPage.svelte @@ -1049,6 +1049,7 @@ aria-label={`Audio preview of ${artifact.artifact_id}`} > {:else if renderer.family === 'video'} + + {#if renderer.captionNotice} +

+ {renderer.captionNotice} +

+ {/if} {:else if bodyPreview.text !== null && bodyPreview.text !== undefined}
             {/each}
             {#each canvas.nodes as node (node.id)}
+              
               
                {
   assert.equal(rows.find((row) => row.label === 'Last Event Seq')?.value, '44');
   assert.deepEqual(buildRetentionCleanupDetailRows(null), []);
 });
+
+
+test('only video previews disclose the currently absent caption-track association', () => {
+  const video = buildIoArtifactRendererSummary({ media_type: 'video/mp4', payload_ref: 'artifact://video' });
+  assert.equal(video.family, 'video');
+  assert.equal(video.captionNotice, 'No caption track is provided for this preview.');
+  const inferredVideo = buildIoArtifactRendererSummary({ media_type: null, payload_kind: 'video', payload_ref: 'artifact://video' });
+  assert.equal(inferredVideo.captionNotice, video.captionNotice);
+  for (const media_type of ['audio/wav', 'image/png', 'text/plain', 'application/json', 'application/octet-stream']) {
+    assert.equal(buildIoArtifactRendererSummary({ media_type, payload_ref: 'artifact://other' }).captionNotice, undefined);
+  }
+});
diff --git a/src/components/workbench/ioInspectorPresenters.ts b/src/components/workbench/ioInspectorPresenters.ts
index e1d7b1392..d496ae17e 100644
--- a/src/components/workbench/ioInspectorPresenters.ts
+++ b/src/components/workbench/ioInspectorPresenters.ts
@@ -40,6 +40,7 @@ export interface IoArtifactRendererSummary {
   family: IoArtifactMediaFamily;
   title: string;
   detail: string;
+  captionNotice?: string;
 }
 
 export interface IoArtifactDescriptorMetadataRow {
@@ -467,7 +468,8 @@ export function buildIoArtifactRendererSummary(
     case 'audio':
       return { family, title: 'Audio', detail };
     case 'video':
-      return { family, title: 'Video', detail };
+      // The artifact projection has no caption-track association to supply to this preview.
+      return { family, title: 'Video', detail, captionNotice: 'No caption track is provided for this preview.' };
     case '3d':
       return { family, title: '3D asset', detail };
     case 'table':

From 0b5c0df663d76dc97f2ba3836d33ea2404ca79df Mon Sep 17 00:00:00 2001
From: Puma 
Date: Sat, 3 Oct 2026 03:12:13 -0700
Subject: [PATCH 03/21] fix(deps): update frontend security dependency cohort

---
 ...2026-10-03-frontend-dependency-security.md |  35 ++
 package-lock.json                             | 552 ++++++------------
 package.json                                  |  13 +-
 3 files changed, 233 insertions(+), 367 deletions(-)
 create mode 100644 docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-frontend-dependency-security.md

diff --git a/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-frontend-dependency-security.md b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-frontend-dependency-security.md
new file mode 100644
index 000000000..4bb99fe80
--- /dev/null
+++ b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-frontend-dependency-security.md
@@ -0,0 +1,35 @@
+# Frontend dependency security repair (2026-10-03)
+
+Status: implemented and source-reviewed; exact-head hosted and GUI qualification remain pending. Parent: PR #20, `8a08b6a010ee62fbd3b29e193c65922c0769b928`. Standards reviewed at Coding-Standards `dcc56f26e884ade260770beceba2501d3746200d`; this is newer than the active plan's planning baseline, without changing plan or release authority.
+
+## Resolution and rationale
+
+- Upgrade the four direct Tiptap requirements from `^3.15.3` to `^3.30.5`; all 26 locked Tiptap packages resolve to `3.30.5`, satisfying upstream's exact cohort peers.
+- Upgrade Svelte `5.55.4` to `5.55.7` (requirement `^5.55.7`), and resolve its transitive devalue from `5.7.1` to `5.9.3`. No direct devalue dependency or permanent override is added.
+- [Upstream pm 3.30.5](https://github.com/ueberdosis/tiptap/blob/v3.30.5/packages/pm/package.json) removes prosemirror-markdown. The regenerated graph prunes prosemirror-markdown, markdown-it, linkify-it and their unneeded dependencies; remove the obsolete markdown-it 14.1.1 override. The separate linkifyjs dependency remains and resolves to 4.3.3 through Tiptap.
+- Preserve Vite 6.4.1 and Svelte Vite plugin 5.1.1. Other version changes are confined to Tiptap's ProseMirror requirements. No new package entries appear. Resolver-only peer-flag normalization affects unchanged packages; argparse, entities and escape-string-regexp become dev-only after the production chain is pruned.
+- Root manifest/root lock remain the only npm resolution authority. Registry metadata selected exact candidates; temporary generation constraints prevented caret ranges selecting later releases, then were removed and the lock regenerated. Recovery is restoration of the prior manifest/lock pair, not a second active resolution.
+
+[Official Svelte release](https://github.com/sveltejs/svelte/releases/tag/svelte@5.55.7) and [devalue release](https://github.com/sveltejs/devalue/releases/tag/v5.9.3) describe the security corrections. No source-search result is treated as proof of non-exposure. npm audit and resolver evidence prove their stated graph/advisory properties, not complete application security or release readiness.
+
+## Advisory disposition
+
+A fresh production audit of the parent lock reported five affected package nodes (three high, two moderate). The following advisory IDs are captured from that audit; remediation is the selected upgrade, or graph removal for markdown-it/linkify-it:
+
+- @tiptap/core: [GHSA-cp6q-959q-f8rh](https://github.com/advisories/GHSA-cp6q-959q-f8rh), [GHSA-j95f-988m-3j2f](https://github.com/advisories/GHSA-j95f-988m-3j2f).
+- devalue: [GHSA-77vg-94rm-hx3p](https://github.com/advisories/GHSA-77vg-94rm-hx3p), [GHSA-9rgm-9g3h-6x36](https://github.com/advisories/GHSA-9rgm-9g3h-6x36), [GHSA-j22f-vq7h-c4qm](https://github.com/advisories/GHSA-j22f-vq7h-c4qm), [GHSA-hx4r-w6wj-j8fg](https://github.com/advisories/GHSA-hx4r-w6wj-j8fg), [GHSA-mcm9-63f2-9j32](https://github.com/advisories/GHSA-mcm9-63f2-9j32), [GHSA-wf3x-273g-mvxv](https://github.com/advisories/GHSA-wf3x-273g-mvxv), [GHSA-4q55-j62x-fr9h](https://github.com/advisories/GHSA-4q55-j62x-fr9h).
+- linkify-it: [GHSA-22p9-wv53-3rq4](https://github.com/advisories/GHSA-22p9-wv53-3rq4), [GHSA-v245-v573-v5vm](https://github.com/advisories/GHSA-v245-v573-v5vm).
+- markdown-it: [GHSA-6v5v-wf23-fmfq](https://github.com/advisories/GHSA-6v5v-wf23-fmfq), [GHSA-253c-mchw-3w2r](https://github.com/advisories/GHSA-253c-mchw-3w2r).
+- svelte: [GHSA-f3cj-j4f6-wq85](https://github.com/advisories/GHSA-f3cj-j4f6-wq85), [GHSA-rcqx-6q8c-2c42](https://github.com/advisories/GHSA-rcqx-6q8c-2c42), [GHSA-9rmh-mm8f-r9h6](https://github.com/advisories/GHSA-9rmh-mm8f-r9h6), [GHSA-pr6f-5x2q-rwfp](https://github.com/advisories/GHSA-pr6f-5x2q-rwfp).
+
+## Verification and limits
+
+Local generation and clean install used Node 24.19.0/npm 11.9.0 because the pinned Node 24.12.0/npm 11.6.2 were unavailable. No global toolchain was changed. A clean hosted install and applicable checks using both declared versions are required before qualification; the local results do not waive this requirement.
+
+Passed against the reviewed manifest/lock: `npm ci --include=optional --no-fund --no-audit`, `npm ls --all`, `npm audit --omit=dev --audit-level=high` (zero findings), `npm run typecheck`, `npm run test:frontend` (540/540), `npm run build`, `npm run lint`, `npm run lint:full`, staged decision traceability, and whitespace validation. Existing resolver and audit checks provide the dependency regression evidence; no duplicate version-assertion harness is added.
+
+`npm run lint:no-new` stops at the existing IoInspectorPage.svelte:520 DOM-mutation finding. Separately, `npm run lint:a11y` passes its eight parser tests but reports existing reviewed-ignore annotations at IoInspectorPage.svelte:1034 and RunGraphSnapshot.svelte:156. These unchanged UI issues belong to the parallel UI repair.
+
+`npm run test:workflow-editor-image-gui` exits 2 at preflight because PANTOGRAPH_DIFFUSION_SMOKE_PUMAS_MODEL_ID is unavailable. Real Pumas model/artifact IDs, saved workflow, Python runtime, desktop drivers/display and built host remain prerequisites; no GUI success is claimed. The integration owner must obtain that qualified evidence. Rust/desktop compilation and release publication are outside this dependency slice.
+
+The source manifest/lock pair was independently reviewed before publication at staged tree `b75bac90dbbda40c3006e3228f0ce5eab8ed8f56`. Subsequent documentation adds this report without changing those files. Keep the task worktree protected while hosted qualification or integration is pending; the integration owner controls final merge and terminal disposition.
diff --git a/package-lock.json b/package-lock.json
index d1f5f8729..a7ecc0235 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,13 +13,13 @@
       "dependencies": {
         "@tauri-apps/api": "^2.9.0",
         "@tauri-apps/plugin-dialog": "^2.6.0",
-        "@tiptap/core": "^3.15.3",
-        "@tiptap/extension-placeholder": "^3.15.3",
-        "@tiptap/pm": "^3.15.3",
-        "@tiptap/starter-kit": "^3.15.3",
+        "@tiptap/core": "^3.30.5",
+        "@tiptap/extension-placeholder": "^3.30.5",
+        "@tiptap/pm": "^3.30.5",
+        "@tiptap/starter-kit": "^3.30.5",
         "@xyflow/svelte": "^1.5.0",
         "lucide-svelte": "^0.562.0",
-        "svelte": "^5.55.4",
+        "svelte": "^5.55.7",
         "three": "^0.183.1"
       },
       "devDependencies": {
@@ -1377,12 +1377,6 @@
         "node": ">=18"
       }
     },
-    "node_modules/@remirror/core-constants": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz",
-      "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==",
-      "license": "MIT"
-    },
     "node_modules/@rollup/rollup-android-arm-eabi": {
       "version": "4.55.1",
       "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.55.1.tgz",
@@ -1784,7 +1778,6 @@
       "integrity": "sha512-Y1Cs7hhTc+a5E9Va/xwKlAJoariQyHY+5zBgCZg4PFWNYQ1nMN9sjK1zhw1gK69DuqVP++sht/1GZg1aRwmAXQ==",
       "dev": true,
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "@sveltejs/vite-plugin-svelte-inspector": "^4.0.1",
         "debug": "^4.4.1",
@@ -2056,353 +2049,345 @@
       }
     },
     "node_modules/@tiptap/core": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.15.3.tgz",
-      "integrity": "sha512-bmXydIHfm2rEtGju39FiQNfzkFx9CDvJe+xem1dgEZ2P6Dj7nQX9LnA1ZscW7TuzbBRkL5p3dwuBIi3f62A66A==",
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.30.5.tgz",
+      "integrity": "sha512-3O7N0FyKIfuLV+xrdWyDM3V5eUY/q2CgLjhhMwOAbM1Pu7VPp9VP+TpEYOdH8aRyB+h1vj5hX5A747D8ZrPfHA==",
       "license": "MIT",
-      "peer": true,
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/pm": "^3.15.3"
+        "@tiptap/pm": "3.30.5"
       }
     },
     "node_modules/@tiptap/extension-blockquote": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.15.3.tgz",
-      "integrity": "sha512-13x5UsQXtttFpoS/n1q173OeurNxppsdWgP3JfsshzyxIghhC141uL3H6SGYQLPU31AizgDs2OEzt6cSUevaZg==",
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.30.5.tgz",
+      "integrity": "sha512-8pf1ZDrl6XlVPUee/2YlWZPFSF7yqsJEX6WLW41x06MT1dapG3Qudcns0znj6kYsX8JXTJ+Mhegy4z19bvTtRg==",
       "license": "MIT",
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/core": "^3.15.3"
+        "@tiptap/core": "3.30.5",
+        "@tiptap/pm": "3.30.5"
       }
     },
     "node_modules/@tiptap/extension-bold": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.15.3.tgz",
-      "integrity": "sha512-I8JYbkkUTNUXbHd/wCse2bR0QhQtJD7+0/lgrKOmGfv5ioLxcki079Nzuqqay3PjgYoJLIJQvm3RAGxT+4X91w==",
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.30.5.tgz",
+      "integrity": "sha512-MLZS+s/BJiJbv2C2M3G4FQGn43kPwYUUr2TiW3afSn+dRkEZlDxx5CwZYbe1PEt2+VX/SIifn945Oqr3s0axSA==",
       "license": "MIT",
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/core": "^3.15.3"
+        "@tiptap/core": "3.30.5"
       }
     },
     "node_modules/@tiptap/extension-bullet-list": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.15.3.tgz",
-      "integrity": "sha512-MGwEkNT7ltst6XaWf0ObNgpKQ4PvuuV3igkBrdYnQS+qaAx9IF4isygVPqUc9DvjYC306jpyKsNqNrENIXcosA==",
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-bullet-list/-/extension-bullet-list-3.30.5.tgz",
+      "integrity": "sha512-66OGw4suO0Gr/4QAEiSvVi0eSvEqStvu1moHUSvUlwvEqnuB0ME2w9HuRogPElzWdrYLzbBA47mlCi9Veva0ew==",
       "license": "MIT",
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/extension-list": "^3.15.3"
+        "@tiptap/extension-list": "3.30.5"
       }
     },
     "node_modules/@tiptap/extension-code": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.15.3.tgz",
-      "integrity": "sha512-x6LFt3Og6MFINYpsMzrJnz7vaT9Yk1t4oXkbJsJRSavdIWBEBcoRudKZ4sSe/AnsYlRJs8FY2uR76mt9e+7xAQ==",
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.30.5.tgz",
+      "integrity": "sha512-0dCt8eBo4sMtw+LjUu+0GF0JrTlREROdWoy8TM5kFPxJ5ZU2LKDiROXDPwXStaaoFFR/aStLH9xSpdz7cHUiUA==",
       "license": "MIT",
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/core": "^3.15.3"
+        "@tiptap/core": "3.30.5"
       }
     },
     "node_modules/@tiptap/extension-code-block": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.15.3.tgz",
-      "integrity": "sha512-q1UB9icNfdJppTqMIUWfoRKkx5SSdWIpwZoL2NeOI5Ah3E20/dQKVttIgLhsE521chyvxCYCRaHD5tMNGKfhyw==",
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.30.5.tgz",
+      "integrity": "sha512-SvkNoOio2xBy9AtSMyFKMp88MTUvyIXsGCnuz71INV2wHdH4VfRPoQLT6e077LpTCa9w6LHyTKtbZA4HYOp7wg==",
       "license": "MIT",
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/core": "^3.15.3",
-        "@tiptap/pm": "^3.15.3"
+        "@tiptap/core": "3.30.5",
+        "@tiptap/pm": "3.30.5"
       }
     },
     "node_modules/@tiptap/extension-document": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.15.3.tgz",
-      "integrity": "sha512-AC72nI2gnogBuETCKbZekn+h6t5FGGcZG2abPGKbz/x9rwpb6qV2hcbAQ30t6M7H6cTOh2/Ut8bEV2MtMB15sw==",
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.30.5.tgz",
+      "integrity": "sha512-4mKoD3bBr5W2AQ2Y/7amOqcVw0eqJbUwRtwvxypeo5Hi0PB9O5Ev8P05c3EUTo10bllqdKYXGeCU5AcgdQsHWQ==",
       "license": "MIT",
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/core": "^3.15.3"
+        "@tiptap/core": "3.30.5"
       }
     },
     "node_modules/@tiptap/extension-dropcursor": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.15.3.tgz",
-      "integrity": "sha512-jGI5XZpdo8GSYQFj7HY15/oEwC2m2TqZz0/Fln5qIhY32XlZhWrsMuMI6WbUJrTH16es7xO6jmRlDsc6g+vJWg==",
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-dropcursor/-/extension-dropcursor-3.30.5.tgz",
+      "integrity": "sha512-gJxn9PMUee8zazQBYqbOZiI7zTFXPVJ15AzTnLfUu4eDtPieMwtpgcjhCt4bZlp37drLm+Li76wWLirb7iLxsw==",
       "license": "MIT",
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/extensions": "^3.15.3"
+        "@tiptap/extensions": "3.30.5"
       }
     },
     "node_modules/@tiptap/extension-gapcursor": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.15.3.tgz",
-      "integrity": "sha512-Kaw0sNzP0bQI/xEAMSfIpja6xhsu9WqqAK/puzOIS1RKWO47Wps/tzqdSJ9gfslPIb5uY5mKCfy8UR8Xgiia8w==",
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-gapcursor/-/extension-gapcursor-3.30.5.tgz",
+      "integrity": "sha512-h3m2ZA1XXLAfdHwdtG0MZnf0KWbNyP8xTI3RUlTDR5apmKmYVBaeocOJwTcFFZ92gPKqxYKyhteZqYtHOCZGJA==",
       "license": "MIT",
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/extensions": "^3.15.3"
+        "@tiptap/extensions": "3.30.5"
       }
     },
     "node_modules/@tiptap/extension-hard-break": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.15.3.tgz",
-      "integrity": "sha512-8HjxmeRbBiXW+7JKemAJtZtHlmXQ9iji398CPQ0yYde68WbIvUhHXjmbJE5pxFvvQTJ/zJv1aISeEOZN2bKBaw==",
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.30.5.tgz",
+      "integrity": "sha512-PcL4Z8l/DlauwZUJg0jf6SXKk6/YOXJtd8yYzqHrmCLRoY4kAa7+TxUhc/lWhuitUiOZ8+nB8V/NNM+WR6mRaw==",
       "license": "MIT",
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/core": "^3.15.3"
+        "@tiptap/core": "3.30.5"
       }
     },
     "node_modules/@tiptap/extension-heading": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.15.3.tgz",
-      "integrity": "sha512-G1GG6iN1YXPS+75arDpo+bYRzhr3dNDw99c7D7na3aDawa9Qp7sZ/bVrzFUUcVEce0cD6h83yY7AooBxEc67hA==",
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.30.5.tgz",
+      "integrity": "sha512-x7e7+p1bWXvwz6vqONPP1SVB73V0gZP7LIDL/5KMnxWLjahMTabgVPgjV30kz7GsOEgUYtEf7eCyGVGNNWL5Dg==",
       "license": "MIT",
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/core": "^3.15.3"
+        "@tiptap/core": "3.30.5"
       }
     },
     "node_modules/@tiptap/extension-horizontal-rule": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.15.3.tgz",
-      "integrity": "sha512-FYkN7L6JsfwwNEntmLklCVKvgL0B0N47OXMacRk6kYKQmVQ4Nvc7q/VJLpD9sk4wh4KT1aiCBfhKEBTu5pv1fg==",
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.30.5.tgz",
+      "integrity": "sha512-s5t2xM6wPYRJl2cqYplc7Ze8m8xddk4DP2v0aLFcUuD100D1B5eJbxsiwLK6wBAzNwiuVjd486RnmUpcswaXhQ==",
       "license": "MIT",
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/core": "^3.15.3",
-        "@tiptap/pm": "^3.15.3"
+        "@tiptap/core": "3.30.5",
+        "@tiptap/pm": "3.30.5"
       }
     },
     "node_modules/@tiptap/extension-italic": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.15.3.tgz",
-      "integrity": "sha512-6XeuPjcWy7OBxpkgOV7bD6PATO5jhIxc8SEK4m8xn8nelGTBIbHGqK37evRv+QkC7E0MUryLtzwnmmiaxcKL0Q==",
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.30.5.tgz",
+      "integrity": "sha512-Fu9EuSRlHQNGES7UhY4mQod3yUKnWwxsGlPuDJURfwju/Ug04Jz8iRuERZBPcRgLNkwKDmbDsWUZF72RRIL2Uw==",
       "license": "MIT",
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/core": "^3.15.3"
+        "@tiptap/core": "3.30.5"
       }
     },
     "node_modules/@tiptap/extension-link": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.15.3.tgz",
-      "integrity": "sha512-PdDXyBF9Wco9U1x6e+b7tKBWG+kqBDXDmaYXHkFm/gYuQCQafVJ5mdrDdKgkHDWVnJzMWZXBcZjT9r57qtlLWg==",
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.30.5.tgz",
+      "integrity": "sha512-zsQ+q83HpCYOMTNzdjb12dggE7sZMp0aqQJhArdavSTl8hLbVY/u3qJN88t25IEwAKVK0pcTJfWWA2QFl8bjAQ==",
       "license": "MIT",
       "dependencies": {
-        "linkifyjs": "^4.3.2"
+        "linkifyjs": "^4.3.3"
       },
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/core": "^3.15.3",
-        "@tiptap/pm": "^3.15.3"
+        "@tiptap/core": "3.30.5",
+        "@tiptap/pm": "3.30.5"
       }
     },
     "node_modules/@tiptap/extension-list": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.15.3.tgz",
-      "integrity": "sha512-n7y/MF9lAM5qlpuH5IR4/uq+kJPEJpe9NrEiH+NmkO/5KJ6cXzpJ6F4U17sMLf2SNCq+TWN9QK8QzoKxIn50VQ==",
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.30.5.tgz",
+      "integrity": "sha512-CHThihH+7TA0TfwtmA+eTXP1MAsA/+p011olJHg8Rilj5xA+L+0IFkjhN16JnZkBgSA4MnfEv15UMNfRaNpuVg==",
       "license": "MIT",
-      "peer": true,
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/core": "^3.15.3",
-        "@tiptap/pm": "^3.15.3"
+        "@tiptap/core": "3.30.5",
+        "@tiptap/pm": "3.30.5"
       }
     },
     "node_modules/@tiptap/extension-list-item": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.15.3.tgz",
-      "integrity": "sha512-CCxL5ek1p0lO5e8aqhnPzIySldXRSigBFk2fP9OLgdl5qKFLs2MGc19jFlx5+/kjXnEsdQTFbGY1Sizzt0TVDw==",
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-list-item/-/extension-list-item-3.30.5.tgz",
+      "integrity": "sha512-N0fKUyQkPQBvVB48imjfIfdexU9VsAX1RydajYP2xAt+QOJ8KHVmXv1EGTp+v7pGdZMLdls1cSZ2f352vW3aWQ==",
       "license": "MIT",
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/extension-list": "^3.15.3"
+        "@tiptap/extension-list": "3.30.5"
       }
     },
     "node_modules/@tiptap/extension-list-keymap": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.15.3.tgz",
-      "integrity": "sha512-UxqnTEEAKrL+wFQeSyC9z0mgyUUVRS2WTcVFoLZCE6/Xus9F53S4bl7VKFadjmqI4GpDk5Oe2IOUc72o129jWg==",
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-list-keymap/-/extension-list-keymap-3.30.5.tgz",
+      "integrity": "sha512-3pLR2yo29uhowaGMbD8v71XpgdNmqotBg1NXlyTMVFzxELj+VjjeLLTqSguR/iWDUbHXNzIsfMEgpRnNMO//8w==",
       "license": "MIT",
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/extension-list": "^3.15.3"
+        "@tiptap/extension-list": "3.30.5"
       }
     },
     "node_modules/@tiptap/extension-ordered-list": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.15.3.tgz",
-      "integrity": "sha512-/8uhw528Iy0c9wF6tHCiIn0ToM0Ml6Ll2c/3iPRnKr4IjXwx2Lr994stUFihb+oqGZwV1J8CPcZJ4Ufpdqi4Dw==",
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-3.30.5.tgz",
+      "integrity": "sha512-bUGUnSAgjZhoUWBtr+1wRJHF3NnNvuKQr6sQ8le1tJ2yvLq448ZsSZjZGvTNeLsy3GDKBoMJ0rqLUK34+nM3hg==",
       "license": "MIT",
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/extension-list": "^3.15.3"
+        "@tiptap/extension-list": "3.30.5"
       }
     },
     "node_modules/@tiptap/extension-paragraph": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.15.3.tgz",
-      "integrity": "sha512-lc0Qu/1AgzcEfS67NJMj5tSHHhH6NtA6uUpvppEKGsvJwgE2wKG1onE4isrVXmcGRdxSMiCtyTDemPNMu6/ozQ==",
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.30.5.tgz",
+      "integrity": "sha512-GrNNlAImfQhYRtGE95YNAjcTclUUMPHs9JeE9vMgfcoKcOmZ6GsbWUdN0hA55xqwGaUjnroOVtf77K9z1EbkJQ==",
       "license": "MIT",
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/core": "^3.15.3"
+        "@tiptap/core": "3.30.5"
       }
     },
     "node_modules/@tiptap/extension-placeholder": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-3.15.3.tgz",
-      "integrity": "sha512-XcHHnojT186hKIoOgcPBesXk89+caNGVUdMtc171Vcr/5s0dpnr4q5LfE+YRC+S85CpCxCRRnh84Ou+XRtOqrw==",
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-placeholder/-/extension-placeholder-3.30.5.tgz",
+      "integrity": "sha512-WgkX4ImAAeV+Qe7ATJ+Gio2MmKWON1CKpCKPHWgCObaoMEaSmdoxXPmI9wXMuM+AJydJepp4n7U/V4TVts14rg==",
       "license": "MIT",
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/extensions": "^3.15.3"
+        "@tiptap/extensions": "3.30.5"
       }
     },
     "node_modules/@tiptap/extension-strike": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.15.3.tgz",
-      "integrity": "sha512-Y1P3eGNY7RxQs2BcR6NfLo9VfEOplXXHAqkOM88oowWWOE7dMNeFFZM9H8HNxoQgXJ7H0aWW9B7ZTWM9hWli2Q==",
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.30.5.tgz",
+      "integrity": "sha512-r8IbWUm4YXNCkmc6h6dzuREQIGbO0x9z9l3GfQPUR3LxFtGssDLngE4dFXHRt/ejLWKEauy6clklf7LawtvYzw==",
       "license": "MIT",
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/core": "^3.15.3"
+        "@tiptap/core": "3.30.5"
       }
     },
     "node_modules/@tiptap/extension-text": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.15.3.tgz",
-      "integrity": "sha512-MhkBz8ZvrqOKtKNp+ZWISKkLUlTrDR7tbKZc2OnNcUTttL9dz0HwT+cg91GGz19fuo7ttDcfsPV6eVmflvGToA==",
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.30.5.tgz",
+      "integrity": "sha512-pOgj4mIGFlw4NUdA6PCTFP/MHrYWnOYiZRYeu0I3/Yo3iN4Am/CgYBLl8PWsNh+YPO2hA2DWs7ZMke9D2j5R+g==",
       "license": "MIT",
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/core": "^3.15.3"
+        "@tiptap/core": "3.30.5"
       }
     },
     "node_modules/@tiptap/extension-underline": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.15.3.tgz",
-      "integrity": "sha512-r/IwcNN0W366jGu4Y0n2MiFq9jGa4aopOwtfWO4d+J0DyeS2m7Go3+KwoUqi0wQTiVU74yfi4DF6eRsMQ9/iHQ==",
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.30.5.tgz",
+      "integrity": "sha512-u12G/WW2uFRY95BzrSAU9RW002K+7lFpSCL6fb7Puh8yLhek3lddOtAx0P+NI/PyMKtcoVMtBxoi+0mwVM7NPQ==",
       "license": "MIT",
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/core": "^3.15.3"
+        "@tiptap/core": "3.30.5"
       }
     },
     "node_modules/@tiptap/extensions": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.15.3.tgz",
-      "integrity": "sha512-ycx/BgxR4rc9tf3ZyTdI98Z19yKLFfqM3UN+v42ChuIwkzyr9zyp7kG8dB9xN2lNqrD+5y/HyJobz/VJ7T90gA==",
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/extensions/-/extensions-3.30.5.tgz",
+      "integrity": "sha512-5x3OiCYBvXz0G5OGM8f7ka++1dh9EXdNRZ8IcMcEd+QwW4RPwvmJ2EjheA1eRRyMlcbNU7T+4IRlBXURlTetEA==",
       "license": "MIT",
-      "peer": true,
       "funding": {
         "type": "github",
         "url": "https://github.com/sponsors/ueberdosis"
       },
       "peerDependencies": {
-        "@tiptap/core": "^3.15.3",
-        "@tiptap/pm": "^3.15.3"
+        "@tiptap/core": "3.30.5",
+        "@tiptap/pm": "3.30.5"
       }
     },
     "node_modules/@tiptap/pm": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.15.3.tgz",
-      "integrity": "sha512-Zm1BaU1TwFi3CQiisxjgnzzIus+q40bBKWLqXf6WEaus8Z6+vo1MT2pU52dBCMIRaW9XNDq3E5cmGtMc1AlveA==",
-      "license": "MIT",
-      "peer": true,
-      "dependencies": {
-        "prosemirror-changeset": "^2.3.0",
-        "prosemirror-collab": "^1.3.1",
-        "prosemirror-commands": "^1.6.2",
-        "prosemirror-dropcursor": "^1.8.1",
-        "prosemirror-gapcursor": "^1.3.2",
-        "prosemirror-history": "^1.4.1",
-        "prosemirror-inputrules": "^1.4.0",
-        "prosemirror-keymap": "^1.2.2",
-        "prosemirror-markdown": "^1.13.1",
-        "prosemirror-menu": "^1.2.4",
-        "prosemirror-model": "^1.24.1",
-        "prosemirror-schema-basic": "^1.2.3",
-        "prosemirror-schema-list": "^1.5.0",
-        "prosemirror-state": "^1.4.3",
-        "prosemirror-tables": "^1.6.4",
-        "prosemirror-trailing-node": "^3.0.0",
-        "prosemirror-transform": "^1.10.2",
-        "prosemirror-view": "^1.38.1"
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.30.5.tgz",
+      "integrity": "sha512-gufkLkW2tA6PZPjivYxDiGzTIIftwqhmYI6lvvKu2S4FbhcysJgMAe/GXVSywzCRVVex9SrvCe6RFYrqnwRitQ==",
+      "license": "MIT",
+      "dependencies": {
+        "prosemirror-changeset": "^2.4.1",
+        "prosemirror-commands": "^1.7.1",
+        "prosemirror-dropcursor": "^1.8.2",
+        "prosemirror-gapcursor": "^1.4.1",
+        "prosemirror-history": "^1.5.0",
+        "prosemirror-inputrules": "^1.5.1",
+        "prosemirror-keymap": "^1.2.3",
+        "prosemirror-model": "^1.25.11",
+        "prosemirror-schema-list": "^1.5.1",
+        "prosemirror-state": "^1.4.4",
+        "prosemirror-tables": "^1.8.5",
+        "prosemirror-transform": "^1.12.0",
+        "prosemirror-view": "^1.41.9"
       },
       "funding": {
         "type": "github",
@@ -2410,35 +2395,35 @@
       }
     },
     "node_modules/@tiptap/starter-kit": {
-      "version": "3.15.3",
-      "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.15.3.tgz",
-      "integrity": "sha512-ia+eQr9Mt1ln2UO+kK4kFTJOrZK4GhvZXFjpCCYuHtco3rhr2fZAIxEEY4cl/vo5VO5WWyPqxhkFeLcoWmNjSw==",
-      "license": "MIT",
-      "dependencies": {
-        "@tiptap/core": "^3.15.3",
-        "@tiptap/extension-blockquote": "^3.15.3",
-        "@tiptap/extension-bold": "^3.15.3",
-        "@tiptap/extension-bullet-list": "^3.15.3",
-        "@tiptap/extension-code": "^3.15.3",
-        "@tiptap/extension-code-block": "^3.15.3",
-        "@tiptap/extension-document": "^3.15.3",
-        "@tiptap/extension-dropcursor": "^3.15.3",
-        "@tiptap/extension-gapcursor": "^3.15.3",
-        "@tiptap/extension-hard-break": "^3.15.3",
-        "@tiptap/extension-heading": "^3.15.3",
-        "@tiptap/extension-horizontal-rule": "^3.15.3",
-        "@tiptap/extension-italic": "^3.15.3",
-        "@tiptap/extension-link": "^3.15.3",
-        "@tiptap/extension-list": "^3.15.3",
-        "@tiptap/extension-list-item": "^3.15.3",
-        "@tiptap/extension-list-keymap": "^3.15.3",
-        "@tiptap/extension-ordered-list": "^3.15.3",
-        "@tiptap/extension-paragraph": "^3.15.3",
-        "@tiptap/extension-strike": "^3.15.3",
-        "@tiptap/extension-text": "^3.15.3",
-        "@tiptap/extension-underline": "^3.15.3",
-        "@tiptap/extensions": "^3.15.3",
-        "@tiptap/pm": "^3.15.3"
+      "version": "3.30.5",
+      "resolved": "https://registry.npmjs.org/@tiptap/starter-kit/-/starter-kit-3.30.5.tgz",
+      "integrity": "sha512-cmDRvukpoqjQjhE96q+l3tCLNZZcJKehxK+00Sp9F4XbKecfM2QpZ2TRHh3vi8qhCeDQNyMoCqfNaqznIjLjIQ==",
+      "license": "MIT",
+      "dependencies": {
+        "@tiptap/core": "3.30.5",
+        "@tiptap/extension-blockquote": "3.30.5",
+        "@tiptap/extension-bold": "3.30.5",
+        "@tiptap/extension-bullet-list": "3.30.5",
+        "@tiptap/extension-code": "3.30.5",
+        "@tiptap/extension-code-block": "3.30.5",
+        "@tiptap/extension-document": "3.30.5",
+        "@tiptap/extension-dropcursor": "3.30.5",
+        "@tiptap/extension-gapcursor": "3.30.5",
+        "@tiptap/extension-hard-break": "3.30.5",
+        "@tiptap/extension-heading": "3.30.5",
+        "@tiptap/extension-horizontal-rule": "3.30.5",
+        "@tiptap/extension-italic": "3.30.5",
+        "@tiptap/extension-link": "3.30.5",
+        "@tiptap/extension-list": "3.30.5",
+        "@tiptap/extension-list-item": "3.30.5",
+        "@tiptap/extension-list-keymap": "3.30.5",
+        "@tiptap/extension-ordered-list": "3.30.5",
+        "@tiptap/extension-paragraph": "3.30.5",
+        "@tiptap/extension-strike": "3.30.5",
+        "@tiptap/extension-text": "3.30.5",
+        "@tiptap/extension-underline": "3.30.5",
+        "@tiptap/extensions": "3.30.5",
+        "@tiptap/pm": "3.30.5"
       },
       "funding": {
         "type": "github",
@@ -2548,28 +2533,6 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/@types/linkify-it": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz",
-      "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
-      "license": "MIT"
-    },
-    "node_modules/@types/markdown-it": {
-      "version": "14.1.2",
-      "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz",
-      "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/linkify-it": "^5",
-        "@types/mdurl": "^2"
-      }
-    },
-    "node_modules/@types/mdurl": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz",
-      "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
-      "license": "MIT"
-    },
     "node_modules/@types/mocha": {
       "version": "10.0.10",
       "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.10.tgz",
@@ -2583,7 +2546,6 @@
       "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==",
       "dev": true,
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "undici-types": "~6.21.0"
       }
@@ -2735,7 +2697,6 @@
       "integrity": "sha512-npiaib8XzbjtzS2N4HlqPvlpxpmZ14FjSJrteZpPxGUaYPlvhzlzUZ4mZyABo0EFrOWnvyd0Xxroq//hKhtAWg==",
       "dev": true,
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "@typescript-eslint/scope-manager": "8.53.0",
         "@typescript-eslint/types": "8.53.0",
@@ -2843,7 +2804,6 @@
       "integrity": "sha512-Bmh9KX31Vlxa13+PqPvt4RzKRN1XORYSLlAE+sO1i28NkisGbTtSLFVB3l7PWdHtR3E0mVMuC7JilWJ99m2HxQ==",
       "devOptional": true,
       "license": "MIT",
-      "peer": true,
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
       },
@@ -3155,7 +3115,6 @@
       "integrity": "sha512-RNvk07rQFEizO30a0m5snaWDY4EF0vUZcUDV/aK7bs5RIao5RQwMr+cPNVu9CbvizAATGVRlQz02ppHF7U2Ypg==",
       "dev": true,
       "license": "MIT",
-      "peer": true,
       "engines": {
         "node": ">=18.20.0"
       },
@@ -3210,7 +3169,6 @@
       "integrity": "sha512-HdzDrRs+ywAqbXGKqe1i/bLtCv47plz4TvsHFH3j729OooT5VH38ctFn5aLXgECmiAKDkmH/A6kOq2Zh5DIxww==",
       "dev": true,
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "chalk": "^5.1.2",
         "loglevel": "^1.6.0",
@@ -3468,7 +3426,6 @@
       "resolved": "https://registry.npmjs.org/@xyflow/svelte/-/svelte-1.5.0.tgz",
       "integrity": "sha512-kHJVwMtabN7xthDJqLvx5wGT5qjOAVV90J9a26geDS51lrGsGb7CCxNRiZCMuVDlRkczGVIy9dkqdph6FL4slQ==",
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "@svelte-put/shortcut": "^4.1.0",
         "@xyflow/system": "0.0.74"
@@ -3524,7 +3481,6 @@
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz",
       "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
       "license": "MIT",
-      "peer": true,
       "bin": {
         "acorn": "bin/acorn"
       },
@@ -3701,6 +3657,7 @@
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
       "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+      "dev": true,
       "license": "Python-2.0"
     },
     "node_modules/aria-query": {
@@ -4020,7 +3977,6 @@
         }
       ],
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "baseline-browser-mapping": "^2.9.0",
         "caniuse-lite": "^1.0.30001759",
@@ -4551,12 +4507,6 @@
         "node": ">=20"
       }
     },
-    "node_modules/crelt": {
-      "version": "1.0.6",
-      "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz",
-      "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==",
-      "license": "MIT"
-    },
     "node_modules/cross-spawn": {
       "version": "7.0.6",
       "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -4685,7 +4635,6 @@
       "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
       "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
       "license": "ISC",
-      "peer": true,
       "engines": {
         "node": ">=12"
       }
@@ -4842,9 +4791,9 @@
       }
     },
     "node_modules/devalue": {
-      "version": "5.7.1",
-      "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.7.1.tgz",
-      "integrity": "sha512-MUbZ586EgQqdRnC4yDrlod3BEdyvE4TapGYHMW2CiaW+KkkFmWEFqBUaLltEZCGi0iFXCEjRF0OjF0DV2QHjOA==",
+      "version": "5.9.3",
+      "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.9.3.tgz",
+      "integrity": "sha512-xRumYOCUZN/EesqHEU3WOXanOZNvfZFZ/o1AHVFDX1yI0UAkZkOgDXt341CzKoBVIkgQba55/+DjGBKrIoKcHw==",
       "license": "MIT"
     },
     "node_modules/didyoumean": {
@@ -5101,6 +5050,7 @@
       "version": "4.5.0",
       "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
       "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+      "dev": true,
       "license": "BSD-2-Clause",
       "engines": {
         "node": ">=0.12"
@@ -5182,6 +5132,7 @@
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
       "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=10"
@@ -5218,7 +5169,6 @@
       "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
       "dev": true,
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.8.0",
         "@eslint-community/regexpp": "^4.12.1",
@@ -5532,7 +5482,6 @@
       "integrity": "sha512-VfLC9o84B40LEw+zX7UykUptKkscX1rPYY4jaAsQ6KyKL0X0ltDkWzKIUiY9g/u0ApNerrkhM/QEY0TDT8pJOQ==",
       "dev": true,
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "@vitest/snapshot": "^4.1.7",
         "deep-eql": "^5.0.2",
@@ -6709,7 +6658,6 @@
       "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
       "dev": true,
       "license": "MIT",
-      "peer": true,
       "bin": {
         "jiti": "bin/jiti.js"
       }
@@ -6925,19 +6873,10 @@
       "dev": true,
       "license": "MIT"
     },
-    "node_modules/linkify-it": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz",
-      "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==",
-      "license": "MIT",
-      "dependencies": {
-        "uc.micro": "^2.0.0"
-      }
-    },
     "node_modules/linkifyjs": {
-      "version": "4.3.2",
-      "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz",
-      "integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==",
+      "version": "4.3.3",
+      "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.3.tgz",
+      "integrity": "sha512-P8aEP5U/D1/IlTY2OeYsErdwh9bGuLE30NcXtKEjgdHcahveQoQwM2yZNsioQHsWFz0P7KKudisbrzCgR0sDHg==",
       "license": "MIT"
     },
     "node_modules/locate-app": {
@@ -7122,29 +7061,6 @@
         "@jridgewell/sourcemap-codec": "^1.5.5"
       }
     },
-    "node_modules/markdown-it": {
-      "version": "14.1.1",
-      "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz",
-      "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==",
-      "license": "MIT",
-      "dependencies": {
-        "argparse": "^2.0.1",
-        "entities": "^4.4.0",
-        "linkify-it": "^5.0.0",
-        "mdurl": "^2.0.0",
-        "punycode.js": "^2.3.1",
-        "uc.micro": "^2.1.0"
-      },
-      "bin": {
-        "markdown-it": "bin/markdown-it.mjs"
-      }
-    },
-    "node_modules/mdurl": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz",
-      "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
-      "license": "MIT"
-    },
     "node_modules/merge2": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@@ -7959,7 +7875,6 @@
         }
       ],
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "nanoid": "^3.3.11",
         "picocolors": "^1.1.1",
@@ -8251,27 +8166,18 @@
       }
     },
     "node_modules/prosemirror-changeset": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.3.1.tgz",
-      "integrity": "sha512-j0kORIBm8ayJNl3zQvD1TTPHJX3g042et6y/KQhZhnPrruO8exkTgG8X+NRpj7kIyMMEx74Xb3DyMIBtO0IKkQ==",
+      "version": "2.4.4",
+      "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.4.tgz",
+      "integrity": "sha512-Gyuz9jYdrAeA9I7AholyOes9KUmv/lEwsC5O9s5xF5VB3EgTPsXMGUxz4+H1B+wKVPdZbvRcg1Cp3x8qX+8ofA==",
       "license": "MIT",
       "dependencies": {
         "prosemirror-transform": "^1.0.0"
       }
     },
-    "node_modules/prosemirror-collab": {
-      "version": "1.3.1",
-      "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz",
-      "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==",
-      "license": "MIT",
-      "dependencies": {
-        "prosemirror-state": "^1.0.0"
-      }
-    },
     "node_modules/prosemirror-commands": {
-      "version": "1.7.1",
-      "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz",
-      "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==",
+      "version": "1.7.2",
+      "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.2.tgz",
+      "integrity": "sha512-q6Q6szxqdu9Xd6EcdKsqXghu5nQdZTpB4Q9yd04WRc7/jt763e/rT60Owh0L1GYY+T46o5rD+9lEN36dZS43tw==",
       "license": "MIT",
       "dependencies": {
         "prosemirror-model": "^1.0.0",
@@ -8280,9 +8186,9 @@
       }
     },
     "node_modules/prosemirror-dropcursor": {
-      "version": "1.8.2",
-      "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz",
-      "integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==",
+      "version": "1.8.4",
+      "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.4.tgz",
+      "integrity": "sha512-VmKxhcA6a+h5oPSYmfF1ZNeQqPbyYaIxqZsKqeYCyTlPtrjUgNaHL3P8vVaO19oqAV0zuaM/axkowZHrB1P3Tg==",
       "license": "MIT",
       "dependencies": {
         "prosemirror-state": "^1.0.0",
@@ -8291,9 +8197,9 @@
       }
     },
     "node_modules/prosemirror-gapcursor": {
-      "version": "1.4.0",
-      "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.0.tgz",
-      "integrity": "sha512-z00qvurSdCEWUIulij/isHaqu4uLS8r/Fi61IbjdIPJEonQgggbJsLnstW7Lgdk4zQ68/yr6B6bf7sJXowIgdQ==",
+      "version": "1.4.1",
+      "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz",
+      "integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==",
       "license": "MIT",
       "dependencies": {
         "prosemirror-keymap": "^1.0.0",
@@ -8303,9 +8209,9 @@
       }
     },
     "node_modules/prosemirror-history": {
-      "version": "1.5.0",
-      "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz",
-      "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==",
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.1.tgz",
+      "integrity": "sha512-onlwnqKvgtFg6srZOmDOAhlAy1FIHQpOExORybX0S+DvJepE5tPALrB9iGet/2sqqqrelSnB10OoiOJqAvu2DA==",
       "license": "MIT",
       "dependencies": {
         "prosemirror-state": "^1.2.2",
@@ -8334,48 +8240,15 @@
         "w3c-keyname": "^2.2.0"
       }
     },
-    "node_modules/prosemirror-markdown": {
-      "version": "1.13.2",
-      "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.2.tgz",
-      "integrity": "sha512-FPD9rHPdA9fqzNmIIDhhnYQ6WgNoSWX9StUZ8LEKapaXU9i6XgykaHKhp6XMyXlOWetmaFgGDS/nu/w9/vUc5g==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/markdown-it": "^14.0.0",
-        "markdown-it": "^14.0.0",
-        "prosemirror-model": "^1.25.0"
-      }
-    },
-    "node_modules/prosemirror-menu": {
-      "version": "1.2.5",
-      "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.2.5.tgz",
-      "integrity": "sha512-qwXzynnpBIeg1D7BAtjOusR+81xCp53j7iWu/IargiRZqRjGIlQuu1f3jFi+ehrHhWMLoyOQTSRx/IWZJqOYtQ==",
-      "license": "MIT",
-      "dependencies": {
-        "crelt": "^1.0.0",
-        "prosemirror-commands": "^1.0.0",
-        "prosemirror-history": "^1.0.0",
-        "prosemirror-state": "^1.0.0"
-      }
-    },
     "node_modules/prosemirror-model": {
-      "version": "1.25.4",
-      "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz",
-      "integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==",
+      "version": "1.25.12",
+      "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.12.tgz",
+      "integrity": "sha512-Ue2gTmXMa7EhpLNhC7J+h4+ykD8ha12K6rrZFFKKJHBForfIStw5gJ6Zrf1mqqAa9NmpmEB4wp9bKmA4eUVjWg==",
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "orderedmap": "^2.0.0"
       }
     },
-    "node_modules/prosemirror-schema-basic": {
-      "version": "1.2.4",
-      "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz",
-      "integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==",
-      "license": "MIT",
-      "dependencies": {
-        "prosemirror-model": "^1.25.0"
-      }
-    },
     "node_modules/prosemirror-schema-list": {
       "version": "1.5.1",
       "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz",
@@ -8392,7 +8265,6 @@
       "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz",
       "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==",
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "prosemirror-model": "^1.0.0",
         "prosemirror-transform": "^1.0.0",
@@ -8412,38 +8284,22 @@
         "prosemirror-view": "^1.41.4"
       }
     },
-    "node_modules/prosemirror-trailing-node": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz",
-      "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==",
-      "license": "MIT",
-      "dependencies": {
-        "@remirror/core-constants": "3.0.0",
-        "escape-string-regexp": "^4.0.0"
-      },
-      "peerDependencies": {
-        "prosemirror-model": "^1.22.1",
-        "prosemirror-state": "^1.4.2",
-        "prosemirror-view": "^1.33.8"
-      }
-    },
     "node_modules/prosemirror-transform": {
-      "version": "1.10.5",
-      "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.5.tgz",
-      "integrity": "sha512-RPDQCxIDhIBb1o36xxwsaeAvivO8VLJcgBtzmOwQ64bMtsVFh5SSuJ6dWSxO1UsHTiTXPCgQm3PDJt7p6IOLbw==",
+      "version": "1.12.2",
+      "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.12.2.tgz",
+      "integrity": "sha512-PE/aY0HEY4zczvmqrilgkUK/WautF0chvMqkmM/iN5/aPHvwrCWaQZjS6CIZp+K84YrvPFqrL+YArrfJX8xX7g==",
       "license": "MIT",
       "dependencies": {
         "prosemirror-model": "^1.21.0"
       }
     },
     "node_modules/prosemirror-view": {
-      "version": "1.41.5",
-      "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.5.tgz",
-      "integrity": "sha512-UDQbIPnDrjE8tqUBbPmCOZgtd75htE6W3r0JCmY9bL6W1iemDM37MZEKC49d+tdQ0v/CKx4gjxLoLsfkD2NiZA==",
+      "version": "1.42.6",
+      "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.42.6.tgz",
+      "integrity": "sha512-3g25f6hKV2bBxo3puH3EcYWmmVNSH0iWmNsMVT7sugDSpA025jgpOLarcDWr39SdzPjYMaiYtRdNtXialPSjEQ==",
       "license": "MIT",
-      "peer": true,
       "dependencies": {
-        "prosemirror-model": "^1.20.0",
+        "prosemirror-model": "^1.25.12",
         "prosemirror-state": "^1.0.0",
         "prosemirror-transform": "^1.1.0"
       }
@@ -8506,15 +8362,6 @@
         "node": ">=6"
       }
     },
-    "node_modules/punycode.js": {
-      "version": "2.3.1",
-      "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz",
-      "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
-      "license": "MIT",
-      "engines": {
-        "node": ">=6"
-      }
-    },
     "node_modules/query-selector-shadow-dom": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/query-selector-shadow-dom/-/query-selector-shadow-dom-1.0.1.tgz",
@@ -9511,11 +9358,10 @@
       }
     },
     "node_modules/svelte": {
-      "version": "5.55.4",
-      "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.55.4.tgz",
-      "integrity": "sha512-q8DFohk6vUswSng95IZb9nzWJnbINZsK7OiM1snAa3qCjJBL0ZQpvMyAaVXjUukdM75J/m8UE8xwqat8Ors/zQ==",
+      "version": "5.55.7",
+      "resolved": "https://registry.npmjs.org/svelte/-/svelte-5.55.7.tgz",
+      "integrity": "sha512-ymI5ykLPwIHW839E053FQbI1G+jnRFJEw3Kv5Y4njixVWywQBx+NUFpkkKyk5LIb36Fg9DVXSYpqiGekLD0hyw==",
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "@jridgewell/remapping": "^2.3.4",
         "@jridgewell/sourcemap-codec": "^1.5.0",
@@ -9526,7 +9372,7 @@
         "aria-query": "5.3.1",
         "axobject-query": "^4.1.0",
         "clsx": "^2.1.1",
-        "devalue": "^5.6.4",
+        "devalue": "^5.8.1",
         "esm-env": "^1.2.1",
         "esrap": "^2.2.4",
         "is-reference": "^3.0.3",
@@ -9794,7 +9640,6 @@
       "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
       "dev": true,
       "license": "MIT",
-      "peer": true,
       "engines": {
         "node": ">=12"
       },
@@ -9858,7 +9703,6 @@
       "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==",
       "dev": true,
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "esbuild": "~0.28.0"
       },
@@ -10388,7 +10232,6 @@
       "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
       "dev": true,
       "license": "Apache-2.0",
-      "peer": true,
       "bin": {
         "tsc": "bin/tsc",
         "tsserver": "bin/tsserver"
@@ -10421,12 +10264,6 @@
         "typescript": ">=4.8.4 <6.0.0"
       }
     },
-    "node_modules/uc.micro": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",
-      "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
-      "license": "MIT"
-    },
     "node_modules/undici": {
       "version": "6.27.0",
       "resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz",
@@ -10539,7 +10376,6 @@
       "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
       "dev": true,
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "esbuild": "^0.25.0",
         "fdir": "^6.4.4",
@@ -10633,7 +10469,6 @@
       "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
       "dev": true,
       "license": "MIT",
-      "peer": true,
       "engines": {
         "node": ">=12"
       },
@@ -10745,7 +10580,6 @@
       "integrity": "sha512-OoKlyOHhqUGIT1kL5mYbkjDI3bMf5xkBl4FsBwElOkU3aeUAKRe4aW8LzoQ1aNfut8POpjqcadQ4L5Lc4tnUwg==",
       "dev": true,
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "@types/node": "^20.11.30",
         "@types/sinonjs__fake-timers": "^8.1.5",
diff --git a/package.json b/package.json
index 6f3b5f18e..7667024ce 100644
--- a/package.json
+++ b/package.json
@@ -38,18 +38,15 @@
   "dependencies": {
     "@tauri-apps/api": "^2.9.0",
     "@tauri-apps/plugin-dialog": "^2.6.0",
-    "@tiptap/core": "^3.15.3",
-    "@tiptap/extension-placeholder": "^3.15.3",
-    "@tiptap/pm": "^3.15.3",
-    "@tiptap/starter-kit": "^3.15.3",
+    "@tiptap/core": "^3.30.5",
+    "@tiptap/extension-placeholder": "^3.30.5",
+    "@tiptap/pm": "^3.30.5",
+    "@tiptap/starter-kit": "^3.30.5",
     "@xyflow/svelte": "^1.5.0",
     "lucide-svelte": "^0.562.0",
-    "svelte": "^5.55.4",
+    "svelte": "^5.55.7",
     "three": "^0.183.1"
   },
-  "overrides": {
-    "markdown-it": "14.1.1"
-  },
   "devDependencies": {
     "@eslint/js": "^9.0.0",
     "@sveltejs/vite-plugin-svelte": "^5.0.0",

From f6d03a0ea4588f23aa9533c95835c92e42e64810 Mon Sep 17 00:00:00 2001
From: Puma 
Date: Sat, 3 Oct 2026 03:19:50 -0700
Subject: [PATCH 04/21] ci: qualify frontend dependency tree and build

---
 .github/workflows/quality-gates.yml                       | 6 ++++++
 .../reports/2026-10-03-frontend-dependency-security.md    | 8 ++++++++
 2 files changed, 14 insertions(+)

diff --git a/.github/workflows/quality-gates.yml b/.github/workflows/quality-gates.yml
index 6481af342..a06d4a195 100644
--- a/.github/workflows/quality-gates.yml
+++ b/.github/workflows/quality-gates.yml
@@ -121,6 +121,12 @@ jobs:
       - name: Block high severity production vulnerabilities
         run: npm audit --omit=dev --audit-level=high
 
+      - name: Verify installed dependency tree
+        run: npm ls --all
+
+      - name: Build frontend assets
+        run: npm run build
+
   rust-check:
     name: Rust workspace check
     runs-on: ubuntu-latest
diff --git a/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-frontend-dependency-security.md b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-frontend-dependency-security.md
index 4bb99fe80..81477aa9e 100644
--- a/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-frontend-dependency-security.md
+++ b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-frontend-dependency-security.md
@@ -33,3 +33,11 @@ Passed against the reviewed manifest/lock: `npm ci --include=optional --no-fund
 `npm run test:workflow-editor-image-gui` exits 2 at preflight because PANTOGRAPH_DIFFUSION_SMOKE_PUMAS_MODEL_ID is unavailable. Real Pumas model/artifact IDs, saved workflow, Python runtime, desktop drivers/display and built host remain prerequisites; no GUI success is claimed. The integration owner must obtain that qualified evidence. Rust/desktop compilation and release publication are outside this dependency slice.
 
 The source manifest/lock pair was independently reviewed before publication at staged tree `b75bac90dbbda40c3006e3228f0ce5eab8ed8f56`. Subsequent documentation adds this report without changing those files. Keep the task worktree protected while hosted qualification or integration is pending; the integration owner controls final merge and terminal disposition.
+
+## Hosted qualification follow-up
+
+[Quality Gates run 37115665903](https://github.com/MrScripty/Pantograph/actions/runs/37115665903) confirms Node 24.12.0/npm 11.6.2, clean npm ci, zero production-audit findings, 540/540 frontend tests, typecheck and full lint. Its synthetic merge `e66f54bc7b3f2963956918a6a781fc3a2ecf47ca` has the same tree `1c51bc4dcce332846e550ecf6374864521a11099` as published source commit `0b5c0df663d76dc97f2ba3836d33ea2404ca79df`. The unchanged critical-lint failure remains visible.
+
+That workflow lacked installed-tree and frontend-build checks. Add `npm ls --all` and `npm run build` to the existing dependency-audit job after clean installation and the production audit, preserving every existing command and aggregate gate. These checks prove resolver consistency and frontend asset compilation on the declared toolchain; they do not establish GUI or packaged-release acceptance. Exact-head hosted results for this workflow change remain pending.
+
+The hosted development-inclusive install audit reports 41 affected package nodes (1 low, 5 moderate, 35 high). Dependency maintainers must triage that separate follow-up before any broader security claim; this slice does not upgrade unrelated development tools or claim zero vulnerabilities across the complete graph. Headless/Runtime Separation results remain under observation, and the real-model GUI prerequisites above remain unavailable.

From c023cec148b1c4a340bcedd4d7f01f327a2eb2f5 Mon Sep 17 00:00:00 2001
From: Puma 
Date: Sat, 3 Oct 2026 08:41:53 -0700
Subject: [PATCH 05/21] refactor(embedded): preserve defaults and routing while
 clearing style lints

---
 .github/workflows/quality-gates.yml           |  2 ++
 .../src/dependency_inventory_dispatch.rs      |  2 +-
 .../runtime_dispatch_candidate_provider.rs    | 30 ++++++++++++-------
 .../dependency_environment/helpers.rs         | 30 ++++++++++++++++---
 .../src/task_executor/puma_lib.rs             |  2 +-
 ...2026-10-03-embedded-runtime-lint-basics.md |  7 +++++
 6 files changed, 57 insertions(+), 16 deletions(-)
 create mode 100644 docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-embedded-runtime-lint-basics.md

diff --git a/.github/workflows/quality-gates.yml b/.github/workflows/quality-gates.yml
index 2edc2cb9d..8a0d02108 100644
--- a/.github/workflows/quality-gates.yml
+++ b/.github/workflows/quality-gates.yml
@@ -251,6 +251,8 @@ jobs:
       - name: Run workflow-nodes tests
         run: cargo test -p workflow-nodes --lib
 
+      - name: Run embedded runtime unit tests
+        run: cargo test -p pantograph-embedded-runtime --lib
       - name: Run runtime registry contract tests
         run: cargo test -p pantograph-runtime-registry
       - name: Run task summary contract tests
diff --git a/crates/pantograph-embedded-runtime/src/dependency_inventory_dispatch.rs b/crates/pantograph-embedded-runtime/src/dependency_inventory_dispatch.rs
index 5e6894a0d..64908d305 100644
--- a/crates/pantograph-embedded-runtime/src/dependency_inventory_dispatch.rs
+++ b/crates/pantograph-embedded-runtime/src/dependency_inventory_dispatch.rs
@@ -360,7 +360,7 @@ impl DependencyInventoryProvider for NotImplementedDependencyInventoryProvider {
         let failure = PackageReadinessProbeFailure::new(
             PackageReadinessProviderDiagnosticCode::ProbeNotImplemented,
             None,
-            CapabilityAvailabilityReason::parse(¬_implemented_reason(&request.payload))
+            CapabilityAvailabilityReason::parse(not_implemented_reason(&request.payload))
                 .expect("inventory provider not implemented reason is valid"),
         );
         let (rows, diagnostics) =
diff --git a/crates/pantograph-embedded-runtime/src/runtime_dispatch_candidate_provider.rs b/crates/pantograph-embedded-runtime/src/runtime_dispatch_candidate_provider.rs
index a00456cce..ade14c215 100644
--- a/crates/pantograph-embedded-runtime/src/runtime_dispatch_candidate_provider.rs
+++ b/crates/pantograph-embedded-runtime/src/runtime_dispatch_candidate_provider.rs
@@ -70,7 +70,7 @@ const INCOMPATIBLE_RUNTIME_BACKEND_HINT: &str =
 const MISSING_RUNTIME_DISPATCH_EVIDENCE_HINT: &str =
     "embedded_runtime_dispatch_candidate_provider.missing_runtime_dispatch_evidence";
 
-#[derive(Clone)]
+#[derive(Clone, Default)]
 pub(crate) struct EmbeddedRuntimeDispatchCandidateProvider {
     source_snapshot: EmbeddedRuntimeDispatchCandidateSource,
     resource_facts_source: Option,
@@ -88,15 +88,6 @@ impl Default for EmbeddedRuntimeDispatchCandidateSource {
     }
 }
 
-impl Default for EmbeddedRuntimeDispatchCandidateProvider {
-    fn default() -> Self {
-        Self {
-            source_snapshot: EmbeddedRuntimeDispatchCandidateSource::default(),
-            resource_facts_source: None,
-        }
-    }
-}
-
 #[derive(Debug, Clone, PartialEq, Eq)]
 struct EmbeddedRuntimeDispatchCandidateDraft {
     candidate_id: SchedulerDispatchCandidateId,
@@ -1011,6 +1002,25 @@ mod tests {
         RuntimeDispatchCapabilityFactsProjection, RuntimeDispatchRuntimeCapabilityFacts,
     };
 
+    #[test]
+    fn default_provider_retains_empty_snapshot_and_fail_closed_diagnostics() {
+        let provider = EmbeddedRuntimeDispatchCandidateProvider::default();
+        assert!(provider.resource_facts_source.is_none());
+        let EmbeddedRuntimeDispatchCandidateSource::Snapshot(snapshot) = provider.source_snapshot
+        else {
+            panic!("default provider must retain snapshot mode");
+        };
+        assert_eq!(
+            snapshot,
+            EmbeddedRuntimeDispatchCandidateSourceSnapshot::default()
+        );
+        let diagnostics = fail_closed_diagnostics(&snapshot, &path_free_model_ref());
+        assert_eq!(diagnostics.len(), 4);
+        assert!(diagnostics.iter().all(|diagnostic| diagnostic.code
+            == SchedulerDispatchSelectionDiagnosticCode::NoCandidates
+            && diagnostic.severity == SchedulerDispatchSelectionDiagnosticSeverity::Error));
+    }
+
     #[test]
     fn fail_closed_provider_reports_missing_source_facts() {
         let diagnostics = fail_closed_diagnostics(
diff --git a/crates/pantograph-embedded-runtime/src/task_executor/dependency_environment/helpers.rs b/crates/pantograph-embedded-runtime/src/task_executor/dependency_environment/helpers.rs
index 52a1db68e..72b252f4c 100644
--- a/crates/pantograph-embedded-runtime/src/task_executor/dependency_environment/helpers.rs
+++ b/crates/pantograph-embedded-runtime/src/task_executor/dependency_environment/helpers.rs
@@ -103,10 +103,7 @@ impl TauriTaskExecutor {
     }
 
     pub(in crate::task_executor) fn python_runtime_handles_node(node_type: &str) -> bool {
-        match node_type {
-            "audio-generation" | "onnx-inference" => true,
-            _ => false,
-        }
+        matches!(node_type, "audio-generation" | "onnx-inference")
     }
 
     pub(in crate::task_executor) fn sanitize_key_component(raw: &str) -> String {
@@ -130,3 +127,28 @@ impl TauriTaskExecutor {
         format!("{:016x}", digest)
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use super::TauriTaskExecutor;
+
+    #[test]
+    fn python_runtime_node_classification_keeps_exact_accepted_set() {
+        for node_type in ["audio-generation", "onnx-inference"] {
+            assert!(TauriTaskExecutor::python_runtime_handles_node(node_type));
+        }
+        for node_type in [
+            "llm-inference",
+            "embedding",
+            "vision",
+            "",
+            "Audio-generation",
+            "onnx-inference ",
+        ] {
+            assert!(
+                !TauriTaskExecutor::python_runtime_handles_node(node_type),
+                "{node_type}"
+            );
+        }
+    }
+}
diff --git a/crates/pantograph-embedded-runtime/src/task_executor/puma_lib.rs b/crates/pantograph-embedded-runtime/src/task_executor/puma_lib.rs
index a6da852cd..02135e330 100644
--- a/crates/pantograph-embedded-runtime/src/task_executor/puma_lib.rs
+++ b/crates/pantograph-embedded-runtime/src/task_executor/puma_lib.rs
@@ -151,7 +151,7 @@ impl TauriTaskExecutor {
             if let Some(selector_access) =
                 extensions.get::>(PUMAS_SELECTOR_ACCESS)
             {
-                match Self::resolve_puma_lib_selected_detail(&selector_access, requested_model_id)
+                match Self::resolve_puma_lib_selected_detail(selector_access, requested_model_id)
                     .await
                 {
                     Ok(Some(detail)) => {
diff --git a/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-embedded-runtime-lint-basics.md b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-embedded-runtime-lint-basics.md
new file mode 100644
index 000000000..fd1b2ba93
--- /dev/null
+++ b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-embedded-runtime-lint-basics.md
@@ -0,0 +1,7 @@
+# Embedded runtime mechanical lint basics
+
+Fresh PR #40 Clippy clears runtime-registry and exposes thirteen embedded-runtime library findings. This first area milestone addresses only four mechanical findings: derive the candidate provider's identical default, remove a redundant inventory-reason borrow and selector-access borrow, and express the Python runtime node predicate with matches!. Payload/error layout and arity findings remain separate.
+
+Regressions prove the default still uses an empty snapshot with no resource facts and four fail-closed diagnostics, and the helper accepts exactly audio-generation/onnx-inference while rejecting other, differently cased and padded node identifiers. CI runs the complete embedded-runtime unit suite with its default backend features. Public APIs, runtime policy and errors are unchanged.
+
+The same checkout is reused on an embedded-runtime branch, preserving prior refs. The actual full pinned cargo fmt --all -- --check passes locally; root source review accepted frozen tree 405aea674557289e585587c1b23b48e381acbb0d; hosted tests remain required. No local Rust execution or heavy build is claimed.

From b73c5d1f1912ec1726b7f9c7b68e98a3cfc6a51a Mon Sep 17 00:00:00 2001
From: Puma 
Date: Sat, 3 Oct 2026 08:48:13 -0700
Subject: [PATCH 06/21] refactor(embedded): preserve artifact error source
 through boxed payload

---
 .../src/runtime_host_media_artifact_sink.rs   | 87 +++++++++++++++----
 ...2026-10-03-artifact-write-error-payload.md |  7 ++
 2 files changed, 76 insertions(+), 18 deletions(-)
 create mode 100644 docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-artifact-write-error-payload.md

diff --git a/crates/pantograph-embedded-runtime/src/runtime_host_media_artifact_sink.rs b/crates/pantograph-embedded-runtime/src/runtime_host_media_artifact_sink.rs
index c80763974..bc4bb4f13 100644
--- a/crates/pantograph-embedded-runtime/src/runtime_host_media_artifact_sink.rs
+++ b/crates/pantograph-embedded-runtime/src/runtime_host_media_artifact_sink.rs
@@ -37,14 +37,18 @@ pub(crate) enum RuntimeHostMediaArtifactSinkError {
         image_index: usize,
         message: String,
     },
-    #[error("runtime-host image output artifact write failed for {task_id}.{port_id}[{image_index}]: {source}")]
-    ArtifactWriteFailed {
-        task_id: String,
-        port_id: String,
-        image_index: usize,
-        #[source]
-        source: WorkflowServiceError,
-    },
+    #[error(transparent)]
+    ArtifactWriteFailed(Box),
+}
+
+#[derive(Debug, Error)]
+#[error("runtime-host image output artifact write failed for {task_id}.{port_id}[{image_index}]: {source}")]
+pub(crate) struct RuntimeHostArtifactWriteFailure {
+    task_id: String,
+    port_id: String,
+    image_index: usize,
+    #[source]
+    source: WorkflowServiceError,
 }
 
 #[derive(Clone)]
@@ -101,14 +105,16 @@ impl RuntimeHostMediaArtifactSink for WorkflowServiceRuntimeHostMediaArtifactSin
                 revision_index: Some(request.image_index as u64),
                 body,
             })
-            .map_err(
-                |source| RuntimeHostMediaArtifactSinkError::ArtifactWriteFailed {
-                    task_id: request.task_id.to_string(),
-                    port_id: request.port_id.to_string(),
-                    image_index: request.image_index,
-                    source,
-                },
-            )?;
+            .map_err(|source| {
+                RuntimeHostMediaArtifactSinkError::ArtifactWriteFailed(Box::new(
+                    RuntimeHostArtifactWriteFailure {
+                        task_id: request.task_id.to_string(),
+                        port_id: request.port_id.to_string(),
+                        image_index: request.image_index,
+                        source,
+                    },
+                ))
+            })?;
 
         Ok(RuntimeHostExecutionMediaArtifactRef {
             artifact_id: descriptor.artifact_id,
@@ -316,11 +322,56 @@ mod tests {
             })
             .expect_err("missing artifact store must fail closed");
 
-        let RuntimeHostMediaArtifactSinkError::ArtifactWriteFailed { source, .. } = error else {
+        let concrete_source = std::error::Error::source(&error)
+            .expect("artifact write failure retains its source")
+            .downcast_ref::()
+            .expect("source remains the concrete workflow error");
+        assert_eq!(concrete_source.code(), WorkflowErrorCode::InternalError);
+        let RuntimeHostMediaArtifactSinkError::ArtifactWriteFailed(failure) = error else {
             panic!("expected artifact write failure");
         };
+        assert_eq!(failure.source.code(), WorkflowErrorCode::InternalError);
+        assert!(failure
+            .source
+            .to_string()
+            .contains("artifact store io error"));
+    }
+
+    #[test]
+    fn boxed_write_failure_preserves_exact_display_and_concrete_source_chain() {
+        let error = RuntimeHostMediaArtifactSinkError::ArtifactWriteFailed(Box::new(
+            RuntimeHostArtifactWriteFailure {
+                task_id: "task.image".to_string(),
+                port_id: "image".to_string(),
+                image_index: 2,
+                source: WorkflowServiceError::Internal("fixture failure".to_string()),
+            },
+        ));
+        assert_eq!(error.to_string(), "runtime-host image output artifact write failed for task.image.image[2]: internal_error: fixture failure");
+        let source = std::error::Error::source(&error).unwrap();
+        assert!(source
+            .downcast_ref::()
+            .is_none());
+        assert!(source.downcast_ref::>().is_none());
+        let source = source
+            .downcast_ref::()
+            .expect("unboxed workflow source identity");
         assert_eq!(source.code(), WorkflowErrorCode::InternalError);
-        assert!(source.to_string().contains("artifact store io error"));
+        assert_eq!(source.to_string(), "internal_error: fixture failure");
+        assert!(std::error::Error::source(source).is_none());
+        assert!(std::mem::size_of::() <= 128);
+    }
+
+    #[test]
+    fn invalid_image_failure_retains_message_and_absent_source() {
+        let error = RuntimeHostMediaArtifactSinkError::InvalidImagePayload {
+            task_id: "task.image".to_string(),
+            port_id: "image".to_string(),
+            image_index: 2,
+            message: "invalid fixture bytes".to_string(),
+        };
+        assert_eq!(error.to_string(), "runtime-host image output base64 decode failed for task.image.image[2]: invalid fixture bytes");
+        assert!(std::error::Error::source(&error).is_none());
     }
 
     fn artifact_writer(temp: &TempDir) -> WorkflowArtifactWriter {
diff --git a/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-artifact-write-error-payload.md b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-artifact-write-error-payload.md
new file mode 100644
index 000000000..1bc1eb74c
--- /dev/null
+++ b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-artifact-write-error-payload.md
@@ -0,0 +1,7 @@
+# Private artifact-write failure payload and source identity
+
+Fresh embedded-runtime Clippy identifies three large-error returns caused by ArtifactWriteFailed carrying a raw WorkflowServiceError plus attribution. Keep all media sink trait and Result signatures unchanged, but box a private RuntimeHostArtifactWriteFailure payload containing the unchanged raw source. The enum variant is error-transparent. One production constructor and one existing test pattern are migrated.
+
+Directly boxing the #[source] field was rejected during design: pinned thiserror 1.0.69 calls source.as_dyn_error(), which can expose Box to downcast consumers. The transparent payload delegates Error::source through its own raw #[source] field instead. Tests explicitly require the concrete WorkflowServiceError downcast and classification, reject wrapper/boxed-source downcasts, preserve exact outer/source messages, and retain the invalid-image variant's message and absent source. The real missing-artifact-store failure now also checks concrete source identity. A private enum-size bound targets the reported large errors.
+
+Root approved this source-chain-preserving design. Full pinned cargo fmt and staged checks passed. Root source review accepted both files at frozen tree 002ee22ba66b1a957906fc530d1ac080f54c8dbe; actual hosted execution remains required. No public DTO/API or failure classification change is intended.

From 788ce31ec3f522cc2a243455f298fbe31c566ff8 Mon Sep 17 00:00:00 2001
From: Puma 
Date: Sat, 3 Oct 2026 09:05:54 -0700
Subject: [PATCH 07/21] Reduce private embedded source payload sizes

---
 .../src/inference_interface_facts_provider.rs |  4 +-
 .../src/pumas_dispatch_package_facts.rs       |  4 +-
 .../runtime_dispatch_candidate_provider.rs    | 53 ++++++++++++++++---
 ...26-10-03-embedded-private-source-layout.md |  9 ++++
 4 files changed, 58 insertions(+), 12 deletions(-)
 create mode 100644 docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-embedded-private-source-layout.md

diff --git a/crates/pantograph-embedded-runtime/src/inference_interface_facts_provider.rs b/crates/pantograph-embedded-runtime/src/inference_interface_facts_provider.rs
index d6854450e..69efd748c 100644
--- a/crates/pantograph-embedded-runtime/src/inference_interface_facts_provider.rs
+++ b/crates/pantograph-embedded-runtime/src/inference_interface_facts_provider.rs
@@ -357,7 +357,7 @@ mod tests {
 
         let facts = resolver_facts_from_sources(
             PumasDispatchPackageFactsBridgeOutcome::Projected {
-                facts: package,
+                facts: Box::new(package),
                 diagnostics: Vec::new(),
             },
             &runtime,
@@ -388,7 +388,7 @@ mod tests {
     fn missing_runtime_facts_keep_capability_but_publish_no_runtime_availability() {
         let facts = resolver_facts_from_sources(
             PumasDispatchPackageFactsBridgeOutcome::Projected {
-                facts: projected_package_facts(),
+                facts: Box::new(projected_package_facts()),
                 diagnostics: Vec::new(),
             },
             &RuntimeDispatchCapabilityFactsOutcome::Unavailable {
diff --git a/crates/pantograph-embedded-runtime/src/pumas_dispatch_package_facts.rs b/crates/pantograph-embedded-runtime/src/pumas_dispatch_package_facts.rs
index 7e561d403..16366135d 100644
--- a/crates/pantograph-embedded-runtime/src/pumas_dispatch_package_facts.rs
+++ b/crates/pantograph-embedded-runtime/src/pumas_dispatch_package_facts.rs
@@ -84,7 +84,7 @@ pub(crate) enum PumasDispatchPackageFactsDiagnosticCode {
 #[derive(Debug, Clone, PartialEq)]
 pub(crate) enum PumasDispatchPackageFactsBridgeOutcome {
     Projected {
-        facts: PumasDispatchPackageFactsProjection,
+        facts: Box,
         diagnostics: Vec,
     },
     Unavailable {
@@ -210,7 +210,7 @@ fn validate_and_project_dispatch_package_facts(
     }
 
     PumasDispatchPackageFactsBridgeOutcome::Projected {
-        facts: project_dispatch_package_facts(facts),
+        facts: Box::new(project_dispatch_package_facts(facts)),
         diagnostics,
     }
 }
diff --git a/crates/pantograph-embedded-runtime/src/runtime_dispatch_candidate_provider.rs b/crates/pantograph-embedded-runtime/src/runtime_dispatch_candidate_provider.rs
index ade14c215..f167e753c 100644
--- a/crates/pantograph-embedded-runtime/src/runtime_dispatch_candidate_provider.rs
+++ b/crates/pantograph-embedded-runtime/src/runtime_dispatch_candidate_provider.rs
@@ -78,13 +78,13 @@ pub(crate) struct EmbeddedRuntimeDispatchCandidateProvider {
 
 #[derive(Clone)]
 enum EmbeddedRuntimeDispatchCandidateSource {
-    Snapshot(EmbeddedRuntimeDispatchCandidateSourceSnapshot),
+    Snapshot(Box),
     Store(EmbeddedRuntimeDispatchSourceFactSnapshotStore),
 }
 
 impl Default for EmbeddedRuntimeDispatchCandidateSource {
     fn default() -> Self {
-        Self::Snapshot(EmbeddedRuntimeDispatchCandidateSourceSnapshot::default())
+        Self::Snapshot(Box::default())
     }
 }
 
@@ -113,7 +113,9 @@ impl EmbeddedRuntimeDispatchCandidateProvider {
         source_snapshot: EmbeddedRuntimeDispatchCandidateSourceSnapshot,
     ) -> Self {
         Self {
-            source_snapshot: EmbeddedRuntimeDispatchCandidateSource::Snapshot(source_snapshot),
+            source_snapshot: EmbeddedRuntimeDispatchCandidateSource::Snapshot(Box::new(
+                source_snapshot,
+            )),
             resource_facts_source: None,
         }
     }
@@ -172,7 +174,7 @@ impl EmbeddedRuntimeDispatchCandidateSource {
         model_ref: &PumasModelRef,
     ) -> EmbeddedRuntimeDispatchCandidateSourceSnapshot {
         match self {
-            Self::Snapshot(snapshot) => snapshot.clone(),
+            Self::Snapshot(snapshot) => snapshot.as_ref().clone(),
             Self::Store(store) => store.snapshot_for_dispatch(model_ref, current_time_ms()),
         }
     }
@@ -1002,6 +1004,35 @@ mod tests {
         RuntimeDispatchCapabilityFactsProjection, RuntimeDispatchRuntimeCapabilityFacts,
     };
 
+    #[test]
+    fn private_source_indirections_preserve_raw_facts_and_snapshot_clone_isolation() {
+        let expected = source_snapshot(Vec::new(), Vec::new());
+        let expected_facts = pumas_package_facts(vec![inference::BackendHintLabel::Diffusers]);
+        let provider =
+            EmbeddedRuntimeDispatchCandidateProvider::with_source_snapshot(expected.clone());
+        let mut returned = provider
+            .source_snapshot
+            .snapshot_for_dispatch(&path_free_model_ref());
+        assert_eq!(returned, expected);
+        let Some(PumasDispatchPackageFactsBridgeOutcome::Projected { facts, diagnostics }) =
+            returned.pumas_package_facts.as_mut()
+        else {
+            panic!("snapshot must preserve projected package facts");
+        };
+        assert_eq!(facts.as_ref(), &expected_facts);
+        assert!(diagnostics.is_empty());
+        facts.model_ref.model_id = "changed-returned-copy".to_string();
+        returned.snapshot_version = 99;
+        assert_eq!(
+            provider
+                .source_snapshot
+                .snapshot_for_dispatch(&path_free_model_ref()),
+            expected
+        );
+        assert!(std::mem::size_of::() <= 64);
+        assert!(std::mem::size_of::() <= 64);
+    }
+
     #[test]
     fn default_provider_retains_empty_snapshot_and_fail_closed_diagnostics() {
         let provider = EmbeddedRuntimeDispatchCandidateProvider::default();
@@ -1011,7 +1042,7 @@ mod tests {
             panic!("default provider must retain snapshot mode");
         };
         assert_eq!(
-            snapshot,
+            *snapshot,
             EmbeddedRuntimeDispatchCandidateSourceSnapshot::default()
         );
         let diagnostics = fail_closed_diagnostics(&snapshot, &path_free_model_ref());
@@ -1245,7 +1276,9 @@ mod tests {
         let provider = EmbeddedRuntimeDispatchCandidateProvider::with_source_snapshot(
             EmbeddedRuntimeDispatchCandidateSourceSnapshot {
                 pumas_package_facts: Some(PumasDispatchPackageFactsBridgeOutcome::Projected {
-                    facts: pumas_package_facts(vec![inference::BackendHintLabel::Diffusers]),
+                    facts: Box::new(pumas_package_facts(vec![
+                        inference::BackendHintLabel::Diffusers,
+                    ])),
                     diagnostics: Vec::new(),
                 }),
                 runtime_capability_facts: Some(RuntimeDispatchCapabilityFactsOutcome::Projected {
@@ -1405,7 +1438,9 @@ mod tests {
         let provider = EmbeddedRuntimeDispatchCandidateProvider::with_source_snapshot(
             EmbeddedRuntimeDispatchCandidateSourceSnapshot {
                 pumas_package_facts: Some(PumasDispatchPackageFactsBridgeOutcome::Projected {
-                    facts: pumas_package_facts(vec![inference::BackendHintLabel::Diffusers]),
+                    facts: Box::new(pumas_package_facts(vec![
+                        inference::BackendHintLabel::Diffusers,
+                    ])),
                     diagnostics: Vec::new(),
                 }),
                 runtime_capability_facts: Some(RuntimeDispatchCapabilityFactsOutcome::Projected {
@@ -1546,7 +1581,9 @@ mod tests {
     ) -> EmbeddedRuntimeDispatchCandidateSourceSnapshot {
         EmbeddedRuntimeDispatchCandidateSourceSnapshot {
             pumas_package_facts: Some(PumasDispatchPackageFactsBridgeOutcome::Projected {
-                facts: pumas_package_facts(vec![inference::BackendHintLabel::Diffusers]),
+                facts: Box::new(pumas_package_facts(vec![
+                    inference::BackendHintLabel::Diffusers,
+                ])),
                 diagnostics: Vec::new(),
             }),
             runtime_capability_facts: Some(RuntimeDispatchCapabilityFactsOutcome::Projected {
diff --git a/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-embedded-private-source-layout.md b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-embedded-private-source-layout.md
new file mode 100644
index 000000000..a9b50dbc4
--- /dev/null
+++ b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-embedded-private-source-layout.md
@@ -0,0 +1,9 @@
+# Private embedded source payload layout
+
+Fresh PR42 Clippy reports large variants in the package-facts bridge outcome and dispatch candidate source. Box only Projected.facts and Snapshot, keeping raw projection/snapshot DTOs and public APIs unchanged. The package bridge has one production constructor and five fixture constructors across three files. Candidate source has two constructors (default and explicit snapshot) and one raw-cloning reader. Borrowed readers retain deref coercion; the owned reader explicitly clones the raw snapshot rather than returning a box.
+
+The regression compares complete raw package facts and snapshots, mutates a returned nested fact and snapshot version, then proves provider state remains isolated. Existing source-owner, stale/missing/blocked, resolver, and candidate-selection tests remain in the full embedded unit gate. Size bounds apply only to these private enums; no measured throughput claim is made.
+
+Prior artifact-error head b73c5d1 has actual hosted concrete source/downcast and artifact-write tests passing. Its full embedded unit run has 449 passed and three failures: image dispatch gets RetryDeferred instead of RuntimeHostCompleted; resource-backed validation gets Blocked instead of Executable; publication dependency readiness gets Blocked instead of RequestReady with DriftDetected. Those failures remain separate and are not waived or classified as baseline without exact comparison. Format and workspace default/all-feature compilation pass at that head.
+
+Root source review accepted all four files at frozen tree 614eb5f7f61ee8ffa96d4d07cdaa238f7275e1a2. Fresh hosted execution remains required. Local Rust execution is not attempted under the shared disk constraint; full pinned formatting is checked separately.

From bbce93c4890fad38ec7bb0d901bf3463e4e0a763 Mon Sep 17 00:00:00 2001
From: Puma 
Date: Sat, 3 Oct 2026 09:12:10 -0700
Subject: [PATCH 08/21] Align embedded fixtures with validation and reservation
 contracts

---
 .../lib_tests/workflow_run_execution_tests.rs | 37 ++++++++++++++++---
 .../src/workflow_service_composition.rs       | 28 +++++++++++++-
 .../2026-10-03-embedded-contract-fixtures.md  |  9 +++++
 3 files changed, 67 insertions(+), 7 deletions(-)
 create mode 100644 docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-embedded-contract-fixtures.md

diff --git a/crates/pantograph-embedded-runtime/src/lib_tests/workflow_run_execution_tests.rs b/crates/pantograph-embedded-runtime/src/lib_tests/workflow_run_execution_tests.rs
index 31a01ef9c..40fe05b9c 100644
--- a/crates/pantograph-embedded-runtime/src/lib_tests/workflow_run_execution_tests.rs
+++ b/crates/pantograph-embedded-runtime/src/lib_tests/workflow_run_execution_tests.rs
@@ -34,9 +34,10 @@ use pantograph_inference_interface_contracts::{
 use pantograph_runtime_attribution::WorkflowVersionRecord;
 use pantograph_runtime_host_contracts::{
     ReservationLifecycleApplication, ReservationLifecycleApplicationState,
-    ReservationLifecycleEvent, ReservationLifecycleOutcome, ReservationLifecyclePort,
-    ReservationLifecyclePortError, RESERVATION_LIFECYCLE_CONTRACT_VERSION,
-    RUNTIME_SESSION_LOAD_PROOF_CONTRACT_VERSION,
+    ReservationLifecycleDiagnostic, ReservationLifecycleDiagnosticCode,
+    ReservationLifecycleDiagnosticSeverity, ReservationLifecycleEvent, ReservationLifecycleOutcome,
+    ReservationLifecyclePort, ReservationLifecyclePortError,
+    RESERVATION_LIFECYCLE_CONTRACT_VERSION, RUNTIME_SESSION_LOAD_PROOF_CONTRACT_VERSION,
 };
 use pantograph_scheduler::{
     SchedulerDispatchCandidateId, SchedulerEstimateHint, SchedulerEstimateHintKind,
@@ -239,17 +240,41 @@ async fn workflow_execution_session_dispatches_through_production_embedded_image
     assert_eq!(body.response.media_type, "image/png");
     assert_eq!(dependency_readiness_work_queue.len(), 1);
     assert_eq!(source_refresher.model_refs(), vec![MODEL_ID.to_string()]);
+    let lifecycle_events = reservation_lifecycle_port.events();
     assert_eq!(
-        reservation_lifecycle_port
-            .events()
+        lifecycle_events
             .iter()
             .map(|event| &event.outcome)
             .collect::>(),
         vec![
             &ReservationLifecycleOutcome::DispatchStarted,
-            &ReservationLifecycleOutcome::RuntimeHostCompleted,
+            &ReservationLifecycleOutcome::RetryDeferred,
         ]
     );
+    let deferred = &lifecycle_events[1];
+    assert_eq!(
+        deferred.reservation_lease_id,
+        lifecycle_events[0].reservation_lease_id
+    );
+    assert_eq!(
+        deferred.reservation_lease_id.as_str(),
+        "reservation.embedded_runtime_session_test.infer"
+    );
+    assert_eq!(
+        deferred.workflow_run_id,
+        lifecycle_events[0].workflow_run_id
+    );
+    assert_eq!(deferred.task_id, lifecycle_events[0].task_id);
+    assert_eq!(deferred.candidate_id, lifecycle_events[0].candidate_id);
+    assert_eq!(
+        deferred.diagnostics,
+        vec![ReservationLifecycleDiagnostic {
+            severity: ReservationLifecycleDiagnosticSeverity::Info,
+            code: ReservationLifecycleDiagnosticCode::RetryDeferred,
+            message: "embedded runtime-host image batch member completed".to_string(),
+            hint: None,
+        }]
+    );
     assert_eq!(host.runtime_load_attempts.load(Ordering::SeqCst), 0);
     assert_eq!(host.run_attempts.load(Ordering::SeqCst), 0);
 }
diff --git a/crates/pantograph-embedded-runtime/src/workflow_service_composition.rs b/crates/pantograph-embedded-runtime/src/workflow_service_composition.rs
index 87b076248..023092405 100644
--- a/crates/pantograph-embedded-runtime/src/workflow_service_composition.rs
+++ b/crates/pantograph-embedded-runtime/src/workflow_service_composition.rs
@@ -850,6 +850,12 @@ mod tests {
             .await
             .expect("refresh validation summary");
 
+        assert!(
+            validation.summary.diagnostics.is_empty(),
+            "validation diagnostics: {:?}",
+            validation.summary.diagnostics
+        );
+        assert_eq!(validation.node_projections.len(), 1);
         let summary = validation
             .summary
             .summary
@@ -946,6 +952,21 @@ mod tests {
             )
             .await
             .expect("refresh validation summary");
+        assert!(
+            validation.summary.diagnostics.is_empty(),
+            "validation diagnostics: {:?}",
+            validation.summary.diagnostics
+        );
+        assert_eq!(validation.node_projections.len(), 1);
+        assert_eq!(
+            validation
+                .summary
+                .summary
+                .as_ref()
+                .expect("current validation summary")
+                .status,
+            DraftGraphValidationStatus::Executable
+        );
         let validation_session_id = validation
             .summary
             .validation_session_id
@@ -1435,7 +1456,12 @@ mod tests {
                     position: Position { x: 200.0, y: 0.0 },
                     data: serde_json::json!({
                         "task_kind": "image_generation",
-                        "runtime": "pytorch"
+                        "runtime": "pytorch",
+                        "runtime_source_context": {
+                            "operation_type": "image-generation.txt2img",
+                            "context_shape_key": "txt2img.1024x1024.steps30",
+                            "cancellation_mode": "per-run-fanout"
+                        }
                     }),
                 },
             ],
diff --git a/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-embedded-contract-fixtures.md b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-embedded-contract-fixtures.md
new file mode 100644
index 000000000..b7e5ba4ae
--- /dev/null
+++ b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-embedded-contract-fixtures.md
@@ -0,0 +1,9 @@
+# Embedded runtime integration fixture contracts
+
+The full default-feature embedded suite was newly enabled on PR42 and exposed three failures. At b73c5d1 it executed 452 tests: 449 passed, three failed. Preserve the full gate and original execution/publication assertions; this milestone changes only fixtures and assertions.
+
+The two resource-backed composition fixtures omit mandatory runtime_source_context. inference_interface_request.rs rejects that absence before facts resolution; aggregate publication makes request diagnostics Blocked. The dependency-action owner maps that blocked summary to DriftDetected, so this downstream code alone does not establish descriptor fingerprint drift. Add the explicit image-generation operation/context/cancellation values already used by the image execution fixture. Assert no validation diagnostics and exactly one projection before the original Executable/RequestReady/publication assertions. No production admission bypass or inferred default is added.
+
+The image test already verifies real production runtime-host output, stored image bytes/media type and no legacy host load/run. Its reservation assertion was outdated: the scheduler explicitly requests DeferToScheduler; the embedded batch response reports DeferredToScheduler even when Completed; the scheduler maps that reservation disposition to RetryDeferred. Preserve every output assertion and require the exact deferred diagnostic (Info, RetryDeferred, successful image-member completion message, no hint) and correlated lease/run/task/candidate across start and terminal events. This asserts deferred reservation ownership without confusing it with failed image execution.
+
+Root source review accepted the complete three-file fixture-only diff at frozen tree d204dd518d905a4d6d1f1fe471678cb511822f5c. Fresh hosted full embedded execution remains pending. Local execution is not claimed; full pinned formatting is checked. The prior three failing assertions remain recorded in hosted logs rather than reclassified without baseline execution.

From badd8026924dab251e2122194ba2b6e9d09f9496 Mon Sep 17 00:00:00 2001
From: Puma 
Date: Sat, 3 Oct 2026 09:22:57 -0700
Subject: [PATCH 09/21] Group private inventory diagnostics and reuse stream
 attribution

---
 .../src/dependency_inventory.rs               |  8 ++
 .../dependency_inventory_device_toolchain.rs  | 63 +++++++++-----
 .../dependency_inventory_system_package.rs    | 63 +++++++++-----
 .../src/dependency_inventory_tests.rs         | 25 ++++++
 .../src/task_executor/stream_artifacts.rs     | 85 +++++++++++++++----
 .../2026-10-03-embedded-private-inputs.md     |  9 ++
 6 files changed, 192 insertions(+), 61 deletions(-)
 create mode 100644 docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-embedded-private-inputs.md

diff --git a/crates/pantograph-embedded-runtime/src/dependency_inventory.rs b/crates/pantograph-embedded-runtime/src/dependency_inventory.rs
index ebb0b0652..d4cfb8a31 100644
--- a/crates/pantograph-embedded-runtime/src/dependency_inventory.rs
+++ b/crates/pantograph-embedded-runtime/src/dependency_inventory.rs
@@ -48,6 +48,14 @@ use crate::dependency_inventory_system_package_source::SystemPackageProviderSour
 use crate::package_readiness_provider::PackageReadinessProbeRunner;
 use crate::python_package_readiness_probe::ProcessPythonPackageReadinessProbeRunner;
 
+/// Provider-owned diagnostic details; request attribution remains with the observer.
+#[cfg(any(test, feature = "standalone"))]
+pub(crate) struct DependencyInventoryDiagnosticInput {
+    pub code: pantograph_dependency_planning::DependencyPlanningDiagnosticCode,
+    pub message: String,
+    pub field_path: &'static str,
+}
+
 /// Request context passed from the snapshot producer to dependency inventory.
 #[derive(Debug, Clone)]
 pub(crate) struct DependencyInventoryRequest {
diff --git a/crates/pantograph-embedded-runtime/src/dependency_inventory_device_toolchain.rs b/crates/pantograph-embedded-runtime/src/dependency_inventory_device_toolchain.rs
index 6455dee2d..e228458eb 100644
--- a/crates/pantograph-embedded-runtime/src/dependency_inventory_device_toolchain.rs
+++ b/crates/pantograph-embedded-runtime/src/dependency_inventory_device_toolchain.rs
@@ -16,7 +16,8 @@ use pantograph_dependency_planning::{
 };
 
 use crate::dependency_inventory::{
-    DependencyInventoryObservation, DependencyInventoryProvider, DependencyInventoryRequest,
+    DependencyInventoryDiagnosticInput, DependencyInventoryObservation,
+    DependencyInventoryProvider, DependencyInventoryRequest,
 };
 use crate::dependency_inventory_device_toolchain_source::DeviceToolchainProviderSource;
 
@@ -128,9 +129,12 @@ fn observe_device_toolchain_binding(
             binding.binding_id.clone(),
             DependencyInventoryObservationState::Missing,
             DependencyEnvironmentValidationState::Valid,
-            DependencyPlanningDiagnosticCode::ArtifactMissing,
-            "Device-toolchain source facts are missing for the requested toolchain.",
-            "dependency_environment.device_toolchain.source",
+            DependencyInventoryDiagnosticInput {
+                code: DependencyPlanningDiagnosticCode::ArtifactMissing,
+                message: "Device-toolchain source facts are missing for the requested toolchain."
+                    .into(),
+                field_path: "dependency_environment.device_toolchain.source",
+            },
             ready_alternatives(&snapshot.rows),
         );
     }
@@ -225,9 +229,11 @@ fn observation_from_source_row(
             binding_id,
             DependencyInventoryObservationState::Unavailable,
             DependencyEnvironmentValidationState::Stale,
-            DependencyPlanningDiagnosticCode::ArtifactStale,
-            "Device-toolchain source facts are stale.",
-            "dependency_environment.device_toolchain.source",
+            DependencyInventoryDiagnosticInput {
+                code: DependencyPlanningDiagnosticCode::ArtifactStale,
+                message: "Device-toolchain source facts are stale.".into(),
+                field_path: "dependency_environment.device_toolchain.source",
+            },
             source_row.alternatives.clone(),
         ),
         (DependencyProviderSourceState::Ready, _) => row(
@@ -242,9 +248,11 @@ fn observation_from_source_row(
             binding_id,
             DependencyInventoryObservationState::Missing,
             DependencyEnvironmentValidationState::Valid,
-            DependencyPlanningDiagnosticCode::ArtifactMissing,
-            "Device-toolchain source facts are missing.",
-            "dependency_environment.device_toolchain.source",
+            DependencyInventoryDiagnosticInput {
+                code: DependencyPlanningDiagnosticCode::ArtifactMissing,
+                message: "Device-toolchain source facts are missing.".into(),
+                field_path: "dependency_environment.device_toolchain.source",
+            },
             source_row.alternatives.clone(),
         ),
         (DependencyProviderSourceState::Failed, _) => observation_with_diagnostic(
@@ -252,9 +260,11 @@ fn observation_from_source_row(
             binding_id,
             DependencyInventoryObservationState::Failed,
             DependencyEnvironmentValidationState::Valid,
-            DependencyPlanningDiagnosticCode::RuntimeUnavailable,
-            "Device-toolchain source reported a failure.",
-            "dependency_environment.device_toolchain.source",
+            DependencyInventoryDiagnosticInput {
+                code: DependencyPlanningDiagnosticCode::RuntimeUnavailable,
+                message: "Device-toolchain source reported a failure.".into(),
+                field_path: "dependency_environment.device_toolchain.source",
+            },
             source_row.alternatives.clone(),
         ),
         (DependencyProviderSourceState::Unsupported, _)
@@ -267,9 +277,11 @@ fn observation_from_source_row(
             binding_id,
             DependencyInventoryObservationState::Unavailable,
             DependencyEnvironmentValidationState::Valid,
-            DependencyPlanningDiagnosticCode::RuntimeUnavailable,
-            "Device-toolchain source is not ready for the requested toolchain.",
-            "dependency_environment.device_toolchain.source",
+            DependencyInventoryDiagnosticInput {
+                code: DependencyPlanningDiagnosticCode::RuntimeUnavailable,
+                message: "Device-toolchain source is not ready for the requested toolchain.".into(),
+                field_path: "dependency_environment.device_toolchain.source",
+            },
             source_row.alternatives.clone(),
         ),
     }
@@ -336,9 +348,11 @@ fn invalid_row(
         binding_id,
         DependencyInventoryObservationState::Invalid,
         DependencyEnvironmentValidationState::Invalid,
-        DependencyPlanningDiagnosticCode::InvalidRequest,
-        message,
-        field_path,
+        DependencyInventoryDiagnosticInput {
+            code: DependencyPlanningDiagnosticCode::InvalidRequest,
+            message: message.into(),
+            field_path,
+        },
         Vec::new(),
     )
 }
@@ -348,12 +362,15 @@ fn observation_with_diagnostic(
     binding_id: DependencyBindingId,
     state: DependencyInventoryObservationState,
     validation_state: DependencyEnvironmentValidationState,
-    code: DependencyPlanningDiagnosticCode,
-    message: impl Into,
-    field_path: &'static str,
+    diagnostic_input: DependencyInventoryDiagnosticInput,
     alternatives: Vec,
 ) -> DependencyInventoryObservationRow {
-    let diagnostic = diagnostic(item, code, message.into(), field_path);
+    let DependencyInventoryDiagnosticInput {
+        code,
+        message,
+        field_path,
+    } = diagnostic_input;
+    let diagnostic = diagnostic(item, code, message, field_path);
     row(
         binding_id,
         state,
diff --git a/crates/pantograph-embedded-runtime/src/dependency_inventory_system_package.rs b/crates/pantograph-embedded-runtime/src/dependency_inventory_system_package.rs
index 61686a8d6..526b698ea 100644
--- a/crates/pantograph-embedded-runtime/src/dependency_inventory_system_package.rs
+++ b/crates/pantograph-embedded-runtime/src/dependency_inventory_system_package.rs
@@ -16,7 +16,8 @@ use pantograph_dependency_planning::{
 };
 
 use crate::dependency_inventory::{
-    DependencyInventoryObservation, DependencyInventoryProvider, DependencyInventoryRequest,
+    DependencyInventoryDiagnosticInput, DependencyInventoryObservation,
+    DependencyInventoryProvider, DependencyInventoryRequest,
 };
 use crate::dependency_inventory_system_package_source::{
     SystemPackageProviderSource, SystemPackageProviderSourceError,
@@ -135,9 +136,12 @@ fn observe_system_package_binding(
             binding.binding_id.clone(),
             DependencyInventoryObservationState::Missing,
             DependencyEnvironmentValidationState::Valid,
-            DependencyPlanningDiagnosticCode::ArtifactMissing,
-            "System-package source facts are missing for the requested package.",
-            "dependency_environment.system_package.source",
+            DependencyInventoryDiagnosticInput {
+                code: DependencyPlanningDiagnosticCode::ArtifactMissing,
+                message: "System-package source facts are missing for the requested package."
+                    .into(),
+                field_path: "dependency_environment.system_package.source",
+            },
             ready_alternatives(&snapshot.rows),
         );
     }
@@ -240,9 +244,11 @@ fn observation_from_source_row(
             binding_id,
             DependencyInventoryObservationState::Unavailable,
             DependencyEnvironmentValidationState::Stale,
-            DependencyPlanningDiagnosticCode::ArtifactStale,
-            "System-package source facts are stale.",
-            "dependency_environment.system_package.source",
+            DependencyInventoryDiagnosticInput {
+                code: DependencyPlanningDiagnosticCode::ArtifactStale,
+                message: "System-package source facts are stale.".into(),
+                field_path: "dependency_environment.system_package.source",
+            },
             source_row.alternatives.clone(),
         ),
         (DependencyProviderSourceState::Ready, _) => row(
@@ -257,9 +263,11 @@ fn observation_from_source_row(
             binding_id,
             DependencyInventoryObservationState::Missing,
             DependencyEnvironmentValidationState::Valid,
-            DependencyPlanningDiagnosticCode::ArtifactMissing,
-            "System-package source facts are missing.",
-            "dependency_environment.system_package.source",
+            DependencyInventoryDiagnosticInput {
+                code: DependencyPlanningDiagnosticCode::ArtifactMissing,
+                message: "System-package source facts are missing.".into(),
+                field_path: "dependency_environment.system_package.source",
+            },
             source_row.alternatives.clone(),
         ),
         (DependencyProviderSourceState::Failed, _) => observation_with_diagnostic(
@@ -267,9 +275,11 @@ fn observation_from_source_row(
             binding_id,
             DependencyInventoryObservationState::Failed,
             DependencyEnvironmentValidationState::Valid,
-            DependencyPlanningDiagnosticCode::RuntimeUnavailable,
-            "System-package source reported a failure.",
-            "dependency_environment.system_package.source",
+            DependencyInventoryDiagnosticInput {
+                code: DependencyPlanningDiagnosticCode::RuntimeUnavailable,
+                message: "System-package source reported a failure.".into(),
+                field_path: "dependency_environment.system_package.source",
+            },
             source_row.alternatives.clone(),
         ),
         (DependencyProviderSourceState::Unsupported, _)
@@ -282,9 +292,11 @@ fn observation_from_source_row(
             binding_id,
             DependencyInventoryObservationState::Unavailable,
             DependencyEnvironmentValidationState::Valid,
-            DependencyPlanningDiagnosticCode::RuntimeUnavailable,
-            "System-package source is not ready for the requested package.",
-            "dependency_environment.system_package.source",
+            DependencyInventoryDiagnosticInput {
+                code: DependencyPlanningDiagnosticCode::RuntimeUnavailable,
+                message: "System-package source is not ready for the requested package.".into(),
+                field_path: "dependency_environment.system_package.source",
+            },
             source_row.alternatives.clone(),
         ),
     }
@@ -380,9 +392,11 @@ fn invalid_row(
         binding_id,
         DependencyInventoryObservationState::Invalid,
         DependencyEnvironmentValidationState::Invalid,
-        DependencyPlanningDiagnosticCode::InvalidRequest,
-        message,
-        field_path,
+        DependencyInventoryDiagnosticInput {
+            code: DependencyPlanningDiagnosticCode::InvalidRequest,
+            message: message.into(),
+            field_path,
+        },
         Vec::new(),
     )
 }
@@ -392,12 +406,15 @@ fn observation_with_diagnostic(
     binding_id: DependencyBindingId,
     state: DependencyInventoryObservationState,
     validation_state: DependencyEnvironmentValidationState,
-    code: DependencyPlanningDiagnosticCode,
-    message: impl Into,
-    field_path: &'static str,
+    diagnostic_input: DependencyInventoryDiagnosticInput,
     alternatives: Vec,
 ) -> DependencyInventoryObservationRow {
-    let diagnostic = diagnostic(item, code, message.into(), field_path);
+    let DependencyInventoryDiagnosticInput {
+        code,
+        message,
+        field_path,
+    } = diagnostic_input;
+    let diagnostic = diagnostic(item, code, message, field_path);
     row(
         binding_id,
         state,
diff --git a/crates/pantograph-embedded-runtime/src/dependency_inventory_tests.rs b/crates/pantograph-embedded-runtime/src/dependency_inventory_tests.rs
index a452e640f..bb4126025 100644
--- a/crates/pantograph-embedded-runtime/src/dependency_inventory_tests.rs
+++ b/crates/pantograph-embedded-runtime/src/dependency_inventory_tests.rs
@@ -326,6 +326,31 @@ async fn inventory_service_routes_device_toolchain_payloads_with_alternatives()
         device_toolchain_status.state,
         DependencyBindingStatusState::Unavailable
     );
+    assert_eq!(
+        device_toolchain_status.diagnostics,
+        vec![
+            pantograph_dependency_planning::DependencyPlanningDiagnostic {
+                code: DependencyPlanningDiagnosticCode::RuntimeUnavailable,
+                severity: pantograph_dependency_planning::DependencyPlanningSeverity::Error,
+                message: "Device-toolchain source is not ready for the requested toolchain."
+                    .to_string(),
+                model_id: Some(request.as_request().identity_key.model_ref.model_id.clone()),
+                runtime_id: request
+                    .as_request()
+                    .identity_key
+                    .scheduler_intent
+                    .requested_runtime_id
+                    .clone(),
+                device_id: request
+                    .as_request()
+                    .identity_key
+                    .scheduler_intent
+                    .requested_device_id
+                    .clone(),
+                field_path: Some("dependency_environment.device_toolchain.source".to_string()),
+            }
+        ]
+    );
     assert_eq!(device_toolchain_status.alternatives.len(), 1);
     assert_eq!(
         device_toolchain_status.alternatives[0]
diff --git a/crates/pantograph-embedded-runtime/src/task_executor/stream_artifacts.rs b/crates/pantograph-embedded-runtime/src/task_executor/stream_artifacts.rs
index 64f9f9f7c..5c0002629 100644
--- a/crates/pantograph-embedded-runtime/src/task_executor/stream_artifacts.rs
+++ b/crates/pantograph-embedded-runtime/src/task_executor/stream_artifacts.rs
@@ -57,15 +57,8 @@ impl StreamArtifactizer {
         let relationship = artifact_relationship_from_chunk(chunk);
 
         let key = MediaStreamKey::new(execution_id, task_id, port);
-        let (artifact_id, stream_handle, byte_range_start) = self.open_or_get_stream(
-            &key,
-            task_id,
-            execution_id,
-            port,
-            media_body.kind,
-            &media_type,
-            &relationship,
-        )?;
+        let (artifact_id, stream_handle, byte_range_start) =
+            self.open_or_get_stream(&key, media_body.kind, &media_type, &relationship)?;
         let (byte_range_end_exclusive, next_sequence) =
             checked_stream_artifact_progress(byte_range_start, body.len(), sequence)?;
 
@@ -167,9 +160,6 @@ impl StreamArtifactizer {
     fn open_or_get_stream(
         &self,
         key: &MediaStreamKey,
-        task_id: &str,
-        execution_id: &str,
-        port: &str,
         payload_kind: ArtifactPayloadKind,
         media_type: &str,
         relationship: &ArtifactRelationship,
@@ -184,11 +174,11 @@ impl StreamArtifactizer {
                     media_type: media_type.to_string(),
                     format: Some(format_metadata(payload_kind, media_type)),
                     attribution: ArtifactAttribution {
-                        workflow_run_id: execution_id.to_string(),
+                        workflow_run_id: key.workflow_run_id.clone(),
                         workflow_id: None,
                         workflow_version_id: None,
-                        node_id: Some(task_id.to_string()),
-                        port_id: Some(port.to_string()),
+                        node_id: Some(key.node_id.clone()),
+                        port_id: Some(key.port.clone()),
                         model_id: None,
                         runtime_id: None,
                     },
@@ -414,6 +404,71 @@ mod tests {
     use pantograph_workflow_service::WorkflowService;
     use std::sync::Arc;
 
+    #[test]
+    fn stream_key_preserves_attribution_reuse_and_scope_isolation() {
+        use pantograph_workflow_service::{
+            ArtifactDescriptorQueryRequest, ArtifactPolicy, ArtifactReadRequest, ArtifactStore,
+            WorkflowArtifactWriter,
+        };
+        let temp = tempfile::tempdir().expect("artifact directory");
+        let store = ArtifactStore::open(
+            temp.path().join("artifacts"),
+            ArtifactPolicy {
+                policy_id: "stream-key-test".to_string(),
+                policy_version: 1,
+                ttl_seconds: None,
+                max_disk_bytes: None,
+                max_memory_bytes: None,
+                max_single_artifact_bytes: None,
+                spill_threshold_bytes: None,
+                delete_on_consume: false,
+            },
+        )
+        .expect("artifact store");
+        let service = Arc::new(
+            WorkflowService::new().with_artifact_writer(WorkflowArtifactWriter::new(store)),
+        );
+        let artifactizer = StreamArtifactizer::new(service.clone());
+        let chunk = serde_json::json!({"audio_base64":"AA==", "media_type":"audio/wav"});
+        let first = artifactizer.artifactize_chunk("node-a", "run-a", "audio", chunk.clone());
+        let mut final_chunk = chunk.clone();
+        final_chunk["is_final"] = serde_json::json!(true);
+        let second = artifactizer.artifactize_chunk("node-a", "run-a", "audio", final_chunk);
+        assert_eq!(first["artifact_id"], second["artifact_id"]);
+        assert_eq!(first["byte_range_start"], 0);
+        assert_eq!(second["byte_range_start"], 1);
+        assert_eq!(second["available_byte_length"], 2);
+        let id = first["artifact_id"].as_str().expect("artifact id");
+        let descriptor = service
+            .artifact_descriptor(ArtifactDescriptorQueryRequest {
+                artifact_id: id.to_string(),
+            })
+            .expect("descriptor")
+            .artifact
+            .expect("stored artifact");
+        assert_eq!(descriptor.attribution.workflow_run_id, "run-a");
+        assert_eq!(descriptor.attribution.node_id.as_deref(), Some("node-a"));
+        assert_eq!(descriptor.attribution.port_id.as_deref(), Some("audio"));
+        let body = service
+            .read_artifact_body(ArtifactReadRequest {
+                artifact_id: id.to_string(),
+                byte_range_start: None,
+                byte_range_end_exclusive: None,
+            })
+            .expect("body");
+        assert_eq!(body.body, vec![0, 0]);
+        for (node, run, port) in [
+            ("node-b", "run-a", "audio"),
+            ("node-a", "run-b", "audio"),
+            ("node-a", "run-a", "other"),
+        ] {
+            let isolated = artifactizer.artifactize_chunk(node, run, port, chunk.clone());
+            assert_ne!(isolated["artifact_id"], first["artifact_id"]);
+            assert_eq!(isolated["byte_range_start"], 0);
+        }
+        assert_eq!(artifactizer.streams.lock().expect("streams").len(), 3);
+    }
+
     #[test]
     fn stream_artifact_progress_rejects_byte_range_overflow() {
         assert_eq!(checked_stream_artifact_progress(u64::MAX, 1, 0), None);
diff --git a/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-embedded-private-inputs.md b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-embedded-private-inputs.md
new file mode 100644
index 000000000..efa88b303
--- /dev/null
+++ b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-embedded-private-inputs.md
@@ -0,0 +1,9 @@
+# Private inventory diagnostic input and stream attribution
+
+Fresh Clippy at 788ce31 reports only four embedded-runtime arities. This milestone repairs three private methods; the public hosted startup constructor remains separate.
+
+Both device-toolchain and system-package observation helpers had eight arguments and six callers each. Group only code, owned message and static field_path in a shared private DependencyInventoryDiagnosticInput, gated with the existing test/standalone owners. Keep work item, binding identity, observation state, validation state and alternatives distinct. The message conversion moves from helper entry to the named field at each call; diagnostic attribution lookup and row builder remain unchanged. Exact diagnostic equality is added to the existing real inventory-service device-toolchain test, including model/runtime/device and field path; existing ready/unavailable/system-package coverage remains.
+
+The stream helper has one caller. MediaStreamKey already copies the exact execution/task/port strings, so remove those three duplicated arguments and clone attribution directly from that key. Lock/map/reuse/relationship/progress/failure behavior remains unchanged. A real artifact-store regression verifies two chunks reuse one artifact, finalization retains exact bytes, attribution matches all three key fields, and changing each field isolates a fresh stream. Existing overflow and redaction regressions remain.
+
+All changes are private; public DTOs and Result signatures are unchanged. Root source review accepted all six files at frozen tree 6d72a526a367dfd9bfd60e7e1d78ad0eb46d265c. Hosted full embedded execution remains pending. Full pinned cargo fmt passes locally; no local Rust execution is claimed. Prior layout head has 450 embedded passes and three fixture failures, with actual clone-isolation test, Headless and Runtime Separation passing. The fixture repair is a separate preceding commit bbce93c whose hosted result remains pending.

From e7e52775f3479dc89f1d703ae9ab7715124756f8 Mon Sep 17 00:00:00 2001
From: Puma 
Date: Sat, 3 Oct 2026 09:28:24 -0700
Subject: [PATCH 10/21] Name owned hosted startup configuration inputs

---
 crates/pantograph-embedded-runtime/src/lib.rs |   3 +-
 .../src/workflow_service_composition.rs       | 165 ++++++++++++------
 .../2026-10-03-hosted-startup-named-config.md |   9 +
 src-tauri/src/app_setup.rs                    |  29 +--
 4 files changed, 141 insertions(+), 65 deletions(-)
 create mode 100644 docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-hosted-startup-named-config.md

diff --git a/crates/pantograph-embedded-runtime/src/lib.rs b/crates/pantograph-embedded-runtime/src/lib.rs
index 9eb2ad885..4873a50b9 100644
--- a/crates/pantograph-embedded-runtime/src/lib.rs
+++ b/crates/pantograph-embedded-runtime/src/lib.rs
@@ -163,7 +163,8 @@ pub use task_executor::{runtime_extension_keys, TauriTaskExecutor as PantographT
 pub(crate) use workflow_scheduler_diagnostics::EmbeddedWorkflowSchedulerDiagnosticsProvider;
 pub use workflow_service_composition::{
     EmbeddedHostedStartupCompositionInput, EmbeddedHostedStartupCompositionOutput,
-    EmbeddedHostedStartupPumasSelectorSource, EmbeddedWorkflowServiceComposition,
+    EmbeddedHostedStartupConfig, EmbeddedHostedStartupPumasSelectorSource,
+    EmbeddedWorkflowServiceComposition,
 };
 
 pub type SharedWorkflowService = Arc;
diff --git a/crates/pantograph-embedded-runtime/src/workflow_service_composition.rs b/crates/pantograph-embedded-runtime/src/workflow_service_composition.rs
index 023092405..189dc9c09 100644
--- a/crates/pantograph-embedded-runtime/src/workflow_service_composition.rs
+++ b/crates/pantograph-embedded-runtime/src/workflow_service_composition.rs
@@ -140,6 +140,22 @@ pub enum EmbeddedHostedStartupPumasSelectorSource {
     SetupPath(Option),
 }
 
+/// Owned inputs for hosted startup composition, without changing startup defaults.
+///
+/// Rust callers construct these named fields and pass the config to
+/// `EmbeddedHostedStartupCompositionInput::new`.
+pub struct EmbeddedHostedStartupConfig {
+    pub runtime_registry: SharedRuntimeRegistry,
+    pub runtime_registry_controller: Arc,
+    pub gateway: Arc,
+    pub pumas_selector_source: Option,
+    pub project_root: PathBuf,
+    pub kv_cache_dir: PathBuf,
+    pub dependency_readiness_runtime_handle: tokio::runtime::Handle,
+    pub max_loaded_sessions: Option,
+    pub max_dispatch_source_snapshot_age_ms: u64,
+}
+
 pub struct EmbeddedHostedStartupCompositionInput {
     workflow_service: WorkflowService,
     max_loaded_sessions: Option,
@@ -156,17 +172,18 @@ pub struct EmbeddedHostedStartupCompositionInput {
 
 impl EmbeddedHostedStartupCompositionInput {
     #[must_use]
-    pub fn new(
-        runtime_registry: SharedRuntimeRegistry,
-        runtime_registry_controller: Arc,
-        gateway: Arc,
-        pumas_selector_source: Option,
-        project_root: PathBuf,
-        kv_cache_dir: PathBuf,
-        dependency_readiness_runtime_handle: tokio::runtime::Handle,
-        max_loaded_sessions: Option,
-        max_dispatch_source_snapshot_age_ms: u64,
-    ) -> Self {
+    pub fn new(config: EmbeddedHostedStartupConfig) -> Self {
+        let EmbeddedHostedStartupConfig {
+            runtime_registry,
+            runtime_registry_controller,
+            gateway,
+            pumas_selector_source,
+            project_root,
+            kv_cache_dir,
+            dependency_readiness_runtime_handle,
+            max_loaded_sessions,
+            max_dispatch_source_snapshot_age_ms,
+        } = config;
         Self {
             workflow_service: WorkflowService::new(),
             max_loaded_sessions,
@@ -1216,6 +1233,52 @@ mod tests {
             .contains("dependency-readiness snapshot producer poll interval"));
     }
 
+    #[tokio::test]
+    async fn hosted_startup_named_config_preserves_owned_values_and_defaults() {
+        let registry: SharedRuntimeRegistry = Arc::new(RuntimeRegistry::new());
+        let gateway = Arc::new(inference::InferenceGateway::new());
+        let input = EmbeddedHostedStartupCompositionInput::new(EmbeddedHostedStartupConfig {
+            runtime_registry: registry.clone(),
+            runtime_registry_controller: gateway.clone(),
+            gateway: gateway.clone(),
+            pumas_selector_source: Some(EmbeddedHostedStartupPumasSelectorSource::SetupPath(Some(
+                PathBuf::from("pumas-root"),
+            ))),
+            project_root: PathBuf::from("project-root"),
+            kv_cache_dir: PathBuf::from("kv-root"),
+            dependency_readiness_runtime_handle: tokio::runtime::Handle::current(),
+            max_loaded_sessions: Some(7),
+            max_dispatch_source_snapshot_age_ms: 2_345,
+        });
+        assert!(Arc::ptr_eq(&input.runtime_registry, ®istry));
+        assert!(Arc::ptr_eq(&input.runtime_registry_controller, &gateway));
+        assert!(Arc::ptr_eq(&input.gateway, &gateway));
+        assert!(
+            matches!(&input.pumas_selector_source, Some(EmbeddedHostedStartupPumasSelectorSource::SetupPath(Some(path))) if path == std::path::Path::new("pumas-root"))
+        );
+        assert_eq!(input.project_root, PathBuf::from("project-root"));
+        assert_eq!(input.kv_cache_dir, PathBuf::from("kv-root"));
+        assert_eq!(input.max_loaded_sessions, Some(7));
+        assert_eq!(input.max_dispatch_source_snapshot_age_ms, 2_345);
+        assert_eq!(
+            input.dependency_readiness_producer_config,
+            EmbeddedDependencyReadinessSnapshotProducerConfig::default()
+        );
+        assert_eq!(
+            input
+                .dependency_readiness_runtime_handle
+                .spawn(async { 42 })
+                .await
+                .expect("original runtime handle"),
+            42
+        );
+        let config = EmbeddedDependencyReadinessSnapshotProducerConfig {
+            poll_interval: std::time::Duration::from_secs(17),
+        };
+        let input = input.with_dependency_readiness_producer_config(config.clone());
+        assert_eq!(input.dependency_readiness_producer_config, config);
+    }
+
     #[tokio::test]
     async fn hosted_startup_composition_returns_service_extensions_and_lifecycle_handle() {
         let temp_dir = tempfile::tempdir().expect("tempdir");
@@ -1229,19 +1292,19 @@ mod tests {
         );
         let registry: SharedRuntimeRegistry = Arc::new(RuntimeRegistry::new());
         let gateway = Arc::new(inference::InferenceGateway::new());
-        let input = EmbeddedHostedStartupCompositionInput::new(
-            registry,
-            gateway.clone(),
+        let input = EmbeddedHostedStartupCompositionInput::new(EmbeddedHostedStartupConfig {
+            runtime_registry: registry,
+            runtime_registry_controller: gateway.clone(),
             gateway,
-            Some(EmbeddedHostedStartupPumasSelectorSource::Provided(
+            pumas_selector_source: Some(EmbeddedHostedStartupPumasSelectorSource::Provided(
                 Arc::new(PumasSelectorAccess::Owner(pumas_api.clone())),
             )),
-            temp_dir.path().to_path_buf(),
-            temp_dir.path().join("kv-cache"),
-            tokio::runtime::Handle::current(),
-            Some(1),
-            1_000,
-        )
+            project_root: temp_dir.path().to_path_buf(),
+            kv_cache_dir: temp_dir.path().join("kv-cache"),
+            dependency_readiness_runtime_handle: tokio::runtime::Handle::current(),
+            max_loaded_sessions: Some(1),
+            max_dispatch_source_snapshot_age_ms: 1_000,
+        })
         .with_workflow_service(workflow_service_with_artifact_store(&temp_dir));
 
         let output = EmbeddedWorkflowServiceComposition::resource_backed_hosted_startup(input)
@@ -1304,17 +1367,17 @@ mod tests {
         let temp_dir = tempfile::tempdir().expect("tempdir");
         let registry: SharedRuntimeRegistry = Arc::new(RuntimeRegistry::new());
         let gateway = Arc::new(inference::InferenceGateway::new());
-        let input = EmbeddedHostedStartupCompositionInput::new(
-            registry,
-            gateway.clone(),
+        let input = EmbeddedHostedStartupCompositionInput::new(EmbeddedHostedStartupConfig {
+            runtime_registry: registry,
+            runtime_registry_controller: gateway.clone(),
             gateway,
-            None,
-            temp_dir.path().to_path_buf(),
-            temp_dir.path().join("kv-cache"),
-            tokio::runtime::Handle::current(),
-            Some(1),
-            1_000,
-        );
+            pumas_selector_source: None,
+            project_root: temp_dir.path().to_path_buf(),
+            kv_cache_dir: temp_dir.path().join("kv-cache"),
+            dependency_readiness_runtime_handle: tokio::runtime::Handle::current(),
+            max_loaded_sessions: Some(1),
+            max_dispatch_source_snapshot_age_ms: 1_000,
+        });
 
         let error =
             match EmbeddedWorkflowServiceComposition::resource_backed_hosted_startup(input).await {
@@ -1347,19 +1410,19 @@ mod tests {
         .expect("read-only pumas");
         let registry: SharedRuntimeRegistry = Arc::new(RuntimeRegistry::new());
         let gateway = Arc::new(inference::InferenceGateway::new());
-        let input = EmbeddedHostedStartupCompositionInput::new(
-            registry,
-            gateway.clone(),
+        let input = EmbeddedHostedStartupCompositionInput::new(EmbeddedHostedStartupConfig {
+            runtime_registry: registry,
+            runtime_registry_controller: gateway.clone(),
             gateway,
-            Some(EmbeddedHostedStartupPumasSelectorSource::Provided(
+            pumas_selector_source: Some(EmbeddedHostedStartupPumasSelectorSource::Provided(
                 Arc::new(PumasSelectorAccess::ReadOnly(Arc::new(read_only))),
             )),
-            temp_dir.path().to_path_buf(),
-            temp_dir.path().join("kv-cache"),
-            tokio::runtime::Handle::current(),
-            Some(1),
-            1_000,
-        );
+            project_root: temp_dir.path().to_path_buf(),
+            kv_cache_dir: temp_dir.path().join("kv-cache"),
+            dependency_readiness_runtime_handle: tokio::runtime::Handle::current(),
+            max_loaded_sessions: Some(1),
+            max_dispatch_source_snapshot_age_ms: 1_000,
+        });
 
         let error =
             match EmbeddedWorkflowServiceComposition::resource_backed_hosted_startup(input).await {
@@ -1386,19 +1449,19 @@ mod tests {
         );
         let registry: SharedRuntimeRegistry = Arc::new(RuntimeRegistry::new());
         let gateway = Arc::new(inference::InferenceGateway::new());
-        let input = EmbeddedHostedStartupCompositionInput::new(
-            registry,
-            gateway.clone(),
+        let input = EmbeddedHostedStartupCompositionInput::new(EmbeddedHostedStartupConfig {
+            runtime_registry: registry,
+            runtime_registry_controller: gateway.clone(),
             gateway,
-            Some(EmbeddedHostedStartupPumasSelectorSource::Provided(
+            pumas_selector_source: Some(EmbeddedHostedStartupPumasSelectorSource::Provided(
                 Arc::new(PumasSelectorAccess::Owner(pumas_api)),
             )),
-            temp_dir.path().to_path_buf(),
-            temp_dir.path().join("kv-cache"),
-            tokio::runtime::Handle::current(),
-            Some(0),
-            1_000,
-        )
+            project_root: temp_dir.path().to_path_buf(),
+            kv_cache_dir: temp_dir.path().join("kv-cache"),
+            dependency_readiness_runtime_handle: tokio::runtime::Handle::current(),
+            max_loaded_sessions: Some(0),
+            max_dispatch_source_snapshot_age_ms: 1_000,
+        })
         .with_workflow_service(workflow_service_with_store(
             &temp_dir,
             WorkflowService::with_capacity_limits(2, 2),
diff --git a/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-hosted-startup-named-config.md b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-hosted-startup-named-config.md
new file mode 100644
index 000000000..2e6c22ada
--- /dev/null
+++ b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-hosted-startup-named-config.md
@@ -0,0 +1,9 @@
+# Named owned hosted startup configuration
+
+The last public embedded-runtime arity finding is the nine-argument EmbeddedHostedStartupCompositionInput::new. Introduce exported EmbeddedHostedStartupConfig with exactly the nine existing owned inputs: runtime registry, registry controller, inference gateway, optional selector source, project root, KV cache path, readiness runtime handle, optional loaded-session limit, and source-snapshot age limit. The constructor destructures these fields into its unchanged initializer. WorkflowService::new and default producer configuration remain unchanged, as do both builder methods.
+
+All five existing calls are migrated: desktop app_setup and four hosted composition tests. Each original expression, clone and ownership transfer stays attached to its corresponding named field; no policy is inferred or optional field silently defaulted. A constructor regression verifies Arc identity, paths, selector setup path, limits, functional runtime handle, default producer configuration and producer builder override. Existing real startup tests retain success/lifecycle, missing selector, wrong owner and invalid service-capacity coverage. Hosted workspace all-feature compilation covers the desktop caller, and the full embedded unit gate exercises startup tests.
+
+This is a public Rust source signature change: callers must pass the new config instead of nine positional arguments. The crate is publish=false and uses the workspace version, so no independently published crate version is changed. Unseen Rust consumers are not claimed source-compatible. Serialized DTOs, runtime policy and startup order are unchanged.
+
+Root source review accepted all four files at frozen tree afc4cc26997ba3f61dde38ab77e1927563e1f18e. Hosted execution remains pending. The constructor initializer and both existing builder bodies were also compared byte-for-byte with the published parent and remain identical. Full pinned formatting passes locally; no local Rust execution is claimed under the shared resource constraint.
diff --git a/src-tauri/src/app_setup.rs b/src-tauri/src/app_setup.rs
index d68f0ed1b..cb5d69ee5 100644
--- a/src-tauri/src/app_setup.rs
+++ b/src-tauri/src/app_setup.rs
@@ -9,8 +9,8 @@ use crate::llm::{
 use crate::project_root::resolve_project_root;
 use crate::workflow;
 use pantograph_embedded_runtime::{
-    EmbeddedHostedStartupCompositionInput, EmbeddedHostedStartupPumasSelectorSource,
-    EmbeddedWorkflowServiceComposition,
+    EmbeddedHostedStartupCompositionInput, EmbeddedHostedStartupConfig,
+    EmbeddedHostedStartupPumasSelectorSource, EmbeddedWorkflowServiceComposition,
 };
 use std::sync::Arc;
 use tauri::{Emitter, Manager};
@@ -245,17 +245,20 @@ pub fn run_app() -> AppStartupResult<()> {
                 let pumas_library_path = pumas_release_dir.or(pumas_launcher_root);
 
                 let startup_input = EmbeddedHostedStartupCompositionInput::new(
-                    runtime_registry.clone(),
-                    gateway.clone(),
-                    gateway.inner_arc(),
-                    Some(EmbeddedHostedStartupPumasSelectorSource::SetupPath(
-                        pumas_library_path,
-                    )),
-                    project_root.clone(),
-                    kv_cache_dir,
-                    runtime_handle.clone(),
-                    max_loaded_sessions,
-                    HOSTED_DISPATCH_SOURCE_SNAPSHOT_MAX_AGE_MS,
+                    EmbeddedHostedStartupConfig {
+                        runtime_registry: runtime_registry.clone(),
+                        runtime_registry_controller: gateway.clone(),
+                        gateway: gateway.inner_arc(),
+                        pumas_selector_source: Some(
+                            EmbeddedHostedStartupPumasSelectorSource::SetupPath(pumas_library_path),
+                        ),
+                        project_root: project_root.clone(),
+                        kv_cache_dir,
+                        dependency_readiness_runtime_handle: runtime_handle.clone(),
+                        max_loaded_sessions,
+                        max_dispatch_source_snapshot_age_ms:
+                            HOSTED_DISPATCH_SOURCE_SNAPSHOT_MAX_AGE_MS,
+                    },
                 )
                 .with_workflow_service(workflow_service);
                 let startup_output = tauri::async_runtime::block_on(

From e2dbe87c14a0bd34fc8519991f42bb522f4ce186 Mon Sep 17 00:00:00 2001
From: Puma 
Date: Sat, 3 Oct 2026 09:50:11 -0700
Subject: [PATCH 11/21] Propagate owned runtime shutdown failures through
 UniFFI

---
 Cargo.lock                                    |   1 +
 bindings/csharp/README.md                     |  10 ++
 crates/pantograph-uniffi/Cargo.toml           |   1 +
 crates/pantograph-uniffi/src/runtime.rs       |  13 +-
 .../src/runtime_shutdown_tests.rs             | 159 ++++++++++++++++++
 crates/pantograph-uniffi/src/runtime_tests.rs |  18 +-
 .../src/runtime_validation_tests.rs           |   6 +-
 .../2026-10-03-uniffi-shutdown-result.md      |  13 ++
 8 files changed, 207 insertions(+), 14 deletions(-)
 create mode 100644 crates/pantograph-uniffi/src/runtime_shutdown_tests.rs
 create mode 100644 docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-uniffi-shutdown-result.md

diff --git a/Cargo.lock b/Cargo.lock
index 4be64790e..1127ac859 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -7552,6 +7552,7 @@ name = "pantograph-uniffi"
 version = "0.1.0"
 dependencies = [
  "async-trait",
+ "futures-util",
  "graph-flow",
  "inference",
  "node-engine",
diff --git a/bindings/csharp/README.md b/bindings/csharp/README.md
index ff8566ece..febd8eed0 100644
--- a/bindings/csharp/README.md
+++ b/bindings/csharp/README.md
@@ -30,6 +30,16 @@ with any redistributed generator source/binary; this repository does not vendor
 or package the generator itself. Existing generated-binding/artifact licensing
 obligations remain unchanged.
 
+## Shutdown result
+
+Await `runtime.Shutdown()` and handle the generated `FfiException` if the
+owned backend cannot stop. Its message contains the standard JSON error envelope
+with `internal_error` and the original shutdown cause. A failed stop does not
+mean the runtime released its residency; callers may retry after resolving the
+cause. Existing C# await expressions remain valid, but regenerate bindings and
+ship them with the matching native library when adopting this fallible API.
+Rust callers now handle `Result<(), FfiError>` explicitly.
+
 ## Usage
 Run the repository-level smoke script:
 
diff --git a/crates/pantograph-uniffi/Cargo.toml b/crates/pantograph-uniffi/Cargo.toml
index ddd64f636..68b0e573a 100644
--- a/crates/pantograph-uniffi/Cargo.toml
+++ b/crates/pantograph-uniffi/Cargo.toml
@@ -54,6 +54,7 @@ required-features = ["cli"]
 uniffi = { version = "0.28", features = ["build"] }
 
 [dev-dependencies]
+futures-util.workspace = true
 uniffi = { version = "0.28", features = ["bindgen-tests"] }
 
 [features]
diff --git a/crates/pantograph-uniffi/src/runtime.rs b/crates/pantograph-uniffi/src/runtime.rs
index 1e4ab0b11..22a0b07d5 100644
--- a/crates/pantograph-uniffi/src/runtime.rs
+++ b/crates/pantograph-uniffi/src/runtime.rs
@@ -208,8 +208,13 @@ impl FfiPantographRuntime {
     }
 
     /// Stop inference backends owned by this runtime.
-    pub async fn shutdown(&self) {
-        self.runtime.shutdown().await;
+    ///
+    /// A failed shutdown is returned through the standard error envelope; the
+    /// embedded owner retains residency and permits a later retry.
+    pub async fn shutdown(&self) -> Result<(), FfiError> {
+        self.runtime.shutdown().await.map_err(|error| {
+            workflow_adapter_error(WorkflowErrorCode::InternalError, error.to_string())
+        })
     }
 
     /// Register an attribution client and return ClientRegistrationResponse JSON.
@@ -1116,3 +1121,7 @@ mod runtime_tests;
 #[cfg(test)]
 #[path = "runtime_validation_tests.rs"]
 mod runtime_validation_tests;
+
+#[cfg(test)]
+#[path = "runtime_shutdown_tests.rs"]
+mod runtime_shutdown_tests;
diff --git a/crates/pantograph-uniffi/src/runtime_shutdown_tests.rs b/crates/pantograph-uniffi/src/runtime_shutdown_tests.rs
new file mode 100644
index 000000000..940b3f0c8
--- /dev/null
+++ b/crates/pantograph-uniffi/src/runtime_shutdown_tests.rs
@@ -0,0 +1,159 @@
+use std::pin::Pin;
+use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
+use std::sync::Arc;
+
+use async_trait::async_trait;
+use futures_util::Stream;
+use inference::backend::BackendStartOutcome;
+use inference::{
+    BackendCapabilities, BackendConfig, BackendError, ChatChunk, EmbeddingResult, InferenceBackend,
+    InferenceGateway, ProcessSpawner, RerankRequest, RerankResponse,
+};
+use node_engine::ExecutorExtensions;
+use pantograph_embedded_runtime::{EmbeddedRuntime, EmbeddedRuntimeConfig};
+use pantograph_workflow_service::WorkflowService;
+use tokio::sync::RwLock;
+
+use super::FfiPantographRuntime;
+use crate::FfiError;
+
+struct ShutdownBackend {
+    fail_next_stop: AtomicBool,
+    ready: AtomicBool,
+    stop_calls: Arc,
+}
+
+#[async_trait]
+impl InferenceBackend for ShutdownBackend {
+    fn name(&self) -> &'static str {
+        "shutdown-fixture"
+    }
+    fn description(&self) -> &'static str {
+        "bounded shutdown fixture"
+    }
+    fn capabilities(&self) -> BackendCapabilities {
+        BackendCapabilities::default()
+    }
+    async fn start(
+        &mut self,
+        _: &BackendConfig,
+        _: Arc,
+    ) -> Result {
+        panic!("shutdown fixture must not start a backend")
+    }
+    async fn stop(&mut self) -> Result<(), BackendError> {
+        self.stop_calls.fetch_add(1, Ordering::SeqCst);
+        if self.fail_next_stop.swap(false, Ordering::SeqCst) {
+            return Err(BackendError::Inference("fixture stop failure".to_string()));
+        }
+        self.ready.store(false, Ordering::SeqCst);
+        Ok(())
+    }
+    fn is_ready(&self) -> bool {
+        self.ready.load(Ordering::SeqCst)
+    }
+    async fn health_check(&self) -> bool {
+        self.is_ready()
+    }
+    fn base_url(&self) -> Option {
+        None
+    }
+    async fn chat_completion_stream(
+        &self,
+        _: String,
+    ) -> Result> + Send>>, BackendError>
+    {
+        panic!("shutdown fixture must not execute inference")
+    }
+    async fn embeddings(
+        &self,
+        _: Vec,
+        _: &str,
+    ) -> Result, BackendError> {
+        panic!("shutdown fixture must not execute embeddings")
+    }
+    async fn rerank(&self, _: RerankRequest) -> Result {
+        panic!("shutdown fixture must not execute reranking")
+    }
+}
+
+fn runtime(
+    fail_next_stop: bool,
+) -> (
+    FfiPantographRuntime,
+    Arc,
+    Arc,
+    std::path::PathBuf,
+) {
+    let root = super::runtime_tests::create_temp_root("shutdown-fixture");
+    let stop_calls = Arc::new(AtomicUsize::new(0));
+    let gateway = Arc::new(InferenceGateway::with_backend(
+        Box::new(ShutdownBackend {
+            fail_next_stop: AtomicBool::new(fail_next_stop),
+            ready: AtomicBool::new(true),
+            stop_calls: stop_calls.clone(),
+        }),
+        "shutdown-fixture",
+    ));
+    let extensions = Arc::new(RwLock::new(ExecutorExtensions::new()));
+    let app_data_dir = root.join("app-data");
+    let embedded = EmbeddedRuntime::with_default_python_runtime(
+        EmbeddedRuntimeConfig::new(app_data_dir.clone(), root.clone()),
+        gateway.clone(),
+        extensions.clone(),
+        Arc::new(WorkflowService::new()),
+        None,
+    );
+    (
+        FfiPantographRuntime {
+            runtime: Arc::new(embedded),
+            app_data_dir,
+            node_registry: Arc::new(node_engine::NodeRegistry::with_builtins()),
+            extensions,
+        },
+        gateway,
+        stop_calls,
+        root,
+    )
+}
+
+#[tokio::test]
+async fn ffi_shutdown_reports_owner_failure_and_allows_retry() {
+    let (runtime, gateway, stop_calls, root) = runtime(true);
+    assert!(gateway.is_ready().await);
+    let error = runtime
+        .shutdown()
+        .await
+        .expect_err("failed owner stop must cross FFI");
+    let FfiError::Other { message } = error else {
+        panic!("expected standard FFI error envelope");
+    };
+    assert_eq!(
+        serde_json::from_str::(&message).unwrap(),
+        serde_json::json!({
+            "code": "internal_error", "message": "Backend error: Inference error: fixture stop failure"
+        })
+    );
+    assert_eq!(stop_calls.load(Ordering::SeqCst), 1);
+    assert!(
+        gateway.is_ready().await,
+        "failed stop must preserve backend readiness"
+    );
+    runtime
+        .shutdown()
+        .await
+        .expect("retry observes successful owner stop");
+    assert_eq!(stop_calls.load(Ordering::SeqCst), 2);
+    assert!(!gateway.is_ready().await);
+    std::fs::remove_dir_all(root).expect("remove fixture files");
+}
+
+#[tokio::test]
+async fn ffi_shutdown_success_and_repeated_success_are_explicit() {
+    let (runtime, gateway, stop_calls, root) = runtime(false);
+    runtime.shutdown().await.expect("successful shutdown");
+    assert!(!gateway.is_ready().await);
+    runtime.shutdown().await.expect("repeated shutdown");
+    assert_eq!(stop_calls.load(Ordering::SeqCst), 2);
+    std::fs::remove_dir_all(root).expect("remove fixture files");
+}
diff --git a/crates/pantograph-uniffi/src/runtime_tests.rs b/crates/pantograph-uniffi/src/runtime_tests.rs
index 9b3492f5e..481f2eade 100644
--- a/crates/pantograph-uniffi/src/runtime_tests.rs
+++ b/crates/pantograph-uniffi/src/runtime_tests.rs
@@ -371,7 +371,7 @@ async fn direct_runtime_runs_workflow_session_from_json() {
     let close: serde_json::Value = serde_json::from_str(&close_json).expect("parse close");
     assert_eq!(close["ok"], true);
 
-    runtime.shutdown().await;
+    runtime.shutdown().await.expect("runtime shutdown");
 
     let _ = std::fs::remove_dir_all(root);
 }
@@ -434,7 +434,7 @@ async fn direct_runtime_rejects_interactive_graph_at_scheduler_boundary() {
         .contains("scheduler task session runner has no execution path"));
     assert!(envelope.message.contains("unsupported=1"));
 
-    runtime.shutdown().await;
+    runtime.shutdown().await.expect("runtime shutdown");
     let _ = std::fs::remove_dir_all(root);
 }
 
@@ -490,7 +490,7 @@ async fn direct_runtime_exposes_attribution_client_session_json() {
         serde_json::from_str(&open_session_json).expect("parse open response");
     assert!(opened["session"]["client_session_id"].as_str().is_some());
 
-    runtime.shutdown().await;
+    runtime.shutdown().await.expect("runtime shutdown");
     let _ = std::fs::remove_dir_all(root);
 }
 
@@ -610,7 +610,7 @@ async fn direct_runtime_exposes_workflow_graph_persistence_and_edit_session() {
         )
         .await
         .expect("close graph edit session");
-    runtime.shutdown().await;
+    runtime.shutdown().await.expect("runtime shutdown");
 
     let _ = std::fs::remove_dir_all(root);
 }
@@ -701,7 +701,7 @@ async fn direct_runtime_exposes_backend_owned_graph_authoring_discovery() {
         .message
         .contains("No options provider for text-input:text"));
 
-    runtime.shutdown().await;
+    runtime.shutdown().await.expect("runtime shutdown");
     let _ = std::fs::remove_dir_all(root);
 }
 
@@ -781,7 +781,7 @@ async fn direct_runtime_puma_lib_options_use_selector_access_from_pumas_api() {
         .as_str()
         .is_some_and(|cursor| cursor.starts_with("model-library-updates:")));
 
-    runtime.shutdown().await;
+    runtime.shutdown().await.expect("runtime shutdown");
     let _ = std::fs::remove_dir_all(root);
 }
 
@@ -841,7 +841,7 @@ async fn direct_runtime_exposes_artifact_store_contract_surface() {
     assert_eq!(envelope.code, WorkflowErrorCode::InvalidRequest);
     assert!(envelope.message.contains("artifact not found"));
 
-    runtime.shutdown().await;
+    runtime.shutdown().await.expect("runtime shutdown");
     let _ = std::fs::remove_dir_all(root);
 }
 
@@ -987,7 +987,7 @@ async fn direct_runtime_exposes_artifact_format_settings_and_capabilities_json()
         "image quality_percent 0 is outside allowed range"
     );
 
-    runtime.shutdown().await;
+    runtime.shutdown().await.expect("runtime shutdown");
     let _ = std::fs::remove_dir_all(root);
 }
 
@@ -1184,7 +1184,7 @@ async fn direct_runtime_exposes_managed_media_dependency_statuses_and_actions_js
         assert!(envelope.message.contains("missing expected file"));
     }
 
-    runtime.shutdown().await;
+    runtime.shutdown().await.expect("runtime shutdown");
     let _ = std::fs::remove_dir_all(root);
 }
 
diff --git a/crates/pantograph-uniffi/src/runtime_validation_tests.rs b/crates/pantograph-uniffi/src/runtime_validation_tests.rs
index b1c09bfa7..96fc1b288 100644
--- a/crates/pantograph-uniffi/src/runtime_validation_tests.rs
+++ b/crates/pantograph-uniffi/src/runtime_validation_tests.rs
@@ -170,7 +170,7 @@ async fn public_validation_publication_is_required_again_after_runtime_reopen()
             .expect("published text runs"),
     );
     assert_eq!(response["outputs"][0]["value"], "published text");
-    runtime.shutdown().await;
+    runtime.shutdown().await.expect("runtime shutdown");
     drop(runtime);
 
     let reopened = open_runtime(&root).await;
@@ -186,7 +186,7 @@ async fn public_validation_publication_is_required_again_after_runtime_reopen()
             .expect("republished text runs"),
     );
     assert_eq!(response["outputs"][0]["value"], "published text");
-    reopened.shutdown().await;
+    reopened.shutdown().await.expect("runtime shutdown");
     drop(reopened);
     std::fs::remove_dir_all(root).expect("remove fixture");
 }
@@ -234,7 +234,7 @@ async fn public_publication_rejects_stale_validation_and_caller_supplied_proof()
             .await
             .expect_err("rejected publication stores no executable snapshot"),
     );
-    runtime.shutdown().await;
+    runtime.shutdown().await.expect("runtime shutdown");
     drop(runtime);
     std::fs::remove_dir_all(root).expect("remove fixture");
 }
diff --git a/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-uniffi-shutdown-result.md b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-uniffi-shutdown-result.md
new file mode 100644
index 000000000..80a09b69e
--- /dev/null
+++ b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-uniffi-shutdown-result.md
@@ -0,0 +1,13 @@
+# Preserve fallible shutdown through UniFFI
+
+Exact PR42 head e7e5277 clears embedded-runtime Clippy and exposes one UniFFI unused Result at runtime.rs shutdown. EmbeddedRuntime::shutdown already awaits owned shutdown, preserves gateway residency on stop failure and only invalidates loaded session runtimes after successful stop. The adapter must propagate that result rather than report success or discard it.
+
+Return Result<(), FfiError> through the existing workflow_adapter_error internal_error JSON envelope, retaining the original owner Display cause. Twelve existing Rust shutdown calls now require success explicitly. No embedded lifecycle, invalidation, retry or backend policy is changed. A dedicated real adapter-chain regression uses the production FFI, EmbeddedRuntime and InferenceGateway with a failure-once backend: it checks the exact envelope, one stop invocation, retained actual gateway readiness, then successful retry and stopped readiness. A second test covers initial/repeated success. Non-shutdown trait methods panic if unexpectedly invoked.
+
+The test-only futures-util manifest/lock edge reuses the existing locked workspace version for the backend trait stream signature; no package version changes. The mock remains a backend fixture, not an alternate adapter implementation.
+
+Generated C# calls remain await runtime.Shutdown() (NativeSmoke has one executed call plus its compile-surface call; Quickstart has one executed call). Fallible metadata now enables the existing generated FfiException path for Rust FfiError; bindings and native library must be regenerated/shipped together, with no old-generated-binary compatibility claim. Headless CI regenerates and executes NativeSmoke and compiles/runs the packaged quickstart. A C# shutdown-failure injection is not available through the public runtime constructor without adding a production testing API or forcing an actual backend failure; the bounded failure test therefore exercises the real Rust adapter chain, while C# success/generated plumbing remains hosted qualification.
+
+Root source review accepted the complete eight-file checkpoint at frozen tree 18436e4cf375a4bb204f086453991fa07966eca0. Full pinned format passes locally, with no local Rust/native build claimed. PR42 has 455/455 embedded tests, full focused tests, workspace feature checks and format green; this distinct adapter milestone requires new exact-head aggregate and generated binding qualification.
+
+The pinned generator source confirms the C# error naming: CsCodeOracle::convert_error_suffix maps Error to Exception, so Rust FfiError is generated as FfiException ([official source](https://github.com/NordSecurity/uniffi-bindgen-cs/blob/2f4880f03ed08d960ad0ee14d11cf95444eee540/bindgen/src/gen_cs/mod.rs)).

From a387e75bdf0d417257d63bf9c05db19b6a2c9574 Mon Sep 17 00:00:00 2001
From: Puma 
Date: Sat, 3 Oct 2026 10:23:14 -0700
Subject: [PATCH 12/21] Preserve node test contracts while clearing strict lint

---
 .github/workflows/quality-gates.yml               | 15 +++++++++++++++
 .../src/core_executor/inference_tests.rs          |  5 ++++-
 .../node-engine/src/engine/dependency_inputs.rs   |  2 +-
 .../reports/2026-10-03-node-engine-test-lints.md  |  7 +++++++
 4 files changed, 27 insertions(+), 2 deletions(-)
 create mode 100644 docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-node-engine-test-lints.md

diff --git a/.github/workflows/quality-gates.yml b/.github/workflows/quality-gates.yml
index 8a0d02108..d2b2244e0 100644
--- a/.github/workflows/quality-gates.yml
+++ b/.github/workflows/quality-gates.yml
@@ -245,6 +245,21 @@ jobs:
           python3 -c 'import pathlib, sys; names = [line for line in pathlib.Path(sys.argv[1]).read_text().splitlines() if line.startswith("core_executor::tests::inference_tests::") and "rejects_contract_only_with_lifecycle" in line and line.endswith(": test")]; print("Contract-only lifecycle tests discovered:", len(names)); sys.exit(0 if names else 1)' "$RUNNER_TEMP/node-validation-lifecycle-tests.list"
           cargo test -p node-engine --features inference-nodes --lib rejects_contract_only_with_lifecycle
 
+      - name: Run typed embedding capture regressions
+        run: |
+          set -euo pipefail
+          tests=(
+            core_executor::tests::inference_tests::test_canonical_llm_embedding_uses_typed_gateway_boundary
+            core_executor::tests::inference_tests::test_canonical_llm_embedding_with_package_facts_emits_compatibility_lifecycle
+          )
+          cargo test -p node-engine --features inference-nodes --lib test_canonical_llm_embedding -- --list > "$RUNNER_TEMP/node-embedding-tests.list"
+          for test_name in "${tests[@]}"; do
+            python3 -c 'import pathlib, sys; lines = pathlib.Path(sys.argv[1]).read_text().splitlines(); sys.exit(0 if sys.argv[2] + ": test" in lines else 1)' "$RUNNER_TEMP/node-embedding-tests.list" "$test_name"
+            result_path="$RUNNER_TEMP/node-embedding-${test_name##*::}.log"
+            cargo test -p node-engine --features inference-nodes --lib "$test_name" -- --exact 2>&1 | tee "$result_path"
+            python3 -c 'import pathlib, re, sys; text = pathlib.Path(sys.argv[1]).read_text(); sys.exit(0 if re.search(r"^test result: ok\. 1 passed; 0 failed; 0 ignored;", text, re.M) else 1)' "$result_path"
+          done
+
       - name: Run node-engine tests
         run: cargo test -p node-engine --lib
 
diff --git a/crates/node-engine/src/core_executor/inference_tests.rs b/crates/node-engine/src/core_executor/inference_tests.rs
index 6e4660cbe..5261b7ab2 100644
--- a/crates/node-engine/src/core_executor/inference_tests.rs
+++ b/crates/node-engine/src/core_executor/inference_tests.rs
@@ -3177,9 +3177,12 @@ impl InferenceBackend for MockTypedTextBackend {
     }
 }
 
+#[cfg(feature = "inference-nodes")]
+type CapturedEmbeddingRequest = (Vec, String);
+
 #[cfg(feature = "inference-nodes")]
 struct MockTypedEmbeddingBackend {
-    embedding_requests: Arc, String)>>>,
+    embedding_requests: Arc>>,
 }
 
 #[cfg(feature = "inference-nodes")]
diff --git a/crates/node-engine/src/engine/dependency_inputs.rs b/crates/node-engine/src/engine/dependency_inputs.rs
index 89869b4fb..df812fe07 100644
--- a/crates/node-engine/src/engine/dependency_inputs.rs
+++ b/crates/node-engine/src/engine/dependency_inputs.rs
@@ -421,7 +421,7 @@ mod tests {
             inputs.get("text"),
             Some(&serde_json::json!("generated text"))
         );
-        assert!(inputs.get("stream").is_none());
+        assert!(!inputs.contains_key("stream"));
     }
 
     #[test]
diff --git a/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-node-engine-test-lints.md b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-node-engine-test-lints.md
new file mode 100644
index 000000000..1c6ffef43
--- /dev/null
+++ b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-node-engine-test-lints.md
@@ -0,0 +1,7 @@
+# Node-engine test-only lint repair
+
+Fresh strict Clippy at PR43 head e2dbe87 clears the UniFFI library and reaches two node-engine lib-test findings. Both exact source expressions also exist at main 4938e405. This milestone only names the captured embedding request tuple (Vec, String) with a private feature-gated alias and expresses absent stream membership with !contains_key instead of get().is_none(). All tuple ordering, values, test actions and assertions are unchanged; there is no production API or behavior change.
+
+The embedding mock has two constructors and two existing test readers, plus one push site. Existing tests assert captured input/model and compatibility lifecycle/output behavior. Because the default node-engine suite does not enable inference-nodes, add a feature-enabled gate requiring both exact existing canonical embedding test names and an individual execution receipt of 1 passed / 0 failed / 0 ignored for each, retaining the full default suite and strict aggregate lint.
+
+Root source review accepted the mechanical repairs and the corrected exact-name/execution-count gate at frozen tree 2b5bc9252887e86bed791b4c759923592ff9435d. Fresh hosted execution remains pending. Full pinned formatting passes locally; no local Rust build is claimed. PR43 shutdown success/failure and regenerated C# qualification continue independently on its exact published head.

From 9a4679604ca65647fdb49d4b940b00d4d9b2bb69 Mon Sep 17 00:00:00 2001
From: Puma 
Date: Sat, 3 Oct 2026 10:27:15 -0700
Subject: [PATCH 13/21] Collect independent strict Clippy failures without
 early abort

---
 .github/workflows/quality-gates.yml                           | 2 +-
 .../reports/2026-10-03-node-engine-test-lints.md              | 4 ++++
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/quality-gates.yml b/.github/workflows/quality-gates.yml
index d2b2244e0..50ee54cf5 100644
--- a/.github/workflows/quality-gates.yml
+++ b/.github/workflows/quality-gates.yml
@@ -456,7 +456,7 @@ jobs:
       - name: Run warning-deny clippy audit
         id: run-clippy-audit
         continue-on-error: true
-        run: cargo clippy --workspace --all-targets --all-features -- -D warnings
+        run: cargo clippy --workspace --all-targets --all-features --keep-going -- -D warnings
 
       - name: Record clippy audit result
         id: report-clippy-audit
diff --git a/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-node-engine-test-lints.md b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-node-engine-test-lints.md
index 1c6ffef43..995b8dbd3 100644
--- a/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-node-engine-test-lints.md
+++ b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-node-engine-test-lints.md
@@ -5,3 +5,7 @@ Fresh strict Clippy at PR43 head e2dbe87 clears the UniFFI library and reaches t
 The embedding mock has two constructors and two existing test readers, plus one push site. Existing tests assert captured input/model and compatibility lifecycle/output behavior. Because the default node-engine suite does not enable inference-nodes, add a feature-enabled gate requiring both exact existing canonical embedding test names and an individual execution receipt of 1 passed / 0 failed / 0 ignored for each, retaining the full default suite and strict aggregate lint.
 
 Root source review accepted the mechanical repairs and the corrected exact-name/execution-count gate at frozen tree 2b5bc9252887e86bed791b4c759923592ff9435d. Fresh hosted execution remains pending. Full pinned formatting passes locally; no local Rust build is claimed. PR43 shutdown success/failure and regenerated C# qualification continue independently on its exact published head.
+
+## Complete independent diagnostics without weakening failure
+
+Root separately reviewed and accepted adding only --keep-going to the existing strict workspace/all-targets/all-features Clippy invocation. Pinned Cargo 1.92 lists this option, and Clippy delegates these options to cargo check. A dependency-free two-crate local probe emitted both independent denied warnings and still exited 101 (56 KiB output directory, 0.13 seconds). The workflow retains -D warnings and its explicit failing audit/aggregate result. This permits diagnostics from independent compilable crates; dependencies that fail compilation can still prevent downstream checks. No lint is suppressed and no warning becomes acceptable.

From 854a245b7ed711b585c45ba8ff66ae75dd3920b2 Mon Sep 17 00:00:00 2001
From: Puma 
Date: Sat, 3 Oct 2026 12:50:18 -0700
Subject: [PATCH 14/21] fix(ci): clear aggregate mechanical and test lint
 findings

---
 .github/workflows/quality-gates.yml           |   2 +
 crates/inference/src/resource_monitor/mod.rs  |   2 +-
 .../src/tests.rs                              |  14 +-
 .../lib_tests/data_graph_execution_tests.rs   |   2 +-
 .../lib_tests/workflow_run_execution_tests.rs |   2 +-
 .../src/node_io_artifacts.rs                  | 134 +++++++++---------
 .../src/task_executor_tests/puma_lib.rs       |  42 +++---
 .../src/technical_fit.rs                      |   2 +-
 .../src/redistributables/paths.rs             |  44 +++---
 .../pantograph-scheduler/tests/queue_state.rs |   4 +-
 .../src/graph/connection_insert.rs            |  56 ++++----
 .../src/graph/registry.rs                     |   2 +-
 .../runtime_branch_batch_execution.rs         |   2 +-
 .../src/workflow/service_config.rs            |  40 +++---
 .../src/workflow/tests/session_execution.rs   |   2 +-
 .../workflow/tests/task_result_contracts.rs   |   6 +-
 .../src/workflow/tests/workflow_version.rs    |   2 +-
 .../src/workflow/validation.rs                |  34 ++---
 .../tests/artifact_store.rs                   |   2 +-
 crates/workflow-nodes/src/input/puma_lib.rs   |   2 +-
 .../2026-10-03-aggregate-test-style.md        |  11 ++
 src-tauri/src/llm/commands/agent.rs           |  26 ++--
 src-tauri/src/workflow/diagnostics/overlay.rs |   2 +-
 .../tests/translation_projection.rs           |   2 +-
 src-tauri/src/workflow/puma_lib_commands.rs   |   2 +-
 25 files changed, 225 insertions(+), 214 deletions(-)
 create mode 100644 docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-aggregate-test-style.md

diff --git a/.github/workflows/quality-gates.yml b/.github/workflows/quality-gates.yml
index 50ee54cf5..5a4a6a76e 100644
--- a/.github/workflows/quality-gates.yml
+++ b/.github/workflows/quality-gates.yml
@@ -311,6 +311,8 @@ jobs:
           cargo test -p pantograph-workflow-service --lib scheduler::task_orchestrator::tests::
       - name: Run workflow-service contract tests
         run: cargo test -p pantograph-workflow-service --test contract
+      - name: Run artifact-store integration contracts
+        run: cargo test -p pantograph-workflow-service --test artifact_store
 
   rust-doc-tests:
     name: Rust doc tests
diff --git a/crates/inference/src/resource_monitor/mod.rs b/crates/inference/src/resource_monitor/mod.rs
index 2a9d64f49..8ad12f028 100644
--- a/crates/inference/src/resource_monitor/mod.rs
+++ b/crates/inference/src/resource_monitor/mod.rs
@@ -135,7 +135,7 @@ mod tests {
 
     #[test]
     fn unsupported_resource_monitor_returns_typed_unavailable_observation() {
-        let monitor = unsupported::UnsupportedRuntimeResourceMonitor::default();
+        let monitor = unsupported::UnsupportedRuntimeResourceMonitor;
         let guard = monitor
             .start_process_monitor(std::process::id())
             .expect("unsupported monitor starts");
diff --git a/crates/pantograph-diagnostics-ledger/src/tests.rs b/crates/pantograph-diagnostics-ledger/src/tests.rs
index 5d8ff19ba..8d28d95ba 100644
--- a/crates/pantograph-diagnostics-ledger/src/tests.rs
+++ b/crates/pantograph-diagnostics-ledger/src/tests.rs
@@ -1571,12 +1571,9 @@ fn scheduler_timeline_projection_includes_inference_execution_diagnostics() {
     assert!(detail.contains("cache handle observed"));
     assert!(detail.contains("artifact refs 1"));
     assert!(detail.contains("kv cache restore_input hit"));
-    assert_eq!(
-        record
-            .payload_json
-            .contains("generated text should not appear"),
-        false
-    );
+    assert!(!record
+        .payload_json
+        .contains("generated text should not appear"));
 }
 
 #[test]
@@ -5083,10 +5080,9 @@ fn sqlite_column_exists(conn: &Connection, table_name: &str, column_name: &str)
     let columns = stmt
         .query_map([], |row| row.get::<_, String>(1))
         .expect("table info query succeeds");
-    let exists = columns
+    columns
         .map(|column| column.expect("column row loads"))
-        .any(|column| column == column_name);
-    exists
+        .any(|column| column == column_name)
 }
 
 fn assert_columns_exist(conn: &Connection, table_name: &str, column_names: &[&str]) {
diff --git a/crates/pantograph-embedded-runtime/src/lib_tests/data_graph_execution_tests.rs b/crates/pantograph-embedded-runtime/src/lib_tests/data_graph_execution_tests.rs
index f90e8a9bf..3c3c1b01e 100644
--- a/crates/pantograph-embedded-runtime/src/lib_tests/data_graph_execution_tests.rs
+++ b/crates/pantograph-embedded-runtime/src/lib_tests/data_graph_execution_tests.rs
@@ -41,7 +41,7 @@ async fn execute_data_graph_retired_onnx_audio_path_does_not_call_python_sidecar
         .await
         .expect("data graph execution");
 
-    assert!(outputs.get("audio").is_none());
+    assert!(!outputs.contains_key("audio"));
     assert_eq!(
         outputs.get("_graph_id"),
         Some(&serde_json::json!("runtime-onnx-audio-data-graph"))
diff --git a/crates/pantograph-embedded-runtime/src/lib_tests/workflow_run_execution_tests.rs b/crates/pantograph-embedded-runtime/src/lib_tests/workflow_run_execution_tests.rs
index 40fe05b9c..89336a945 100644
--- a/crates/pantograph-embedded-runtime/src/lib_tests/workflow_run_execution_tests.rs
+++ b/crates/pantograph-embedded-runtime/src/lib_tests/workflow_run_execution_tests.rs
@@ -450,7 +450,7 @@ fn image_runtime_validation_snapshot(
         workflow_semantic_version: version.semantic_version.clone(),
         workflow_execution_fingerprint: version.execution_fingerprint.clone(),
         descriptor_contract_version: INFERENCE_INTERFACE_CONTRACT_VERSION,
-        graph_revision: WorkflowGraphRevision::parse(&graph.compute_fingerprint())
+        graph_revision: WorkflowGraphRevision::parse(graph.compute_fingerprint())
             .expect("valid graph revision"),
         validation_session_id: DraftGraphValidationSessionId::parse(
             "embedded_runtime_validation_session_1",
diff --git a/crates/pantograph-embedded-runtime/src/node_io_artifacts.rs b/crates/pantograph-embedded-runtime/src/node_io_artifacts.rs
index d7ddc31e1..d5034f075 100644
--- a/crates/pantograph-embedded-runtime/src/node_io_artifacts.rs
+++ b/crates/pantograph-embedded-runtime/src/node_io_artifacts.rs
@@ -304,6 +304,73 @@ fn node_io_artifact_format_metadata(media_type: &str) -> ArtifactFormatMetadata
     }
 }
 
+fn io_artifact_payload_kind(kind: ArtifactPayloadKind) -> IoArtifactPayloadKind {
+    match kind {
+        ArtifactPayloadKind::Text => IoArtifactPayloadKind::Text,
+        ArtifactPayloadKind::Image => IoArtifactPayloadKind::Image,
+        ArtifactPayloadKind::Audio => IoArtifactPayloadKind::Audio,
+        ArtifactPayloadKind::Video => IoArtifactPayloadKind::Video,
+        ArtifactPayloadKind::ThreeD => IoArtifactPayloadKind::ThreeD,
+        ArtifactPayloadKind::LargeTable => IoArtifactPayloadKind::LargeTable,
+        ArtifactPayloadKind::GenericBinary => IoArtifactPayloadKind::GenericBinary,
+        ArtifactPayloadKind::Structured => IoArtifactPayloadKind::Structured,
+    }
+}
+
+fn io_artifact_lifecycle_state(state: ArtifactLifecycleState) -> IoArtifactLifecycleState {
+    match state {
+        ArtifactLifecycleState::Declared => IoArtifactLifecycleState::Declared,
+        ArtifactLifecycleState::Writing => IoArtifactLifecycleState::Writing,
+        ArtifactLifecycleState::Streaming => IoArtifactLifecycleState::Streaming,
+        ArtifactLifecycleState::Finalizing => IoArtifactLifecycleState::Finalizing,
+        ArtifactLifecycleState::Retained => IoArtifactLifecycleState::Retained,
+        ArtifactLifecycleState::Failed => IoArtifactLifecycleState::Failed,
+        ArtifactLifecycleState::Expired => IoArtifactLifecycleState::Expired,
+        ArtifactLifecycleState::Deleted => IoArtifactLifecycleState::Deleted,
+    }
+}
+
+fn io_artifact_access_mode(mode: ArtifactAccessMode) -> IoArtifactAccessMode {
+    match mode {
+        ArtifactAccessMode::Read => IoArtifactAccessMode::Read,
+        ArtifactAccessMode::Download => IoArtifactAccessMode::Download,
+        ArtifactAccessMode::Stream => IoArtifactAccessMode::Stream,
+    }
+}
+
+fn io_artifact_format_metadata(format: ArtifactFormatMetadata) -> IoArtifactFormatMetadata {
+    IoArtifactFormatMetadata {
+        format_id: format.format_id,
+        media_type: format.media_type,
+        codec_id: format.codec_id,
+        quality_percent: format.quality_percent,
+        bitrate_kbps: format.bitrate_kbps,
+        crf: format.crf,
+        bit_depth: format.bit_depth,
+        color_profile_id: format.color_profile_id,
+        converter_id: format.converter_id,
+        converter_version: format.converter_version,
+        library_version: format.library_version,
+        conversion_id: format.conversion_id,
+        conversion_status: format.conversion_status.map(|status| match status {
+            ArtifactConversionStatus::Converted => IoArtifactConversionStatus::Converted,
+            ArtifactConversionStatus::PassedThrough => IoArtifactConversionStatus::PassedThrough,
+            ArtifactConversionStatus::Failed => IoArtifactConversionStatus::Failed,
+        }),
+        conversion_command_id: format.conversion_command_id,
+        conversion_dependencies: format
+            .conversion_dependencies
+            .into_iter()
+            .map(|dependency| IoArtifactConversionDependency {
+                dependency_id: dependency.dependency_id,
+                active_version: dependency.active_version,
+                lease_id: dependency.lease_id,
+                lease_holder: dependency.lease_holder,
+            })
+            .collect(),
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::{
@@ -394,70 +461,3 @@ mod tests {
         );
     }
 }
-
-fn io_artifact_payload_kind(kind: ArtifactPayloadKind) -> IoArtifactPayloadKind {
-    match kind {
-        ArtifactPayloadKind::Text => IoArtifactPayloadKind::Text,
-        ArtifactPayloadKind::Image => IoArtifactPayloadKind::Image,
-        ArtifactPayloadKind::Audio => IoArtifactPayloadKind::Audio,
-        ArtifactPayloadKind::Video => IoArtifactPayloadKind::Video,
-        ArtifactPayloadKind::ThreeD => IoArtifactPayloadKind::ThreeD,
-        ArtifactPayloadKind::LargeTable => IoArtifactPayloadKind::LargeTable,
-        ArtifactPayloadKind::GenericBinary => IoArtifactPayloadKind::GenericBinary,
-        ArtifactPayloadKind::Structured => IoArtifactPayloadKind::Structured,
-    }
-}
-
-fn io_artifact_lifecycle_state(state: ArtifactLifecycleState) -> IoArtifactLifecycleState {
-    match state {
-        ArtifactLifecycleState::Declared => IoArtifactLifecycleState::Declared,
-        ArtifactLifecycleState::Writing => IoArtifactLifecycleState::Writing,
-        ArtifactLifecycleState::Streaming => IoArtifactLifecycleState::Streaming,
-        ArtifactLifecycleState::Finalizing => IoArtifactLifecycleState::Finalizing,
-        ArtifactLifecycleState::Retained => IoArtifactLifecycleState::Retained,
-        ArtifactLifecycleState::Failed => IoArtifactLifecycleState::Failed,
-        ArtifactLifecycleState::Expired => IoArtifactLifecycleState::Expired,
-        ArtifactLifecycleState::Deleted => IoArtifactLifecycleState::Deleted,
-    }
-}
-
-fn io_artifact_access_mode(mode: ArtifactAccessMode) -> IoArtifactAccessMode {
-    match mode {
-        ArtifactAccessMode::Read => IoArtifactAccessMode::Read,
-        ArtifactAccessMode::Download => IoArtifactAccessMode::Download,
-        ArtifactAccessMode::Stream => IoArtifactAccessMode::Stream,
-    }
-}
-
-fn io_artifact_format_metadata(format: ArtifactFormatMetadata) -> IoArtifactFormatMetadata {
-    IoArtifactFormatMetadata {
-        format_id: format.format_id,
-        media_type: format.media_type,
-        codec_id: format.codec_id,
-        quality_percent: format.quality_percent,
-        bitrate_kbps: format.bitrate_kbps,
-        crf: format.crf,
-        bit_depth: format.bit_depth,
-        color_profile_id: format.color_profile_id,
-        converter_id: format.converter_id,
-        converter_version: format.converter_version,
-        library_version: format.library_version,
-        conversion_id: format.conversion_id,
-        conversion_status: format.conversion_status.map(|status| match status {
-            ArtifactConversionStatus::Converted => IoArtifactConversionStatus::Converted,
-            ArtifactConversionStatus::PassedThrough => IoArtifactConversionStatus::PassedThrough,
-            ArtifactConversionStatus::Failed => IoArtifactConversionStatus::Failed,
-        }),
-        conversion_command_id: format.conversion_command_id,
-        conversion_dependencies: format
-            .conversion_dependencies
-            .into_iter()
-            .map(|dependency| IoArtifactConversionDependency {
-                dependency_id: dependency.dependency_id,
-                active_version: dependency.active_version,
-                lease_id: dependency.lease_id,
-                lease_holder: dependency.lease_holder,
-            })
-            .collect(),
-    }
-}
diff --git a/crates/pantograph-embedded-runtime/src/task_executor_tests/puma_lib.rs b/crates/pantograph-embedded-runtime/src/task_executor_tests/puma_lib.rs
index bce51235e..479ee3fa7 100644
--- a/crates/pantograph-embedded-runtime/src/task_executor_tests/puma_lib.rs
+++ b/crates/pantograph-embedded-runtime/src/task_executor_tests/puma_lib.rs
@@ -49,7 +49,7 @@ async fn puma_lib_execution_hydrates_model_ref_from_model_id_without_path_output
         .expect("puma-lib should resolve selector metadata");
 
     assert!(
-        outputs.get("model_path").is_none(),
+        !outputs.contains_key("model_path"),
         "puma-lib must not emit executable path outputs"
     );
     assert_eq!(
@@ -74,15 +74,15 @@ async fn puma_lib_execution_hydrates_model_ref_from_model_id_without_path_output
         "puma-lib must not hide executable paths inside pumas_model_ref"
     );
     assert!(
-        outputs.get("backend_key").is_none(),
+        !outputs.contains_key("backend_key"),
         "puma-lib must not emit graph-visible backend-key aliases"
     );
     assert!(
-        outputs.get("resolved_model_package_facts").is_none(),
+        !outputs.contains_key("resolved_model_package_facts"),
         "puma-lib must not emit hidden package facts"
     );
     assert!(
-        outputs.get("resolved_model_artifact_load_target").is_none(),
+        !outputs.contains_key("resolved_model_artifact_load_target"),
         "puma-lib must not emit hidden artifact load targets"
     );
 }
@@ -117,7 +117,7 @@ async fn puma_lib_execution_preserves_explicit_model_ref_without_model_path_outp
         .expect("puma-lib should preserve explicit model ref");
 
     assert!(
-        outputs.get("model_path").is_none(),
+        !outputs.contains_key("model_path"),
         "puma-lib must not promote selected artifact paths to executable outputs"
     );
     assert_eq!(
@@ -133,7 +133,7 @@ async fn puma_lib_execution_preserves_explicit_model_ref_without_model_path_outp
         ))
     );
     assert!(
-        outputs.get("backend_key").is_none(),
+        !outputs.contains_key("backend_key"),
         "puma-lib must not emit graph-visible backend-key aliases"
     );
 }
@@ -182,7 +182,7 @@ async fn puma_lib_execution_does_not_rebind_model_id_from_raw_pumas_api() {
         .expect("puma-lib should preserve saved data without selector access");
 
     assert!(
-        outputs.get("model_path").is_none(),
+        !outputs.contains_key("model_path"),
         "raw saved paths must not be emitted as executable outputs"
     );
     assert_eq!(
@@ -196,15 +196,15 @@ async fn puma_lib_execution_does_not_rebind_model_id_from_raw_pumas_api() {
         Some(&serde_json::json!(model_id))
     );
     assert!(
-        outputs.get("backend_key").is_none(),
+        !outputs.contains_key("backend_key"),
         "puma-lib must not emit graph-visible backend-key aliases"
     );
     assert!(
-        outputs.get("resolved_model_package_facts").is_none(),
+        !outputs.contains_key("resolved_model_package_facts"),
         "raw PUMAS_API alone must not rehydrate selected model facts"
     );
     assert!(
-        outputs.get("resolved_model_artifact_load_target").is_none(),
+        !outputs.contains_key("resolved_model_artifact_load_target"),
         "raw PUMAS_API alone must not rehydrate selected artifact load targets"
     );
 }
@@ -260,7 +260,7 @@ async fn puma_lib_execution_hydrates_model_ref_from_selector_access_without_puma
         .expect("puma-lib should resolve selector metadata from selector access");
 
     assert!(
-        outputs.get("model_path").is_none(),
+        !outputs.contains_key("model_path"),
         "puma-lib must not emit executable path outputs"
     );
     assert_eq!(outputs.get("model_id"), Some(&serde_json::json!(model_id)));
@@ -282,11 +282,11 @@ async fn puma_lib_execution_hydrates_model_ref_from_selector_access_without_puma
         "puma-lib must not hide executable paths inside pumas_model_ref"
     );
     assert!(
-        outputs.get("resolved_model_package_facts").is_none(),
+        !outputs.contains_key("resolved_model_package_facts"),
         "read-only selector rows must not be promoted to full package facts"
     );
     assert!(
-        outputs.get("resolved_model_artifact_load_target").is_none(),
+        !outputs.contains_key("resolved_model_artifact_load_target"),
         "read-only selector rows must not be promoted to artifact load targets"
     );
 }
@@ -349,7 +349,7 @@ async fn puma_lib_execution_does_not_emit_inference_settings_from_saved_or_selec
         .expect("puma-lib should resolve selected detail without inference settings");
 
     assert!(
-        outputs.get("inference_settings").is_none(),
+        !outputs.contains_key("inference_settings"),
         "puma-lib must not emit inference settings from saved node data or selected detail"
     );
 }
@@ -396,13 +396,13 @@ async fn puma_lib_execution_does_not_resolve_saved_model_name_without_model_id()
         .expect("puma-lib should execute with saved data only");
 
     assert!(
-        outputs.get("model_path").is_none(),
+        !outputs.contains_key("model_path"),
         "puma-lib must not emit empty path outputs"
     );
-    assert!(outputs.get("model_id").is_none());
-    assert!(outputs.get("model_type").is_none());
-    assert!(outputs.get("task_type_primary").is_none());
-    assert!(outputs.get("resolved_model_package_facts").is_none());
-    assert!(outputs.get("resolved_model_artifact_load_target").is_none());
-    assert!(outputs.get("pumas_model_ref").is_none());
+    assert!(!outputs.contains_key("model_id"));
+    assert!(!outputs.contains_key("model_type"));
+    assert!(!outputs.contains_key("task_type_primary"));
+    assert!(!outputs.contains_key("resolved_model_package_facts"));
+    assert!(!outputs.contains_key("resolved_model_artifact_load_target"));
+    assert!(!outputs.contains_key("pumas_model_ref"));
 }
diff --git a/crates/pantograph-embedded-runtime/src/technical_fit.rs b/crates/pantograph-embedded-runtime/src/technical_fit.rs
index 74d1534e3..5cf66eef6 100644
--- a/crates/pantograph-embedded-runtime/src/technical_fit.rs
+++ b/crates/pantograph-embedded-runtime/src/technical_fit.rs
@@ -1837,7 +1837,7 @@ mod tests {
                         kv_cache: inference::BackendFeatureSupport::Supported,
                     },
                     runtime_variants: vec![inference::RuntimeVariantCapability {
-                        runtime_variant_id: inference::RuntimeVariantId::parse(&format!(
+                        runtime_variant_id: inference::RuntimeVariantId::parse(format!(
                             "{}.cuda",
                             backend_key
                         ))
diff --git a/crates/pantograph-managed-dependencies/src/redistributables/paths.rs b/crates/pantograph-managed-dependencies/src/redistributables/paths.rs
index 8e89e663a..7f623f046 100644
--- a/crates/pantograph-managed-dependencies/src/redistributables/paths.rs
+++ b/crates/pantograph-managed-dependencies/src/redistributables/paths.rs
@@ -121,6 +121,28 @@ pub(crate) fn library_path(name: &str) -> String {
     }
 }
 
+pub(crate) fn sanitize_path_segment(segment: &str) -> String {
+    segment
+        .chars()
+        .map(|character| {
+            if character.is_ascii_alphanumeric() || matches!(character, '.' | '-' | '_') {
+                character
+            } else {
+                '_'
+            }
+        })
+        .collect()
+}
+
+pub(crate) fn current_unix_timestamp_ms() -> u64 {
+    use std::time::{SystemTime, UNIX_EPOCH};
+
+    SystemTime::now()
+        .duration_since(UNIX_EPOCH)
+        .unwrap_or_default()
+        .as_millis() as u64
+}
+
 #[cfg(test)]
 mod tests {
     use super::{managed_redistributable_version_dir, ManagedRedistributableId};
@@ -146,25 +168,3 @@ mod tests {
         );
     }
 }
-
-pub(crate) fn sanitize_path_segment(segment: &str) -> String {
-    segment
-        .chars()
-        .map(|character| {
-            if character.is_ascii_alphanumeric() || matches!(character, '.' | '-' | '_') {
-                character
-            } else {
-                '_'
-            }
-        })
-        .collect()
-}
-
-pub(crate) fn current_unix_timestamp_ms() -> u64 {
-    use std::time::{SystemTime, UNIX_EPOCH};
-
-    SystemTime::now()
-        .duration_since(UNIX_EPOCH)
-        .unwrap_or_default()
-        .as_millis() as u64
-}
diff --git a/crates/pantograph-scheduler/tests/queue_state.rs b/crates/pantograph-scheduler/tests/queue_state.rs
index 766d2814e..ec04122b3 100644
--- a/crates/pantograph-scheduler/tests/queue_state.rs
+++ b/crates/pantograph-scheduler/tests/queue_state.rs
@@ -34,8 +34,10 @@ fn boxed_runtime_intent_preserves_exact_json_and_borrowed_payload() {
 #[test]
 fn boxed_runtime_intent_retains_validation_and_task_correlation() {
     let valid = task_intent("run.001", "task.001");
-    ValidatedSchedulerTaskStateRecord::try_from(task_record_with_state(ready_state(valid.clone())))
+    let expected_record = task_record_with_state(ready_state(valid.clone()));
+    let validated = ValidatedSchedulerTaskStateRecord::try_from(expected_record.clone())
         .expect("valid runtime intent remains accepted");
+    assert_eq!(validated.as_ref(), &expected_record);
     let mut invalid = valid;
     invalid.contract_version = 0;
     let expected = invalid.validate().expect_err("invalid raw version");
diff --git a/crates/pantograph-workflow-service/src/graph/connection_insert.rs b/crates/pantograph-workflow-service/src/graph/connection_insert.rs
index e436aa8f5..bd49a8b39 100644
--- a/crates/pantograph-workflow-service/src/graph/connection_insert.rs
+++ b/crates/pantograph-workflow-service/src/graph/connection_insert.rs
@@ -383,6 +383,34 @@ pub fn rejected_insert_response(
     }
 }
 
+pub fn rejected_edge_insert_preview_response(
+    graph: &WorkflowGraph,
+    rejection: ConnectionRejection,
+) -> EdgeInsertionPreviewResponse {
+    EdgeInsertionPreviewResponse {
+        accepted: false,
+        graph_revision: graph.compute_fingerprint(),
+        bridge: None,
+        rejection: Some(rejection),
+    }
+}
+
+pub fn rejected_insert_on_edge_response(
+    graph: &WorkflowGraph,
+    rejection: ConnectionRejection,
+) -> InsertNodeOnEdgeResponse {
+    InsertNodeOnEdgeResponse {
+        accepted: false,
+        graph_revision: graph.compute_fingerprint(),
+        inserted_node_id: None,
+        bridge: None,
+        graph: Some(graph.clone()),
+        workflow_event: None,
+        workflow_execution_session_state: None,
+        rejection: Some(rejection),
+    }
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -419,31 +447,3 @@ mod tests {
         );
     }
 }
-
-pub fn rejected_edge_insert_preview_response(
-    graph: &WorkflowGraph,
-    rejection: ConnectionRejection,
-) -> EdgeInsertionPreviewResponse {
-    EdgeInsertionPreviewResponse {
-        accepted: false,
-        graph_revision: graph.compute_fingerprint(),
-        bridge: None,
-        rejection: Some(rejection),
-    }
-}
-
-pub fn rejected_insert_on_edge_response(
-    graph: &WorkflowGraph,
-    rejection: ConnectionRejection,
-) -> InsertNodeOnEdgeResponse {
-    InsertNodeOnEdgeResponse {
-        accepted: false,
-        graph_revision: graph.compute_fingerprint(),
-        inserted_node_id: None,
-        bridge: None,
-        graph: Some(graph.clone()),
-        workflow_event: None,
-        workflow_execution_session_state: None,
-        rejection: Some(rejection),
-    }
-}
diff --git a/crates/pantograph-workflow-service/src/graph/registry.rs b/crates/pantograph-workflow-service/src/graph/registry.rs
index 0850822d0..1828c6433 100644
--- a/crates/pantograph-workflow-service/src/graph/registry.rs
+++ b/crates/pantograph-workflow-service/src/graph/registry.rs
@@ -166,7 +166,7 @@ mod tests {
         let payload = &encoded["inference_payloads"][0];
         assert_eq!(payload["role"], serde_json::json!("diagnostics"));
         assert_eq!(payload["task_id"], serde_json::json!("text_generation"));
-        assert_llm_inference_payloads_do_not_expose_runtime_policy(&definition);
+        assert_llm_inference_payloads_do_not_expose_runtime_policy(definition);
     }
 
     #[test]
diff --git a/crates/pantograph-workflow-service/src/workflow/runtime_branch_batch_execution.rs b/crates/pantograph-workflow-service/src/workflow/runtime_branch_batch_execution.rs
index be0d264c8..b91ca792c 100644
--- a/crates/pantograph-workflow-service/src/workflow/runtime_branch_batch_execution.rs
+++ b/crates/pantograph-workflow-service/src/workflow/runtime_branch_batch_execution.rs
@@ -1981,7 +1981,7 @@ mod tests {
             plan.runtime_host_request
                 .members
                 .iter()
-                .map(|member| prompt_text_from_runtime_host_member_request(member))
+                .map(prompt_text_from_runtime_host_member_request)
                 .collect::>(),
             vec![
                 "prompt owned by run.2026-05-22.001".to_string(),
diff --git a/crates/pantograph-workflow-service/src/workflow/service_config.rs b/crates/pantograph-workflow-service/src/workflow/service_config.rs
index 1fe7892f8..7baeca144 100644
--- a/crates/pantograph-workflow-service/src/workflow/service_config.rs
+++ b/crates/pantograph-workflow-service/src/workflow/service_config.rs
@@ -439,6 +439,26 @@ fn dependency_requirements_registry_error(
     ))
 }
 
+fn load_artifact_format_settings(
+    path: &Path,
+) -> Result {
+    if !path.exists() {
+        return Ok(ArtifactFormatSettings::default());
+    }
+    let content = std::fs::read_to_string(path).map_err(|error| {
+        WorkflowServiceError::Internal(format!(
+            "failed to read artifact format settings {:?}: {error}",
+            path
+        ))
+    })?;
+    serde_json::from_str(&content).map_err(|error| {
+        WorkflowServiceError::InvalidRequest(format!(
+            "artifact format settings file {:?} is invalid: {error}",
+            path
+        ))
+    })
+}
+
 #[cfg(test)]
 mod tests {
     use std::sync::{Arc, Mutex};
@@ -594,23 +614,3 @@ mod tests {
         }
     }
 }
-
-fn load_artifact_format_settings(
-    path: &Path,
-) -> Result {
-    if !path.exists() {
-        return Ok(ArtifactFormatSettings::default());
-    }
-    let content = std::fs::read_to_string(path).map_err(|error| {
-        WorkflowServiceError::Internal(format!(
-            "failed to read artifact format settings {:?}: {error}",
-            path
-        ))
-    })?;
-    serde_json::from_str(&content).map_err(|error| {
-        WorkflowServiceError::InvalidRequest(format!(
-            "artifact format settings file {:?} is invalid: {error}",
-            path
-        ))
-    })
-}
diff --git a/crates/pantograph-workflow-service/src/workflow/tests/session_execution.rs b/crates/pantograph-workflow-service/src/workflow/tests/session_execution.rs
index fa52cdfee..311f1a1cd 100644
--- a/crates/pantograph-workflow-service/src/workflow/tests/session_execution.rs
+++ b/crates/pantograph-workflow-service/src/workflow/tests/session_execution.rs
@@ -4801,7 +4801,7 @@ fn runtime_executable_validation_snapshot(
         workflow_semantic_version: version.semantic_version.clone(),
         workflow_execution_fingerprint: version.execution_fingerprint.clone(),
         descriptor_contract_version: INFERENCE_INTERFACE_CONTRACT_VERSION,
-        graph_revision: WorkflowGraphRevision::parse(&graph.compute_fingerprint())
+        graph_revision: WorkflowGraphRevision::parse(graph.compute_fingerprint())
             .expect("valid graph revision"),
         validation_session_id: DraftGraphValidationSessionId::parse("runtime_validation_session_1")
             .expect("valid validation session id"),
diff --git a/crates/pantograph-workflow-service/src/workflow/tests/task_result_contracts.rs b/crates/pantograph-workflow-service/src/workflow/tests/task_result_contracts.rs
index fd3b1e8be..bea71a2ee 100644
--- a/crates/pantograph-workflow-service/src/workflow/tests/task_result_contracts.rs
+++ b/crates/pantograph-workflow-service/src/workflow/tests/task_result_contracts.rs
@@ -61,9 +61,9 @@ fn scheduler_task_result_validates_path_free_typed_outputs() {
         encoded["outputs"][1]["value"]["value"]["artifact_id"],
         "artifact-image-1"
     );
-    assert_eq!(encoded.to_string().contains("model_path"), false);
-    assert_eq!(encoded.to_string().contains("local_load_path"), false);
-    assert_eq!(encoded.to_string().contains("runtime_handoff"), false);
+    assert!(!encoded.to_string().contains("model_path"));
+    assert!(!encoded.to_string().contains("local_load_path"));
+    assert!(!encoded.to_string().contains("runtime_handoff"));
 }
 
 #[test]
diff --git a/crates/pantograph-workflow-service/src/workflow/tests/workflow_version.rs b/crates/pantograph-workflow-service/src/workflow/tests/workflow_version.rs
index 762ff0cb0..79bbcb226 100644
--- a/crates/pantograph-workflow-service/src/workflow/tests/workflow_version.rs
+++ b/crates/pantograph-workflow-service/src/workflow/tests/workflow_version.rs
@@ -423,7 +423,7 @@ fn executable_validation_publication(
     graph: &WorkflowGraph,
 ) -> WorkflowGraphInferenceValidationPublication {
     let graph_revision =
-        WorkflowGraphRevision::parse(&graph.compute_fingerprint()).expect("valid graph revision");
+        WorkflowGraphRevision::parse(graph.compute_fingerprint()).expect("valid graph revision");
     let validation_session_id = DraftGraphValidationSessionId::parse("validation_session_publish")
         .expect("valid validation session id");
     let summary = DraftGraphValidationSummary {
diff --git a/crates/pantograph-workflow-service/src/workflow/validation.rs b/crates/pantograph-workflow-service/src/workflow/validation.rs
index 7ba8508a9..d0588c9e8 100644
--- a/crates/pantograph-workflow-service/src/workflow/validation.rs
+++ b/crates/pantograph-workflow-service/src/workflow/validation.rs
@@ -154,23 +154,6 @@ pub(super) fn validate_bindings(
     Ok(())
 }
 
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn stale_graph_remaining_count_rejects_formatter_underflow() {
-        let error = remaining_stale_graph_diagnostic_count(1, 2)
-            .expect_err("shown diagnostics cannot exceed total diagnostics");
-
-        assert!(matches!(
-            error,
-            WorkflowServiceError::Internal(message)
-                if message.contains("formatter showed 2 reasons for 1 diagnostics")
-        ));
-    }
-}
-
 pub(super) fn validate_host_output_bindings(
     bindings: &[WorkflowPortBinding],
     field_name: &str,
@@ -340,3 +323,20 @@ pub(super) fn validate_payload_size(
 
     Ok(())
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn stale_graph_remaining_count_rejects_formatter_underflow() {
+        let error = remaining_stale_graph_diagnostic_count(1, 2)
+            .expect_err("shown diagnostics cannot exceed total diagnostics");
+
+        assert!(matches!(
+            error,
+            WorkflowServiceError::Internal(message)
+                if message.contains("formatter showed 2 reasons for 1 diagnostics")
+        ));
+    }
+}
diff --git a/crates/pantograph-workflow-service/tests/artifact_store.rs b/crates/pantograph-workflow-service/tests/artifact_store.rs
index 2178eea24..236531ae3 100644
--- a/crates/pantograph-workflow-service/tests/artifact_store.rs
+++ b/crates/pantograph-workflow-service/tests/artifact_store.rs
@@ -83,7 +83,7 @@ fn artifact_store_writes_descriptor_and_reads_body_without_path_leak() {
     );
     assert!(!serde_json::to_string(&descriptor)
         .expect("serialize descriptor")
-        .contains(temp.path().to_string_lossy().as_ref()));
+        .contains::<&str>(temp.path().to_string_lossy().as_ref()));
 
     let read = store
         .read_body(ArtifactReadRequest {
diff --git a/crates/workflow-nodes/src/input/puma_lib.rs b/crates/workflow-nodes/src/input/puma_lib.rs
index 6c6680cdb..2d7c09e86 100644
--- a/crates/workflow-nodes/src/input/puma_lib.rs
+++ b/crates/workflow-nodes/src/input/puma_lib.rs
@@ -271,7 +271,7 @@ mod options_provider {
         }
 
         pub(crate) fn needs_resolution(&self, model_id: &str) -> bool {
-            self.summaries.get(model_id).map_or(true, |result| {
+            self.summaries.get(model_id).is_none_or(|result| {
                 result.summary.is_none()
                     || matches!(
                         result.status,
diff --git a/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-aggregate-test-style.md b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-aggregate-test-style.md
new file mode 100644
index 000000000..741986314
--- /dev/null
+++ b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-aggregate-test-style.md
@@ -0,0 +1,11 @@
+# Aggregate mechanical and test qualification repair
+
+The first keep-going strict audit at 9a46796 emitted 51 diagnostics at 50 unique source sites across eight crate/desktop groups. Four desktop command/delegate arity sites are excluded for a wire-compatible design. This milestone addresses the remaining 46 sites without changing public signatures, data contracts, errors or validation policy.
+
+Exact semantic groups: 23 absent-key assertions become !contains_key (a present null value remains present); six redundant borrows are removed; four literal-false comparisons become negated assertions; one unit Default becomes its unit value; one Option::map_or(true, predicate) becomes is_none_or with the same predicate; one redundant closure becomes its function; one iterator result returns directly; one successful must-use scheduler validation now compares the complete validated raw record; and one desktop base64-length divisibility check uses is_multiple_of(4). The artifact-store path-redaction assertion explicitly selects the &str Pattern type, resolving the all-feature Cow AsRef ambiguity caused by typed_path without allocation or weakening the assertion.
+
+Six existing cfg(test) modules move after production items in node_io_artifacts, connection_insert, service_config, validation, redistributables/paths and desktop llm/commands/agent. Each moved test-module body is byte-identical after formatting; a Rust lexer comparison verifies all production tokens (including literals and comments) are identical before/after these moves. Production item ordering is retained.
+
+The required focused workflow retains all existing suites and adds the complete artifact_store integration target (13 source tests), so the compile-blocked redaction/storage contract now executes. Existing embedded, workflow-service, scheduler, ledger, inference and workflow-node suites remain. Desktop/test-only source changes still require actual hosted strict all-target compilation; no blanket lint allowance or assertion removal is introduced.
+
+Root requested this grouped mechanical milestone from the complete diagnostic inventory. Root source review accepted frozen tree 29cb5ce693a0b6a091a807d3cfa3b203a60468d3 after reviewing the non-move changes and independently checking identical nonblank line multisets for all six module moves. Exact-head hosted execution remains pending. Full pinned cargo fmt is checked locally; no heavy local Rust build is performed. The separate four Tauri arity findings remain open and must preserve existing IPC wire keys.
diff --git a/src-tauri/src/llm/commands/agent.rs b/src-tauri/src/llm/commands/agent.rs
index 889879154..65c9605da 100644
--- a/src-tauri/src/llm/commands/agent.rs
+++ b/src-tauri/src/llm/commands/agent.rs
@@ -508,19 +508,6 @@ fn format_agent_prompt_with_analysis(
     prompt
 }
 
-#[cfg(test)]
-mod tests {
-    use super::drawing_vision_contract_only_error;
-
-    #[test]
-    fn drawing_vision_error_points_to_typed_inference_contracts() {
-        let error = drawing_vision_contract_only_error();
-
-        assert!(error.contains("image_understanding"));
-        assert!(error.contains("canonical typed inference contracts"));
-    }
-}
-
 /// Format the agent prompt for fix/repair mode.
 /// Skips vision analysis, includes error context and file content directly.
 fn format_fix_mode_prompt(request: &AgentRequest) -> String {
@@ -588,3 +575,16 @@ fn create_component_updates(
         })
         .collect()
 }
+
+#[cfg(test)]
+mod tests {
+    use super::drawing_vision_contract_only_error;
+
+    #[test]
+    fn drawing_vision_error_points_to_typed_inference_contracts() {
+        let error = drawing_vision_contract_only_error();
+
+        assert!(error.contains("image_understanding"));
+        assert!(error.contains("canonical typed inference contracts"));
+    }
+}
diff --git a/src-tauri/src/workflow/diagnostics/overlay.rs b/src-tauri/src/workflow/diagnostics/overlay.rs
index 369eeee1c..f576c236d 100644
--- a/src-tauri/src/workflow/diagnostics/overlay.rs
+++ b/src-tauri/src/workflow/diagnostics/overlay.rs
@@ -434,7 +434,7 @@ fn is_inline_media_string_key(key: &str, value: &str) -> bool {
 fn is_probably_base64_body(value: &str) -> bool {
     let value = value.trim();
     value.len() >= 128
-        && value.len() % 4 == 0
+        && value.len().is_multiple_of(4)
         && value
             .bytes()
             .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'+' | b'/' | b'='))
diff --git a/src-tauri/src/workflow/event_adapter/tests/translation_projection.rs b/src-tauri/src/workflow/event_adapter/tests/translation_projection.rs
index 719fe35ea..5c7403afe 100644
--- a/src-tauri/src/workflow/event_adapter/tests/translation_projection.rs
+++ b/src-tauri/src/workflow/event_adapter/tests/translation_projection.rs
@@ -143,7 +143,7 @@ fn translated_task_inputs_resolved_event_preserves_inputs_without_trace_noise()
         TauriWorkflowEvent::DiagnosticsSnapshot { snapshot, .. } => {
             let trace = snapshot.runs_by_id.get("exec-1").expect("trace");
             assert_eq!(trace.event_count, 1);
-            assert!(trace.nodes.get("node-a").is_none());
+            assert!(!trace.nodes.contains_key("node-a"));
         }
         other => panic!("unexpected diagnostics event: {other:?}"),
     }
diff --git a/src-tauri/src/workflow/puma_lib_commands.rs b/src-tauri/src/workflow/puma_lib_commands.rs
index 3b670f181..ca8498756 100644
--- a/src-tauri/src/workflow/puma_lib_commands.rs
+++ b/src-tauri/src/workflow/puma_lib_commands.rs
@@ -160,7 +160,7 @@ pub async fn resolve_model_package_facts_summary(
         let ext = extensions.read().await;
         pumas_update_feed_access_from_extensions(&ext)
     };
-    resolve_model_package_facts_summary_from_access(selector_access, &model_id).await
+    resolve_model_package_facts_summary_from_access(selector_access, model_id).await
 }
 
 pub async fn list_model_library_updates_since(

From e47ddc85e42e0d3e65888e3fbee03e7dea3b2c4c Mon Sep 17 00:00:00 2001
From: Puma 
Date: Sat, 3 Oct 2026 12:50:21 -0700
Subject: [PATCH 15/21] refactor(desktop): bundle injected Tauri command state

---
 .github/workflows/quality-gates.yml           |   3 +
 .../reports/2026-10-03-tauri-command-state.md |  21 ++
 scripts/check-tauri-command-state-tests.sh    |  21 ++
 src-tauri/Cargo.toml                          |   1 +
 src-tauri/src/workflow/command_state.rs       |  66 +++++
 src-tauri/src/workflow/command_state_tests.rs | 230 ++++++++++++++++++
 src-tauri/src/workflow/commands.rs            |  24 +-
 .../workflow/headless_workflow_commands.rs    |  18 +-
 src-tauri/src/workflow/mod.rs                 |   1 +
 .../workflow/workflow_port_query_commands.rs  |  12 +-
 10 files changed, 368 insertions(+), 29 deletions(-)
 create mode 100644 docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-tauri-command-state.md
 create mode 100644 scripts/check-tauri-command-state-tests.sh
 create mode 100644 src-tauri/src/workflow/command_state.rs
 create mode 100644 src-tauri/src/workflow/command_state_tests.rs

diff --git a/.github/workflows/quality-gates.yml b/.github/workflows/quality-gates.yml
index 5a4a6a76e..e81a5ab8e 100644
--- a/.github/workflows/quality-gates.yml
+++ b/.github/workflows/quality-gates.yml
@@ -156,6 +156,9 @@ jobs:
       - name: Check workspace with all features
         run: cargo check --workspace --all-features
 
+      - name: Verify Tauri injected-state IPC contracts
+        run: bash scripts/check-tauri-command-state-tests.sh
+
   rust-tests:
     name: Rust focused tests
     runs-on: ubuntu-latest
diff --git a/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-tauri-command-state.md b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-tauri-command-state.md
new file mode 100644
index 000000000..b4b75606d
--- /dev/null
+++ b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-tauri-command-state.md
@@ -0,0 +1,21 @@
+# Tauri injected-state bundles with unchanged IPC payloads
+
+The keep-going strict audit reports four nine-argument sites: workflow_run_execution_session and query_port_options command wrappers plus their two sole delegates. Bundle only framework-injected State arguments in two operation-specific CommandArg implementations. No new managed owner, serialized request, client field, default or service lookup policy is introduced.
+
+WorkflowRunCommandState preserves this exact extraction sequence and macro-generated keys: gateway, runtimeRegistry, extensions, ragManager, workflowService, diagnosticsStore. PortOptionsCommandState preserves registry, extensions, workflowService. Each extraction calls Tauri State::from_command with the original plugin, command name, InvokeMessage and ACL reference, substituting only the original field key. The bundles hold the same borrowed State values and do not implement Deserialize. Client-supplied state fields cannot replace application-managed values. Missing-state errors retain the framework's original key/name text and first-failure order.
+
+The two wrappers have one delegate caller each; both delegates destructure the corresponding bundle then execute byte-identical previous bodies. The execution command still receives request, AppHandle and channel in their original relative order. The query command still receives flat nodeType, portId, search, limit, offset and context. The three frontend invokers (WorkflowCommandService, portOptionsCache and pumaModelOptionsCache) are unchanged. This is an internal Rust parameter grouping, not a Tauri wire-key rename.
+
+Five tests use the official Tauri MockRuntime/IPC path. They verify all six/three managed Arc identities, exact missing-state order/keys, and the real query_port_options handler with full and minimal flat payloads, original optional defaults, provider callbacks and managed extension data. Deliberately supplied client state values are ignored. A dormant gateway's test ProcessSpawner panics if unexpectedly used; no model/process/database setup is part of state extraction. Session state extraction is exercised through a test command using the same production bundle; the real session delegate body is separately byte-compared unchanged, rather than claimed executed by that test.
+
+The existing Tauri dependency gains only its test-only test feature (empty upstream feature, same locked 2.9.5 package). Required hosted workspace verification adds a script requiring all five exact test names and an individual 1 passed / 0 failed / 0 ignored receipt for each, with backend-llamacpp and no default app features. Existing all-feature workspace compilation and strict all-target Clippy remain. Frontend invocation tests remain part of the existing full frontend suite.
+
+Source review is pending. The source checkout was restored after a filesystem reset, and pinned Rust/rustfmt are currently unavailable. Formatting, Rust compilation and native mock-IPC execution are unrun locally; no installation or heavy build is attempted under the recovery hold. Bash syntax and the unchanged-body comparison pass. Publication remains paused with the separate mechanical batch until explicit retry authorization and the serialized write window are available.
+
+## Exact pinned-source compatibility
+
+Cargo.lock pins Tauri 2.9.5 and tauri-macros 2.5.2. The official tauri-v2.9.5 tag resolves to f2e0405dc2d8bae949de6bb5e479bbf3cde401b6, whose two manifests match those versions. [CommandItem and CommandArg](https://github.com/tauri-apps/tauri/blob/f2e0405dc2d8bae949de6bb5e479bbf3cde401b6/crates/tauri/src/ipc/command.rs) expose exactly the fields and method used here. [State extraction](https://github.com/tauri-apps/tauri/blob/f2e0405dc2d8bae949de6bb5e479bbf3cde401b6/crates/tauri/src/state.rs) has the same lifetime/outlives bounds, state-map lookup and missing-state error as the delegated calls.
+
+The pinned [command wrapper](https://github.com/tauri-apps/tauri/blob/f2e0405dc2d8bae949de6bb5e479bbf3cde401b6/crates/tauri-macros/src/command/wrapper.rs) builds arguments in signature order, defaults keys to camelCase, and calls CommandArg with the original plugin/name/message/ACL. Its async wrapper propagates the first argument error with ?, while its blocking wrapper reports the first match error. Grouping adjacent state arguments preserves this sequence inside the bundle.
+
+The pinned [mock IPC helpers](https://github.com/tauri-apps/tauri/blob/f2e0405dc2d8bae949de6bb5e479bbf3cde401b6/crates/tauri/src/test/mod.rs), InvokeRequest fields and InvokeBody::Json match the test fixture. The test module is enabled by the empty test feature; no package upgrade or additional dependency package is required. These are source compatibility checks, not compiled macro expansion or executed test evidence.
diff --git a/scripts/check-tauri-command-state-tests.sh b/scripts/check-tauri-command-state-tests.sh
new file mode 100644
index 000000000..458965b57
--- /dev/null
+++ b/scripts/check-tauri-command-state-tests.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+log_dir="${RUNNER_TEMP:-target}/tauri-command-state-tests"
+mkdir -p "$log_dir"
+cargo_args=(test -p pantograph --bin pantograph --no-default-features --features backend-llamacpp)
+tests=(
+  workflow::command_state::tests::run_bundle_preserves_managed_identity_without_payload_state
+  workflow::command_state::tests::run_bundle_preserves_missing_state_order_and_original_camel_case_keys
+  workflow::command_state::tests::query_bundle_preserves_managed_identity_without_payload_state
+  workflow::command_state::tests::query_command_preserves_missing_state_order_and_original_keys
+  workflow::command_state::tests::query_command_preserves_flat_required_optional_and_context_payloads
+)
+
+cargo "${cargo_args[@]}" workflow::command_state::tests:: -- --list > "$log_dir/tests.list"
+for test_name in "${tests[@]}"; do
+  python3 -c 'import pathlib, sys; lines = pathlib.Path(sys.argv[1]).read_text().splitlines(); sys.exit(0 if sys.argv[2] + ": test" in lines else 1)' "$log_dir/tests.list" "$test_name"
+  result_path="$log_dir/${test_name##*::}.log"
+  cargo "${cargo_args[@]}" "$test_name" -- --exact 2>&1 | tee "$result_path"
+  python3 -c 'import pathlib, re, sys; text = pathlib.Path(sys.argv[1]).read_text(); sys.exit(0 if re.search(r"^test result: ok\. 1 passed; 0 failed; 0 ignored;", text, re.M) else 1)' "$result_path"
+done
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
index 929b6e50a..1107c59b9 100644
--- a/src-tauri/Cargo.toml
+++ b/src-tauri/Cargo.toml
@@ -96,4 +96,5 @@ axum = { workspace = true }
 tower-http = { workspace = true, optional = true }
 
 [dev-dependencies]
+tauri = { version = "2.9.0", features = ["test"] }
 tempfile.workspace = true
diff --git a/src-tauri/src/workflow/command_state.rs b/src-tauri/src/workflow/command_state.rs
new file mode 100644
index 000000000..572532809
--- /dev/null
+++ b/src-tauri/src/workflow/command_state.rs
@@ -0,0 +1,66 @@
+//! Operation-specific bundles of Tauri-injected state; no client payload fields.
+
+use tauri::ipc::{CommandArg, CommandItem, InvokeError};
+use tauri::{Runtime, State};
+
+use crate::agent::rag::SharedRagManager;
+use crate::llm::{SharedGateway, SharedRuntimeRegistry};
+use super::commands::{
+    SharedExtensions, SharedNodeRegistry, SharedWorkflowDiagnosticsStore, SharedWorkflowService,
+};
+
+pub struct WorkflowRunCommandState<'r> {
+    pub gateway: State<'r, SharedGateway>,
+    pub runtime_registry: State<'r, SharedRuntimeRegistry>,
+    pub extensions: State<'r, SharedExtensions>,
+    pub rag_manager: State<'r, SharedRagManager>,
+    pub workflow_service: State<'r, SharedWorkflowService>,
+    pub diagnostics_store: State<'r, SharedWorkflowDiagnosticsStore>,
+}
+
+impl<'r, 'de: 'r, R: Runtime> CommandArg<'de, R> for WorkflowRunCommandState<'r> {
+    fn from_command(command: CommandItem<'de, R>) -> Result {
+        // Match the original command's extraction order and macro-generated keys.
+        Ok(Self {
+            gateway: managed_state(&command, "gateway")?,
+            runtime_registry: managed_state(&command, "runtimeRegistry")?,
+            extensions: managed_state(&command, "extensions")?,
+            rag_manager: managed_state(&command, "ragManager")?,
+            workflow_service: managed_state(&command, "workflowService")?,
+            diagnostics_store: managed_state(&command, "diagnosticsStore")?,
+        })
+    }
+}
+
+pub struct PortOptionsCommandState<'r> {
+    pub registry: State<'r, SharedNodeRegistry>,
+    pub extensions: State<'r, SharedExtensions>,
+    pub workflow_service: State<'r, SharedWorkflowService>,
+}
+
+impl<'r, 'de: 'r, R: Runtime> CommandArg<'de, R> for PortOptionsCommandState<'r> {
+    fn from_command(command: CommandItem<'de, R>) -> Result {
+        Ok(Self {
+            registry: managed_state(&command, "registry")?,
+            extensions: managed_state(&command, "extensions")?,
+            workflow_service: managed_state(&command, "workflowService")?,
+        })
+    }
+}
+
+fn managed_state<'r, 'de: 'r, T: Send + Sync + 'static, R: Runtime>(
+    command: &CommandItem<'de, R>,
+    key: &'static str,
+) -> Result, InvokeError> {
+    State::from_command(CommandItem {
+        plugin: command.plugin,
+        name: command.name,
+        key,
+        message: command.message,
+        acl: command.acl,
+    })
+}
+
+#[cfg(test)]
+#[path = "command_state_tests.rs"]
+mod tests;
diff --git a/src-tauri/src/workflow/command_state_tests.rs b/src-tauri/src/workflow/command_state_tests.rs
new file mode 100644
index 000000000..3774d62c5
--- /dev/null
+++ b/src-tauri/src/workflow/command_state_tests.rs
@@ -0,0 +1,230 @@
+use std::path::PathBuf;
+use std::sync::atomic::{AtomicUsize, Ordering};
+use std::sync::Arc;
+
+use async_trait::async_trait;
+use serde_json::{json, Value};
+use tauri::test::{mock_builder, mock_context, noop_assets, MockRuntime};
+use tauri::{App, Builder, WebviewWindow, WebviewWindowBuilder};
+use tokio::sync::{mpsc, RwLock};
+
+use super::*;
+use crate::workflow::commands;
+
+struct NoSpawn;
+
+#[async_trait]
+impl inference::ProcessSpawner for NoSpawn {
+    async fn spawn_sidecar(
+        &self,
+        _: &str,
+        _: &[&str],
+    ) -> Result<(mpsc::Receiver, Box), String> {
+        panic!("state extraction must not spawn a backend")
+    }
+
+    fn app_data_dir(&self) -> Result {
+        panic!("state extraction must not resolve runtime paths")
+    }
+
+    fn binaries_dir(&self) -> Result {
+        panic!("state extraction must not resolve runtime paths")
+    }
+}
+
+struct ManagedStates {
+    gateway: SharedGateway,
+    runtime_registry: SharedRuntimeRegistry,
+    extensions: SharedExtensions,
+    rag_manager: SharedRagManager,
+    workflow_service: SharedWorkflowService,
+    diagnostics_store: SharedWorkflowDiagnosticsStore,
+    registry: SharedNodeRegistry,
+}
+
+impl ManagedStates {
+    fn new(registry: SharedNodeRegistry) -> Self {
+        let mut extensions = node_engine::ExecutorExtensions::new();
+        extensions.set("state-fixture-marker", "managed extension".to_string());
+        Self {
+            gateway: Arc::new(crate::llm::InferenceGateway::new(Arc::new(NoSpawn))),
+            runtime_registry: Arc::new(pantograph_runtime_registry::RuntimeRegistry::new()),
+            extensions: Arc::new(RwLock::new(extensions)),
+            rag_manager: crate::agent::rag::create_rag_manager(PathBuf::from("unused-state-fixture")),
+            workflow_service: Arc::new(pantograph_workflow_service::WorkflowService::new()),
+            diagnostics_store: Arc::new(crate::workflow::WorkflowDiagnosticsStore::default()),
+            registry,
+        }
+    }
+
+    fn run_builder(&self, prefix: usize) -> Builder {
+        let mut builder = mock_builder();
+        if prefix > 0 {
+            builder = builder.manage(self.gateway.clone());
+        }
+        if prefix > 1 {
+            builder = builder.manage(self.runtime_registry.clone());
+        }
+        if prefix > 2 {
+            builder = builder.manage(self.extensions.clone());
+        }
+        if prefix > 3 {
+            builder = builder.manage(self.rag_manager.clone());
+        }
+        if prefix > 4 {
+            builder = builder.manage(self.workflow_service.clone());
+        }
+        if prefix > 5 {
+            builder = builder.manage(self.diagnostics_store.clone());
+        }
+        builder
+    }
+
+    fn query_builder(&self, prefix: usize) -> Builder {
+        let mut builder = mock_builder();
+        if prefix > 0 {
+            builder = builder.manage(self.registry.clone());
+        }
+        if prefix > 1 {
+            builder = builder.manage(self.extensions.clone());
+        }
+        if prefix > 2 {
+            builder = builder.manage(self.workflow_service.clone());
+        }
+        builder
+    }
+}
+
+fn identity(value: &Arc) -> String {
+    format!("{:p}", Arc::as_ptr(value))
+}
+
+#[tauri::command]
+fn inspect_run_state(state: WorkflowRunCommandState<'_>) -> Vec {
+    vec![
+        identity(state.gateway.inner()),
+        identity(state.runtime_registry.inner()),
+        identity(state.extensions.inner()),
+        identity(state.rag_manager.inner()),
+        identity(state.workflow_service.inner()),
+        identity(state.diagnostics_store.inner()),
+    ]
+}
+
+#[tauri::command]
+fn inspect_query_state(state: PortOptionsCommandState<'_>) -> Vec {
+    vec![
+        identity(state.registry.inner()),
+        identity(state.extensions.inner()),
+        identity(state.workflow_service.inner()),
+    ]
+}
+
+fn build(builder: Builder) -> (App, WebviewWindow) {
+    let app = builder
+        .invoke_handler(tauri::generate_handler![
+            inspect_run_state,
+            inspect_query_state,
+            commands::query_port_options,
+        ])
+        .build(mock_context(noop_assets()))
+        .expect("mock app");
+    let webview = WebviewWindowBuilder::new(&app, "main", Default::default())
+        .build()
+        .expect("mock webview");
+    (app, webview)
+}
+
+fn invoke(webview: &WebviewWindow, command: &str, body: Value) -> Result {
+    tauri::test::get_ipc_response(webview, tauri::webview::InvokeRequest {
+        cmd: command.to_string(),
+        callback: tauri::ipc::CallbackFn(0),
+        error: tauri::ipc::CallbackFn(1),
+        url: if cfg!(any(windows, target_os = "android")) {
+            "http://tauri.localhost"
+        } else {
+            "tauri://localhost"
+        }.parse().expect("mock IPC URL"),
+        body: tauri::ipc::InvokeBody::Json(body),
+        headers: Default::default(),
+        invoke_key: tauri::test::INVOKE_KEY.to_string(),
+    }).map(|body| body.deserialize().expect("JSON response"))
+}
+
+fn missing_state(command: &str, key: &str) -> Value {
+    json!(format!("state not managed for field `{key}` on command `{command}`. You must call `.manage()` before using this command"))
+}
+
+#[test]
+fn run_bundle_preserves_managed_identity_without_payload_state() {
+    let states = ManagedStates::new(Arc::new(node_engine::NodeRegistry::new()));
+    let (_app, webview) = build(states.run_builder(6));
+    let actual = invoke(&webview, "inspect_run_state", json!({"state": "ignored client input"})).expect("managed state");
+    assert_eq!(actual, json!([
+        identity(&states.gateway), identity(&states.runtime_registry), identity(&states.extensions),
+        identity(&states.rag_manager), identity(&states.workflow_service), identity(&states.diagnostics_store),
+    ]));
+}
+
+#[test]
+fn run_bundle_preserves_missing_state_order_and_original_camel_case_keys() {
+    let states = ManagedStates::new(Arc::new(node_engine::NodeRegistry::new()));
+    for (prefix, key) in ["gateway", "runtimeRegistry", "extensions", "ragManager", "workflowService", "diagnosticsStore"].iter().enumerate() {
+        let (_app, webview) = build(states.run_builder(prefix));
+        assert_eq!(invoke(&webview, "inspect_run_state", json!({})), Err(missing_state("inspect_run_state", key)));
+    }
+}
+
+#[test]
+fn query_bundle_preserves_managed_identity_without_payload_state() {
+    let states = ManagedStates::new(Arc::new(node_engine::NodeRegistry::new()));
+    let (_app, webview) = build(states.query_builder(3));
+    let actual = invoke(&webview, "inspect_query_state", json!({})).expect("managed state");
+    assert_eq!(actual, json!([identity(&states.registry), identity(&states.extensions), identity(&states.workflow_service)]));
+}
+
+#[test]
+fn query_command_preserves_missing_state_order_and_original_keys() {
+    let states = ManagedStates::new(Arc::new(node_engine::NodeRegistry::new()));
+    for (prefix, key) in ["registry", "extensions", "workflowService"].iter().enumerate() {
+        let (_app, webview) = build(states.query_builder(prefix));
+        assert_eq!(invoke(&webview, "query_port_options", json!({"nodeType":"fixture", "portId":"choice"})), Err(missing_state("query_port_options", key)));
+    }
+}
+
+struct EchoOptionsProvider(Arc);
+
+#[async_trait]
+impl node_engine::PortOptionsProvider for EchoOptionsProvider {
+    async fn query_options(
+        &self,
+        query: &node_engine::PortOptionsQuery,
+        extensions: &node_engine::ExecutorExtensions,
+    ) -> node_engine::Result {
+        self.0.fetch_add(1, Ordering::SeqCst);
+        Ok(node_engine::PortOptionsResult {
+            options: Vec::new(), total_count: 0, searchable: true,
+            metadata: Some(json!({"query":query,"marker":extensions.get::("state-fixture-marker")})),
+        })
+    }
+}
+
+#[test]
+fn query_command_preserves_flat_required_optional_and_context_payloads() {
+    let calls = Arc::new(AtomicUsize::new(0));
+    let mut registry = node_engine::NodeRegistry::new();
+    registry.register_port_provider("fixture", "choice", Box::new(EchoOptionsProvider(calls.clone())));
+    let states = ManagedStates::new(Arc::new(registry));
+    let (_app, webview) = build(states.query_builder(3));
+    let context = json!({"targetNodeId":"target-1", "taskKind":"embedding"});
+    let actual = invoke(&webview, "query_port_options", json!({
+        "nodeType":"fixture", "portId":"choice", "search":"needle", "limit":7, "offset":3,
+        "context":context, "state":"must not deserialize", "registry":"must not replace managed state",
+    })).expect("original flat command payload");
+    assert_eq!(actual, json!({"options":[],"totalCount":0,"searchable":true,"metadata":{
+        "query":{"search":"needle","limit":7,"offset":3,"context":context},"marker":"managed extension",
+    }}));
+    let minimal = invoke(&webview, "query_port_options", json!({"nodeType":"fixture", "portId":"choice"})).expect("original optional defaults");
+    assert_eq!(minimal["metadata"]["query"], json!({"search":null,"limit":null,"offset":null}));
+    assert_eq!(calls.load(Ordering::SeqCst), 2);
+}
diff --git a/src-tauri/src/workflow/commands.rs b/src-tauri/src/workflow/commands.rs
index 1c23b739f..495b23e69 100644
--- a/src-tauri/src/workflow/commands.rs
+++ b/src-tauri/src/workflow/commands.rs
@@ -6,6 +6,8 @@
 use std::{path::PathBuf, sync::Arc};
 
 use tauri::{command, AppHandle, Manager, State};
+
+pub use super::command_state::{PortOptionsCommandState, WorkflowRunCommandState};
 use tokio::sync::RwLock;
 
 use crate::agent::rag::SharedRagManager;
@@ -199,23 +201,13 @@ pub async fn workflow_create_execution_session(
 pub async fn workflow_run_execution_session(
     request: pantograph_workflow_service::WorkflowExecutionSessionRunRequest,
     app: AppHandle,
-    gateway: State<'_, SharedGateway>,
-    runtime_registry: State<'_, SharedRuntimeRegistry>,
-    extensions: State<'_, SharedExtensions>,
-    rag_manager: State<'_, SharedRagManager>,
-    workflow_service: State<'_, SharedWorkflowService>,
-    diagnostics_store: State<'_, SharedWorkflowDiagnosticsStore>,
+    state: WorkflowRunCommandState<'_>,
     channel: tauri::ipc::Channel,
 ) -> Result {
     super::headless_workflow_commands::workflow_run_execution_session(
         request,
         app,
-        gateway,
-        runtime_registry,
-        extensions,
-        rag_manager,
-        workflow_service,
-        diagnostics_store,
+        state,
         channel,
     )
     .await
@@ -769,9 +761,7 @@ pub async fn workflow_set_execution_session_keep_alive(
 
 #[command]
 pub async fn query_port_options(
-    registry: State<'_, SharedNodeRegistry>,
-    extensions: State<'_, SharedExtensions>,
-    workflow_service: State<'_, SharedWorkflowService>,
+    state: PortOptionsCommandState<'_>,
     node_type: String,
     port_id: String,
     search: Option,
@@ -780,9 +770,7 @@ pub async fn query_port_options(
     context: Option,
 ) -> Result {
     super::workflow_port_query_commands::query_port_options(
-        registry,
-        extensions,
-        workflow_service,
+        state,
         node_type,
         port_id,
         search,
diff --git a/src-tauri/src/workflow/headless_workflow_commands.rs b/src-tauri/src/workflow/headless_workflow_commands.rs
index 88b439c11..ef8e2ab8b 100644
--- a/src-tauri/src/workflow/headless_workflow_commands.rs
+++ b/src-tauri/src/workflow/headless_workflow_commands.rs
@@ -59,7 +59,8 @@ use tauri::{ipc::Channel, AppHandle, State};
 use crate::agent::rag::SharedRagManager;
 use crate::llm::{SharedGateway, SharedRuntimeRegistry};
 
-use super::commands::{SharedExtensions, SharedWorkflowDiagnosticsStore, SharedWorkflowService};
+use super::command_state::WorkflowRunCommandState;
+use super::commands::{SharedExtensions, SharedWorkflowService};
 use super::events::WorkflowEvent;
 use super::headless_diagnostics::workflow_scheduler_snapshot_response;
 pub(crate) use super::headless_runtime::build_runtime;
@@ -188,14 +189,17 @@ pub async fn workflow_create_execution_session(
 pub async fn workflow_run_execution_session(
     request: WorkflowExecutionSessionRunRequest,
     app: AppHandle,
-    gateway: State<'_, SharedGateway>,
-    runtime_registry: State<'_, SharedRuntimeRegistry>,
-    extensions: State<'_, SharedExtensions>,
-    rag_manager: State<'_, SharedRagManager>,
-    workflow_service: State<'_, SharedWorkflowService>,
-    diagnostics_store: State<'_, SharedWorkflowDiagnosticsStore>,
+    state: WorkflowRunCommandState<'_>,
     channel: Channel,
 ) -> Result {
+    let WorkflowRunCommandState {
+        gateway,
+        runtime_registry,
+        extensions,
+        rag_manager,
+        workflow_service,
+        diagnostics_store,
+    } = state;
     let runtime = build_runtime(
         &app,
         gateway.inner(),
diff --git a/src-tauri/src/workflow/mod.rs b/src-tauri/src/workflow/mod.rs
index a338767b3..e6486440e 100644
--- a/src-tauri/src/workflow/mod.rs
+++ b/src-tauri/src/workflow/mod.rs
@@ -21,6 +21,7 @@
 //!                            └─────────────────────────────────┘
 //! ```
 
+mod command_state;
 pub mod commands;
 pub mod diagnostics;
 pub mod event_adapter;
diff --git a/src-tauri/src/workflow/workflow_port_query_commands.rs b/src-tauri/src/workflow/workflow_port_query_commands.rs
index 269704fed..af16377ba 100644
--- a/src-tauri/src/workflow/workflow_port_query_commands.rs
+++ b/src-tauri/src/workflow/workflow_port_query_commands.rs
@@ -1,14 +1,13 @@
+use super::command_state::PortOptionsCommandState;
 use tauri::State;
 
-use super::commands::{SharedExtensions, SharedNodeRegistry, SharedWorkflowService};
+use super::commands::{SharedNodeRegistry, SharedWorkflowService};
 
 const PUMA_LIB_NODE_TYPE: &str = "puma-lib";
 const PUMAS_MODEL_REF_PORT_ID: &str = "pumas_model_ref";
 
 pub async fn query_port_options(
-    registry: State<'_, SharedNodeRegistry>,
-    extensions: State<'_, SharedExtensions>,
-    workflow_service: State<'_, SharedWorkflowService>,
+    state: PortOptionsCommandState<'_>,
     node_type: String,
     port_id: String,
     search: Option,
@@ -16,6 +15,11 @@ pub async fn query_port_options(
     offset: Option,
     context: Option,
 ) -> Result {
+    let PortOptionsCommandState {
+        registry,
+        extensions,
+        workflow_service,
+    } = state;
     let ext = extensions.read().await;
     let query = node_engine::PortOptionsQuery {
         search: search.clone(),

From b2490aeae74f891a7e24dd8360f62d28018c90b3 Mon Sep 17 00:00:00 2001
From: Puma 
Date: Sat, 3 Oct 2026 12:50:26 -0700
Subject: [PATCH 16/21] style(desktop): apply pinned Rust formatting to IPC
 fixtures

---
 .../reports/2026-10-03-tauri-command-state.md |   2 +-
 src-tauri/src/workflow/command_state.rs       |   4 +-
 src-tauri/src/workflow/command_state_tests.rs | 147 ++++++++++++++----
 src-tauri/src/workflow/commands.rs            |  17 +-
 4 files changed, 119 insertions(+), 51 deletions(-)

diff --git a/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-tauri-command-state.md b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-tauri-command-state.md
index b4b75606d..91792cf7a 100644
--- a/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-tauri-command-state.md
+++ b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-tauri-command-state.md
@@ -10,7 +10,7 @@ Five tests use the official Tauri MockRuntime/IPC path. They verify all six/thre
 
 The existing Tauri dependency gains only its test-only test feature (empty upstream feature, same locked 2.9.5 package). Required hosted workspace verification adds a script requiring all five exact test names and an individual 1 passed / 0 failed / 0 ignored receipt for each, with backend-llamacpp and no default app features. Existing all-feature workspace compilation and strict all-target Clippy remain. Frontend invocation tests remain part of the existing full frontend suite.
 
-Source review is pending. The source checkout was restored after a filesystem reset, and pinned Rust/rustfmt are currently unavailable. Formatting, Rust compilation and native mock-IPC execution are unrun locally; no installation or heavy build is attempted under the recovery hold. Bash syntax and the unchanged-body comparison pass. Publication remains paused with the separate mechanical batch until explicit retry authorization and the serialized write window are available.
+Root source review accepted corrected tree 58ef1d4de278b899c3d2c72553275d7d3697f1bd after inspecting the import placement and exact pinned-version evidence. This is source acceptance, not Rust execution. After the user approved recovery, the restored official Rust 1.92.0 formatter found formatting changes in three IPC files. Applying pinned cargo fmt --all and rerunning the full cargo fmt --all -- --check passed. The formatting delta contains whitespace, optional trailing commas and two unchanged reordered imports; token comparison preserves the remaining source, literals and comments. Bash syntax and the unchanged delegate-body comparison also pass. Rust compilation and all five native mock-IPC executions remain pending hosted qualification. The optional formatting-artifact workflow is omitted because local pinned formatting is now available.
 
 ## Exact pinned-source compatibility
 
diff --git a/src-tauri/src/workflow/command_state.rs b/src-tauri/src/workflow/command_state.rs
index 572532809..689d99010 100644
--- a/src-tauri/src/workflow/command_state.rs
+++ b/src-tauri/src/workflow/command_state.rs
@@ -3,11 +3,11 @@
 use tauri::ipc::{CommandArg, CommandItem, InvokeError};
 use tauri::{Runtime, State};
 
-use crate::agent::rag::SharedRagManager;
-use crate::llm::{SharedGateway, SharedRuntimeRegistry};
 use super::commands::{
     SharedExtensions, SharedNodeRegistry, SharedWorkflowDiagnosticsStore, SharedWorkflowService,
 };
+use crate::agent::rag::SharedRagManager;
+use crate::llm::{SharedGateway, SharedRuntimeRegistry};
 
 pub struct WorkflowRunCommandState<'r> {
     pub gateway: State<'r, SharedGateway>,
diff --git a/src-tauri/src/workflow/command_state_tests.rs b/src-tauri/src/workflow/command_state_tests.rs
index 3774d62c5..905774798 100644
--- a/src-tauri/src/workflow/command_state_tests.rs
+++ b/src-tauri/src/workflow/command_state_tests.rs
@@ -19,7 +19,13 @@ impl inference::ProcessSpawner for NoSpawn {
         &self,
         _: &str,
         _: &[&str],
-    ) -> Result<(mpsc::Receiver, Box), String> {
+    ) -> Result<
+        (
+            mpsc::Receiver,
+            Box,
+        ),
+        String,
+    > {
         panic!("state extraction must not spawn a backend")
     }
 
@@ -50,7 +56,9 @@ impl ManagedStates {
             gateway: Arc::new(crate::llm::InferenceGateway::new(Arc::new(NoSpawn))),
             runtime_registry: Arc::new(pantograph_runtime_registry::RuntimeRegistry::new()),
             extensions: Arc::new(RwLock::new(extensions)),
-            rag_manager: crate::agent::rag::create_rag_manager(PathBuf::from("unused-state-fixture")),
+            rag_manager: crate::agent::rag::create_rag_manager(PathBuf::from(
+                "unused-state-fixture",
+            )),
             workflow_service: Arc::new(pantograph_workflow_service::WorkflowService::new()),
             diagnostics_store: Arc::new(crate::workflow::WorkflowDiagnosticsStore::default()),
             registry,
@@ -135,20 +143,30 @@ fn build(builder: Builder) -> (App, WebviewWindow, command: &str, body: Value) -> Result {
-    tauri::test::get_ipc_response(webview, tauri::webview::InvokeRequest {
-        cmd: command.to_string(),
-        callback: tauri::ipc::CallbackFn(0),
-        error: tauri::ipc::CallbackFn(1),
-        url: if cfg!(any(windows, target_os = "android")) {
-            "http://tauri.localhost"
-        } else {
-            "tauri://localhost"
-        }.parse().expect("mock IPC URL"),
-        body: tauri::ipc::InvokeBody::Json(body),
-        headers: Default::default(),
-        invoke_key: tauri::test::INVOKE_KEY.to_string(),
-    }).map(|body| body.deserialize().expect("JSON response"))
+fn invoke(
+    webview: &WebviewWindow,
+    command: &str,
+    body: Value,
+) -> Result {
+    tauri::test::get_ipc_response(
+        webview,
+        tauri::webview::InvokeRequest {
+            cmd: command.to_string(),
+            callback: tauri::ipc::CallbackFn(0),
+            error: tauri::ipc::CallbackFn(1),
+            url: if cfg!(any(windows, target_os = "android")) {
+                "http://tauri.localhost"
+            } else {
+                "tauri://localhost"
+            }
+            .parse()
+            .expect("mock IPC URL"),
+            body: tauri::ipc::InvokeBody::Json(body),
+            headers: Default::default(),
+            invoke_key: tauri::test::INVOKE_KEY.to_string(),
+        },
+    )
+    .map(|body| body.deserialize().expect("JSON response"))
 }
 
 fn missing_state(command: &str, key: &str) -> Value {
@@ -159,19 +177,44 @@ fn missing_state(command: &str, key: &str) -> Value {
 fn run_bundle_preserves_managed_identity_without_payload_state() {
     let states = ManagedStates::new(Arc::new(node_engine::NodeRegistry::new()));
     let (_app, webview) = build(states.run_builder(6));
-    let actual = invoke(&webview, "inspect_run_state", json!({"state": "ignored client input"})).expect("managed state");
-    assert_eq!(actual, json!([
-        identity(&states.gateway), identity(&states.runtime_registry), identity(&states.extensions),
-        identity(&states.rag_manager), identity(&states.workflow_service), identity(&states.diagnostics_store),
-    ]));
+    let actual = invoke(
+        &webview,
+        "inspect_run_state",
+        json!({"state": "ignored client input"}),
+    )
+    .expect("managed state");
+    assert_eq!(
+        actual,
+        json!([
+            identity(&states.gateway),
+            identity(&states.runtime_registry),
+            identity(&states.extensions),
+            identity(&states.rag_manager),
+            identity(&states.workflow_service),
+            identity(&states.diagnostics_store),
+        ])
+    );
 }
 
 #[test]
 fn run_bundle_preserves_missing_state_order_and_original_camel_case_keys() {
     let states = ManagedStates::new(Arc::new(node_engine::NodeRegistry::new()));
-    for (prefix, key) in ["gateway", "runtimeRegistry", "extensions", "ragManager", "workflowService", "diagnosticsStore"].iter().enumerate() {
+    for (prefix, key) in [
+        "gateway",
+        "runtimeRegistry",
+        "extensions",
+        "ragManager",
+        "workflowService",
+        "diagnosticsStore",
+    ]
+    .iter()
+    .enumerate()
+    {
         let (_app, webview) = build(states.run_builder(prefix));
-        assert_eq!(invoke(&webview, "inspect_run_state", json!({})), Err(missing_state("inspect_run_state", key)));
+        assert_eq!(
+            invoke(&webview, "inspect_run_state", json!({})),
+            Err(missing_state("inspect_run_state", key))
+        );
     }
 }
 
@@ -180,15 +223,32 @@ fn query_bundle_preserves_managed_identity_without_payload_state() {
     let states = ManagedStates::new(Arc::new(node_engine::NodeRegistry::new()));
     let (_app, webview) = build(states.query_builder(3));
     let actual = invoke(&webview, "inspect_query_state", json!({})).expect("managed state");
-    assert_eq!(actual, json!([identity(&states.registry), identity(&states.extensions), identity(&states.workflow_service)]));
+    assert_eq!(
+        actual,
+        json!([
+            identity(&states.registry),
+            identity(&states.extensions),
+            identity(&states.workflow_service)
+        ])
+    );
 }
 
 #[test]
 fn query_command_preserves_missing_state_order_and_original_keys() {
     let states = ManagedStates::new(Arc::new(node_engine::NodeRegistry::new()));
-    for (prefix, key) in ["registry", "extensions", "workflowService"].iter().enumerate() {
+    for (prefix, key) in ["registry", "extensions", "workflowService"]
+        .iter()
+        .enumerate()
+    {
         let (_app, webview) = build(states.query_builder(prefix));
-        assert_eq!(invoke(&webview, "query_port_options", json!({"nodeType":"fixture", "portId":"choice"})), Err(missing_state("query_port_options", key)));
+        assert_eq!(
+            invoke(
+                &webview,
+                "query_port_options",
+                json!({"nodeType":"fixture", "portId":"choice"})
+            ),
+            Err(missing_state("query_port_options", key))
+        );
     }
 }
 
@@ -203,8 +263,12 @@ impl node_engine::PortOptionsProvider for EchoOptionsProvider {
     ) -> node_engine::Result {
         self.0.fetch_add(1, Ordering::SeqCst);
         Ok(node_engine::PortOptionsResult {
-            options: Vec::new(), total_count: 0, searchable: true,
-            metadata: Some(json!({"query":query,"marker":extensions.get::("state-fixture-marker")})),
+            options: Vec::new(),
+            total_count: 0,
+            searchable: true,
+            metadata: Some(
+                json!({"query":query,"marker":extensions.get::("state-fixture-marker")}),
+            ),
         })
     }
 }
@@ -213,7 +277,11 @@ impl node_engine::PortOptionsProvider for EchoOptionsProvider {
 fn query_command_preserves_flat_required_optional_and_context_payloads() {
     let calls = Arc::new(AtomicUsize::new(0));
     let mut registry = node_engine::NodeRegistry::new();
-    registry.register_port_provider("fixture", "choice", Box::new(EchoOptionsProvider(calls.clone())));
+    registry.register_port_provider(
+        "fixture",
+        "choice",
+        Box::new(EchoOptionsProvider(calls.clone())),
+    );
     let states = ManagedStates::new(Arc::new(registry));
     let (_app, webview) = build(states.query_builder(3));
     let context = json!({"targetNodeId":"target-1", "taskKind":"embedding"});
@@ -221,10 +289,21 @@ fn query_command_preserves_flat_required_optional_and_context_payloads() {
         "nodeType":"fixture", "portId":"choice", "search":"needle", "limit":7, "offset":3,
         "context":context, "state":"must not deserialize", "registry":"must not replace managed state",
     })).expect("original flat command payload");
-    assert_eq!(actual, json!({"options":[],"totalCount":0,"searchable":true,"metadata":{
-        "query":{"search":"needle","limit":7,"offset":3,"context":context},"marker":"managed extension",
-    }}));
-    let minimal = invoke(&webview, "query_port_options", json!({"nodeType":"fixture", "portId":"choice"})).expect("original optional defaults");
-    assert_eq!(minimal["metadata"]["query"], json!({"search":null,"limit":null,"offset":null}));
+    assert_eq!(
+        actual,
+        json!({"options":[],"totalCount":0,"searchable":true,"metadata":{
+            "query":{"search":"needle","limit":7,"offset":3,"context":context},"marker":"managed extension",
+        }})
+    );
+    let minimal = invoke(
+        &webview,
+        "query_port_options",
+        json!({"nodeType":"fixture", "portId":"choice"}),
+    )
+    .expect("original optional defaults");
+    assert_eq!(
+        minimal["metadata"]["query"],
+        json!({"search":null,"limit":null,"offset":null})
+    );
     assert_eq!(calls.load(Ordering::SeqCst), 2);
 }
diff --git a/src-tauri/src/workflow/commands.rs b/src-tauri/src/workflow/commands.rs
index 495b23e69..6b2b64548 100644
--- a/src-tauri/src/workflow/commands.rs
+++ b/src-tauri/src/workflow/commands.rs
@@ -204,13 +204,8 @@ pub async fn workflow_run_execution_session(
     state: WorkflowRunCommandState<'_>,
     channel: tauri::ipc::Channel,
 ) -> Result {
-    super::headless_workflow_commands::workflow_run_execution_session(
-        request,
-        app,
-        state,
-        channel,
-    )
-    .await
+    super::headless_workflow_commands::workflow_run_execution_session(request, app, state, channel)
+        .await
 }
 
 #[command]
@@ -770,13 +765,7 @@ pub async fn query_port_options(
     context: Option,
 ) -> Result {
     super::workflow_port_query_commands::query_port_options(
-        state,
-        node_type,
-        port_id,
-        search,
-        limit,
-        offset,
-        context,
+        state, node_type, port_id, search, limit, offset, context,
     )
     .await
 }

From 60302e6d1e97b47974b72d2b5e7cb955298e5f9a Mon Sep 17 00:00:00 2001
From: Puma 
Date: Sat, 3 Oct 2026 13:16:01 -0700
Subject: [PATCH 17/21] fix(test): keep SQLite column iterator within statement
 lifetime

---
 crates/pantograph-diagnostics-ledger/src/tests.rs           | 6 ++----
 .../reports/2026-10-03-aggregate-test-style.md              | 4 ++++
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/crates/pantograph-diagnostics-ledger/src/tests.rs b/crates/pantograph-diagnostics-ledger/src/tests.rs
index 8d28d95ba..4ddeeafce 100644
--- a/crates/pantograph-diagnostics-ledger/src/tests.rs
+++ b/crates/pantograph-diagnostics-ledger/src/tests.rs
@@ -5077,12 +5077,10 @@ fn sqlite_column_exists(conn: &Connection, table_name: &str, column_name: &str)
     let mut stmt = conn
         .prepare(&format!("PRAGMA table_info({table_name})"))
         .expect("table info statement prepares");
-    let columns = stmt
+    let mut columns = stmt
         .query_map([], |row| row.get::<_, String>(1))
         .expect("table info query succeeds");
-    columns
-        .map(|column| column.expect("column row loads"))
-        .any(|column| column == column_name)
+    columns.any(|column| column.expect("column row loads") == column_name)
 }
 
 fn assert_columns_exist(conn: &Connection, table_name: &str, column_names: &[&str]) {
diff --git a/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-aggregate-test-style.md b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-aggregate-test-style.md
index 741986314..4f792788e 100644
--- a/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-aggregate-test-style.md
+++ b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-aggregate-test-style.md
@@ -9,3 +9,7 @@ Six existing cfg(test) modules move after production items in node_io_artifacts,
 The required focused workflow retains all existing suites and adds the complete artifact_store integration target (13 source tests), so the compile-blocked redaction/storage contract now executes. Existing embedded, workflow-service, scheduler, ledger, inference and workflow-node suites remain. Desktop/test-only source changes still require actual hosted strict all-target compilation; no blanket lint allowance or assertion removal is introduced.
 
 Root requested this grouped mechanical milestone from the complete diagnostic inventory. Root source review accepted frozen tree 29cb5ce693a0b6a091a807d3cfa3b203a60468d3 after reviewing the non-move changes and independently checking identical nonblank line multisets for all six module moves. Exact-head hosted execution remains pending. Full pinned cargo fmt is checked locally; no heavy local Rust build is performed. The separate four Tauri arity findings remain open and must preserve existing IPC wire keys.
+
+## Hosted qualification correction
+
+PR43 head b2490ae reached the full ledger target in Quality run 37149377334, job 111279795852, and exposed E0597 in the test-only sqlite_column_exists helper. Returning the Map iterator expression extended its temporary borrow beyond the statement's lifetime under the crate's Rust 2021 edition. The original direct-return lint repair was therefore not source-compatible despite its equivalent row predicate. Bind the mapped rows mutably and call any with the same expect and equality directly; the named iterator then drops before the statement, with the same short-circuit order and row-error panic. No production code, SQL, assertions or error text changes. Full ledger and remaining focused suites require a fresh hosted run; the failed run is retained as evidence.

From ca9edde6850cd58ade0b7e534bb4f58e704c2c64 Mon Sep 17 00:00:00 2001
From: Puma 
Date: Sat, 3 Oct 2026 19:24:49 -0700
Subject: [PATCH 18/21] fix(quality): address external range and snippet
 findings

Fetch a missing exact event base and fail closed if unavailable, without substituting an empty range. Exclude inert snippet declarations from rendered-name evidence. Exercise actual CI shell ranges with real Git fixtures and add the snippet regression.

Locally verified tree: c5a72e68a784ff09756ad7ce5000660be58d2868. Parent independent review accepted; external repair review and hosted exact-head qualification remain pending.
---
 .github/workflows/quality-gates.yml          | 11 ++++
 scripts/check-decision-traceability.test.mjs | 54 +++++++++++++++++++-
 scripts/svelte-role-button-check.mjs         |  5 +-
 scripts/svelte-role-button-check.test.mjs    |  5 ++
 4 files changed, 72 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/quality-gates.yml b/.github/workflows/quality-gates.yml
index e1d63cdc9..186aa8b69 100644
--- a/.github/workflows/quality-gates.yml
+++ b/.github/workflows/quality-gates.yml
@@ -44,6 +44,17 @@ jobs:
         shell: bash
         run: |
           set -euo pipefail
+          if [[ ! "$EVENT_BASE" =~ ^[0-9a-fA-F]{40}$ ]]; then
+            echo "Traceability requires the event's full base commit SHA" >&2
+            exit 1
+          fi
+          if ! git cat-file -e "${EVENT_BASE}^{commit}" 2>/dev/null; then
+            if ! git fetch --no-tags origin "$EVENT_BASE" ||
+              ! git cat-file -e "${EVENT_BASE}^{commit}" 2>/dev/null; then
+              echo "Traceability event base is unavailable; cannot compute the no-new lint range" >&2
+              exit 1
+            fi
+          fi
           base="$(git rev-parse --verify --end-of-options "${EVENT_BASE}^{commit}")"
           head="$(git rev-parse --verify --end-of-options "${EVENT_HEAD}^{commit}")"
           if [[ "$EVENT_NAME" == pull_request ]]; then
diff --git a/scripts/check-decision-traceability.test.mjs b/scripts/check-decision-traceability.test.mjs
index 104f91296..1b02407ba 100644
--- a/scripts/check-decision-traceability.test.mjs
+++ b/scripts/check-decision-traceability.test.mjs
@@ -1,6 +1,6 @@
 import assert from 'node:assert/strict';
 import { spawnSync } from 'node:child_process';
-import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
+import { existsSync, mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
 import { tmpdir } from 'node:os';
 import path from 'node:path';
 import { fileURLToPath } from 'node:url';
@@ -327,7 +327,59 @@ test('PR callers select the fork point explicitly without changing exact range s
   failed(f.run(env), /unavailable: docs\/target-guide.md is missing/);
   const base = f.git('merge-base', '--all', target, head);
   assert.equal(base, f.base);
+  const workflowRange = runWorkflowRange(f.root, target, head, 'pull_request');
+  assert.equal(workflowRange.status, 0, workflowRange.stderr);
+  assert.equal(workflowRange.range, `base=${base}\nhead=${head}\n`);
   const result = f.run({ ...env, TRACEABILITY_BASE_REF: base });
   passed(result);
   assert.match(result.output, /1 changed path\(s\), 0 mapped impact\(s\)/);
 });
+
+// Exercise the actual CI shell block, not a second implementation of its range policy.
+function runWorkflowRange(root, base, head, event = 'push') {
+  const workflow = readFileSync(new URL('../.github/workflows/quality-gates.yml', import.meta.url), 'utf8');
+  const step = workflow.split('        id: traceability-range\n')[1];
+  assert.ok(step, 'CI traceability step must exist');
+  const block = step.split('        run: |\n')[1]?.split('\n      - name:')[0];
+  assert.ok(block, 'CI traceability shell block must exist');
+  const script = block.split('\n').map((line) => line.replace(/^ {10}/, '')).join('\n');
+  const outputPath = path.join(root, 'event-range-output');
+  const result = spawnSync('bash', ['-c', script], {
+    cwd: root, encoding: 'utf8',
+    env: { ...process.env, EVENT_NAME: event, EVENT_BASE: base, EVENT_HEAD: head, GITHUB_OUTPUT: outputPath },
+  });
+  return { ...result, range: existsSync(outputPath) ? readFileSync(outputPath, 'utf8') : '' };
+}
+
+test('CI uses a present event base without fetching or replacing its push range', t => {
+  const f = fixture(t);
+  f.write('src/internal.rs', 'fn next() {}\n');
+  const head = f.commit();
+  // No origin is configured, so an unnecessary fetch would fail this check.
+  const result = runWorkflowRange(f.root, f.base, head);
+  assert.equal(result.status, 0, result.stderr);
+  assert.equal(result.range, `base=${f.base}\nhead=${head}\n`);
+});
+
+test('CI fetches the exact missing event base and retains the explicit push range', t => {
+  const f = fixture(t);
+  f.write('src/internal.rs', 'fn next() {}\n');
+  const head = f.commit();
+  const client = path.join(f.root, 'client');
+  const clone = spawnSync(realGit, ['clone', '--depth', '1', `file://${f.root}`, client], { encoding: 'utf8' });
+  assert.equal(clone.status, 0, clone.stderr);
+  assert.notEqual(spawnSync(realGit, ['cat-file', '-e', `${f.base}^{commit}`], { cwd: client }).status, 0);
+  const result = runWorkflowRange(client, f.base, head);
+  assert.equal(result.status, 0, result.stderr);
+  assert.equal(result.range, `base=${f.base}\nhead=${head}\n`);
+});
+
+test('CI fails closed when the event base cannot be fetched or is not a full SHA', t => {
+  for (const base of ['f'.repeat(40), '', '--all']) {
+    const f = fixture(t);
+    const result = runWorkflowRange(f.root, base, f.base);
+    assert.equal(result.status, 1, result.stderr);
+    assert.equal(result.range, '');
+    assert.match(result.stderr, /Traceability (event base is unavailable|requires the event's full base commit SHA)/);
+  }
+});
diff --git a/scripts/svelte-role-button-check.mjs b/scripts/svelte-role-button-check.mjs
index 7b0e9fc17..de472f84f 100644
--- a/scripts/svelte-role-button-check.mjs
+++ b/scripts/svelte-role-button-check.mjs
@@ -7,8 +7,9 @@ const INTERACTIVE_SEMANTIC_TAGS = new Set(['button', 'a', 'input', 'select', 'te
 function hasRenderedName(node) {
   if (!node || typeof node !== 'object') return false;
   if (node.type === 'Text') return /[A-Za-z0-9]/.test(node.data);
-  if (node.type === 'ExpressionTag') return true;
-  if (node.type === 'Comment') return false;
+  if (node.type === 'ExpressionTag' || node.type === 'RenderTag') return true;
+  // A snippet declaration is inert until a RenderTag invokes it.
+  if (node.type === 'Comment' || node.type === 'SnippetBlock') return false;
   if (node.type === 'RegularElement' && ['script', 'style'].includes(node.name)) return false;
   return ['nodes', 'fragment', 'consequent', 'alternate', 'body', 'fallback'].some((key) => {
     const value = node[key];
diff --git a/scripts/svelte-role-button-check.test.mjs b/scripts/svelte-role-button-check.test.mjs
index 6b65e31cc..dec79b9ff 100644
--- a/scripts/svelte-role-button-check.test.mjs
+++ b/scripts/svelte-role-button-check.test.mjs
@@ -40,3 +40,8 @@ test('visible descendant text supplies a name, but handler expressions do not',
   assert.deepEqual(check('
{label}
'), []); assert.deepEqual(rules('
activate()}>
'), ['role-button-accessible-name']); }); + +test('an unused snippet declaration is not rendered name evidence', () => { + assert.deepEqual(rules('
{#snippet label()}Select{/snippet}
'), ['role-button-accessible-name']); + assert.deepEqual(check('
{#snippet label()}Select{/snippet}{@render label()}
'), []); +}); From 572ccbc45c0030a7eef9938702c4c3b2f9b97d27 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Sun, 4 Oct 2026 02:49:58 +0000 Subject: [PATCH 19/21] fix(test): isolate shared Python fixtures in aggregate inference runs Guard Python fixture lifetimes across the worker contract, image and text suites, including asynchronous production-worker lifecycle tests. The GIL can be released during imports and worker execution and does not isolate process-global modules. Keep runtime behavior and existing assertions intact. Add the full PyTorch-enabled inference library suite to Quality Gates so cross-suite interference is exercised alongside the focused contract checks. Qualification on PR44 ca9edde6850cd58ade0b7e534bb4f58e704c2c64: - Before: parallel aggregate 675 passed, 7 failed; serial 682 passed. - After: default parallel 682 passed; three 16-thread runs each 682 passed. - Strict inference library/tests Clippy, Rust formatting and staged critical/a11y/traceability gates passed. - Independent domain aggregate: 566 passed; node/workflow: 426 passed, one unit test and 14 doctests ignored; frontend: 547 passed; tooling: 45. - Full frontend lint, typecheck, dependency tree and production audit passed. Native qualification remains incomplete: workflow-service compilation hit cdn.pyke.io CONNECT proxy 403; Tauri IPC compilation hit missing glib-2.0.pc. No Tauri IPC tests, desktop GUI or model inference were executed. Python fixtures use stubs and do not prove real inference. No dependency pins, audit gates, network settings or permissions were changed. --- .github/workflows/quality-gates.yml | 3 ++ crates/inference/src/backend/pytorch.rs | 6 +++ crates/inference/src/backend/pytorch_tests.rs | 40 +++++++++++++++++++ .../pytorch_worker_image_contract_tests.rs | 4 ++ .../pytorch_worker_image_python_tests.rs | 8 ++++ 5 files changed, 61 insertions(+) diff --git a/.github/workflows/quality-gates.yml b/.github/workflows/quality-gates.yml index 186aa8b69..5fc56a060 100644 --- a/.github/workflows/quality-gates.yml +++ b/.github/workflows/quality-gates.yml @@ -251,6 +251,9 @@ jobs: cargo test -p inference --features backend-pytorch --lib pytorch_named_text_request cargo test -p inference --features backend-pytorch --lib test_pytorch_generate_text_ + - name: Run aggregate inference tests with Python worker fixtures + run: cargo test -p inference --features backend-pytorch --lib + - name: Run llama server startup and reuse contract tests run: | for test_filter in server::tests backend::llamacpp::tests; do diff --git a/crates/inference/src/backend/pytorch.rs b/crates/inference/src/backend/pytorch.rs index 48271cdb3..3ba25a139 100644 --- a/crates/inference/src/backend/pytorch.rs +++ b/crates/inference/src/backend/pytorch.rs @@ -3103,6 +3103,12 @@ fn extract_system_prompt(request: &serde_json::Value) -> Option { }) } +// Python fixtures replace process-global modules and production worker functions. +// The GIL may be released during imports and worker execution, so hold this lock +// for each complete fixture lifetime, including asynchronous lifecycle tests. +#[cfg(test)] +static PYTHON_TEST_LOCK: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(()); + #[cfg(test)] #[path = "pytorch_worker_image_contract_tests.rs"] mod pytorch_worker_image_contract_tests; diff --git a/crates/inference/src/backend/pytorch_tests.rs b/crates/inference/src/backend/pytorch_tests.rs index ca751e16a..910748fea 100644 --- a/crates/inference/src/backend/pytorch_tests.rs +++ b/crates/inference/src/backend/pytorch_tests.rs @@ -645,6 +645,7 @@ fn test_pytorch_worker_load_envelope_tolerates_additive_fields() { #[test] fn test_python_worker_contract_projects_task_profile_loader() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let envelope = serde_json::json!({ @@ -689,6 +690,7 @@ fn test_python_worker_contract_projects_task_profile_loader() { #[test] fn test_python_worker_contract_tolerates_additive_load_fields() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let envelope = serde_json::json!({ @@ -748,6 +750,7 @@ fn test_python_worker_contract_tolerates_additive_load_fields() { #[test] fn test_python_worker_contract_rejects_unsupported_task_profile_loader() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let envelope = serde_json::json!({ @@ -789,6 +792,7 @@ fn test_python_worker_contract_rejects_unsupported_task_profile_loader() { #[test] fn test_python_worker_contract_rejects_missing_load_entry_path() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let envelope = serde_json::json!({ @@ -826,6 +830,7 @@ fn test_python_worker_contract_rejects_missing_load_entry_path() { #[test] fn test_python_worker_load_value_error_after_projection_maps_to_model_load_failed() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_module_with_stubbed_dependencies(py); let patch = CString::new( @@ -882,6 +887,7 @@ module.load_model = fail_load_model #[test] fn test_python_worker_load_unexpected_loader_exception_maps_to_model_load_failed() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_module_with_stubbed_dependencies(py); let patch = CString::new( @@ -938,6 +944,7 @@ module.load_model = fail_load_model #[test] fn test_python_worker_load_invalid_loader_stays_invalid_request() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_module_with_stubbed_dependencies(py); let envelope = serde_json::json!({ @@ -1069,6 +1076,7 @@ fn test_pytorch_worker_generate_text_dllm_envelope_decodes_backend_local_control #[test] fn test_python_worker_contract_projects_dllm_generation_controls() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let fixture = include_str!( @@ -1103,6 +1111,7 @@ fn test_python_worker_contract_projects_dllm_generation_controls() { #[test] fn test_python_worker_contract_tolerates_additive_generate_fields() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let envelope = serde_json::json!({ @@ -1572,6 +1581,7 @@ fn test_pytorch_worker_truncate_kv_cache_envelope_rejects_empty_path() { #[test] fn test_python_worker_contract_projects_unload_envelope() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let fixture = @@ -1588,6 +1598,7 @@ fn test_python_worker_contract_projects_unload_envelope() { #[test] fn test_python_worker_contract_projects_init_envelope() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let fixture = @@ -1604,6 +1615,7 @@ fn test_python_worker_contract_projects_init_envelope() { #[test] fn test_python_worker_contract_projects_shutdown_envelope() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let fixture = include_str!( @@ -1621,6 +1633,7 @@ fn test_python_worker_contract_projects_shutdown_envelope() { #[test] fn test_python_worker_shutdown_from_envelope_returns_structured_success() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_module_with_stubbed_dependencies(py); let fixture = include_str!( @@ -1645,6 +1658,7 @@ fn test_python_worker_shutdown_from_envelope_returns_structured_success() { #[test] fn test_python_worker_response_helpers_emit_success_contract() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let locals = PyDict::new(py); @@ -1690,6 +1704,7 @@ response_json = worker_contract.worker_success_response_json( #[test] fn test_python_worker_response_helpers_emit_error_contract() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); @@ -1727,6 +1742,7 @@ fn test_python_worker_response_helpers_emit_error_contract() { #[test] fn test_python_worker_contract_rejects_invalid_init_envelope() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let wrong_operation = serde_json::json!({ @@ -1769,6 +1785,7 @@ fn test_python_worker_contract_rejects_invalid_init_envelope() { #[test] fn test_python_worker_contract_rejects_invalid_shutdown_envelope() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let wrong_operation = serde_json::json!({ @@ -1811,6 +1828,7 @@ fn test_python_worker_contract_rejects_invalid_shutdown_envelope() { #[test] fn test_python_worker_contract_projects_get_loaded_info_envelope() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let fixture = include_str!( @@ -1828,6 +1846,7 @@ fn test_python_worker_contract_projects_get_loaded_info_envelope() { #[test] fn test_python_worker_contract_rejects_invalid_get_loaded_info_envelope() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let wrong_operation = serde_json::json!({ @@ -1870,6 +1889,7 @@ fn test_python_worker_contract_rejects_invalid_get_loaded_info_envelope() { #[test] fn test_python_worker_contract_projects_clear_kv_cache_envelope() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let fixture = include_str!( @@ -1887,6 +1907,7 @@ fn test_python_worker_contract_projects_clear_kv_cache_envelope() { #[test] fn test_python_worker_contract_rejects_invalid_clear_kv_cache_envelope() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let wrong_operation = serde_json::json!({ @@ -1929,6 +1950,7 @@ fn test_python_worker_contract_rejects_invalid_clear_kv_cache_envelope() { #[test] fn test_python_worker_contract_projects_save_kv_cache_envelope() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let fixture = @@ -1951,6 +1973,7 @@ fn test_python_worker_contract_projects_save_kv_cache_envelope() { #[test] fn test_python_worker_contract_rejects_invalid_save_kv_cache_envelope() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let wrong_operation = serde_json::json!({ @@ -1993,6 +2016,7 @@ fn test_python_worker_contract_rejects_invalid_save_kv_cache_envelope() { #[test] fn test_python_worker_contract_projects_restore_kv_cache_envelope() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let fixture = include_str!( @@ -2016,6 +2040,7 @@ fn test_python_worker_contract_projects_restore_kv_cache_envelope() { #[test] fn test_python_worker_contract_rejects_invalid_restore_kv_cache_envelope() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let wrong_operation = serde_json::json!({ @@ -2058,6 +2083,7 @@ fn test_python_worker_contract_rejects_invalid_restore_kv_cache_envelope() { #[test] fn test_python_worker_contract_projects_truncate_kv_cache_envelope() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let fixture = include_str!( @@ -2089,6 +2115,7 @@ fn test_python_worker_contract_projects_truncate_kv_cache_envelope() { #[test] fn test_python_worker_contract_rejects_invalid_truncate_kv_cache_envelope() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let wrong_operation = serde_json::json!({ @@ -2131,6 +2158,7 @@ fn test_python_worker_contract_rejects_invalid_truncate_kv_cache_envelope() { #[test] fn test_python_worker_contract_rejects_invalid_unload_envelope() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let wrong_operation = serde_json::json!({ @@ -2254,6 +2282,7 @@ fn test_pytorch_worker_audio_transcription_envelope_tolerates_additive_fields() #[test] fn test_python_worker_contract_projects_audio_transcription_fields() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let fixture = include_str!( @@ -2309,6 +2338,7 @@ fn test_python_worker_contract_projects_audio_transcription_fields() { #[test] fn test_python_worker_contract_tolerates_additive_audio_transcription_fields() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let envelope = serde_json::json!({ @@ -2344,6 +2374,7 @@ fn test_python_worker_contract_tolerates_additive_audio_transcription_fields() { #[test] fn test_python_worker_contract_rejects_audio_transcription_legacy_device() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let mut value: serde_json::Value = serde_json::from_str(include_str!( @@ -2366,6 +2397,7 @@ fn test_python_worker_contract_rejects_audio_transcription_legacy_device() { #[test] fn test_python_worker_contract_rejects_audio_transcription_auto_device_field() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let mut value: serde_json::Value = serde_json::from_str(include_str!( @@ -2748,6 +2780,7 @@ fn test_pytorch_generate_text_stream_envelope_rejects_policy_transformers_kwargs #[test] fn test_python_worker_contract_rejects_additive_backend_kwargs() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_contract_module(py); let envelope = serde_json::json!({ @@ -3625,6 +3658,7 @@ fn test_pytorch_worker_stream_token_extraction_error_normalizes_to_backend_error #[test] fn test_pytorch_worker_stream_token_accepts_string_chunk() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let token = pyo3::types::PyString::new(py, "hello"); let chunk = @@ -3638,6 +3672,7 @@ fn test_pytorch_worker_stream_token_accepts_string_chunk() { #[test] fn test_pytorch_worker_stream_token_accepts_replace_dict_chunk() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let token = pyo3::types::PyDict::new(py); token.set_item("mode", "replace").expect("set mode"); @@ -3665,6 +3700,7 @@ fn test_pytorch_worker_stream_token_accepts_replace_dict_chunk() { #[test] fn test_pytorch_worker_stream_token_accepts_usage_only_dict_chunk() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let token = pyo3::types::PyDict::new(py); let usage = pyo3::types::PyDict::new(py); @@ -3690,6 +3726,7 @@ fn test_pytorch_worker_stream_token_accepts_usage_only_dict_chunk() { #[test] fn test_pytorch_worker_stream_token_bounds_usage_counts() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let token = pyo3::types::PyDict::new(py); let usage = pyo3::types::PyDict::new(py); @@ -3715,6 +3752,7 @@ fn test_pytorch_worker_stream_token_bounds_usage_counts() { #[test] fn test_pytorch_worker_stream_token_rejects_dict_without_text_or_usage() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let token = pyo3::types::PyDict::new(py); token.set_item("mode", "replace").expect("set mode"); @@ -5905,6 +5943,7 @@ impl TextIteratorBarrier { #[tokio::test] async fn production_text_iterator_drains_before_successful_load_and_unload() { + let _python_fixture = super::PYTHON_TEST_LOCK.lock().await; use futures_util::{FutureExt, StreamExt}; for action in ["load", "unload", "stop", "failed_restart", "failed_load"] { let (entered_tx, entered_rx) = tokio::sync::oneshot::channel(); @@ -6213,6 +6252,7 @@ impl crate::InferenceExecutionCancellationSignal for SelectedTextCancellation { #[tokio::test] async fn selected_text_production_loader_and_worker_retain_selection_until_termination() { + let _python_fixture = super::PYTHON_TEST_LOCK.lock().await; use futures_util::FutureExt; for mode in ["success", "error", "cancel"] { let (_directory, request, target, mut decision) = crate::selected_text_execution::fixture(); diff --git a/crates/inference/src/backend/pytorch_worker_image_contract_tests.rs b/crates/inference/src/backend/pytorch_worker_image_contract_tests.rs index 410d80dc2..7fa4c6ac7 100644 --- a/crates/inference/src/backend/pytorch_worker_image_contract_tests.rs +++ b/crates/inference/src/backend/pytorch_worker_image_contract_tests.rs @@ -292,6 +292,7 @@ fn test_pytorch_worker_generate_image_request_maps_from_validated_plan() { #[test] fn test_python_worker_generate_image_contract_projects_planned_kwargs() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_image_contract_module(py); let envelope = include_str!( @@ -349,6 +350,7 @@ fn test_python_worker_generate_image_contract_projects_planned_kwargs() { #[test] fn test_python_worker_generate_image_batch_contract_projects_member_kwargs() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_image_contract_module(py); let envelope = include_str!( @@ -413,6 +415,7 @@ fn test_python_worker_generate_image_batch_contract_projects_member_kwargs() { #[test] fn test_python_worker_generate_image_contract_rejects_unknown_payload_fields() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_image_contract_module(py); let mut envelope: serde_json::Value = serde_json::from_str(include_str!( @@ -436,6 +439,7 @@ fn test_python_worker_generate_image_contract_rejects_unknown_payload_fields() { #[test] fn test_python_worker_generate_image_contract_requires_rust_selected_device() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_image_contract_module(py); let mut envelope: serde_json::Value = serde_json::from_str(include_str!( diff --git a/crates/inference/src/backend/pytorch_worker_image_python_tests.rs b/crates/inference/src/backend/pytorch_worker_image_python_tests.rs index 11c6cb0fb..580c96c57 100644 --- a/crates/inference/src/backend/pytorch_worker_image_python_tests.rs +++ b/crates/inference/src/backend/pytorch_worker_image_python_tests.rs @@ -174,6 +174,7 @@ def load_diffusion_model(path, device=None, torch_dtype=None): #[test] fn test_python_worker_generate_image_batch_from_envelope_returns_worker_response() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_module_with_image_stubs(py); attach_stub_diffusion_pipeline(&module); @@ -240,6 +241,7 @@ fn test_python_worker_generate_image_batch_from_envelope_returns_worker_response #[test] fn test_python_worker_generate_image_from_envelope_returns_worker_response() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_module_with_image_stubs(py); attach_stub_diffusion_pipeline(&module); @@ -275,6 +277,7 @@ fn test_python_worker_generate_image_from_envelope_returns_worker_response() { #[test] fn test_python_worker_generate_image_from_envelope_reports_cuda_peak_vram() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_module_with_image_stubs(py); attach_stub_diffusion_pipeline(&module); @@ -334,6 +337,7 @@ worker.torch.cuda = _Cuda() #[test] fn test_python_worker_generate_image_from_envelope_reports_mps_metric_unimplemented() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_module_with_image_stubs(py); attach_stub_diffusion_pipeline(&module); @@ -386,6 +390,7 @@ worker.torch.backends = types.SimpleNamespace( #[test] fn test_python_worker_generate_image_from_envelope_reports_oom_failure() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_module_with_image_stubs(py); attach_stub_diffusion_pipeline(&module); @@ -432,6 +437,7 @@ worker._diffusion_pipeline = _FailingPipeline() #[test] fn test_python_worker_generate_image_from_envelope_rejects_unsupported_denoising_scheduler() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_module_with_image_stubs(py); attach_stub_diffusion_pipeline(&module); @@ -462,6 +468,7 @@ fn test_python_worker_generate_image_from_envelope_rejects_unsupported_denoising #[test] fn test_python_worker_generate_image_from_envelope_rejects_unplanned_fields() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_module_with_image_stubs(py); attach_stub_diffusion_pipeline(&module); @@ -494,6 +501,7 @@ fn test_python_worker_generate_image_from_envelope_rejects_unplanned_fields() { #[test] fn test_python_worker_real_diffusion_loader_enforces_closed_bundle_admission() { + let _python_fixture = super::PYTHON_TEST_LOCK.blocking_lock(); Python::with_gil(|py| { let module = load_worker_module_with_image_stubs(py); let locals = PyDict::new(py); From 827b1b264a5918e2339c503f447aeb01008ac9b4 Mon Sep 17 00:00:00 2001 From: Jeremy Date: Sun, 4 Oct 2026 02:58:48 +0000 Subject: [PATCH 20/21] docs: record PR44 native qualification evidence and blockers --- ...026-10-04-native-workflow-qualification.md | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 docs/plans/domain-architecture-and-multimodal/reports/2026-10-04-native-workflow-qualification.md diff --git a/docs/plans/domain-architecture-and-multimodal/reports/2026-10-04-native-workflow-qualification.md b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-04-native-workflow-qualification.md new file mode 100644 index 000000000..e04bafa5b --- /dev/null +++ b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-04-native-workflow-qualification.md @@ -0,0 +1,156 @@ +# PR44 native/workflow qualification + +Status on 2026-10-04: aggregate inference repair ready for independent review; +native/workflow qualification remains blocked. This report records local +execution, not a new hosted Actions run or model acceptance. + +## Exact source and repair + +- Requested PR44 base: `ca9edde6850cd58ade0b7e534bb4f58e704c2c64`. +- Repair: `572ccbc45c0030a7eef9938702c4c3b2f9b97d27`. +- Repair tree: `265d802354146b20bd1bf3bd6d4d3e350f3589c3`. +- Branch: `qualification/pr44-native-workflow`. +- Repair parent is exactly the requested base; full stacked ancestry is retained. +- Pumas dependency remains pinned to `f87c3da8276a914a54c6f4f36d617bef9d9f424e`. + +The repair changes five files with 61 insertions. A shared test-only Tokio mutex +protects 52 Python fixture lifetimes across text, image and worker-contract +suites. Synchronous tests acquire it before the GIL; two asynchronous lifecycle +tests retain it across worker execution. The GIL can be released during imports +and worker execution and does not isolate process-global `sys.modules` or +patched worker functions. Runtime behavior and existing assertions are unchanged. +Quality Gates adds the full PyTorch-enabled inference library run while retaining +all focused checks, audit and traceability gates. + +The requested base failed seven of 682 tests in the ordinary parallel aggregate; +all 682 passed serially. Failures included replaced/missing module symbols, +altered envelope validation and worker-state interference. This demonstrates a +failure present at the requested head, not its historical introducing commit. + +## Executed checks + +| Check | Result | +| --- | --- | +| Baseline parallel inference aggregate | 675 passed, 7 failed | +| Baseline diagnostic serial inference aggregate | 682 passed | +| Repaired default parallel inference aggregate | 682 passed | +| Rebuilt inference binary, three separate 16-thread runs | 682 passed in each run | +| Inference library and tests, Clippy with warnings denied | Passed | +| Fourteen domain packages, unit/integration/doc targets | 566 passed, zero ignored | +| Node-engine and workflow-nodes, including doctests | 426 passed; one unit test and 14 doctests ignored | +| Frontend discovery, including command compatibility | 547 passed | +| All tooling test files | 45 passed | +| Full frontend lint, TypeScript, Rust formatting and diff whitespace | Passed | +| Production dependency audit | Zero vulnerabilities | +| Installed dependency tree | Passed; optional native addons absent | +| Critical lint, accessibility and explicit base-range traceability | Passed | +| Staged repair critical lint/accessibility/traceability | Passed, five paths and zero mapped impacts | +| Canonical image-workflow and scheduler-only guard scripts | Passed | + +The frontend invocation included command-related arguments but ran the complete +discovered suite; it was not a filtered run. Aggregate domain packages were +`pantograph-scheduler`, `pantograph-runtime-host-contracts`, +`pantograph-diagnostics-ledger`, `pantograph-media-conversion`, +`pantograph-dependency-environment-service`, `pantograph-dependency-planning`, +`pantograph-managed-dependencies`, `pantograph-runtime-registry`, +`pantograph-runtime-attribution`, `pantograph-runtime-identity`, +`pantograph-node-contracts`, `pantograph-inference-interface-contracts`, +`pantograph-path-security` and `pantograph-timing-contracts`. + +The source qualification commands include: + +```bash +cargo test --locked -p inference --features backend-pytorch --lib +cargo clippy --locked -p inference --features backend-pytorch --lib --tests -- -D warnings +cargo test --locked -p node-engine -p workflow-nodes +node --test scripts/*.test.mjs +npm run lint:full +npm run typecheck +npm audit --omit=dev --audit-level=high +npm ls --all +cargo fmt --all -- --check +TRACEABILITY_STAGED_ONLY=1 npm run lint:no-new +``` + +The earlier complete-range gate explicitly compared main +`4938e405c7f656365eefdca492774ccae110c90d` with the requested PR44 base. + +## Environment and native blockers + +The prepared environment is Debian GNU/Linux 13.6, with pinned Node 24.12.0, +npm 11.6.2, Rust 1.92.0 and Python 3.12.3. Builds used the existing two-job CPU +configuration and a 600-second timeout. In this environment, activate +`/workspace/pantograph-tools/activate.sh` before running checks. PyO3 test +execution also required the process-local `LD_LIBRARY_PATH` to include the +existing `/workspace/pantograph-tools/python/cpython-3.12.3-linux-x86_64-gnu/lib`. +The first inference executable exited 127 without that loader path; its build +had succeeded. No Python installation or system configuration was changed. + +`cargo test --locked -p pantograph-workflow-service` stopped in `ort-sys` while +fetching ONNX Runtime 1.24.2. No workflow-service tests executed. +After the parent reported the approved network allowance saved and republished, +exactly one follow-up HEAD request at 2026-10-04T02:53:35Z used a 15-second +connect timeout, 45-second total timeout, zero retries and no redirect following: + +```text +https://cdn.pyke.io/0/pyke:ort-rs/ms@1.24.2/x86_64-unknown-linux-gnu.tar.lzma2 +curl exit: 56 +CONNECT proxy status: 403 Forbidden +Origin response: none (http_code 000) +``` + +The existing task remained denied. No further request or native build followed. +A fresh environment with the updated policy is required before retrying. + +`scripts/check-tauri-command-state-tests.sh` stopped building `glib-sys` because +`glib-2.0 >= 2.70` pkg-config metadata was unavailable. Zero of its five IPC tests +executed. Read-only package and pkg-config probes confirmed these missing native +prerequisites from the repository's shared Ubuntu installer: + +```text +libayatana-appindicator3-dev +libglib2.0-dev +libgtk-3-dev +libjavascriptcoregtk-4.1-dev +librsvg2-dev +libsoup-3.0-dev +libwebkit2gtk-4.1-dev +libxdo-dev +protobuf-compiler +``` + +`build-essential`, compilers, make and `libssl-dev` are installed. The functional +`pkg-config` command is supplied by installed `pkgconf`/`pkgconf-bin`; the absent +`pkg-config` package name is not a missing tool. `protoc`, `tauri-driver`, +`WebKitWebDriver`, `Xvfb`, `dotnet`, `elixir` and `erl` were not found. +Neither DISPLAY nor WAYLAND_DISPLAY was set. No system packages were installed; +the repository installer targets Ubuntu, while this host is Debian. + +## Acceptance limits and handoff + +No actual desktop IPC round trip, GUI, model loading or model inference was +executed. Existing Tauri fixtures use MockRuntime dispatch; Python contract and +lifecycle fixtures use stubs, including tests whose names mention production or +real loaders. Passing these tests does not prove inference acceptance. +Model-dependent smokes still require canonical Pumas model/artifact IDs, a saved +image workflow and native/runtime prerequisites. No model downloads were made. +C#/BEAM/package acceptance, the full workspace feature matrix and native linkage +were not locally executed. Previously green CI was reported by the parent, +not independently re-queried here. + +Local detailed logs and the full text handoff remain in +`/workspace/pantograph-cache/qualification-*`. In particular, the before/after +logs are `qualification-inference-retry.log` and +`qualification-inference-fixed.log`; the blocker evidence is +`qualification-workflow-service.log`, `qualification-tauri-ipc.log`, +`qualification-onnx-policy-check.log` and its `.headers` companion; +`qualification-native-prerequisites.txt` records the read-only inventory. +These local files are not assumed to transfer to a fresh environment; this +tracked report preserves their results and exact source identities. + +The original verified `pr44-native-qualification.bundle` remains in the local +cache and contains repair 572ccbc on this branch, requiring the exact PR44 base. +This report is a documentation-only follow-up to that unchanged repair. The +parent owns independent review and coordinated integration. No PR44/47 metadata +or review requests are part of this publication. No credentials, permissions, +network/security, speed or paid settings were changed. From 9996ddfd8b4c15c11de4d7141e20e0165dd0f12c Mon Sep 17 00:00:00 2001 From: Jeremy Date: Sun, 4 Oct 2026 03:13:39 +0000 Subject: [PATCH 21/21] fix(a11y): require independent name evidence for snippet renders Address PR47 review comments 4175927978 and 4175927981 on exact base ca9edde6850cd58ade0b7e534bb4f58e704c2c64. RenderTag alone no longer counts as a role-button name. Keep ExpressionTag and direct-text evidence, explicit labels, native-button rules and reviewed-ignore policy unchanged. Use the parent-approved conservative static policy instead of implementing Svelte binding evaluation. Document that even nonempty local snippet-only labels need independent evidence. Test empty, nonempty, nested, shadowed, parameter, self/mutual recursive, unknown, optional and member calls, plus explicit labels and adjacent visible text/expressions. Scanning all 126 Svelte files produces no changed violations. Correct the dated dependency report with verified Quality Gates run 37170993215 and its successful dependency checks for ca9edde only. Retain historical evidence and pending GUI/package acceptance; do not attribute that hosted run to this repair. Validation on pinned Node 24.12.0/npm 11.6.2: - New tests against original scanner: 15 pass, 12 fail. - Repaired focused scanner suite: 27 pass; all tooling: 63 pass. - Frontend aggregate: 547 pass. - Full lint, typecheck, production build, npm ls --all, staged critical lint, accessibility, decision traceability and diff whitespace: pass. - Production audit: zero vulnerabilities. - Build emits an existing stale Browserslist data notice; no dependency change. No GUI/native/model checks, blocked download retries, external review requests or merge. This branch is directly atop ca9 and excludes fixture-lock 572ccbc. --- ...2026-10-03-frontend-dependency-security.md | 8 ++-- scripts/README.md | 15 +++++++ scripts/svelte-role-button-check.mjs | 8 ++-- scripts/svelte-role-button-check.test.mjs | 42 ++++++++++++++++++- 4 files changed, 65 insertions(+), 8 deletions(-) diff --git a/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-frontend-dependency-security.md b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-frontend-dependency-security.md index 81477aa9e..9ddaa48d9 100644 --- a/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-frontend-dependency-security.md +++ b/docs/plans/domain-architecture-and-multimodal/reports/2026-10-03-frontend-dependency-security.md @@ -1,6 +1,6 @@ # Frontend dependency security repair (2026-10-03) -Status: implemented and source-reviewed; exact-head hosted and GUI qualification remain pending. Parent: PR #20, `8a08b6a010ee62fbd3b29e193c65922c0769b928`. Standards reviewed at Coding-Standards `dcc56f26e884ade260770beceba2501d3746200d`; this is newer than the active plan's planning baseline, without changing plan or release authority. +Status: implemented and source-reviewed; dependency-audit checks passed on reviewed head `ca9edde6850cd58ade0b7e534bb4f58e704c2c64`. GUI and packaged-release qualification remain pending. Parent: PR #20, `8a08b6a010ee62fbd3b29e193c65922c0769b928`. Standards reviewed at Coding-Standards `dcc56f26e884ade260770beceba2501d3746200d`; this is newer than the active plan's planning baseline, without changing plan or release authority. ## Resolution and rationale @@ -22,7 +22,7 @@ A fresh production audit of the parent lock reported five affected package nodes - markdown-it: [GHSA-6v5v-wf23-fmfq](https://github.com/advisories/GHSA-6v5v-wf23-fmfq), [GHSA-253c-mchw-3w2r](https://github.com/advisories/GHSA-253c-mchw-3w2r). - svelte: [GHSA-f3cj-j4f6-wq85](https://github.com/advisories/GHSA-f3cj-j4f6-wq85), [GHSA-rcqx-6q8c-2c42](https://github.com/advisories/GHSA-rcqx-6q8c-2c42), [GHSA-9rmh-mm8f-r9h6](https://github.com/advisories/GHSA-9rmh-mm8f-r9h6), [GHSA-pr6f-5x2q-rwfp](https://github.com/advisories/GHSA-pr6f-5x2q-rwfp). -## Verification and limits +## Original slice verification and limits (historical) Local generation and clean install used Node 24.19.0/npm 11.9.0 because the pinned Node 24.12.0/npm 11.6.2 were unavailable. No global toolchain was changed. A clean hosted install and applicable checks using both declared versions are required before qualification; the local results do not waive this requirement. @@ -38,6 +38,6 @@ The source manifest/lock pair was independently reviewed before publication at s [Quality Gates run 37115665903](https://github.com/MrScripty/Pantograph/actions/runs/37115665903) confirms Node 24.12.0/npm 11.6.2, clean npm ci, zero production-audit findings, 540/540 frontend tests, typecheck and full lint. Its synthetic merge `e66f54bc7b3f2963956918a6a781fc3a2ecf47ca` has the same tree `1c51bc4dcce332846e550ecf6374864521a11099` as published source commit `0b5c0df663d76dc97f2ba3836d33ea2404ca79df`. The unchanged critical-lint failure remains visible. -That workflow lacked installed-tree and frontend-build checks. Add `npm ls --all` and `npm run build` to the existing dependency-audit job after clean installation and the production audit, preserving every existing command and aggregate gate. These checks prove resolver consistency and frontend asset compilation on the declared toolchain; they do not establish GUI or packaged-release acceptance. Exact-head hosted results for this workflow change remain pending. +That earlier workflow lacked installed-tree and frontend-build checks. The integration added `npm ls --all` and `npm run build` to the existing dependency-audit job after clean installation and the production audit, preserving every existing command and aggregate gate. [Quality Gates run 37170993215](https://github.com/MrScripty/Pantograph/actions/runs/37170993215), associated with reviewed head `ca9edde6850cd58ade0b7e534bb4f58e704c2c64`, completed successfully. Its Dependency audit job passed clean installation, the production audit, installed-tree verification and frontend asset compilation. These results qualify those checks for that head only; they do not establish GUI or packaged-release acceptance or qualify subsequent repair commits. -The hosted development-inclusive install audit reports 41 affected package nodes (1 low, 5 moderate, 35 high). Dependency maintainers must triage that separate follow-up before any broader security claim; this slice does not upgrade unrelated development tools or claim zero vulnerabilities across the complete graph. Headless/Runtime Separation results remain under observation, and the real-model GUI prerequisites above remain unavailable. +At the earlier hosted milestone, the development-inclusive install audit reported 41 affected package nodes (1 low, 5 moderate, 35 high). Dependency maintainers must triage that separate follow-up before any broader security claim; this slice does not upgrade unrelated development tools or claim zero vulnerabilities across the complete graph. Headless/Runtime Separation results were still under observation at that earlier milestone; this report does not use those suites to claim GUI or packaged-release acceptance. The real-model GUI prerequisites above remain unqualified. diff --git a/scripts/README.md b/scripts/README.md index c4d431ff6..af5b4ff74 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -219,3 +219,18 @@ hosted runners. It retains the existing GTK/WebKit/libsoup packages and installs uses `sudo apt-get` and changes the runner's system packages; do not treat it as a read-only check or run it on another computer without authorization. A failed install fails the step. It changes no Rust features, pins or verification gates. + +## Role-Button Name Evidence + +The parsed literal-role scanner accepts explicit `aria-label`/`aria-labelledby` +attributes or directly rendered descendant text and `ExpressionTag` evidence. +Snippet declarations are inert, and a `RenderTag` alone does not prove an +accessible name: local, nested, shadowed, recursive and external calls may +produce no text. The guard intentionally does not resolve snippet bindings or +execute calls. Even a nonempty local snippet used as the sole label needs an +explicit label attribute or independent rendered evidence to pass this gate. +This conservative policy avoids treating render-call source as visible content; +it preserves the existing expression-evidence policy rather than proving its +runtime value. Native-button rules and reviewed-ignore rules are unchanged. +Runtime accessibility and dynamically empty/hidden content still require UI +validation. diff --git a/scripts/svelte-role-button-check.mjs b/scripts/svelte-role-button-check.mjs index de472f84f..5590b4aea 100644 --- a/scripts/svelte-role-button-check.mjs +++ b/scripts/svelte-role-button-check.mjs @@ -7,9 +7,11 @@ const INTERACTIVE_SEMANTIC_TAGS = new Set(['button', 'a', 'input', 'select', 'te function hasRenderedName(node) { if (!node || typeof node !== 'object') return false; if (node.type === 'Text') return /[A-Za-z0-9]/.test(node.data); - if (node.type === 'ExpressionTag' || node.type === 'RenderTag') return true; - // A snippet declaration is inert until a RenderTag invokes it. - if (node.type === 'Comment' || node.type === 'SnippetBlock') return false; + if (node.type === 'ExpressionTag') return true; + // Snippet declarations are inert. Render calls may produce no text, including + // local, nested or recursive calls. Resolving Svelte bindings is outside this + // static guard: require a label or direct rendered text/expression evidence. + if (['Comment', 'SnippetBlock', 'RenderTag'].includes(node.type)) return false; if (node.type === 'RegularElement' && ['script', 'style'].includes(node.name)) return false; return ['nodes', 'fragment', 'consequent', 'alternate', 'body', 'fallback'].some((key) => { const value = node[key]; diff --git a/scripts/svelte-role-button-check.test.mjs b/scripts/svelte-role-button-check.test.mjs index dec79b9ff..b04810e7d 100644 --- a/scripts/svelte-role-button-check.test.mjs +++ b/scripts/svelte-role-button-check.test.mjs @@ -43,5 +43,45 @@ test('visible descendant text supplies a name, but handler expressions do not', test('an unused snippet declaration is not rendered name evidence', () => { assert.deepEqual(rules('
{#snippet label()}Select{/snippet}
'), ['role-button-accessible-name']); - assert.deepEqual(check('
{#snippet label()}Select{/snippet}{@render label()}
'), []); }); + +// This static guard deliberately does not resolve snippet calls or their bindings. +const renderOnlyCases = [ + ['empty local snippet', '{#snippet label()}{/snippet}{@render label()}'], + ['whitespace and comments', '{#snippet label()} \n{/snippet}{@render label()}'], + ['nonempty local snippet', '{#snippet label()}Select{/snippet}{@render label()}'], + ['nested unused snippet', '{#snippet label()}{#snippet unused()}Select{/snippet}{/snippet}{@render label()}'], + ['nested render call', '{#snippet inner()}Select{/snippet}{#snippet label()}{@render inner()}{/snippet}{@render label()}'], + ['shadowed local snippet', '{#snippet label()}Select{/snippet}{#if visible}{#snippet label()}{/snippet}{@render label()}{/if}'], + ['snippet parameter', '{#snippet wrapper(label)}{@render label()}{/snippet}{@render wrapper(label)}'], + ['self recursion', '{#snippet label()}{@render label()}{/snippet}{@render label()}'], + ['mutual recursion', '{#snippet first()}{@render second()}{/snippet}{#snippet second()}{@render first()}{/snippet}{@render first()}'], + ['unknown snippet', '{@render unknown()}'], + ['optional snippet', '{@render unknown?.()}'], + ['member call', '{@render snippets.label()}'], +]; + +for (const [name, content] of renderOnlyCases) { + test(`render-only content requires independent name evidence: ${name}`, () => { + const violations = check(`\n
${content}
`); + assert.deepEqual(violations.map(({ rule }) => rule), ['role-button-accessible-name']); + assert.equal(violations[0].file, 'Fixture.svelte'); + assert.equal(violations[0].line, 2); + }); +} + +for (const attribute of ['aria-label="Select"', 'aria-labelledby="label"']) { + test(`explicit ${attribute} supports render-only content`, () => { + for (const [, content] of renderOnlyCases) { + assert.deepEqual(check(`
${content}
`), []); + } + }); +} + +for (const name of ['Select', 'Select', '{label}', '{label}']) { + test(`adjacent rendered evidence remains accepted: ${name}`, () => { + for (const [, content] of renderOnlyCases) { + assert.deepEqual(check(`
${content}${name}
`), []); + } + }); +}