// SPEC: PJ2026-0104010803 Workbench唯一投影 draft-2026-06-20-p2-terminal-outbox-recovery; PJ2026-010403 API契约 draft-2026-06-20-p2-terminal-outbox-recovery.
// Responsibility: Workbench read model, durable projection diagnostics, and pure-GET regression tests.

import assert from "node:assert/strict";
import { test } from "bun:test";

import { createCloudApiServer } from "./server.ts";
import { createBackendPerformanceStore } from "./backend-performance.ts";
import { createCodeAgentTraceStore } from "./code-agent-trace-store.ts";
import { classifyWorkbenchReadModelFailure } from "./server-workbench-http.ts";
import { codeAgentTurnStatusPayload, createCodeAgentChatResultStore } from "./server-code-agent-http.ts";
import { createWorkbenchTurnProjection, durableTraceStatus, projectionDiagnostics, traceTerminalEvidence } from "./workbench-turn-projection.ts";

import {
  ACTOR,
  getJson,
  waitForCondition,
  createDurableFactsRuntimeStore,
  createRuntimeStoreFromFacts,
  buildDurableFactsForSession,
  normalizeTestMessages,
  testTimingProjection,
  timestampIso,
  elapsedMs,
  normalizeTestEvents,
  filterFacts,
  workbenchTestFactOrder,
  matchesFact,
  matchesTraceFact,
  testFactFamilySet,
  mergeFacts,
  emptyFacts,
  normalizeTestStatus,
  getSseEvents,
  parseSseBlock,
  createFakeKafkaFactory,
  waitFor
} from "./server-workbench-http-test-helpers.ts";

test("workbench projection diagnostics exposes AgentRun refresh errors without terminal inference", () => {
  const traceId = "trc_projection_refresh_degraded";
  const result = {
    traceId,
    status: "running",
    agentRun: { runId: "run_projection_refresh_degraded", commandId: "cmd_projection_refresh_degraded", status: "running" }
  };
  const trace = { traceId, status: "running", events: [], eventCount: 0, updatedAt: "2026-06-18T01:00:00.000Z" };
  const projection = createWorkbenchTurnProjection({ traceId, result, trace });
  const diagnostics = projectionDiagnostics({
    traceId,
    result,
    trace,
    projection,
    refreshError: { code: "agentrun_result_timeout", message: "状态更新超时，AgentRun 结果暂不可见。", retryable: true, timeoutMs: 2500 }
  });
  assert.equal(projection.status, "running");
  assert.equal(diagnostics.projectionStatus, "blocked");
  assert.equal(diagnostics.projectionHealth, "degraded");
  assert.equal(diagnostics.blocker.code, "agentrun_result_timeout");
  assert.equal(diagnostics.blocker.message, "状态更新超时，AgentRun 结果暂不可见。");
  assert.equal(diagnostics.sourceRunId, "run_projection_refresh_degraded");
  assert.equal(diagnostics.sourceCommandId, "cmd_projection_refresh_degraded");
});
test("workbench projection diagnostics keeps projecting health distinct from caught-up", () => {
  const traceId = "trc_projection_health_projecting";
  const result = {
    traceId,
    status: "running",
    agentRun: { runId: "run_projection_health_projecting", commandId: "cmd_projection_health_projecting", status: "running" }
  };
  const trace = {
    traceId,
    status: "running",
    events: [{ traceId, seq: 7, sourceSeq: 7, type: "backend_status", status: "running", createdAt: "2026-06-19T04:40:00.000Z" }],
    eventCount: 1,
    updatedAt: "2026-06-19T04:40:00.000Z"
  };
  const projection = createWorkbenchTurnProjection({ traceId, result, trace });
  const diagnostics = projectionDiagnostics({ traceId, result, trace, projection });
  assert.equal(projection.status, "running");
  assert.equal(diagnostics.projectionStatus, "projecting");
  assert.equal(diagnostics.projectionHealth, "projecting");
});
test("workbench runtime retryable dependency failures classify as 503 (#2020)", () => {
  const error = new Error("fetch failed");
  error.name = "WorkbenchRuntimeDependencyError";
  error.code = "workbench_runtime_unreachable";
  error.data = {
    serviceId: "hwlab-workbench-runtime",
    retryable: true,
    transient: true,
    valuesRedacted: true
  };
  const classified = classifyWorkbenchReadModelFailure(error);
  assert.equal(classified.statusCode, 503);
  assert.equal(classified.code, "workbench_runtime_unreachable");
  assert.equal(classified.retryable, true);
  assert.equal(classified.transient, true);
  assert.equal(classified.serviceId, "hwlab-workbench-runtime");
});

test("workbench turn projection keeps lastEventAt on the latest event instead of stale admission timing (#1889)", () => {
  const traceId = "trc_turn_timing_latest_event";
  const result = {
    traceId,
    status: "running",
    startedAt: "2026-06-23T21:43:48.740Z",
    lastEventAt: "2026-06-23T21:43:48.740Z",
    timing: {
      startedAt: "2026-06-23T21:43:48.740Z",
      lastEventAt: "2026-06-23T21:43:48.740Z",
      durationMs: null
    }
  };
  const trace = {
    traceId,
    status: "running",
    updatedAt: "2026-06-23T21:44:33.486Z",
    lastEventAt: "2026-06-23T21:44:31.000Z",
    events: [
      { seq: 1, type: "admitted", createdAt: "2026-06-23T21:43:48.740Z" },
      { seq: 2, type: "commandExecution", status: "completed", updatedAt: "2026-06-23T21:44:31.000Z" }
    ],
    eventCount: 2
  };
  const projection = createWorkbenchTurnProjection({ traceId, result, trace });
  assert.equal(projection.startedAt, "2026-06-23T21:43:48.740Z");
  assert.equal(projection.lastEventAt, "2026-06-23T21:44:33.486Z");
  assert.equal(projection.timing.lastEventAt, "2026-06-23T21:44:33.486Z");
});

test("workbench turn projection treats retryable provider stream disconnect as active", () => {
  const traceId = "trc_retryable_provider_stream_disconnect";
  const result = {
    traceId,
    status: "canceled",
    failureKind: "provider-stream-disconnected",
    willRetry: true,
    agentRun: {
      runId: "run_retryable_provider_stream_disconnect",
      commandId: "cmd_retryable_provider_stream_disconnect",
      terminalStatus: "cancelled",
      failureKind: "provider-stream-disconnected",
      willRetry: true
    },
    providerTrace: { failureKind: "provider-stream-disconnected", willRetry: true }
  };
  const trace = {
    traceId,
    status: "canceled",
    events: [
      { seq: 1, type: "error", status: "retrying", errorCode: "provider-stream-disconnected", willRetry: true, terminal: false },
      { seq: 2, type: "result", status: "canceled", errorCode: "provider-stream-disconnected", willRetry: true, terminal: true }
    ],
    eventCount: 2
  };
  const projection = createWorkbenchTurnProjection({ traceId, result, trace });
  assert.equal(projection.status, "retrying");
  assert.equal(projection.running, true);
  assert.equal(projection.terminal, false);
  assert.equal(projection.finalResponse, null);
  assert.equal(durableTraceStatus(trace.events), "retrying");
  assert.equal(traceTerminalEvidence(trace), null);

  const completedProjection = createWorkbenchTurnProjection({
    traceId,
    result: {
      ...result,
      status: "completed",
      finalResponse: "retry recovered",
      agentRun: { ...result.agentRun, terminalStatus: "completed" }
    },
    trace: {
      ...trace,
      status: "completed",
      events: [
        ...trace.events,
        { seq: 3, type: "assistant", status: "completed", message: "retry recovered", terminal: true, replyAuthority: true }
      ],
      eventCount: 3
    }
  });
  assert.equal(completedProjection.status, "completed");
  assert.equal(completedProjection.running, false);
  assert.equal(completedProjection.terminal, true);
  assert.equal(completedProjection.finalResponse?.text, "retry recovered");
});

test("workbench turn projection sanitizes legacy agentrun-unreachable into retryable AgentRun transport interruption (#1853)", () => {
  const traceId = "trc_retryable_legacy_agentrun_unreachable";
  const result = {
    traceId,
    status: "failed",
    failureKind: "agentrun-unreachable",
    willRetry: true,
    agentRun: {
      runId: "run_retryable_legacy_agentrun_unreachable",
      commandId: "cmd_retryable_legacy_agentrun_unreachable",
      terminalStatus: "failed",
      failureKind: "agentrun-unreachable",
      willRetry: true
    },
    providerTrace: { failureKind: "agentrun-unreachable", willRetry: true }
  };
  const trace = {
    traceId,
    status: "failed",
    events: [
      { seq: 1, type: "error", status: "failed", errorCode: "agentrun-unreachable", failureKind: "agentrun-unreachable", willRetry: true, terminal: true }
    ],
    eventCount: 1
  };
  const projection = createWorkbenchTurnProjection({ traceId, result, trace });
  assert.equal(projection.status, "retrying");
  assert.equal(projection.running, true);
  assert.equal(projection.terminal, false);
  assert.equal(projection.finalResponse, null);
  assert.equal(projection.source, "provider-retry");
  assert.equal(projection.terminalEvidence, null);
  assert.equal(durableTraceStatus(trace.events), "retrying");
  assert.equal(traceTerminalEvidence(trace), null);
});

