You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by as...@apache.org on 2026/09/05 17:19:37 UTC

(maka) branch main updated: refactor(artifacts): make child outputs exact and retire stale lifecycle state (#4808)

This is an automated email from the ASF dual-hosted git repository.

Astro-Han pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/maka.git


The following commit(s) were added to refs/heads/main by this push:
     new 02f97c16d7 refactor(artifacts): make child outputs exact and retire stale lifecycle state (#4808)
02f97c16d7 is described below

commit 02f97c16d76e644d5b565889701958293ff7b5fb
Author: AstroHan <le...@outlook.com>
AuthorDate: Sun Sep 6 01:19:30 2026 +0800

    refactor(artifacts): make child outputs exact and retire stale lifecycle state (#4808)
    
    * fix(storage): ignore retired artifact metadata
    
    * fix(runtime): return only child output artifacts
    
    * refactor(core): remove unused artifact sources
    
    * refactor(storage): remove retired capture sweep
    
    * refactor(artifacts): replace tombstones with physical deletion
    
    * fix(desktop): allow deleting all artifacts
    
    * refactor(storage): remove self-managed artifact writes
    
    * refactor(artifacts): remove dead change notifications
    
    * refactor(storage): retire artifact status metadata
    
    * refactor(storage): remove global artifact deletion
    
    * refactor(artifacts): require explicit source ownership
    
    * refactor(storage): use artifact id as metadata key
    
    * refactor(storage): remove artifact metadata readiness shim
    
    * refactor(storage): remove artifact first-read lock state
    
    * refactor(storage): remove artifact metadata interface
    
    * refactor(storage): remove unscoped attachment reads
    
    * refactor(storage): remove unscoped artifact reads
    
    * refactor(storage): remove artifact purge journal
    
    * refactor(storage): remove artifact store facade type
    
    * fix(storage): discard invalid artifact metadata
    
    * refactor(storage): remove artifact publication recovery
    
    * refactor(storage): remove artifact recovery API
    
    * refactor(storage): inline artifact lock layout
    
    * test(artifacts): align disposable residue gates
    
    * test(desktop): use visible artifact story fixture
    
    * fix(storage): preserve durable artifacts across v1 upgrades
    
    * fix(desktop): refresh visible artifact panes after publication
    
    * fix(artifacts): protect workflow-owned evidence from user deletion
    
    * fix(storage): retain artifact cleanup obligations until durable removal
    
    * fix(runtime-host): fence artifact publication with session admission
    
    * test(runtime-host): keep recap retries safe after rejected artifact deletion
    
    * refactor(artifacts): let session cleanup own projection image reclamation
    
    * fix(desktop): refresh artifact counts on background workbar tabs
    
    * test(desktop): use supported tab for background artifact coverage
    
    * test(desktop): decouple artifact count checks from tab name formatting
---
 apps/desktop/README.md                             |   2 +-
 .../runtime-host-artifacts-ipc-main.test.ts        |   6 +-
 .../__tests__/workbar-services-adapter.test.ts     |   5 +-
 .../src/main/runtime-host-artifacts-ipc-main.ts    |  32 +-
 apps/desktop/src/main/runtime-host-boot.ts         |   1 -
 apps/desktop/src/preload/bridge-contract.d.ts      |   4 +-
 apps/desktop/src/preload/preload.ts                |  13 +-
 .../desktop/src/renderer/features/workbar/ports.ts |   9 +-
 .../src/renderer/features/workbar/testing.ts       |   1 -
 .../workbar/tools/artifacts/artifact-pane.tsx      |  62 +-
 .../workbar/tools/artifacts/artifact-preview.tsx   |  19 +-
 .../features/workbar/ui/workbar-surface.tsx        |   1 +
 apps/desktop/src/renderer/locales/artifact-copy.ts |  32 +-
 .../platform/desktop/create-workbar-services.ts    |   3 +-
 .../src/renderer/styles/workbar/artifacts.css      |   6 +-
 .../accessibility-runtime-surfaces.stories.tsx     |  67 +-
 apps/desktop/stories/session-workbar.stories.tsx   |   3 -
 docs/astryx-surface-file-inventory.md              |   2 +-
 packages/core/src/__tests__/artifacts.test.ts      |  33 +-
 packages/core/src/artifacts.ts                     |  64 +-
 .../src/__tests__/artifact-coordinator.test.ts     |   8 +-
 .../src/__tests__/artifact-protocol.test.ts        |  23 +-
 .../src/__tests__/artifact-two-client-uds.test.ts  |  69 +-
 .../src/__tests__/execution-artifacts.test.ts      | 142 ++-
 .../__tests__/execution-model-composition.test.ts  |   2 -
 .../src/__tests__/fixtures/artifact-v1.ts          |  48 +
 .../src/__tests__/root-turn-coordinator.test.ts    |   1 -
 .../src/__tests__/session-admission-gate.test.ts   |   7 +-
 .../session-catalog-two-client-uds.test.ts         |   6 +-
 .../__tests__/session-effect-coordinator.test.ts   |  36 +-
 .../session-effect-two-client-uds.test.ts          |   2 +-
 .../session-retirement-coordinator.test.ts         | 115 +++
 .../session-revision-graph-references.test.ts      |   6 +-
 .../session-revision-two-client-uds.test.ts        |   2 -
 packages/runtime-host/src/protocol/artifact.ts     |  32 +-
 packages/runtime-host/src/protocol/index.ts        |   5 +-
 .../src/server/artifact-coordinator.ts             |  15 +-
 .../runtime-host/src/server/execution-artifacts.ts |  65 +-
 .../src/server/execution-composition.ts            |  49 +-
 .../src/server/execution-model-composition.ts      |   8 +-
 .../src/server/session-admission-gate.ts           |  12 +-
 .../src/server/session-effect-coordinator.ts       |   2 +-
 .../server/session-revision-graph-references.ts    |   5 +-
 .../src/__tests__/deep-research-tools.test.ts      |  15 +-
 .../durable-tool-result-projection.test.ts         |  31 +-
 .../runtime/src/__tests__/session-manager.test.ts  |  28 +-
 packages/runtime/src/deep-research-tools.ts        |  27 +-
 .../runtime/src/durable-tool-result-projection.ts  |  12 +-
 packages/runtime/src/session-manager.ts            |  32 +-
 .../src/__tests__/artifact-attachments.test.ts     |  91 +-
 .../storage/src/__tests__/artifact-store.test.ts   | 820 ++++++-----------
 .../storage/src/__tests__/artifact-stores.test.ts  | 307 +++----
 .../src/__tests__/artifact-writer-lock.test.ts     |  89 +-
 .../fixtures/artifact-writer-lock-holder.ts        |  14 +-
 .../src/__tests__/operational-state-backup.test.ts |  16 +-
 .../src/__tests__/operational-state-store.test.ts  |  79 +-
 .../src/__tests__/sqlite-artifact-metadata.test.ts |  51 +-
 packages/storage/src/artifact-attachments.ts       |  76 +-
 packages/storage/src/artifact-metadata-codec.ts    |  54 +-
 .../storage/src/artifact-metadata-repository.ts    |  32 -
 packages/storage/src/artifact-storage-layout.ts    |  35 -
 packages/storage/src/artifact-store.ts             | 965 ++-------------------
 packages/storage/src/artifact-stores.ts            | 122 +--
 packages/storage/src/artifact-writer-lock.ts       |   2 +-
 packages/storage/src/operational-state-backup.ts   |  12 +-
 packages/storage/src/session-bundle-policy.ts      |   2 +-
 packages/storage/src/sqlite-artifact-metadata.ts   |  45 +-
 packages/storage/src/sqlite-artifact-schema.ts     |  46 +-
 68 files changed, 1429 insertions(+), 2599 deletions(-)

diff --git a/apps/desktop/README.md b/apps/desktop/README.md
index d611cb4285..93e60739b1 100644
--- a/apps/desktop/README.md
+++ b/apps/desktop/README.md
@@ -139,7 +139,7 @@ Sub-folders hold OS-facing implementations such as `browser/`, `computer-use/`,
 Three patterns, all rooted in preload's `maka` namespace. Channel names are `<domain>:<action>`.
 
 - **Request/response** — `ipcRenderer.invoke('<domain>:<action>', …args)` in preload ↔ `ipcMain.handle('<domain>:<action>', …)`. Runtime domains are projected by `runtime-host-*-ipc-main.ts`; OS-facing client domains use a focused `*-ipc-main.ts` module.
-- **Main→renderer push** — main sends through the safe-send guard (`safeSendToRenderer` via `mainWindowController.send`), not raw `webContents.send` (which throws when the window/`webContents` is destroyed); preload subscribes via `ipcRenderer.on` and returns an unsubscribe fn (e.g. `sessions:changed`, `scheduled-tasks:changed`, `artifacts:changed`). The guard checks both the `BrowserWindow` and its `webContents` before delivery. Route every new main-window push through it.
+- **Main→renderer push** — main sends through the safe-send guard (`safeSendToRenderer` via `mainWindowController.send`), not raw `webContents.send` (which throws when the window/`webContents` is destroyed); preload subscribes via `ipcRenderer.on` and returns an unsubscribe fn (e.g. `sessions:changed`, `scheduled-tasks:changed`). The guard checks both the `BrowserWindow` and its `webContents` before delivery. Route every new main-window push through it.
 - **Renderer→main fire-and-forget** — `ipcRenderer.send('<domain>:<action>', …)` in preload ↔ `ipcMain.on('<domain>:<action>', …)`. Used when no response is needed (e.g. `browser:active-session`, `browser:setViewport`).
 
 Adding a new IPC surface: if extracting, write the `*-ipc-main.ts` exporting a `register*Ipc(...)`, import it in `main.ts`, and call it inside `registerIpc()`; add the matching method to the `maka` namespace in `preload.ts`; add the method to the `window.maka` type in `src/global.d.ts` (the renderer's typed bridge — without it, renderer calls get a TS error); keep the `<domain>:<action>` channel naming. A handler file that isn't registered in `registerIpc()` compiles but never mounts.
diff --git a/apps/desktop/src/main/__tests__/runtime-host-artifacts-ipc-main.test.ts b/apps/desktop/src/main/__tests__/runtime-host-artifacts-ipc-main.test.ts
index 6c452d5922..283d33f342 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-artifacts-ipc-main.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-artifacts-ipc-main.test.ts
@@ -63,7 +63,6 @@ function attachmentReadHandler(
       streamArtifact,
     } as never,
     mainWindowController: {} as never,
-    sendToRenderer() {},
     showItemInFolder() {},
   });
   const handler = handlers.get("attachments:readBytes");
@@ -78,7 +77,6 @@ test("Runtime Host Artifact IPC preserves previews and streams complete exports"
   const content = Buffer.alloc(70 * 1024, 5);
   const handlers = new Map<string, Handler>();
   const opened: string[] = [];
-  const events: unknown[] = [];
   const artifact = {
     id: "artifact-1",
     sessionId: "session-1",
@@ -105,7 +103,7 @@ test("Runtime Host Artifact IPC preserves previews and streams complete exports"
       return { ok: false, reason: "unsupported_mime" };
     },
     async deleteArtifact() {
-      return { kind: "deleted", artifact: { ...artifact, status: "deleted" } };
+      return { kind: "deleted" };
     },
     async streamArtifact(
       _sessionId: string,
@@ -128,7 +126,6 @@ test("Runtime Host Artifact IPC preserves previews and streams complete exports"
       mainWindowController: {
         showSaveDialog: async () => ({ canceled: false, filePath: savedPath }),
       } as never,
-      sendToRenderer: (_channel, event) => events.push(event),
       showItemInFolder: (path) => opened.push(path),
       presentationRoot,
     });
@@ -159,7 +156,6 @@ test("Runtime Host Artifact IPC preserves previews and streams complete exports"
     assert.deepEqual(await readFile(opened[0]!), content);
 
     await handlers.get("artifacts:delete")?.({}, "session-1", "artifact-1");
-    assert.equal((events[0] as { reason: string }).reason, "deleted");
   } finally {
     await rm(root, { recursive: true, force: true });
   }
diff --git a/apps/desktop/src/main/__tests__/workbar-services-adapter.test.ts b/apps/desktop/src/main/__tests__/workbar-services-adapter.test.ts
index 05ba07dce8..6e92fabf11 100644
--- a/apps/desktop/src/main/__tests__/workbar-services-adapter.test.ts
+++ b/apps/desktop/src/main/__tests__/workbar-services-adapter.test.ts
@@ -37,7 +37,6 @@ function createBridgeRecorder(): {
     'browser.setViewport',
     'browser.onState',
     'browser.onLive',
-    'artifacts.subscribeChanges',
     'inspector.subscribeUsageChanges',
   ]);
   // Adapters that reshape a bridge answer need one to reshape.
@@ -132,11 +131,10 @@ describe('createDesktopWorkbarServices', () => {
     services.browser.subscribeState(eventHandler)();
     services.browser.subscribeLive(eventHandler)();
 
-    await services.artifacts.list('s', { includeDeleted: true });
+    await services.artifacts.list('s');
     await services.artifacts.readText('s', 'a');
     await services.artifacts.readBinary('s', 'a');
     await services.artifacts.delete('s', 'a');
-    services.artifacts.subscribeChanges(eventHandler)();
     await services.artifacts.openPath('s', 'a');
     await services.artifacts.saveAs('s', 'a');
 
@@ -207,7 +205,6 @@ describe('createDesktopWorkbarServices', () => {
         'artifacts.readText',
         'artifacts.readBinary',
         'artifacts.delete',
-        'artifacts.subscribeChanges',
         'app.openArtifactPath',
         'app.saveArtifactAs',
         'inspector.trace',
diff --git a/apps/desktop/src/main/runtime-host-artifacts-ipc-main.ts b/apps/desktop/src/main/runtime-host-artifacts-ipc-main.ts
index 4032293ef4..f5c3b7200b 100644
--- a/apps/desktop/src/main/runtime-host-artifacts-ipc-main.ts
+++ b/apps/desktop/src/main/runtime-host-artifacts-ipc-main.ts
@@ -39,7 +39,6 @@ interface RuntimeHostArtifactsIpcDeps {
   readonly ipcMain: ReconnectableReadIpcMain;
   readonly client: DesktopRuntimeHostClient;
   readonly mainWindowController: ReturnType<typeof createMainWindowController>;
-  readonly sendToRenderer: (channel: string, ...args: unknown[]) => void;
   readonly showItemInFolder: (path: string) => void;
   readonly presentationRoot?: string;
 }
@@ -61,16 +60,7 @@ export function registerRuntimeHostArtifactsIpc(
   handleReconnectableRead(
     deps.ipcMain,
     "artifacts:list",
-    async (
-      _event,
-      sessionId: string,
-      options?: { includeDeleted?: boolean },
-    ) => {
-      const artifacts = await deps.client.listArtifacts(sessionId);
-      return options?.includeDeleted
-        ? artifacts
-        : artifacts.filter(({ status }) => status !== "deleted");
-    },
+    (_event, sessionId: string) => deps.client.listArtifacts(sessionId),
   );
   handleReconnectableRead(
     deps.ipcMain,
@@ -86,23 +76,15 @@ export function registerRuntimeHostArtifactsIpc(
   );
   deps.ipcMain.handle(
     "artifacts:delete",
-    async (_event, sessionId: string, artifactId: string) => {
-      const result = await deps.client.deleteArtifact(sessionId, artifactId);
-      deps.sendToRenderer("artifacts:changed", {
-        reason: "deleted",
-        artifactId,
-        sessionId,
-        ts: Date.now(),
-      });
-      return result;
-    },
+    (_event, sessionId: string, artifactId: string) =>
+      deps.client.deleteArtifact(sessionId, artifactId),
   );
   registerRuntimeHostAttachmentPreviewIpc(deps);
   deps.ipcMain.handle(
     "app:openArtifactPath",
     async (_event, sessionId: string, artifactId: string) => {
       const artifact = await deps.client.getArtifact(sessionId, artifactId);
-      if (!artifact || artifact.status === "deleted") {
+      if (!artifact) {
         return { ok: false as const, reason: "missing" as const };
       }
       try {
@@ -128,7 +110,6 @@ export function registerRuntimeHostArtifactsIpc(
     ): Promise<ArtifactSaveResult> => {
       const artifact = await deps.client.getArtifact(sessionId, artifactId);
       if (!artifact) return { ok: false, reason: "not_found" };
-      if (artifact.status === "deleted") return { ok: false, reason: "deleted" };
       const result = await deps.mainWindowController.showSaveDialog({
         title: `另存为 ${artifact.name}`,
         defaultPath: artifact.name,
@@ -161,10 +142,7 @@ export function registerRuntimeHostAttachmentPreviewIpc(
     "attachments:readBytes",
     async (_event, sessionId: string, artifactId: string) => {
       const artifact = await deps.client.getArtifact(sessionId, artifactId);
-      if (
-        !artifact ||
-        artifact.status === "deleted"
-      ) {
+      if (!artifact) {
         return { ok: false as const, reason: "not_found" };
       }
       const preview = resolveArtifactImagePreview(artifact);
diff --git a/apps/desktop/src/main/runtime-host-boot.ts b/apps/desktop/src/main/runtime-host-boot.ts
index 7caee1cf13..192e465f8f 100644
--- a/apps/desktop/src/main/runtime-host-boot.ts
+++ b/apps/desktop/src/main/runtime-host-boot.ts
@@ -1499,7 +1499,6 @@ function registerHostClientIpc(
     ipcMain: scopedIpc,
     client,
     mainWindowController,
-    sendToRenderer,
     showItemInFolder: (path) => shell.showItemInFolder(path),
   });
   registerRuntimeHostOAuthIpc({
diff --git a/apps/desktop/src/preload/bridge-contract.d.ts b/apps/desktop/src/preload/bridge-contract.d.ts
index 70b940e6b6..779c6651a6 100644
--- a/apps/desktop/src/preload/bridge-contract.d.ts
+++ b/apps/desktop/src/preload/bridge-contract.d.ts
@@ -76,7 +76,6 @@ import type {
 } from '@maka/core/git-review';
 import type {
   ArtifactBinaryReadResult,
-  ArtifactChangedEvent,
   ArtifactDescriptor,
   ArtifactSaveResult,
   ArtifactTextReadResult,
@@ -1810,11 +1809,10 @@ export interface MakaBridge {
     getState(): Promise<E2eFixtureState | null>;
   };
   artifacts: {
-    list(sessionId: string, opts?: { includeDeleted?: boolean }): Promise<ArtifactDescriptor[]>;
+    list(sessionId: string): Promise<ArtifactDescriptor[]>;
     readText(sessionId: string, artifactId: string): Promise<ArtifactTextReadResult>;
     readBinary(sessionId: string, artifactId: string): Promise<ArtifactBinaryReadResult>;
     delete(sessionId: string, artifactId: string): Promise<void>;
-    subscribeChanges(handler: (event: ArtifactChangedEvent) => void): () => void;
   };
   skills: {
     list(host?: DesktopRuntimeHostRef): Promise<SkillEntry[]>;
diff --git a/apps/desktop/src/preload/preload.ts b/apps/desktop/src/preload/preload.ts
index e0f03716b4..7041eda546 100644
--- a/apps/desktop/src/preload/preload.ts
+++ b/apps/desktop/src/preload/preload.ts
@@ -162,7 +162,6 @@ import type {
 } from '@maka/core/git-review';
 import type {
   ArtifactBinaryReadResult,
-  ArtifactChangedEvent,
   ArtifactDescriptor,
   ArtifactSaveResult,
   ArtifactTextReadResult,
@@ -3603,8 +3602,8 @@ const makaBridge = {
     },
   },
   artifacts: {
-    list(sessionId: string, opts?: { includeDeleted?: boolean }): Promise<ArtifactDescriptor[]> {
-      return invokeProjectedSessionRuntimeHost('artifacts:list', sessionId, opts);
+    list(sessionId: string): Promise<ArtifactDescriptor[]> {
+      return invokeProjectedSessionRuntimeHost('artifacts:list', sessionId);
     },
     readText(sessionId: string, artifactId: string): Promise<ArtifactTextReadResult> {
       return invokeSessionRuntimeHost('artifacts:readText', sessionId, artifactId);
@@ -3615,14 +3614,6 @@ const makaBridge = {
     delete(sessionId: string, artifactId: string): Promise<void> {
       return invokeSessionRuntimeHost('artifacts:delete', sessionId, artifactId);
     },
-    subscribeChanges(handler: (event: ArtifactChangedEvent) => void): () => void {
-      return subscribeEveryRuntimeHostEvent('artifacts:changed', (scope, event: ArtifactChangedEvent) =>
-        handler({
-          ...event,
-          sessionId: recordRuntimeHostSessionScope(scope, event.sessionId),
-        }),
-      );
-    },
   },
   skills: {
     list(host?: DesktopRuntimeHostRef): Promise<SkillEntry[]> {
diff --git a/apps/desktop/src/renderer/features/workbar/ports.ts b/apps/desktop/src/renderer/features/workbar/ports.ts
index 34ac1d89e7..4ee8b1cd65 100644
--- a/apps/desktop/src/renderer/features/workbar/ports.ts
+++ b/apps/desktop/src/renderer/features/workbar/ports.ts
@@ -24,7 +24,6 @@ import type {
 } from '@maka/core/events';
 import type {
   ArtifactBinaryReadResult,
-  ArtifactChangedEvent,
   ArtifactDescriptor,
   ArtifactSaveResult,
   ArtifactTextReadResult,
@@ -126,10 +125,7 @@ export type WorkbarOpenArtifactResult =
     };
 
 export interface WorkbarArtifactsService {
-  list(
-    sessionId: string,
-    options?: { includeDeleted?: boolean },
-  ): Promise<ArtifactDescriptor[]>;
+  list(sessionId: string): Promise<ArtifactDescriptor[]>;
   readText(
     sessionId: string,
     artifactId: string,
@@ -139,9 +135,6 @@ export interface WorkbarArtifactsService {
     artifactId: string,
   ): Promise<ArtifactBinaryReadResult>;
   delete(sessionId: string, artifactId: string): Promise<void>;
-  subscribeChanges(
-    handler: (event: ArtifactChangedEvent) => void,
-  ): WorkbarUnsubscribe;
   openPath(
     sessionId: string,
     artifactId: string,
diff --git a/apps/desktop/src/renderer/features/workbar/testing.ts b/apps/desktop/src/renderer/features/workbar/testing.ts
index 28604bef18..11c1e45a56 100644
--- a/apps/desktop/src/renderer/features/workbar/testing.ts
+++ b/apps/desktop/src/renderer/features/workbar/testing.ts
@@ -102,7 +102,6 @@ export function createFakeWorkbarServices(
       readText: async () => ({ ok: false, reason: 'not_found' }),
       readBinary: async () => ({ ok: false, reason: 'not_found' }),
       delete: async () => undefined,
-      subscribeChanges: noopSubscription,
       openPath: async () => ({ ok: false, reason: 'missing' }),
       saveAs: async () => ({ ok: false, reason: 'canceled' }),
     },
diff --git a/apps/desktop/src/renderer/features/workbar/tools/artifacts/artifact-pane.tsx b/apps/desktop/src/renderer/features/workbar/tools/artifacts/artifact-pane.tsx
index 89fd331441..b7c27cb40c 100644
--- a/apps/desktop/src/renderer/features/workbar/tools/artifacts/artifact-pane.tsx
+++ b/apps/desktop/src/renderer/features/workbar/tools/artifacts/artifact-pane.tsx
@@ -60,12 +60,11 @@ import {
   Copy,
   Trash2,
 } from '@maka/ui/icons';
-import type { ArtifactDescriptor, ArtifactKind } from '@maka/core/artifacts';
+import { canUserDeleteArtifact, type ArtifactDescriptor, type ArtifactKind } from '@maka/core/artifacts';
 import type { UiLocale } from '@maka/core/ui-locale';
 import { formatRelativeTimestamp } from '@maka/core/relative-time';
 import { generalizedErrorMessageForLocale, redactSecrets } from '@maka/core/redaction';
 import {
-  Badge,
   Banner,
   Button,
   MoreMenu,
@@ -84,6 +83,7 @@ import { useWorkbarServices } from '../../services-context.js';
 
 export function ArtifactPane(props: {
   sessionId: string;
+  refreshEnabled: boolean;
   onCountChange?: (count: number) => void;
   onDismiss?: () => void;
 }) {
@@ -133,7 +133,7 @@ export function ArtifactPane(props: {
     setSelectedId(null);
   }, [sessionId]);
 
-  const refresh = useCallback(async () => {
+  const refresh = useCallback(async (showFailureToast = true) => {
     const requestSeq = ++artifactListRequestSeqRef.current;
     if (!sessionId) {
       recordsSessionIdRef.current = undefined;
@@ -143,9 +143,7 @@ export function ArtifactPane(props: {
       return;
     }
     try {
-      const next = await artifacts.list(sessionId, {
-        includeDeleted: true,
-      });
+      const next = await artifacts.list(sessionId);
       if (artifactPaneMountedRef.current && requestSeq === artifactListRequestSeqRef.current) {
         recordsSessionIdRef.current = sessionId;
         setRecordsSessionId(sessionId);
@@ -160,7 +158,7 @@ export function ArtifactPane(props: {
           recordsSessionIdRef.current = undefined;
           setRecordsSessionId(undefined);
           setRecords([]);
-        } else {
+        } else if (showFailureToast) {
           toast.error(copy.pane.refreshFailed, message, undefined, { sessionId });
         }
       }
@@ -168,22 +166,22 @@ export function ArtifactPane(props: {
   }, [artifacts, copy, locale, sessionId, toast]);
 
   useEffect(() => {
-    void refresh();
-    if (!sessionId) return;
-    // Keep the list in sync without polling. The
-    // backend emits `{ reason: 'created' | 'deleted' | 'purged' }` on the
-    // `artifacts:changed` channel; we just re-list since the list is bounded
-    // (one session's worth) and the metadata is already in memory on main.
-    const unsubscribe = artifacts.subscribeChanges((event) => {
-      if (event.sessionId === sessionId) {
-        void refresh();
-      }
-    });
+    if (!props.refreshEnabled) return;
+    let stopped = false;
+    let timer: ReturnType<typeof setTimeout> | undefined;
+    // Writeback can commit after the terminal Session event. Read the existing
+    // catalog while the workbar is visible, including its background file tab's count.
+    const poll = async () => {
+      await refresh(false);
+      if (!stopped) timer = setTimeout(() => void poll(), 2_000);
+    };
+    void poll();
     return () => {
+      stopped = true;
+      clearTimeout(timer);
       artifactListRequestSeqRef.current += 1;
-      unsubscribe();
     };
-  }, [artifacts, sessionId, refresh]);
+  }, [props.refreshEnabled, sessionId, refresh]);
 
   const activeRecords = useMemo(
     () => (recordsSessionId === sessionId ? filterUserVisibleArtifacts(records) : []),
@@ -194,7 +192,6 @@ export function ArtifactPane(props: {
     props.onCountChange?.(activeRecords.length);
   }, [activeRecords.length, props.onCountChange]);
 
-  // 已删除墓碑记录保持可选,用于展示明确失败态;只有选中 id 彻底消失时才回退到最新 live artifact。
   useEffect(() => {
     if (activeRecords.length === 0) {
       if (selectedId !== null) setSelectedId(null);
@@ -356,6 +353,7 @@ export function ArtifactPane(props: {
   async function deleteArtifact(artifactId: string) {
     const actionSessionId = sessionId;
     const record = activeRecords.find((entry) => entry.id === artifactId);
+    if (!record || !canUserDeleteArtifact(record)) return;
     const name = record?.name ?? copy.pane.fallbackName;
     const ok = await toast.confirm({
       title: copy.pane.deleteTitle(name),
@@ -494,7 +492,6 @@ export function ArtifactPane(props: {
                   // ArrowUp/Down.
                   tabIndex={-1}
                   data-selected={record.id === selectedId ? 'true' : 'false'}
-                  data-deleted={record.status === 'deleted' ? 'true' : 'false'}
                   onClick={() => openPreview(record.id)}
                   label={record.name}
                   icon={(
@@ -508,9 +505,6 @@ export function ArtifactPane(props: {
                       <span className="maka-artifact-row-time">
                         {formatRelativeTimestamp(record.createdAt, Date.now(), locale)}
                       </span>
-                      {record.status === 'deleted' && (
-                        <Badge variant="error" className="maka-artifact-row-badge" label={copy.pane.deletedBadge} />
-                      )}
                     </span>
                   )}
                 />
@@ -566,22 +560,14 @@ export function ArtifactPane(props: {
                       onClick: () => void runArtifactAction(`${previewRecord.id}:copy`, () => copyText(previewRecord.id)),
                     }]
                   : []),
-                { type: 'divider' as const },
-                {
-                  label:
-                    previewRecord.source === 'deep_research' ||
-                    previewRecord.source === 'tool_result_archive'
-                      ? copy.pane.deleteReadOnly
-                      : copy.pane.delete,
+                ...(canUserDeleteArtifact(previewRecord) ? [{ type: 'divider' as const }, {
+                  label: copy.pane.delete,
                   icon: <Trash2 size={ICON_SIZE.control} aria-hidden="true" />,
-                  isDisabled:
-                    previewRecord.source === 'deep_research' ||
-                    previewRecord.source === 'tool_result_archive',
                   onClick: () => void runArtifactAction(
                     `${previewRecord.id}:delete`,
                     () => deleteArtifact(previewRecord.id),
                   ),
-                },
+                }] : []),
               ]}
             />
           </header>
@@ -619,8 +605,6 @@ function saveArtifactFailureCopy(reason: string, copy: ArtifactCopy): string {
       return copy.pane.saveFailures.not_found;
     case 'not_allowed':
       return copy.pane.saveFailures.not_allowed;
-    case 'deleted':
-      return copy.pane.saveFailures.deleted;
     case 'write_failed':
       return copy.pane.saveFailures.write_failed;
     default:
@@ -661,5 +645,5 @@ function KindIcon(props: { kind: ArtifactKind }) {
    formatter cache. Removed; we import the shared helper. */
 
 function preferredArtifactSelectionId(records: readonly ArtifactDescriptor[]): string | null {
-  return (records.find((record) => record.status !== 'deleted') ?? records[0])?.id ?? null;
+  return records[0]?.id ?? null;
 }
diff --git a/apps/desktop/src/renderer/features/workbar/tools/artifacts/artifact-preview.tsx b/apps/desktop/src/renderer/features/workbar/tools/artifacts/artifact-preview.tsx
index 7de1f6adcc..4cf547a0d9 100644
--- a/apps/desktop/src/renderer/features/workbar/tools/artifacts/artifact-preview.tsx
+++ b/apps/desktop/src/renderer/features/workbar/tools/artifacts/artifact-preview.tsx
@@ -104,11 +104,7 @@ function FilePreview(props: { record: ArtifactDescriptor; copy: ArtifactCopy })
   const result = useTextRead(props.record.sessionId, props.record.id);
   if (result.state === 'loading') return <PreviewLoading label={props.copy.preview.loadingFile} />;
   if (!result.value.ok) return <TextFailureCard record={props.record} reason={result.value.reason} copy={props.copy} />;
-  const text =
-    props.record.source === 'tool_result_archive' && result.value.text.length <= TEXT_DISPLAY_LIMIT_BYTES
-      ? prettyArchiveJson(result.value.text)
-      : result.value.text;
-  return <TextFilePreview name={props.record.name} text={text} copy={props.copy} />;
+  return <TextFilePreview name={props.record.name} text={result.value.text} copy={props.copy} />;
 }
 
 function TextFilePreview(props: { name: string; text: string; copy: ArtifactCopy }) {
@@ -156,14 +152,6 @@ function TextFilePreview(props: { name: string; text: string; copy: ArtifactCopy
   );
 }
 
-function prettyArchiveJson(text: string): string {
-  try {
-    return JSON.stringify(JSON.parse(text), null, 2);
-  } catch {
-    return text;
-  }
-}
-
 function DiffPreview(props: { record: ArtifactDescriptor; copy: ArtifactCopy }) {
   const result = useTextRead(props.record.sessionId, props.record.id);
   if (result.state === 'loading') return <PreviewLoading label={props.copy.preview.loadingDiff} />;
@@ -387,11 +375,6 @@ function failureCopyText(record: ArtifactDescriptor, reason: TextFailureReason,
         status: 'info',
         ...copy.preview.tooLarge(record.sizeBytes),
       };
-    case 'deleted':
-      return {
-        status: 'info',
-        ...copy.preview.deleted,
-      };
   }
 }
 
diff --git a/apps/desktop/src/renderer/features/workbar/ui/workbar-surface.tsx b/apps/desktop/src/renderer/features/workbar/ui/workbar-surface.tsx
index 1ab2a63fa6..e0077ba8ba 100644
--- a/apps/desktop/src/renderer/features/workbar/ui/workbar-surface.tsx
+++ b/apps/desktop/src/renderer/features/workbar/ui/workbar-surface.tsx
@@ -524,6 +524,7 @@ export function WorkbarSurface(props: {
             <Suspense fallback={<WorkbarPanelLoading label={copy.files} />}>
               <ArtifactPane
                 sessionId={props.sessionId}
+                refreshEnabled={!props.hidden && panelVisible}
                 onCountChange={setArtifactCount}
                 onDismiss={() => props.onDismissPanel(placement)}
               />
diff --git a/apps/desktop/src/renderer/locales/artifact-copy.ts b/apps/desktop/src/renderer/locales/artifact-copy.ts
index ad73b11379..7f83467fd7 100644
--- a/apps/desktop/src/renderer/locales/artifact-copy.ts
+++ b/apps/desktop/src/renderer/locales/artifact-copy.ts
@@ -34,7 +34,6 @@ export type ArtifactCopy = {
     deleteTitle(name: string): string;
     deleteDescription: string;
     delete: string;
-    deleteReadOnly: string;
     cancel: string;
     deleted(name: string): string;
     deleteFailed(name: string): string;
@@ -43,7 +42,6 @@ export type ArtifactCopy = {
     retrying: string;
     retry: string;
     listAria: string;
-    deletedBadge: string;
     previewNamed(name: string): string;
     empty: string;
     emptyHint: string;
@@ -52,7 +50,7 @@ export type ArtifactCopy = {
     openInFinder: string;
     saveAs: string;
     copy: string;
-    saveFailures: Record<'not_found' | 'not_allowed' | 'deleted' | 'write_failed' | 'default', string>;
+    saveFailures: Record<'not_found' | 'not_allowed' | 'write_failed' | 'default', string>;
     actionFailed: string;
   };
   preview: {
@@ -72,7 +70,6 @@ export type ArtifactCopy = {
     readFailed: ReasonCopy;
     notAllowed: ReasonCopy;
     tooLarge(bytes: number): ReasonCopy;
-    deleted: ReasonCopy;
     unsupportedMime: ReasonCopy;
   };
   registry: {
@@ -96,13 +93,13 @@ const ARTIFACT_COPY = {
     pane: {
       refreshFailed: '刷新生成文件失败', openFailed: '无法在 Finder 中打开生成文件', copyFailed: '复制失败',
       readTextFailed: '无法读取生成文件文本内容。', copied: '已复制生成文件文本', saved: '已另存生成文件', saveFailed: '另存失败',
-      fallbackName: '生成文件', deleteTitle: (name) => `删除 "${name}"`, deleteDescription: '软删除:在记录中标记为已删除,文件保留 6 小时可恢复。',
-      delete: '删除', deleteReadOnly: '删除(只读文件)', cancel: '取消', deleted: (name) => `已删除 ${name}`, deleteFailed: (name) => `删除 ${name} 失败`, panelAria: '生成文件预览面板',
-      listLoadFailed: '生成文件列表载入失败', retrying: '重试中…', retry: '重试', listAria: '生成文件列表', deletedBadge: '已删除',
+      fallbackName: '生成文件', deleteTitle: (name) => `删除 "${name}"`, deleteDescription: '永久删除此生成文件及其记录,无法恢复。',
+      delete: '删除', cancel: '取消', deleted: (name) => `已删除 ${name}`, deleteFailed: (name) => `删除 ${name} 失败`, panelAria: '生成文件预览面板',
+      listLoadFailed: '生成文件列表载入失败', retrying: '重试中…', retry: '重试', listAria: '生成文件列表',
       previewNamed: (name) => `预览 ${name}`, empty: '暂无生成文件', emptyHint: '助手生成文件后会显示在这里。',
       back: '返回生成文件列表', moreActions: (name) => `${name} 的更多操作`,
       openInFinder: '在 Finder 中打开', saveAs: '另存为', copy: '复制',
-      saveFailures: { not_found: '生成文件不存在。', not_allowed: '生成文件路径检查未通过。', deleted: '生成文件已删除,不能另存。', write_failed: '目标位置无法写入。', default: '无法保存生成文件。' },
+      saveFailures: { not_found: '生成文件不存在。', not_allowed: '生成文件路径检查未通过。', write_failed: '目标位置无法写入。', default: '无法保存生成文件。' },
       actionFailed: '生成文件操作失败,请稍后重试。',
     },
     preview: {
@@ -116,7 +113,6 @@ const ARTIFACT_COPY = {
       readFailed: { title: '无法读取生成文件', description: '路径可能已被外部删除。请通过更多菜单「在 Finder 中打开」检查文件位置。' },
       notAllowed: { title: '无法读取生成文件', description: '路径检查未通过,文件已不在允许预览的生成文件目录内。' },
       tooLarge: (bytes) => ({ title: '文件超出预览大小', description: `${bytes} 字节超过文本预览阈值,请通过更多菜单打开或另存完整内容。` }),
-      deleted: { title: '此生成文件已删除', description: '预览已停止。如需查看原文件请使用「在 Finder 中打开」。' },
       unsupportedMime: { title: '不支持的文件类型', description: '该生成文件的 MIME 类型不在内联预览允许列表中。请使用工具栏「在 Finder 中打开」或「另存为」。' },
     },
     registry: {
@@ -132,13 +128,13 @@ const ARTIFACT_COPY = {
     pane: {
       refreshFailed: '重新整理生成檔案失敗', openFailed: '無法在 Finder 中開啟生成檔案', copyFailed: '複製失敗',
       readTextFailed: '無法讀取生成檔案文本內容。', copied: '已複製生成檔案文本', saved: '已另存生成檔案', saveFailed: '另存失敗',
-      fallbackName: '生成檔案', deleteTitle: (name) => `刪除 "${name}"`, deleteDescription: '軟刪除:在記錄中標記為已刪除,檔案保留 6 小時可恢復。',
-      delete: '刪除', deleteReadOnly: '刪除(只讀檔案)', cancel: '取消', deleted: (name) => `已刪除 ${name}`, deleteFailed: (name) => `刪除 ${name} 失敗`, panelAria: '生成檔案預覽面板',
-      listLoadFailed: '生成檔案列表載入失敗', retrying: '重試中…', retry: '重試', listAria: '生成檔案列表', deletedBadge: '已刪除',
+      fallbackName: '生成檔案', deleteTitle: (name) => `刪除 "${name}"`, deleteDescription: '永久刪除此生成檔案及其記錄,無法復原。',
+      delete: '刪除', cancel: '取消', deleted: (name) => `已刪除 ${name}`, deleteFailed: (name) => `刪除 ${name} 失敗`, panelAria: '生成檔案預覽面板',
+      listLoadFailed: '生成檔案列表載入失敗', retrying: '重試中…', retry: '重試', listAria: '生成檔案列表',
       previewNamed: (name) => `預覽 ${name}`, empty: '暫無生成檔案', emptyHint: '助手生成檔案後會顯示在這裡。',
       back: '返回生成檔案列表', moreActions: (name) => `${name} 的更多操作`,
       openInFinder: '在 Finder 中開啟', saveAs: '另存為', copy: '複製',
-      saveFailures: { not_found: '生成檔案不存在。', not_allowed: '生成檔案路徑檢查未透過。', deleted: '生成檔案已刪除,不能另存。', write_failed: '目標位置無法寫入。', default: '無法儲存生成檔案。' },
+      saveFailures: { not_found: '生成檔案不存在。', not_allowed: '生成檔案路徑檢查未透過。', write_failed: '目標位置無法寫入。', default: '無法儲存生成檔案。' },
       actionFailed: '生成檔案操作失敗,請稍後重試。',
     },
     preview: {
@@ -152,7 +148,6 @@ const ARTIFACT_COPY = {
       readFailed: { title: '無法讀取生成檔案', description: '路徑可能已被外部刪除。請透過更多選單「在 Finder 中開啟」檢查檔案位置。' },
       notAllowed: { title: '無法讀取生成檔案', description: '路徑檢查未透過,檔案已不在允許預覽的生成檔案目錄內。' },
       tooLarge: (bytes) => ({ title: '檔案超出預覽大小', description: `${bytes} 位元組超過文本預覽閾值,請透過更多選單開啟或另存完整內容。` }),
-      deleted: { title: '此生成檔案已刪除', description: '預覽已停止。如需檢視原檔案請使用「在 Finder 中開啟」。' },
       unsupportedMime: { title: '不支援的檔案型別', description: '該生成檔案的 MIME 型別不在內聯預覽允許列表中。請使用工具欄「在 Finder 中開啟」或「另存為」。' },
     },
     registry: {
@@ -168,13 +163,13 @@ const ARTIFACT_COPY = {
     pane: {
       refreshFailed: 'Failed to refresh generated files', openFailed: 'Could not show generated file in Finder', copyFailed: 'Copy failed',
       readTextFailed: 'Could not read the generated file as text.', copied: 'Generated file text copied', saved: 'Generated file saved as', saveFailed: 'Save as failed',
-      fallbackName: 'generated file', deleteTitle: (name) => `Delete "${name}"`, deleteDescription: 'Soft delete: mark this record as deleted and keep the file recoverable for 6 hours.',
-      delete: 'Delete', deleteReadOnly: 'Delete (read-only file)', cancel: 'Cancel', deleted: (name) => `Deleted ${name}`, deleteFailed: (name) => `Failed to delete ${name}`, panelAria: 'Generated file preview panel',
-      listLoadFailed: 'Failed to load generated files', retrying: 'Retrying…', retry: 'Retry', listAria: 'Generated files', deletedBadge: 'Deleted',
+      fallbackName: 'generated file', deleteTitle: (name) => `Delete "${name}"`, deleteDescription: 'Permanently delete this generated file and its record. This cannot be undone.',
+      delete: 'Delete', cancel: 'Cancel', deleted: (name) => `Deleted ${name}`, deleteFailed: (name) => `Failed to delete ${name}`, panelAria: 'Generated file preview panel',
+      listLoadFailed: 'Failed to load generated files', retrying: 'Retrying…', retry: 'Retry', listAria: 'Generated files',
       previewNamed: (name) => `Preview ${name}`, empty: 'No generated files', emptyHint: 'Files generated by the assistant appear here.',
       back: 'Back to generated files', moreActions: (name) => `More actions for ${name}`,
       openInFinder: 'Show in Finder', saveAs: 'Save as', copy: 'Copy',
-      saveFailures: { not_found: 'The generated file does not exist.', not_allowed: 'The generated file failed the path safety check.', deleted: 'Deleted generated files cannot be saved.', write_failed: 'The destination is not writable.', default: 'Could not save the generated file.' },
+      saveFailures: { not_found: 'The generated file does not exist.', not_allowed: 'The generated file failed the path safety check.', write_failed: 'The destination is not writable.', default: 'Could not save the generated file.' },
       actionFailed: 'The generated file action failed. Try again later.',
     },
     preview: {
@@ -188,7 +183,6 @@ const ARTIFACT_COPY = {
       readFailed: { title: 'Could not read generated file', description: 'The file may have been deleted externally. Use “Show in Finder” in the More menu to check its location.' },
       notAllowed: { title: 'Could not read generated file', description: 'The path safety check failed because the file is no longer inside the allowed generated-files directory.' },
       tooLarge: (bytes) => ({ title: 'File exceeds preview size', description: `${bytes} bytes exceeds the text preview limit. Use the More menu to open or save the complete file.` }),
-      deleted: { title: 'This generated file was deleted', description: 'The preview has stopped. Use “Show in Finder” to inspect the original file.' },
       unsupportedMime: { title: 'Unsupported file type', description: 'This generated file’s MIME type is not allowed for inline preview. Use “Show in Finder” or “Save as”.' },
     },
     registry: {
diff --git a/apps/desktop/src/renderer/platform/desktop/create-workbar-services.ts b/apps/desktop/src/renderer/platform/desktop/create-workbar-services.ts
index 8313458a27..39a6e490c5 100644
--- a/apps/desktop/src/renderer/platform/desktop/create-workbar-services.ts
+++ b/apps/desktop/src/renderer/platform/desktop/create-workbar-services.ts
@@ -68,14 +68,13 @@ export function createDesktopWorkbarServices(
       subscribeLive: (handler) => bridge.browser.onLive(handler),
     },
     artifacts: {
-      list: (sessionId, options) => bridge.artifacts.list(sessionId, options),
+      list: (sessionId) => bridge.artifacts.list(sessionId),
       readText: (sessionId, artifactId) =>
         bridge.artifacts.readText(sessionId, artifactId),
       readBinary: (sessionId, artifactId) =>
         bridge.artifacts.readBinary(sessionId, artifactId),
       delete: (sessionId, artifactId) =>
         bridge.artifacts.delete(sessionId, artifactId),
-      subscribeChanges: (handler) => bridge.artifacts.subscribeChanges(handler),
       openPath: (sessionId, artifactId) =>
         bridge.app.openArtifactPath(sessionId, artifactId),
       saveAs: (sessionId, artifactId) =>
diff --git a/apps/desktop/src/renderer/styles/workbar/artifacts.css b/apps/desktop/src/renderer/styles/workbar/artifacts.css
index 949161aa8b..3b87bcffc8 100644
--- a/apps/desktop/src/renderer/styles/workbar/artifacts.css
+++ b/apps/desktop/src/renderer/styles/workbar/artifacts.css
@@ -77,7 +77,7 @@
   box-shadow: inset 0 0 0 var(--focus-ring-width) var(--focus-ring);
 }
 
-.maka-artifact-row:active:not([data-deleted="true"]) {
+.maka-artifact-row:active {
   background: var(--state-selected-bg);
 }
 
@@ -95,10 +95,6 @@
   }
 }
 
-.maka-artifact-row[data-deleted="true"] {
-  opacity: var(--opacity-muted);
-}
-
 .maka-artifact-row-icon {
   display: inline-flex;
   align-items: center;
diff --git a/apps/desktop/stories/accessibility-runtime-surfaces.stories.tsx b/apps/desktop/stories/accessibility-runtime-surfaces.stories.tsx
index ffc98ef4ed..199e8e5434 100644
--- a/apps/desktop/stories/accessibility-runtime-surfaces.stories.tsx
+++ b/apps/desktop/stories/accessibility-runtime-surfaces.stories.tsx
@@ -21,7 +21,7 @@ import type { Decorator, Meta, StoryObj } from '@storybook/react-vite';
 import { useRef, useState } from 'react';
 import { expect, fn, userEvent, waitFor, within } from 'storybook/test';
 import type { ArtifactDescriptor } from '@maka/core/artifacts';
-import { ToastProvider } from '@maka/ui';
+import { Button, ToastProvider } from '@maka/ui';
 import {
   createFakeWorkbarServices,
   createSessionWorkbarPanelsState,
@@ -65,7 +65,7 @@ function withWorkbarServices(overrides: Partial<WorkbarServices>): Decorator {
   );
 }
 
-function WorkbarToolSurface(props: { kind: 'terminal' | 'browser' | 'files' }) {
+function WorkbarToolSurface(props: { kind: 'terminal' | 'browser' | 'files'; hidden?: boolean; backgroundFiles?: boolean }) {
   const terminalRef = 'pty:storybook';
   const right = props.kind === 'terminal'
     ? createSessionWorkbarTabsState(
@@ -87,9 +87,9 @@ function WorkbarToolSurface(props: { kind: 'terminal' | 'browser' | 'files' }) {
         <div className="mainColumn" />
         <WorkbarSurface
           sessionId="runtime-surface"
-          hidden={false}
+          hidden={props.hidden ?? false}
           onDismissPanel={noop}
-          panelsState={createSessionWorkbarPanelsState(right)}
+          panelsState={createSessionWorkbarPanelsState(props.backgroundFiles ? openStaticSessionWorkbarTab(right, 'review') : right)}
           rightCollapsed={false}
           bottomOpen={false}
           onActivateTab={noop}
@@ -244,8 +244,7 @@ const htmlArtifact: ArtifactDescriptor = {
   kind: 'html',
   sizeBytes: 256,
   mimeType: 'text/html',
-  source: 'fixture',
-  status: 'live',
+  source: 'subagent_writeback',
 };
 
 function RemoteProjectDirectoryStory() {
@@ -268,11 +267,18 @@ function RemoteProjectDirectoryStory() {
   );
 }
 
+let htmlPublished = false;
+const listHtmlArtifacts = fn(async () => htmlPublished ? [htmlArtifact] : []);
+
 export const HtmlArtifact: Story = {
+  beforeEach: () => {
+    htmlPublished = false;
+    listHtmlArtifacts.mockClear();
+  },
   decorators: [
     withWorkbarServices({
       artifacts: {
-        list: async () => [htmlArtifact],
+        list: listHtmlArtifacts,
         readText: async () => ({
           ok: true,
           text: [
@@ -285,7 +291,6 @@ export const HtmlArtifact: Story = {
         }),
         readBinary: async () => ({ ok: false, reason: 'unsupported_mime' }),
         delete: async () => undefined,
-        subscribeChanges: unsubscribe,
         openPath: async () => ({ ok: false, reason: 'missing' }),
         saveAs: async () => ({ ok: false, reason: 'canceled' }),
       },
@@ -294,8 +299,11 @@ export const HtmlArtifact: Story = {
   render: () => <WorkbarToolSurface kind="files" />,
   play: async ({ canvasElement }) => {
     const canvas = within(canvasElement);
+    await waitFor(() => expect(listHtmlArtifacts).toHaveBeenCalled());
+    await expect(canvas.queryByRole('option', { name: /agent-report\.html/ })).toBeNull();
+    htmlPublished = true;
     await userEvent.click(
-      await canvas.findByRole('option', { name: /agent-report\.html/ }),
+      await canvas.findByRole('option', { name: /agent-report\.html/ }, { timeout: 10_000 }),
     );
     const frame = await canvas.findByTitle('生成文件预览 · agent-report.html');
     await expect(frame).toHaveAttribute('sandbox', 'allow-scripts');
@@ -303,6 +311,47 @@ export const HtmlArtifact: Story = {
   },
 };
 
+function HiddenArtifactSurface() {
+  const [hidden, setHidden] = useState(true);
+  return <>
+    <Button label={hidden ? 'Show files' : 'Hide files'} onClick={() => setHidden(!hidden)} />
+    <WorkbarToolSurface kind="files" hidden={hidden} />
+  </>;
+}
+
+export const HiddenArtifact: Story = {
+  decorators: HtmlArtifact.decorators,
+  beforeEach: HtmlArtifact.beforeEach,
+  render: () => <HiddenArtifactSurface />,
+  play: async ({ canvasElement }) => {
+    const canvas = within(canvasElement);
+    htmlPublished = true;
+    await new Promise(resolve => setTimeout(resolve, 2_200));
+    await expect(listHtmlArtifacts).not.toHaveBeenCalled();
+    await userEvent.click(canvas.getByRole('button', { name: 'Show files' }));
+    await canvas.findByRole('option', { name: /agent-report\.html/ });
+    await userEvent.click(canvas.getByRole('button', { name: 'Hide files' }));
+    const reads = listHtmlArtifacts.mock.calls.length;
+    await new Promise(resolve => setTimeout(resolve, 2_200));
+    await expect(listHtmlArtifacts.mock.calls.length).toBe(reads);
+  },
+};
+
+export const BackgroundArtifactCount: Story = {
+  decorators: HtmlArtifact.decorators,
+  beforeEach: HtmlArtifact.beforeEach,
+  render: () => <WorkbarToolSurface kind="files" backgroundFiles />,
+  play: async ({ canvasElement }) => {
+    const canvas = within(canvasElement);
+    const tab = await canvas.findByRole('tab', { name: /^生成文件/ });
+    await expect(within(tab).getByText('0', { exact: true })).toBeVisible();
+    await expect(tab).toHaveAttribute('aria-selected', 'false');
+    htmlPublished = true;
+    await within(tab).findByText('1', { exact: true }, { timeout: 10_000 });
+    await expect(tab).toHaveAttribute('aria-selected', 'false');
+  },
+};
+
 export const RemoteProjectDirectory: Story = {
   decorators: [
     withScopedMakaBridge({
diff --git a/apps/desktop/stories/session-workbar.stories.tsx b/apps/desktop/stories/session-workbar.stories.tsx
index f12f2f7989..168856188c 100644
--- a/apps/desktop/stories/session-workbar.stories.tsx
+++ b/apps/desktop/stories/session-workbar.stories.tsx
@@ -159,7 +159,6 @@ const artifacts: ArtifactRecord[] = [
     sizeBytes: 4_812,
     mimeType: 'text/x-diff',
     source: 'tool_result',
-    status: 'live',
   },
   {
     id: 'artifact-notes',
@@ -172,7 +171,6 @@ const artifacts: ArtifactRecord[] = [
     sizeBytes: 1_284,
     mimeType: 'text/markdown',
     source: 'tool_result',
-    status: 'live',
   },
 ];
 
@@ -787,7 +785,6 @@ function bridge(options: {
       readText: async (_sessionId: string, id: string) => ({ ok: true, text: artifactText[id] ?? '' }),
       readBinary: async () => ({ ok: false, reason: 'unsupported_mime' }),
       delete: async () => undefined,
-      subscribeChanges: unsubscribe,
       openPath: async () => ({ ok: true, opened: 'artifact-patch' }),
       saveAs: async () => ({ ok: true, saved: 'slice-9-conversation.diff' }),
     },
diff --git a/docs/astryx-surface-file-inventory.md b/docs/astryx-surface-file-inventory.md
index 274904ffde..8a09a3f13a 100644
--- a/docs/astryx-surface-file-inventory.md
+++ b/docs/astryx-surface-file-inventory.md
@@ -77,7 +77,7 @@ Wiki bar: Design Conventions · API Use-the-System · Theming · Container Paddi
 | `apps/desktop/src/renderer/features/usage/ui/usage-settings-view.tsx` | other | Banner, Button, SegmentedControl, SegmentedControlItem, Selector, Switch, Tab, TabList, TextInput, Tooltip | aligned — uses Astryx (Banner, Button, SegmentedControl, SegmentedControlItem, Selector, Switch, Tab, TabList) | aligned |
 | `apps/desktop/src/renderer/features/usage/ui/usage-stats-table.tsx` | other | Card, EmptyState, Table | aligned — uses Astryx (Card, EmptyState, Table) | aligned |
 | `apps/desktop/src/renderer/features/workbar/services-context.tsx` | shell-chrome-or-panel | none | aligned — no raw controls; no Astryx JSX usage | aligned |
-| `apps/desktop/src/renderer/features/workbar/tools/artifacts/artifact-pane.tsx` | shell-chrome-or-panel | Badge, Banner, Button, EmptyState, MoreMenu | aligned — uses Astryx (Badge, Banner, Button, EmptyState, MoreMenu) | aligned |
+| `apps/desktop/src/renderer/features/workbar/tools/artifacts/artifact-pane.tsx` | shell-chrome-or-panel | Banner, Button, EmptyState, MoreMenu | aligned — uses Astryx (Banner, Button, EmptyState, MoreMenu) | aligned |
 | `apps/desktop/src/renderer/features/workbar/tools/artifacts/artifact-preview-registry-shell.tsx` | shell-chrome-or-panel | Banner, Button, Spinner | aligned — uses Astryx (Banner, Button, Spinner) | aligned |
 | `apps/desktop/src/renderer/features/workbar/tools/artifacts/artifact-preview.tsx` | shell-chrome-or-panel | Banner, Button, CodeBlock, Spinner | aligned — uses Astryx (Banner, Button, CodeBlock, Spinner) | aligned |
 | `apps/desktop/src/renderer/features/workbar/tools/browser/browser-panel.tsx` | shell-chrome-or-panel | EmptyState, IconButton, TextInput, Toolbar, Tooltip | aligned — uses Astryx (EmptyState, IconButton, TextInput, Toolbar, Tooltip) | aligned |
diff --git a/packages/core/src/__tests__/artifacts.test.ts b/packages/core/src/__tests__/artifacts.test.ts
index 53f5d4b2fb..93557757ce 100644
--- a/packages/core/src/__tests__/artifacts.test.ts
+++ b/packages/core/src/__tests__/artifacts.test.ts
@@ -22,7 +22,9 @@ import { describe, test } from 'node:test';
 import {
   ARTIFACT_ENTITY_ID_MAX_CHARS,
   ARTIFACT_TURN_KEY_MAX_CHARS,
+  ARTIFACT_SOURCES,
   canUserDeleteArtifact,
+  isArtifactChildResultOutput,
   isArtifactSharedSessionReadable,
   isArtifactUserVisible,
   isArtifactTurnKey,
@@ -56,28 +58,27 @@ describe('Artifact turn key', () => {
   });
 });
 
-describe('Artifact user-delete policy', () => {
-  test('protects durable evidence while allowing ordinary and unattributed artifacts', () => {
-    assert.equal(canUserDeleteArtifact({ source: 'deep_research' }), false);
-    assert.equal(canUserDeleteArtifact({ source: 'user_upload' }), true);
-    assert.equal(canUserDeleteArtifact({ source: undefined }), true);
+describe('Artifact source policy', () => {
+  test('keeps workflow-owned evidence out of independent user deletion', () => {
+    for (const source of ARTIFACT_SOURCES) {
+      assert.equal(
+        canUserDeleteArtifact({ source }),
+        source === 'tool_result' || source === 'user_upload',
+        source,
+      );
+    }
+  });
+  test('includes produced outputs in child results without leaking internal artifacts', () => {
+    assert.equal(isArtifactChildResultOutput({ source: 'tool_result' }), true);
+    assert.equal(isArtifactChildResultOutput({ source: 'subagent_writeback' }), true);
+    assert.equal(isArtifactChildResultOutput({ source: 'tool_result_archive' }), false);
+    assert.equal(isArtifactChildResultOutput({ source: 'user_upload' }), false);
   });
-});
 
-describe('Artifact source policy', () => {
   test('keeps projection artifacts internal, durable, and readable in shared sessions', () => {
     const projection = { source: 'tool_result_projection' as const };
 
-    assert.equal(canUserDeleteArtifact(projection), false);
     assert.equal(isArtifactUserVisible(projection), false);
     assert.equal(isArtifactSharedSessionReadable(projection), true);
   });
-
-  test('preserves unattributed artifact defaults', () => {
-    const unattributed = { source: undefined };
-
-    assert.equal(canUserDeleteArtifact(unattributed), true);
-    assert.equal(isArtifactUserVisible(unattributed), true);
-    assert.equal(isArtifactSharedSessionReadable(unattributed), false);
-  });
 });
diff --git a/packages/core/src/artifacts.ts b/packages/core/src/artifacts.ts
index 9fd6bcf5c2..48c058ca76 100644
--- a/packages/core/src/artifacts.ts
+++ b/packages/core/src/artifacts.ts
@@ -90,25 +90,14 @@ export const ARTIFACT_SOURCES = [
   'tool_result',
   'tool_result_projection',
   'tool_result_archive',
-  'synthesis_cache_block',
-  'history_compact_block',
-  'history_compact_source',
-  'provider_request_capture',
   'subagent_writeback',
   'deep_research',
   'user_upload',
-  'export',
-  'snapshot',
   'session_effect',
-  'fixture',
 ] as const;
 
 export type ArtifactSource = (typeof ARTIFACT_SOURCES)[number];
 
-export const ARTIFACT_STATUSES = ['live', 'deleted'] as const;
-
-export type ArtifactStatus = (typeof ARTIFACT_STATUSES)[number];
-
 export const ARTIFACT_ENTITY_ID_MAX_CHARS = 128;
 export const ARTIFACT_TURN_KEY_MAX_CHARS = 512;
 
@@ -138,9 +127,8 @@ export interface ArtifactDescriptor {
   kind: ArtifactKind;
   sizeBytes: number;
   mimeType?: string;
-  source?: ArtifactSource;
+  source: ArtifactSource;
   summary?: string;
-  status: ArtifactStatus;
 }
 
 export interface ArtifactRecord extends ArtifactDescriptor {
@@ -163,48 +151,37 @@ const ARTIFACT_SOURCE_POLICIES = {
   tool_result: { userDeletable: true, userVisible: false, sharedReadable: true },
   tool_result_projection: { userDeletable: false, userVisible: false, sharedReadable: true },
   tool_result_archive: { userDeletable: false, userVisible: false, sharedReadable: false },
-  synthesis_cache_block: { userDeletable: true, userVisible: false, sharedReadable: false },
-  history_compact_block: { userDeletable: true, userVisible: false, sharedReadable: false },
-  history_compact_source: { userDeletable: true, userVisible: false, sharedReadable: false },
-  // Historical only: nothing produces these any more. The policy stays so the
-  // records already on disk keep decoding and stay deletable.
-  provider_request_capture: { userDeletable: true, userVisible: false, sharedReadable: false },
   subagent_writeback: { userDeletable: false, userVisible: true, sharedReadable: false },
   deep_research: { userDeletable: false, userVisible: true, sharedReadable: false },
   user_upload: { userDeletable: true, userVisible: false, sharedReadable: true },
-  export: { userDeletable: true, userVisible: true, sharedReadable: false },
-  snapshot: { userDeletable: true, userVisible: true, sharedReadable: false },
   session_effect: { userDeletable: false, userVisible: false, sharedReadable: false },
-  fixture: { userDeletable: true, userVisible: true, sharedReadable: false },
 } as const satisfies Record<ArtifactSource, ArtifactSourcePolicy>;
 
-export function canUserDeleteArtifact(record: Pick<ArtifactRecord, 'source'>): boolean {
-  return record.source === undefined || ARTIFACT_SOURCE_POLICIES[record.source].userDeletable;
-}
+const CHILD_RESULT_OUTPUT_SOURCES = new Set<ArtifactSource>([
+  'tool_result',
+  'tool_result_projection',
+  'subagent_writeback',
+  'deep_research',
+]);
 
 export function isArtifactUserVisible(record: Pick<ArtifactRecord, 'source'>): boolean {
-  return record.source === undefined || ARTIFACT_SOURCE_POLICIES[record.source].userVisible;
+  return ARTIFACT_SOURCE_POLICIES[record.source].userVisible;
 }
 
-export function isArtifactSharedSessionReadable(record: Pick<ArtifactRecord, 'source'>): boolean {
-  return record.source !== undefined && ARTIFACT_SOURCE_POLICIES[record.source].sharedReadable;
+/** Visibility does not grant permission to destroy evidence owned by a runtime workflow. */
+export function canUserDeleteArtifact(record: Pick<ArtifactRecord, 'source'>): boolean {
+  return ARTIFACT_SOURCE_POLICIES[record.source].userDeletable;
 }
 
-export type ArtifactChangedReason = 'created' | 'deleted' | 'purged';
+export function isArtifactSharedSessionReadable(record: Pick<ArtifactRecord, 'source'>): boolean {
+  return ARTIFACT_SOURCE_POLICIES[record.source].sharedReadable;
+}
 
-export interface ArtifactChangedEvent {
-  reason: ArtifactChangedReason;
-  artifactId: string;
-  sessionId: string;
-  ts: number;
+export function isArtifactChildResultOutput(record: Pick<ArtifactRecord, 'source'>): boolean {
+  return CHILD_RESULT_OUTPUT_SOURCES.has(record.source);
 }
 
-export type ArtifactReadFailureReason =
-  | 'not_found'
-  | 'too_large'
-  | 'read_failed'
-  | 'not_allowed'
-  | 'deleted';
+export type ArtifactReadFailureReason = 'not_found' | 'too_large' | 'read_failed' | 'not_allowed';
 
 export type ArtifactBinaryReadFailureReason = ArtifactReadFailureReason | 'unsupported_mime';
 
@@ -216,12 +193,7 @@ export type ArtifactBinaryReadResult =
   | { ok: true; base64: string; mimeType: string }
   | { ok: false; reason: ArtifactBinaryReadFailureReason };
 
-export type ArtifactSaveFailureReason =
-  | 'canceled'
-  | 'not_found'
-  | 'not_allowed'
-  | 'deleted'
-  | 'write_failed';
+export type ArtifactSaveFailureReason = 'canceled' | 'not_found' | 'not_allowed' | 'write_failed';
 
 export type ArtifactSaveResult =
   | { ok: true; saved: string }
diff --git a/packages/runtime-host/src/__tests__/artifact-coordinator.test.ts b/packages/runtime-host/src/__tests__/artifact-coordinator.test.ts
index 409f0c6490..8e0dd1e7a5 100644
--- a/packages/runtime-host/src/__tests__/artifact-coordinator.test.ts
+++ b/packages/runtime-host/src/__tests__/artifact-coordinator.test.ts
@@ -44,7 +44,6 @@ test('Artifact ingest is connection-bound, replay-safe, and commits one durable
   assert.ok(owner);
   try {
     const store = await openInteractiveArtifactStoreForWrite(owner.lease);
-    await store.recover();
     let now = 0;
     const coordinator = new HostArtifactCoordinator(
       store,
@@ -271,7 +270,6 @@ test('Artifact mutation failure requests Host drain and fails closed', async ()
   assert.ok(owner);
   try {
     const store = await openInteractiveArtifactStoreForWrite(owner.lease);
-    await store.recover();
     await store.create({
       id: 'artifact-1',
       sessionId: 'session-1',
@@ -279,6 +277,7 @@ test('Artifact mutation failure requests Host drain and fails closed', async ()
       name: 'artifact.txt',
       kind: 'file',
       content: 'durable',
+      source: 'tool_result',
       now: 1,
     });
 
@@ -345,7 +344,6 @@ test('Artifact query streams complete content in bounded ordered chunks', async
   assert.ok(owner);
   try {
     const store = await openInteractiveArtifactStoreForWrite(owner.lease);
-    await store.recover();
     const content = Buffer.alloc(ARTIFACT_READ_CHUNK_MAX_BYTES + 17, 7);
     await store.create({
       id: 'artifact-large',
@@ -354,6 +352,7 @@ test('Artifact query streams complete content in bounded ordered chunks', async
       name: 'large.bin',
       kind: 'file',
       content,
+      source: 'tool_result',
       now: 1,
     });
     const coordinator = new HostArtifactCoordinator(
@@ -408,7 +407,6 @@ test('Session Guests can read only shared attachment Artifacts from their grante
   assert.ok(owner);
   try {
     const store = await openInteractiveArtifactStoreForWrite(owner.lease);
-    await store.recover();
     await store.create({
       id: 'shared-image',
       sessionId: 'session-1',
@@ -426,7 +424,7 @@ test('Session Guests can read only shared attachment Artifacts from their grante
       turnId: 'turn-1',
       name: 'private.txt',
       kind: 'file',
-      source: 'provider_request_capture',
+      source: 'session_effect',
       content: Buffer.from('private'),
       now: 2,
     });
diff --git a/packages/runtime-host/src/__tests__/artifact-protocol.test.ts b/packages/runtime-host/src/__tests__/artifact-protocol.test.ts
index aa4391e2b1..906289e27f 100644
--- a/packages/runtime-host/src/__tests__/artifact-protocol.test.ts
+++ b/packages/runtime-host/src/__tests__/artifact-protocol.test.ts
@@ -55,6 +55,11 @@ describe('Artifact protocol', () => {
     assert.doesNotThrow(() =>
       request('artifact.delete', { sessionId: 'session-1', artifactId: 'artifact-1' }),
     );
+    assert.doesNotThrow(() => response('artifact.delete', { kind: 'deleted' }));
+    assert.throws(
+      () => response('artifact.delete', { kind: 'deleted', artifact: validArtifact() }),
+      isInvalidFrame,
+    );
 
     for (const input of [
       { kind: 'list_start', sessionId: 'session-1', includeDeleted: false },
@@ -242,13 +247,6 @@ describe('Artifact protocol', () => {
 
   test('keeps operation failures closed and typed', () => {
     assert.doesNotThrow(() => failure('artifact.delete', 'not_found', 'Artifact was not found'));
-    assert.doesNotThrow(() =>
-      failure(
-        'artifact.delete',
-        'operation_conflict',
-        'Protected runtime evidence cannot be deleted through Runtime Host',
-      ),
-    );
     assert.doesNotThrow(() =>
       failure('artifact.query', 'persistence_failed', 'Artifact projection is unavailable'),
     );
@@ -257,6 +255,9 @@ describe('Artifact protocol', () => {
       () => failure('artifact.query', 'operation_conflict', 'Protected runtime evidence'),
       isInvalidFrame,
     );
+    assert.doesNotThrow(() =>
+      failure('artifact.delete', 'operation_conflict', 'Protected runtime evidence'),
+    );
     assert.throws(() => failure('artifact.delete', 'outcome_unknown', 'Unknown'), isInvalidFrame);
   });
 
@@ -413,9 +414,8 @@ function validArtifact() {
     kind: 'file' as const,
     sizeBytes: 4,
     mimeType: 'text/plain',
-    source: 'fixture' as const,
+    source: 'tool_result' as const,
     summary: 'bounded',
-    status: 'live' as const,
   };
 }
 
@@ -426,7 +426,10 @@ function request(
   decodeClientFrame({ requestId: 'request', operation, input });
 }
 
-function response(operation: 'artifact.ingest' | 'artifact.query', result: unknown): void {
+function response(
+  operation: 'artifact.ingest' | 'artifact.query' | 'artifact.delete',
+  result: unknown,
+): void {
   decodeHostFrame({ requestId: 'response', operation, ok: true, result });
 }
 
diff --git a/packages/runtime-host/src/__tests__/artifact-two-client-uds.test.ts b/packages/runtime-host/src/__tests__/artifact-two-client-uds.test.ts
index bdabdb8598..06b45444f4 100644
--- a/packages/runtime-host/src/__tests__/artifact-two-client-uds.test.ts
+++ b/packages/runtime-host/src/__tests__/artifact-two-client-uds.test.ts
@@ -21,7 +21,7 @@ import { withTimeout } from '@maka/core/test-only/async-primitives';
 import assert from 'node:assert/strict';
 import { fork, type ChildProcess } from 'node:child_process';
 import { createHash } from 'node:crypto';
-import { link, mkdir, mkdtemp, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises';
+import { link, mkdir, mkdtemp, readFile, readdir, rm, writeFile } from 'node:fs/promises';
 import { tmpdir } from 'node:os';
 import { basename, dirname, join } from 'node:path';
 import { test } from 'node:test';
@@ -55,20 +55,18 @@ const PROTECTED_ARTIFACTS = [
   { id: 'tool-result-archive', source: 'tool_result_archive' },
 ] as const;
 
-test('production Host recovers Artifact publication and preserves deletes across owner death', {
+test('production Host ignores Artifact publication residue and preserves deletes across owner death', {
   timeout: 120_000,
 }, async () => {
   const base = await mkdtemp(join(tmpdir(), 'maka-runtime-host-artifacts-'));
   const root = join(base, 'root');
   const capability = await resolveStorageRoot({ path: root, kind: 'interactive' });
   const { sessionId, otherSessionId } = await seedExecutionRoot(capability, root);
-  const residue = await createPublicationResidue(root, sessionId);
+  await createPublicationResidue(root, sessionId);
   let firstHost: ExecutionHostHandle | undefined;
   let successor: ExecutionHostHandle | undefined;
   try {
     firstHost = await startHost(root, capability.rootId);
-    await assert.rejects(() => stat(residue.stagingPath), { code: 'ENOENT' });
-    await assert.rejects(() => stat(residue.targetPath), { code: 'ENOENT' });
 
     const desktop = await connectClient(root);
     const tui = await connectClient(root);
@@ -168,11 +166,11 @@ test('production Host recovers Artifact publication and preserves deletes across
         desktop.request('artifact.delete', { sessionId, artifactId: deleteA }),
         tui.request('artifact.delete', { sessionId, artifactId: deleteB }),
       ]);
-      assert.equal(deletedA.artifact.status, 'deleted');
-      assert.equal(deletedB.artifact.status, 'deleted');
-      assert.deepEqual(
-        await desktop.request('artifact.delete', { sessionId, artifactId: deleteA }),
-        deletedA,
+      assert.deepEqual(deletedA, { kind: 'deleted' });
+      assert.deepEqual(deletedB, { kind: 'deleted' });
+      await assert.rejects(
+        desktop.request('artifact.delete', { sessionId, artifactId: deleteA }),
+        operationError('not_found'),
       );
       await assert.rejects(
         tui.request('artifact.delete', {
@@ -182,9 +180,6 @@ test('production Host recovers Artifact publication and preserves deletes across
         operationError('not_found'),
       );
 
-      const protectedBefore = await Promise.all(
-        PROTECTED_ARTIFACTS.map(({ id }) => getArtifact(desktop, sessionId, id)),
-      );
       for (const [index, artifact] of PROTECTED_ARTIFACTS.entries()) {
         const client = index % 2 === 0 ? desktop : tui;
         await assert.rejects(
@@ -192,10 +187,6 @@ test('production Host recovers Artifact publication and preserves deletes across
           operationError('operation_conflict'),
         );
       }
-      const protectedAfter = await Promise.all(
-        PROTECTED_ARTIFACTS.map(({ id }) => getArtifact(tui, sessionId, id)),
-      );
-      assert.deepEqual(protectedAfter, protectedBefore);
 
       const stale = await tui.request('artifact.query', {
         kind: 'list_continue',
@@ -226,34 +217,27 @@ test('production Host recovers Artifact publication and preserves deletes across
     successor = await startHost(root, capability.rootId);
     const observer = await connectClient(root);
     try {
-      for (const artifactId of [deleteA, deleteB]) {
-        const getResult = await getArtifact(observer, sessionId, artifactId);
-        const readResult = await observer.request('artifact.query', {
+      for (const { id: artifactId } of PROTECTED_ARTIFACTS) {
+        assert.ok((await getArtifact(observer, sessionId, artifactId)).artifact);
+        const read = await observer.request('artifact.query', {
           kind: 'read_text',
           sessionId,
           artifactId,
         });
-        assert.equal(getResult.artifact?.status, 'deleted');
-        assert.equal(readResult.kind, 'text');
-        if (readResult.kind === 'text') {
-          assert.deepEqual(readResult.preview, { ok: false, reason: 'deleted' });
-        }
+        assert.equal(read.kind, 'text');
+        assert.ok(read.kind === 'text' && read.preview.ok);
       }
-      for (const artifact of PROTECTED_ARTIFACTS) {
-        const getResult = await getArtifact(observer, sessionId, artifact.id);
-        assert.equal(getResult.artifact?.status, 'live');
-        assert.equal(getResult.artifact?.source, artifact.source);
+      for (const artifactId of [deleteA, deleteB]) {
+        const getResult = await getArtifact(observer, sessionId, artifactId);
         const readResult = await observer.request('artifact.query', {
           kind: 'read_text',
           sessionId,
-          artifactId: artifact.id,
+          artifactId,
         });
+        assert.equal(getResult.artifact, null);
         assert.equal(readResult.kind, 'text');
         if (readResult.kind === 'text') {
-          assert.deepEqual(readResult.preview, {
-            ok: true,
-            text: `protected ${artifact.source}`,
-          });
+          assert.deepEqual(readResult.preview, { ok: false, reason: 'not_found' });
         }
       }
     } finally {
@@ -307,7 +291,6 @@ async function seedExecutionRoot(
     });
     const openedArtifacts = await openInteractiveArtifactStoreForWrite(owner.lease);
     artifacts = openedArtifacts;
-    await openedArtifacts.recover();
     await Promise.all([
       ...Array.from({ length: 130 }, (_, index) =>
         openedArtifacts.create({
@@ -318,7 +301,7 @@ async function seedExecutionRoot(
           kind: 'file',
           content: `content ${index}`,
           mimeType: 'text/plain',
-          source: 'fixture',
+          source: 'tool_result',
           now: 10_000 - index,
         }),
       ),
@@ -330,7 +313,7 @@ async function seedExecutionRoot(
         kind: 'file',
         content: 'small text preview',
         mimeType: 'text/plain',
-        source: 'fixture',
+        source: 'tool_result',
         now: 20_000,
       }),
       openedArtifacts.create({
@@ -341,7 +324,7 @@ async function seedExecutionRoot(
         kind: 'image',
         content: tinyPng(),
         mimeType: 'image/png',
-        source: 'fixture',
+        source: 'tool_result',
         now: 19_999,
       }),
       openedArtifacts.create({
@@ -352,7 +335,7 @@ async function seedExecutionRoot(
         kind: 'file',
         content: Buffer.alloc(12 * 1024, 0xff),
         mimeType: 'text/plain',
-        source: 'fixture',
+        source: 'tool_result',
         now: 9_000,
       }),
       ...PROTECTED_ARTIFACTS.map((artifact, index) =>
@@ -378,7 +361,7 @@ async function seedExecutionRoot(
           kind: 'file',
           content: `bulk artifact payload ${index}`,
           mimeType: 'text/plain',
-          source: 'fixture',
+          source: 'tool_result',
           summary: BULK_ARTIFACT_SUMMARY,
           now: 19_000 - index,
         });
@@ -392,10 +375,7 @@ async function seedExecutionRoot(
   }
 }
 
-async function createPublicationResidue(
-  root: string,
-  sessionId: string,
-): Promise<{ stagingPath: string; targetPath: string }> {
+async function createPublicationResidue(root: string, sessionId: string): Promise<void> {
   const sessionDirectory = join(root, 'artifacts', sessionId);
   await mkdir(sessionDirectory, { recursive: true });
   const targetPath = join(sessionDirectory, 'publication-residue-residue.txt');
@@ -406,7 +386,6 @@ async function createPublicationResidue(
   );
   await writeFile(stagingPath, 'uncommitted publication', { flag: 'wx' });
   await link(stagingPath, targetPath);
-  return { stagingPath, targetPath };
 }
 
 async function startHost(root: string, rootId: string): Promise<ExecutionHostHandle> {
diff --git a/packages/runtime-host/src/__tests__/execution-artifacts.test.ts b/packages/runtime-host/src/__tests__/execution-artifacts.test.ts
index b0fcbe59e1..1143f0c5de 100644
--- a/packages/runtime-host/src/__tests__/execution-artifacts.test.ts
+++ b/packages/runtime-host/src/__tests__/execution-artifacts.test.ts
@@ -19,14 +19,129 @@
 
 import assert from 'node:assert/strict';
 import { createHash } from 'node:crypto';
-import { mkdir, mkdtemp, rm, truncate, writeFile } from 'node:fs/promises';
+import { mkdir, mkdtemp, rm, stat, truncate, writeFile } from 'node:fs/promises';
 import { tmpdir } from 'node:os';
 import { join } from 'node:path';
 import { test } from 'node:test';
 import { MAX_ATTACHMENT_BYTES } from '@maka/core/attachments';
-import { openInteractiveArtifactStoreForWrite } from '@maka/storage/artifact-stores';
+import {
+  openInteractiveArtifactStoreForWrite,
+  createReadImageSnapshotPlanner,
+} from '@maka/storage/artifact-stores';
+import { encodeDurableToolResultOutputWithArtifacts } from '@maka/runtime/durable-tool-result-projection';
+import { deferred } from '@maka/core/test-only/async-primitives';
 import { resolveStorageRoot, tryAcquireInteractiveRootOwner } from '@maka/storage/root-authority';
 import { createHostExecutionArtifactServices } from '../server/execution-artifacts.js';
+import { restoreArtifactV1Shape } from './fixtures/artifact-v1.js';
+import { SessionAdmissionGate } from '../server/session-admission-gate.js';
+
+test('a refused projection preserves a shared image until Session cleanup', async () => {
+  const root = await mkdtemp(join(tmpdir(), 'maka-shared-projection-'));
+  const owner = await tryAcquireInteractiveRootOwner(
+    await resolveStorageRoot({ path: root, kind: 'interactive' }),
+  );
+  assert.ok(owner);
+  const store = await openInteractiveArtifactStoreForWrite(owner.lease);
+  const entered = deferred();
+  const fail = deferred();
+  let rejected: PromiseLike<unknown> | undefined;
+  try {
+    const bytes = Buffer.from(
+      'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aN1sAAAAASUVORK5CYII=',
+      'base64',
+    );
+    const image = {
+      type: 'file' as const,
+      mediaType: 'image/png',
+      data: { type: 'data' as const, data: bytes.toString('base64') },
+    };
+    const plan = createReadImageSnapshotPlanner(store);
+    const shared = () =>
+      plan({
+        sessionId: 'session-1',
+        turnId: 'turn-1',
+        name: 'Tool Result image',
+        bytes,
+        mimeType: 'image/png',
+      });
+    let ordinal = 0;
+    rejected = Promise.resolve(
+      encodeDurableToolResultOutputWithArtifacts(
+        {
+          type: 'content',
+          value: [
+            image,
+            {
+              ...image,
+              data: {
+                type: 'data',
+                data: Buffer.concat([bytes, Buffer.from([0])]).toString('base64'),
+              },
+            },
+          ],
+        },
+        'session-1',
+        () => {
+          if (++ordinal === 1) return shared();
+          return {
+            ref: {
+              kind: 'session_file' as const,
+              sessionId: 'session-1',
+              relativePath: 'failed-image',
+            },
+            persist: async () => {
+              entered.resolve();
+              await fail.promise;
+              throw new Error('injected publication failure');
+            },
+          };
+        },
+      ),
+    );
+    await entered.promise;
+    const accepted = await encodeDurableToolResultOutputWithArtifacts(
+      { type: 'content', value: [image] },
+      'session-1',
+      shared,
+    );
+    assert.equal(accepted.kind, 'content');
+    fail.resolve();
+    assert.equal(((await rejected) as { kind: string }).kind, 'failure');
+    const ref = shared().ref;
+    assert.deepEqual(
+      await store.readDurableAttachmentBinary({
+        sessionId: ref.sessionId,
+        artifactId: ref.relativePath,
+      }),
+      {
+        ok: true,
+        base64: bytes.toString('base64'),
+        mimeType: 'image/png',
+      },
+    );
+    const record = (await store.getInSession(ref.sessionId, ref.relativePath)).record;
+    assert.ok(record);
+    await store.purgeSessionArtifacts('session-1');
+    await assert.rejects(stat(join(root, 'artifacts', record.relativePath)), { code: 'ENOENT' });
+    assert.equal((await store.listPage('session-1', { offset: 0, limit: 10 })).total, 0);
+    assert.deepEqual(
+      await store.readDurableAttachmentBinary({
+        sessionId: ref.sessionId,
+        artifactId: ref.relativePath,
+      }),
+      {
+        ok: false,
+        reason: 'not_found',
+      },
+    );
+  } finally {
+    fail.resolve();
+    await rejected;
+    store.close();
+    await owner.close();
+    await rm(root, { recursive: true, force: true });
+  }
+});
 
 test('Hosted execution publishes contained Tool Artifacts and durable result archives', async () => {
   const base = await mkdtemp(join(tmpdir(), 'maka-host-execution-artifacts-'));
@@ -42,9 +157,10 @@ test('Hosted execution publishes contained Tool Artifacts and durable result arc
   assert.ok(owner);
   try {
     const store = await openInteractiveArtifactStoreForWrite(owner.lease);
-    await store.recover();
     const services = createHostExecutionArtifactServices({
       artifacts: store,
+      sessionAdmission: new SessionAdmissionGate(),
+      sessions: { probeSessionRemoval: async () => ({ kind: 'present' }) },
       requestDrain: () => assert.fail('successful Artifact writes must not request Host drain'),
     });
     await services.recordToolArtifacts({
@@ -98,6 +214,26 @@ test('Hosted execution publishes contained Tool Artifacts and durable result arc
       { ok: true, serializedResult },
     );
     await store.close();
+    restoreArtifactV1Shape(join(base, 'root'));
+    const upgraded = await openInteractiveArtifactStoreForWrite(owner.lease);
+    try {
+      const successor = createHostExecutionArtifactServices({
+        artifacts: upgraded,
+        sessionAdmission: new SessionAdmissionGate(),
+        sessions: { probeSessionRemoval: async () => ({ kind: 'present' }) },
+        requestDrain: () => assert.fail('reading an upgraded archive must not drain'),
+      });
+      assert.deepEqual(
+        await successor.toolResultArchive.services.readToolResultArchive({
+          ...archiveInput,
+          kind: 'maka.archived_tool_result',
+          artifactId: archived.artifactId,
+        }),
+        { ok: true, serializedResult },
+      );
+    } finally {
+      upgraded.close();
+    }
   } finally {
     await owner.close();
     await rm(base, { recursive: true, force: true });
diff --git a/packages/runtime-host/src/__tests__/execution-model-composition.test.ts b/packages/runtime-host/src/__tests__/execution-model-composition.test.ts
index b94f08d66a..9b18f5b261 100644
--- a/packages/runtime-host/src/__tests__/execution-model-composition.test.ts
+++ b/packages/runtime-host/src/__tests__/execution-model-composition.test.ts
@@ -647,7 +647,6 @@ test('Host reopens one projected image from its ArtifactStore authority', async
   let runtime = createSqliteRuntimeStore(runtimePath);
   try {
     artifacts = await openInteractiveArtifactStoreForWrite(owner.lease);
-    await artifacts.recover();
     await runtime.appendRuntimeEvent(sessionId, runId, head);
     backend = await createHostAiSdkBackend(
       backendCreationFixture({
@@ -737,7 +736,6 @@ test('Host reopens one projected image from its ArtifactStore authority', async
     assert.ok(owner);
     if (!owner) return;
     artifacts = await openInteractiveArtifactStoreForWrite(owner.lease);
-    await artifacts.recover();
     runtime = createSqliteRuntimeStore(runtimePath);
     const recoveredEvents = await runtime.readRuntimeEvents(sessionId, runId);
     backend = await createHostAiSdkBackend(
diff --git a/packages/runtime-host/src/__tests__/fixtures/artifact-v1.ts b/packages/runtime-host/src/__tests__/fixtures/artifact-v1.ts
new file mode 100644
index 0000000000..713be8d50f
--- /dev/null
+++ b/packages/runtime-host/src/__tests__/fixtures/artifact-v1.ts
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import { DatabaseSync } from 'node:sqlite';
+import { join } from 'node:path';
+
+// Preserve production-written payloads while restoring the released v1 SQL/JSON shape.
+export function restoreArtifactV1Shape(root: string): void {
+  const db = new DatabaseSync(join(root, 'runtime.sqlite'));
+  try {
+    db.exec(`
+      ALTER TABLE artifact_records RENAME TO current_artifacts;
+      DROP INDEX artifact_records_session_order;
+      DROP INDEX artifact_records_relative_path;
+      CREATE TABLE artifact_records (
+        storage_key TEXT PRIMARY KEY, artifact_id TEXT NOT NULL,
+        session_id TEXT NOT NULL, created_at INTEGER NOT NULL CHECK(created_at >= 0),
+        status TEXT NOT NULL CHECK(status IN ('live', 'deleted')),
+        relative_path TEXT NOT NULL, record_json TEXT NOT NULL
+      );
+      INSERT INTO artifact_records
+        SELECT artifact_id, artifact_id, session_id, created_at, 'live', relative_path,
+          json_set(record_json, '$.status', 'live') FROM current_artifacts;
+      DROP TABLE current_artifacts;
+      CREATE INDEX artifact_records_session_order ON artifact_records(session_id, created_at, storage_key);
+      CREATE UNIQUE INDEX artifact_records_relative_path ON artifact_records(relative_path);
+      UPDATE operational_schema_migrations SET version = 1 WHERE scope = 'artifact';
+    `);
+  } finally {
+    db.close();
+  }
+}
diff --git a/packages/runtime-host/src/__tests__/root-turn-coordinator.test.ts b/packages/runtime-host/src/__tests__/root-turn-coordinator.test.ts
index 553d5d1d4b..7897dde2ea 100644
--- a/packages/runtime-host/src/__tests__/root-turn-coordinator.test.ts
+++ b/packages/runtime-host/src/__tests__/root-turn-coordinator.test.ts
@@ -5175,7 +5175,6 @@ async function createFailureFixture(options: {
   const artifacts = options.withArtifacts
     ? await openInteractiveArtifactStoreForWrite(owner.lease)
     : undefined;
-  await artifacts?.recover();
   const session = await stores.sessionStore.create({
     cwd: capability.canonicalPath,
     ...(options.legacyConnectionIdentity
diff --git a/packages/runtime-host/src/__tests__/session-admission-gate.test.ts b/packages/runtime-host/src/__tests__/session-admission-gate.test.ts
index 84d9e743ed..f94d87a92f 100644
--- a/packages/runtime-host/src/__tests__/session-admission-gate.test.ts
+++ b/packages/runtime-host/src/__tests__/session-admission-gate.test.ts
@@ -90,15 +90,16 @@ test('serializes overlapping multi-Session admissions without lock-order deadloc
   assert.deepEqual(order, ['first:start', 'third', 'first:end', 'second']);
 });
 
-test('keeps the admission open until admitted child work settles', async () => {
+test('keeps the admission open until joined leaf work settles', async () => {
   const gate = new SessionAdmissionGate();
   const childEntered = deferred();
   const releaseChild = deferred();
   let outerSettled = false;
 
   const outer = gate
-    .run('session', (lease) => {
-      void gate.runAdmitted('session', lease, async () => {
+    .run('session', () => {
+      assert.throws(() => gate.runOrJoin('other', () => undefined), /does not match/);
+      void gate.runOrJoin('session', async () => {
         childEntered.resolve();
         await releaseChild.promise;
       });
diff --git a/packages/runtime-host/src/__tests__/session-catalog-two-client-uds.test.ts b/packages/runtime-host/src/__tests__/session-catalog-two-client-uds.test.ts
index 9f4415e2df..560bf3a3f5 100644
--- a/packages/runtime-host/src/__tests__/session-catalog-two-client-uds.test.ts
+++ b/packages/runtime-host/src/__tests__/session-catalog-two-client-uds.test.ts
@@ -887,7 +887,6 @@ async function seedAuthority(
     });
     const artifacts = await openInteractiveArtifactStoreForWrite(owner.lease);
     const todos = await openInteractiveSessionTodoStoreForWrite(owner.lease);
-    await artifacts.recover();
     await Promise.all([
       artifacts.create({
         id: 'retirement-artifact',
@@ -897,7 +896,7 @@ async function seedAuthority(
         kind: 'file',
         content: 'remove me',
         mimeType: 'text/plain',
-        source: 'fixture',
+        source: 'tool_result',
         now: 1,
       }),
       artifacts.create({
@@ -908,7 +907,7 @@ async function seedAuthority(
         kind: 'file',
         content: 'recover cleanup',
         mimeType: 'text/plain',
-        source: 'fixture',
+        source: 'tool_result',
         now: 2,
       }),
       todos.replaceAll(retirement.id, [{ content: 'Remove retirement task', status: 'pending' }]),
@@ -996,7 +995,6 @@ async function assertRetirementCleanup(
   try {
     const execution = await openInteractiveExecutionStoresForWrite(owner.lease);
     const artifacts = await openInteractiveArtifactStoreForWrite(owner.lease);
-    await artifacts.recover();
     assert.deepEqual(await execution.sessionStore.listPendingSessionRetirementCleanupIds(), []);
     for (const sessionId of sessionIds) {
       assert.equal((await artifacts.listPage(sessionId, { offset: 0, limit: 1 })).total, 0);
diff --git a/packages/runtime-host/src/__tests__/session-effect-coordinator.test.ts b/packages/runtime-host/src/__tests__/session-effect-coordinator.test.ts
index 6c4be6855d..eb0b0e28ff 100644
--- a/packages/runtime-host/src/__tests__/session-effect-coordinator.test.ts
+++ b/packages/runtime-host/src/__tests__/session-effect-coordinator.test.ts
@@ -22,6 +22,7 @@ import { mkdtemp, rm } from 'node:fs/promises';
 import { tmpdir } from 'node:os';
 import { join } from 'node:path';
 import { test } from 'node:test';
+import { restoreArtifactV1Shape } from './fixtures/artifact-v1.js';
 import type { SessionHeader } from '@maka/core/session';
 import type { RuntimeReadModelSessionView } from '@maka/runtime/runtime-read-model';
 import { openInteractiveArtifactStoreForWrite } from '@maka/storage/artifact-stores';
@@ -38,8 +39,8 @@ const connectionContext: ConnectionContext = {
   acquireResidency: () => ({ release: () => undefined }),
 };
 
-test('Session recap publishes one protected result and exact retries never repeat provider work', async () => {
-  await withHarness(async ({ store, coordinator, modelCalls }) => {
+test('Session recap retries preserve the original result across a v1 upgrade', async () => {
+  await withHarness(async ({ coordinator, modelCalls, reopenFromV1 }) => {
     const input = { sessionId: 'session-1', effectId: 'effect-1', reason: 'manual' as const };
     const generated = {
       ok: true as const,
@@ -65,15 +66,18 @@ test('Session recap publishes one protected result and exact retries never repea
     );
     assert.equal(modelCalls.count, 1);
 
+    await coordinator.close();
+    const upgraded = await reopenFromV1();
+    let archived = false;
     const successor = createCoordinator(
-      store,
+      upgraded,
       {
         generateTitle: async () => undefined,
         generateRecap: async () => assert.fail('a durable exact retry must not call the model'),
       },
       {
         readSessionHeader: async () =>
-          ({ isArchived: true, status: 'active' }) as unknown as SessionHeader,
+          ({ isArchived: archived, status: 'active' }) as unknown as SessionHeader,
       },
     );
     assert.deepEqual(
@@ -81,6 +85,7 @@ test('Session recap publishes one protected result and exact retries never repea
       generated,
     );
     assert.equal(modelCalls.count, 1);
+    archived = true;
     assert.deepEqual(
       await successor.handlers['session.recap.generate'](
         { ...input, reason: 'idle' },
@@ -108,16 +113,21 @@ test('Session recap publishes one protected result and exact retries never repea
       },
     );
 
-    const records = await store.listPage('session-1', { offset: 0, limit: 10 });
+    const records = await upgraded.listPage('session-1', { offset: 0, limit: 10 });
     assert.equal(records.records.length, 2);
     for (const record of records.records) {
       assert.equal(record.source, 'session_effect');
       assert.equal(
-        (await store.deleteUserArtifactInSession('session-1', record.id)).kind,
+        (await upgraded.deleteUserArtifactInSession('session-1', record.id)).kind,
         'protected',
       );
     }
+    assert.deepEqual(
+      await successor.handlers['session.recap.generate'](input, connectionContext),
+      generated,
+    );
     await successor.close();
+    upgraded.close();
   });
 });
 
@@ -422,6 +432,7 @@ async function withHarness(
     coordinator: HostSessionEffectCoordinator;
     modelCalls: { count: number };
     admission: SessionAdmissionGate;
+    reopenFromV1(): Promise<Awaited<ReturnType<typeof openInteractiveArtifactStoreForWrite>>>;
   }) => Promise<void>,
   overrideModel?: HostSessionEffectModel,
   options?: CoordinatorOptions,
@@ -432,7 +443,6 @@ async function withHarness(
   assert.ok(owner);
   try {
     const store = await openInteractiveArtifactStoreForWrite(owner.lease);
-    await store.recover();
     const modelCalls = { count: 0 };
     const model: HostSessionEffectModel =
       overrideModel ??
@@ -450,7 +460,17 @@ async function withHarness(
       } satisfies HostSessionEffectModel);
     const admission = options?.admission ?? new SessionAdmissionGate();
     const coordinator = createCoordinator(store, model, { ...options, admission });
-    await run({ store, coordinator, modelCalls, admission });
+    await run({
+      store,
+      coordinator,
+      modelCalls,
+      admission,
+      reopenFromV1: async () => {
+        store.close();
+        restoreArtifactV1Shape(root);
+        return openInteractiveArtifactStoreForWrite(owner.lease);
+      },
+    });
     await coordinator.close();
   } finally {
     await owner.close();
diff --git a/packages/runtime-host/src/__tests__/session-effect-two-client-uds.test.ts b/packages/runtime-host/src/__tests__/session-effect-two-client-uds.test.ts
index ad46b006b3..618c255cb4 100644
--- a/packages/runtime-host/src/__tests__/session-effect-two-client-uds.test.ts
+++ b/packages/runtime-host/src/__tests__/session-effect-two-client-uds.test.ts
@@ -88,7 +88,7 @@ test('two Clients share one durable Session recap effect', async () => {
           ...coordinator.handlers,
         },
         beginDrain: () => coordinator.beginDrain(),
-        recover: () => artifacts.recover(),
+        recover: async () => {},
         close: async () => {
           await coordinator.close();
           artifacts.close();
diff --git a/packages/runtime-host/src/__tests__/session-retirement-coordinator.test.ts b/packages/runtime-host/src/__tests__/session-retirement-coordinator.test.ts
index c191cf0a0b..163e8fd10b 100644
--- a/packages/runtime-host/src/__tests__/session-retirement-coordinator.test.ts
+++ b/packages/runtime-host/src/__tests__/session-retirement-coordinator.test.ts
@@ -22,6 +22,11 @@ import { mkdtemp, rm } from 'node:fs/promises';
 import { tmpdir } from 'node:os';
 import { join } from 'node:path';
 import { DatabaseSync } from 'node:sqlite';
+import fsPromises from 'node:fs/promises';
+import { syncBuiltinESMExports } from 'node:module';
+import { openInteractiveArtifactStoreForWrite } from '@maka/storage/artifact-stores';
+import { resolveStorageRoot, tryAcquireInteractiveRootOwner } from '@maka/storage/root-authority';
+import { createHostExecutionArtifactServices } from '../server/execution-artifacts.js';
 import { describe, test } from 'node:test';
 import type { AgentGraphOperatorProvisionRequest } from '@maka/core/agent-graph-topology';
 import type { CreateSessionInput } from '@maka/core/runtime-inputs';
@@ -52,6 +57,116 @@ const CONNECTION_CONTEXT: ConnectionContext = {
 };
 
 describe('Host Session retirement coordinator', () => {
+  test('retirement finalizes admitted patches and fences late artifact publication', async (t) => {
+    await withHarness(async (harness) => {
+      const capability = await resolveStorageRoot({
+        path: harness.workspaceRoot,
+        kind: 'interactive',
+      });
+      const owner = await tryAcquireInteractiveRootOwner(capability);
+      assert.ok(owner);
+      const artifacts = await openInteractiveArtifactStoreForWrite(owner.lease);
+      const source = join(harness.workspaceRoot, 'late.txt');
+      await fsPromises.writeFile(source, 'late payload');
+      let entered!: () => void;
+      let release!: () => void;
+      const started = new Promise<void>((resolve) => {
+        entered = resolve;
+      });
+      const blocked = new Promise<void>((resolve) => {
+        release = resolve;
+      });
+      const original = fsPromises.realpath;
+      const pause = t.mock.method(
+        fsPromises,
+        'realpath',
+        async (...args: Parameters<typeof original>) => {
+          if (args[0] === source) {
+            entered();
+            await blocked;
+          }
+          return original(...args);
+        },
+      );
+      syncBuiltinESMExports();
+      let recording: Promise<void> | undefined;
+      try {
+        harness.purgeArtifact = (sessionId) => artifacts.purgeSessionArtifacts(sessionId);
+        const recorder = createHostExecutionArtifactServices({
+          artifacts,
+          requestDrain: () => assert.fail('unexpected drain'),
+          sessionAdmission: harness.admission,
+          sessions: harness.store,
+        });
+        const patch = {
+          turnId: 'turn-1',
+          patch: Buffer.from('workspace patch'),
+          binding: {
+            schemaVersion: 1 as const,
+            kind: 'git_worktree' as const,
+            leaseId: `subagent_worktree_${'a'.repeat(32)}`,
+            gitCommonDir: join(harness.workspaceRoot, '.git'),
+            worktreePath: harness.workspaceRoot,
+            branch: `maka/subagent/${'a'.repeat(32)}`,
+            baseCommit: 'b'.repeat(40),
+          },
+        };
+        harness.finalizeWorkspacePatches = async (sessionId) => {
+          const published = await recorder.publishChildWorkspacePatch({ ...patch, sessionId });
+          assert.equal(published.source, 'subagent_writeback');
+          assert.deepEqual(await artifacts.readTextInSession(sessionId, published.id), {
+            ok: true,
+            text: 'workspace patch',
+          });
+        };
+        recording = recorder.recordToolArtifacts({
+          sessionId: harness.revisionId,
+          turnId: 'turn-1',
+          toolUseId: 'tool-1',
+          toolName: 'Write',
+          args: {},
+          result: {},
+          cwd: harness.workspaceRoot,
+          candidates: [{ kind: 'file', name: 'late.txt', sourcePath: source }],
+        });
+        await started;
+        const target = await harness.store.readHeaderRecordSnapshot(harness.revisionId);
+        const removed = await harness.coordinator.handlers['session.remove'](
+          {
+            sessionId: harness.revisionId,
+            expectedRevision: target.revision,
+          },
+          CONNECTION_CONTEXT,
+        );
+        assert.equal(removed.ok, true);
+        await waitFor(
+          async () => (await harness.store.listPendingSessionRetirementCleanupIds()).length === 0,
+          'retirement cleanup completes while source read is paused',
+        );
+        release();
+        await recording;
+        await assert.rejects(
+          recorder.publishChildWorkspacePatch({ ...patch, sessionId: harness.revisionId }),
+          /retired before patch publication/,
+        );
+        assert.deepEqual(
+          (await artifacts.listPage(harness.revisionId, { offset: 0, limit: 10 })).records,
+          [],
+        );
+      } finally {
+        release();
+        try {
+          await recording;
+        } finally {
+          pause.mock.restore();
+          syncBuiltinESMExports();
+          artifacts.close();
+          await owner.close();
+        }
+      }
+    });
+  });
+
   test('retires context refs before draining every physical garbage batch', async () => {
     const contextActions: string[] = [];
     let garbageBatches = 0;
diff --git a/packages/runtime-host/src/__tests__/session-revision-graph-references.test.ts b/packages/runtime-host/src/__tests__/session-revision-graph-references.test.ts
index a963491729..e8fb45f78a 100644
--- a/packages/runtime-host/src/__tests__/session-revision-graph-references.test.ts
+++ b/packages/runtime-host/src/__tests__/session-revision-graph-references.test.ts
@@ -231,9 +231,6 @@ test('Agent Graph revision references outlive the Artifacts they name', async ()
   // still fails on.
   const reclaimed = await prepare({ artifactMissing: true });
   assert.equal(reclaimed.ok, true);
-
-  const userDeleted = await prepare({ artifactStatus: 'deleted' });
-  assert.equal(userDeleted.ok, true);
 });
 
 test('Agent Graph revision references reject invalid ownership boundaries', async () => {
@@ -331,7 +328,6 @@ interface PrepareOverrides {
   readonly sessionGraphState?: 'absent' | 'live' | 'terminal';
   readonly graphState?: 'absent' | 'live' | 'terminal';
   readonly artifactTurnId?: string;
-  readonly artifactStatus?: 'live' | 'deleted';
   readonly artifactMissing?: boolean;
   readonly childActive?: boolean;
 }
@@ -370,7 +366,7 @@ async function prepare(overrides: PrepareOverrides = {}) {
                 kind: 'file',
                 relativePath: 'result.txt',
                 sizeBytes: 1,
-                status: overrides.artifactStatus ?? 'live',
+                source: 'tool_result',
               },
         }),
       },
diff --git a/packages/runtime-host/src/__tests__/session-revision-two-client-uds.test.ts b/packages/runtime-host/src/__tests__/session-revision-two-client-uds.test.ts
index efdc7f4895..8e039340ca 100644
--- a/packages/runtime-host/src/__tests__/session-revision-two-client-uds.test.ts
+++ b/packages/runtime-host/src/__tests__/session-revision-two-client-uds.test.ts
@@ -810,7 +810,6 @@ async function seedSource(
     const execution = await openInteractiveExecutionStoresForWrite(owner.lease);
     const artifacts = await openInteractiveArtifactStoreForWrite(owner.lease);
     const todos = await openInteractiveSessionTodoStoreForWrite(owner.lease);
-    await artifacts.recover();
     const source = await execution.sessionStore.create({
       cwd: root,
       name: 'Source Session',
@@ -1689,7 +1688,6 @@ async function verifyDurableBranch(
     const execution = await openInteractiveExecutionStoresForWrite(owner.lease);
     const artifacts = await openInteractiveArtifactStoreForWrite(owner.lease);
     const todos = await openInteractiveSessionTodoStoreForWrite(owner.lease);
-    await artifacts.recover();
     // Every copy kind that retains the upload turn must carry a rewritten,
     // readable copy of the user-uploaded attachment (regression guard for the
     // turn-scoped-only artifact selection that dropped user uploads).
diff --git a/packages/runtime-host/src/protocol/artifact.ts b/packages/runtime-host/src/protocol/artifact.ts
index 9b9e2b06d6..0c78f530ee 100644
--- a/packages/runtime-host/src/protocol/artifact.ts
+++ b/packages/runtime-host/src/protocol/artifact.ts
@@ -20,13 +20,11 @@
 import {
   ARTIFACT_KINDS,
   ARTIFACT_SOURCES,
-  ARTIFACT_STATUSES,
   type ArtifactRecord,
   type ArtifactBinaryReadFailureReason,
   type ArtifactKind,
   type ArtifactReadFailureReason,
   type ArtifactSource,
-  type ArtifactStatus,
   isArtifactTurnKey,
   isCanonicalArtifactEntityId,
 } from '@maka/core/artifacts';
@@ -65,9 +63,9 @@ const ARTIFACT_REQUIRED_FIELDS = [
   'name',
   'kind',
   'sizeBytes',
-  'status',
+  'source',
 ] as const;
-const ARTIFACT_FIELDS = new Set([...ARTIFACT_REQUIRED_FIELDS, 'mimeType', 'source', 'summary']);
+const ARTIFACT_FIELDS = new Set([...ARTIFACT_REQUIRED_FIELDS, 'mimeType', 'summary']);
 
 export type ArtifactRevision = `sha256:${string}`;
 
@@ -80,9 +78,8 @@ export interface ArtifactProjection {
   readonly kind: ArtifactKind;
   readonly sizeBytes: number;
   readonly mimeType?: string;
-  readonly source?: ArtifactSource;
+  readonly source: ArtifactSource;
   readonly summary?: string;
-  readonly status: ArtifactStatus;
 }
 
 export type ArtifactQueryInput =
@@ -158,7 +155,6 @@ export interface ArtifactDeleteInput {
 
 export interface ArtifactDeleteResult {
   readonly kind: 'deleted';
-  readonly artifact: ArtifactProjection;
 }
 
 export type ArtifactIngestInput =
@@ -549,9 +545,9 @@ export function decodeArtifactQueryResult(value: unknown): ArtifactQueryResult {
 export const encodeArtifactQueryResult = decodeArtifactQueryResult;
 
 export function decodeArtifactDeleteResult(value: unknown): ArtifactDeleteResult {
-  const result = requireExactRecord(value, 'artifact delete result', ['kind', 'artifact']);
+  const result = requireExactRecord(value, 'artifact delete result', ['kind']);
   if (result.kind !== 'deleted') throw invalidProtocolFrame('Invalid artifact delete result kind');
-  const decoded = { kind: 'deleted' as const, artifact: decodeArtifactProjection(result.artifact) };
+  const decoded = { kind: 'deleted' as const };
   assertResultSize(decoded);
   return decoded;
 }
@@ -570,11 +566,10 @@ export function encodeArtifactProjection(record: ArtifactRecord): ArtifactProjec
     ...(record.mimeType === undefined
       ? {}
       : { mimeType: projectArtifactText(record.mimeType, ARTIFACT_MIME_TYPE_MAX_BYTES) }),
-    ...(record.source === undefined ? {} : { source: record.source }),
+    source: record.source,
     ...(record.summary === undefined
       ? {}
       : { summary: projectArtifactText(record.summary, ARTIFACT_SUMMARY_MAX_BYTES) }),
-    status: record.status,
   };
 }
 
@@ -594,13 +589,12 @@ function decodeArtifactProjection(value: unknown): ArtifactProjection {
     name: boundedText(record.name, 'artifact name', ARTIFACT_NAME_MAX_BYTES),
     kind: artifactKind(record.kind),
     sizeBytes: requireCount(record.sizeBytes, 'artifact sizeBytes'),
-    status: artifactStatus(record.status),
     ...(Object.hasOwn(record, 'mimeType')
       ? {
           mimeType: boundedText(record.mimeType, 'artifact mimeType', ARTIFACT_MIME_TYPE_MAX_BYTES),
         }
       : {}),
-    ...(Object.hasOwn(record, 'source') ? { source: artifactSource(record.source) } : {}),
+    source: artifactSource(record.source),
     ...(Object.hasOwn(record, 'summary')
       ? { summary: boundedText(record.summary, 'artifact summary', ARTIFACT_SUMMARY_MAX_BYTES) }
       : {}),
@@ -678,7 +672,7 @@ function boundedText(value: unknown, label: string, maxBytes: number, allowEmpty
 function boundedIngestText(value: unknown, label: string, maxBytes: number): string {
   const text = boundedText(value, label, maxBytes);
   // eslint-disable-next-line no-control-regex
-  if (/[-]/.test(text)) throw invalidProtocolFrame(`Invalid ${label}`);
+  if (/[\x00-\x1f\x7f]/.test(text)) throw invalidProtocolFrame(`Invalid ${label}`);
   return text;
 }
 
@@ -728,20 +722,12 @@ function artifactSource(value: unknown): ArtifactSource {
   return value as ArtifactSource;
 }
 
-function artifactStatus(value: unknown): ArtifactStatus {
-  if (typeof value !== 'string' || !ARTIFACT_STATUSES.includes(value as ArtifactStatus)) {
-    throw invalidProtocolFrame('Invalid artifact status');
-  }
-  return value as ArtifactStatus;
-}
-
 function readFailureReason(value: unknown): ArtifactReadFailureReason {
   if (
     value === 'not_found' ||
     value === 'too_large' ||
     value === 'read_failed' ||
-    value === 'not_allowed' ||
-    value === 'deleted'
+    value === 'not_allowed'
   ) {
     return value;
   }
diff --git a/packages/runtime-host/src/protocol/index.ts b/packages/runtime-host/src/protocol/index.ts
index 57953277b6..b4223846eb 100644
--- a/packages/runtime-host/src/protocol/index.ts
+++ b/packages/runtime-host/src/protocol/index.ts
@@ -101,7 +101,10 @@ export const RUNTIME_HOST_REGISTRATION_SCHEMA_VERSION = 1 as const;
 export const RUNTIME_HOST_PROTOCOL_VERSION = 0 as const;
 // Increment when the same protocol version no longer guarantees safe Client-Host
 // interoperability. Mismatches are rejected before domain commands are admitted.
-export const RUNTIME_HOST_COMPATIBILITY_EPOCH = 113 as const;
+export const RUNTIME_HOST_COMPATIBILITY_EPOCH = 116 as const;
+// 116: User deletion rejects workflow-owned Artifacts with operation_conflict.
+// 115: Artifact creation requires explicit source ownership.
+// 114: Artifacts are physically deleted and no longer expose tombstone status.
 // 113: Client Capability tool schemas add `patternProperties` and draft-07 tuple
 // `additionalItems`; validation and projection share one per-keyword shape table.
 // Older peers reject these keywords and fail the handshake.
diff --git a/packages/runtime-host/src/server/artifact-coordinator.ts b/packages/runtime-host/src/server/artifact-coordinator.ts
index 3a278deb99..9b507ba550 100644
--- a/packages/runtime-host/src/server/artifact-coordinator.ts
+++ b/packages/runtime-host/src/server/artifact-coordinator.ts
@@ -118,7 +118,7 @@ export class HostArtifactCoordinator {
       }
       const entry = await this.#store.getInSession(sessionId, attachment.ref.relativePath);
       const record = entry.record;
-      if (!record || record.status !== 'live') return 'Attachment Artifact was not found';
+      if (!record) return 'Attachment Artifact was not found';
       if (
         record.name !== attachment.name ||
         record.mimeType !== attachment.mimeType ||
@@ -309,7 +309,7 @@ export class HostArtifactCoordinator {
     );
     const record = entry.record;
     if (!record) return { kind: 'missing' };
-    if (record.status !== 'live' || record.source !== 'user_upload' || record.turnId !== uploadId) {
+    if (record.source !== 'user_upload' || record.turnId !== uploadId) {
       return { kind: 'conflict' };
     }
     return { kind: 'committed', record };
@@ -361,7 +361,7 @@ export class HostArtifactCoordinator {
           maxBytes: ARTIFACT_READ_CHUNK_MAX_BYTES,
         });
         if (!chunk.ok) {
-          if (chunk.reason === 'not_found' || chunk.reason === 'deleted') {
+          if (chunk.reason === 'not_found') {
             return notFound('artifact.query', 'Artifact was not found');
           }
           if (chunk.reason === 'out_of_range') {
@@ -441,7 +441,7 @@ export class HostArtifactCoordinator {
     );
     if (!grant) return;
     const entry = await this.#store.getInSession(input.sessionId, input.artifactId);
-    return entry.record?.status === 'live' && isArtifactSharedSessionReadable(entry.record)
+    return entry.record && isArtifactSharedSessionReadable(entry.record)
       ? grant.grantId
       : undefined;
   }
@@ -484,16 +484,13 @@ export class HostArtifactCoordinator {
           ok: false,
           error: {
             code: 'operation_conflict',
-            message: 'Protected runtime evidence cannot be deleted through Runtime Host',
+            message: 'Runtime-owned evidence cannot be deleted independently of its workflow',
           },
         };
       }
       return {
         ok: true,
-        result: encodeArtifactDeleteResult({
-          kind: 'deleted',
-          artifact: encodeArtifactProjection(deleted.record),
-        }),
+        result: encodeArtifactDeleteResult({ kind: 'deleted' }),
       };
     } catch {
       this.#requestDrain();
diff --git a/packages/runtime-host/src/server/execution-artifacts.ts b/packages/runtime-host/src/server/execution-artifacts.ts
index 7ec86eb52d..43186e196f 100644
--- a/packages/runtime-host/src/server/execution-artifacts.ts
+++ b/packages/runtime-host/src/server/execution-artifacts.ts
@@ -35,9 +35,13 @@ import {
 } from '@maka/runtime/context-budget';
 import { type ToolResultArchiveResourceReadInput } from '@maka/runtime/tool-result-archive-resource';
 import type { InteractiveArtifactStoreWriter } from '@maka/storage/artifact-stores';
+import type { SessionManagerDeps } from '@maka/runtime/session-manager';
+import type { SessionAdmissionGate } from './session-admission-gate.js';
+import type { SessionPresenceReader } from './session-presence.js';
 
 export interface HostExecutionArtifactServices {
   recordToolArtifacts(event: ToolArtifactRecorderInput): Promise<void>;
+  publishChildWorkspacePatch: NonNullable<SessionManagerDeps['publishChildWorkspacePatch']>;
   /**
    * One archive authority over the session artifact store (#2026). All three
    * reads and the writer address the same store, so the host has no way to hand
@@ -49,6 +53,8 @@ export interface HostExecutionArtifactServices {
 export function createHostExecutionArtifactServices(input: {
   artifacts: InteractiveArtifactStoreWriter;
   requestDrain: () => void;
+  sessionAdmission: SessionAdmissionGate;
+  sessions: SessionPresenceReader;
 }): HostExecutionArtifactServices {
   const runWrite = async <T>(operation: () => Promise<T>): Promise<T> => {
     try {
@@ -59,6 +65,13 @@ export function createHostExecutionArtifactServices(input: {
     }
   };
 
+  const publish = (artifact: Parameters<InteractiveArtifactStoreWriter['create']>[0]) =>
+    input.sessionAdmission.runOrJoin(artifact.sessionId, async () => {
+      if ((await input.sessions.probeSessionRemoval(artifact.sessionId)).kind !== 'present')
+        return null;
+      return runWrite(() => input.artifacts.create(artifact));
+    });
+
   const recordToolArtifacts = async (event: ToolArtifactRecorderInput): Promise<void> => {
     for (const candidate of event.candidates) {
       let content = candidate.content;
@@ -66,29 +79,43 @@ export function createHostExecutionArtifactServices(input: {
         content = (await readBoundedSourceFile(event.cwd, candidate.sourcePath)) ?? undefined;
       }
       if (content === undefined || contentBytes(content) > MAX_ATTACHMENT_BYTES) continue;
-      await runWrite(() =>
-        input.artifacts.create({
-          sessionId: event.sessionId,
-          turnId: event.turnId,
-          name: candidate.name,
-          kind: candidate.kind,
-          content,
-          ...(candidate.mimeType ? { mimeType: candidate.mimeType } : {}),
-          source: candidate.source ?? 'tool_result',
-          ...(candidate.summary ? { summary: candidate.summary } : {}),
-        }),
-      );
+      await publish({
+        sessionId: event.sessionId,
+        turnId: event.turnId,
+        name: candidate.name,
+        kind: candidate.kind,
+        content,
+        ...(candidate.mimeType ? { mimeType: candidate.mimeType } : {}),
+        source: candidate.source ?? 'tool_result',
+        ...(candidate.summary ? { summary: candidate.summary } : {}),
+      });
     }
   };
 
   const services: HostExecutionArtifactServices = {
     recordToolArtifacts,
+    publishChildWorkspacePatch: async ({ sessionId, turnId, binding, patch }) => {
+      const artifact = await publish({
+        id: subagentWritebackArtifactId(sessionId, turnId),
+        sessionId,
+        turnId,
+        name: 'workspace.patch',
+        kind: 'diff',
+        content: patch,
+        mimeType: 'text/x-diff; charset=utf-8',
+        source: 'subagent_writeback',
+        summary: `Workspace changes relative to ${binding.baseCommit}.`,
+      });
+      if (!artifact)
+        throw new Error(`Child Session ${sessionId} was retired before patch publication`);
+      return artifact;
+    },
     toolResultArchive: createToolResultArchiveCapability({
       archiveToolResult: (event: ToolResultArchiveRecorderInput) =>
         runWrite(async () => {
           const artifactId = stableToolResultArchiveArtifactId(event);
           const existing = await input.artifacts.getInSession(event.sessionId, artifactId);
-          if (existing.record?.status === 'live') {
+          if (existing.record) {
             const read = await readArchive(input.artifacts, {
               artifactId,
               sessionId: event.sessionId,
@@ -123,6 +150,17 @@ export function createHostExecutionArtifactServices(input: {
   return Object.freeze(services);
 }
 
+function subagentWritebackArtifactId(sessionId: string, turnId: string): string {
+  const digest = createHash('sha256')
+    .update('maka-subagent-writeback-v1\0')
+    .update(sessionId)
+    .update('\0')
+    .update(turnId)
+    .digest('hex')
+    .slice(0, 32);
+  return `subagent_writeback_${digest}`;
+}
+
 async function readBoundedSourceFile(cwd: string, sourcePath: string): Promise<Buffer | null> {
   const candidate = isAbsolute(sourcePath) ? sourcePath : resolve(cwd, sourcePath);
   let root: string;
@@ -171,7 +209,6 @@ async function readArchive(
   const entry = await artifacts.getInSession(event.sessionId, event.artifactId);
   const record = entry.record;
   if (!record) return { ok: false, reason: 'not_found' };
-  if (record.status === 'deleted') return { ok: false, reason: 'deleted' };
   if (record.source !== 'tool_result_archive') return { ok: false, reason: 'source_mismatch' };
   if (record.sizeBytes !== event.originalBytes) return { ok: false, reason: 'size_mismatch' };
   const read = await artifacts.readTextInSession(event.sessionId, event.artifactId, {
diff --git a/packages/runtime-host/src/server/execution-composition.ts b/packages/runtime-host/src/server/execution-composition.ts
index 2e38c38f8d..d6fad15d56 100644
--- a/packages/runtime-host/src/server/execution-composition.ts
+++ b/packages/runtime-host/src/server/execution-composition.ts
@@ -80,10 +80,7 @@ import { type MakaTool } from '@maka/runtime/tool-runtime';
 import { type RuntimeHostedRootAuthority } from '@maka/runtime/message-authority';
 import { isHostedExecutionTerminal } from './hosted-execution-authority.js';
 import { createAgentGraphControlStore } from '@maka/storage/agent-graph-control-store';
-import {
-  createArtifactAttachmentResourceReader,
-  startRetiredCaptureSweep,
-} from '@maka/storage/artifact-stores';
+import { createArtifactAttachmentResourceReader } from '@maka/storage/artifact-stores';
 import { createReadImageSnapshotStore } from '@maka/storage/read-image-snapshot-store';
 import { isSessionNotFoundError } from '@maka/storage/execution-stores';
 import { createExternalSessionAdapterRegistry } from '@maka/storage/external-sessions';
@@ -266,7 +263,6 @@ export async function createExecutionRuntimeHostComposition(
       `[runtime-host] optional context-offload Store could not be opened: ${generalizedErrorMessage(storage.contextOffloadUnavailable.cause)}`,
     );
   }
-  let stopRetiredCaptureSweep: (() => void) | undefined;
   const stores = storage.execution;
   let graphControlStore: ReturnType<typeof createAgentGraphControlStore> | undefined;
   let graphClient: HostAgentGraphCoordinator | undefined;
@@ -419,6 +415,8 @@ export async function createExecutionRuntimeHostComposition(
     const executionArtifacts = createHostExecutionArtifactServices({
       artifacts: openedArtifactStore,
       requestDrain: context.requestDrain,
+      sessionAdmission,
+      sessions: stores.sessionStore,
     });
     const builtinTools = {
       shellRuns: runtimeResources,
@@ -1064,18 +1062,7 @@ export async function createExecutionRuntimeHostComposition(
       worktreeChildExecutor,
       listArtifactsForTurn: (sessionId, turnId) =>
         openedArtifactStore.listTurnArtifacts(sessionId, turnId),
-      publishChildWorkspacePatch: ({ sessionId, turnId, binding, patch }) =>
-        openedArtifactStore.create({
-          id: subagentWritebackArtifactId(sessionId, turnId),
-          sessionId,
-          turnId,
-          name: 'workspace.patch',
-          kind: 'diff',
-          content: patch,
-          mimeType: 'text/x-diff; charset=utf-8',
-          source: 'subagent_writeback',
-          summary: `Workspace changes relative to ${binding.baseCommit}.`,
-        }),
+      publishChildWorkspacePatch: executionArtifacts.publishChildWorkspacePatch,
       assertChildWorkspaceQuiescent: async (sessionId) => {
         if (await runtimeResources!.hasLiveSessionResources(sessionId)) {
           throw new Error(
@@ -1762,22 +1749,6 @@ export async function createExecutionRuntimeHostComposition(
         recovery: {
           state: async () => {
             await skills.recover();
-            await openedArtifactStore.recover();
-            // Only now: a write authority refuses every mutation until it has
-            // recovered, and the sweep gives up on its first failure.
-            stopRetiredCaptureSweep = startRetiredCaptureSweep(storage.artifacts, {
-              onError: async (error) => {
-                console.error(
-                  `[runtime-host] retired provider-request captures could not be reclaimed: ${generalizedErrorMessage(error)}`,
-                );
-                // A purge that fails part way leaves the write authority
-                // refusing every mutation until something recovers it -- not
-                // just this sweep's, but the live turn's tool results and the
-                // user's uploads. Recovering here is what hands those back,
-                // and it replays the purge intent the failed batch left.
-                await openedArtifactStore.recover();
-              },
-            });
           },
         },
         drain: [
@@ -1794,7 +1765,6 @@ export async function createExecutionRuntimeHostComposition(
           () => {
             unsubscribeTranscriptChanges?.();
             unsubscribeUsageChanges?.();
-            stopRetiredCaptureSweep?.();
           },
         ],
         releaseConnection: [(connectionId) => artifacts.releaseConnection(connectionId)],
@@ -2158,17 +2128,6 @@ function adaptWorkspaceFilesystemWorker(
   };
 }
 
-function subagentWritebackArtifactId(sessionId: string, turnId: string): string {
-  const digest = createHash('sha256')
-    .update('maka-subagent-writeback-v1\0')
-    .update(sessionId)
-    .update('\0')
-    .update(turnId)
-    .digest('hex')
-    .slice(0, 32);
-  return `subagent_writeback_${digest}`;
-}
-
 function requireContinuity(
   continuity: SessionContinuityCoordinator | undefined,
 ): SessionContinuityCoordinator {
diff --git a/packages/runtime-host/src/server/execution-model-composition.ts b/packages/runtime-host/src/server/execution-model-composition.ts
index cc495950e2..f06cb71872 100644
--- a/packages/runtime-host/src/server/execution-model-composition.ts
+++ b/packages/runtime-host/src/server/execution-model-composition.ts
@@ -101,7 +101,7 @@ type HostExecutionRuntimePolicyAuthority = {
 
 type HostExecutionArtifactAuthority = Pick<
   InteractiveArtifactStoreWriter,
-  'create' | 'createOwned' | 'readDurableAttachmentBinary' | 'deleteOwnedArtifactInSession'
+  'create' | 'readDurableAttachmentBinary'
 >;
 
 type HostExecutionUsageAuthority = {
@@ -329,11 +329,7 @@ async function buildHostAiSdkBackend(
         );
       }
     : undefined;
-  const planProjectionImage = createReadImageSnapshotPlanner(
-    input.artifacts,
-    (sessionId, artifactId) =>
-      input.artifacts.deleteOwnedArtifactInSession(sessionId, artifactId, 'tool_result_projection'),
-  );
+  const planProjectionImage = createReadImageSnapshotPlanner(input.artifacts);
 
   try {
     return new HostAiSdkBackend(
diff --git a/packages/runtime-host/src/server/session-admission-gate.ts b/packages/runtime-host/src/server/session-admission-gate.ts
index 69576581d3..162c2f7b3a 100644
--- a/packages/runtime-host/src/server/session-admission-gate.ts
+++ b/packages/runtime-host/src/server/session-admission-gate.ts
@@ -27,6 +27,7 @@ export interface SessionAdmissionLease {
 
 interface SessionAdmissionContext {
   readonly sessionIds: ReadonlySet<string>;
+  readonly lease: SessionAdmissionLease;
   active: boolean;
 }
 
@@ -74,6 +75,15 @@ export class SessionAdmissionGate {
     return this.#runQueued(sessionIds, operation);
   }
 
+  /** Leaf work may join an existing admission, but never acquire a second Session inside it. */
+  runOrJoin<T>(sessionId: string, operation: () => Promise<T> | T): Promise<T> {
+    const inherited = this.#context.getStore();
+    if (inherited?.active) {
+      return this.runAdmitted(sessionId, inherited.lease, operation);
+    }
+    return this.run(sessionId, operation);
+  }
+
   enqueueDetached(
     sessionId: string,
     operation: (lease: SessionAdmissionLease) => Promise<void> | void,
@@ -150,10 +160,10 @@ export class SessionAdmissionGate {
     }
 
     const ownedSessionIds = new Set(sessionIds);
-    const context: SessionAdmissionContext = { sessionIds: ownedSessionIds, active: true };
     const lease: SessionAdmissionLease = Object.freeze({
       [sessionAdmissionLeaseBrand]: true as const,
     });
+    const context: SessionAdmissionContext = { sessionIds: ownedSessionIds, lease, active: true };
     const state: SessionAdmissionLeaseState = {
       sessionIds: ownedSessionIds,
       context,
diff --git a/packages/runtime-host/src/server/session-effect-coordinator.ts b/packages/runtime-host/src/server/session-effect-coordinator.ts
index b8fc3e2249..2514ee35b5 100644
--- a/packages/runtime-host/src/server/session-effect-coordinator.ts
+++ b/packages/runtime-host/src/server/session-effect-coordinator.ts
@@ -434,7 +434,7 @@ export class HostSessionEffectCoordinator {
     artifactId: string,
   ): Promise<Record<string, unknown> | undefined> {
     const entry = await this.#artifacts.getInSession(sessionId, artifactId);
-    if (!entry.record || entry.record.status !== 'live') return undefined;
+    if (!entry.record) return undefined;
     const read = await this.#artifacts.readTextInSession(sessionId, artifactId, {
       maxBytes: SESSION_EFFECT_ARTIFACT_MAX_BYTES,
     });
diff --git a/packages/runtime-host/src/server/session-revision-graph-references.ts b/packages/runtime-host/src/server/session-revision-graph-references.ts
index 9e9385e462..cd2cd79155 100644
--- a/packages/runtime-host/src/server/session-revision-graph-references.ts
+++ b/packages/runtime-host/src/server/session-revision-graph-references.ts
@@ -244,8 +244,7 @@ export async function prepareAgentGraphRevisionReferences(
     // A child result names every Artifact its turn held, and the ledger that
     // records it can never be rewritten -- so an id in it outlives whatever it
     // named. What this checks is therefore that a reference does not reach
-    // outside its own child and lineage, not that its target survived: the
-    // retired provider-request captures are reclaimed on their own, and a user
+    // outside its own child and lineage, not that its target survived: a user
     // may delete a child's Artifact. A reference whose target is gone stays
     // admissible and simply resolves to nothing, while one that crosses a
     // Session or a lineage was never admissible and still fails.
@@ -253,7 +252,7 @@ export async function prepareAgentGraphRevisionReferences(
       const artifact = await dependencies.artifacts
         .getInSession(childSessionId, artifactId)
         .catch(() => null);
-      if (!artifact?.record || artifact.record.status === 'deleted') continue;
+      if (!artifact?.record) continue;
       if (
         artifact.record.sessionId !== childSessionId ||
         !lineage.turnIds.has(artifact.record.turnId)
diff --git a/packages/runtime/src/__tests__/deep-research-tools.test.ts b/packages/runtime/src/__tests__/deep-research-tools.test.ts
index fd553a6053..7be150d75d 100644
--- a/packages/runtime/src/__tests__/deep-research-tools.test.ts
+++ b/packages/runtime/src/__tests__/deep-research-tools.test.ts
@@ -63,7 +63,6 @@ class FakeArtifactStore implements DeepResearchArtifactStore {
       source: input.source,
       summary: input.summary,
       deepResearchRole: input.deepResearchRole,
-      status: 'live',
     };
     this.records.push(record);
     this.contents.set(record.id, input.content);
@@ -83,8 +82,9 @@ class FakeArtifactStore implements DeepResearchArtifactStore {
 
   async delete(artifactId: string): Promise<void> {
     this.deleted.push(artifactId);
-    const record = this.records.find((item) => item.id === artifactId);
-    if (record) record.status = 'deleted';
+    const index = this.records.findIndex((item) => item.id === artifactId);
+    if (index >= 0) this.records.splice(index, 1);
+    this.contents.delete(artifactId);
   }
 }
 
@@ -148,14 +148,10 @@ describe('Deep Research runtime tools', () => {
   it('runs the source-checkpoint-report lifecycle and makes artifact retries idempotent', async () => {
     await withTempRoot(async (root) => {
       const artifactStore = new FakeArtifactStore();
-      const notifications: string[] = [];
       const store = createSqliteDeepResearchStore(root);
       const tools = buildDeepResearchTools({
         store,
         artifactStore,
-        onArtifactCreated: (event) => {
-          notifications.push(event.artifactId);
-        },
       });
 
       await execute(
@@ -395,13 +391,13 @@ describe('Deep Research runtime tools', () => {
         verification_commands: ['npm test'],
       };
       const sourceRecord = artifactStore.records[0]!;
-      sourceRecord.status = 'deleted';
+      artifactStore.records.splice(0, 1);
       await assert.rejects(
         () =>
           execute(tools, DEEP_RESEARCH_COMPLETE_TOOL_NAME, completeInput, 'call-complete-deleted'),
         /missing or deleted/,
       );
-      sourceRecord.status = 'live';
+      artifactStore.records.unshift(sourceRecord);
 
       const sectionRecord = artifactStore.records[1]!;
       const sectionContent = artifactStore.contents.get(sectionRecord.id)!;
@@ -462,7 +458,6 @@ describe('Deep Research runtime tools', () => {
       const status = await execute(tools, DEEP_RESEARCH_STATUS_TOOL_NAME, {}, 'call-status');
       assert.match(status, new RegExp(`Final report: ${reportId}`));
       assert.match(status, new RegExp(`Handoff artifact: ${handoffId}`));
-      assert.equal(notifications.length, 8);
       assert.equal((await store.readEvents(SESSION_ID)).length, 16);
     });
   });
diff --git a/packages/runtime/src/__tests__/durable-tool-result-projection.test.ts b/packages/runtime/src/__tests__/durable-tool-result-projection.test.ts
index 7a92f11878..a1094c8e1e 100644
--- a/packages/runtime/src/__tests__/durable-tool-result-projection.test.ts
+++ b/packages/runtime/src/__tests__/durable-tool-result-projection.test.ts
@@ -275,9 +275,8 @@ describe('durable Tool Result projection codec', () => {
     assert.equal(writes, 0);
   });
 
-  it('retracts already-published artifacts when a later one cannot be published', async () => {
+  it('refuses the whole projection when a later artifact cannot be published', async () => {
     const published: string[] = [];
-    const retracted: string[] = [];
     let nextId = 0;
     const projection = await encodeDurableToolResultOutputWithArtifacts(
       twoInlineImages(),
@@ -290,40 +289,12 @@ describe('durable Tool Result projection codec', () => {
             if (relativePath === 'artifact-2') throw new Error('artifact store is unavailable');
             published.push(relativePath);
           },
-          retract: async () => {
-            retracted.push(relativePath);
-          },
         };
       },
     );
 
     assert.equal(projection.kind, 'failure');
     assert.deepEqual(published, ['artifact-1']);
-    assert.deepEqual(retracted, ['artifact-1']);
-  });
-
-  it('still refuses the projection when the retraction itself fails', async () => {
-    let nextId = 0;
-    const projection = await encodeDurableToolResultOutputWithArtifacts(
-      twoInlineImages(),
-      'session-1',
-      () => {
-        const relativePath = `artifact-${++nextId}`;
-        return {
-          ref: { kind: 'session_file' as const, sessionId: 'session-1', relativePath },
-          persist: async () => {
-            if (relativePath === 'artifact-2') throw new Error('artifact store is unavailable');
-          },
-          // Reclamation may be delayed; admitting a partially published
-          // projection may not happen at all.
-          retract: async () => {
-            throw new Error('cleanup is unavailable too');
-          },
-        };
-      },
-    );
-
-    assert.equal(projection.kind, 'failure');
   });
 
   it('validates default image refs through the same closed schema', () => {
diff --git a/packages/runtime/src/__tests__/session-manager.test.ts b/packages/runtime/src/__tests__/session-manager.test.ts
index 4aff33d521..b059a7e8af 100644
--- a/packages/runtime/src/__tests__/session-manager.test.ts
+++ b/packages/runtime/src/__tests__/session-manager.test.ts
@@ -1121,7 +1121,6 @@ describe('SessionManager graph operator provisioning', () => {
           sizeBytes: patch.byteLength,
           mimeType: 'text/x-diff; charset=utf-8',
           source: 'subagent_writeback',
-          status: 'live',
         };
         artifacts.set(`${sessionId}:${turnId}`, [record]);
         return record;
@@ -1671,6 +1670,30 @@ describe('SessionManager claimed graph intent execution', () => {
       runtimeEventStore: runStore,
       backends,
       childTools: [testTool('Read'), testTool('Glob'), testTool('Grep')],
+      listArtifactsForTurn: async (sessionId, turnId) => [
+        {
+          id: 'child-output',
+          sessionId,
+          turnId,
+          createdAt: 98,
+          name: 'answer.txt',
+          kind: 'file',
+          relativePath: `${sessionId}/child-output-answer.txt`,
+          sizeBytes: 6,
+          source: 'tool_result',
+        },
+        {
+          id: 'child-internal-archive',
+          sessionId,
+          turnId,
+          createdAt: 99,
+          name: 'tool-result.json',
+          kind: 'file',
+          relativePath: `${sessionId}/child-internal-archive-tool-result.json`,
+          sizeBytes: 12,
+          source: 'tool_result_archive',
+        },
+      ],
       newId: nextId(),
       now: nextNow(40),
     });
@@ -1705,6 +1728,7 @@ describe('SessionManager claimed graph intent execution', () => {
       status: 'completed',
       summary: 'ok',
     });
+    assert.deepStrictEqual(result.artifactIds, ['child-output']);
     assert.deepStrictEqual(ready, [
       {
         claimId: claim.claimId,
@@ -9733,7 +9757,7 @@ describe('SessionManager permission mode updates', () => {
                 kind: 'file',
                 relativePath: 'artifacts/notes.md',
                 sizeBytes: 12,
-                status: 'live',
+                source: 'tool_result',
               },
             ]
           : [],
diff --git a/packages/runtime/src/deep-research-tools.ts b/packages/runtime/src/deep-research-tools.ts
index 2a8c5a4883..f544c30591 100644
--- a/packages/runtime/src/deep-research-tools.ts
+++ b/packages/runtime/src/deep-research-tools.ts
@@ -106,7 +106,7 @@ export interface DeepResearchArtifactStore {
   get(artifactId: string): Promise<ArtifactRecord | null>;
   readText(
     artifactId: string,
-    options?: { maxBytes?: number; includeDeleted?: boolean },
+    options?: { maxBytes?: number },
   ): Promise<{ ok: true; text: string } | { ok: false; reason: string }>;
   delete(artifactId: string): Promise<void>;
 }
@@ -114,12 +114,6 @@ export interface DeepResearchArtifactStore {
 export interface BuildDeepResearchToolsDeps {
   store: DeepResearchStore;
   artifactStore: DeepResearchArtifactStore;
-  onArtifactCreated?: (event: {
-    reason: 'created';
-    artifactId: string;
-    sessionId: string;
-    ts: number;
-  }) => void | Promise<void>;
 }
 
 export function buildDeepResearchTools(deps: BuildDeepResearchToolsDeps): MakaTool[] {
@@ -219,12 +213,7 @@ function buildReadArtifactTool(deps: BuildDeepResearchToolsDeps): MakaTool<
       const ref = run.artifacts.find((artifact) => artifact.artifactId === input.artifact_id);
       if (!ref) throw new Error('Research artifact is not part of this session workspace');
       const record = await deps.artifactStore.get(input.artifact_id);
-      if (
-        !record ||
-        record.sessionId !== ctx.sessionId ||
-        record.source !== 'deep_research' ||
-        record.status !== 'live'
-      ) {
+      if (!record || record.sessionId !== ctx.sessionId || record.source !== 'deep_research') {
         throw new Error('Research artifact is missing, deleted, or belongs to another session');
       }
       const read = await deps.artifactStore.readText(input.artifact_id, {
@@ -433,16 +422,6 @@ function buildSaveArtifactTool(deps: BuildDeepResearchToolsDeps): MakaTool<
         await deps.artifactStore.delete(artifactId).catch(() => undefined);
         throw error;
       }
-      try {
-        await deps.onArtifactCreated?.({
-          reason: 'created',
-          artifactId,
-          sessionId: ctx.sessionId,
-          ts: artifact.createdAt,
-        });
-      } catch {
-        // Renderer notification is best effort; both durable authorities already committed.
-      }
       return `Saved ${input.role} artifact ${artifactId}.\n${renderRunStatus(run)}`;
     },
   };
@@ -872,7 +851,7 @@ async function validateArtifactIntegrity(
   ref: DeepResearchArtifactRef,
 ): Promise<void> {
   const record = await artifactStore.get(ref.artifactId);
-  if (!record || record.status !== 'live') {
+  if (!record) {
     throw new Error(`Deep Research artifact ${ref.artifactId} is missing or deleted`);
   }
   if (record.sessionId !== sessionId || record.source !== 'deep_research') {
diff --git a/packages/runtime/src/durable-tool-result-projection.ts b/packages/runtime/src/durable-tool-result-projection.ts
index 376d374e65..f392c0dae6 100644
--- a/packages/runtime/src/durable-tool-result-projection.ts
+++ b/packages/runtime/src/durable-tool-result-projection.ts
@@ -70,8 +70,6 @@ export function encodeDurableToolResultOutput(
 interface DurableProjectionArtifactPlan {
   ref: Extract<DurableProjectionArtifactRef, { kind: 'session_file' }>;
   persist(): Promise<void>;
-  /** Undo a publication whose projection is not going to be admitted. */
-  retract?(): Promise<void>;
 }
 
 type DurableProjectionArtifactPlanner = (input: {
@@ -88,7 +86,6 @@ export function encodeDurableToolResultOutputWithArtifacts(
     return encodeDurableToolResultOutput(output, sessionId);
   }
   return (async () => {
-    const publishedPlans: DurableProjectionArtifactPlan[] = [];
     try {
       const prepared = prepareContentProjection(output, sessionId, planArtifact);
       const persisted = new Set<string>();
@@ -96,16 +93,11 @@ export function encodeDurableToolResultOutputWithArtifacts(
         if (persisted.has(artifact.ref.relativePath)) continue;
         await artifact.persist();
         persisted.add(artifact.ref.relativePath);
-        publishedPlans.push(artifact);
       }
       return prepared.projection;
     } catch {
-      // Publication is all-or-nothing: a projection this codec refuses must not
-      // leave images behind that no durable record will ever reference (#4283).
-      // Retraction is best effort — a failed retraction only delays reclamation.
-      for (const artifact of publishedPlans) {
-        await artifact.retract?.().catch(() => undefined);
-      }
+      // Refuse partial projections, but never retract shared image bytes:
+      // another projection may already reference them. Session cleanup owns reclamation.
       return DURABLE_TOOL_RESULT_PROJECTION_FAILURE;
     }
   })();
diff --git a/packages/runtime/src/session-manager.ts b/packages/runtime/src/session-manager.ts
index 91bd2d1021..38ac4474e2 100644
--- a/packages/runtime/src/session-manager.ts
+++ b/packages/runtime/src/session-manager.ts
@@ -119,7 +119,7 @@ import type {
 } from '@maka/core/agent-graph-topology';
 import type { AgentGraphScheduleUpdateSource } from '@maka/core/agent-graph-schedule';
 import type { AgentRunEvent, AgentRunStore } from '@maka/core/agent-run';
-import type { ArtifactRecord } from '@maka/core/artifacts';
+import { isArtifactChildResultOutput, type ArtifactRecord } from '@maka/core/artifacts';
 import { invocationMatchesClaimTarget } from '@maka/core/runtime-boundary';
 import type { ContinuationClaimV1 } from '@maka/core/runtime-boundary';
 import {
@@ -1027,21 +1027,23 @@ export class SessionManager {
   ): Promise<ArtifactRecord[]> {
     const list = this.deps.listArtifactsForTurn;
     if (!list) return [];
-    const artifacts = await list(sessionId, turnId);
-    if (!isTerminalRunStatus(status) || !this.hasWorktreePatchWriteBack()) return artifacts;
-    const header = await this.deps.store.readHeader(sessionId);
-    if (!header.subagentWorkspace) return artifacts;
-    const existing = artifacts.find((artifact) => artifact.source === 'subagent_writeback');
-    if (existing) return artifacts;
-
-    await this.finalizeChildWorkspacePatches(sessionId);
-    const finalized = await list(sessionId, turnId);
-    if (!finalized.some((artifact) => artifact.source === 'subagent_writeback')) {
-      throw new Error(
-        `Child Session ${sessionId} cannot reconstruct the historical workspace patch for Turn ${turnId}`,
-      );
+    let artifacts = await list(sessionId, turnId);
+    if (isTerminalRunStatus(status) && this.hasWorktreePatchWriteBack()) {
+      const header = await this.deps.store.readHeader(sessionId);
+      if (
+        header.subagentWorkspace &&
+        !artifacts.some((artifact) => artifact.source === 'subagent_writeback')
+      ) {
+        await this.finalizeChildWorkspacePatches(sessionId);
+        artifacts = await list(sessionId, turnId);
+        if (!artifacts.some((artifact) => artifact.source === 'subagent_writeback')) {
+          throw new Error(
+            `Child Session ${sessionId} cannot reconstruct the historical workspace patch for Turn ${turnId}`,
+          );
+        }
+      }
     }
-    return finalized;
+    return artifacts.filter(isArtifactChildResultOutput);
   }
 
   /**
diff --git a/packages/storage/src/__tests__/artifact-attachments.test.ts b/packages/storage/src/__tests__/artifact-attachments.test.ts
index d62ce889ca..b9f26a35d8 100644
--- a/packages/storage/src/__tests__/artifact-attachments.test.ts
+++ b/packages/storage/src/__tests__/artifact-attachments.test.ts
@@ -38,6 +38,14 @@ import {
 
 const png = Uint8Array.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
 
+async function listArtifacts(store: ArtifactAuthorityStore, sessionId: string) {
+  return (await store.listPage(sessionId, { offset: 0, limit: Number.MAX_SAFE_INTEGER })).records;
+}
+
+function readArtifactBinary(store: ArtifactAuthorityStore, artifactId: string) {
+  return store.readBinaryInSession('session-1', artifactId);
+}
+
 describe('artifact attachment authority', () => {
   test('reads only live user-uploaded text within the invoking Session', async () => {
     await withStore(async (store) => {
@@ -63,7 +71,7 @@ describe('artifact attachment authority', () => {
         reader.readAttachmentResource('session-2', 'notes-1', signal),
         /not found in this Session/,
       );
-      await store.delete('notes-1');
+      await store.deleteUserArtifactInSession('session-1', 'notes-1');
       await assert.rejects(
         reader.readAttachmentResource('session-1', 'notes-1', signal),
         /not found in this Session/,
@@ -80,6 +88,7 @@ describe('artifact attachment authority', () => {
         name: 'image.png',
         kind: 'image',
         content: png,
+        source: 'tool_result',
         now: 1,
       });
       const reader = createAttachmentByteReader({
@@ -90,10 +99,10 @@ describe('artifact attachment authority', () => {
         ok: true,
         bytes: Buffer.from(png),
       });
-      await store.delete('image-1');
+      await store.deleteUserArtifactInSession('session-1', 'image-1');
       assert.deepEqual(await reader(sessionFileRef('image-1')), {
         ok: false,
-        reason: 'deleted',
+        reason: 'not_found',
       });
       assert.deepEqual(await reader(sessionFileRef('image-1', 'other-session')), {
         ok: false,
@@ -111,6 +120,7 @@ describe('artifact attachment authority', () => {
         name: 'large.png',
         kind: 'image',
         content: new Uint8Array(MAX_ATTACHMENT_BYTES + 1).fill(0x89),
+        source: 'tool_result',
         now: 1,
       });
       const reader = createAttachmentByteReader({
@@ -200,6 +210,7 @@ describe('artifact attachment authority', () => {
         name: 'unknown.bin',
         kind: 'file',
         content: Uint8Array.from([0, 1, 2, 3]),
+        source: 'tool_result',
         now: 1,
       });
       assert.deepEqual(await reader(sessionFileRef('unknown-binary')), {
@@ -221,7 +232,7 @@ describe('artifact attachment authority', () => {
         }),
         /Image exceeds the 5MB model input limit/,
       );
-      assert.deepEqual(await store.list('session-1'), []);
+      assert.deepEqual(await listArtifacts(store, 'session-1'), []);
     });
   });
 
@@ -240,11 +251,11 @@ describe('artifact attachment authority', () => {
       const repeated = await snapshot(input);
 
       assert.deepEqual(repeated, first);
-      assert.equal((await store.list('session-1')).length, 1);
+      assert.equal((await listArtifacts(store, 'session-1')).length, 1);
     });
   });
 
-  test('keeps a durable projection image live when a user requests deletion', async () => {
+  test('protects a durable projection image until its Session is purged', async () => {
     await withStore(async (store) => {
       const ref = await createReadImageSnapshotter(store)({
         sessionId: 'session-1',
@@ -254,13 +265,15 @@ describe('artifact attachment authority', () => {
         mimeType: 'image/png',
       });
 
-      assert.deepEqual(await store.deleteUserArtifactInSession('session-1', ref.relativePath), {
-        kind: 'protected',
-      });
-      assert.deepEqual(await store.readBinary(ref.relativePath), {
-        ok: true,
-        base64: Buffer.from(png).toString('base64'),
-        mimeType: 'image/png',
+      assert.equal(
+        (await store.deleteUserArtifactInSession('session-1', ref.relativePath)).kind,
+        'protected',
+      );
+      assert.equal((await readArtifactBinary(store, ref.relativePath)).ok, true);
+      await store.purgeSessionArtifacts('session-1');
+      assert.deepEqual(await readArtifactBinary(store, ref.relativePath), {
+        ok: false,
+        reason: 'not_found',
       });
     });
   });
@@ -277,68 +290,23 @@ describe('artifact attachment authority', () => {
       };
       const plan = createReadImageSnapshotPlanner(store)(input);
 
-      assert.deepEqual(await store.list('session-1'), []);
+      assert.deepEqual(await listArtifacts(store, 'session-1'), []);
       bytes[0] = 0;
       input.name = 'mutated after prepare';
       await Promise.all([plan.persist(), plan.persist()]);
-      const published = await store.list('session-1');
+      const published = await listArtifacts(store, 'session-1');
       assert.deepEqual(
         published.map((artifact) => artifact.id),
         [plan.ref.relativePath],
       );
       assert.equal(published[0]?.name, 'Tool Result image');
-      assert.deepEqual(await store.readBinary(plan.ref.relativePath), {
+      assert.deepEqual(await readArtifactBinary(store, plan.ref.relativePath), {
         ok: true,
         base64: Buffer.from(png).toString('base64'),
         mimeType: 'image/png',
       });
     });
   });
-  test('retraction reclaims only what the retracting plan published', async () => {
-    await withStore(async (store) => {
-      const input = {
-        sessionId: 'session-1',
-        turnId: 'turn-1',
-        name: 'Tool Result image',
-        bytes: png.slice(),
-        mimeType: 'image/png',
-      };
-      const deleted: string[] = [];
-      // The id is derived from the bytes, so a second projection for the same
-      // image in the same Turn gets the first one's record back. Retracting the
-      // second must not delete the artifact the first one committed.
-      const owned = {
-        create: (createInput: Parameters<typeof store.create>[0]) => store.create(createInput),
-        createOwned: async (createInput: Parameters<typeof store.create>[0]) => {
-          const existing = createInput.id
-            ? await store.getInSession(createInput.sessionId, createInput.id)
-            : undefined;
-          const record = await store.create(createInput);
-          return { record, publishedByThisCall: !existing?.record };
-        },
-      };
-      const planner = createReadImageSnapshotPlanner(owned, async (_sessionId, artifactId) => {
-        deleted.push(artifactId);
-        await store.delete(artifactId);
-      });
-
-      const first = planner(input);
-      await first.persist();
-      const second = planner(input);
-      await second.persist();
-      assert.equal(second.ref.relativePath, first.ref.relativePath);
-
-      await second.retract();
-
-      assert.deepEqual(deleted, []);
-      assert.equal((await store.readBinary(first.ref.relativePath)).ok, true);
-
-      await first.retract();
-
-      assert.deepEqual(deleted, [first.ref.relativePath]);
-      assert.equal((await store.readBinary(first.ref.relativePath)).ok, false);
-    });
-  });
 });
 
 function sessionFileRef(relativePath: string, sessionId = 'session-1'): StorageRef {
@@ -353,7 +321,6 @@ async function withStore(run: (store: ArtifactAuthorityStore) => Promise<void>):
   const root = await mkdtemp(join(tmpdir(), 'maka-artifact-attachment-'));
   const authority = createSqliteArtifactStoreWriteAuthority(root);
   try {
-    await authority.recover();
     const { store } = authority;
     await run(store);
   } finally {
diff --git a/packages/storage/src/__tests__/artifact-store.test.ts b/packages/storage/src/__tests__/artifact-store.test.ts
index 15e6eb4334..6119b08a40 100644
--- a/packages/storage/src/__tests__/artifact-store.test.ts
+++ b/packages/storage/src/__tests__/artifact-store.test.ts
@@ -18,6 +18,8 @@
  */
 
 import assert from 'node:assert/strict';
+import fsPromises from 'node:fs/promises';
+import { syncBuiltinESMExports } from 'node:module';
 import { createHash } from 'node:crypto';
 import {
   link,
@@ -41,10 +43,9 @@ import {
 } from '@maka/core/artifacts';
 import {
   ARTIFACT_TEXT_PREVIEW_LIMIT_BYTES,
-  type ArtifactStore,
+  type ArtifactAuthorityStore,
   type ArtifactStoreWriteAuthority,
   type CreateArtifactInput,
-  createSqliteArtifactStore,
   createSqliteArtifactStoreWriteAuthority,
   isSafeRelativeArtifactPath,
   resolveArtifactPath,
@@ -61,10 +62,38 @@ function trackArtifactStoreCloser(root: string, close: () => void): void {
   artifactStoreClosersByRoot.set(root, closers);
 }
 
-function createArtifactStore(root: string): ArtifactStore {
-  const store = createSqliteArtifactStore(root);
-  trackArtifactStoreCloser(root, () => store.close?.());
-  return store;
+function createArtifactStore(root: string): ArtifactAuthorityStore {
+  const authority = createSqliteArtifactStoreWriteAuthority(root);
+  trackArtifactStoreCloser(root, () => authority.close());
+  return authority.store;
+}
+
+async function listArtifacts(store: ArtifactAuthorityStore, sessionId: string) {
+  return (await store.listPage(sessionId, { offset: 0, limit: Number.MAX_SAFE_INTEGER })).records;
+}
+
+async function getArtifact(
+  store: ArtifactAuthorityStore,
+  artifactId: string,
+  sessionId = 'session-1',
+) {
+  return (await store.getInSession(sessionId, artifactId)).record;
+}
+
+function readArtifactText(
+  store: ArtifactAuthorityStore,
+  artifactId: string,
+  sessionId = 'session-1',
+) {
+  return store.readTextInSession(sessionId, artifactId);
+}
+
+function readArtifactBinary(
+  store: ArtifactAuthorityStore,
+  artifactId: string,
+  sessionId = 'session-1',
+) {
+  return store.readBinaryInSession(sessionId, artifactId);
 }
 
 function createArtifactStoreWriteAuthority(root: string): ArtifactStoreWriteAuthority {
@@ -92,7 +121,7 @@ describe('SQLite Artifact store', () => {
       assert.equal((await stat(root)).isDirectory(), true);
       assert.equal(created.id, 'missing-root');
       assert.deepEqual(
-        (await createArtifactStore(root).list('session-1')).map((record) => record.id),
+        (await listArtifacts(createArtifactStore(root), 'session-1')).map((record) => record.id),
         ['missing-root'],
       );
     } finally {
@@ -115,25 +144,27 @@ describe('SQLite Artifact store', () => {
       assert.equal(first.relativePath, 'session-1/artifact-1-artifact-1.txt');
       assert.equal(first.sizeBytes, 7);
       assert.deepEqual(
-        (await store.list('session-1')).map((record) => record.id),
+        (await listArtifacts(store, 'session-1')).map((record) => record.id),
         ['artifact-2', 'artifact-1'],
       );
-      assert.deepEqual(await store.readText(first.id), { ok: true, text: '# Notes' });
+      assert.deepEqual(await readArtifactText(store, first.id), { ok: true, text: '# Notes' });
 
       const reopened = createArtifactStore(root);
-      assert.deepEqual(await reopened.get(second.id), second);
+      assert.deepEqual(await getArtifact(reopened, second.id), second);
       await assert.rejects(
         () => reopened.create(artifactInput('artifact-1', 'replacement', 300)),
         /Artifact artifact-1 already exists/,
       );
-      assert.deepEqual(await reopened.readText('artifact-1'), { ok: true, text: '# Notes' });
+      assert.deepEqual(await readArtifactText(reopened, 'artifact-1'), {
+        ok: true,
+        text: '# Notes',
+      });
     });
   });
 
   test('lists only live Artifacts committed by one exact Turn', async () => {
     await withWorkspace(async (root) => {
       const authority = createArtifactStoreWriteAuthority(root);
-      await authority.recover();
       const { store } = authority;
       await store.create({ ...artifactInput('turn-a-new', 'new', 30), turnId: 'turn-a' });
       await store.create({ ...artifactInput('turn-b', 'other turn', 20), turnId: 'turn-b' });
@@ -168,7 +199,10 @@ describe('SQLite Artifact store', () => {
         const input = { ...artifactInput(id, 'compacted', 1), turnId };
         const created = await createArtifactStore(root).create(input);
         assert.equal(created.turnId, input.turnId);
-        assert.equal((await createArtifactStore(root).get(created.id))?.turnId, input.turnId);
+        assert.equal(
+          (await getArtifact(createArtifactStore(root), created.id))?.turnId,
+          input.turnId,
+        );
       }
 
       for (const turnId of [
@@ -192,7 +226,6 @@ describe('SQLite Artifact store', () => {
   test('owns stable session revisions across paging, reopen, mutations, and no-ops', async () => {
     await withWorkspace(async (root) => {
       const authority = createArtifactStoreWriteAuthority(root);
-      await authority.recover();
       const { store } = authority;
       const empty = await store.listPage('session-1', { offset: 0, limit: 2 });
       assert.equal(empty.total, 0);
@@ -215,7 +248,6 @@ describe('SQLite Artifact store', () => {
       assert.equal((await store.getInSession('session-1', 'first')).revision, created.revision);
 
       const reopenedAuthority = createArtifactStoreWriteAuthority(root);
-      await reopenedAuthority.recover();
       const reopenedPage = await reopenedAuthority.store.listPage('session-1', {
         offset: 0,
         limit: 2,
@@ -245,8 +277,14 @@ describe('SQLite Artifact store', () => {
       assert.equal(deletedResult.kind, 'deleted');
       const deleted = await store.listPage('session-1', { offset: 0, limit: 3 });
       assert.notEqual(deleted.revision, created.revision);
-      assert.equal(deleted.records.find((record) => record.id === 'first')?.status, 'deleted');
-      assert.equal((await store.deleteUserArtifactInSession('session-1', 'first')).kind, 'deleted');
+      assert.equal(
+        deleted.records.find((record) => record.id === 'first'),
+        undefined,
+      );
+      assert.equal(
+        (await store.deleteUserArtifactInSession('session-1', 'first')).kind,
+        'not_found',
+      );
       assert.equal(
         (await store.listPage('session-1', { offset: 0, limit: 3 })).revision,
         deleted.revision,
@@ -255,9 +293,9 @@ describe('SQLite Artifact store', () => {
       await store.create(firstInput);
       const revived = await store.listPage('session-1', { offset: 0, limit: 3 });
       assert.notEqual(revived.revision, deleted.revision);
-      assert.equal(revived.records.find((record) => record.id === 'first')?.status, 'live');
+      assert.equal(revived.records.find((record) => record.id === 'first')?.id, 'first');
 
-      await store.purge(['first', 'second', 'revision-race']);
+      await store.purgeSessionArtifacts('session-1');
       const purged = await store.listPage('session-1', { offset: 0, limit: 2 });
       assert.equal(purged.revision, empty.revision);
       assert.equal(purged.total, 0);
@@ -267,7 +305,6 @@ describe('SQLite Artifact store', () => {
   test('copies an exact turn-scoped Artifact snapshot and purges only the target Session', async () => {
     await withWorkspace(async (root) => {
       const authority = createArtifactStoreWriteAuthority(root);
-      await authority.recover();
       const { store } = authority;
       const retained = await store.create({
         ...artifactInput('retained-artifact', 'retained', 10),
@@ -278,7 +315,7 @@ describe('SQLite Artifact store', () => {
         ...artifactInput('deleted-artifact', 'deleted', 11),
         turnId: 'turn-retained',
       });
-      await store.delete(deleted.id);
+      await store.deleteUserArtifactInSession('session-1', deleted.id);
       await store.create({
         ...artifactInput('later-artifact', 'later', 20),
         turnId: 'turn-later',
@@ -292,158 +329,31 @@ describe('SQLite Artifact store', () => {
       const copiedId = copied.artifactIds.get(retained.id);
       const copiedDeletedId = copied.artifactIds.get(deleted.id);
       assert.ok(copiedId);
-      assert.ok(copiedDeletedId);
+      assert.equal(copiedDeletedId, undefined);
       assert.notEqual(copiedId, retained.id);
-      const target = await store.list('session-copy');
+      const target = await listArtifacts(store, 'session-copy');
       assert.equal(target.length, 1);
       assert.equal(target[0]?.id, copiedId);
       assert.equal(target[0]?.turnId, retained.turnId);
       assert.equal(copied.relativePaths.get(retained.relativePath), target[0]?.relativePath);
-      assert.deepEqual(await store.readText(copiedId!), {
+      assert.deepEqual(await readArtifactText(store, copiedId!, 'session-copy'), {
         ok: true,
         text: 'retained',
       });
-      const targetWithTombstones = await store.list('session-copy', { includeDeleted: true });
-      assert.equal(targetWithTombstones.find((record) => record.id === copiedId)?.status, 'live');
-      const copiedDeleted = targetWithTombstones.find((record) => record.id === copiedDeletedId);
-      assert.equal(copiedDeleted?.status, 'deleted');
-      assert.equal(copied.relativePaths.get(deleted.relativePath), copiedDeleted?.relativePath);
-      assert.deepEqual(await store.readText(copiedDeletedId!, { includeDeleted: true }), {
-        ok: true,
-        text: 'deleted',
-      });
+      assert.equal(copied.relativePaths.get(deleted.relativePath), undefined);
 
       await store.purgeSessionArtifacts('session-copy');
-      assert.deepEqual(await store.list('session-copy'), []);
-      assert.deepEqual((await store.list('session-1')).map((record) => record.id).sort(), [
-        'later-artifact',
-        'retained-artifact',
-      ]);
-    });
-  });
-
-  test('a conversation copy leaves retired captures behind rather than reintroducing them', async () => {
-    await withWorkspace(async (root) => {
-      const authority = createArtifactStoreWriteAuthority(root);
-      await authority.recover();
-      const { store } = authority;
-      const kept = await store.create(artifactInput('kept-artifact', 'kept', 10));
-      await store.create({
-        ...artifactInput('capture-artifact', 'request', 11),
-        source: 'provider_request_capture',
-      });
-
-      const copied = await store.copyConversationArtifacts({
-        sourceSessionId: 'session-1',
-        targetSessionId: 'session-copy',
-        turnIds: ['turn-1'],
-      });
-
-      // The sweep runs once and stops when nothing is left. A copy that
-      // carried captures would put them back afterwards, so every fork would
-      // hand the new Session a fresh set of bytes nobody reads and nothing
-      // will come back for.
-      assert.equal(copied.artifactIds.get('capture-artifact'), undefined);
-      assert.ok(copied.artifactIds.get(kept.id));
-      assert.deepEqual(
-        (await store.list('session-copy', { includeDeleted: true })).map((record) => record.source),
-        ['fixture'],
-      );
-      // And the source keeps its own until the sweep takes them.
-      assert.deepEqual(await store.purgeRetiredCaptures(8), { purged: 1, remaining: 0 });
-    });
-  });
-
-  test('and leaves them behind on the linked and included paths too', async () => {
-    await withWorkspace(async (root) => {
-      const authority = createArtifactStoreWriteAuthority(root);
-      await authority.recover();
-      const { store } = authority;
-      // A Side Conversation copy names artifacts three ways: by turn, by a
-      // linked child Session, and by an explicit include list for the refs
-      // that carry no conversation turn. All three reach the same records.
-      const linkedKept = await store.create({
-        ...artifactInput('linked-kept', 'child result', 10),
-        sessionId: 'session-child',
-      });
-      await store.create({
-        ...artifactInput('linked-capture', 'child request', 11),
-        sessionId: 'session-child',
-        source: 'provider_request_capture',
-      });
-      await store.create({
-        ...artifactInput('upload-capture', 'uploaded request', 12),
-        turnId: 'upload-1',
-        source: 'provider_request_capture',
-      });
-
-      const copied = await store.copyConversationArtifacts({
-        sourceSessionId: 'session-1',
-        targetSessionId: 'session-copy',
-        turnIds: ['turn-1'],
-        includeArtifactIds: ['upload-capture'],
-        linkedArtifacts: [{ sessionId: 'session-child', artifactIds: [linkedKept.id] }],
-      });
-
-      assert.ok(copied.artifactIds.get(linkedKept.id), 'ordinary linked artifacts still copy');
-      assert.equal(copied.artifactIds.get('upload-capture'), undefined);
-      assert.deepEqual(
-        (await store.list('session-copy', { includeDeleted: true })).map((record) => record.source),
-        ['fixture'],
-      );
-
-      // A child result lists every Artifact its turn held, captures included,
-      // and the ledger holding that list cannot be rewritten. So a request
-      // naming one is answered with what is still copyable, not refused: the
-      // alternative is a Session that can never be forked again.
-      const named = await store.copyConversationArtifacts({
-        sourceSessionId: 'session-1',
-        targetSessionId: 'session-copy-2',
-        turnIds: ['turn-1'],
-        linkedArtifacts: [
-          { sessionId: 'session-child', artifactIds: ['linked-capture', linkedKept.id] },
-        ],
-      });
-      assert.equal(named.artifactIds.get('linked-capture'), undefined);
-      assert.ok(named.artifactIds.get(linkedKept.id));
-    });
-  });
-
-  test('reclaims retired request captures in bounded batches and leaves everything else', async () => {
-    await withWorkspace(async (root) => {
-      const authority = createArtifactStoreWriteAuthority(root);
-      await authority.recover();
-      const { store } = authority;
-      for (let index = 0; index < 3; index += 1) {
-        await store.create({
-          ...artifactInput(`capture-${index}`, `request-${index}`, 10 + index),
-          source: 'provider_request_capture',
-        });
-      }
-      await store.create(artifactInput('kept-artifact', 'kept', 20));
-
-      assert.deepEqual(await store.purgeRetiredCaptures(2), { purged: 2, remaining: 1 });
-      assert.deepEqual(await store.purgeRetiredCaptures(2), { purged: 1, remaining: 0 });
-      // The sweep stops on its own rather than spinning once the residue is gone.
-      assert.deepEqual(await store.purgeRetiredCaptures(2), { purged: 0, remaining: 0 });
-
+      assert.deepEqual(await listArtifacts(store, 'session-copy'), []);
       assert.deepEqual(
-        (await store.list('session-1', { includeDeleted: true })).map((record) => record.id),
-        ['kept-artifact'],
+        (await listArtifacts(store, 'session-1')).map((record) => record.id).sort(),
+        ['later-artifact', 'retained-artifact'],
       );
-      // Purge, not a tombstone: the bytes are what this reclaims.
-      const kept = await store.getInSession('session-1', 'kept-artifact');
-      assert.ok(kept.record);
-      assert.deepEqual(await readdir(join(root, 'artifacts', 'session-1')), [
-        basename(kept.record.relativePath),
-      ]);
     });
   });
 
   test('excludes selected Artifacts from a conversation snapshot', async () => {
     await withWorkspace(async (root) => {
       const authority = createArtifactStoreWriteAuthority(root);
-      await authority.recover();
       const { store } = authority;
       await store.create({
         ...artifactInput('retained-artifact', 'retained', 10),
@@ -464,17 +374,16 @@ describe('SQLite Artifact store', () => {
 
       assert.equal(copied.artifactIds.has('excluded-archive'), false);
       assert.deepEqual(
-        (await store.list('session-copy')).map((record) => record.name),
+        (await listArtifacts(store, 'session-copy')).map((record) => record.name),
         ['retained-artifact.txt'],
       );
-      assert.equal((await store.get('excluded-archive'))?.sessionId, 'session-1');
+      assert.equal((await getArtifact(store, 'excluded-archive'))?.sessionId, 'session-1');
     });
   });
 
   test('copies explicit linked child Artifacts into a conversation snapshot', async () => {
     await withWorkspace(async (root) => {
       const authority = createArtifactStoreWriteAuthority(root);
-      await authority.recover();
       const { store } = authority;
       await store.create({
         ...artifactInput('child-artifact', 'child result', 10),
@@ -491,16 +400,21 @@ describe('SQLite Artifact store', () => {
 
       const copiedId = copied.artifactIds.get('child-artifact');
       assert.ok(copiedId);
-      assert.deepEqual(await store.readText(copiedId), { ok: true, text: 'child result' });
-      assert.equal((await store.get(copiedId))?.sessionId, 'session-copy');
-      assert.equal((await store.get('child-artifact'))?.sessionId, 'child-session');
+      assert.deepEqual(await readArtifactText(store, copiedId, 'session-copy'), {
+        ok: true,
+        text: 'child result',
+      });
+      assert.equal((await getArtifact(store, copiedId, 'session-copy'))?.sessionId, 'session-copy');
+      assert.equal(
+        (await getArtifact(store, 'child-artifact', 'child-session'))?.sessionId,
+        'child-session',
+      );
     });
   });
 
   test('includes explicit same-Session Artifacts outside the copied turns', async () => {
     await withWorkspace(async (root) => {
       const authority = createArtifactStoreWriteAuthority(root);
-      await authority.recover();
       const { store } = authority;
       await store.create({
         ...artifactInput('retained-artifact', 'retained', 10),
@@ -529,11 +443,14 @@ describe('SQLite Artifact store', () => {
       });
       const copiedUploadId = withInclude.artifactIds.get(upload.id);
       assert.ok(copiedUploadId);
-      assert.deepEqual(await store.readText(copiedUploadId), {
+      assert.deepEqual(await readArtifactText(store, copiedUploadId, 'session-copy-2'), {
         ok: true,
         text: 'uploaded bytes',
       });
-      assert.equal((await store.get(copiedUploadId))?.sessionId, 'session-copy-2');
+      assert.equal(
+        (await getArtifact(store, copiedUploadId, 'session-copy-2'))?.sessionId,
+        'session-copy-2',
+      );
       // Unknown include ids are a no-op, not an error.
       const withUnknown = await store.copyConversationArtifacts({
         sourceSessionId: 'session-1',
@@ -545,31 +462,35 @@ describe('SQLite Artifact store', () => {
     });
   });
 
-  test('user delete evaluates current-generation policy before tombstone state', async () => {
+  test('user deletion respects the current artifact source while owner cleanup remains possible', async () => {
     await withWorkspace(async (root) => {
       const authority = createArtifactStoreWriteAuthority(root);
-      await authority.recover();
       const { store } = authority;
       const input = artifactInput('current-policy', 'replaceable', 1);
       await store.create(input);
-      await store.purge([input.id]);
-      const protectedRecord = await store.create({
+      await store.deleteUserArtifactInSession(input.sessionId, input.id);
+      await store.create({
         ...input,
         content: 'protected replacement',
         source: 'deep_research',
       });
 
-      assert.deepEqual(await store.deleteUserArtifactInSession(input.sessionId, input.id), {
-        kind: 'protected',
+      assert.equal(
+        (await store.deleteUserArtifactInSession(input.sessionId, input.id)).kind,
+        'protected',
+      );
+      assert.equal((await getArtifact(store, input.id))?.source, 'deep_research');
+      assert.deepEqual(await store.readTextInSession(input.sessionId, input.id), {
+        ok: true,
+        text: 'protected replacement',
       });
-      assert.deepEqual(await store.get(input.id), protectedRecord);
       assert.deepEqual(await store.deleteUserArtifactInSession('different-session', input.id), {
         kind: 'not_found',
       });
 
-      await store.delete(input.id);
+      await store.purgeSessionArtifacts(input.sessionId);
       assert.deepEqual(await store.deleteUserArtifactInSession(input.sessionId, input.id), {
-        kind: 'protected',
+        kind: 'not_found',
       });
     });
   });
@@ -602,12 +523,14 @@ describe('SQLite Artifact store', () => {
         const created = await createArtifactStore(root).create(input);
         const reopened = createArtifactStore(root);
 
-        assert.deepEqual(await reopened.list(input.sessionId), [
+        assert.deepEqual(await listArtifacts(reopened, input.sessionId), [
           created,
-          ...(await reopened.list(input.sessionId)).filter((record) => record.id !== created.id),
+          ...(await listArtifacts(reopened, input.sessionId)).filter(
+            (record) => record.id !== created.id,
+          ),
         ]);
-        assert.deepEqual(await reopened.get(created.id), created);
-        assert.deepEqual(await reopened.readText(created.id), {
+        assert.deepEqual(await getArtifact(reopened, created.id), created);
+        assert.deepEqual(await readArtifactText(reopened, created.id), {
           ok: true,
           text: input.content,
         });
@@ -616,15 +539,17 @@ describe('SQLite Artifact store', () => {
     });
   });
 
-  test('rejects metadata names that neither writer could have produced', async () => {
+  test('ignores metadata names that neither writer could have produced', async () => {
     for (const name of ['short ', 'embedded\ttab', 'embedded\nnewline']) {
       await withWorkspace(async (root) => {
         await writeArtifactMetadata(root, [
           canonicalRecord({ id: 'invalid-name', sessionId: 'session-1', name, sizeBytes: 0 }),
         ]);
-        await assert.rejects(
-          () => createArtifactStore(root).list('session-1', { includeDeleted: true }),
-          /Invalid artifact metadata record 1/,
+        const store = createArtifactStore(root);
+        assert.deepEqual(await listArtifacts(store, 'session-1'), []);
+        assert.equal(
+          (await store.create(artifactInput('replacement', 'kept', 1))).id,
+          'replacement',
         );
       });
     }
@@ -640,16 +565,13 @@ describe('SQLite Artifact store', () => {
 
       const created = await createArtifactStore(root).create(input);
       assert.equal(created.name, 'a'.repeat(119));
-      const targetPath = join(root, 'artifacts', created.relativePath);
-      const stagingPath = publicationStagingPath(targetPath);
-      await writeFile(stagingPath, input.content, { flag: 'wx' });
-
       const authority = createArtifactStoreWriteAuthority(root);
       const reopened = authority.store;
-      await authority.recover();
-      await assert.rejects(() => stat(stagingPath), { code: 'ENOENT' });
       assert.deepEqual(await reopened.create(input), created);
-      assert.deepEqual(await reopened.readText(created.id), { ok: true, text: input.content });
+      assert.deepEqual(await readArtifactText(reopened, created.id), {
+        ok: true,
+        text: input.content,
+      });
     });
   });
 
@@ -683,13 +605,13 @@ describe('SQLite Artifact store', () => {
       });
 
       const reopened = createArtifactStore(root);
-      assert.deepEqual(await reopened.get(report.id), report);
-      assert.deepEqual(await reopened.get(archive.id), archive);
-      assert.deepEqual(await reopened.readText(report.id), {
+      assert.deepEqual(await getArtifact(reopened, report.id), report);
+      assert.deepEqual(await getArtifact(reopened, archive.id), archive);
+      assert.deepEqual(await readArtifactText(reopened, report.id), {
         ok: true,
         text: '<h1>Research</h1>',
       });
-      assert.deepEqual(await reopened.readText(archive.id), {
+      assert.deepEqual(await readArtifactText(reopened, archive.id), {
         ok: true,
         text: '{"ok":true}',
       });
@@ -706,7 +628,7 @@ describe('SQLite Artifact store', () => {
       const replayed = await createArtifactStore(root).create(input);
       assert.deepEqual(replayed, first);
       await assert.rejects(() => stat(metadataPath), { code: 'ENOENT' });
-      assert.deepEqual(await createArtifactStore(root).readText(first.id), {
+      assert.deepEqual(await readArtifactText(createArtifactStore(root), first.id), {
         ok: true,
         text: '# Durable result',
       });
@@ -732,31 +654,20 @@ describe('SQLite Artifact store', () => {
     });
   });
 
-  test('exact tombstone replay atomically revives only an unchanged canonical payload', async () => {
+  test('a stable id can be created again after physical deletion', async () => {
     await withWorkspace(async (root) => {
       const input = deepResearchArtifactInput('stable-revive', '# Revivable');
       const store = createArtifactStore(root);
       const first = await store.create(input);
-      await store.delete(first.id);
-      const deleted = await store.get(first.id);
-      assert.equal(deleted?.status, 'deleted');
+      await store.deleteOwnedArtifactInSession(input.sessionId, first.id, input.source);
+      assert.equal(await getArtifact(store, first.id), null);
 
-      const revived = await createArtifactStore(root).create(input);
-      assert.deepEqual(revived, { ...first, status: 'live' });
-      assert.deepEqual(await createArtifactStore(root).readText(first.id), {
+      const recreated = await createArtifactStore(root).create(input);
+      assert.deepEqual({ ...recreated, createdAt: first.createdAt }, first);
+      assert.deepEqual(await readArtifactText(createArtifactStore(root), first.id), {
         ok: true,
         text: '# Revivable',
       });
-
-      const reopened = createArtifactStore(root);
-      await reopened.delete(first.id);
-      await writeFile(join(root, 'artifacts', first.relativePath), '# Rewritten', 'utf8');
-      assert.equal(Buffer.byteLength(input.content), Buffer.byteLength('# Rewritten'));
-      await assert.rejects(
-        () => createArtifactStore(root).create(input),
-        /already exists with different metadata or content/,
-      );
-      assert.equal((await createArtifactStore(root).get(first.id))?.status, 'deleted');
     });
   });
 
@@ -767,7 +678,7 @@ describe('SQLite Artifact store', () => {
       await Promise.all(ids.map((id, index) => store.create(artifactInput(id, id, index + 1))));
 
       const reopened = createArtifactStore(root);
-      const rows = await reopened.list('session-1', { includeDeleted: true });
+      const rows = await listArtifacts(reopened, 'session-1');
       assert.deepEqual(rows.map((record) => record.id).sort(), ids.sort());
       await assert.rejects(() => stat(join(root, 'artifacts', 'metadata.jsonl')), {
         code: 'ENOENT',
@@ -784,7 +695,7 @@ describe('SQLite Artifact store', () => {
         second.create(artifactInput('independent-second', 'second', 2)),
       ]);
 
-      const rows = await createArtifactStore(root).list('session-1', { includeDeleted: true });
+      const rows = await listArtifacts(createArtifactStore(root), 'session-1');
       assert.deepEqual(rows.map((record) => record.id).sort(), [
         'independent-first',
         'independent-second',
@@ -817,7 +728,7 @@ describe('SQLite Artifact store', () => {
         kind: 'file',
         content: bytes,
         mimeType: 'application/octet-stream',
-        source: 'fixture',
+        source: 'tool_result',
         summary: 'accepted summary',
         now: 7,
       };
@@ -848,18 +759,17 @@ describe('SQLite Artifact store', () => {
         relativePath: 'session-1/accepted-id-accepted.bin',
         sizeBytes: 4,
         mimeType: 'application/octet-stream',
-        source: 'fixture',
+        source: 'tool_result',
         summary: 'accepted summary',
-        status: 'live',
       });
       assert.deepEqual(
         await readFile(join(root, 'artifacts', record.relativePath)),
         Buffer.from('safe'),
       );
       const reopened = createArtifactStore(root);
-      assert.deepEqual(await reopened.get(record.id), record);
-      assert.deepEqual(await reopened.list('session-1'), [record]);
-      assert.equal(await reopened.get('mutated-id'), null);
+      assert.deepEqual(await getArtifact(reopened, record.id), record);
+      assert.deepEqual(await listArtifacts(reopened, 'session-1'), [record]);
+      assert.equal(await getArtifact(reopened, 'mutated-id'), null);
     });
   });
 
@@ -869,38 +779,18 @@ describe('SQLite Artifact store', () => {
       const writer = createArtifactStore(root);
       await stale.create(artifactInput('first', 'first', 1));
       assert.deepEqual(
-        (await stale.list('session-1')).map((record) => record.id),
+        (await listArtifacts(stale, 'session-1')).map((record) => record.id),
         ['first'],
       );
 
       await writer.create(artifactInput('second', 'second', 2));
 
       assert.deepEqual(
-        (await stale.list('session-1')).map((record) => record.id),
+        (await listArtifacts(stale, 'session-1')).map((record) => record.id),
         ['second', 'first'],
       );
-      assert.equal((await stale.get('second'))?.id, 'second');
-      assert.deepEqual(await stale.readText('second'), { ok: true, text: 'second' });
-    });
-  });
-
-  test('snapshots purge inputs before lock waits', async () => {
-    await withWorkspace(async (root) => {
-      const authority = createArtifactStoreWriteAuthority(root);
-      await authority.recover();
-      const { store } = authority;
-      await store.create(artifactInput('purge-me', 'purge', 2));
-      await store.create(artifactInput('keep-me', 'keep', 3));
-
-      const purgeIds = ['purge-me'];
-      const purgeLock = await holdArtifactWriterLock(root);
-      const purged = store.purge(purgeIds);
-      purgeIds[0] = 'keep-me';
-      purgeLock.release();
-      await purgeLock.finished;
-      await purged;
-      assert.equal(await store.get('purge-me'), null);
-      assert.equal((await store.get('keep-me'))?.status, 'live');
+      assert.equal((await getArtifact(stale, 'second'))?.id, 'second');
+      assert.deepEqual(await readArtifactText(stale, 'second'), { ok: true, text: 'second' });
     });
   });
 
@@ -908,7 +798,7 @@ describe('SQLite Artifact store', () => {
     await withWorkspace(async (root) => {
       const store = createArtifactStore(root);
       await store.create(artifactInput('published', 'kept', 1));
-      store.close?.();
+      store.close();
 
       await assert.rejects(
         () => store.create(artifactInput('rejected', 'not durable', 2)),
@@ -919,62 +809,26 @@ describe('SQLite Artifact store', () => {
         { code: 'ENOENT' },
       );
       const reopened = createArtifactStore(root);
-      assert.equal(await reopened.get('rejected'), null);
+      assert.equal(await getArtifact(reopened, 'rejected'), null);
     });
   });
 
-  test('explicit write recovery removes an uncommitted publication and permits stable-id retry', async () => {
+  test('legacy publication residue cannot block stable-id creation', async () => {
     await withWorkspace(async (root) => {
       const residue = await createPublicationResidue(root, 'stable-retry', 'retry.txt', 'old');
       const authority = createArtifactStoreWriteAuthority(root);
       const { store } = authority;
-
-      await assert.rejects(
-        () =>
-          store.create({
-            ...artifactInput('stable-retry', 'new', 1),
-            name: 'retry.txt',
-          }),
-        /Artifact write recovery is required/,
-      );
-      await assert.rejects(() => store.delete('missing'), /Artifact write recovery is required/);
-      await assert.rejects(() => store.purge(['missing']), /Artifact write recovery is required/);
-      assert.equal(await readFile(residue.stagingPath, 'utf8'), 'old');
-      assert.equal(await readFile(residue.targetPath, 'utf8'), 'old');
-
-      await authority.recover();
-      await assert.rejects(() => stat(residue.stagingPath), { code: 'ENOENT' });
-      await assert.rejects(() => stat(residue.targetPath), { code: 'ENOENT' });
-
       const retried = await store.create({
         ...artifactInput('stable-retry', 'new', 1),
         name: 'retry.txt',
       });
       assert.equal(retried.id, 'stable-retry');
-      assert.deepEqual(await store.readText(retried.id), { ok: true, text: 'new' });
-    });
-  });
-
-  test('explicit write recovery completes a committed publication without removing its payload', async () => {
-    await withWorkspace(async (root) => {
-      const first = createArtifactStore(root);
-      const record = await first.create(artifactInput('committed', 'durable', 1));
-      const targetPath = join(root, 'artifacts', record.relativePath);
-      const stagingPath = publicationStagingPath(targetPath);
-      await writeFile(stagingPath, await readFile(targetPath));
-      assert.notEqual((await stat(targetPath)).ino, (await stat(stagingPath)).ino);
-
-      const authority = createArtifactStoreWriteAuthority(root);
-      const reopened = authority.store;
-      await authority.recover();
-
-      await assert.rejects(() => stat(stagingPath), { code: 'ENOENT' });
-      assert.equal(await readFile(targetPath, 'utf8'), 'durable');
-      assert.deepEqual(await reopened.readText(record.id), { ok: true, text: 'durable' });
+      assert.deepEqual(await readArtifactText(store, retried.id), { ok: true, text: 'new' });
+      assert.equal(await readFile(residue.stagingPath, 'utf8'), 'old');
     });
   });
 
-  test('self-managed SQLite mutation adopts an exact stable-id target-only orphan', async () => {
+  test('stable-id creation replaces an untracked payload', async () => {
     await withWorkspace(async (root) => {
       const input = {
         ...artifactInput('target-orphan', 'orphan bytes', 1),
@@ -988,147 +842,14 @@ describe('SQLite Artifact store', () => {
       const adopted = await bare.create(input);
       assert.equal(adopted.name, 'report.txt');
       assert.equal(adopted.relativePath, 'session-1/target-orphan-report.txt');
-      assert.deepEqual(await createArtifactStore(root).readText(adopted.id), {
+      assert.deepEqual(await readArtifactText(createArtifactStore(root), adopted.id), {
         ok: true,
         text: input.content,
       });
     });
   });
 
-  test('self-managed SQLite mutations recover target-local publications without rescanning unrelated sessions', async () => {
-    await withWorkspace(async (root) => {
-      const store = createArtifactStore(root);
-      await store.create(artifactInput('delete-me', 'delete', 1));
-      await store.create(artifactInput('purge-me', 'purge', 2));
-
-      const unrelated = await createPublicationResidue(
-        root,
-        'unrelated',
-        'unrelated.txt',
-        'publication bytes',
-        'session-2',
-      );
-      await rm(unrelated.targetPath);
-      await writeFile(unrelated.targetPath, 'different bytes');
-
-      const created = await store.create({
-        ...artifactInput('target-only', 'created', 3),
-        sessionId: 'session-1',
-      });
-      assert.equal(created.id, 'target-only');
-      await store.delete('delete-me');
-      await store.purge(['purge-me']);
-      assert.equal((await store.get('delete-me'))?.status, 'deleted');
-      assert.equal(await store.get('purge-me'), null);
-
-      await assert.rejects(
-        () => createArtifactStoreWriteAuthority(root).recover(),
-        /Artifact publication residue does not match canonical state/,
-      );
-    });
-  });
-
-  test('authority recovery adopts only its pre-existing exact stable-id orphan snapshot', async () => {
-    await withWorkspace(async (root) => {
-      const input = artifactInput('drifted-orphan', 'expected bytes', 1);
-      const orphanPath = join(root, 'artifacts', 'session-1', 'drifted-orphan-drifted-orphan.txt');
-      await mkdir(dirname(orphanPath), { recursive: true });
-      await writeFile(orphanPath, 'drifted bytes!', { flag: 'wx' });
-      const authority = createArtifactStoreWriteAuthority(root);
-      const authorized = authority.store;
-      await authority.recover();
-      await rm(orphanPath);
-      await writeFile(orphanPath, input.content, { flag: 'wx' });
-
-      await assert.rejects(
-        () => authorized.create(input),
-        /already exists with different metadata or content/,
-      );
-      assert.equal(await readFile(orphanPath, 'utf8'), input.content);
-      await assert.rejects(() => stat(join(root, 'artifacts', 'metadata.jsonl')), {
-        code: 'ENOENT',
-      });
-    });
-
-    await withWorkspace(async (root) => {
-      const input = artifactInput('late-payload', 'same bytes', 1);
-      const authority = createArtifactStoreWriteAuthority(root);
-      const store = authority.store;
-      await authority.recover();
-      const residue = await createPublicationResidue(
-        root,
-        input.id,
-        input.name,
-        input.content as string,
-      );
-
-      await assert.rejects(() => store.create(input), /Artifact write recovery is required/);
-      assert.equal(await readFile(residue.stagingPath, 'utf8'), input.content);
-      assert.equal(await readFile(residue.targetPath, 'utf8'), input.content);
-    });
-  });
-
-  test('write recovery removes only canonical purge-intent temp files', async () => {
-    await withWorkspace(async (root) => {
-      const artifactRoot = join(root, 'artifacts');
-      await mkdir(artifactRoot, { recursive: true });
-      const canonicalTemps = [
-        join(
-          artifactRoot,
-          '.artifact-purge-intent.json.123.00000000-0000-4000-8000-000000000000.tmp',
-        ),
-      ];
-      const unknownFiles = [
-        join(artifactRoot, 'metadata.jsonl.123.00000000-0000-4000-8000-000000000000.tmp'),
-        join(artifactRoot, 'metadata.jsonl.123.1700000000000.tmp'),
-        join(artifactRoot, 'metadata.jsonl.123.not-a-uuid.tmp'),
-        join(artifactRoot, '.artifact-purge-intent.json.123.not-a-uuid.tmp'),
-        join(artifactRoot, 'other.jsonl.123.00000000-0000-4000-8000-000000000000.tmp'),
-      ];
-      for (const path of canonicalTemps) await writeFile(path, 'stale', 'utf8');
-      for (const path of unknownFiles) await writeFile(path, 'keep', 'utf8');
-
-      await createArtifactStoreWriteAuthority(root).recover();
-
-      for (const path of canonicalTemps) {
-        await assert.rejects(() => stat(path), { code: 'ENOENT' });
-      }
-      for (const path of unknownFiles) assert.equal(await readFile(path, 'utf8'), 'keep');
-    });
-  });
-
-  test('write recovery fails closed on a non-file canonical transaction temp', async () => {
-    await withWorkspace(async (root) => {
-      const artifactRoot = join(root, 'artifacts');
-      const canonicalTemp = join(
-        artifactRoot,
-        '.artifact-purge-intent.json.123.00000000-0000-4000-8000-000000000000.tmp',
-      );
-      await mkdir(canonicalTemp, { recursive: true });
-
-      await assert.rejects(
-        () => createArtifactStoreWriteAuthority(root).recover(),
-        /Artifact recovery temp is not a regular file/,
-      );
-      assert.equal((await stat(canonicalTemp)).isDirectory(), true);
-    });
-  });
-
-  test('fails closed on mismatched publication residue', async () => {
-    await withWorkspace(async (root) => {
-      const residue = await createPublicationResidue(root, 'mismatch', 'file.txt', 'payload');
-      await rm(residue.targetPath);
-      await writeFile(residue.targetPath, 'different inode', 'utf8');
-      await assert.rejects(
-        () => createArtifactStoreWriteAuthority(root).recover(),
-        /Artifact publication residue does not match canonical state/,
-      );
-      assert.equal(await readFile(residue.stagingPath, 'utf8'), 'payload');
-      assert.equal(await readFile(residue.targetPath, 'utf8'), 'different inode');
-    });
-  });
-
-  test('metadata fails closed on invalid path identities and bounded turn keys', async () => {
+  test('invalid path identities and turn keys cannot block later artifact writes', async () => {
     const invalidIdentities = [
       { field: 'id', value: 'a'.repeat(ARTIFACT_ENTITY_ID_MAX_CHARS + 1) },
       { field: 'id', value: '.' },
@@ -1140,68 +861,107 @@ describe('SQLite Artifact store', () => {
     for (const { field, value } of invalidIdentities) {
       await withWorkspace(async (root) => {
         await writeArtifactMetadata(root, [recordWithIdentity(field, value)]);
-        await assert.rejects(
-          () => createArtifactStore(root).list('session-1', { includeDeleted: true }),
-          /Invalid artifact metadata record 1/,
+        const store = createArtifactStore(root);
+        assert.deepEqual(await listArtifacts(store, 'session-1'), []);
+        assert.equal(
+          (await store.create(artifactInput('replacement', 'kept', 1))).id,
+          'replacement',
         );
       });
     }
   });
 
-  test('purge-intent recovery fails closed on non-canonical artifact identities', async () => {
-    for (const artifactId of [
-      'a'.repeat(ARTIFACT_ENTITY_ID_MAX_CHARS + 1),
-      '.',
-      'artifact/id',
-      'artifact id',
-      'artifact\nid',
-    ]) {
-      await withWorkspace(async (root) => {
-        const intentPath = join(root, 'artifacts', '.artifact-purge-intent.json');
-        await mkdir(dirname(intentPath), { recursive: true });
-        await writeFile(
-          intentPath,
-          JSON.stringify({ schemaVersion: 1, artifactIds: [artifactId] }),
-          'utf8',
-        );
+  test('legacy purge-intent residue cannot block later writes', async () => {
+    await withWorkspace(async (root) => {
+      const intentPath = join(root, 'artifacts', '.artifact-purge-intent.json');
+      await mkdir(dirname(intentPath), { recursive: true });
+      await writeFile(intentPath, 'not valid json', 'utf8');
 
-        await assert.rejects(
-          () => createArtifactStoreWriteAuthority(root).recover(),
-          /Invalid artifact purge intent/,
-        );
-        assert.equal((await stat(intentPath)).isFile(), true);
-      });
-    }
+      const authority = createArtifactStoreWriteAuthority(root);
+      const record = await authority.store.create(artifactInput('after-retired-purge', 'kept', 1));
+
+      assert.equal(record.id, 'after-retired-purge');
+      assert.equal(await readFile(intentPath, 'utf8'), 'not valid json');
+    });
   });
 
-  test('soft delete tombstones bytes until idempotent purge', async () => {
+  test('user delete physically removes metadata and bytes idempotently', async () => {
     await withWorkspace(async (root) => {
       const store = createArtifactStore(root);
       const record = await store.create(artifactInput('artifact-1', '<h1>Report</h1>', 1));
 
-      await store.delete(record.id);
-      await store.delete(record.id);
-      assert.deepEqual(await store.list('session-1'), []);
-      assert.equal((await store.get(record.id))?.status, 'deleted');
-      assert.deepEqual(await store.readText(record.id), { ok: false, reason: 'deleted' });
-      assert.deepEqual(await store.readText(record.id, { includeDeleted: true }), {
-        ok: true,
-        text: '<h1>Report</h1>',
-      });
-      assert.equal(
-        await readFile(join(root, 'artifacts', record.relativePath), 'utf8'),
-        '<h1>Report</h1>',
-      );
-
-      await store.purge([record.id]);
-      await store.purge([record.id]);
-      assert.equal(await store.get(record.id), null);
+      await store.deleteUserArtifactInSession(record.sessionId, record.id);
+      await store.deleteUserArtifactInSession(record.sessionId, record.id);
+      assert.deepEqual(await listArtifacts(store, 'session-1'), []);
+      assert.equal(await getArtifact(store, record.id), null);
       await assert.rejects(() => stat(join(root, 'artifacts', record.relativePath)), {
         code: 'ENOENT',
       });
     });
   });
 
+  test('a partial purge retains its cleanup obligations across reopen', async (t) => {
+    await withWorkspace(async (root) => {
+      const authority = createArtifactStoreWriteAuthority(root);
+      const first = await authority.store.create(artifactInput('purge-first', 'first', 1));
+      const second = await authority.store.create(artifactInput('purge-second', 'second', 2));
+      const target = await fsPromises.realpath(join(root, 'artifacts', second.relativePath));
+      const originalRm = fsPromises.rm;
+      const injected = t.mock.method(
+        fsPromises,
+        'rm',
+        async (...[path, options]: Parameters<typeof originalRm>) => {
+          if (path === target)
+            throw Object.assign(new Error('injected unlink failure'), { code: 'EIO' });
+          return originalRm(path, options);
+        },
+      );
+      syncBuiltinESMExports();
+      try {
+        await assert.rejects(authority.store.purgeSessionArtifacts(first.sessionId), {
+          code: 'EIO',
+        });
+      } finally {
+        injected.mock.restore();
+        syncBuiltinESMExports();
+      }
+      authority.close();
+      const reopened = createArtifactStore(root);
+      assert.equal((await listArtifacts(reopened, first.sessionId)).length, 2);
+      if (process.platform !== 'win32') {
+        const originalOpen = fsPromises.open;
+        const syncFailure = t.mock.method(
+          fsPromises,
+          'open',
+          async (...args: Parameters<typeof originalOpen>) => {
+            if (args[0] === dirname(target)) {
+              throw Object.assign(new Error('injected directory sync failure'), { code: 'EIO' });
+            }
+            return originalOpen(...args);
+          },
+        );
+        syncBuiltinESMExports();
+        try {
+          // The second attempt sees no payloads, but still owes the directory sync.
+          for (let attempt = 0; attempt < 2; attempt++) {
+            await assert.rejects(reopened.purgeSessionArtifacts(first.sessionId), { code: 'EIO' });
+            assert.equal((await listArtifacts(reopened, first.sessionId)).length, 2);
+          }
+        } finally {
+          syncFailure.mock.restore();
+          syncBuiltinESMExports();
+        }
+      }
+      await reopened.purgeSessionArtifacts(first.sessionId);
+      assert.deepEqual(await listArtifacts(reopened, first.sessionId), []);
+      for (const record of [first, second]) {
+        await assert.rejects(stat(join(root, 'artifacts', record.relativePath)), {
+          code: 'ENOENT',
+        });
+      }
+    });
+  });
+
   test('purge rejects a symlink escape without deleting external bytes or metadata', async (t) => {
     const outsideRoot = await mkdtemp(join(tmpdir(), 'maka-artifact-outside-'));
     try {
@@ -1230,15 +990,18 @@ describe('SQLite Artifact store', () => {
 
         const store = createArtifactStore(root);
         await assert.rejects(
-          () => store.purge([safeRecord.id, escapedRecord.id]),
+          () => store.deleteUserArtifactInSession(escapedRecord.sessionId, escapedRecord.id),
           /outside the artifact root/,
         );
 
         assert.equal(await readFile(externalPath, 'utf8'), 'external');
         assert.equal(await readFile(join(artifactRoot, safeRecord.relativePath), 'utf8'), 'safe');
         assert.equal(await readFile(metadataPath, 'utf8'), metadataBefore);
-        assert.equal((await store.get(safeRecord.id))?.id, safeRecord.id);
-        assert.equal((await store.get(escapedRecord.id))?.id, escapedRecord.id);
+        assert.equal((await getArtifact(store, safeRecord.id))?.id, safeRecord.id);
+        assert.equal(
+          (await getArtifact(store, escapedRecord.id, escapedRecord.sessionId))?.id,
+          escapedRecord.id,
+        );
       });
     } finally {
       await rm(outsideRoot, { recursive: true, force: true });
@@ -1263,11 +1026,11 @@ describe('SQLite Artifact store', () => {
       await writeArtifactMetadata(root, [record]);
 
       const store = createArtifactStore(root);
-      await store.purge([record.id]);
+      await store.deleteUserArtifactInSession(record.sessionId, record.id);
 
       assert.equal(await readFile(targetPath, 'utf8'), 'keep target');
       await assert.rejects(() => stat(linkPath), { code: 'ENOENT' });
-      assert.equal(await store.get(record.id), null);
+      assert.equal(await getArtifact(store, record.id), null);
     });
   });
 
@@ -1297,12 +1060,15 @@ describe('SQLite Artifact store', () => {
       assert.equal(firstStat.ino, secondStat.ino);
 
       const store = createArtifactStore(root);
-      await store.purge([first.id]);
+      await store.deleteUserArtifactInSession(first.sessionId, first.id);
 
       await assert.rejects(() => stat(firstPath), { code: 'ENOENT' });
       assert.equal(await readFile(secondPath, 'utf8'), 'shared bytes');
-      assert.equal((await store.get(second.id))?.id, second.id);
-      assert.deepEqual(await store.readText(second.id), { ok: true, text: 'shared bytes' });
+      assert.equal((await getArtifact(store, second.id))?.id, second.id);
+      assert.deepEqual(await readArtifactText(store, second.id), {
+        ok: true,
+        text: 'shared bytes',
+      });
     });
   });
 
@@ -1332,9 +1098,12 @@ describe('SQLite Artifact store', () => {
       await writeArtifactMetadata(root, [lower, upper]);
 
       const store = createArtifactStore(root);
-      await assert.rejects(() => store.purge([lower.id]), /path is still referenced/);
+      await assert.rejects(
+        () => store.deleteUserArtifactInSession(lower.sessionId, lower.id),
+        /path is still referenced/,
+      );
       assert.equal(await readFile(upperPath, 'utf8'), 'shared bytes');
-      assert.equal((await store.get(upper.id))?.status, 'live');
+      assert.equal((await getArtifact(store, upper.id))?.id, upper.id);
     });
   });
 
@@ -1367,102 +1136,42 @@ describe('SQLite Artifact store', () => {
       await writeArtifactMetadata(root, [lower, upper]);
 
       const store = createArtifactStore(root);
-      await assert.rejects(() => store.purge([lower.id]), /path is still referenced/);
+      await assert.rejects(
+        () => store.deleteUserArtifactInSession(lower.sessionId, lower.id),
+        /path is still referenced/,
+      );
 
-      assert.deepEqual(await store.readText(upper.id), { ok: true, text: 'shared bytes' });
+      assert.deepEqual(await readArtifactText(store, upper.id), { ok: true, text: 'shared bytes' });
       assert.equal((await lstat(lowerPath)).isSymbolicLink(), true);
       assert.equal((await lstat(upperPath)).isSymbolicLink(), true);
       assert.equal(await readFile(targetPath, 'utf8'), 'shared bytes');
     });
   });
 
-  test('startup recovery completes purge after payload removal but before metadata commit', async () => {
-    await withWorkspace(async (root) => {
-      const authority = createArtifactStoreWriteAuthority(root);
-      const store = authority.store;
-      await authority.recover();
-      const record = await store.create(artifactInput('purge-retry', 'remove me', 1));
-      const payloadPath = join(root, 'artifacts', record.relativePath);
-      const metadataPath = join(root, 'artifacts', 'metadata.jsonl');
-      const purgeIntentPath = join(root, 'artifacts', '.artifact-purge-intent.json');
-      await rm(payloadPath);
-      await writeFile(
-        purgeIntentPath,
-        JSON.stringify({ schemaVersion: 1, artifactIds: [record.id] }),
-        'utf8',
-      );
-      await assert.rejects(() => stat(payloadPath), { code: 'ENOENT' });
-      assert.equal((await store.get(record.id))?.id, record.id);
-      assert.deepEqual(JSON.parse(await readFile(purgeIntentPath, 'utf8')), {
-        schemaVersion: 1,
-        artifactIds: [record.id],
-      });
-      await assert.rejects(
-        () => store.create(artifactInput('same-instance-blocked', 'blocked', 2)),
-        /Artifact write recovery is required/,
-      );
-
-      await authority.recover();
-      await authority.recover();
-
-      assert.equal(await store.get(record.id), null);
-      await assert.rejects(() => stat(metadataPath), { code: 'ENOENT' });
-      await assert.rejects(() => stat(purgeIntentPath), { code: 'ENOENT' });
-      await store.purge([record.id]);
-
-      await writeFile(
-        purgeIntentPath,
-        JSON.stringify({ schemaVersion: 1, artifactIds: [record.id] }),
-        'utf8',
-      );
-      await createArtifactStoreWriteAuthority(root).recover();
-      await assert.rejects(() => stat(metadataPath), { code: 'ENOENT' });
-      await assert.rejects(() => stat(purgeIntentPath), { code: 'ENOENT' });
-    });
-  });
-
-  test('self-managed SQLite stores recover an interrupted purge before the next write', async () => {
-    await withWorkspace(async (root) => {
-      const store = createArtifactStore(root);
-      const record = await store.create(artifactInput('interrupted-purge', 'remove me', 1));
-      await rm(join(root, 'artifacts', record.relativePath));
-      await writeFile(
-        join(root, 'artifacts', '.artifact-purge-intent.json'),
-        JSON.stringify({ schemaVersion: 1, artifactIds: [record.id] }),
-        'utf8',
-      );
-
-      const reopened = createArtifactStore(root);
-      const next = await reopened.create(artifactInput('after-recovery', 'kept', 2));
-      assert.equal(next.id, 'after-recovery');
-      assert.equal(await reopened.get(record.id), null);
-      await assert.rejects(() => stat(join(root, 'artifacts', '.artifact-purge-intent.json')), {
-        code: 'ENOENT',
-      });
-    });
-  });
-
-  test('durable attachment reads authenticate the owning session and reject tombstoned bytes', async () => {
+  test('durable attachment reads report physically deleted bytes as missing', async () => {
     await withWorkspace(async (root) => {
       const store = createArtifactStore(root);
       const png = Uint8Array.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
       await store.create({ ...artifactInput('image', png, 1), name: 'image.png', kind: 'image' });
-      await store.delete('image');
+      await store.deleteUserArtifactInSession('session-1', 'image');
 
-      assert.deepEqual(await store.readBinary('image'), { ok: false, reason: 'deleted' });
+      assert.deepEqual(await readArtifactBinary(store, 'image'), {
+        ok: false,
+        reason: 'not_found',
+      });
       assert.deepEqual(
         await store.readDurableAttachmentBinary({
           artifactId: 'image',
           sessionId: 'other-session',
         }),
-        { ok: false, reason: 'session_mismatch' },
+        { ok: false, reason: 'not_found' },
       );
       assert.deepEqual(
         await store.readDurableAttachmentBinary({
           artifactId: 'image',
           sessionId: 'session-1',
         }),
-        { ok: false, reason: 'deleted' },
+        { ok: false, reason: 'not_found' },
       );
     });
   });
@@ -1478,8 +1187,8 @@ describe('SQLite Artifact store', () => {
         name: 'unknown.bin',
       });
 
-      assert.deepEqual(await store.readText('large'), { ok: false, reason: 'too_large' });
-      assert.deepEqual(await store.readBinary('unknown'), {
+      assert.deepEqual(await readArtifactText(store, 'large'), { ok: false, reason: 'too_large' });
+      assert.deepEqual(await readArtifactBinary(store, 'unknown'), {
         ok: false,
         reason: 'unsupported_mime',
       });
@@ -1497,12 +1206,16 @@ describe('SQLite Artifact store', () => {
         name: 'boundary.txt',
         kind: 'file',
         content: 'boundary',
+        source: 'tool_result',
         now: 1,
       });
 
       assert.equal(record.id.length, ARTIFACT_ENTITY_ID_MAX_CHARS);
-      assert.deepEqual(await createArtifactStore(root).get(boundaryId), record);
-      assert.deepEqual(await createArtifactStore(root).list(boundaryId), [record]);
+      assert.deepEqual(
+        await getArtifact(createArtifactStore(root), boundaryId, boundaryId),
+        record,
+      );
+      assert.deepEqual(await listArtifacts(createArtifactStore(root), boundaryId), [record]);
     });
   });
 
@@ -1596,7 +1309,7 @@ describe('SQLite Artifact store', () => {
           }),
           { ok: false, reason: 'not_allowed' },
         );
-        assert.deepEqual(await createArtifactStore(root).readText(record.id), {
+        assert.deepEqual(await readArtifactText(createArtifactStore(root), record.id), {
           ok: false,
           reason: 'not_allowed',
         });
@@ -1657,7 +1370,7 @@ function artifactInput(id: string, content: string | Uint8Array, now: number) {
     name: `${id}.txt`,
     kind: 'file' as const,
     content,
-    source: 'fixture' as const,
+    source: 'tool_result' as const,
     now,
   };
 }
@@ -1692,8 +1405,7 @@ function canonicalRecord(input: {
     kind: 'file',
     relativePath: `${input.sessionId}/${input.id}-${input.name}`,
     sizeBytes: input.sizeBytes,
-    source: 'fixture',
-    status: 'live',
+    source: 'tool_result',
   };
 }
 
diff --git a/packages/storage/src/__tests__/artifact-stores.test.ts b/packages/storage/src/__tests__/artifact-stores.test.ts
index c50bdab1b5..1965bbcc63 100644
--- a/packages/storage/src/__tests__/artifact-stores.test.ts
+++ b/packages/storage/src/__tests__/artifact-stores.test.ts
@@ -18,17 +18,17 @@
  */
 
 import assert from 'node:assert/strict';
-import { mkdtemp, rm, stat } from 'node:fs/promises';
+import { mkdir, mkdtemp, rm, stat, writeFile } from 'node:fs/promises';
+import { DatabaseSync } from 'node:sqlite';
 import { tmpdir } from 'node:os';
 import { join } from 'node:path';
 import { after, describe, test } from 'node:test';
 import {
   authenticateInteractiveArtifactStoreWriter,
   openInteractiveArtifactStoreForWrite,
-  startRetiredCaptureSweep,
   type InteractiveArtifactStoreWriter,
 } from '../artifact-stores.js';
-import { ARTIFACT_WRITER_LOCK_FILE } from '../artifact-storage-layout.js';
+import { ARTIFACT_WRITER_LOCK_FILE } from '../artifact-writer-lock.js';
 import {
   resolveStorageRoot,
   StorageRootAuthorityError,
@@ -45,6 +45,96 @@ import {
 after(removeTrackedControlDirectories);
 
 describe('interactive artifact store authority', () => {
+  test('reads retained v1 payloads after upgrade without reviving retired rows', async () => {
+    await withInteractiveOwner(async (owner, root, track) => {
+      const initial = await openInteractiveArtifactStoreForWrite(owner.lease);
+      initial.close();
+      const db = new DatabaseSync(join(root, 'runtime.sqlite'));
+      db.exec(`
+        DROP TABLE artifact_records;
+        CREATE TABLE artifact_records (
+          storage_key TEXT PRIMARY KEY, artifact_id TEXT NOT NULL,
+          session_id TEXT NOT NULL, created_at INTEGER NOT NULL CHECK(created_at >= 0),
+          status TEXT NOT NULL CHECK(status IN ('live', 'deleted')),
+          relative_path TEXT NOT NULL, record_json TEXT NOT NULL
+        );
+        CREATE INDEX artifact_records_session_order ON artifact_records(session_id, created_at, storage_key);
+        CREATE UNIQUE INDEX artifact_records_relative_path ON artifact_records(relative_path);
+        UPDATE operational_schema_migrations SET version = 1 WHERE scope = 'artifact';
+      `);
+      const retained = [
+        'tool_result',
+        'tool_result_projection',
+        'tool_result_archive',
+        'subagent_writeback',
+        'deep_research',
+        'user_upload',
+        'session_effect',
+      ];
+      const image = Buffer.from(
+        'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aN1sAAAAASUVORK5CYII=',
+        'base64',
+      );
+      await mkdir(join(root, 'artifacts', 'session-1'), { recursive: true });
+      for (const source of [...retained, 'fixture', 'deleted', 'malformed']) {
+        const path = `session-1/${source}-result.txt`;
+        const content =
+          source === 'tool_result_projection' || source === 'user_upload'
+            ? image
+            : `original ${source}`;
+        const record = {
+          id: source,
+          sessionId: 'session-1',
+          turnId: 'turn-1',
+          createdAt: 1,
+          name: 'result.txt',
+          kind: 'file',
+          sizeBytes: Buffer.byteLength(content),
+          relativePath: path,
+          source: source === 'deleted' ? 'tool_result' : source,
+          status: source === 'deleted' ? 'deleted' : 'live',
+        };
+        await writeFile(join(root, 'artifacts', path), content);
+        db.prepare('INSERT INTO artifact_records VALUES (?, ?, ?, ?, ?, ?, ?)').run(
+          source,
+          source,
+          'session-1',
+          1,
+          record.status,
+          path,
+          source === 'malformed' ? '{' : JSON.stringify(record),
+        );
+      }
+      db.close();
+      for (let reopen = 0; reopen < 2; reopen += 1) {
+        const store = track(await openInteractiveArtifactStoreForWrite(owner.lease));
+        assert.deepEqual(
+          (await store.listTurnArtifacts('session-1', 'turn-1')).map((r) => r.id).sort(),
+          [...retained].sort(),
+        );
+        for (const source of retained) {
+          if (source === 'tool_result_projection' || source === 'user_upload') {
+            assert.deepEqual(
+              await store.readDurableAttachmentBinary({
+                sessionId: 'session-1',
+                artifactId: source,
+              }),
+              { ok: true, base64: image.toString('base64'), mimeType: 'image/png' },
+            );
+            continue;
+          }
+          const result = await store.readTextInSession('session-1', source);
+          assert.equal(result.ok, true);
+          if (result.ok) assert.equal(result.text, `original ${source}`);
+        }
+        for (const id of ['fixture', 'deleted', 'malformed']) {
+          assert.equal((await store.getInSession('session-1', id)).record, null);
+        }
+        store.close();
+      }
+    });
+  });
+
   test('requires authentic leases and writer facades', async () => {
     await assert.rejects(
       () =>
@@ -72,22 +162,20 @@ describe('interactive artifact store authority', () => {
 
       assert.strictEqual(first, second);
       assert.strictEqual(authenticateInteractiveArtifactStoreWriter(first), first);
-      await first.recover();
       await first.create(artifactInput('deleted', 'delete me'));
       const deleted = await first.deleteUserArtifactInSession('session-1', 'deleted');
 
       assert.strictEqual(await openInteractiveArtifactStoreForWrite(owner.lease), first);
       assert.equal(deleted.kind, 'deleted');
       const page = await first.listPage('session-1', { offset: 0, limit: 1 });
-      assert.equal(page.total, 1);
-      assert.equal(page.records[0]?.status, 'deleted');
+      assert.equal(page.total, 0);
       assert.deepEqual(await first.getInSession('session-1', 'deleted'), {
         revision: page.revision,
-        record: page.records[0],
+        record: null,
       });
       assert.deepEqual(await first.readTextInSession('session-1', 'deleted'), {
         ok: false,
-        reason: 'deleted',
+        reason: 'not_found',
       });
       assert.deepEqual(await first.readTextInSession('other-session', 'deleted'), {
         ok: false,
@@ -98,7 +186,7 @@ describe('interactive artifact store authority', () => {
       first.close();
       const reopened = track(await openInteractiveArtifactStoreForWrite(owner.lease));
       assert.notStrictEqual(reopened, first);
-      assert.equal((await reopened.getInSession('session-1', 'deleted')).record?.status, 'deleted');
+      assert.equal((await reopened.getInSession('session-1', 'deleted')).record, null);
     });
   });
 
@@ -110,7 +198,6 @@ describe('interactive artifact store authority', () => {
       const owner = await tryAcquireInteractiveRootOwner(capability);
       assert.ok(owner);
       const writer = track(await openInteractiveArtifactStoreForWrite(owner.lease));
-      await writer.recover();
       const accepted = writer.create(
         artifactInput('accepted', new Uint8Array(8 * 1024 * 1024).fill(0x62)),
       );
@@ -127,7 +214,6 @@ describe('interactive artifact store authority', () => {
   test('snapshots create inputs and makes user deletion idempotent', async () => {
     await withInteractiveOwner(async (owner, _root, track) => {
       const writer = track(await openInteractiveArtifactStoreForWrite(owner.lease));
-      await writer.recover();
       const bytes = Uint8Array.from([0x73, 0x61, 0x66, 0x65]);
       const createInput = artifactInput('accepted', bytes);
       const created = writer.create(createInput);
@@ -147,207 +233,13 @@ describe('interactive artifact store authority', () => {
       assert.equal((await deleted).kind, 'deleted');
       assert.equal(
         (await writer.deleteUserArtifactInSession('session-1', record.id)).kind,
-        'deleted',
-      );
-      assert.equal((await writer.getInSession('session-1', 'accepted')).record?.status, 'deleted');
-    });
-  });
-});
-
-describe('retired request capture sweep', () => {
-  test('drains a real writer, and cannot run before that writer has recovered', async () => {
-    await withInteractiveOwner(async (owner, _root, track) => {
-      const writer = track(await openInteractiveArtifactStoreForWrite(owner.lease));
-      await writer.recover();
-      for (let index = 0; index < 40; index += 1) {
-        await writer.create({
-          ...artifactInput(`capture-${index}`, `request-${index}`),
-          source: 'provider_request_capture',
-        });
-      }
-      await writer.create(artifactInput('kept', 'kept'));
-
-      // Every other test here injects a fake, which is why the failure that
-      // actually shipped got through: wired ahead of recovery, each batch was
-      // refused, the sweep gave up on the first one, and it reclaimed nothing
-      // at all for anyone. Only the real writer and its real queue show that.
-      const errors: unknown[] = [];
-      startRetiredCaptureSweep(writer, {
-        onError: (error) => {
-          errors.push(error);
-        },
-      });
-      await settled(
-        async () => (await writer.listPage('session-1', { offset: 0, limit: 100 })).total === 1,
+        'not_found',
       );
-      assert.deepEqual(errors, []);
-      const remaining = await writer.listPage('session-1', { offset: 0, limit: 100 });
-      assert.equal(remaining.records[0]?.id, 'kept');
-    });
-  });
-
-  test('keeps taking batches until the residue is gone, then stops', async () => {
-    const limits: number[] = [];
-    // A store that purges fewer than asked still has to be revisited.
-    let residue = 5;
-    startRetiredCaptureSweep({
-      purgeRetiredCaptures: async (limit) => {
-        limits.push(limit);
-        const purged = Math.min(2, residue);
-        residue -= purged;
-        return { purged, remaining: residue };
-      },
-    });
-
-    await settled(() => residue === 0);
-    const passes = limits.length;
-    assert.equal(passes, 3, 'a batch that clears part of the residue is followed by another');
-
-    await idleLongerThanOnePause();
-    assert.equal(limits.length, passes, 'an empty residue does not schedule another pass');
-  });
-
-  test('waits longer after a batch that took longer', async () => {
-    const gaps: number[] = [];
-    let previousEnd = 0;
-    let residue = 3;
-    // A batch that holds the writer lock for 300 ms must not be followed
-    // straight away on a store large enough for that to happen.
-    startRetiredCaptureSweep({
-      purgeRetiredCaptures: async () => {
-        if (previousEnd) gaps.push(Date.now() - previousEnd);
-        await new Promise<void>((resolve) => {
-          setTimeout(resolve, 300);
-        });
-        residue -= 1;
-        previousEnd = Date.now();
-        return { purged: 1, remaining: residue };
-      },
+      assert.equal((await writer.getInSession('session-1', 'accepted')).record, null);
     });
-
-    await settled(() => residue === 0);
-    assert.ok(gaps.length >= 1, 'the sweep took more than one batch');
-    assert.ok(
-      gaps.every((gap) => gap >= 600),
-      `a 300 ms batch must be followed by a pause of at least 600 ms, saw ${gaps.join(', ')}`,
-    );
-  });
-
-  test('retries a failed batch, and lets onError repair what made it fail', async () => {
-    let calls = 0;
-    const errors: unknown[] = [];
-    let residue = 2;
-    // The first failure says nothing about the second: a write authority that
-    // another mutation left needing recovery refuses this batch too, until
-    // something recovers it. That something is onError.
-    let recovered = false;
-    startRetiredCaptureSweep(
-      {
-        purgeRetiredCaptures: async () => {
-          calls += 1;
-          if (!recovered) throw new Error('Artifact write recovery is required');
-          residue -= 1;
-          return { purged: 1, remaining: residue };
-        },
-      },
-      {
-        onError: async (error) => {
-          errors.push(error);
-          recovered = true;
-        },
-      },
-    );
-
-    await settled(() => residue === 0);
-    assert.equal(errors.length, 1);
-    assert.match(String(errors[0]), /recovery is required/);
-    assert.ok(calls > 1, 'the sweep came back after the failure');
-  });
-
-  test('gives up once failures stop looking temporary', async () => {
-    let calls = 0;
-    const errors: unknown[] = [];
-    startRetiredCaptureSweep(
-      {
-        purgeRetiredCaptures: async () => {
-          calls += 1;
-          throw new Error('artifact store is unavailable');
-        },
-      },
-      {
-        onError: (error) => {
-          errors.push(error);
-        },
-      },
-    );
-
-    await settled(() => errors.length === 5);
-    await idleLongerThanOnePause();
-    assert.equal(calls, 5, 'a permanent failure does not retry forever');
-  });
-
-  test('does not shrink a batch that cost a lot, because the cost is not the batch', async () => {
-    const limits: number[] = [];
-    let residue = 400;
-    // A batch costs what the whole store costs, not what its own size costs.
-    // Asking for less would pay that same toll again for fewer records, so an
-    // expensive batch is answered by waiting longer, not by taking less.
-    startRetiredCaptureSweep({
-      purgeRetiredCaptures: async (limit) => {
-        limits.push(limit);
-        await new Promise<void>((resolve) => {
-          setTimeout(resolve, 400);
-        });
-        residue -= limit;
-        return { purged: limit, remaining: Math.max(0, residue) };
-      },
-    });
-
-    await settled(() => limits.length >= 2);
-    assert.deepEqual(limits.slice(0, 2), [256, 256]);
-  });
-
-  test('stop keeps the next batch from starting', async () => {
-    let calls = 0;
-    let release!: () => void;
-    const firstBatch = new Promise<void>((resolve) => {
-      release = resolve;
-    });
-    const stop = startRetiredCaptureSweep({
-      purgeRetiredCaptures: async () => {
-        calls += 1;
-        await firstBatch;
-        return { purged: 1, remaining: 99 };
-      },
-    });
-
-    await settled(() => calls === 1);
-    stop();
-    release();
-    await idleLongerThanOnePause();
-    assert.equal(calls, 1, 'a residue that remains is left for a later run');
   });
 });
 
-/** Lets the sweep's own timers run until it reaches the state under test. */
-async function settled(done: () => boolean | Promise<boolean>): Promise<void> {
-  const deadline = Date.now() + 15_000;
-  while (Date.now() < deadline) {
-    if (await done()) return;
-    await new Promise<void>((resolve) => {
-      setTimeout(resolve, 10);
-    });
-  }
-  throw new Error('The capture sweep did not reach the expected state');
-}
-
-/** Long enough that a sweep which meant to continue would have called again. */
-async function idleLongerThanOnePause(): Promise<void> {
-  await new Promise<void>((resolve) => {
-    setTimeout(resolve, 400);
-  });
-}
-
 function artifactInput(id: string, content: string | Uint8Array) {
   return {
     id,
@@ -356,6 +248,7 @@ function artifactInput(id: string, content: string | Uint8Array) {
     name: `${id}.txt`,
     kind: 'file' as const,
     content,
+    source: 'tool_result' as const,
     now: 1,
   };
 }
diff --git a/packages/storage/src/__tests__/artifact-writer-lock.test.ts b/packages/storage/src/__tests__/artifact-writer-lock.test.ts
index 6647eea0fe..6dac4ec52a 100644
--- a/packages/storage/src/__tests__/artifact-writer-lock.test.ts
+++ b/packages/storage/src/__tests__/artifact-writer-lock.test.ts
@@ -41,9 +41,9 @@ import {
   type InteractiveArtifactStoreWriter,
 } from '../artifact-stores.js';
 import {
-  type ArtifactStore,
+  type ArtifactAuthorityStore,
   type CreateArtifactInput,
-  createSqliteArtifactStore,
+  createSqliteArtifactStoreWriteAuthority,
 } from '../artifact-store.js';
 import { withArtifactWriterLock } from '../artifact-writer-lock.js';
 import {
@@ -72,12 +72,20 @@ const COMPETING_PAYLOAD_BYTES = 4 * 1024 * 1024;
 const SKIP_OPEN_SQLITE_ROOT_REPLACEMENT = process.platform === 'win32';
 const artifactStoreClosersByRoot = new Map<string, Set<() => void>>();
 
-function createArtifactStore(root: string): ArtifactStore {
-  const store = createSqliteArtifactStore(root);
+function createArtifactStore(root: string): ArtifactAuthorityStore {
+  const authority = createSqliteArtifactStoreWriteAuthority(root);
   const closers = artifactStoreClosersByRoot.get(root) ?? new Set<() => void>();
-  closers.add(() => store.close?.());
+  closers.add(() => authority.close());
   artifactStoreClosersByRoot.set(root, closers);
-  return store;
+  return authority.store;
+}
+
+async function listArtifacts(store: ArtifactAuthorityStore, sessionId: string) {
+  return (await store.listPage(sessionId, { offset: 0, limit: Number.MAX_SAFE_INTEGER })).records;
+}
+
+function readArtifactText(store: ArtifactAuthorityStore, artifactId: string) {
+  return store.readTextInSession('session-1', artifactId);
 }
 
 async function openInteractiveArtifactStoreForWrite(
@@ -99,7 +107,7 @@ function closeArtifactStoresUnder(root: string): void {
   }
 }
 
-test('public Store mutation waits for a child-held writer lock and preserves metadata', {
+test('unleased write authority waits for a child-held writer lock and preserves metadata', {
   timeout: TEST_TIMEOUT_MS,
 }, async () => {
   await withTemporaryDirectory(async (root) => {
@@ -114,7 +122,9 @@ test('public Store mutation waits for a child-held writer lock and preserves met
       await withTimeout(mutation, OPERATION_TIMEOUT_MS, 'Store mutation');
 
       assert.deepEqual(
-        (await createArtifactStore(stateRoot).list('session-1')).map((record) => record.id).sort(),
+        (await listArtifacts(createArtifactStore(stateRoot), 'session-1'))
+          .map((record) => record.id)
+          .sort(),
         ['after-lock', 'seed'],
       );
     } finally {
@@ -123,7 +133,7 @@ test('public Store mutation waits for a child-held writer lock and preserves met
   });
 });
 
-test('public Store mutations in separate processes reload and publish under one OS lock', {
+test('unleased write authorities in separate processes reload and publish under one OS lock', {
   timeout: TEST_TIMEOUT_MS,
 }, async () => {
   await withTemporaryDirectory(async (root) => {
@@ -131,7 +141,7 @@ test('public Store mutations in separate processes reload and publish under one
     const seedRecord = await createArtifactStore(stateRoot).create(artifactInput('seed'));
 
     const parentStore = createArtifactStore(stateRoot);
-    await parentStore.list('session-1');
+    await listArtifacts(parentStore, 'session-1');
     const child = await spawnPublicWriter(stateRoot, 'session-1');
     const holder = await spawnLockHolder(stateRoot);
     try {
@@ -148,21 +158,24 @@ test('public Store mutations in separate processes reload and publish under one
       const [parentRecord, childCreated] = await withTimeout(
         Promise.all([parentMutation, childMutation.created]),
         OPERATION_TIMEOUT_MS,
-        'competing public Store mutations',
+        'competing unleased write authority mutations',
       );
       await withTimeout(waitForExit(child), OPERATION_TIMEOUT_MS, 'public writer shutdown');
 
       const freshStore = createArtifactStore(stateRoot);
-      const records = (await freshStore.list('session-1')).sort(compareRecordsById);
+      const records = [...(await listArtifacts(freshStore, 'session-1'))].sort(compareRecordsById);
       assert.deepEqual(
         records,
         [seedRecord, parentRecord, childCreated.record].sort(compareRecordsById),
       );
-      assert.deepEqual(await freshStore.readText('parent-public'), {
+      assert.deepEqual(await readArtifactText(freshStore, 'parent-public'), {
         ok: true,
         text: parentPayload,
       });
-      assert.deepEqual(await freshStore.readText('child-public'), { ok: true, text: childPayload });
+      assert.deepEqual(await readArtifactText(freshStore, 'child-public'), {
+        ok: true,
+        text: childPayload,
+      });
     } finally {
       await stopHolder(child);
       await stopHolder(holder);
@@ -170,7 +183,7 @@ test('public Store mutations in separate processes reload and publish under one
   });
 });
 
-test('public Store mutations share the rootId writer lock used by lease-bound authority', {
+test('unleased writes share the rootId writer lock used by lease-bound authority', {
   timeout: TEST_TIMEOUT_MS,
 }, async () => {
   await withTemporaryDirectory(async (root) => {
@@ -182,7 +195,7 @@ test('public Store mutations share the rootId writer lock used by lease-bound au
     const holder = await spawnAuthorityLockHolder(stateRoot, capability.rootId);
     try {
       const mutation = createArtifactStore(stateRoot).create(artifactInput('public-marked-root'));
-      await assertPending(mutation, 'public Store mutation on a marked root');
+      await assertPending(mutation, 'unleased write authority mutation on a marked root');
 
       await releaseHolder(holder);
       assert.equal(
@@ -190,7 +203,7 @@ test('public Store mutations share the rootId writer lock used by lease-bound au
           await withTimeout(
             mutation,
             OPERATION_TIMEOUT_MS,
-            'public Store mutation on a marked root',
+            'unleased write authority mutation on a marked root',
           )
         ).id,
         'public-marked-root',
@@ -212,13 +225,13 @@ test('mutations spanning initial root marking remain serialized by the bootstrap
       const firstMutation = createArtifactStore(stateRoot).create(
         artifactInput('before-root-marking'),
       );
-      await assertPending(firstMutation, 'public mutation started before root marking');
+      await assertPending(firstMutation, 'unleased mutation started before root marking');
 
       trackControlDirectory(await resolveStorageRoot({ path: stateRoot, kind: 'interactive' }));
       const secondMutation = createArtifactStore(stateRoot).create(
         artifactInput('after-root-marking', undefined, 2),
       );
-      await assertPending(secondMutation, 'public mutation started after root marking');
+      await assertPending(secondMutation, 'unleased mutation started after root marking');
 
       await releaseHolder(holder);
       await withTimeout(
@@ -228,15 +241,15 @@ test('mutations spanning initial root marking remain serialized by the bootstrap
       );
 
       const freshStore = createArtifactStore(stateRoot);
-      assert.deepEqual((await freshStore.list('session-1')).map((record) => record.id).sort(), [
-        'after-root-marking',
-        'before-root-marking',
-      ]);
-      assert.deepEqual(await freshStore.readText('before-root-marking'), {
+      assert.deepEqual(
+        (await listArtifacts(freshStore, 'session-1')).map((record) => record.id).sort(),
+        ['after-root-marking', 'before-root-marking'],
+      );
+      assert.deepEqual(await readArtifactText(freshStore, 'before-root-marking'), {
         ok: true,
         text: 'before-root-marking',
       });
-      assert.deepEqual(await freshStore.readText('after-root-marking'), {
+      assert.deepEqual(await readArtifactText(freshStore, 'after-root-marking'), {
         ok: true,
         text: 'after-root-marking',
       });
@@ -246,7 +259,7 @@ test('mutations spanning initial root marking remain serialized by the bootstrap
   });
 });
 
-test('public mutation rejects an unmarked replacement installed while waiting for bootstrap', {
+test('unleased mutation rejects an unmarked replacement installed while waiting for bootstrap', {
   timeout: TEST_TIMEOUT_MS,
   skip: SKIP_OPEN_SQLITE_ROOT_REPLACEMENT,
 }, async () => {
@@ -259,7 +272,10 @@ test('public mutation rejects an unmarked replacement installed while waiting fo
       const mutation = createArtifactStore(stateRoot).create(
         artifactInput('stale-public-replacement'),
       );
-      await assertPending(mutation, 'public mutation waiting for its captured bootstrap authority');
+      await assertPending(
+        mutation,
+        'unleased mutation waiting for its captured bootstrap authority',
+      );
 
       await rename(stateRoot, displacedRoot);
       await mkdir(stateRoot);
@@ -276,7 +292,7 @@ test('public mutation rejects an unmarked replacement installed while waiting fo
   });
 });
 
-test('public mutation through a retargeted alias stays bound to its verified canonical root', {
+test('unleased mutation through a retargeted alias stays bound to its verified canonical root', {
   timeout: TEST_TIMEOUT_MS,
 }, async () => {
   await withTemporaryDirectory(async (root) => {
@@ -292,14 +308,14 @@ test('public mutation through a retargeted alias stays bound to its verified can
     await symlink(stateRoot, alias, process.platform === 'win32' ? 'junction' : 'dir');
     const store = createArtifactStore(alias);
     assert.deepEqual(
-      (await store.list('session-1')).map((record) => record.id),
+      (await listArtifacts(store, 'session-1')).map((record) => record.id),
       ['seed'],
     );
 
     const holder = await spawnAuthorityLockHolder(stateRoot, capability.rootId);
     try {
       const mutation = store.create(artifactInput('alias-mutation', undefined, 2));
-      await assertPending(mutation, 'public mutation through the original alias target');
+      await assertPending(mutation, 'unleased mutation through the original alias target');
 
       await rm(alias, { force: true });
       await symlink(replacementRoot, alias, process.platform === 'win32' ? 'junction' : 'dir');
@@ -307,7 +323,9 @@ test('public mutation through a retargeted alias stays bound to its verified can
       assert.equal((await mutation).id, 'alias-mutation');
 
       assert.deepEqual(
-        (await createArtifactStore(stateRoot).list('session-1')).map((record) => record.id).sort(),
+        (await listArtifacts(createArtifactStore(stateRoot), 'session-1'))
+          .map((record) => record.id)
+          .sort(),
         ['alias-mutation', 'seed'],
       );
       assert.deepEqual(await readdir(replacementRoot), ['replacement-sentinel']);
@@ -426,17 +444,19 @@ test('bundle export excludes a mutation queued behind the same child-held writer
       await withTimeout(bundleExport, BUNDLE_EXPORT_TIMEOUT_MS, 'bundle export');
       await withTimeout(mutation, OPERATION_TIMEOUT_MS, 'Store mutation');
 
-      const exportedStore = createSqliteArtifactStore(destinationRoot);
+      const exportedStore = createArtifactStore(destinationRoot);
       try {
         assert.deepEqual(
-          (await exportedStore.list(session.id)).map((record) => record.id),
+          (await listArtifacts(exportedStore, session.id)).map((record) => record.id),
           ['seed'],
         );
       } finally {
         exportedStore.close?.();
       }
       assert.deepEqual(
-        (await createArtifactStore(stateRoot).list(session.id)).map((record) => record.id).sort(),
+        (await listArtifacts(createArtifactStore(stateRoot), session.id))
+          .map((record) => record.id)
+          .sort(),
         ['after-export', 'seed'],
       );
     } finally {
@@ -514,6 +534,7 @@ function artifactInput(id: string, content = id, now = 1): CreateArtifactInput {
     name: `${id}.txt`,
     kind: 'file' as const,
     content,
+    source: 'tool_result',
     now,
   };
 }
diff --git a/packages/storage/src/__tests__/fixtures/artifact-writer-lock-holder.ts b/packages/storage/src/__tests__/fixtures/artifact-writer-lock-holder.ts
index 7eb6337093..2e786bb301 100644
--- a/packages/storage/src/__tests__/fixtures/artifact-writer-lock-holder.ts
+++ b/packages/storage/src/__tests__/fixtures/artifact-writer-lock-holder.ts
@@ -21,7 +21,8 @@ import { createHash } from 'node:crypto';
 import { mkdir, rm, writeFile } from 'node:fs/promises';
 import { join } from 'node:path';
 import {
-  createSqliteArtifactStore as createArtifactStore,
+  type ArtifactAuthorityStore,
+  createSqliteArtifactStoreWriteAuthority,
   type CreateArtifactInput,
 } from '../../artifact-store.js';
 import {
@@ -41,6 +42,10 @@ if (!workspaceRoot || !process.send) {
   );
 }
 
+async function listArtifacts(store: ArtifactAuthorityStore, sessionId: string) {
+  return (await store.listPage(sessionId, { offset: 0, limit: Number.MAX_SAFE_INTEGER })).records;
+}
+
 try {
   if (modeOrTransientResidueSessionId === '--public-writer') {
     if (!modeArgument) throw new Error('public writer session id is required');
@@ -100,9 +105,10 @@ async function runAuthorityLockHolder(workspaceRoot: string, rootId: string): Pr
 }
 
 async function runPublicWriter(workspaceRoot: string, sessionId: string): Promise<void> {
-  const store = createArtifactStore(workspaceRoot);
+  const authority = createSqliteArtifactStoreWriteAuthority(workspaceRoot);
+  const store = authority.store;
   try {
-    await store.list(sessionId);
+    await listArtifacts(store, sessionId);
     await send({ type: 'ready' });
     const command = await waitForCreateCommand();
     const mutation = store.create({
@@ -113,7 +119,7 @@ async function runPublicWriter(workspaceRoot: string, sessionId: string): Promis
     await send({ type: 'queued' });
     await send({ type: 'created', record: await mutation });
   } finally {
-    store.close?.();
+    authority.close();
   }
 }
 
diff --git a/packages/storage/src/__tests__/operational-state-backup.test.ts b/packages/storage/src/__tests__/operational-state-backup.test.ts
index 9965b9e7c3..228f3abf86 100644
--- a/packages/storage/src/__tests__/operational-state-backup.test.ts
+++ b/packages/storage/src/__tests__/operational-state-backup.test.ts
@@ -24,7 +24,7 @@ import { tmpdir } from 'node:os';
 import { join } from 'node:path';
 import { DatabaseSync } from 'node:sqlite';
 import { test } from 'node:test';
-import { createSqliteArtifactStore } from '../artifact-store.js';
+import { createSqliteArtifactStoreWriteAuthority } from '../artifact-store.js';
 import { createProjectCatalog } from '../project-catalog.js';
 import { createSessionStore } from '../session-store.js';
 import {
@@ -70,7 +70,8 @@ test('backs up and restores runtime.sqlite plus artifact bytes', async () => {
     } as const;
     await sessions.appendMessage(session.id, message);
     await sessions.close?.();
-    const artifacts = createSqliteArtifactStore(stateRoot);
+    const artifactAuthority = createSqliteArtifactStoreWriteAuthority(stateRoot);
+    const artifacts = artifactAuthority.store;
     const artifact = await artifacts.create({
       id: 'artifact-1',
       sessionId: session.id,
@@ -78,10 +79,10 @@ test('backs up and restores runtime.sqlite plus artifact bytes', async () => {
       name: 'note.txt',
       kind: 'file',
       content: 'artifact',
-      source: 'fixture',
+      source: 'tool_result',
       now: 2,
     });
-    artifacts.close?.();
+    artifactAuthority.close();
 
     await createOperationalStateBackup({ stateRoot, destinationRoot: backupRoot, now: () => 10 });
     assert.equal((await validateOperationalStateBackup(backupRoot)).createdAt, 10);
@@ -123,7 +124,8 @@ test('rejects a backup whose SQLite Artifact metadata has no matching payload',
   const base = await mkdtemp(join(tmpdir(), 'maka-operational-backup-artifact-'));
   const stateRoot = join(base, 'state');
   try {
-    const artifacts = createSqliteArtifactStore(stateRoot);
+    const artifactAuthority = createSqliteArtifactStoreWriteAuthority(stateRoot);
+    const artifacts = artifactAuthority.store;
     const artifact = await artifacts.create({
       id: 'artifact-1',
       sessionId: 'session-1',
@@ -131,10 +133,10 @@ test('rejects a backup whose SQLite Artifact metadata has no matching payload',
       name: 'note.txt',
       kind: 'file',
       content: 'artifact',
-      source: 'fixture',
+      source: 'tool_result',
       now: 2,
     });
-    artifacts.close?.();
+    artifactAuthority.close();
     await rm(join(stateRoot, 'artifacts', artifact.relativePath));
 
     await assert.rejects(
diff --git a/packages/storage/src/__tests__/operational-state-store.test.ts b/packages/storage/src/__tests__/operational-state-store.test.ts
index 11b8bb9ea6..f6274475f6 100644
--- a/packages/storage/src/__tests__/operational-state-store.test.ts
+++ b/packages/storage/src/__tests__/operational-state-store.test.ts
@@ -28,6 +28,7 @@ import {
   acquireOperationalStateDatabase,
   OperationalStateMigrationBlockedError,
 } from '../operational-state-store.js';
+import { SQLITE_ARTIFACT_SCHEMA_VERSION } from '../sqlite-artifact-schema.js';
 import { SQLITE_RUNTIME_SCHEMA_VERSION } from '../sqlite-runtime-schema.js';
 import { SQLITE_SESSION_METADATA_SCHEMA_VERSION } from '../sqlite-session-metadata-schema.js';
 import { SQLITE_USAGE_SCHEMA_VERSION } from '../sqlite-usage-schema.js';
@@ -165,6 +166,76 @@ test('a non-owner rejects an older schema without migrating it behind the Runtim
   }
 });
 
+test('preserves live supported v1 Artifacts when opening existing Sessions', async () => {
+  const root = await mkdtemp(join(tmpdir(), 'maka-operational-artifact-v1-retirement-'));
+  const databasePath = join(root, 'runtime.sqlite');
+  try {
+    const lease = acquireOperationalStateDatabase(root);
+    const metadata = createSqliteSessionMetadataStore(databasePath, { databaseLease: lease });
+    await metadata.create(sessionHeader());
+    metadata.close();
+    lease.close();
+
+    const legacy = new DatabaseSync(databasePath);
+    legacy.exec(`
+      DROP TABLE artifact_records;
+      CREATE TABLE artifact_records (
+        storage_key TEXT PRIMARY KEY,
+        artifact_id TEXT NOT NULL,
+        session_id TEXT NOT NULL,
+        created_at INTEGER NOT NULL CHECK (created_at >= 0),
+        status TEXT NOT NULL CHECK (status IN ('live', 'deleted')),
+        relative_path TEXT NOT NULL,
+        record_json TEXT NOT NULL
+      );
+      CREATE INDEX artifact_records_session_order
+        ON artifact_records(session_id, created_at, storage_key);
+      CREATE UNIQUE INDEX artifact_records_relative_path
+        ON artifact_records(relative_path);
+      INSERT INTO artifact_records VALUES (
+        'legacy-key',
+        'legacy-artifact',
+        'session-1',
+        1,
+        'live',
+        'session-1/legacy-artifact-result.txt',
+        '{"id":"legacy-artifact","sessionId":"session-1","turnId":"turn-1","createdAt":1,"name":"result.txt","kind":"file","sizeBytes":4,"relativePath":"session-1/legacy-artifact-result.txt","source":"tool_result_archive","status":"live"}'
+      );
+      UPDATE operational_schema_migrations SET version = 1 WHERE scope = 'artifact';
+    `);
+    legacy.close();
+
+    const reopened = acquireOperationalStateDatabase(root);
+    assert.equal(
+      (
+        reopened.database
+          .prepare("SELECT COUNT(*) AS count FROM session_metadata WHERE session_id = 'session-1'")
+          .get() as { count: number }
+      ).count,
+      1,
+    );
+    assert.equal(
+      (
+        reopened.database.prepare('SELECT COUNT(*) AS count FROM artifact_records').get() as {
+          count: number;
+        }
+      ).count,
+      1,
+    );
+    assert.equal(
+      (
+        reopened.database
+          .prepare("SELECT version FROM operational_schema_migrations WHERE scope = 'artifact'")
+          .get() as { version: number }
+      ).version,
+      SQLITE_ARTIFACT_SCHEMA_VERSION,
+    );
+    reopened.close();
+  } finally {
+    await rm(root, { recursive: true, force: true });
+  }
+});
+
 test('retires completed released migration metadata during schema convergence', async () => {
   const root = await mkdtemp(join(tmpdir(), 'maka-operational-cutover-retirement-'));
   const databasePath = join(root, 'runtime.sqlite');
@@ -977,7 +1048,7 @@ for (const { name, mutation } of [
         DROP INDEX artifact_records_relative_path;
         CREATE UNIQUE INDEX artifact_records_relative_path
           ON artifact_records(relative_path)
-          WHERE status = 'live';
+          WHERE relative_path <> '';
       `),
   },
   {
@@ -986,12 +1057,10 @@ for (const { name, mutation } of [
       database.exec(`
         DROP TABLE artifact_records;
         CREATE TABLE artifact_records (
-          storage_key TEXT PRIMARY KEY,
-          artifact_id TEXT NOT NULL,
+          artifact_id TEXT PRIMARY KEY,
           session_id TEXT NOT NULL,
           created_at INTEGER NOT NULL CHECK (created_at >= 0),
-          status TEXT NOT NULL CHECK (status IN ('LIVE', 'DELETED')),
-          relative_path TEXT NOT NULL,
+          relative_path TEXT NOT NULL CHECK (relative_path <> ''),
           record_json TEXT NOT NULL
         );
       `),
diff --git a/packages/storage/src/__tests__/sqlite-artifact-metadata.test.ts b/packages/storage/src/__tests__/sqlite-artifact-metadata.test.ts
index a9214898ab..e8ddc5b96f 100644
--- a/packages/storage/src/__tests__/sqlite-artifact-metadata.test.ts
+++ b/packages/storage/src/__tests__/sqlite-artifact-metadata.test.ts
@@ -49,7 +49,7 @@ test('Artifact metadata changes only write changed rows', async () => {
     `);
 
     repository.applyChanges({
-      upserts: [unchanged, { ...updated, status: 'deleted' }, artifactRecord('added')],
+      upserts: [unchanged, { ...updated, summary: 'changed' }, artifactRecord('added')],
       deleteIds: [removed.id],
     });
 
@@ -67,12 +67,12 @@ test('Artifact metadata changes only write changed rows', async () => {
     assert.deepEqual(
       repository
         .readAll()
-        .map(({ id, status }) => ({ id, status }))
+        .map(({ id, summary }) => ({ id, summary }))
         .sort((left, right) => left.id.localeCompare(right.id)),
       [
-        { id: 'added', status: 'live' },
-        { id: 'unchanged', status: 'live' },
-        { id: 'updated', status: 'deleted' },
+        { id: 'added', summary: undefined },
+        { id: 'unchanged', summary: undefined },
+        { id: 'updated', summary: 'changed' },
       ],
     );
 
@@ -89,6 +89,44 @@ test('Artifact metadata changes only write changed rows', async () => {
   }
 });
 
+test('Artifact metadata recovery ignores records from unsupported sources', async () => {
+  const root = await mkdtemp(join(tmpdir(), 'maka-artifact-metadata-unsupported-'));
+  const repository = createSqliteArtifactMetadataRepository(root);
+  let inspector: DatabaseSync | undefined;
+  try {
+    const supported = artifactRecord('supported');
+    repository.applyChanges({ upserts: [supported] });
+
+    const unsupported = {
+      ...artifactRecord('unsupported'),
+      source: 'retired_artifact_source',
+    };
+    inspector = new DatabaseSync(join(root, OPERATIONAL_STATE_DATABASE_NAME));
+    const insert = inspector.prepare(`
+        INSERT INTO artifact_records(
+          artifact_id,
+          session_id,
+          created_at,
+          relative_path,
+          record_json
+        ) VALUES (?, ?, ?, ?, ?)
+      `);
+    insert.run(
+      unsupported.id,
+      unsupported.sessionId,
+      unsupported.createdAt,
+      unsupported.relativePath,
+      JSON.stringify(unsupported),
+    );
+
+    assert.deepEqual(repository.readAll(), [supported]);
+  } finally {
+    inspector?.close();
+    repository.close();
+    await rm(root, { recursive: true, force: true });
+  }
+});
+
 function artifactRecord(id: string): ArtifactRecord {
   return {
     id,
@@ -99,7 +137,6 @@ function artifactRecord(id: string): ArtifactRecord {
     kind: 'file',
     sizeBytes: id.length,
     relativePath: `session-1/${id}-${id}.txt`,
-    source: 'fixture',
-    status: 'live',
+    source: 'tool_result',
   };
 }
diff --git a/packages/storage/src/artifact-attachments.ts b/packages/storage/src/artifact-attachments.ts
index 07d06c66c2..90ace71c6e 100644
--- a/packages/storage/src/artifact-attachments.ts
+++ b/packages/storage/src/artifact-attachments.ts
@@ -31,11 +31,7 @@ import {
 import { createHash } from 'node:crypto';
 import { type StorageRef, type ToolResultContent } from '@maka/core/events';
 import type { ReadImageSnapshotReader } from '@maka/core/context-offload';
-import type {
-  ArtifactAuthorityStore,
-  ArtifactStore,
-  DurableArtifactAttachmentReader,
-} from './artifact-store.js';
+import type { ArtifactAuthorityStore, DurableArtifactAttachmentReader } from './artifact-store.js';
 import { sanitizeArtifactName } from './artifact-store.js';
 
 export interface ArtifactAttachmentResourceReader {
@@ -48,9 +44,7 @@ export interface ArtifactAttachmentResourceReader {
 
 /** Read a user-uploaded Artifact without exposing its storage path. */
 export function createArtifactAttachmentResourceReader(input: {
-  artifactStore:
-    | Pick<ArtifactAuthorityStore, 'getInSession' | 'readTextInSession'>
-    | Pick<ArtifactStore, 'get' | 'readText'>;
+  artifactStore: Pick<ArtifactAuthorityStore, 'getInSession' | 'readTextInSession'>;
 }): ArtifactAttachmentResourceReader {
   return Object.freeze({
     async readAttachmentResource(
@@ -59,11 +53,8 @@ export function createArtifactAttachmentResourceReader(input: {
       abortSignal: AbortSignal,
     ): Promise<ToolResultContent> {
       abortSignal.throwIfAborted();
-      const record =
-        'getInSession' in input.artifactStore
-          ? (await input.artifactStore.getInSession(sessionId, artifactId)).record
-          : await input.artifactStore.get(artifactId);
-      if (!record || record.status !== 'live' || record.source !== 'user_upload') {
+      const record = (await input.artifactStore.getInSession(sessionId, artifactId)).record;
+      if (!record || record.source !== 'user_upload') {
         throw new Error('Attachment was not found in this Session');
       }
       if (record.sessionId !== sessionId) {
@@ -80,10 +71,7 @@ export function createArtifactAttachmentResourceReader(input: {
       if (record.kind === 'pdf') {
         throw new Error('PDF attachments cannot be decoded by Read');
       }
-      const read =
-        'readTextInSession' in input.artifactStore
-          ? await input.artifactStore.readTextInSession(sessionId, artifactId)
-          : await input.artifactStore.readText(artifactId);
+      const read = await input.artifactStore.readTextInSession(sessionId, artifactId);
       abortSignal.throwIfAborted();
       if (!read.ok) throw new Error(`Attachment could not be read: ${read.reason}`);
       return { kind: 'text', text: read.text };
@@ -137,36 +125,11 @@ interface ReadImageSnapshotInput {
 export interface ReadImageSnapshotPlan {
   ref: Extract<StorageRef, { kind: 'session_file' }>;
   persist(): Promise<void>;
-  /**
-   * Undo a publication whose projection was never admitted (#4283).
-   *
-   * A Tool Result projection carrying several images publishes them one at a
-   * time; if a later one fails, the projection is rejected and the earlier
-   * publications become artifacts no durable record will ever name. Retracting
-   * them is best-effort by design: failing to retract only delays reclamation,
-   * while failing to reject would put an unreferenced artifact in front of the
-   * user as if the tool had produced it.
-   */
-  retract(): Promise<void>;
 }
 
-export interface ReadImageSnapshotArtifactStore extends Pick<ArtifactStore, 'create'> {
-  /** Create with a receipt saying whether this call published the artifact. */
-  createOwned?: (input: Parameters<ArtifactStore['create']>[0]) => Promise<{
-    record: Awaited<ReturnType<ArtifactStore['create']>>;
-    publishedByThisCall: boolean;
-  }>;
-}
+export type ReadImageSnapshotArtifactStore = Pick<ArtifactAuthorityStore, 'create'>;
 
-export function createReadImageSnapshotPlanner(
-  artifactStore: ReadImageSnapshotArtifactStore,
-  /**
-   * Narrow reclaim for a `tool_result_projection` artifact this planner
-   * published. Optional so callers that cannot reclaim still get the planner;
-   * without it `retract()` is a no-op and reclamation waits for reachability.
-   */
-  retractPublished?: (sessionId: string, artifactId: string) => Promise<void>,
-) {
+export function createReadImageSnapshotPlanner(artifactStore: ReadImageSnapshotArtifactStore) {
   return (input: ReadImageSnapshotInput): ReadImageSnapshotPlan => {
     if (input.bytes.byteLength > MAX_READ_IMAGE_BYTES) {
       throw new Error(READ_IMAGE_TOO_LARGE_MESSAGE);
@@ -199,11 +162,8 @@ export function createReadImageSnapshotPlanner(
       .update(accepted.bytes)
       .digest('hex')}`;
     let publication: Promise<void> | undefined;
-    // Ownership, not success. The id is derived from the bytes, so a create for
-    // an image an earlier projection already published succeeds by replaying
-    // that record — and reclaiming it would delete content that is still in
-    // use. Only a create that actually published may be retracted.
-    let owned = false;
+    // Content-derived identities are shared within the Turn. Their bytes live
+    // until Session cleanup, regardless of which individual projection succeeds.
     const ref = Object.freeze({
       kind: 'session_file' as const,
       sessionId: accepted.sessionId,
@@ -222,30 +182,16 @@ export function createReadImageSnapshotPlanner(
           mimeType: accepted.mimeType,
           source: 'tool_result_projection' as const,
         };
-        publication ??= (
-          artifactStore.createOwned
-            ? artifactStore.createOwned(input)
-            : artifactStore.create(input).then((record) => ({
-                record,
-                publishedByThisCall: false,
-              }))
-        ).then(({ record, publishedByThisCall }) => {
+        publication ??= artifactStore.create(input).then((record) => {
           if (record.id !== id) throw new Error('Artifact publication changed its planned id');
-          owned = publishedByThisCall;
         });
         return publication;
       },
-      async retract() {
-        if (!owned || !retractPublished) return;
-        owned = false;
-        publication = undefined;
-        await retractPublished(accepted.sessionId, id).catch(() => undefined);
-      },
     });
   };
 }
 
-export function createReadImageSnapshotter(artifactStore: Pick<ArtifactStore, 'create'>) {
+export function createReadImageSnapshotter(artifactStore: Pick<ArtifactAuthorityStore, 'create'>) {
   const planSnapshot = createReadImageSnapshotPlanner(artifactStore);
   return async (
     input: ReadImageSnapshotInput,
diff --git a/packages/storage/src/artifact-metadata-codec.ts b/packages/storage/src/artifact-metadata-codec.ts
index e5f9d05b2d..217833f97b 100644
--- a/packages/storage/src/artifact-metadata-codec.ts
+++ b/packages/storage/src/artifact-metadata-codec.ts
@@ -17,11 +17,10 @@
  * under the License.
  */
 
-import { isAbsolute, basename } from 'node:path';
+import { isAbsolute } from 'node:path';
 import {
   ARTIFACT_KINDS,
   ARTIFACT_SOURCES,
-  ARTIFACT_STATUSES,
   type ArtifactKind,
   type ArtifactRecord,
   type ArtifactSource,
@@ -29,11 +28,9 @@ import {
   isCanonicalArtifactEntityId,
 } from '@maka/core/artifacts';
 import { isDeepResearchArtifactRole } from '@maka/core/deep-research-run';
-import { ARTIFACT_PUBLICATION_STAGING_PATTERN } from './artifact-storage-layout.js';
 
 const ARTIFACT_KIND_SET = new Set<ArtifactKind>(ARTIFACT_KINDS);
 const ARTIFACT_SOURCE_SET = new Set<ArtifactSource>(ARTIFACT_SOURCES);
-const ARTIFACT_STATUS_SET = new Set<string>(ARTIFACT_STATUSES);
 const ARTIFACT_RECORD_KEYS = new Set([
   'id',
   'sessionId',
@@ -47,29 +44,38 @@ const ARTIFACT_RECORD_KEYS = new Set([
   'source',
   'summary',
   'deepResearchRole',
-  'status',
 ]);
 
 export function decodeArtifactRecordJsons(values: readonly unknown[]): ArtifactRecord[] {
   const records: ArtifactRecord[] = [];
   const ids = new Set<string>();
   for (const [index, value] of values.entries()) {
+    if (typeof value !== 'string') continue;
+    let parsed: unknown;
     try {
-      if (typeof value !== 'string') throw invalidMetadataRecord(index + 1);
-      const record = decodeArtifactRecord(JSON.parse(value), index + 1);
-      if (ids.has(record.id)) throw invalidMetadataRecord(index + 1);
+      parsed = JSON.parse(value);
+    } catch {
+      continue;
+    }
+    if (!hasSupportedArtifactSource(parsed)) continue;
+    try {
+      const record = decodeArtifactRecord(parsed, index + 1);
+      if (ids.has(record.id)) continue;
       ids.add(record.id);
       records.push(record);
-    } catch (error) {
-      if (error instanceof Error && error.message.startsWith('Invalid artifact metadata record')) {
-        throw error;
-      }
-      throw invalidMetadataRecord(index + 1, error);
-    }
+    } catch {}
   }
   return records;
 }
 
+function hasSupportedArtifactSource(value: unknown): boolean {
+  return (
+    isRecord(value) &&
+    typeof value.source === 'string' &&
+    ARTIFACT_SOURCE_SET.has(value.source as ArtifactSource)
+  );
+}
+
 export function isSafeRelativeArtifactPath(relativePath: string): boolean {
   if (!relativePath || isAbsolute(relativePath)) return false;
   if (relativePath.includes('\0')) return false;
@@ -85,12 +91,6 @@ export function validateRelativeArtifactPath(relativePath: string): void {
   }
 }
 
-export function validateCanonicalArtifactTargetName(targetName: string): void {
-  if (ARTIFACT_PUBLICATION_STAGING_PATTERN.test(targetName)) {
-    throw new Error('Artifact target name uses the reserved publication staging namespace');
-  }
-}
-
 function decodeArtifactRecord(value: unknown, index: number): ArtifactRecord {
   if (!isRecord(value)) throw invalidMetadataRecord(index);
   if (Object.keys(value).some((key) => !ARTIFACT_RECORD_KEYS.has(key))) {
@@ -110,19 +110,14 @@ function decodeArtifactRecord(value: unknown, index: number): ArtifactRecord {
     typeof value.sizeBytes !== 'number' ||
     !Number.isSafeInteger(value.sizeBytes) ||
     value.sizeBytes < 0 ||
-    typeof value.status !== 'string' ||
-    !ARTIFACT_STATUS_SET.has(value.status) ||
     !isOptionalNonEmptyString(value.mimeType) ||
     !isOptionalNonEmptyString(value.summary) ||
     (value.deepResearchRole !== undefined && !isDeepResearchArtifactRole(value.deepResearchRole)) ||
-    (value.source !== undefined &&
-      (typeof value.source !== 'string' ||
-        !ARTIFACT_SOURCE_SET.has(value.source as ArtifactSource)))
+    typeof value.source !== 'string'
   ) {
     throw invalidMetadataRecord(index);
   }
   validateRelativeArtifactPath(value.relativePath);
-  validateCanonicalArtifactTargetName(basename(value.relativePath));
   if (!isCompatibleArtifactName(value.name)) throw invalidMetadataRecord(index);
   if (value.relativePath !== `${value.sessionId}/${value.id}-${value.name}`) {
     throw invalidMetadataRecord(index);
@@ -138,11 +133,8 @@ function isCompatibleArtifactName(name: string): boolean {
   return true;
 }
 
-function invalidMetadataRecord(index: number, cause?: unknown): Error {
-  return new Error(
-    `Invalid artifact metadata record ${index}`,
-    cause === undefined ? {} : { cause },
-  );
+function invalidMetadataRecord(index: number): Error {
+  return new Error(`Invalid artifact metadata record ${index}`);
 }
 
 function isRecord(value: unknown): value is Record<string, unknown> {
diff --git a/packages/storage/src/artifact-metadata-repository.ts b/packages/storage/src/artifact-metadata-repository.ts
deleted file mode 100644
index 0ee907d385..0000000000
--- a/packages/storage/src/artifact-metadata-repository.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-import type { ArtifactRecord } from '@maka/core/artifacts';
-
-export interface ArtifactMetadataChanges {
-  readonly upserts?: readonly ArtifactRecord[];
-  readonly deleteIds?: readonly string[];
-}
-
-export interface ArtifactMetadataRepository {
-  ready(): Promise<void>;
-  readAll(): ArtifactRecord[];
-  applyChanges(changes: ArtifactMetadataChanges): void;
-  close(): void;
-}
diff --git a/packages/storage/src/artifact-storage-layout.ts b/packages/storage/src/artifact-storage-layout.ts
deleted file mode 100644
index 63b50ee369..0000000000
--- a/packages/storage/src/artifact-storage-layout.ts
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-const UUID_V4_FRAGMENT = '[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}';
-
-export const ARTIFACT_PURGE_INTENT_FILE = '.artifact-purge-intent.json';
-export const ARTIFACT_WRITER_LOCK_FILE = '.maka-artifact-writer.lock';
-
-export const ARTIFACT_PURGE_INTENT_TEMP_PATTERN = new RegExp(
-  `^\\.artifact-purge-intent\\.json\\.[0-9]+\\.${UUID_V4_FRAGMENT}\\.tmp$`,
-);
-
-export const ARTIFACT_PUBLICATION_STAGING_PATTERN = new RegExp(
-  `^\\.artifact-publish\\.([a-f0-9]{64})\\.${UUID_V4_FRAGMENT}\\.tmp$`,
-);
-
-export function isArtifactPurgeRecoveryTempName(name: string): boolean {
-  return ARTIFACT_PURGE_INTENT_TEMP_PATTERN.test(name);
-}
diff --git a/packages/storage/src/artifact-store.ts b/packages/storage/src/artifact-store.ts
index 174d12a49e..c2c5519f04 100644
--- a/packages/storage/src/artifact-store.ts
+++ b/packages/storage/src/artifact-store.ts
@@ -18,16 +18,14 @@
  */
 
 import { createHash, randomUUID } from 'node:crypto';
-import { type BigIntStats, constants as fsConstants, createReadStream, type Dirent } from 'node:fs';
+import { type BigIntStats, constants as fsConstants } from 'node:fs';
 import {
   access,
   copyFile,
-  link,
   lstat,
   mkdir,
   open,
   readFile,
-  readdir,
   realpath,
   rm,
   stat,
@@ -53,15 +51,8 @@ import {
   type DeepResearchArtifactRole,
 } from '@maka/core/deep-research-run';
 import { sniffAttachmentMimeType } from '@maka/core/attachments';
-import { publishMarkerFile, readBoundedMarkerFile } from './marker-file.js';
-import {
-  ARTIFACT_PUBLICATION_STAGING_PATTERN,
-  ARTIFACT_PURGE_INTENT_FILE,
-  isArtifactPurgeRecoveryTempName,
-} from './artifact-storage-layout.js';
 import {
   isSafeRelativeArtifactPath,
-  validateCanonicalArtifactTargetName,
   validateRelativeArtifactPath,
 } from './artifact-metadata-codec.js';
 import {
@@ -70,40 +61,17 @@ import {
 } from './artifact-writer-lock.js';
 import type { ArtifactWriterLockAuthority } from './root-authority.js';
 import { syncDirectory, syncDirectoryChain, syncFile } from './stable-storage.js';
-import type {
-  ArtifactMetadataChanges,
-  ArtifactMetadataRepository,
-} from './artifact-metadata-repository.js';
-import { createSqliteArtifactMetadataRepository } from './sqlite-artifact-metadata.js';
+import {
+  createSqliteArtifactMetadataRepository,
+  type ArtifactMetadataChanges,
+} from './sqlite-artifact-metadata.js';
 
 export { isSafeRelativeArtifactPath } from './artifact-metadata-codec.js';
 
 export const ARTIFACT_TEXT_PREVIEW_LIMIT_BYTES = 10 * 1024 * 1024;
 export const ARTIFACT_BINARY_PREVIEW_LIMIT_BYTES = 50 * 1024 * 1024;
 
-const PURGE_INTENT_SCHEMA_VERSION = 1 as const;
-
-const MAX_PURGE_INTENT_BYTES = 64 * 1024 * 1024;
 const ARTIFACT_PURGE_RESOLVE_CONCURRENCY = 8;
-/**
- * The source of the artifacts the retired capture sink wrote. The value stays
- * a valid source so the records still decode; nothing produces new ones.
- */
-const RETIRED_CAPTURE_ARTIFACT_SOURCE: ArtifactSource = 'provider_request_capture';
-
-/**
- * A record on its way off disk, which no copy may carry anywhere.
- *
- * Copying one would hand the target Session bytes already condemned, and would
- * put records back after the sweep finished and stopped looking. Leaving them
- * out also keeps the two from racing: the sweep can no longer delete a record a
- * copy is holding. That property belongs to the copy, not to one of its three
- * selection passes, so every pass asks the same question here.
- */
-function isRetiredCapture(record: ArtifactRecord): boolean {
-  return record.source === RETIRED_CAPTURE_ARTIFACT_SOURCE;
-}
-
 interface ArtifactSessionSnapshot {
   readonly records: readonly ArtifactRecord[];
   readonly revision: ArtifactListRevision;
@@ -111,7 +79,7 @@ interface ArtifactSessionSnapshot {
 
 type ArtifactReadFailure = {
   readonly ok: false;
-  readonly reason: 'not_found' | 'too_large' | 'read_failed' | 'not_allowed' | 'deleted';
+  readonly reason: 'not_found' | 'too_large' | 'read_failed' | 'not_allowed';
 };
 
 interface PreparedArtifactRead {
@@ -128,14 +96,6 @@ interface ArtifactRemovalEntry {
 
 type ArtifactRecordDraft = Omit<ArtifactRecord, 'sizeBytes'>;
 
-interface RecoverableOrphan {
-  readonly canonicalPath: string;
-  readonly dev: number;
-  readonly ino: number;
-  readonly size: number;
-  readonly digest: string;
-}
-
 export interface CreateArtifactInput {
   sessionId: string;
   turnId: string;
@@ -143,7 +103,7 @@ export interface CreateArtifactInput {
   kind: ArtifactKind;
   content: string | Uint8Array;
   mimeType?: string;
-  source?: ArtifactSource;
+  source: ArtifactSource;
   summary?: string;
   deepResearchRole?: DeepResearchArtifactRole;
   now?: number;
@@ -198,16 +158,6 @@ export interface ConversationArtifactCopyResult {
   readonly relativePaths: ReadonlyMap<string, string>;
 }
 
-export interface ArtifactStoreReader {
-  list(sessionId: string, opts?: { includeDeleted?: boolean }): Promise<ArtifactRecord[]>;
-  get(artifactId: string): Promise<ArtifactRecord | null>;
-  readText(
-    artifactId: string,
-    opts?: { maxBytes?: number; includeDeleted?: boolean },
-  ): Promise<ArtifactTextReadResult>;
-  readBinary(artifactId: string, opts?: { maxBytes?: number }): Promise<ArtifactBinaryReadResult>;
-}
-
 export type DurableArtifactBinaryReadResult =
   | ArtifactBinaryReadResult
   | { ok: false; reason: 'session_mismatch' };
@@ -220,25 +170,23 @@ export interface DurableArtifactAttachmentReader {
   }): Promise<DurableArtifactBinaryReadResult>;
 }
 
-export interface ArtifactStore extends ArtifactStoreReader, DurableArtifactAttachmentReader {
-  create(input: CreateArtifactInput): Promise<ArtifactRecord>;
-  delete(artifactId: string): Promise<void>;
-  purge(artifactIds: readonly string[]): Promise<void>;
-  close?(): void;
-}
-
 export type ArtifactUserDeleteResult =
-  | { readonly kind: 'deleted'; readonly record: ArtifactRecord }
-  | { readonly kind: 'not_found' }
-  | { readonly kind: 'protected' };
+  | { readonly kind: 'deleted' }
+  | { readonly kind: 'protected' }
+  | { readonly kind: 'not_found' };
 
-export interface ArtifactAuthorityStore extends ArtifactStore {
+export interface ArtifactAuthorityStore extends DurableArtifactAttachmentReader {
+  create(input: CreateArtifactInput): Promise<ArtifactRecord>;
+  close(): void;
   copyConversationArtifacts(
     input: ConversationArtifactCopyInput,
   ): Promise<ConversationArtifactCopyResult>;
   purgeSessionArtifacts(sessionId: string): Promise<void>;
-  /** Drops up to `limit` retired prepared-request captures; reports what remains. */
-  purgeRetiredCaptures(limit: number): Promise<{ purged: number; remaining: number }>;
+  deleteOwnedArtifactInSession(
+    sessionId: string,
+    artifactId: string,
+    source: ArtifactSource,
+  ): Promise<void>;
   deleteUserArtifactInSession(
     sessionId: string,
     artifactId: string,
@@ -268,20 +216,9 @@ export interface ArtifactAuthorityStore extends ArtifactStore {
 
 export interface ArtifactStoreWriteAuthority {
   readonly store: ArtifactAuthorityStore;
-  recover(): Promise<void>;
   close(): void;
 }
 
-export function createSqliteArtifactStore(workspaceRoot: string): ArtifactStore {
-  return new SqliteArtifactStore(
-    workspaceRoot,
-    'self_managed',
-    createSqliteArtifactMetadataRepository(workspaceRoot),
-    undefined,
-    undefined,
-  );
-}
-
 export function createSqliteArtifactStoreWriteAuthority(
   workspaceRoot: string,
   options: {
@@ -291,39 +228,28 @@ export function createSqliteArtifactStoreWriteAuthority(
 ): ArtifactStoreWriteAuthority {
   const store = new SqliteArtifactStore(
     workspaceRoot,
-    'authority',
     createSqliteArtifactMetadataRepository(workspaceRoot),
     options.assertAuthority,
     options.leaseBoundWriterLockAuthority,
   );
   return Object.freeze({
     store,
-    recover: () => store.recoverForWriteWithAuthority(),
     close: () => store.close(),
   });
 }
 
 class SqliteArtifactStore implements ArtifactAuthorityStore {
   private artifactRoot: string;
-  private purgeIntentPath: string;
   private records: ArtifactRecord[] = [];
-  private metadataReady = false;
-  private recoveryRequired: boolean;
-  private selfManagedRecoveryRequired: boolean;
-  private recoverableOrphans = new Map<string, RecoverableOrphan>();
   private queue: Promise<void> = Promise.resolve();
 
   constructor(
     private workspaceRoot: string,
-    private readonly recoveryMode: 'self_managed' | 'authority',
-    private readonly metadataRepository: ArtifactMetadataRepository,
+    private readonly metadataRepository: ReturnType<typeof createSqliteArtifactMetadataRepository>,
     private readonly assertAuthority?: () => Promise<void>,
     private readonly leaseBoundWriterLockAuthority?: ArtifactWriterLockAuthority,
   ) {
     this.artifactRoot = join(workspaceRoot, 'artifacts');
-    this.purgeIntentPath = join(this.artifactRoot, ARTIFACT_PURGE_INTENT_FILE);
-    this.recoveryRequired = recoveryMode === 'authority';
-    this.selfManagedRecoveryRequired = recoveryMode === 'self_managed';
   }
 
   close(): void {
@@ -337,7 +263,7 @@ class SqliteArtifactStore implements ArtifactAuthorityStore {
     });
     const id = acceptedInput.id ?? randomUUID();
     if (!ARTIFACT_KIND_SET.has(acceptedInput.kind)) throw new Error('Invalid Artifact kind');
-    if (acceptedInput.source !== undefined && !ARTIFACT_SOURCE_SET.has(acceptedInput.source)) {
+    if (!ARTIFACT_SOURCE_SET.has(acceptedInput.source)) {
       throw new Error('Invalid Artifact source');
     }
     if (
@@ -358,13 +284,8 @@ class SqliteArtifactStore implements ArtifactAuthorityStore {
     const name = sanitizeArtifactName(acceptedInput.name);
     const relativePath = `${acceptedInput.sessionId}/${id}-${name}`;
     validateRelativeArtifactPath(relativePath);
-    validateCanonicalArtifactTargetName(basename(relativePath));
     return this.enqueueMutation(async () => {
-      await this.prepareMutationUnlocked({
-        kind: 'create',
-        input: acceptedInput,
-        identity: { id, canonicalName: name },
-      });
+      await this.prepareMutationUnlocked();
       const existing = this.records.find((record) => record.id === id);
       if (existing) {
         return this.replayExistingArtifactUnlocked(existing, acceptedInput, {
@@ -373,19 +294,6 @@ class SqliteArtifactStore implements ArtifactAuthorityStore {
           relativePath,
         });
       }
-      await this.assertNoCompatiblePublicationStagingUnlocked(acceptedInput, {
-        id,
-        canonicalName: name,
-      });
-      const adopted = await this.adoptRecoverableOrphanUnlocked(acceptedInput, {
-        id,
-        canonicalName: name,
-      });
-      if (adopted) return adopted;
-      await this.assertNoCompatiblePayloadExistsUnlocked(acceptedInput, {
-        id,
-        canonicalName: name,
-      });
       return this.publishNewArtifactUnlocked(
         {
           id,
@@ -396,14 +304,13 @@ class SqliteArtifactStore implements ArtifactAuthorityStore {
           kind: acceptedInput.kind,
           relativePath,
           ...(acceptedInput.mimeType ? { mimeType: acceptedInput.mimeType } : {}),
-          ...(acceptedInput.source ? { source: acceptedInput.source } : {}),
+          source: acceptedInput.source,
           ...(acceptedInput.summary ? { summary: acceptedInput.summary } : {}),
           ...(acceptedInput.deepResearchRole
             ? { deepResearchRole: acceptedInput.deepResearchRole }
             : {}),
-          status: 'live',
         },
-        (tempPath) => writeFile(tempPath, acceptedInput.content, { flag: 'wx' }),
+        (targetPath) => writeFile(targetPath, acceptedInput.content, { flag: 'wx' }),
       );
     });
   }
@@ -441,18 +348,13 @@ class SqliteArtifactStore implements ArtifactAuthorityStore {
           (record) =>
             record.sessionId === input.sourceSessionId &&
             turnIds.has(record.turnId) &&
-            !excludedArtifactIds.has(record.id) &&
-            !isRetiredCapture(record),
+            !excludedArtifactIds.has(record.id),
         )
         .map((record) => ({ ...record }));
       for (const [sessionId, artifactIds] of requestedLinkedArtifactIds) {
         for (const artifactId of artifactIds) {
           const record = this.records.find(
-            (candidate) =>
-              candidate.sessionId === sessionId &&
-              candidate.id === artifactId &&
-              candidate.status !== 'deleted' &&
-              !isRetiredCapture(candidate),
+            (candidate) => candidate.sessionId === sessionId && candidate.id === artifactId,
           );
           // A linked child result names every Artifact its turn held, and the
           // ledger naming them cannot be rewritten. One that is no longer
@@ -468,8 +370,7 @@ class SqliteArtifactStore implements ArtifactAuthorityStore {
           record.sessionId === input.sourceSessionId &&
           includedArtifactIds.has(record.id) &&
           !excludedArtifactIds.has(record.id) &&
-          !selectedIds.has(record.id) &&
-          !isRetiredCapture(record)
+          !selectedIds.has(record.id)
         ) {
           selected.push({ ...record });
           selectedIds.add(record.id);
@@ -486,9 +387,7 @@ class SqliteArtifactStore implements ArtifactAuthorityStore {
         input.targetSessionId,
         record.id,
       );
-      const prepared = await this.enqueue(() =>
-        this.prepareRecordRead(record, record.sizeBytes, true),
-      );
+      const prepared = await this.enqueue(() => this.prepareRecordRead(record, record.sizeBytes));
       if (!prepared.ok) {
         throw new Error(`Artifact ${record.id} could not be copied: ${prepared.reason}`);
       }
@@ -511,27 +410,13 @@ class SqliteArtifactStore implements ArtifactAuthorityStore {
     const source = prepared.record;
     const name = sanitizeArtifactName(source.name);
     const relativePath = `${targetSessionId}/${targetId}-${name}`;
-    const publicationInput = { sessionId: targetSessionId, name };
     assertCanonicalArtifactEntityId(targetId, 'id');
     validateRelativeArtifactPath(relativePath);
-    validateCanonicalArtifactTargetName(basename(relativePath));
     return this.enqueueMutation(async () => {
-      await this.prepareMutationUnlocked({
-        kind: 'copy',
-        input: publicationInput,
-        identity: { id: targetId, canonicalName: name },
-      });
+      await this.prepareMutationUnlocked();
       if (this.records.some((record) => record.id === targetId)) {
         throw new Error(`Artifact target already exists: ${targetId}`);
       }
-      await this.assertNoCompatiblePublicationStagingUnlocked(publicationInput, {
-        id: targetId,
-        canonicalName: name,
-      });
-      await this.assertNoCompatiblePayloadExistsUnlocked(publicationInput, {
-        id: targetId,
-        canonicalName: name,
-      });
       return this.publishNewArtifactUnlocked(
         {
           ...source,
@@ -540,7 +425,7 @@ class SqliteArtifactStore implements ArtifactAuthorityStore {
           name,
           relativePath,
         },
-        (tempPath) => copyFile(prepared.path, tempPath, fsConstants.COPYFILE_EXCL),
+        (targetPath) => copyFile(prepared.path, targetPath, fsConstants.COPYFILE_EXCL),
         source.sizeBytes,
       );
     });
@@ -548,7 +433,7 @@ class SqliteArtifactStore implements ArtifactAuthorityStore {
 
   private async publishNewArtifactUnlocked(
     draft: ArtifactRecordDraft,
-    writeStaging: (tempPath: string) => Promise<void>,
+    writeTarget: (targetPath: string) => Promise<void>,
     expectedSize?: number,
   ): Promise<ArtifactRecord> {
     const target = join(this.artifactRoot, draft.relativePath);
@@ -558,64 +443,34 @@ class SqliteArtifactStore implements ArtifactAuthorityStore {
       await syncDirectoryChain(targetDirectory, this.workspaceRoot);
     }
     await assertArtifactDirectory(this.artifactRoot, targetDirectory);
-    const tempPath = join(targetDirectory, publicationStagingName(basename(target)));
-    let preserveStaging = false;
-    let targetLinked = false;
+    await rm(target, { force: true });
     try {
-      await writeStaging(tempPath);
-      await syncFile(tempPath);
+      await writeTarget(target);
+      await syncFile(target);
       await syncDirectory(targetDirectory);
-      const size = await stat(tempPath);
+      const size = await stat(target);
       if (expectedSize !== undefined && size.size !== expectedSize) {
         throw new Error(`Artifact source changed while copying: ${draft.id}`);
       }
       const record: ArtifactRecord = { ...draft, sizeBytes: size.size };
       const nextRecords = [...this.records, record];
-      try {
-        try {
-          await link(tempPath, target);
-          targetLinked = true;
-        } catch (error) {
-          if (isAlreadyExists(error)) {
-            throw new Error(`Artifact target already exists: ${draft.id}`);
-          }
-          throw error;
-        }
-        await syncDirectory(targetDirectory);
-        await this.writeMetadataUnlocked({ upserts: [record] });
-      } catch (error) {
-        if (targetLinked) {
-          try {
-            await removeFileDurably(target, targetDirectory);
-          } catch (cleanupError) {
-            preserveStaging = true;
-            this.invalidateWriterState();
-            throw new AggregateError(
-              [error, cleanupError],
-              `Artifact ${draft.id} metadata publication and payload cleanup both failed`,
-            );
-          }
-        }
-        throw error;
-      }
+      await this.writeMetadataUnlocked({ upserts: [record] });
       this.records = nextRecords;
       return { ...record };
-    } finally {
-      if (!preserveStaging) {
-        try {
-          await removeFileDurably(tempPath, targetDirectory);
-        } catch (error) {
-          this.invalidateWriterState();
-          throw error;
-        }
-      }
+    } catch (error) {
+      await removeFileDurably(target, targetDirectory).catch(() => undefined);
+      throw error;
     }
   }
 
   async purgeSessionArtifacts(sessionId: string): Promise<void> {
     assertCanonicalArtifactEntityId(sessionId, 'sessionId');
-    const records = await this.list(sessionId, { includeDeleted: true });
-    if (records.length > 0) await this.purge(records.map((record) => record.id));
+    await this.enqueueMutation(async () => {
+      await this.prepareMutationUnlocked();
+      await this.purgeRecordsUnlocked(
+        this.records.filter((record) => record.sessionId === sessionId),
+      );
+    });
   }
 
   private async replayExistingArtifactUnlocked(
@@ -659,51 +514,7 @@ class SqliteArtifactStore implements ArtifactAuthorityStore {
       throw artifactReplayConflict(canonical.id);
     }
 
-    if (existing.status === 'live') return { ...existing };
-    const revived: ArtifactRecord = { ...existing, status: 'live' };
-    const nextRecords = this.records.map((record) =>
-      record.id === canonical.id ? revived : record,
-    );
-    await this.writeMetadataUnlocked({ upserts: [revived] });
-    this.records = nextRecords;
-    return { ...revived };
-  }
-
-  recoverForWriteWithAuthority(): Promise<void> {
-    return this.enqueueMutation(async () => {
-      this.recoveryRequired = true;
-      this.recoverableOrphans.clear();
-      await this.prepareRecoveryUnlocked();
-      this.recoverableOrphans = await this.findRecoverableOrphansUnlocked();
-      this.recoveryRequired = false;
-    });
-  }
-
-  async list(
-    sessionId: string,
-    opts: { includeDeleted?: boolean } = {},
-  ): Promise<ArtifactRecord[]> {
-    const includeDeleted = opts.includeDeleted ?? false;
-    return this.enqueue(async () => {
-      await this.load();
-      return (
-        this.records
-          .filter((record) => record.sessionId === sessionId)
-          .filter((record) => includeDeleted || record.status !== 'deleted')
-          // Secondary `id` sort for determinism when fixture artifacts share
-          // a frozen createdAt (PR108k-yj e2e-fixture determinism).
-          .sort(compareArtifactRecords)
-          .map((record) => ({ ...record }))
-      );
-    });
-  }
-
-  async get(artifactId: string): Promise<ArtifactRecord | null> {
-    return this.enqueue(async () => {
-      await this.load();
-      const record = this.records.find((item) => item.id === artifactId);
-      return record ? { ...record } : null;
-    });
+    return { ...existing };
   }
 
   async listPage(
@@ -731,7 +542,7 @@ class SqliteArtifactStore implements ArtifactAuthorityStore {
       await this.load();
       const snapshot = this.sessionSnapshot(sessionId);
       return snapshot.records
-        .filter((record) => record.turnId === turnId && record.status !== 'deleted')
+        .filter((record) => record.turnId === turnId)
         .map((record) => ({ ...record }));
     });
   }
@@ -748,30 +559,6 @@ class SqliteArtifactStore implements ArtifactAuthorityStore {
     });
   }
 
-  async readText(
-    artifactId: string,
-    opts: { maxBytes?: number; includeDeleted?: boolean } = {},
-  ): Promise<ArtifactTextReadResult> {
-    const prepared = await this.prepareRead(
-      artifactId,
-      opts.maxBytes ?? ARTIFACT_TEXT_PREVIEW_LIMIT_BYTES,
-      opts.includeDeleted ?? false,
-    );
-    return this.readPreparedText(prepared);
-  }
-
-  async readBinary(
-    artifactId: string,
-    opts: { maxBytes?: number } = {},
-  ): Promise<ArtifactBinaryReadResult> {
-    const prepared = await this.prepareRead(
-      artifactId,
-      opts.maxBytes ?? ARTIFACT_BINARY_PREVIEW_LIMIT_BYTES,
-      false,
-    );
-    return this.readPreparedBinary(prepared);
-  }
-
   readTextInSession(
     sessionId: string,
     artifactId: string,
@@ -840,7 +627,6 @@ class SqliteArtifactStore implements ArtifactAuthorityStore {
       const prepared = await this.prepareRecordRead(
         record,
         input.maxBytes ?? ARTIFACT_BINARY_PREVIEW_LIMIT_BYTES,
-        false,
       );
       return this.readPreparedBinary(prepared);
     });
@@ -865,19 +651,19 @@ class SqliteArtifactStore implements ArtifactAuthorityStore {
     return { ok: true, base64: read.bytes.toString('base64'), mimeType };
   }
 
-  async delete(artifactId: string): Promise<void> {
-    await this.enqueueMutation(async () => {
-      await this.prepareMutationUnlocked({ kind: 'delete' });
-      const existing = this.records.find(
-        (record) => record.id === artifactId && record.status !== 'deleted',
-      );
-      if (!existing) return;
-      const tombstone: ArtifactRecord = { ...existing, status: 'deleted' };
-      const nextRecords: ArtifactRecord[] = this.records.map((record) =>
-        record.id === artifactId ? tombstone : record,
-      );
-      await this.writeMetadataUnlocked({ upserts: [tombstone] });
-      this.records = nextRecords;
+  deleteOwnedArtifactInSession(
+    sessionId: string,
+    artifactId: string,
+    source: ArtifactSource,
+  ): Promise<void> {
+    return this.enqueueMutation(async () => {
+      await this.prepareMutationUnlocked();
+      const snapshot = this.sessionSnapshot(sessionId);
+      const existing = snapshot.records.find((record) => record.id === artifactId);
+      if (!existing || existing.source !== source) {
+        throw new Error('Artifact does not belong to the expected Session authority');
+      }
+      await this.purgeRecordsUnlocked([existing]);
     });
   }
 
@@ -886,68 +672,21 @@ class SqliteArtifactStore implements ArtifactAuthorityStore {
     artifactId: string,
   ): Promise<ArtifactUserDeleteResult> {
     return this.enqueueMutation(async () => {
-      await this.prepareMutationUnlocked({ kind: 'delete' });
+      await this.prepareMutationUnlocked();
       const snapshot = this.sessionSnapshot(sessionId);
       const existing = snapshot.records.find((record) => record.id === artifactId);
       if (!existing) return { kind: 'not_found' };
       if (!canUserDeleteArtifact(existing)) return { kind: 'protected' };
-      if (existing.status === 'deleted') {
-        return { kind: 'deleted', record: { ...existing } };
-      }
-      const tombstone: ArtifactRecord = { ...existing, status: 'deleted' };
-      const nextRecords = this.records.map((record) =>
-        record.id === existing.id ? tombstone : record,
-      );
-      await this.writeMetadataUnlocked({ upserts: [tombstone] });
-      this.records = nextRecords;
-      return { kind: 'deleted', record: { ...tombstone } };
+      await this.purgeRecordsUnlocked([existing]);
+      return { kind: 'deleted' };
     });
   }
 
-  async purge(artifactIds: readonly string[]): Promise<void> {
-    const acceptedArtifactIds = Object.freeze([...artifactIds]);
-    await this.enqueueMutation(async () => {
-      await this.prepareMutationUnlocked({ kind: 'purge' });
-      const ids = new Set(acceptedArtifactIds);
-      await this.purgeRecordsUnlocked(this.records.filter((record) => ids.has(record.id)));
-    });
-  }
-
-  /**
-   * Drops a bounded batch of the prepared-request captures left behind by the
-   * retired capture sink, and reports what is still there.
-   *
-   * These are not the user's to clean up: they never appear in the UI, and the
-   * only thing that ever reclaimed one was purging its whole conversation. The
-   * store made them, so the store disposes of them.
-   *
-   * Bounded so a large residue cannot monopolise the mutation queue, and safe
-   * to stop at any point: purge publishes its intent before touching a file,
-   * and the next call reads whatever is left.
-   */
-  async purgeRetiredCaptures(limit: number): Promise<{ purged: number; remaining: number }> {
-    let outcome = { purged: 0, remaining: 0 };
-    await this.enqueueMutation(async () => {
-      await this.prepareMutationUnlocked({ kind: 'purge' });
-      const retired = this.records.filter(isRetiredCapture);
-      const batch = retired.slice(0, limit);
-      await this.purgeRecordsUnlocked(batch);
-      outcome = { purged: batch.length, remaining: retired.length - batch.length };
-    });
-    return outcome;
-  }
-
   private async purgeRecordsUnlocked(records: readonly ArtifactRecord[]): Promise<void> {
     if (records.length === 0) return;
     const ids = new Set(records.map((record) => record.id));
     const paths = await this.preparePurgePathsUnlocked(records);
-    try {
-      await this.publishPurgeIntentUnlocked([...ids]);
-      await this.completePurgeUnlocked(ids, paths);
-    } catch (error) {
-      this.invalidateWriterState();
-      throw error;
-    }
+    await this.completePurgeUnlocked(ids, paths);
   }
 
   private async preparePurgePathsUnlocked(
@@ -1037,6 +776,7 @@ class SqliteArtifactStore implements ArtifactAuthorityStore {
     ids: ReadonlySet<string>,
     paths: readonly string[],
   ): Promise<void> {
+    const nextRecords = this.records.filter((record) => !ids.has(record.id));
     const changedDirectories = new Set<string>();
     try {
       for (const path of paths) {
@@ -1046,20 +786,10 @@ class SqliteArtifactStore implements ArtifactAuthorityStore {
     } finally {
       for (const directory of changedDirectories) await syncDirectory(directory);
     }
-    const nextRecords = this.records.filter((record) => !ids.has(record.id));
+    // Keep the paths discoverable until physical cleanup is durable. Session
+    // retirement already owns the pending cleanup intent and retries on reopen.
     await this.writeMetadataUnlocked({ deleteIds: [...ids] });
     this.records = nextRecords;
-    await this.removePurgeIntentUnlocked();
-  }
-
-  private async prepareRead(
-    artifactId: string,
-    maxBytes: number,
-    includeDeleted = false,
-  ): Promise<PreparedArtifactRead | ArtifactReadFailure> {
-    const record = await this.get(artifactId);
-    if (!record) return { ok: false, reason: 'not_found' };
-    return this.prepareRecordRead(record, maxBytes, includeDeleted);
   }
 
   private async prepareReadInSessionUnlocked(
@@ -1071,15 +801,13 @@ class SqliteArtifactStore implements ArtifactAuthorityStore {
     const snapshot = this.sessionSnapshot(sessionId);
     const record = snapshot.records.find((candidate) => candidate.id === artifactId);
     if (!record) return { ok: false, reason: 'not_found' };
-    return this.prepareRecordRead(record, maxBytes, false);
+    return this.prepareRecordRead(record, maxBytes);
   }
 
   private async prepareRecordRead(
     record: ArtifactRecord,
     maxBytes: number,
-    includeDeleted: boolean,
   ): Promise<PreparedArtifactRead | ArtifactReadFailure> {
-    if (record.status === 'deleted' && !includeDeleted) return { ok: false, reason: 'deleted' };
     const resolved = await resolveArtifactPath({
       artifactRoot: this.artifactRoot,
       relativePath: record.relativePath,
@@ -1089,330 +817,26 @@ class SqliteArtifactStore implements ArtifactAuthorityStore {
   }
 
   private async load(): Promise<void> {
-    await this.metadataRepository.ready();
-    this.metadataReady = true;
     this.records = this.metadataRepository.readAll();
   }
 
   private async writeMetadataUnlocked(changes: ArtifactMetadataChanges): Promise<void> {
-    await this.metadataRepository.ready();
-    this.metadataReady = true;
     this.metadataRepository.applyChanges(changes);
   }
 
-  private async prepareMutationUnlocked(
-    purpose:
-      | {
-          readonly kind: 'create';
-          readonly input: CreateArtifactInput;
-          readonly identity: { readonly id: string; readonly canonicalName: string };
-        }
-      | {
-          readonly kind: 'copy';
-          readonly input: Pick<CreateArtifactInput, 'sessionId' | 'name'>;
-          readonly identity: { readonly id: string; readonly canonicalName: string };
-        }
-      | { readonly kind: 'delete' | 'purge' },
-  ): Promise<void> {
-    if (this.recoveryMode === 'self_managed') {
-      this.recoverableOrphans.clear();
-      if (this.selfManagedRecoveryRequired) {
-        await this.prepareRecoveryUnlocked();
-        this.selfManagedRecoveryRequired = false;
-      } else {
-        await this.reloadForMutationUnlocked();
-        await this.recoverPurgeIntentUnlocked();
-      }
-      if (purpose.kind === 'create' || purpose.kind === 'copy') {
-        await this.recoverCompatiblePublicationsUnlocked(purpose.input, purpose.identity);
-      }
-      if (purpose.kind === 'create') {
-        this.recoverableOrphans = await this.findCompatibleRecoverableOrphansUnlocked(
-          purpose.input,
-          purpose.identity,
-        );
-      }
-      return;
-    }
+  private async prepareMutationUnlocked(): Promise<void> {
     await this.reloadForMutationUnlocked();
-    if (this.recoveryRequired) throw artifactWriteRecoveryRequired();
-    if (!(await this.hasCanonicalRecoveryResidueUnlocked())) return;
-    this.recoveryRequired = true;
-    throw artifactWriteRecoveryRequired();
   }
 
   private async reloadForMutationUnlocked(): Promise<void> {
-    await this.metadataRepository.ready();
-    this.metadataReady = true;
     this.records = this.metadataRepository.readAll();
   }
 
-  private async hasCanonicalRecoveryResidueUnlocked(): Promise<boolean> {
-    try {
-      await lstat(this.purgeIntentPath);
-      return true;
-    } catch (error) {
-      if (!isNotFound(error)) throw error;
-    }
-
-    let sessionEntries: Dirent[];
-    try {
-      sessionEntries = await readdir(this.artifactRoot, { withFileTypes: true });
-    } catch (error) {
-      if (isNotFound(error)) return false;
-      throw error;
-    }
-    if (sessionEntries.some((entry) => isArtifactPurgeRecoveryTempName(entry.name))) {
-      return true;
-    }
-    for (const sessionEntry of sessionEntries) {
-      if (!sessionEntry.isDirectory()) continue;
-      const entries = await readdir(join(this.artifactRoot, sessionEntry.name), {
-        withFileTypes: true,
-      });
-      if (entries.some((entry) => ARTIFACT_PUBLICATION_STAGING_PATTERN.test(entry.name))) {
-        return true;
-      }
-    }
-    return false;
-  }
-
-  private async prepareRecoveryUnlocked(): Promise<void> {
-    await this.reloadForMutationUnlocked();
-    try {
-      await syncDirectory(this.artifactRoot);
-    } catch (error) {
-      if (!isNotFound(error)) throw error;
-    }
-    await this.recoverMetadataTempsUnlocked();
-    await this.recoverPublicationsUnlocked();
-    await this.recoverPurgeIntentUnlocked();
-  }
-
-  private async adoptRecoverableOrphanUnlocked(
-    input: CreateArtifactInput,
-    identity: { id: string; canonicalName: string },
-  ): Promise<ArtifactRecord | null> {
-    const names = [identity.canonicalName];
-    const candidates = names
-      .map((name) => ({
-        name,
-        relativePath: `${input.sessionId}/${identity.id}-${name}`,
-      }))
-      .map((candidate) => ({
-        ...candidate,
-        fingerprint: this.recoverableOrphans.get(filesystemPathKey(candidate.relativePath)),
-      }))
-      .filter(
-        (
-          candidate,
-        ): candidate is typeof candidate & {
-          readonly fingerprint: RecoverableOrphan;
-        } => candidate.fingerprint !== undefined,
-      );
-    if (candidates.length === 0) return null;
-    if (candidates.length !== 1) throw artifactReplayConflict(identity.id);
-
-    const candidate = candidates[0]!;
-    const resolved = await resolveArtifactPath({
-      artifactRoot: this.artifactRoot,
-      relativePath: candidate.relativePath,
-    });
-    if (!resolved.ok) throw artifactReplayConflict(identity.id);
-    const expectedBytes = Buffer.from(input.content);
-    const payloadStat = await lstat(resolved.path).catch(() => null);
-    const expectedDigest = sha256(expectedBytes);
-    if (
-      !payloadStat?.isFile() ||
-      payloadStat.isSymbolicLink() ||
-      payloadStat.size !== expectedBytes.byteLength ||
-      candidate.fingerprint.canonicalPath !== resolved.path ||
-      candidate.fingerprint.dev !== payloadStat.dev ||
-      candidate.fingerprint.ino !== payloadStat.ino ||
-      candidate.fingerprint.size !== payloadStat.size ||
-      candidate.fingerprint.digest !== expectedDigest ||
-      (await digestFile(resolved.path)) !== expectedDigest
-    ) {
-      throw artifactReplayConflict(identity.id);
-    }
-
-    const record: ArtifactRecord = {
-      id: identity.id,
-      sessionId: input.sessionId,
-      turnId: input.turnId,
-      createdAt: input.now ?? Date.now(),
-      name: candidate.name,
-      kind: input.kind,
-      relativePath: candidate.relativePath,
-      sizeBytes: payloadStat.size,
-      ...(input.mimeType ? { mimeType: input.mimeType } : {}),
-      ...(input.source ? { source: input.source } : {}),
-      ...(input.summary ? { summary: input.summary } : {}),
-      ...(input.deepResearchRole ? { deepResearchRole: input.deepResearchRole } : {}),
-      status: 'live',
-    };
-    const nextRecords = [...this.records, record];
-    await this.writeMetadataUnlocked({ upserts: [record] });
-    this.records = nextRecords;
-    this.recoverableOrphans.delete(filesystemPathKey(candidate.relativePath));
-    return { ...record };
-  }
-
-  private async findRecoverableOrphansUnlocked(): Promise<Map<string, RecoverableOrphan>> {
-    const orphans = new Map<string, RecoverableOrphan>();
-    const referencedPaths = new Set(
-      this.records.map((record) => filesystemPathKey(record.relativePath)),
-    );
-    let sessionEntries: Dirent[];
-    try {
-      sessionEntries = await readdir(this.artifactRoot, { withFileTypes: true });
-    } catch (error) {
-      if (isNotFound(error)) return orphans;
-      throw error;
-    }
-    for (const sessionEntry of sessionEntries) {
-      if (!sessionEntry.isDirectory() || !isCanonicalArtifactEntityId(sessionEntry.name)) continue;
-      const sessionDirectory = join(this.artifactRoot, sessionEntry.name);
-      await assertArtifactDirectory(this.artifactRoot, sessionDirectory);
-      for (const entry of await readdir(sessionDirectory, { withFileTypes: true })) {
-        if (!entry.isFile() || ARTIFACT_PUBLICATION_STAGING_PATTERN.test(entry.name)) continue;
-        const relativePath = `${sessionEntry.name}/${entry.name}`;
-        if (referencedPaths.has(filesystemPathKey(relativePath))) continue;
-        const path = join(sessionDirectory, entry.name);
-        const [canonicalPath, pathStat] = await Promise.all([realpath(path), lstat(path)]);
-        if (!pathStat.isFile() || pathStat.isSymbolicLink()) {
-          throw new Error(
-            `Artifact orphan changed while recovery was inspecting it: ${relativePath}`,
-          );
-        }
-        orphans.set(filesystemPathKey(relativePath), {
-          canonicalPath,
-          dev: pathStat.dev,
-          ino: pathStat.ino,
-          size: pathStat.size,
-          digest: await digestFile(path),
-        });
-      }
-    }
-    return orphans;
-  }
-
-  private async findCompatibleRecoverableOrphansUnlocked(
-    input: CreateArtifactInput,
-    identity: { id: string; canonicalName: string },
-  ): Promise<Map<string, RecoverableOrphan>> {
-    const orphans = new Map<string, RecoverableOrphan>();
-    const sessionDirectory = join(this.artifactRoot, input.sessionId);
-    try {
-      await assertArtifactDirectory(this.artifactRoot, sessionDirectory);
-    } catch (error) {
-      if (isNotFound(error)) return orphans;
-      throw error;
-    }
-    const referencedPaths = new Set(
-      this.records.map((record) => filesystemPathKey(record.relativePath)),
-    );
-    for (const name of [identity.canonicalName]) {
-      const relativePath = `${input.sessionId}/${identity.id}-${name}`;
-      if (referencedPaths.has(filesystemPathKey(relativePath))) continue;
-      const path = join(this.artifactRoot, relativePath);
-      let pathStat;
-      try {
-        pathStat = await lstat(path);
-      } catch (error) {
-        if (isNotFound(error)) continue;
-        throw error;
-      }
-      if (!pathStat.isFile() || pathStat.isSymbolicLink()) continue;
-      orphans.set(filesystemPathKey(relativePath), {
-        canonicalPath: await realpath(path),
-        dev: pathStat.dev,
-        ino: pathStat.ino,
-        size: pathStat.size,
-        digest: await digestFile(path),
-      });
-    }
-    return orphans;
-  }
-
-  private async recoverCompatiblePublicationsUnlocked(
-    input: Pick<CreateArtifactInput, 'sessionId' | 'name'>,
-    identity: { id: string; canonicalName: string },
-  ): Promise<void> {
-    const sessionDirectory = join(this.artifactRoot, input.sessionId);
-    const targetHashes = new Set(
-      [identity.canonicalName].map((name) => artifactTargetHash(`${identity.id}-${name}`)),
-    );
-    let entries: Dirent[];
-    try {
-      entries = await readdir(sessionDirectory, { withFileTypes: true });
-    } catch (error) {
-      if (isNotFound(error)) return;
-      throw error;
-    }
-    for (const entry of entries.sort((left, right) => left.name.localeCompare(right.name))) {
-      const match = ARTIFACT_PUBLICATION_STAGING_PATTERN.exec(entry.name);
-      if (!match || !targetHashes.has(match[1]!)) continue;
-      await this.recoverPublicationUnlocked({
-        sessionId: input.sessionId,
-        sessionDirectory,
-        stagingName: entry.name,
-        targetHash: match[1]!,
-      });
-    }
-  }
-
-  private async assertNoCompatiblePayloadExistsUnlocked(
-    input: Pick<CreateArtifactInput, 'sessionId' | 'name'>,
-    identity: { id: string; canonicalName: string },
-  ): Promise<void> {
-    const names = new Set([identity.canonicalName]);
-    for (const name of names) {
-      const path = join(this.artifactRoot, input.sessionId, `${identity.id}-${name}`);
-      try {
-        await lstat(path);
-        throw new Error(`Artifact target already exists: ${identity.id}`);
-      } catch (error) {
-        if (!isNotFound(error)) throw error;
-      }
-    }
-  }
-
-  private async assertNoCompatiblePublicationStagingUnlocked(
-    input: Pick<CreateArtifactInput, 'sessionId' | 'name'>,
-    identity: { id: string; canonicalName: string },
-  ): Promise<void> {
-    const targetHashes = new Set(
-      [identity.canonicalName].map((name) => artifactTargetHash(`${identity.id}-${name}`)),
-    );
-    let entries: Dirent[];
-    try {
-      entries = await readdir(join(this.artifactRoot, input.sessionId), { withFileTypes: true });
-    } catch (error) {
-      if (isNotFound(error)) return;
-      throw error;
-    }
-    for (const entry of entries) {
-      const match = ARTIFACT_PUBLICATION_STAGING_PATTERN.exec(entry.name);
-      if (match && targetHashes.has(match[1]!)) {
-        throw new Error(`Artifact target already exists: ${identity.id}`);
-      }
-    }
-  }
-
-  private invalidateWriterState(): void {
-    if (this.recoveryMode === 'authority') this.recoveryRequired = true;
-    else this.selfManagedRecoveryRequired = true;
-  }
-
   private bindMutationRoot(canonicalRoot: string): void {
     if (this.workspaceRoot === canonicalRoot) return;
     this.workspaceRoot = canonicalRoot;
     this.artifactRoot = join(canonicalRoot, 'artifacts');
-    this.purgeIntentPath = join(this.artifactRoot, ARTIFACT_PURGE_INTENT_FILE);
     this.records = [];
-    this.recoverableOrphans.clear();
-    if (this.recoveryMode === 'self_managed') this.selfManagedRecoveryRequired = true;
   }
 
   /**
@@ -1431,177 +855,6 @@ class SqliteArtifactStore implements ArtifactAuthorityStore {
     return { records, revision: artifactListRevision(records) };
   }
 
-  private async publishPurgeIntentUnlocked(artifactIds: readonly string[]): Promise<void> {
-    const contents = JSON.stringify({
-      schemaVersion: PURGE_INTENT_SCHEMA_VERSION,
-      artifactIds,
-    });
-    const result = await publishMarkerFile({
-      root: this.artifactRoot,
-      markerFile: ARTIFACT_PURGE_INTENT_FILE,
-      contents,
-      maxBytes: MAX_PURGE_INTENT_BYTES,
-      publication: 'create',
-      invalidFile: invalidPurgeIntent,
-    });
-    if (result === 'already_exists') {
-      throw new Error('Artifact purge intent already exists');
-    }
-  }
-
-  private async recoverPurgeIntentUnlocked(): Promise<void> {
-    let contents: string;
-    try {
-      contents = await readBoundedMarkerFile({
-        path: this.purgeIntentPath,
-        maxBytes: MAX_PURGE_INTENT_BYTES,
-        invalidFile: invalidPurgeIntent,
-      });
-    } catch (error) {
-      if (isNotFound(error)) return;
-      throw error;
-    }
-    const intent = parsePurgeIntent(contents);
-    const ids = new Set(intent.artifactIds);
-    const records = this.records.filter((record) => ids.has(record.id));
-    if (records.length === 0) {
-      await this.removePurgeIntentUnlocked();
-      return;
-    }
-    if (records.length !== intent.artifactIds.length) {
-      throw invalidPurgeIntent();
-    }
-    const paths = await this.preparePurgePathsUnlocked(records);
-    await this.completePurgeUnlocked(ids, paths);
-  }
-
-  private async removePurgeIntentUnlocked(): Promise<void> {
-    try {
-      await unlink(this.purgeIntentPath);
-      await syncDirectory(this.artifactRoot);
-    } catch (error) {
-      if (!isNotFound(error)) throw error;
-    }
-  }
-
-  private async recoverMetadataTempsUnlocked(): Promise<void> {
-    let entries: Dirent[];
-    try {
-      entries = await readdir(this.artifactRoot, { withFileTypes: true });
-    } catch (error) {
-      if (isNotFound(error)) return;
-      throw error;
-    }
-    for (const entry of entries) {
-      if (!isArtifactPurgeRecoveryTempName(entry.name)) continue;
-      if (!entry.isFile()) {
-        throw new Error(`Artifact recovery temp is not a regular file: ${entry.name}`);
-      }
-      await removeFileDurably(join(this.artifactRoot, entry.name), this.artifactRoot);
-    }
-  }
-
-  private async recoverPublicationsUnlocked(): Promise<void> {
-    let sessionEntries: Dirent[];
-    try {
-      sessionEntries = await readdir(this.artifactRoot, { withFileTypes: true });
-    } catch (error) {
-      if (isNotFound(error)) return;
-      throw error;
-    }
-
-    for (const sessionEntry of sessionEntries) {
-      if (!sessionEntry.isDirectory()) continue;
-      const sessionDirectory = join(this.artifactRoot, sessionEntry.name);
-      const entries = await readdir(sessionDirectory, { withFileTypes: true });
-      for (const entry of entries) {
-        const match = ARTIFACT_PUBLICATION_STAGING_PATTERN.exec(entry.name);
-        if (!match) continue;
-        await this.recoverPublicationUnlocked({
-          sessionId: sessionEntry.name,
-          sessionDirectory,
-          stagingName: entry.name,
-          targetHash: match[1]!,
-        });
-      }
-    }
-  }
-
-  private async recoverPublicationUnlocked(input: {
-    sessionId: string;
-    sessionDirectory: string;
-    stagingName: string;
-    targetHash: string;
-  }): Promise<void> {
-    const stagingPath = join(input.sessionDirectory, input.stagingName);
-    const stagingStat = await lstat(stagingPath);
-    if (!stagingStat.isFile() || stagingStat.isSymbolicLink()) {
-      throw invalidPublicationResidue(input.stagingName);
-    }
-
-    const metadataMatches = this.records.filter(
-      (record) =>
-        record.sessionId === input.sessionId &&
-        artifactTargetHash(basename(record.relativePath)) === input.targetHash,
-    );
-    if (metadataMatches.length > 1) throw invalidPublicationResidue(input.stagingName);
-
-    const directoryEntries = await readdir(input.sessionDirectory, { withFileTypes: true });
-    const matchingTargets: Array<{ name: string; path: string; size: number }> = [];
-    for (const entry of directoryEntries) {
-      if (entry.name === input.stagingName || ARTIFACT_PUBLICATION_STAGING_PATTERN.test(entry.name))
-        continue;
-      if (artifactTargetHash(entry.name) !== input.targetHash) continue;
-      const candidatePath = join(input.sessionDirectory, entry.name);
-      const candidateStat = await lstat(candidatePath);
-      if (!candidateStat.isFile() || candidateStat.isSymbolicLink()) {
-        throw invalidPublicationResidue(input.stagingName);
-      }
-      matchingTargets.push({
-        name: entry.name,
-        path: candidatePath,
-        size: candidateStat.size,
-      });
-    }
-
-    const metadataRecord = metadataMatches[0];
-    if (matchingTargets.length === 0 && !metadataRecord) {
-      await removeFileDurably(stagingPath, input.sessionDirectory);
-      return;
-    }
-    if (matchingTargets.length !== 1) {
-      throw invalidPublicationResidue(input.stagingName);
-    }
-
-    const [linkedTarget] = matchingTargets;
-    if (
-      !linkedTarget ||
-      linkedTarget.size !== stagingStat.size ||
-      (await digestFile(linkedTarget.path)) !== (await digestFile(stagingPath))
-    ) {
-      throw invalidPublicationResidue(input.stagingName);
-    }
-    const relativePath = `${input.sessionId}/${linkedTarget.name}`;
-    validateRelativeArtifactPath(relativePath);
-
-    if (metadataRecord) {
-      if (
-        metadataRecord.relativePath !== relativePath ||
-        metadataRecord.sizeBytes !== linkedTarget.size
-      ) {
-        throw invalidPublicationResidue(input.stagingName);
-      }
-      await removeFileDurably(stagingPath, input.sessionDirectory);
-      return;
-    }
-
-    await removeFileDurably(
-      join(input.sessionDirectory, linkedTarget.name),
-      input.sessionDirectory,
-    );
-    await removeFileDurably(stagingPath, input.sessionDirectory);
-  }
-
   private enqueueSerialized<T>(operation: () => Promise<T>): Promise<T> {
     const next = this.queue.then(operation, operation);
     this.queue = next.then(
@@ -1613,14 +866,6 @@ class SqliteArtifactStore implements ArtifactAuthorityStore {
 
   private enqueue<T>(operation: () => Promise<T>): Promise<T> {
     return this.enqueueSerialized(async () => {
-      if (!this.metadataReady) {
-        return this.runWithWriterLock(async () => {
-          await this.assertAuthority?.();
-          await this.metadataRepository.ready();
-          this.metadataReady = true;
-          return operation();
-        });
-      }
       await this.assertAuthority?.();
       return operation();
     });
@@ -1647,10 +892,6 @@ class SqliteArtifactStore implements ArtifactAuthorityStore {
   }
 }
 
-function publicationStagingName(targetBasename: string): string {
-  return `.artifact-publish.${artifactTargetHash(targetBasename)}.${randomUUID()}.tmp`;
-}
-
 async function openRealTarget(path: string) {
   const noFollowFlags = fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW;
   try {
@@ -1725,14 +966,6 @@ async function readPreparedChunk(
   }
 }
 
-function artifactTargetHash(targetBasename: string): string {
-  return createHash('sha256').update(targetBasename).digest('hex');
-}
-
-function invalidPublicationResidue(stagingName: string): Error {
-  return new Error(`Artifact publication residue does not match canonical state: ${stagingName}`);
-}
-
 function artifactReplayConflict(artifactId: string): Error {
   return new Error(`Artifact ${artifactId} already exists with different metadata or content`);
 }
@@ -1747,10 +980,6 @@ function conversationCopyArtifactId(
     .digest('hex')}`;
 }
 
-function artifactWriteRecoveryRequired(): Error {
-  return new Error('Artifact write recovery is required before another mutation');
-}
-
 function optionalCanonicalText(value: string | undefined): string | undefined {
   return value ? value : undefined;
 }
@@ -1781,8 +1010,7 @@ function sameArtifactRecord(a: ArtifactRecord, b: ArtifactRecord): boolean {
     a.mimeType === b.mimeType &&
     a.source === b.source &&
     a.summary === b.summary &&
-    a.deepResearchRole === b.deepResearchRole &&
-    a.status === b.status
+    a.deepResearchRole === b.deepResearchRole
   );
 }
 
@@ -1792,12 +1020,6 @@ function assertPageBound(value: number, allowZero: boolean, label: string): void
   }
 }
 
-async function digestFile(path: string): Promise<string> {
-  const hash = createHash('sha256');
-  for await (const chunk of createReadStream(path)) hash.update(chunk);
-  return hash.digest('hex');
-}
-
 export async function resolveArtifactPath(input: {
   artifactRoot: string;
   relativePath: string;
@@ -1829,10 +1051,6 @@ export function sanitizeArtifactName(name: string): string {
   return truncated || 'artifact';
 }
 
-function filesystemPathKey(path: string): string {
-  return Buffer.from(path, 'utf8').toString('utf8');
-}
-
 function truncateWithoutSplittingSurrogate(value: string, maxCodeUnits: number): string {
   const truncated = value.slice(0, maxCodeUnits);
   const last = truncated.charCodeAt(truncated.length - 1);
@@ -1867,39 +1085,6 @@ function assertArtifactTurnKey(value: unknown): asserts value is string {
   }
 }
 
-interface ArtifactPurgeIntent {
-  schemaVersion: typeof PURGE_INTENT_SCHEMA_VERSION;
-  artifactIds: string[];
-}
-
-function parsePurgeIntent(contents: string): ArtifactPurgeIntent {
-  let value: unknown;
-  try {
-    value = JSON.parse(contents);
-  } catch {
-    throw invalidPurgeIntent();
-  }
-  if (
-    !isRecord(value) ||
-    Object.keys(value).some((key) => key !== 'schemaVersion' && key !== 'artifactIds') ||
-    value.schemaVersion !== PURGE_INTENT_SCHEMA_VERSION ||
-    !Array.isArray(value.artifactIds) ||
-    value.artifactIds.length === 0 ||
-    !value.artifactIds.every(isCanonicalArtifactEntityId) ||
-    new Set(value.artifactIds).size !== value.artifactIds.length
-  ) {
-    throw invalidPurgeIntent();
-  }
-  return {
-    schemaVersion: PURGE_INTENT_SCHEMA_VERSION,
-    artifactIds: value.artifactIds,
-  };
-}
-
-function invalidPurgeIntent(): Error {
-  return new Error('Invalid artifact purge intent');
-}
-
 const ARTIFACT_KIND_SET = new Set<ArtifactKind>(ARTIFACT_KINDS);
 const ARTIFACT_SOURCE_SET = new Set<ArtifactSource>(ARTIFACT_SOURCES);
 
@@ -1928,7 +1113,13 @@ async function resolveArtifactRemovalEntry(
   try {
     const parent = await realpath(dirname(target));
     const entry = join(parent, basename(target));
-    const entryStat = await lstat(entry, { bigint: true });
+    const entryStat = await lstat(entry, { bigint: true }).catch((error) => {
+      if (isNotFound(error)) return undefined;
+      throw error;
+    });
+    // A previous attempt may have unlinked the file but failed to sync its
+    // parent. Retain that parent in the next purge's durability barrier.
+    if (!entryStat) return { unlinkPath: entry, comparisonIdentity: `path:${entry}` };
     if (entryStat.isSymbolicLink()) {
       return {
         unlinkPath: entry,
@@ -1976,10 +1167,6 @@ function isNotFound(error: unknown): boolean {
   return typeof error === 'object' && error !== null && 'code' in error && error.code === 'ENOENT';
 }
 
-function isAlreadyExists(error: unknown): boolean {
-  return typeof error === 'object' && error !== null && 'code' in error && error.code === 'EEXIST';
-}
-
 function sniffAllowedBinaryMime(bytes: Uint8Array): string | null {
   // Core owns the binary signatures, shared with the attachment and image-read
   // paths so the three cannot drift. SVG needs a wider text scan than a fixed
diff --git a/packages/storage/src/artifact-stores.ts b/packages/storage/src/artifact-stores.ts
index 2a8ad31455..58d82b3d73 100644
--- a/packages/storage/src/artifact-stores.ts
+++ b/packages/storage/src/artifact-stores.ts
@@ -56,27 +56,12 @@ export interface InteractiveArtifactStoreWriter extends DurableArtifactAttachmen
   readonly kind: 'interactive';
   readonly access: 'write';
   readonly [writerBrand]: true;
-  recover(): Promise<void>;
   create(input: CreateArtifactInput): Promise<ArtifactRecord>;
-  /**
-   * Create, reporting whether THIS call is the one that published the artifact.
-   *
-   * `create` is idempotent on a content-derived id: a second caller for the same
-   * bytes gets the existing record back. A caller that may later reclaim what it
-   * published cannot infer ownership from that success — it would reclaim an
-   * artifact an earlier, already-committed projection still references. The
-   * probe and the create share one write lease, so the receipt is exact.
-   */
-  createOwned(
-    input: CreateArtifactInput,
-  ): Promise<{ record: ArtifactRecord; publishedByThisCall: boolean }>;
   /**
    * Narrow system delete for one Session-owned artifact of a declared source.
    *
-   * Not a user delete: the sources this serves are `userDeletable: false`
-   * precisely because durable replay may depend on them. The caller must name
-   * the source it believes it owns, and a mismatch throws — so a caller that is
-   * wrong about what it is reclaiming reclaims nothing.
+   * The caller must name the source it believes it owns, and a mismatch throws,
+   * so a caller that is wrong about what it is reclaiming reclaims nothing.
    */
   deleteOwnedArtifactInSession(
     sessionId: string,
@@ -87,7 +72,6 @@ export interface InteractiveArtifactStoreWriter extends DurableArtifactAttachmen
     input: ConversationArtifactCopyInput,
   ): Promise<ConversationArtifactCopyResult>;
   purgeSessionArtifacts(sessionId: string): Promise<void>;
-  purgeRetiredCaptures: ArtifactAuthorityStore['purgeRetiredCaptures'];
   listPage: ArtifactAuthorityStore['listPage'];
   listTurnArtifacts: ArtifactAuthorityStore['listTurnArtifacts'];
   getInSession: ArtifactAuthorityStore['getInSession'];
@@ -161,30 +145,12 @@ function createWriterFacade(
     readChunkInSession: (sessionId, artifactId, options) =>
       run(() => store.readChunkInSession(sessionId, artifactId, options)),
     readDurableAttachmentBinary: (input) => run(() => store.readDurableAttachmentBinary(input)),
-    recover: () => run(() => authority.recover()),
     create: (input) => {
       const acceptedInput = snapshotCreateInput(input);
       return run(() => store.create(acceptedInput));
     },
-    createOwned: (input) => {
-      const acceptedInput = snapshotCreateInput(input);
-      return run(async () => {
-        const plannedId = acceptedInput.id;
-        const existing = plannedId
-          ? await store.getInSession(acceptedInput.sessionId, plannedId)
-          : undefined;
-        const record = await store.create(acceptedInput);
-        return { record, publishedByThisCall: !existing?.record };
-      });
-    },
     deleteOwnedArtifactInSession: (sessionId, artifactId, source) =>
-      run(async () => {
-        const entry = await store.getInSession(sessionId, artifactId);
-        if (!entry.record || entry.record.source !== source) {
-          throw new Error('Artifact does not belong to the expected Session authority');
-        }
-        await store.delete(artifactId);
-      }),
+      run(() => store.deleteOwnedArtifactInSession(sessionId, artifactId, source)),
     copyConversationArtifacts: (input) => {
       const acceptedInput: ConversationArtifactCopyInput = Object.freeze({
         ...input,
@@ -211,7 +177,6 @@ function createWriterFacade(
       return run(() => store.copyConversationArtifacts(acceptedInput));
     },
     purgeSessionArtifacts: (sessionId) => run(() => store.purgeSessionArtifacts(sessionId)),
-    purgeRetiredCaptures: (limit) => run(() => store.purgeRetiredCaptures(limit)),
     deleteUserArtifactInSession: (sessionId, artifactId) =>
       run(() => store.deleteUserArtifactInSession(sessionId, artifactId)),
     close: () => {
@@ -222,87 +187,6 @@ function createWriterFacade(
   return Object.freeze(facade);
 }
 
-/**
- * How much one batch deletes -- as much as it may, not as little.
- *
- * A batch costs what the store costs, not what its own size costs: the purge
- * guard resolves the path of every record it is NOT deleting, measured at
- * roughly 0.04 ms per record held, so 9,000 records cost about 370 ms whether
- * the batch deletes 256 of them or 16. That fixed cost is per batch, so a
- * smaller batch cannot shorten the wait a live turn takes -- it only makes the
- * residue take more batches, each paying the same toll again.
- *
- * The lever that does work is the pause below, which keeps the sweep out of the
- * queue for three times as long as it was in it.
- */
-const RETIRED_CAPTURE_SWEEP_BATCH = 256;
-const RETIRED_CAPTURE_SWEEP_PAUSE_MS = 250;
-/** Keeps the sweep to a quarter of the time, however long a batch takes. */
-const RETIRED_CAPTURE_SWEEP_DUTY_DIVISOR = 3;
-/**
- * How many batches may fail in a row before the sweep gives up.
- *
- * Most of what fails here is not permanent. Another mutation's failure makes
- * the write authority refuse everything until something recovers it, and a
- * full or briefly unavailable disk clears on its own -- so the first failure
- * says nothing about the second. Giving up on it is how this sweep once
- * reclaimed nothing at all, for every user, without saying so.
- */
-const RETIRED_CAPTURE_SWEEP_MAX_CONSECUTIVE_FAILURES = 5;
-const RETIRED_CAPTURE_SWEEP_RETRY_MS = 1_000;
-
-/**
- * Drains the prepared-request captures the retired capture sink left behind.
- *
- * The sweep shares one mutation queue with live turns, so it takes bounded
- * batches and waits between them for as long as the last one cost, rather than
- * holding the queue for the whole residue. Stopping only means the next batch
- * does not start: each batch is already durable on its own, and a later run
- * continues from what is left.
- *
- * `onError` is where the decision to repair belongs -- the sweep knows a batch
- * failed, not what would make the next one succeed.
- */
-export function startRetiredCaptureSweep(
-  artifacts: Pick<InteractiveArtifactStoreWriter, 'purgeRetiredCaptures'>,
-  options: { readonly onError?: (error: unknown) => void | Promise<void> } = {},
-): () => void {
-  let stopped = false;
-  void (async () => {
-    let failures = 0;
-    while (!stopped) {
-      let pauseMs: number;
-      try {
-        const startedAt = Date.now();
-        const { remaining } = await artifacts.purgeRetiredCaptures(RETIRED_CAPTURE_SWEEP_BATCH);
-        const batchMs = Date.now() - startedAt;
-        if (remaining === 0) return;
-        failures = 0;
-        pauseMs = Math.max(
-          RETIRED_CAPTURE_SWEEP_PAUSE_MS,
-          batchMs * RETIRED_CAPTURE_SWEEP_DUTY_DIVISOR,
-        );
-      } catch (error) {
-        failures += 1;
-        try {
-          await options.onError?.(error);
-        } catch {
-          // A repair that fails leaves the same state the batch did, and the
-          // failure below is already being counted.
-        }
-        if (failures >= RETIRED_CAPTURE_SWEEP_MAX_CONSECUTIVE_FAILURES) return;
-        pauseMs = RETIRED_CAPTURE_SWEEP_RETRY_MS;
-      }
-      await new Promise<void>((resolve) => {
-        setTimeout(resolve, pauseMs).unref();
-      });
-    }
-  })();
-  return () => {
-    stopped = true;
-  };
-}
-
 function snapshotCreateInput(input: CreateArtifactInput): CreateArtifactInput {
   return Object.freeze({
     ...input,
diff --git a/packages/storage/src/artifact-writer-lock.ts b/packages/storage/src/artifact-writer-lock.ts
index 20869b83a3..6fef705c5f 100644
--- a/packages/storage/src/artifact-writer-lock.ts
+++ b/packages/storage/src/artifact-writer-lock.ts
@@ -23,7 +23,6 @@ import { constants as fsConstants } from 'node:fs';
 import { lstat, mkdir, open, realpath, type FileHandle } from 'node:fs/promises';
 import { join } from 'node:path';
 import { unlock, waitForLock } from 'fs-native-extensions';
-import { ARTIFACT_WRITER_LOCK_FILE } from './artifact-storage-layout.js';
 import { withArtifactWriterBootstrapLock } from './artifact-writer-bootstrap-lock.js';
 import {
   prepareArtifactWriterBootstrapAuthority,
@@ -149,3 +148,4 @@ function releaseLock(handle: FileHandle): void {
     // Closing the handle is the final OS-level release path.
   }
 }
+export const ARTIFACT_WRITER_LOCK_FILE = '.maka-artifact-writer.lock';
diff --git a/packages/storage/src/operational-state-backup.ts b/packages/storage/src/operational-state-backup.ts
index cc64fabf1a..f92f61aad2 100644
--- a/packages/storage/src/operational-state-backup.ts
+++ b/packages/storage/src/operational-state-backup.ts
@@ -365,27 +365,25 @@ function validateSqlite(path: string, files: readonly OperationalBackupFile[]):
 
       const artifactRows = database
         .prepare(`
-          SELECT artifact_id, session_id, created_at, status, relative_path, record_json
+          SELECT artifact_id, session_id, created_at, relative_path, record_json
           FROM artifact_records
-          ORDER BY created_at, storage_key
+          ORDER BY created_at, artifact_id
         `)
         .all() as Array<{
         artifact_id?: unknown;
         session_id?: unknown;
         created_at?: unknown;
-        status?: unknown;
         relative_path?: unknown;
         record_json?: unknown;
       }>;
-      const artifacts = decodeArtifactRecordJsons(artifactRows.map((row) => row.record_json));
       const filesByPath = new Map(files.map((file) => [file.path, file]));
-      for (const [index, record] of artifacts.entries()) {
-        const row = artifactRows[index];
+      for (const row of artifactRows) {
+        const [record] = decodeArtifactRecordJsons([row.record_json]);
+        if (!record) continue;
         if (
           row?.artifact_id !== record.id ||
           row.session_id !== record.sessionId ||
           row.created_at !== record.createdAt ||
-          row.status !== record.status ||
           row.relative_path !== record.relativePath
         ) {
           throw new Error(`artifact indexes do not match record: ${record.id}`);
diff --git a/packages/storage/src/session-bundle-policy.ts b/packages/storage/src/session-bundle-policy.ts
index 6cd27ac8d8..7b215646d4 100644
--- a/packages/storage/src/session-bundle-policy.ts
+++ b/packages/storage/src/session-bundle-policy.ts
@@ -114,7 +114,7 @@ export async function planSessionBundleExport(
     }
     const rows = database
       .prepare(
-        'SELECT record_json FROM artifact_records WHERE session_id = ? ORDER BY created_at, storage_key',
+        'SELECT record_json FROM artifact_records WHERE session_id = ? ORDER BY created_at, artifact_id',
       )
       .all(input.sessionId) as Array<{ record_json?: unknown }>;
     artifacts = decodeArtifactRecordJsons(rows.map((row) => row.record_json));
diff --git a/packages/storage/src/sqlite-artifact-metadata.ts b/packages/storage/src/sqlite-artifact-metadata.ts
index c1678b6011..08c75685e2 100644
--- a/packages/storage/src/sqlite-artifact-metadata.ts
+++ b/packages/storage/src/sqlite-artifact-metadata.ts
@@ -17,26 +17,24 @@
  * under the License.
  */
 
-import { createHash } from 'node:crypto';
 import { resolve } from 'node:path';
 import type { ArtifactRecord } from '@maka/core/artifacts';
-import type {
-  ArtifactMetadataChanges,
-  ArtifactMetadataRepository,
-} from './artifact-metadata-repository.js';
 import { decodeArtifactRecordJsons } from './artifact-metadata-codec.js';
 import {
   acquireOperationalStateDatabase,
   type OperationalStateDatabaseLease,
 } from './operational-state-store.js';
 
-export function createSqliteArtifactMetadataRepository(
-  workspaceRoot: string,
-): ArtifactMetadataRepository {
+export interface ArtifactMetadataChanges {
+  readonly upserts?: readonly ArtifactRecord[];
+  readonly deleteIds?: readonly string[];
+}
+
+export function createSqliteArtifactMetadataRepository(workspaceRoot: string) {
   return new SqliteArtifactMetadataRepository(workspaceRoot);
 }
 
-class SqliteArtifactMetadataRepository implements ArtifactMetadataRepository {
+class SqliteArtifactMetadataRepository {
   readonly #lease: OperationalStateDatabaseLease;
   #closed = false;
 
@@ -44,18 +42,13 @@ class SqliteArtifactMetadataRepository implements ArtifactMetadataRepository {
     this.#lease = acquireOperationalStateDatabase(resolve(workspaceRoot));
   }
 
-  ready(): Promise<void> {
-    this.assertOpen();
-    return Promise.resolve();
-  }
-
   readAll(): ArtifactRecord[] {
     this.assertOpen();
     const rows = this.#lease.database
       .prepare(`
         SELECT record_json
         FROM artifact_records
-        ORDER BY created_at, storage_key
+        ORDER BY created_at, artifact_id
       `)
       .all() as Array<{ record_json: string }>;
     return decodeRows(rows);
@@ -65,41 +58,33 @@ class SqliteArtifactMetadataRepository implements ArtifactMetadataRepository {
     this.assertOpen();
     this.#lease.transaction('write', () => {
       const remove = this.#lease.database.prepare(
-        'DELETE FROM artifact_records WHERE storage_key = ?',
+        'DELETE FROM artifact_records WHERE artifact_id = ?',
       );
-      for (const id of changes.deleteIds ?? []) remove.run(artifactIdentityKey(id));
+      for (const id of changes.deleteIds ?? []) remove.run(id);
 
       const upsert = this.#lease.database.prepare(`
         INSERT INTO artifact_records(
-          storage_key,
           artifact_id,
           session_id,
           created_at,
-          status,
           relative_path,
           record_json
-        ) VALUES (?, ?, ?, ?, ?, ?, ?)
-        ON CONFLICT(storage_key) DO UPDATE SET
-          artifact_id = excluded.artifact_id,
+        ) VALUES (?, ?, ?, ?, ?)
+        ON CONFLICT(artifact_id) DO UPDATE SET
           session_id = excluded.session_id,
           created_at = excluded.created_at,
-          status = excluded.status,
           relative_path = excluded.relative_path,
           record_json = excluded.record_json
-        WHERE artifact_id IS NOT excluded.artifact_id
-           OR session_id IS NOT excluded.session_id
+        WHERE session_id IS NOT excluded.session_id
            OR created_at IS NOT excluded.created_at
-           OR status IS NOT excluded.status
            OR relative_path IS NOT excluded.relative_path
            OR record_json IS NOT excluded.record_json
       `);
       for (const record of changes.upserts ?? []) {
         upsert.run(
-          artifactIdentityKey(record.id),
           record.id,
           record.sessionId,
           record.createdAt,
-          record.status,
           record.relativePath,
           JSON.stringify(record),
         );
@@ -121,7 +106,3 @@ class SqliteArtifactMetadataRepository implements ArtifactMetadataRepository {
 function decodeRows(rows: readonly { record_json: string }[]): ArtifactRecord[] {
   return decodeArtifactRecordJsons(rows.map((row) => row.record_json));
 }
-
-function artifactIdentityKey(id: string): string {
-  return createHash('sha256').update(JSON.stringify(id)).digest('hex');
-}
diff --git a/packages/storage/src/sqlite-artifact-schema.ts b/packages/storage/src/sqlite-artifact-schema.ts
index d330e66833..f8d3f3e733 100644
--- a/packages/storage/src/sqlite-artifact-schema.ts
+++ b/packages/storage/src/sqlite-artifact-schema.ts
@@ -18,25 +18,61 @@
  */
 
 import type { DatabaseSync } from 'node:sqlite';
+import { decodeArtifactRecordJsons } from './artifact-metadata-codec.js';
 
-export const SQLITE_ARTIFACT_SCHEMA_VERSION = 1;
+export const SQLITE_ARTIFACT_SCHEMA_VERSION = 3;
 
 export function migrateSqliteArtifactDatabase(db: DatabaseSync): void {
+  const columns = db.prepare('PRAGMA table_info(artifact_records)').all() as Array<{
+    name?: unknown;
+  }>;
+  const retained: string[] = [];
+  if (columns.some(({ name }) => name === 'status' || name === 'storage_key')) {
+    const rows = db.prepare('SELECT * FROM artifact_records').all();
+    for (const row of rows) {
+      if (columns.some(({ name }) => name === 'status') && row.status !== 'live') continue;
+      try {
+        const parsed = JSON.parse(String(row.record_json));
+        if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) continue;
+        if (parsed.status !== undefined && parsed.status !== 'live') continue;
+        delete parsed.status;
+        if (
+          parsed.id !== row.artifact_id ||
+          parsed.sessionId !== row.session_id ||
+          parsed.createdAt !== row.created_at ||
+          parsed.relativePath !== row.relative_path
+        )
+          continue;
+        retained.push(JSON.stringify(parsed));
+      } catch {}
+    }
+    db.exec('DROP TABLE artifact_records');
+  }
   db.exec(`
     CREATE TABLE IF NOT EXISTS artifact_records (
-      storage_key TEXT PRIMARY KEY,
-      artifact_id TEXT NOT NULL,
+      artifact_id TEXT PRIMARY KEY,
       session_id TEXT NOT NULL,
       created_at INTEGER NOT NULL CHECK (created_at >= 0),
-      status TEXT NOT NULL CHECK (status IN ('live', 'deleted')),
       relative_path TEXT NOT NULL,
       record_json TEXT NOT NULL
     );
 
     CREATE INDEX IF NOT EXISTS artifact_records_session_order
-      ON artifact_records(session_id, created_at, storage_key);
+      ON artifact_records(session_id, created_at, artifact_id);
 
     CREATE UNIQUE INDEX IF NOT EXISTS artifact_records_relative_path
       ON artifact_records(relative_path);
   `);
+  const insert = db.prepare(`
+    INSERT INTO artifact_records VALUES (?, ?, ?, ?, ?)
+  `);
+  for (const record of decodeArtifactRecordJsons(retained)) {
+    insert.run(
+      record.id,
+      record.sessionId,
+      record.createdAt,
+      record.relativePath,
+      JSON.stringify(record),
+    );
+  }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org