test("workbench turn projection seals final response from authoritative terminal assistant trace event (#1629)", () => {
  const traceId = "trc_workbench_terminal_assistant_final";
  const finalText = [
    "全部六份数据到手！下面是完整的六语言终极性能对比：",
    "",
    "| language | runtime | status |",
    "|---|---|---|",
    "| Lua | LuaJIT | pass |"
  ].join("\n");
  const trace = {
    traceId,
    status: "completed",
    events: [
      { seq: 1, type: "assistant_message", status: "running", message: "正在补 Lua 基准测试。" },
      { seq: 2, type: "assistant_message", status: "completed", replyAuthority: true, final: true, message: finalText },
      { seq: 3, type: "result", status: "completed", terminal: true, label: "agentrun:terminal:completed" }
    ],
    eventCount: 3
  };
  const projection = createWorkbenchTurnProjection({ traceId, result: { traceId, status: "completed" }, trace });
  assert.equal(projection.status, "completed");
  assert.equal(projection.terminal, true);
  assert.equal(projection.finalResponse.text, finalText);
  assert.equal(projection.assistantText, finalText);
  assert.equal(projection.terminalEvidence.finalResponse.text, finalText);
});

test("workbench turn projection keeps progress-only assistant trace text out of final response (#1629)", () => {
  const traceId = "trc_workbench_terminal_without_authoritative_final";
  const trace = {
    traceId,
    status: "completed",
    events: [
      { seq: 1, type: "assistant_message", status: "running", message: "这只是运行中的进度。" },
      { seq: 2, type: "result", status: "completed", terminal: true, label: "agentrun:terminal:completed" }
    ],
    eventCount: 2
  };
  const projection = createWorkbenchTurnProjection({ traceId, result: { traceId, status: "completed" }, trace });
  const diagnostics = projectionDiagnostics({ traceId, result: { traceId, status: "completed" }, trace, projection });
  assert.equal(projection.status, "running");
  assert.equal(projection.running, true);
  assert.equal(projection.terminal, false);
  assert.equal(projection.terminalObserved, true);
  assert.equal(projection.terminalObservedStatus, "completed");
  assert.equal(projection.waitingFor, "final_response");
  assert.equal(projection.finalResponse, null);
  assert.equal(projection.assistantText, null);
  assert.equal(diagnostics.projectionStatus, "projecting");
  assert.equal(diagnostics.projectionHealth, "projecting");
  assert.equal(diagnostics.waitingFor, "final_response");
});

test("code agent turn status keeps completed AgentRun without final response unsealed", () => {
  const traceId = "trc_code_agent_terminal_without_final";
  const payload = codeAgentTurnStatusPayload({
    traceId,
    result: {
      traceId,
      status: "completed",
      agentRun: {
        runId: "run_code_agent_terminal_without_final",
        commandId: "cmd_code_agent_terminal_without_final",
        status: "completed",
        terminalStatus: "completed"
      },
      runnerTrace: {
        traceId,
        status: "completed",
        events: [
          { seq: 1, type: "assistant_message", status: "running", message: "progress only" },
          { seq: 2, type: "terminal_status", terminal: true, terminalStatus: "completed" }
        ]
      }
    },
    snapshot: null,
    resultPollError: null,
    refreshError: null,
    options: { env: {} }
  });
  assert.equal(payload.status, "running");
  assert.equal(payload.running, true);
  assert.equal(payload.terminal, false);
  assert.equal(payload.terminalObserved, true);
  assert.equal(payload.terminalObservedStatus, "completed");
  assert.equal(payload.terminalSealBlocked, true);
  assert.equal(payload.waitingFor, "final_response");
  assert.equal(payload.finalResponse, null);
});

test("code agent turn status keeps failed AgentRun without final response unsealed", () => {
  const traceId = "trc_code_agent_failed_without_final";
  const payload = codeAgentTurnStatusPayload({
    traceId,
    result: {
      traceId,
      status: "failed",
      agentRun: {
        runId: "run_code_agent_failed_without_final",
        commandId: "cmd_code_agent_failed_without_final",
        status: "failed",
        terminalStatus: "failed"
      },
      runnerTrace: {
        traceId,
        status: "failed",
        events: [
          { seq: 1, type: "assistant_message", status: "running", message: "progress only" },
          { seq: 2, type: "terminal_status", terminal: true, terminalStatus: "failed" }
        ]
      }
    },
    snapshot: null,
    resultPollError: null,
    refreshError: null,
    options: { env: {} }
  });
  assert.equal(payload.status, "running");
  assert.equal(payload.running, true);
  assert.equal(payload.terminal, false);
  assert.equal(payload.terminalObserved, true);
  assert.equal(payload.terminalObservedStatus, "failed");
  assert.equal(payload.terminalSealBlocked, true);
  assert.equal(payload.waitingFor, "final_response");
  assert.equal(payload.finalResponse, null);
});

test("code agent turn status seals failed AgentRun when final response is authoritative", () => {
  const traceId = "trc_code_agent_failed_with_final";
  const payload = codeAgentTurnStatusPayload({
    traceId,
    result: {
      traceId,
      status: "failed",
      finalResponse: { text: "Workbench terminal failed: provider-stream-disconnected", status: "failed" },
      agentRun: {
        runId: "run_code_agent_failed_with_final",
        commandId: "cmd_code_agent_failed_with_final",
        status: "failed",
        terminalStatus: "failed"
      }
    },
    snapshot: null,
    resultPollError: null,
    refreshError: null,
    options: { env: {} }
  });
  assert.equal(payload.status, "failed");
  assert.equal(payload.running, false);
  assert.equal(payload.terminal, true);
  assert.equal(payload.terminalSealBlocked, false);
  assert.equal(payload.finalResponse.text, "Workbench terminal failed: provider-stream-disconnected");
});

test("code agent turn status seals completed AgentRun when finalText is authoritative", () => {
  const traceId = "trc_code_agent_terminal_with_final_text";
  const payload = codeAgentTurnStatusPayload({
    traceId,
    result: {
      traceId,
      status: "completed",
      finalText: "authoritative final",
      agentRun: {
        runId: "run_code_agent_terminal_with_final_text",
        commandId: "cmd_code_agent_terminal_with_final_text",
        status: "completed",
        terminalStatus: "completed"
      }
    },
    snapshot: null,
    resultPollError: null,
    refreshError: null,
    options: { env: {} }
  });
  assert.equal(payload.status, "completed");
  assert.equal(payload.running, false);
  assert.equal(payload.terminal, true);
  assert.equal(payload.terminalSealBlocked, false);
  assert.equal(payload.finalResponse.text, "authoritative final");
});

test("workbench trace event page exposes projectedSeq cursor range from durable facts", async () => {
  const traceId = "trc_workbench_trace_mixed_cursor";
  const session = {
    id: "ses_workbench_trace_mixed_cursor",
    projectId: "prj_workbench_authority_session",
    agentId: "hwlab-code-agent",
    status: "completed",
    ownerUserId: ACTOR.id,
    conversationId: "cnv_workbench_trace_mixed_cursor",
    threadId: "thread-workbench-trace-mixed-cursor",
    lastTraceId: traceId,
    updatedAt: "2026-06-19T04:10:17.060Z",
    session: { sessionStatus: "completed", lastTraceId: traceId, messages: [{ role: "user", text: "trace", traceId }] }
  };
  const mixedEvents = [
    { projectedSeq: 7, source: "agentrun", sourceSeq: 1, type: "backend", status: "running", label: "projected:7", createdAt: "2026-06-19T01:27:27.092Z" },
    { projectedSeq: 1, source: "agentrun", sourceSeq: 99, type: "backend", status: "running", label: "projected:1", createdAt: "2026-06-19T04:10:15.362Z" },
    { projectedSeq: 11, source: "agentrun", sourceSeq: 4, type: "backend", status: "running", label: "projected:11", createdAt: "2026-06-19T01:27:34.128Z" },
    { projectedSeq: 5, source: "agentrun", sourceSeq: 4, type: "backend", status: "running", label: "projected:5", createdAt: "2026-06-19T04:10:15.664Z" },
    { projectedSeq: 3, source: "agentrun", sourceSeq: 5, type: "backend", status: "running", label: "projected:3", createdAt: "2026-06-19T01:27:34.324Z" },
    { projectedSeq: 9, source: "agentrun", sourceSeq: 5, type: "backend", status: "running", label: "projected:9", createdAt: "2026-06-19T04:10:16.013Z" },
    { projectedSeq: 4, type: "result", status: "completed", label: "projected:4", terminal: true, createdAt: "2026-06-19T01:27:34.324Z" },
    { projectedSeq: 2, type: "request", status: "accepted", label: "projected:2", createdAt: "2026-06-19T04:10:15.362Z" },
    { projectedSeq: 6, type: "backend", status: "running", label: "projected:6", createdAt: "2026-06-19T04:10:15.664Z" },
    { projectedSeq: 8, type: "backend", status: "running", label: "projected:8", createdAt: "2026-06-19T04:10:16.013Z" },
    { projectedSeq: 10, type: "backend", status: "running", label: "projected:10", createdAt: "2026-06-19T04:10:17.060Z" }
  ];
  const accessController = {
    store: {
      async getAgentSession(sessionId) { return sessionId === session.id ? session : null; },
      async getAgentSessionByTraceId(requestTraceId) { return requestTraceId === traceId ? session : null; }
    },
    async ensureBootstrap() {},
    async authenticate() { return { ok: true, actor: ACTOR, session: { id: "uss_workbench_reader" } }; }
  };
  const runtimeStore = createDurableFactsRuntimeStore({ sessions: [{ session, events: mixedEvents, status: "completed" }] });
  const server = createCloudApiServer({ accessController, runtimeStore, codeAgentChatResults: createCodeAgentChatResultStore() });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));

  try {
    const { port } = server.address();
    const trace = await getJson(port, `/v1/workbench/traces/${encodeURIComponent(traceId)}/events?limit=25`);
    assert.equal(trace.status, 200);
    assert.equal(trace.body.events.length, mixedEvents.length);
    assert.deepEqual(trace.body.events.map((event) => event.seq), [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
    assert.equal(trace.body.events[0].label, "projected:1");
    assert.equal(trace.body.events.at(-1).label, "projected:11");
    assert.deepEqual(trace.body.range, { afterProjectedSeq: 0, fromProjectedSeq: 1, toProjectedSeq: 11, limit: 25, returned: 11, total: 11 });
    assert.equal(trace.body.nextProjectedSeq, 11);
    assert.equal(trace.body.hasMore, false);
    assert.equal(trace.body.nextCursor, null);

    const middle = await getJson(port, `/v1/workbench/traces/${encodeURIComponent(traceId)}/events?cursor=projected:4&limit=3`);
    assert.equal(middle.status, 200);
    assert.deepEqual(middle.body.events.map((event) => event.seq), [5, 6, 7]);
    assert.deepEqual(middle.body.range, { afterProjectedSeq: 4, fromProjectedSeq: 5, toProjectedSeq: 7, limit: 3, returned: 3, total: 11 });
    assert.equal(middle.body.hasMore, true);
    assert.equal(middle.body.nextProjectedSeq, 7);
    assert.equal(middle.body.nextCursor, "projected:7");

    const empty = await getJson(port, `/v1/workbench/traces/${encodeURIComponent(traceId)}/events?afterProjectedSeq=999999&limit=5`);
    assert.equal(empty.status, 200);
    assert.deepEqual(empty.body.events, []);
    assert.deepEqual(empty.body.range, { afterProjectedSeq: 999999, fromProjectedSeq: null, toProjectedSeq: null, limit: 5, returned: 0, total: 11 });
    assert.equal(empty.body.nextProjectedSeq, 999999);
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench trace event page pushes projectedSeq cursor into durable trace facts", async () => {
  const traceId = "trc_long_trace_projection_page";
  const session = {
    id: "ses_long_trace_projection_page",
    projectId: "prj_hwpod_workbench",
    agentId: "hwlab-code-agent",
    status: "running",
    ownerUserId: ACTOR.id,
    conversationId: "cnv_long_trace_projection_page",
    threadId: "thread-long-trace-projection-page",
    lastTraceId: traceId,
    updatedAt: "2026-06-20T02:30:00.000Z",
    session: { sessionStatus: "running", lastTraceId: traceId, messages: [{ role: "user", text: "long trace", traceId }] }
  };
  const events = Array.from({ length: 2600 }, (_, index) => ({
    seq: index + 1,
    sourceSeq: index + 1,
    projectedSeq: index + 1,
    type: "backend",
    label: `event:${index + 1}`,
    createdAt: new Date(Date.UTC(2026, 5, 20, 2, 30, index % 60)).toISOString()
  }));
  const queries = [];
  const runtimeStore = createDurableFactsRuntimeStore({ sessions: [{ session, events, status: "running" }], queries });
  const accessController = {
    store: {
      async getAgentSession(sessionId) { return sessionId === session.id ? session : null; },
      async getAgentSessionByTraceId(requestTraceId) { return requestTraceId === traceId ? session : null; }
    },
    async ensureBootstrap() {},
    async authenticate() { return { ok: true, actor: ACTOR, session: { id: "uss_workbench_reader" } }; }
  };
  const server = createCloudApiServer({ accessController, runtimeStore });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));

  try {
    const { port } = server.address();
    const trace = await getJson(port, `/v1/workbench/traces/${encodeURIComponent(traceId)}/events?afterProjectedSeq=2500&limit=25`);
    assert.equal(trace.status, 200);
    assert.deepEqual(trace.body.events.map((event) => event.seq), Array.from({ length: 25 }, (_, index) => 2501 + index));
    assert.equal(trace.body.hasMore, true);
    assert.equal(trace.body.nextCursor, "projected:2525");

    const traceQueries = queries.filter((query) => Array.isArray(query.families) && query.families.includes("traceEvents"));
    assert.equal(traceQueries.length, 1);
    assert.deepEqual(traceQueries[0].families, ["traceEvents"]);
    assert.equal(traceQueries[0].traceId, traceId);
    assert.equal(traceQueries[0].afterProjectedSeq, 2500);
    assert.equal(traceQueries[0].limit, 26);
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench trace events stay visible after session lastTraceId advances", async () => {
  const firstTraceId = "trc_workbench_historical_visible_first";
  const secondTraceId = "trc_workbench_historical_visible_second";
  const session = {
    id: "ses_workbench_historical_visible",
    projectId: "prj_hwpod_workbench",
    agentId: "hwlab-code-agent",
    status: "completed",
    ownerUserId: ACTOR.id,
    conversationId: "cnv_workbench_historical_visible",
    threadId: "thread-workbench-historical-visible",
    lastTraceId: firstTraceId,
    updatedAt: "2026-06-20T10:00:00.000Z",
    session: { sessionStatus: "completed", lastTraceId: firstTraceId, messages: [{ role: "user", text: "first", traceId: firstTraceId, status: "sent" }, { role: "agent", text: "OK", traceId: firstTraceId, status: "completed" }] }
  };
  const facts = buildDurableFactsForSession({
    session,
    status: "completed",
    finalText: "OK",
    events: [
      { projectedSeq: 1, sourceSeq: 1, type: "request", status: "accepted", label: "first:accepted", createdAt: "2026-06-20T09:59:58.000Z" },
      { projectedSeq: 2, sourceSeq: 2, type: "result", status: "completed", label: "first:completed", terminal: true, createdAt: "2026-06-20T10:00:00.000Z" }
    ]
  });
  facts.sessions[0].lastTraceId = secondTraceId;
  facts.sessions[0].status = "running";
  const queries = [];
  const runtimeStore = {
    async queryWorkbenchFacts(params = {}) {
      queries.push(params);
      const filtered = filterFacts(facts, params);
      return {
        facts: filtered,
        count: Object.values(filtered).reduce((sum, rows) => sum + rows.length, 0),
        persistence: { adapter: "test-durable-workbench-facts", durable: true }
      };
    }
  };
  const accessController = {
    store: {},
    async ensureBootstrap() {},
    async authenticate() { return { ok: true, actor: ACTOR, session: { id: "uss_workbench_reader" } }; }
  };
  const server = createCloudApiServer({ accessController, runtimeStore, codeAgentChatResults: createCodeAgentChatResultStore() });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));

  try {
    const { port } = server.address();
    const trace = await getJson(port, `/v1/workbench/traces/${encodeURIComponent(firstTraceId)}/events?limit=10`);
    assert.equal(trace.status, 200);
    assert.equal(trace.body.sessionId, session.id);
    assert.deepEqual(trace.body.events.map((event) => event.label), ["first:accepted", "first:completed"]);
    assert.ok(queries.some((query) => query.traceId === firstTraceId && query.families?.includes("messages") && query.families?.includes("turns")));
    assert.ok(queries.some((query) => query.sessionId === session.id && query.families?.includes("sessions") && !query.traceId));
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench read model exposes session, messages, turn, and trace without write repair", async () => {
  const traceStore = createCodeAgentTraceStore();
  const results = createCodeAgentChatResultStore();
  const writes = { recordAgentSessionOwner: 0, updateWorkspace: 0 };
  const traceId = "trc_workbench_read_model";
  const session = {
    id: "ses_workbench_read_model",
    projectId: "prj_workbench_authority_session",
    agentId: "hwlab-code-agent",
    status: "completed",
    startedAt: "2026-06-17T00:00:00.000Z",
    endedAt: "2026-06-17T00:00:03.000Z",
    ownerUserId: ACTOR.id,
    conversationId: "cnv_workbench_read_model",
    threadId: "thread-workbench-read-model",
    lastTraceId: traceId,
    updatedAt: "2026-06-17T00:00:03.000Z",
    session: {
      sessionStatus: "completed",
      providerProfile: "codex-api",
      lastTraceId: traceId,
      messages: [
        { role: "user", text: "ping", traceId, createdAt: "2026-06-17T00:00:00.000Z" },
        { role: "assistant", text: "pong", traceId, createdAt: "2026-06-17T00:00:03.000Z" }
      ],
      valuesRedacted: true,
      secretMaterialStored: false
    }
  };
  const factQueries = [];
  traceStore.append(traceId, { type: "request", status: "accepted", label: "request:accepted" });
  traceStore.append(traceId, { type: "result", status: "completed", label: "result:completed", terminal: true });
  results.set(traceId, {
    status: "completed",
    traceId,
    ownerUserId: ACTOR.id,
    conversationId: session.conversationId,
    sessionId: session.id,
    threadId: session.threadId,
    finalResponse: "pong",
    agentRun: { runId: "run_workbench_read_model", commandId: "cmd_workbench_read_model", status: "completed" }
  });

  const accessController = {
    store: {
      async listAgentSessionsForUser(input = {}) {
        return [session];
      },
      async getAgentSession(sessionId) { return sessionId === session.id ? session : null; },
      async getAgentSessionByTraceId(requestTraceId) { return requestTraceId === traceId ? session : null; },
      async recordAgentSessionOwner() { writes.recordAgentSessionOwner += 1; throw new Error("read model must not write session owner"); },
      async updateWorkspace() { writes.updateWorkspace += 1; throw new Error("read model must not update workspace"); }
    },
    async ensureBootstrap() {},
    async authenticate() { return { ok: true, actor: ACTOR, session: { id: "uss_workbench_reader" } }; }
  };
  const runtimeStore = createDurableFactsRuntimeStore({
    sessions: [{
      session,
      events: [
        { seq: 1, type: "request", status: "accepted", label: "request:accepted", createdAt: "2026-06-17T00:00:00.000Z" },
        { seq: 2, type: "result", status: "completed", label: "result:completed", terminal: true, createdAt: "2026-06-17T00:00:03.000Z" }
      ],
      status: "completed",
      finalText: "pong",
      runId: "run_workbench_read_model",
      commandId: "cmd_workbench_read_model",
      lastProjectedSeq: 2
    }],
    queries: factQueries
  });
  const server = createCloudApiServer({ accessController, traceStore, runtimeStore, codeAgentChatResults: results });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));

  try {
    const { port } = server.address();
    const sessions = await getJson(port, "/v1/workbench/sessions");
    assert.equal(sessions.status, 200);
    assert.equal(sessions.body.contractVersion, "workbench-sessions-v1");
    assert.equal(sessions.body.sessions[0].sessionId, session.id);
    assert.equal(sessions.body.sessions[0].turnSummary.traceId, traceId);
    assert.equal(sessions.body.sessions[0].turnSummary.lastEventAt, "2026-06-17T00:00:03.000Z");
    assert.equal(sessions.body.sessions[0].turnSummary.durationMs, 3000);
    assert.equal(factQueries[0].projectId, undefined);
    assert.equal(factQueries[0].limit, 21);
    assert.equal(factQueries[0].sessionsOrder, "updated_desc");
    assert.deepEqual(factQueries[0].families, ["sessions"]);
    assert.deepEqual(factQueries[1].families, ["messages", "parts", "turns"]);
    assert.deepEqual(factQueries[1].sessionIds, [session.id]);
    assert.deepEqual(factQueries[2].families, ["checkpoints"]);
    assert.deepEqual(factQueries[2].traceIds, [traceId]);

    const staleProject = await getJson(port, `/v1/workbench/sessions?projectId=prj_stale_filter&includeSessionId=${encodeURIComponent(session.id)}`);
    assert.equal(staleProject.status, 400);
    assert.equal(staleProject.body.error.code, "workbench_authority_removed");
    assert.equal(factQueries.length, 3);

    const detail = await getJson(port, `/v1/workbench/sessions/${encodeURIComponent(session.id)}?includeMessages=false`);
    assert.equal(detail.status, 200);
    assert.equal(detail.body.detailMode, "metadata-only");
    assert.equal(detail.body.messagesIncluded, false);
    assert.equal(detail.body.session.sessionId, session.id);
    assert.equal(detail.body.session.messagePageUrl, `/v1/workbench/sessions/${encodeURIComponent(session.id)}/messages`);
    assert.equal(detail.body.session.messageCount, 2);
    assert.equal(Object.hasOwn(detail.body.session, "messages"), false);

    const fullDetail = await getJson(port, `/v1/workbench/sessions/${encodeURIComponent(session.id)}?includeMessages=true`);
    assert.equal(fullDetail.status, 200);
    assert.equal(fullDetail.body.detailMode, "full");
    assert.equal(fullDetail.body.messagesIncluded, true);
    assert.equal(fullDetail.body.session.messages.length, 2);

    const messages = await getJson(port, `/v1/workbench/sessions/${encodeURIComponent(session.id)}/messages?limit=1`);
    assert.equal(messages.status, 200);
    assert.equal(messages.body.contractVersion, "workbench-message-page-v1");
    assert.equal(messages.body.messages.length, 1);
    assert.equal(messages.body.messages[0].role, "user");
    assert.equal(messages.body.messages[0].text, "ping");
    assert.equal(messages.body.messages[0].parts[0].text, "ping");
    assert.equal(messages.body.hasMore, true);

    const allMessages = await getJson(port, `/v1/workbench/sessions/${encodeURIComponent(session.id)}/messages?limit=2`);
    const agentMessage = allMessages.body.messages.find((message) => message.role !== "user");
    assert.equal(agentMessage.lastEventAt, "2026-06-17T00:00:03.000Z");
    assert.equal(agentMessage.durationMs, 3000);

    const turn = await getJson(port, `/v1/workbench/turns/${encodeURIComponent(traceId)}`);
    assert.equal(turn.status, 200);
    assert.equal(turn.body.contractVersion, "workbench-turn-snapshot-v1");
    assert.equal(turn.body.turn.traceId, traceId);
    assert.equal(turn.body.turn.userMessageId, messages.body.messages[0].messageId);
    assert.equal(turn.body.turn.terminal, true);
    assert.equal(turn.body.turn.startedAt, "2026-06-17T00:00:00.000Z");
    assert.equal(turn.body.turn.finishedAt, "2026-06-17T00:00:03.000Z");
    assert.equal(turn.body.turn.durationMs, 3000);

    const trace = await getJson(port, `/v1/workbench/traces/${encodeURIComponent(traceId)}/events?limit=1`);
    assert.equal(trace.status, 200);
    assert.equal(trace.body.contractVersion, "workbench-trace-events-v1");
    assert.equal(trace.body.events.length, 1);
    assert.equal(trace.body.hasMore, true);
    assert.equal(trace.body.durationMs, 3000);

    const emptyTracePage = await getJson(port, `/v1/workbench/traces/${encodeURIComponent(traceId)}/events?afterProjectedSeq=99`);
    assert.equal(emptyTracePage.status, 200);
    assert.equal(emptyTracePage.body.events.length, 0);
    assert.equal(emptyTracePage.body.range.fromProjectedSeq, null);
    assert.equal(emptyTracePage.body.range.toProjectedSeq, null);
    assert.equal(emptyTracePage.body.nextProjectedSeq, 99);

    assert.deepEqual(writes, { recordAgentSessionOwner: 0, updateWorkspace: 0 });
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench session messages use turn timeline instead of clustered projection writes", async () => {
  const traceIds = Array.from({ length: 5 }, (_, index) => `trc_workbench_timeline_cluster_${index + 1}`);
  const session = {
    id: "ses_workbench_timeline_cluster",
    projectId: "prj_hwpod_workbench",
    agentId: "hwlab-code-agent",
    status: "completed",
    startedAt: "2026-06-27T00:00:00.000Z",
    endedAt: "2026-06-27T00:10:00.000Z",
    ownerUserId: ACTOR.id,
    conversationId: "cnv_workbench_timeline_cluster",
    threadId: "thread-workbench-timeline-cluster",
    lastTraceId: traceIds.at(-1),
    updatedAt: "2026-06-27T00:10:00.000Z",
    session: {
      sessionStatus: "completed",
      messages: traceIds.flatMap((traceId, index) => {
        const turn = index + 1;
        return [
          { role: "user", text: `sentinel-${turn}`, traceId, turnId: traceId, projectedSeq: turn, sourceSeq: turn, status: "sent", createdAt: `2026-06-27T00:0${index}:00.000Z` },
          { role: "agent", text: `ok-${turn}`, traceId, turnId: traceId, projectedSeq: 100 + turn, sourceSeq: 100 + turn, status: "completed", createdAt: `2026-06-27T00:0${index}:30.000Z` }
        ];
      }),
      valuesRedacted: true,
      secretMaterialStored: false
    }
  };
  const runtimeStore = createDurableFactsRuntimeStore({ sessions: [{ session, status: "completed", lastProjectedSeq: 105 }] });
  const accessController = {
    async ensureBootstrap() {},
    async authenticate() { return { ok: true, actor: ACTOR, session: { id: "uss_workbench_reader" } }; }
  };
  const server = createCloudApiServer({ accessController, runtimeStore });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));

  try {
    const { port } = server.address();
    const messages = await getJson(port, `/v1/workbench/sessions/${encodeURIComponent(session.id)}/messages?limit=20`);
    assert.equal(messages.status, 200);
    assert.deepEqual(messages.body.messages.map((message) => message.role), ["user", "agent", "user", "agent", "user", "agent", "user", "agent", "user", "agent"]);
    assert.deepEqual(messages.body.messages.map((message) => message.text), ["sentinel-1", "ok-1", "sentinel-2", "ok-2", "sentinel-3", "ok-3", "sentinel-4", "ok-4", "sentinel-5", "ok-5"]);

    const sessions = await getJson(port, `/v1/workbench/sessions?includeSessionId=${encodeURIComponent(session.id)}`);
    assert.equal(sessions.status, 200);
    assert.equal(sessions.body.sessions[0].title, "sentinel-5");
    assert.equal(sessions.body.sessions[0].preview, "ok-5");
    assert.equal(sessions.body.sessions[0].titleSource, "message-projection");
    assert.equal(sessions.body.sessions[0].previewSource, "message-projection");
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench session messages use prompt admission time when turn seq is stale", async () => {
  const sessionId = "ses_workbench_timeline_stale_turn_seq";
  const turnRows = [
    { traceId: "trc_stale_turn_01", turnSeq: 1, at: "2026-06-28T08:40:42.000Z", prompt: "sentinel-01", reply: "ok-01" },
    { traceId: "trc_stale_turn_02", turnSeq: 3, at: "2026-06-28T08:43:51.000Z", prompt: "sentinel-02", reply: "ok-02" },
    { traceId: "trc_stale_turn_03", turnSeq: 2, at: "2026-06-28T08:44:07.000Z", prompt: "sentinel-03", reply: "ok-03" },
    { traceId: "trc_stale_turn_04", turnSeq: 5, at: "2026-06-28T08:44:27.000Z", prompt: "sentinel-04", reply: "ok-04" },
    { traceId: "trc_stale_turn_05", turnSeq: 4, at: "2026-06-28T08:44:44.000Z", prompt: "sentinel-05", reply: "ok-05" }
  ];
  const facts = emptyFacts();
  facts.sessions.push({
    sessionId,
    ownerUserId: ACTOR.id,
    ownerRole: ACTOR.role,
    agentId: "hwlab-code-agent",
    status: "completed",
    lastTraceId: turnRows.at(-1).traceId,
    projectedSeq: 99,
    sourceSeq: 99,
    terminal: true,
    sealed: true,
    createdAt: turnRows[0].at,
    updatedAt: "2026-06-28T08:45:00.000Z",
    valuesRedacted: true
  });
  turnRows.forEach((row, index) => {
    const userMessageId = `msg_${row.traceId}_user`;
    const agentMessageId = `msg_${row.traceId}_agent`;
    const finishedAt = new Date(Date.parse(row.at) + 5000).toISOString();
    facts.messages.push(
      { messageId: userMessageId, sessionId, turnId: row.traceId, traceId: row.traceId, role: "user", status: "sent", text: row.prompt, projectedSeq: 100 + index, sourceSeq: 100 + index, createdAt: row.at, updatedAt: row.at, valuesRedacted: true },
      { messageId: agentMessageId, sessionId, turnId: row.traceId, traceId: row.traceId, role: "agent", status: "completed", text: row.reply, projectedSeq: 200 + index, sourceSeq: 200 + index, terminal: true, sealed: true, createdAt: finishedAt, updatedAt: finishedAt, timing: { startedAt: row.at, lastEventAt: finishedAt, finishedAt, durationMs: 5000, valuesRedacted: true }, valuesRedacted: true }
    );
    facts.parts.push(
      { partId: `prt_${row.traceId}_user`, messageId: userMessageId, sessionId, turnId: row.traceId, traceId: row.traceId, partIndex: 0, partType: "text", status: "sent", text: row.prompt, projectedSeq: 100 + index, updatedAt: row.at, valuesRedacted: true },
      { partId: `prt_${row.traceId}_agent`, messageId: agentMessageId, sessionId, turnId: row.traceId, traceId: row.traceId, partIndex: 0, partType: "final_response", status: "completed", text: row.reply, projectedSeq: 200 + index, terminal: true, sealed: true, updatedAt: finishedAt, valuesRedacted: true }
    );
    facts.turns.push({ turnId: row.traceId, sessionId, traceId: row.traceId, messageId: agentMessageId, status: "completed", projectedSeq: row.turnSeq, sourceSeq: row.turnSeq, terminal: true, sealed: true, startedAt: row.at, lastEventAt: finishedAt, finishedAt, durationMs: 5000, updatedAt: finishedAt, valuesRedacted: true });
    facts.checkpoints.push({ traceId: row.traceId, sessionId, turnId: row.traceId, status: "completed", projectionStatus: "caught_up", projectionHealth: "caught-up", projectedSeq: row.turnSeq, sourceSeq: row.turnSeq, terminal: true, sealed: true, startedAt: row.at, lastEventAt: finishedAt, finishedAt, durationMs: 5000, updatedAt: finishedAt, valuesRedacted: true });
  });
  const runtimeStore = createRuntimeStoreFromFacts(facts);
  const accessController = {
    async ensureBootstrap() {},
    async authenticate() { return { ok: true, actor: ACTOR, session: { id: "uss_workbench_reader" } }; }
  };
  const server = createCloudApiServer({ accessController, runtimeStore });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));

  try {
    const { port } = server.address();
    const messages = await getJson(port, `/v1/workbench/sessions/${encodeURIComponent(sessionId)}/messages?limit=20`);
    assert.equal(messages.status, 200);
    assert.deepEqual(messages.body.messages.map((message) => message.text), ["sentinel-01", "ok-01", "sentinel-02", "ok-02", "sentinel-03", "ok-03", "sentinel-04", "ok-04", "sentinel-05", "ok-05"]);
    assert.equal(messages.body.roleSequencePrefix, "UAUAUAUAUA");
    assert.equal(messages.body.adjacentSameRoleCount, 0);
    assert.match(messages.body.timelineDigest, /^sha256:[0-9a-f]{64}$/u);

    const sessions = await getJson(port, `/v1/workbench/sessions?includeSessionId=${encodeURIComponent(sessionId)}`);
    assert.equal(sessions.status, 200);
    assert.equal(sessions.body.sessions[0].title, "sentinel-05");
    assert.equal(sessions.body.sessions[0].preview, "ok-05");
    assert.equal(sessions.body.sessions[0].firstUserMessagePreview, "sentinel-01");
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench session list hides shadowed AgentRun alias rows", async () => {
  const sessionId = "ses_fake_echo_canonical";
  const aliasSessionId = "ses_agentrun_fake_echo_canonical";
  const traceId = "trc_fake_echo_alias";
  const facts = emptyFacts();
  facts.sessions.push(
    { sessionId: aliasSessionId, ownerUserId: ACTOR.id, ownerRole: ACTOR.role, status: "failed", lastTraceId: traceId, projectedSeq: 20, sourceSeq: 20, updatedAt: "2026-06-28T08:52:18.000Z", valuesRedacted: true },
    { sessionId, ownerUserId: ACTOR.id, ownerRole: ACTOR.role, status: "completed", lastTraceId: traceId, projectedSeq: 10, sourceSeq: 10, updatedAt: "2026-06-28T08:52:10.000Z", valuesRedacted: true }
  );
  facts.messages.push(
    { messageId: "msg_fake_user", sessionId, turnId: traceId, traceId, role: "user", status: "sent", text: "ECHO sentinel-01", projectedSeq: 1, sourceSeq: 1, createdAt: "2026-06-28T08:52:00.000Z", updatedAt: "2026-06-28T08:52:00.000Z", valuesRedacted: true },
    { messageId: "msg_fake_agent", sessionId, turnId: traceId, traceId, role: "agent", status: "completed", text: "ECHO sentinel-01 ok", projectedSeq: 2, sourceSeq: 2, terminal: true, sealed: true, createdAt: "2026-06-28T08:52:05.000Z", updatedAt: "2026-06-28T08:52:05.000Z", valuesRedacted: true }
  );
  facts.parts.push(
    { partId: "prt_fake_user", messageId: "msg_fake_user", sessionId, turnId: traceId, traceId, partIndex: 0, partType: "text", status: "sent", text: "ECHO sentinel-01", projectedSeq: 1, valuesRedacted: true },
    { partId: "prt_fake_agent", messageId: "msg_fake_agent", sessionId, turnId: traceId, traceId, partIndex: 0, partType: "final_response", status: "completed", text: "ECHO sentinel-01 ok", projectedSeq: 2, terminal: true, sealed: true, valuesRedacted: true }
  );
  const runtimeStore = createRuntimeStoreFromFacts(facts);
  const accessController = {
    async ensureBootstrap() {},
    async authenticate() { return { ok: true, actor: ACTOR, session: { id: "uss_workbench_reader" } }; }
  };
  const server = createCloudApiServer({ accessController, runtimeStore });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));

  try {
    const { port } = server.address();
    const sessions = await getJson(port, "/v1/workbench/sessions?limit=10");
    assert.equal(sessions.status, 200);
    assert.deepEqual(sessions.body.sessions.map((session) => session.sessionId), [sessionId]);
    assert.equal(sessions.body.sessions[0].title, "ECHO sentinel-01");
    assert.equal(sessions.body.sessions[0].preview, "ECHO sentinel-01 ok");
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench trace event continuation returns empty catch-up page instead of 404", async () => {
  const traceId = "trc_workbench_trace_events_catchup";
  const session = {
    id: "ses_workbench_trace_events_catchup",
    projectId: "prj_hwpod_workbench",
    agentId: "hwlab-code-agent",
    status: "running",
    startedAt: "2026-06-27T01:00:00.000Z",
    ownerUserId: ACTOR.id,
    conversationId: "cnv_workbench_trace_events_catchup",
    threadId: "thread-workbench-trace-events-catchup",
    lastTraceId: traceId,
    updatedAt: "2026-06-27T01:00:05.000Z",
    session: {
      sessionStatus: "running",
      messages: [
        { role: "user", text: "start", traceId, createdAt: "2026-06-27T01:00:00.000Z" }
      ],
      valuesRedacted: true,
      secretMaterialStored: false
    }
  };
  const runtimeStore = createDurableFactsRuntimeStore({
    sessions: [{
      session,
      status: "running",
      projectionStatus: "projecting",
      projectionHealth: "projecting",
      lastProjectedSeq: 5,
      events: [
        { projectedSeq: 1, sourceSeq: 1, type: "request", status: "accepted", label: "request:accepted", createdAt: "2026-06-27T01:00:00.000Z" },
        { projectedSeq: 2, sourceSeq: 2, type: "tool", status: "running", label: "tool:running", createdAt: "2026-06-27T01:00:05.000Z" }
      ]
    }]
  });
  const accessController = {
    async ensureBootstrap() {},
    async authenticate() { return { ok: true, actor: ACTOR, session: { id: "uss_workbench_reader" } }; }
  };
  const server = createCloudApiServer({ accessController, runtimeStore });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));

  try {
    const { port } = server.address();
    const response = await getJson(port, `/v1/workbench/traces/${encodeURIComponent(traceId)}/events?afterProjectedSeq=2&limit=100`);
    assert.equal(response.status, 200);
    assert.equal(response.body.status, "projecting");
    assert.deepEqual(response.body.events, []);
    assert.equal(response.body.range.fromProjectedSeq, null);
    assert.equal(response.body.range.toProjectedSeq, null);
    assert.equal(response.body.range.total, 5);
    assert.equal(response.body.nextProjectedSeq, 2);
    assert.equal(response.body.traceLastSeq, 5);
    assert.equal(response.body.fullTraceLoaded, false);
    assert.equal(response.body.projectionStatus, "projecting");
    assert.equal(response.body.projectionHealth, "projecting");
    assert.equal(response.body.diagnostic.code, "workbench_trace_events_missing");
    assert.equal(response.body.blocker, null);
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench terminal timing ignores late projection updatedAt after sealed result (#2132)", async () => {
  const traceId = "trc_workbench_terminal_late_projection_update";
  const session = {
    id: "ses_workbench_terminal_late_projection_update",
    projectId: "prj_hwpod_workbench",
    agentId: "hwlab-code-agent",
    status: "completed",
    startedAt: "2026-06-25T23:07:10.153Z",
    ownerUserId: ACTOR.id,
    conversationId: "cnv_workbench_terminal_late_projection_update",
    threadId: "thread-workbench-terminal-late-projection-update",
    lastTraceId: traceId,
    updatedAt: "2026-06-25T23:08:27.097Z",
    session: {
      sessionStatus: "completed",
      lastTraceId: traceId,
      finalResponse: "P5 durable facts smoke OK",
      messages: [
        { role: "user", text: "请直接回答：P5 durable facts smoke OK", traceId, status: "sent", createdAt: "2026-06-25T23:07:10.153Z" },
        { role: "agent", text: "P5 durable facts smoke OK", traceId, status: "completed", createdAt: "2026-06-25T23:07:10.976Z", updatedAt: "2026-06-25T23:08:27.097Z" }
      ],
      valuesRedacted: true,
      secretMaterialStored: false
    }
  };
  const facts = emptyFacts();
  mergeFacts(facts, buildDurableFactsForSession({
    session,
    status: "completed",
    finalText: "P5 durable facts smoke OK",
    events: [
      { projectedSeq: 1, sourceSeq: 1, type: "backend", status: "running", label: "agentrun:backend:admitted", createdAt: "2026-06-25T23:07:10.153Z", updatedAt: "2026-06-25T23:07:10.153Z" },
      { projectedSeq: 31, sourceSeq: 31, type: "terminal", eventType: "terminal", status: "completed", label: "agentrun:result:completed", terminal: true, createdAt: "2026-06-25T23:07:32.418Z", occurredAt: "2026-06-25T23:07:32.418Z", updatedAt: "2026-06-25T23:08:27.127Z" }
    ],
    lastProjectedSeq: 31
  }));
  const checkpointTiming = {
    startedAt: "2026-06-25T23:07:10.153Z",
    lastEventAt: "2026-06-25T23:07:32.418Z",
    finishedAt: "2026-06-25T23:07:32.418Z",
    durationMs: 22265,
    valuesRedacted: true
  };
  const turnTiming = {
    startedAt: "2026-06-25T23:07:10.976Z",
    lastEventAt: "2026-06-25T23:08:27.097Z",
    finishedAt: "2026-06-25T23:07:32.418Z",
    durationMs: 21442,
    valuesRedacted: true
  };
  for (const record of [...facts.sessions, ...facts.messages.filter((message) => message.role !== "user"), ...facts.turns]) {
    Object.assign(record, turnTiming, { timing: turnTiming, updatedAt: "2026-06-25T23:08:27.097Z" });
  }
  Object.assign(facts.checkpoints[0], checkpointTiming, { timing: checkpointTiming, updatedAt: "2026-06-25T23:08:27.127Z" });
  Object.assign(facts.traceEvents.at(-1), checkpointTiming, { timing: checkpointTiming, updatedAt: "2026-06-25T23:08:27.127Z" });
  const runtimeStore = {
    async queryWorkbenchFacts(params = {}) {
      const filtered = filterFacts(facts, params);
      return {
        facts: filtered,
        count: Object.values(filtered).reduce((sum, rows) => sum + rows.length, 0),
        persistence: { adapter: "test-durable-workbench-facts", durable: true }
      };
    }
  };
  const accessController = {
    async ensureBootstrap() {},
    async authenticate() { return { ok: true, actor: ACTOR, session: { id: "uss_workbench_reader" } }; }
  };
  const server = createCloudApiServer({ accessController, workbenchRuntime: runtimeStore, codeAgentChatResults: createCodeAgentChatResultStore() });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));

  try {
    const { port } = server.address();
    const turn = await getJson(port, `/v1/workbench/turns/${encodeURIComponent(traceId)}`);
    assert.equal(turn.status, 200);
    assert.equal(turn.body.turn.startedAt, "2026-06-25T23:07:10.153Z");
    assert.equal(turn.body.turn.lastEventAt, "2026-06-25T23:07:32.418Z");
    assert.equal(turn.body.turn.finishedAt, "2026-06-25T23:07:32.418Z");
    assert.equal(turn.body.turn.durationMs, 22265);
    assert.equal(turn.body.turn.trace.updatedAt, "2026-06-25T23:08:27.127Z");

    const trace = await getJson(port, `/v1/workbench/traces/${encodeURIComponent(traceId)}/events?limit=80`);
    assert.equal(trace.status, 200);
    assert.equal(trace.body.lastEventAt, "2026-06-25T23:07:32.418Z");
    assert.equal(trace.body.finishedAt, "2026-06-25T23:07:32.418Z");
    assert.equal(trace.body.durationMs, 22265);
    assert.equal(trace.body.events.at(-1).updatedAt, "2026-06-25T23:08:27.127Z");
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench read model recovers trace events from durable projection without duplicating final response", async () => {
  const traceStore = createCodeAgentTraceStore();
  const traceId = "trc_workbench_durable_reload";
  const session = {
    id: "ses_workbench_durable_reload",
    projectId: "prj_hwpod_workbench",
    agentId: "hwlab-code-agent",
    status: "completed",
    startedAt: "2026-06-17T01:00:00.000Z",
    endedAt: "2026-06-17T01:00:04.000Z",
    ownerUserId: ACTOR.id,
    conversationId: "cnv_workbench_durable_reload",
    threadId: "thread-workbench-durable-reload",
    lastTraceId: traceId,
    updatedAt: "2026-06-17T01:00:04.000Z",
    session: {
      sessionStatus: "completed",
      lastTraceId: traceId,
      finalResponse: "durable pong",
      messages: [
        { role: "user", text: "durable ping", traceId, createdAt: "2026-06-17T01:00:00.000Z" },
        { role: "agent", text: "durable pong", traceId, createdAt: "2026-06-17T01:00:04.000Z" }
      ],
      valuesRedacted: true,
      secretMaterialStored: false
    }
  };
  const durableEvents = [
    { traceId, seq: 1, type: "request", status: "accepted", label: "request:accepted", createdAt: "2026-06-17T01:00:00.000Z", valuesPrinted: false },
    { traceId, seq: 2, type: "result", status: "completed", label: "result:completed", terminal: true, createdAt: "2026-06-17T01:00:04.000Z", valuesPrinted: false }
  ];
  const accessController = {
    store: {
      async getAgentSession(sessionId) { return sessionId === session.id ? session : null; },
      async getAgentSessionByTraceId(requestTraceId) { return requestTraceId === traceId ? session : null; }
    },
    async ensureBootstrap() {},
    async authenticate() { return { ok: true, actor: ACTOR, session: { id: "uss_workbench_reader" } }; }
  };
  const factQueries = [];
  const runtimeStore = createDurableFactsRuntimeStore({
    sessions: [{ session, events: durableEvents, status: "completed", finalText: "durable pong", lastProjectedSeq: 2 }],
    queries: factQueries
  });
  const server = createCloudApiServer({ accessController, traceStore, runtimeStore, codeAgentChatResults: createCodeAgentChatResultStore() });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));

  try {
    const { port } = server.address();
    const messages = await getJson(port, `/v1/workbench/sessions/${encodeURIComponent(session.id)}/messages?limit=10`);
    assert.equal(messages.status, 200);
    assert.equal(messages.body.total, 2);
    assert.deepEqual(messages.body.messages.map((message) => message.role), ["user", "agent"]);

    const turn = await getJson(port, `/v1/workbench/turns/${encodeURIComponent(traceId)}`);
    assert.equal(turn.status, 200);
    assert.equal(turn.body.turn.assistantMessageId, messages.body.messages[1].messageId);
    assert.equal(turn.body.turn.trace.eventCount, 2);
    assert.equal(turn.body.turn.trace.status, "completed");

    const trace = await getJson(port, `/v1/workbench/traces/${encodeURIComponent(traceId)}/events?limit=10`);
    assert.equal(trace.status, 200);
    assert.equal(trace.body.events.length, 2);
    assert.equal(trace.body.traceStatus, "completed");
    assert.equal(trace.body.hasMore, false);
    assert.ok(factQueries.length >= 3);
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench read model canonicalizes duplicate assistant facts for the same trace", async () => {
  const traceStore = createCodeAgentTraceStore();
  const traceId = "trc_workbench_duplicate_agent_message";
  const preferredMessageId = "msg_workbench_duplicate_agent_message_agent";
  const duplicateMessageId = "msg_3ae9b11ac11c2dcf816980916f1152b0";
  const finalText = "duplicate history final response";
  const session = {
    id: "ses_workbench_duplicate_agent_message",
    projectId: "prj_hwpod_workbench",
    agentId: "hwlab-code-agent",
    status: "completed",
    startedAt: "2026-06-17T01:20:00.000Z",
    endedAt: "2026-06-17T01:20:16.000Z",
    ownerUserId: ACTOR.id,
    conversationId: "cnv_workbench_duplicate_agent_message",
    threadId: "thread-workbench-duplicate-agent-message",
    lastTraceId: traceId,
    updatedAt: "2026-06-17T01:20:16.000Z",
    session: {
      sessionStatus: "completed",
      lastTraceId: traceId,
      messages: [
        { messageId: "msg_workbench_duplicate_agent_message_user", role: "user", text: "duplicate ping", traceId, createdAt: "2026-06-17T01:20:00.000Z" },
        { messageId: preferredMessageId, role: "agent", text: "", traceId, status: "completed", createdAt: "2026-06-17T01:20:16.000Z", updatedAt: "2026-06-17T01:20:16.000Z" }
      ],
      valuesRedacted: true,
      secretMaterialStored: false
    }
  };
  const facts = buildDurableFactsForSession({
    session,
    events: [
      { seq: 1, type: "request", status: "accepted", label: "request:accepted", createdAt: "2026-06-17T01:20:00.000Z" },
      { seq: 2, type: "result", status: "completed", label: "result:completed", terminal: true, createdAt: "2026-06-17T01:20:16.000Z" }
    ],
    status: "completed",
    lastProjectedSeq: 2
  });
  facts.turns[0].messageId = duplicateMessageId;
  facts.messages.push({
    messageId: duplicateMessageId,
    sessionId: session.id,
    turnId: traceId,
    traceId,
    role: "agent",
    status: "completed",
    projectedSeq: 9,
    sourceSeq: 9,
    sourceEventId: `${session.id}:message:duplicate-hash`,
    terminal: true,
    sealed: true,
    text: finalText,
    timing: {
      startedAt: "2026-06-17T01:20:00.000Z",
      lastEventAt: "2026-06-17T01:21:48.722Z",
      finishedAt: "2026-06-17T01:21:48.722Z",
      durationMs: 108722,
      status: "completed",
      valuesRedacted: true
    },
    startedAt: "2026-06-17T01:20:00.000Z",
    lastEventAt: "2026-06-17T01:21:48.722Z",
    finishedAt: "2026-06-17T01:21:48.722Z",
    durationMs: 108722,
    createdAt: "2026-06-17T01:20:16.000Z",
    updatedAt: "2026-06-17T01:21:48.722Z",
    valuesRedacted: true
  });
  facts.parts.push({
    partId: "prt_workbench_duplicate_agent_message_hash_final",
    messageId: duplicateMessageId,
    sessionId: session.id,
    turnId: traceId,
    traceId,
    partIndex: 0,
    partType: "final_response",
    status: "completed",
    text: finalText,
    projectedSeq: 9,
    sourceSeq: 9,
    sourceEventId: `${session.id}:part:duplicate-hash`,
    terminal: true,
    sealed: true,
    updatedAt: "2026-06-17T01:21:48.722Z",
    valuesRedacted: true
  });
  const runtimeStore = {
    async queryWorkbenchFacts(params = {}) {
      const filtered = filterFacts(facts, params);
      return {
        facts: filtered,
        count: Object.values(filtered).reduce((sum, rows) => sum + rows.length, 0),
        persistence: { adapter: "test-durable-workbench-facts", durable: true }
      };
    }
  };
  const accessController = {
    store: {
      async getAgentSession(sessionId) { return sessionId === session.id ? session : null; },
      async getAgentSessionByTraceId(requestTraceId) { return requestTraceId === traceId ? session : null; }
    },
    async ensureBootstrap() {},
    async authenticate() { return { ok: true, actor: ACTOR, session: { id: "uss_workbench_reader" } }; }
  };
  const server = createCloudApiServer({ accessController, traceStore, workbenchRuntime: runtimeStore, codeAgentChatResults: createCodeAgentChatResultStore() });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));

  try {
    const { port } = server.address();
    const messages = await getJson(port, `/v1/workbench/sessions/${encodeURIComponent(session.id)}/messages?limit=10`);
    assert.equal(messages.status, 200);
    assert.equal(messages.body.total, 2);
    assert.deepEqual(messages.body.messages.map((message) => message.role), ["user", "agent"]);
    const assistant = messages.body.messages[1];
    assert.equal(assistant.messageId, preferredMessageId);
    assert.equal(assistant.text, finalText);
    assert.equal(assistant.parts.length, 1);
    assert.equal(assistant.parts[0].messageId, preferredMessageId);
    assert.equal(assistant.parts[0].type, "final_response");
    assert.equal(assistant.parts[0].text, finalText);
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench read model projects terminal result atomically across session, messages, and turn", async () => {
  const traceStore = createCodeAgentTraceStore();
  const results = createCodeAgentChatResultStore();
  const traceId = "trc_workbench_terminal_atomic";
  const finalText = "terminal projection final response";
  const session = {
    id: "ses_workbench_terminal_atomic",
    projectId: "prj_hwpod_workbench",
    agentId: "hwlab-code-agent",
    status: "running",
    ownerUserId: ACTOR.id,
    conversationId: "cnv_workbench_terminal_atomic",
    threadId: "thread-workbench-terminal-atomic",
    lastTraceId: traceId,
    updatedAt: "2026-06-17T02:10:00.000Z",
    session: {
      sessionStatus: "running",
      lastTraceId: traceId,
      messages: [
        { role: "user", text: "terminal atomic ping", traceId, status: "sent", createdAt: "2026-06-17T02:09:58.000Z" },
        { role: "agent", text: "", traceId, status: "running", createdAt: "2026-06-17T02:09:59.000Z" }
      ],
      valuesRedacted: true,
      secretMaterialStored: false
    }
  };
  traceStore.append(traceId, { seq: 1, type: "request", status: "accepted", label: "request:accepted" });
  traceStore.append(traceId, { seq: 2, type: "result", status: "completed", label: "result:completed", terminal: true });
  results.set(traceId, {
    status: "completed",
    traceId,
    ownerUserId: ACTOR.id,
    conversationId: session.conversationId,
    sessionId: session.id,
    threadId: session.threadId,
    finalResponse: { text: finalText, status: "completed", traceId },
    agentRun: { runId: "run_workbench_terminal_atomic", commandId: "cmd_workbench_terminal_atomic", status: "completed", terminalStatus: "completed" }
  });
  const accessController = {
    store: {
      async listAgentSessionsForUser() { return [session]; },
      async getAgentSession(sessionId) { return sessionId === session.id ? session : null; },
      async getAgentSessionByTraceId(requestTraceId) { return requestTraceId === traceId ? session : null; }
    },
    async ensureBootstrap() {},
    async authenticate() { return { ok: true, actor: ACTOR, session: { id: "uss_workbench_reader" } }; }
  };
  const runtimeStore = createDurableFactsRuntimeStore({
    sessions: [{
      session,
      events: [
        { seq: 1, type: "request", status: "accepted", label: "request:accepted", createdAt: "2026-06-17T02:09:58.000Z" },
        { seq: 2, type: "result", status: "completed", label: "result:completed", terminal: true, createdAt: "2026-06-17T02:10:00.000Z" }
      ],
      status: "completed",
      finalText,
      runId: "run_workbench_terminal_atomic",
      commandId: "cmd_workbench_terminal_atomic",
      lastProjectedSeq: 2
    }]
  });
  const server = createCloudApiServer({ accessController, traceStore, runtimeStore, codeAgentChatResults: results });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));

  try {
    const { port } = server.address();
    const sessions = await getJson(port, `/v1/workbench/sessions?includeSessionId=${encodeURIComponent(session.id)}`);
    assert.equal(sessions.status, 200);
    assert.equal(sessions.body.sessions[0].status, "completed");
    assert.equal(sessions.body.sessions[0].running, false);
    assert.equal(sessions.body.sessions[0].terminal, true);

    const messages = await getJson(port, `/v1/workbench/sessions/${encodeURIComponent(session.id)}/messages?limit=10`);
    assert.equal(messages.status, 200);
    assert.equal(messages.body.total, 2);
    const assistant = messages.body.messages[1];
    assert.equal(assistant.role, "agent");
    assert.equal(assistant.status, "completed");
    assert.equal(assistant.text, finalText);
    assert.equal(assistant.parts[0].status, "completed");
    assert.equal(assistant.parts[0].type, "final_response");
    assert.equal(assistant.parts[0].messageId, assistant.messageId);
    assert.equal(assistant.parts[0].traceId, traceId);
    assert.equal(assistant.parts[0].text, finalText);

    const turn = await getJson(port, `/v1/workbench/turns/${encodeURIComponent(traceId)}`);
    assert.equal(turn.status, 200);
    assert.equal(turn.body.turn.status, "completed");
    assert.equal(turn.body.turn.terminal, true);
    assert.equal(turn.body.turn.assistantMessageId, assistant.messageId);
    assert.equal(turn.body.turn.assistantText, finalText);
    assert.equal(turn.body.turn.finalResponse.source, "message-part");
    assert.equal(turn.body.turn.finalResponse.sealed, true);
    assert.equal(turn.body.turn.finalResponse.text, finalText);
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench read model seals turn from terminal message projection when checkpoint is stale", async () => {
  const sessionId = "ses_workbench_terminal_message_stale_checkpoint";
  const traceId = "trc_terminal_message_stale_checkpoint";
  const finalText = "terminal message projection must close the visible turn";
  const startedAt = "2026-06-29T22:09:32.000Z";
  const finishedAt = "2026-06-29T22:09:47.000Z";
  const facts = emptyFacts();
  facts.sessions.push({
    sessionId,
    ownerUserId: ACTOR.id,
    ownerRole: ACTOR.role,
    agentId: "hwlab-code-agent",
    status: "running",
    lastTraceId: traceId,
    projectedSeq: 33,
    sourceSeq: 36,
    createdAt: startedAt,
    updatedAt: finishedAt,
    valuesRedacted: true
  });
  facts.messages.push(
    { messageId: "msg_stale_user", sessionId, turnId: traceId, traceId, role: "user", status: "sent", text: "hi", projectedSeq: 31, sourceSeq: 31, createdAt: startedAt, updatedAt: startedAt, valuesRedacted: true },
    { messageId: "msg_stale_agent", sessionId, turnId: traceId, traceId, role: "agent", status: "completed", text: finalText, projectedSeq: 36, sourceSeq: 36, terminal: true, sealed: true, timing: { startedAt, lastEventAt: finishedAt, finishedAt, durationMs: 15000, valuesRedacted: true }, startedAt, lastEventAt: finishedAt, finishedAt, durationMs: 15000, createdAt: startedAt, updatedAt: finishedAt, valuesRedacted: true }
  );
  facts.parts.push(
    { partId: "prt_stale_user", messageId: "msg_stale_user", sessionId, turnId: traceId, traceId, partIndex: 0, partType: "text", status: "sent", text: "hi", projectedSeq: 31, sourceSeq: 31, updatedAt: startedAt, valuesRedacted: true },
    { partId: "prt_stale_agent", messageId: "msg_stale_agent", sessionId, turnId: traceId, traceId, partIndex: 0, partType: "final_response", status: "completed", text: finalText, projectedSeq: 36, sourceSeq: 36, terminal: true, sealed: true, updatedAt: finishedAt, valuesRedacted: true }
  );
  facts.turns.push({ turnId: traceId, sessionId, traceId, messageId: "msg_stale_agent", status: "running", projectedSeq: 33, sourceSeq: 33, terminal: false, sealed: false, startedAt, lastEventAt: finishedAt, updatedAt: finishedAt, valuesRedacted: true });
  facts.checkpoints.push({ traceId, sessionId, turnId: traceId, runId: "run_stale_checkpoint", commandId: "cmd_stale_checkpoint", status: "running", projectionStatus: "projecting", projectionHealth: "degraded", projectedSeq: 33, sourceSeq: 33, terminal: false, sealed: false, startedAt, lastEventAt: finishedAt, updatedAt: finishedAt, valuesRedacted: true });
  const runtimeStore = createRuntimeStoreFromFacts(facts);
  const accessController = {
    async ensureBootstrap() {},
    async authenticate() { return { ok: true, actor: ACTOR, session: { id: "uss_workbench_reader" } }; }
  };
  const server = createCloudApiServer({ accessController, runtimeStore });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));

  try {
    const { port } = server.address();
    const sessions = await getJson(port, `/v1/workbench/sessions?includeSessionId=${encodeURIComponent(sessionId)}`);
    assert.equal(sessions.status, 200);
    assert.equal(sessions.body.sessions[0].status, "completed");
    assert.equal(sessions.body.sessions[0].running, false);
    assert.equal(sessions.body.sessions[0].terminal, true);

    const messages = await getJson(port, `/v1/workbench/sessions/${encodeURIComponent(sessionId)}/messages?limit=10`);
    assert.equal(messages.status, 200);
    const assistant = messages.body.messages.find((message) => message.role === "agent");
    assert.equal(assistant.status, "completed");
    assert.equal(assistant.text, finalText);

    const turn = await getJson(port, `/v1/workbench/turns/${encodeURIComponent(traceId)}`);
    assert.equal(turn.status, 200);
    assert.equal(turn.body.turn.status, "completed");
    assert.equal(turn.body.turn.running, false);
    assert.equal(turn.body.turn.terminal, true);
    assert.equal(turn.body.turn.finalResponse.text, finalText);
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench read model exposes failed terminal body when final response part is missing", async () => {
  const sessionId = "ses_workbench_failed_terminal_missing_part";
  const traceId = "trc_failed_terminal_missing_part";
  const startedAt = "2026-06-29T22:19:32.000Z";
  const finishedAt = "2026-06-29T22:19:47.000Z";
  const facts = emptyFacts();
  facts.sessions.push({
    sessionId,
    ownerUserId: ACTOR.id,
    ownerRole: ACTOR.role,
    agentId: "hwlab-code-agent",
    status: "failed",
    lastTraceId: traceId,
    projectedSeq: 40,
    sourceSeq: 40,
    terminal: true,
    sealed: true,
    createdAt: startedAt,
    updatedAt: finishedAt,
    valuesRedacted: true
  });
  facts.messages.push(
    { messageId: "msg_failed_missing_part_user", sessionId, turnId: traceId, traceId, role: "user", status: "sent", text: "hi", projectedSeq: 39, sourceSeq: 39, createdAt: startedAt, updatedAt: startedAt, valuesRedacted: true },
    { messageId: "msg_failed_missing_part_agent", sessionId, turnId: traceId, traceId, role: "agent", status: "failed", text: "", projectedSeq: 40, sourceSeq: 40, terminal: true, sealed: true, timing: { startedAt, lastEventAt: finishedAt, finishedAt, durationMs: 15000, valuesRedacted: true }, startedAt, lastEventAt: finishedAt, finishedAt, durationMs: 15000, createdAt: startedAt, updatedAt: finishedAt, valuesRedacted: true }
  );
  facts.turns.push({ turnId: traceId, sessionId, traceId, messageId: "msg_failed_missing_part_agent", status: "failed", projectedSeq: 40, sourceSeq: 40, terminal: true, sealed: true, finalResponse: null, diagnostic: { blocker: { code: "provider-stream-disconnected", message: "provider stream disconnected" }, projectionStatus: "caught_up", projectionHealth: "healthy", valuesRedacted: true }, startedAt, lastEventAt: finishedAt, finishedAt, durationMs: 15000, updatedAt: finishedAt, valuesRedacted: true });
  facts.checkpoints.push({ traceId, sessionId, turnId: traceId, runId: "run_failed_missing_part", commandId: "cmd_failed_missing_part", status: "failed", projectionStatus: "caught_up", projectionHealth: "healthy", projectedSeq: 40, sourceSeq: 40, terminal: true, sealed: true, diagnostic: { blocker: { code: "provider-stream-disconnected", message: "provider stream disconnected" }, valuesRedacted: true }, startedAt, lastEventAt: finishedAt, finishedAt, durationMs: 15000, updatedAt: finishedAt, valuesRedacted: true });
  const runtimeStore = createRuntimeStoreFromFacts(facts);
  const accessController = {
    async ensureBootstrap() {},
    async authenticate() { return { ok: true, actor: ACTOR, session: { id: "uss_workbench_reader" } }; }
  };
  const server = createCloudApiServer({ accessController, runtimeStore });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));

  try {
    const { port } = server.address();
    const sessions = await getJson(port, `/v1/workbench/sessions?includeSessionId=${encodeURIComponent(sessionId)}`);
    assert.equal(sessions.status, 200);
    assert.equal(sessions.body.sessions[0].status, "failed");
    assert.equal(sessions.body.sessions[0].terminal, true);

    const messages = await getJson(port, `/v1/workbench/sessions/${encodeURIComponent(sessionId)}/messages?limit=10`);
    assert.equal(messages.status, 200);
    const assistant = messages.body.messages.find((message) => message.role === "agent");
    assert.equal(assistant.status, "failed");
    assert.match(assistant.text, /provider-stream-disconnected/u);
    assert.equal(assistant.parts[0].type, "final_response");
    assert.match(assistant.parts[0].text, /provider-stream-disconnected/u);

    const turn = await getJson(port, `/v1/workbench/turns/${encodeURIComponent(traceId)}`);
    assert.equal(turn.status, 200);
    assert.equal(turn.body.turn.status, "failed");
    assert.equal(turn.body.turn.running, false);
    assert.equal(turn.body.turn.terminal, true);
    assert.match(turn.body.turn.finalResponse.text, /provider-stream-disconnected/u);
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench read model does not expose trace-only memory without visible session or result owner", async () => {
  const traceStore = createCodeAgentTraceStore();
  const traceId = "trc_workbench_trace_only";
  traceStore.append(traceId, { type: "request", status: "accepted", label: "request:accepted" });
  const accessController = {
    store: {
      async getAgentSessionByTraceId() { return null; }
    },
    async ensureBootstrap() {},
    async authenticate() { return { ok: true, actor: ACTOR, session: { id: "uss_workbench_reader" } }; }
  };
  const server = createCloudApiServer({ accessController, traceStore, codeAgentChatResults: createCodeAgentChatResultStore() });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));

  try {
    const { port } = server.address();
    const turn = await getJson(port, `/v1/workbench/turns/${encodeURIComponent(traceId)}`);
    assert.equal(turn.status, 404);
    assert.equal(turn.body.error.code, "workbench_turn_not_found");

    const trace = await getJson(port, `/v1/workbench/traces/${encodeURIComponent(traceId)}/events`);
    assert.equal(trace.status, 404);
    assert.equal(trace.body.error.code, "workbench_trace_not_found");
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench trace events reports metadata gap when turn projection is visible", async () => {
  const traceStore = createCodeAgentTraceStore();
  const traceId = "trc_workbench_trace_metadata_gap";
  const session = {
    id: "ses_workbench_trace_metadata_gap",
    projectId: "prj_hwpod_workbench",
    agentId: "hwlab-code-agent",
    status: "completed",
    ownerUserId: ACTOR.id,
    conversationId: "cnv_workbench_trace_metadata_gap",
    threadId: "thread-workbench-trace-metadata-gap",
    lastTraceId: traceId,
    updatedAt: "2026-06-20T13:00:00.000Z",
    session: { sessionStatus: "completed", lastTraceId: traceId, messages: [{ role: "user", text: "metadata gap", traceId }, { role: "agent", text: "OK", traceId }] }
  };
  const accessController = {
    store: {},
    async ensureBootstrap() {},
    async authenticate() { return { ok: true, actor: ACTOR, session: { id: "uss_workbench_reader" } }; }
  };
  const runtimeStore = createDurableFactsRuntimeStore({
    sessions: [{ session, events: [], status: "completed", finalText: "OK", runId: "run_workbench_trace_metadata_gap", commandId: "cmd_workbench_trace_metadata_gap", lastProjectedSeq: 2, omitSessions: true }]
  });
  const server = createCloudApiServer({ accessController, traceStore, runtimeStore, codeAgentChatResults: createCodeAgentChatResultStore() });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));

  try {
    const { port } = server.address();
    const turn = await getJson(port, `/v1/workbench/turns/${encodeURIComponent(traceId)}`);
    assert.equal(turn.status, 200);
    assert.equal(turn.body.turn.status, "completed");

    const trace = await getJson(port, `/v1/workbench/traces/${encodeURIComponent(traceId)}/events?limit=10`);
    assert.equal(trace.status, 404);
    assert.equal(trace.body.error.code, "workbench_trace_metadata_missing");
    assert.equal(trace.body.error.message, "Workbench trace metadata is missing from the trace events read model.");
    assert.equal(trace.body.error.projectionStatus, "blocked");
    assert.equal(trace.body.error.projectionHealth, "degraded");
    assert.equal(trace.body.error.lastProjectedSeq, 2);
    assert.equal(trace.body.error.sourceRunId, "run_workbench_trace_metadata_gap");
    assert.equal(trace.body.error.sourceCommandId, "cmd_workbench_trace_metadata_gap");
    assert.equal(trace.body.error.blocker.code, "workbench_trace_metadata_missing");
    assert.notEqual(trace.body.error.message, "Workbench trace is not visible to the current actor.");
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});
