// 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 { createCodeAgentChatResultStore } from "./server-code-agent-http.ts";
import { createWorkbenchTurnProjection, durableTraceStatus, projectionDiagnostics, traceTerminalEvidence } from "./workbench-turn-projection.ts";

const ACTOR = { id: "usr_workbench_reader", username: "reader", displayName: "Reader", role: "user", status: "active" };

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 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 });
  assert.equal(projection.status, "completed");
  assert.equal(projection.terminal, true);
  assert.equal(projection.finalResponse, null);
  assert.equal(projection.assistantText, null);
});

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)}`);
    assert.equal(detail.status, 200);
    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);

    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 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 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].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.assistantText, finalText);
    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 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()));
  }
});

test("workbench trace events reports missing event page when session projection is visible", async () => {
  const traceStore = createCodeAgentTraceStore();
  const traceId = "trc_workbench_trace_events_gap";
  const session = {
    id: "ses_workbench_trace_events_gap",
    projectId: "prj_hwpod_workbench",
    agentId: "hwlab-code-agent",
    status: "completed",
    ownerUserId: ACTOR.id,
    conversationId: "cnv_workbench_trace_events_gap",
    threadId: "thread-workbench-trace-events-gap",
    lastTraceId: traceId,
    updatedAt: "2026-06-20T13:05:00.000Z",
    session: { sessionStatus: "completed", lastTraceId: traceId, messages: [{ role: "user", text: "events 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_events_gap", commandId: "cmd_workbench_trace_events_gap", lastProjectedSeq: 2 }]
  });
  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_events_missing");
    assert.equal(trace.body.error.message, "Workbench trace events are missing from the durable read model.");
    assert.equal(trace.body.error.sessionId, session.id);
    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_events_gap");
    assert.equal(trace.body.error.sourceCommandId, "cmd_workbench_trace_events_gap");
    assert.equal(trace.body.error.blocker.code, "workbench_trace_events_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()));
  }
});

test("workbench read model projects current turn running state from trace projection", async () => {
  const traceStore = createCodeAgentTraceStore();
  const traceId = "trc_workbench_active_state";
  const session = {
    id: "ses_workbench_active_state",
    projectId: "prj_hwpod_workbench",
    agentId: "hwlab-code-agent",
    status: "active",
    ownerUserId: ACTOR.id,
    conversationId: "cnv_workbench_active_state",
    threadId: "thread-workbench-active-state",
    lastTraceId: traceId,
    updatedAt: "2026-06-17T00:01:00.000Z",
    session: { sessionStatus: "active", lastTraceId: traceId, messages: [{ role: "user", text: "still running", traceId }] }
  };
  traceStore.append(traceId, { type: "backend", status: "running", label: "runner:created", terminal: false });
  const accessController = {
    store: {
      async listAgentSessionsForUser() { return [session]; }
    },
    async ensureBootstrap() {},
    async authenticate() { return { ok: true, actor: ACTOR, session: { id: "uss_workbench_reader" } }; }
  };
  const runtimeStore = createDurableFactsRuntimeStore({
    sessions: [{
      session,
      events: [{ seq: 1, type: "backend", status: "running", label: "runner:created", terminal: false, createdAt: session.updatedAt }],
      status: "running",
      lastProjectedSeq: 1
    }]
  });
  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 sessions = await getJson(port, "/v1/workbench/sessions");
    assert.equal(sessions.status, 200);
    assert.equal(sessions.body.sessions[0].status, "running");
    assert.equal(sessions.body.sessions[0].running, true);
    assert.equal(sessions.body.sessions[0].terminal, false);
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench read model projects current turn running state for idle session summary", async () => {
  const traceStore = createCodeAgentTraceStore();
  const traceId = "trc_workbench_idle_with_running_trace";
  const session = {
    id: "ses_workbench_idle_with_running_trace",
    projectId: "prj_hwpod_workbench",
    agentId: "hwlab-code-agent",
    status: "idle",
    ownerUserId: ACTOR.id,
    conversationId: "cnv_workbench_idle_with_running_trace",
    threadId: "thread-workbench-idle-with-running-trace",
    lastTraceId: traceId,
    updatedAt: "2026-06-17T00:02:00.000Z",
    session: { sessionStatus: "idle", lastTraceId: traceId, messages: [{ role: "user", text: "still running despite idle row", traceId, status: "sent" }] }
  };
  traceStore.append(traceId, { type: "backend", status: "running", label: "runner:created", terminal: false });
  const accessController = {
    store: {
      async listAgentSessionsForUser() { return [session]; }
    },
    async ensureBootstrap() {},
    async authenticate() { return { ok: true, actor: ACTOR, session: { id: "uss_workbench_reader" } }; }
  };
  const runtimeStore = createDurableFactsRuntimeStore({
    sessions: [{
      session,
      events: [{ seq: 1, type: "backend", status: "running", label: "runner:created", terminal: false, createdAt: session.updatedAt }],
      status: "running",
      lastProjectedSeq: 1
    }]
  });
  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 sessions = await getJson(port, `/v1/workbench/sessions?includeSessionId=${encodeURIComponent(session.id)}`);
    assert.equal(sessions.status, 200);
    assert.equal(sessions.body.sessions[0].status, "running");
    assert.equal(sessions.body.sessions[0].running, true);
    assert.equal(sessions.body.sessions[0].terminal, false);
    assert.equal(sessions.body.sessions[0].turnSummary.status, "running");
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench read model projects completed current turn for idle session summary", async () => {
  const traceStore = createCodeAgentTraceStore();
  const results = createCodeAgentChatResultStore();
  const traceId = "trc_workbench_idle_with_completed_trace";
  const finalText = "OK";
  const session = {
    id: "ses_workbench_idle_with_completed_trace",
    projectId: "prj_hwpod_workbench",
    agentId: "hwlab-code-agent",
    status: "idle",
    ownerUserId: ACTOR.id,
    conversationId: "cnv_workbench_idle_with_completed_trace",
    threadId: "thread-workbench-idle-with-completed-trace",
    lastTraceId: traceId,
    updatedAt: "2026-06-17T00:03:00.000Z",
    session: {
      sessionStatus: "idle",
      lastTraceId: traceId,
      messages: [
        { role: "user", text: "reply OK", traceId, status: "sent" },
        { role: "agent", text: "", traceId, status: "running" }
      ]
    }
  };
  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: finalText,
    agentRun: { runId: "run_workbench_idle_completed", commandId: "cmd_workbench_idle_completed", status: "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 backendPerformanceStore = createBackendPerformanceStore({ env: { POD_NAMESPACE: "hwlab-v03", HWLAB_GITOPS_TARGET: "v03", HWLAB_RUNTIME_LANE: "v03", HWLAB_CODE_AGENT_AGENTRUN_PROVIDER_ID: "D601" } });
  const runtimeStore = createDurableFactsRuntimeStore({
    sessions: [{
      session,
      events: [
        { seq: 1, type: "request", status: "accepted", label: "request:accepted", createdAt: "2026-06-17T00:02:58.000Z" },
        { seq: 2, type: "result", status: "completed", label: "result:completed", terminal: true, createdAt: "2026-06-17T00:03:00.000Z" }
      ],
      status: "completed",
      finalText,
      runId: "run_workbench_idle_completed",
      commandId: "cmd_workbench_idle_completed",
      lastProjectedSeq: 2
    }]
  });
  const server = createCloudApiServer({ accessController, traceStore, runtimeStore, codeAgentChatResults: results, backendPerformanceStore });
  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);
    assert.equal(sessions.body.sessions[0].turnSummary.status, "completed");

    const messages = await getJson(port, `/v1/workbench/sessions/${encodeURIComponent(session.id)}/messages?limit=10`);
    assert.equal(messages.status, 200);
    assert.equal(messages.body.messages[1].status, "completed");
    assert.equal(messages.body.messages[1].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.assistantText, finalText);
    assert.equal(turn.body.projectionStatus, "caught-up");
    assert.equal(turn.body.sourceRunId, "run_workbench_idle_completed");
    assert.equal(turn.body.sourceCommandId, "cmd_workbench_idle_completed");
    const metricsText = backendPerformanceStore.metricsText();
    const lagCountLine = metricsText.split("\n").find((line) => line.startsWith("hwlab_workbench_projection_lag_seconds_count") && line.includes('projection_status="caught_up"'));
    const turnGetCountLine = metricsText.split("\n").find((line) => line.startsWith("hwlab_workbench_turn_get_duration_seconds_count") && line.includes('route="/v1/workbench/turns/:traceId"'));
    assert.ok(lagCountLine?.endsWith(" 1"));
    assert.ok(turnGetCountLine?.endsWith(" 1"));

    const trace = await getJson(port, `/v1/workbench/traces/${encodeURIComponent(traceId)}/events?limit=10`);
    assert.equal(trace.status, 200);
    assert.equal(trace.body.projectionStatus, "caught-up");
    assert.equal(trace.body.sourceRunId, "run_workbench_idle_completed");
    assert.equal(trace.body.sourceCommandId, "cmd_workbench_idle_completed");
    assert.equal(trace.body.lastProjectedSeq, 2);
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench read model lets terminal result override stale running session state", async () => {
  const traceStore = createCodeAgentTraceStore();
  const results = createCodeAgentChatResultStore();
  const traceId = "trc_workbench_stale_running_result_completed";
  const finalText = "OK";
  const session = {
    id: "ses_workbench_stale_running_result_completed",
    projectId: "prj_hwpod_workbench",
    agentId: "hwlab-code-agent",
    status: "running",
    ownerUserId: ACTOR.id,
    conversationId: "cnv_workbench_stale_running_result_completed",
    threadId: "thread-workbench-stale-running-result-completed",
    lastTraceId: traceId,
    updatedAt: "2026-06-18T16:31:04.000Z",
    session: {
      sessionStatus: "running",
      lastTraceId: traceId,
      messages: [
        { role: "user", text: "reply OK", traceId, status: "sent" },
        { role: "agent", text: "", traceId, status: "running" }
      ]
    }
  };
  traceStore.append(traceId, { type: "backend", status: "running", label: "runner:created", terminal: false });
  results.set(traceId, {
    status: "completed",
    traceId,
    ownerUserId: ACTOR.id,
    conversationId: session.conversationId,
    sessionId: session.id,
    threadId: session.threadId,
    finalResponse: finalText,
    agentRun: { runId: "run_workbench_stale_running_completed", commandId: "cmd_workbench_stale_running_completed", status: "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: "backend", status: "running", label: "runner:created", terminal: false, createdAt: "2026-06-18T16:30:00.000Z" },
        { seq: 2, type: "result", status: "completed", label: "result:completed", terminal: true, createdAt: "2026-06-18T16:31:04.000Z" }
      ],
      status: "completed",
      finalText,
      runId: "run_workbench_stale_running_completed",
      commandId: "cmd_workbench_stale_running_completed",
      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);
    assert.equal(sessions.body.sessions[0].turnSummary.status, "completed");

    const detail = await getJson(port, `/v1/workbench/sessions/${encodeURIComponent(session.id)}`);
    assert.equal(detail.status, 200);
    assert.equal(detail.body.session.status, "completed");
    assert.equal(detail.body.session.running, false);
    assert.equal(detail.body.session.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.messages[1].status, "completed");
    assert.equal(messages.body.messages[1].text, finalText);

    const turn = await getJson(port, `/v1/workbench/turns/${encodeURIComponent(traceId)}`);
    assert.equal(turn.status, 200);
    assert.equal(turn.body.status, "completed");
    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.assistantText, finalText);
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench session list uses durable projection checkpoint without trace/result hydration", async () => {
  const traceStore = createCodeAgentTraceStore();
  const traceId = "trc_workbench_compact_list_summary";
  const finalText = "compact list summary OK";
  const session = {
    id: "ses_workbench_compact_list_summary",
    projectId: "prj_hwpod_workbench",
    agentId: "hwlab-code-agent",
    status: "running",
    ownerUserId: ACTOR.id,
    conversationId: "cnv_workbench_compact_list_summary",
    threadId: "thread-workbench-compact-list-summary",
    lastTraceId: traceId,
    updatedAt: "2026-06-19T15:40:00.000Z",
    session: {
      sessionStatus: "running",
      lastTraceId: traceId,
      messages: [
        { role: "user", text: "compact summary", traceId, status: "sent" },
        { role: "agent", text: "", traceId, status: "running" }
      ],
      traceResults: {
        [traceId]: {
          traceId,
          status: "completed",
          finalResponse: { text: finalText, status: "completed", traceId, valuesPrinted: false },
          traceSummary: {
            traceId,
            source: "agent-session-compact-summary",
            sourceEventCount: 42,
            terminalStatus: "completed",
            agentRun: { runId: "run_workbench_compact_list_summary", commandId: "cmd_workbench_compact_list_summary", lastSeq: 42, valuesPrinted: false },
            valuesPrinted: false
          },
          agentRun: { runId: "run_workbench_compact_list_summary", commandId: "cmd_workbench_compact_list_summary", status: "completed", terminalStatus: "completed", lastSeq: 42, valuesPrinted: false },
          updatedAt: "2026-06-19T15:40:00.000Z",
          valuesRedacted: true,
          secretMaterialStored: false
        }
      },
      valuesRedacted: true,
      secretMaterialStored: false
    }
  };
  const accessController = {
    store: {
      async listAgentSessionsForUser() { return [session]; }
    },
    async ensureBootstrap() {},
    async authenticate() { return { ok: true, actor: ACTOR, session: { id: "uss_workbench_reader" } }; }
  };
  const factQueries = [];
  const runtimeStore = createDurableFactsRuntimeStore({
    sessions: [{
      session,
      events: [
        { seq: 1, sourceSeq: 1, type: "request", status: "accepted", label: "request:accepted", createdAt: "2026-06-19T15:39:10.000Z" },
        { seq: 42, sourceSeq: 42, type: "result", status: "completed", label: "result:completed", terminal: true, createdAt: "2026-06-19T15:40:00.000Z" }
      ],
      status: "completed",
      finalText,
      runId: "run_workbench_compact_list_summary",
      commandId: "cmd_workbench_compact_list_summary",
      lastProjectedSeq: 42
    }],
    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 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);
    assert.equal(sessions.body.sessions[0].turnSummary.status, "completed");
    assert.equal(sessions.body.sessions[0].turnSummary.eventCount, 42);
    assert.equal(sessions.body.sessions[0].projectionStatus, "caught-up");
    assert.ok(factQueries.length >= 1);
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench session list overlaps include lookup with the page query", async () => {
  const makeSession = (id, traceId, updatedAt) => ({
    id,
    projectId: "prj_hwpod_workbench",
    agentId: "hwlab-code-agent",
    status: "running",
    ownerUserId: ACTOR.id,
    conversationId: id.replace(/^ses_/u, "cnv_"),
    threadId: `thread-${id}`,
    lastTraceId: traceId,
    updatedAt,
    session: {
      sessionStatus: "running",
      lastTraceId: traceId,
      messages: [{ role: "user", text: id, traceId, status: "sent" }]
    }
  });
  const included = makeSession("ses_parallel_include", "trc_parallel_include", "2026-06-19T14:00:00.000Z");
  const firstPage = makeSession("ses_parallel_page_a", "trc_parallel_page_a", "2026-06-19T16:00:00.000Z");
  const secondPage = makeSession("ses_parallel_page_b", "trc_parallel_page_b", "2026-06-19T15:00:00.000Z");
  const facts = emptyFacts();
  for (const session of [included, firstPage, secondPage]) mergeFacts(facts, buildDurableFactsForSession({ session, status: "running" }));
  const accessController = {
    store: {},
    async ensureBootstrap() {},
    async authenticate() { return { ok: true, actor: ACTOR, session: { id: "uss_workbench_reader" } }; }
  };
  let pageReleased = false;
  let releasePage = () => {};
  const pageGate = new Promise((resolve) => { releasePage = resolve; });
  const queries = [];
  const runtimeStore = {
    async queryWorkbenchFacts(params = {}) {
      queries.push({ ...params, pageReleased });
      if (params.sessionsOrder === "updated_desc") await pageGate;
      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 server = createCloudApiServer({ accessController, runtimeStore, codeAgentChatResults: createCodeAgentChatResultStore() });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));

  try {
    const { port } = server.address();
    const pending = getJson(port, `/v1/workbench/sessions?includeSessionId=${encodeURIComponent(included.id)}&limit=1`);
    await waitForCondition(() => queries.some((query) => query.sessionId === included.id) && queries.some((query) => query.sessionsOrder === "updated_desc"));
    const includeIndex = queries.findIndex((query) => query.sessionId === included.id);
    assert.ok(includeIndex >= 0);
    assert.equal(queries[includeIndex].pageReleased, false);
    pageReleased = true;
    releasePage();
    const sessions = await pending;
    assert.equal(sessions.status, 200);
    assert.equal(sessions.body.sessions[0].sessionId, included.id);
    assert.equal(sessions.body.hasMore, true);
    assert.equal(queries.find((query) => query.sessionsOrder === "updated_desc")?.sessionProjection, "summary");
    assert.equal(queries.find((query) => query.sessionId === included.id)?.sessionProjection, undefined);
  } finally {
    pageReleased = true;
    releasePage();
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench read model reads terminal session status from durable facts", async () => {
  const traceStore = createCodeAgentTraceStore();
  const traceId = "trc_workbench_durable_terminal_after_memory_running";
  const session = {
    id: "ses_workbench_durable_terminal_after_memory_running",
    projectId: "prj_hwpod_workbench",
    agentId: "hwlab-code-agent",
    status: "running",
    ownerUserId: ACTOR.id,
    conversationId: "cnv_workbench_durable_terminal_after_memory_running",
    threadId: "thread-workbench-durable-terminal-after-memory-running",
    lastTraceId: traceId,
    updatedAt: "2026-06-18T17:22:00.000Z",
    session: {
      sessionStatus: "running",
      lastTraceId: traceId,
      messages: [
        { role: "user", text: "reply OK", traceId, status: "sent" },
        { role: "agent", text: "", traceId, status: "running" }
      ]
    }
  };
  traceStore.append(traceId, { type: "backend", status: "running", label: "runner:created", terminal: false, seq: 1 });
  const durableEvents = [
    { traceId, seq: 1, type: "request", status: "accepted", label: "request:accepted", createdAt: "2026-06-18T17:21:40.000Z", valuesPrinted: false },
    { traceId, seq: 2, type: "result", status: "completed", label: "result:completed", terminal: true, createdAt: "2026-06-18T17:22:00.000Z", valuesPrinted: false }
  ];
  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: durableEvents, status: "completed", finalText: "OK", lastProjectedSeq: 2 }]
  });
  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 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 detail = await getJson(port, `/v1/workbench/sessions/${encodeURIComponent(session.id)}`);
    assert.equal(detail.status, 200);
    assert.equal(detail.body.session.status, "completed");
    assert.equal(detail.body.session.running, false);
    assert.equal(detail.body.session.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.messages[1].status, "completed");

    const turn = await getJson(port, `/v1/workbench/turns/${encodeURIComponent(traceId)}`);
    assert.equal(turn.status, 200);
    assert.equal(turn.body.status, "completed");
    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.trace.eventCount, 2);
    assert.equal(turn.body.projectionStatus, "caught-up");
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench read model keeps non-terminal durable tool completed events out of turn status", async () => {
  const traceStore = createCodeAgentTraceStore();
  const results = createCodeAgentChatResultStore();
  const traceId = "trc_workbench_durable_tool_completed_nonterminal";
  const session = {
    id: "ses_workbench_durable_tool_completed_nonterminal",
    projectId: "prj_hwpod_workbench",
    agentId: "hwlab-code-agent",
    status: "running",
    ownerUserId: ACTOR.id,
    conversationId: "cnv_workbench_durable_tool_completed_nonterminal",
    threadId: "thread-workbench-durable-tool-completed-nonterminal",
    lastTraceId: traceId,
    updatedAt: "2026-06-18T19:35:51.787Z",
    session: {
      sessionStatus: "running",
      lastTraceId: traceId,
      messages: [
        { role: "user", text: "write benchmark", traceId, status: "sent" },
        { role: "agent", text: "", traceId, status: "running" }
      ],
      valuesRedacted: true,
      secretMaterialStored: false
    }
  };
  traceStore.append(traceId, { seq: 1, type: "backend_status", status: "running", label: "runner:created", terminal: false });
  const durableEvents = [
    { traceId, seq: 1, type: "assistant_message", status: "running", label: "agentrun:assistant:message", terminal: false, message: "正在安装 Python。", createdAt: "2026-06-18T19:35:40.000Z", valuesPrinted: false },
    { traceId, seq: 2, type: "commandExecution", status: "completed", label: "item/commandExecution:completed", terminal: false, command: "ls -ld .", stdout: "OK\n", createdAt: "2026-06-18T19:35:51.787Z", valuesPrinted: false }
  ];
  results.set(traceId, {
    status: "running",
    traceId,
    ownerUserId: ACTOR.id,
    conversationId: session.conversationId,
    sessionId: session.id,
    threadId: session.threadId,
    finalResponse: null,
    assistantText: null,
    agentRun: { runId: "run_workbench_nonterminal_tool_completed", commandId: "cmd_workbench_nonterminal_tool_completed", status: "runner-job-created", runStatus: "pending", commandState: "pending" }
  });
  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: durableEvents,
      status: "running",
      runId: "run_workbench_nonterminal_tool_completed",
      commandId: "cmd_workbench_nonterminal_tool_completed",
      projectionStatus: "projecting",
      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, "running");
    assert.equal(sessions.body.sessions[0].running, true);
    assert.equal(sessions.body.sessions[0].terminal, false);

    const messages = await getJson(port, `/v1/workbench/sessions/${encodeURIComponent(session.id)}/messages?limit=10`);
    assert.equal(messages.status, 200);
    assert.equal(messages.body.messages[1].status, "running");
    assert.equal(messages.body.messages[1].text, "");

    const turn = await getJson(port, `/v1/workbench/turns/${encodeURIComponent(traceId)}`);
    assert.equal(turn.status, 200);
    assert.equal(turn.body.status, "running");
    assert.equal(turn.body.turn.status, "running");
    assert.equal(turn.body.turn.running, true);
    assert.equal(turn.body.turn.terminal, false);
    assert.equal(turn.body.turn.finalResponse, null);
    assert.equal(turn.body.projectionStatus, "projecting");

    const trace = await getJson(port, `/v1/workbench/traces/${encodeURIComponent(traceId)}/events?limit=10`);
    assert.equal(trace.status, 200);
    assert.equal(trace.body.events.at(-1).status, "completed");
    assert.equal(trace.body.events.at(-1).terminal, false);
    assert.equal(trace.body.projectionStatus, "projecting");
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench trace events API blocks historical projectedSeq collisions instead of handing them to renderer", async () => {
  const traceStore = createCodeAgentTraceStore();
  const traceId = "trc_workbench_projected_seq_collision";
  const session = {
    id: "ses_workbench_projected_seq_collision",
    projectId: "prj_hwpod_workbench",
    agentId: "hwlab-code-agent",
    status: "running",
    ownerUserId: ACTOR.id,
    conversationId: "cnv_workbench_projected_seq_collision",
    threadId: "thread-workbench-projected-seq-collision",
    lastTraceId: traceId,
    updatedAt: "2026-06-20T12:20:00.000Z",
    session: { sessionStatus: "running", lastTraceId: traceId, messages: [{ role: "user", text: "collision", traceId, status: "sent" }] }
  };
  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: [
        { id: "wte_collision_a", projectedSeq: 1, sourceSeq: 1, sourceEventId: "source-a", type: "backend", status: "running", label: "projected:1:a", createdAt: "2026-06-20T12:19:58.000Z" },
        { id: "wte_collision_b", projectedSeq: 1, sourceSeq: 2, sourceEventId: "source-b", type: "assistant", status: "running", label: "projected:1:b", message: "later", createdAt: "2026-06-20T12:19:59.000Z" }
      ],
      status: "running",
      projectionStatus: "projecting",
      lastProjectedSeq: 1
    }]
  });
  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 trace = await getJson(port, `/v1/workbench/traces/${encodeURIComponent(traceId)}/events?limit=10`);
    assert.equal(trace.status, 200);
    assert.equal(trace.body.status, "blocked");
    assert.equal(trace.body.projectionStatus, "blocked");
    assert.equal(trace.body.projectionHealth, "degraded");
    assert.equal(trace.body.blocker.code, "projected_seq_collision");
    assert.deepEqual(trace.body.events, []);
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench realtime stream accepts session authority without project or workspace", async () => {
  const traceStore = createCodeAgentTraceStore();
  const results = createCodeAgentChatResultStore();
  const traceId = "trc_workbench_realtime";
  const session = {
    id: "ses_workbench_realtime",
    projectId: "prj_hwpod_workbench",
    agentId: "hwlab-code-agent",
    status: "running",
    ownerUserId: ACTOR.id,
    conversationId: "cnv_workbench_realtime",
    threadId: "thread-workbench-realtime",
    lastTraceId: traceId,
    updatedAt: "2026-06-17T02:00:00.000Z",
    session: { sessionStatus: "running", lastTraceId: traceId, messages: [{ role: "user", text: "stream", traceId }] }
  };
  traceStore.append(traceId, { type: "request", status: "accepted", label: "request:accepted" });
  results.set(traceId, { status: "running", traceId, ownerUserId: ACTOR.id, sessionId: session.id, threadId: session.threadId });
  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, codeAgentChatResults: results, env: { HWLAB_WORKBENCH_SSE_HEARTBEAT_MS: "60000" } });
  await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve));

  try {
    const { port } = server.address();
    const eventsPromise = getSseEvents(port, `/v1/workbench/events?sessionId=${encodeURIComponent(session.id)}`, 4);
    setTimeout(() => traceStore.append(traceId, { type: "backend", status: "running", label: "backend:live" }), 50);
    const events = await eventsPromise;
    assert.deepEqual(events.slice(0, 3).map((event) => event.event), [
      "workbench.connected",
      "workbench.trace.snapshot",
      "workbench.turn.snapshot"
    ]);
    assert.equal(events[0].data.filters.sessionId, session.id);
    assert.equal(events[1].data.traceId, traceId);
    assert.equal(events[2].data.turn.traceId, traceId);
    assert.equal(events[3].event, "workbench.trace.event");
    assert.equal(events[3].data.event.label, "backend:live");
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

test("workbench read model exposes runtime trace projection query failures as projection blockers", async () => {
  const traceStore = createCodeAgentTraceStore();
  const results = createCodeAgentChatResultStore();
  const traceId = "trc_workbench_projection_store_unavailable";
  const session = {
    id: "ses_workbench_projection_store_unavailable",
    projectId: "prj_hwpod_workbench",
    agentId: "hwlab-code-agent",
    status: "running",
    ownerUserId: ACTOR.id,
    conversationId: "cnv_workbench_projection_store_unavailable",
    threadId: "thread-workbench-projection-store-unavailable",
    lastTraceId: traceId,
    updatedAt: "2026-06-18T01:10:00.000Z",
    session: {
      sessionStatus: "running",
      lastTraceId: traceId,
      messages: [
        { role: "user", text: "projection store unavailable", traceId, status: "sent", createdAt: "2026-06-18T01:09:58.000Z" },
        { role: "agent", text: "", traceId, status: "running", createdAt: "2026-06-18T01:09:59.000Z" }
      ],
      valuesRedacted: true,
      secretMaterialStored: false
    }
  };
  traceStore.append(traceId, { seq: 1, type: "backend", status: "running", label: "runner:created", terminal: false, createdAt: "2026-06-18T01:10:00.000Z" });
  results.set(traceId, { status: "running", traceId, ownerUserId: ACTOR.id, sessionId: session.id, threadId: session.threadId });
  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 = {
    async queryWorkbenchFacts() {
      const error = new Error("runtime query failed");
      error.code = "TEST_RUNTIME_QUERY_FAILED";
      throw error;
    }
  };
  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 turn = await getJson(port, `/v1/workbench/turns/${encodeURIComponent(traceId)}`);
    assert.equal(turn.status, 503);
    assert.equal(turn.body.error.code, "projection_store_unavailable");

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

test("workbench trace events remain visible after session lastTraceId moves", async () => {
  const oldTraceId = "trc_workbench_history_old_trace";
  const newTraceId = "trc_workbench_history_new_trace";
  const session = {
    id: "ses_workbench_history_trace",
    projectId: "prj_hwpod_workbench",
    agentId: "hwlab-code-agent",
    status: "running",
    ownerUserId: ACTOR.id,
    conversationId: "cnv_workbench_history_trace",
    threadId: "thread-workbench-history-trace",
    lastTraceId: newTraceId,
    updatedAt: "2026-06-20T10:10:00.000Z",
    session: {
      messages: [
        { role: "user", text: "old turn", traceId: oldTraceId, status: "sent", createdAt: "2026-06-20T10:00:00.000Z" },
        { role: "agent", text: "old final", traceId: oldTraceId, status: "completed", createdAt: "2026-06-20T10:00:01.000Z" }
      ],
      valuesRedacted: true
    }
  };
  const oldFacts = buildDurableFactsForSession({
    session: { ...session, status: "completed", lastTraceId: oldTraceId, updatedAt: "2026-06-20T10:01:00.000Z" },
    status: "completed",
    finalText: "old final",
    runId: "run_workbench_history_old_trace",
    commandId: "cmd_workbench_history_old_trace",
    events: [
      { projectedSeq: 1, sourceSeq: 1, type: "backend", status: "running", label: "runner:started", createdAt: "2026-06-20T10:00:00.000Z" },
      { projectedSeq: 2, sourceSeq: 2, type: "result", status: "completed", label: "runner:completed", terminal: true, createdAt: "2026-06-20T10:01:00.000Z" }
    ],
    lastProjectedSeq: 2
  });
  const newFacts = buildDurableFactsForSession({
    session: { ...session, session: { messages: [{ role: "user", text: "new turn", traceId: newTraceId, status: "sent" }] } },
    status: "running",
    runId: "run_workbench_history_new_trace",
    commandId: "cmd_workbench_history_new_trace",
    events: [{ projectedSeq: 1, sourceSeq: 1, type: "backend", status: "running", label: "runner:started", createdAt: "2026-06-20T10:10:00.000Z" }],
    lastProjectedSeq: 1
  });
  const facts = emptyFacts();
  mergeFacts(facts, oldFacts);
  mergeFacts(facts, newFacts);
  facts.sessions = newFacts.sessions;
  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 = {
    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(oldTraceId)}/events?limit=10`);
    assert.equal(response.status, 200);
    assert.equal(response.body.status, "succeeded");
    assert.equal(response.body.sessionId, session.id);
    assert.equal(response.body.traceId, oldTraceId);
    assert.equal(response.body.events.length, 2);
    assert.equal(response.body.events.at(-1).status, "completed");
    assert.equal(response.body.error, undefined);
    assert.equal(queries.some((query) => query.sessionId === session.id && Array.isArray(query.families) && query.families.includes("sessions")), true);
  } finally {
    await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
  }
});

async function getJson(port, path) {
  const response = await fetch(`http://127.0.0.1:${port}${path}`);
  return {
    status: response.status,
    body: await response.json()
  };
}

async function waitForCondition(predicate, timeoutMs = 500) {
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    if (predicate()) return;
    await new Promise((resolve) => setTimeout(resolve, 5));
  }
  assert.fail("condition was not met before timeout");
}

function createDurableFactsRuntimeStore({ sessions = [], queryError = null, queries = [] } = {}) {
  const facts = emptyFacts();
  for (const item of sessions) mergeFacts(facts, buildDurableFactsForSession(item));
  return {
    queries,
    async queryWorkbenchFacts(params = {}) {
      queries.push(params);
      if (queryError) throw queryError;
      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 }
      };
    }
  };
}

function buildDurableFactsForSession({ session, events = [], status = null, finalText = null, runId = null, commandId = null, projectionStatus = null, projectionHealth = "healthy", lastProjectedSeq = null, omitSessions = false } = {}) {
  const traceId = session.lastTraceId ?? session.session?.lastTraceId;
  const projectedStatus = normalizeTestStatus(status ?? session.status);
  const terminal = testTerminalStatuses.has(projectedStatus);
  const normalizedEvents = normalizeTestEvents(events, session, { traceId });
  const timing = testTimingProjection({ session, events: normalizedEvents, status: projectedStatus, terminal });
  const normalizedMessages = normalizeTestMessages(session, { traceId, status: projectedStatus, finalText, terminal, timing });
  const explicitLastProjectedSeq = lastProjectedSeq !== null && lastProjectedSeq !== undefined ? Number(lastProjectedSeq) : NaN;
  const projectedSeq = Number.isFinite(explicitLastProjectedSeq) ? explicitLastProjectedSeq : Math.max(normalizedEvents.length, normalizedMessages.length, 0);
  const checkpointStatus = projectionStatus ?? (terminal ? "caught_up" : "projecting");
  return {
    sessions: omitSessions ? [] : [{
      sessionId: session.id,
      ownerUserId: session.ownerUserId,
      ownerRole: session.ownerRole ?? null,
      projectId: session.projectId ?? null,
      conversationId: session.conversationId ?? null,
      threadId: session.threadId ?? null,
      agentId: session.agentId ?? "hwlab-code-agent",
      status: projectedStatus,
      lastTraceId: traceId,
      providerProfile: session.session?.providerProfile ?? null,
      projectedSeq,
      sourceSeq: projectedSeq,
      sourceEventId: traceId,
      terminal,
      sealed: terminal,
      timing,
      startedAt: timing.startedAt,
      lastEventAt: timing.lastEventAt,
      finishedAt: timing.finishedAt,
      durationMs: timing.durationMs,
      createdAt: session.startedAt ?? session.createdAt ?? session.updatedAt ?? null,
      updatedAt: session.updatedAt ?? session.endedAt ?? session.startedAt ?? null,
      valuesRedacted: true
    }],
    messages: normalizedMessages,
    parts: normalizedMessages.filter((message) => message.text).map((message, index) => ({
      partId: `prt_${session.id}_${index}`,
      messageId: message.messageId,
      sessionId: session.id,
      turnId: message.turnId,
      traceId: message.traceId,
      partIndex: 0,
      partType: "text",
      status: message.status,
      text: message.text,
      projectedSeq: message.projectedSeq,
      sourceSeq: message.sourceSeq,
      sourceEventId: message.sourceEventId,
      terminal: message.terminal,
      sealed: message.sealed,
      updatedAt: message.updatedAt,
      valuesRedacted: true
    })),
    turns: traceId ? [{
      turnId: traceId,
      sessionId: session.id,
      traceId,
      messageId: normalizedMessages.find((message) => message.role !== "user")?.messageId ?? null,
      status: projectedStatus,
      projectedSeq,
      sourceSeq: projectedSeq,
      sourceEventId: traceId,
      terminal,
      sealed: terminal,
      finalResponse: finalText ? { text: finalText, status: projectedStatus, traceId, valuesPrinted: false } : null,
      diagnostic: { projectionStatus: checkpointStatus, projectionHealth, valuesRedacted: true },
      timing,
      startedAt: timing.startedAt,
      lastEventAt: timing.lastEventAt,
      finishedAt: timing.finishedAt,
      durationMs: timing.durationMs,
      createdAt: session.startedAt ?? session.updatedAt ?? null,
      updatedAt: session.updatedAt ?? null,
      valuesRedacted: true
    }] : [],
    traceEvents: normalizedEvents,
    checkpoints: traceId ? [{
      traceId,
      sessionId: session.id,
      turnId: traceId,
      runId,
      commandId,
      projectedSeq,
      sourceSeq: projectedSeq,
      sourceEventId: traceId,
      projectionStatus: checkpointStatus,
      projectionHealth,
      terminal,
      sealed: terminal,
      diagnostic: { projectionStatus: checkpointStatus, projectionHealth, valuesRedacted: true },
      timing,
      startedAt: timing.startedAt,
      lastEventAt: timing.lastEventAt,
      finishedAt: timing.finishedAt,
      durationMs: timing.durationMs,
      createdAt: session.startedAt ?? session.updatedAt ?? null,
      updatedAt: session.updatedAt ?? null,
      valuesRedacted: true
    }] : []
  };
}

function normalizeTestMessages(session, { traceId, status, finalText, terminal, timing }) {
  const raw = Array.isArray(session.session?.messages) ? session.session.messages : [];
  return raw.map((message, index) => {
    const role = message.role ?? (index % 2 === 0 ? "user" : "agent");
    const isAssistant = role === "agent" || role === "assistant";
    const text = isAssistant && finalText ? finalText : String(message.text ?? message.content ?? "");
    const messageStatus = isAssistant && finalText ? status : normalizeTestStatus(message.status ?? (role === "user" ? "sent" : status));
    return {
      messageId: message.messageId ?? `msg_${session.id}_${index}`,
      sessionId: session.id,
      turnId: message.turnId ?? traceId,
      traceId: message.traceId ?? traceId,
      role,
      status: messageStatus,
      projectedSeq: index + 1,
      sourceSeq: index + 1,
      sourceEventId: `${session.id}:message:${index}`,
      terminal: isAssistant && terminal,
      sealed: isAssistant && terminal,
      text,
      ...(isAssistant ? { timing, startedAt: timing.startedAt, lastEventAt: timing.lastEventAt, finishedAt: timing.finishedAt, durationMs: timing.durationMs } : {}),
      createdAt: message.createdAt ?? session.startedAt ?? session.updatedAt ?? null,
      updatedAt: message.updatedAt ?? session.updatedAt ?? null,
      valuesRedacted: true
    };
  });
}

function testTimingProjection({ session, events = [], status, terminal }) {
  const startedAt = timestampIso(session.startedAt ?? session.createdAt ?? events[0]?.createdAt ?? events[0]?.occurredAt);
  const lastEvent = events.at(-1) ?? null;
  const lastEventAt = timestampIso(lastEvent?.updatedAt ?? lastEvent?.createdAt ?? lastEvent?.occurredAt ?? session.updatedAt);
  const finishedAt = terminal ? timestampIso(session.endedAt ?? lastEventAt ?? session.updatedAt) : null;
  const durationMs = terminal ? elapsedMs(startedAt, finishedAt ?? lastEventAt) : null;
  return { startedAt, lastEventAt, finishedAt, durationMs, status, valuesRedacted: true };
}

function timestampIso(value) {
  const ms = Date.parse(String(value ?? ""));
  return Number.isFinite(ms) ? new Date(ms).toISOString() : null;
}

function elapsedMs(startedAt, endedAt) {
  const start = Date.parse(String(startedAt ?? ""));
  const end = Date.parse(String(endedAt ?? ""));
  if (!Number.isFinite(start) || !Number.isFinite(end) || end < start) return null;
  return end - start;
}

function normalizeTestEvents(events, session, { traceId }) {
  return events.map((event, index) => {
    const projectedSeq = Number.isFinite(Number(event.projectedSeq)) ? Number(event.projectedSeq) : Number.isFinite(Number(event.seq)) ? Number(event.seq) : index + 1;
    const sourceSeq = Number.isFinite(Number(event.sourceSeq)) ? Number(event.sourceSeq) : null;
    return {
      ...event,
      id: event.id ?? `wte_${session.id}_${index}`,
      traceId: event.traceId ?? traceId,
      sessionId: event.sessionId ?? session.id,
      turnId: event.turnId ?? traceId,
      messageId: event.messageId ?? null,
      sourceSeq,
      sourceEventId: event.sourceEventId ?? `${traceId}:${sourceSeq ?? projectedSeq}:${index}`,
      projectedSeq,
      eventType: event.eventType ?? event.type ?? event.label ?? "event",
      terminal: event.terminal === true,
      sealed: event.terminal === true,
      occurredAt: event.occurredAt ?? event.createdAt ?? session.updatedAt ?? null,
      updatedAt: event.updatedAt ?? event.createdAt ?? session.updatedAt ?? null,
      valuesRedacted: true
    };
  });
}

function filterFacts(facts, params = {}) {
  const families = testFactFamilySet(params);
  const afterProjectedSeq = Number.parseInt(params.afterProjectedSeq ?? "", 10);
  const filtered = {
    sessions: families.has("sessions") ? facts.sessions.filter((record) => matchesFact(record, params, ["sessionId", "ownerUserId", "projectId", "conversationId", "threadId", "status"]) && matchesTraceFact(record.lastTraceId, params)) : [],
    messages: families.has("messages") ? facts.messages.filter((record) => matchesFact(record, params, ["messageId", "sessionId", "turnId", "traceId", "role", "status"])) : [],
    parts: families.has("parts") ? facts.parts.filter((record) => matchesFact(record, params, ["partId", "messageId", "sessionId", "turnId", "traceId", "partType", "status"])) : [],
    turns: families.has("turns") ? facts.turns.filter((record) => matchesFact(record, params, ["turnId", "sessionId", "traceId", "messageId", "status"])) : [],
    traceEvents: families.has("traceEvents") ? facts.traceEvents
      .filter((record) => matchesFact(record, params, ["id", "traceId", "sessionId", "turnId", "messageId", "eventType"]))
      .filter((record) => !Number.isInteger(afterProjectedSeq) || afterProjectedSeq <= 0 || Number(record.projectedSeq) > afterProjectedSeq)
      .sort((left, right) => Number(left.projectedSeq) - Number(right.projectedSeq)) : [],
    checkpoints: families.has("checkpoints") ? facts.checkpoints.filter((record) => matchesFact(record, params, ["traceId", "sessionId", "turnId", "runId", "commandId", "projectionStatus", "projectionHealth"])) : []
  };
  if (workbenchTestFactOrder(params, "sessions") === "updated_desc") {
    filtered.sessions.sort((left, right) => String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? "")) || String(right.sessionId ?? "").localeCompare(String(left.sessionId ?? "")));
  }
  const limit = Number.parseInt(params.limit ?? "", 10);
  if (!Number.isInteger(limit) || limit <= 0) return filtered;
  return Object.fromEntries(Object.entries(filtered).map(([key, value]) => [key, value.slice(0, limit)]));
}

function workbenchTestFactOrder(params = {}, family = "") {
  const value = params[`${family}Order`] ?? params.order;
  return value === "updated_desc" ? "updated_desc" : "updated_asc";
}

function matchesFact(record, params, fields) {
  return fields.every((field) => {
    if (params[field] !== undefined && params[field] !== null && record[field] !== params[field]) return false;
    const values = Array.isArray(params[`${field}s`]) ? params[`${field}s`].map((item) => String(item ?? "")).filter(Boolean) : [];
    return values.length === 0 || values.includes(String(record[field] ?? ""));
  });
}

function matchesTraceFact(value, params) {
  if (params.traceId && value !== params.traceId) return false;
  const traceIds = Array.isArray(params.traceIds) ? params.traceIds.map((item) => String(item ?? "")).filter(Boolean) : [];
  return traceIds.length === 0 || traceIds.includes(String(value ?? ""));
}

function testFactFamilySet(params = {}) {
  const all = ["sessions", "messages", "parts", "turns", "traceEvents", "checkpoints"];
  const raw = params.families ?? params.factFamilies;
  if (!raw) return new Set(all);
  const values = Array.isArray(raw) ? raw : String(raw).split(",");
  const selected = values.map((item) => String(item ?? "").trim()).filter((item) => all.includes(item));
  return new Set(selected.length ? selected : all);
}

function mergeFacts(target, source) {
  for (const key of Object.keys(target)) target[key].push(...source[key]);
  return target;
}

function emptyFacts() {
  return { sessions: [], messages: [], parts: [], turns: [], traceEvents: [], checkpoints: [] };
}

function normalizeTestStatus(value) {
  const status = String(value ?? "").trim().toLowerCase().replace(/_/gu, "-");
  if (status === "active" || status === "busy" || status === "processing") return "running";
  if (status === "cancelled") return "canceled";
  return status || "unknown";
}

const testTerminalStatuses = new Set(["completed", "failed", "blocked", "timeout", "canceled", "idle"]);

async function getSseEvents(port, path, count) {
  const controller = new AbortController();
  const response = await fetch(`http://127.0.0.1:${port}${path}`, { signal: controller.signal });
  assert.equal(response.status, 200);
  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";
  const events = [];
  try {
    while (events.length < count) {
      const { value, done } = await reader.read();
      if (done) break;
      buffer += decoder.decode(value, { stream: true });
      for (;;) {
        const index = buffer.indexOf("\n\n");
        if (index < 0) break;
        const block = buffer.slice(0, index);
        buffer = buffer.slice(index + 2);
        events.push(parseSseBlock(block));
        if (events.length >= count) break;
      }
    }
  } finally {
    controller.abort();
  }
  return events;
}

function parseSseBlock(block) {
  const lines = block.split("\n");
  const event = lines.find((line) => line.startsWith("event:"))?.slice(6).trim() ?? "message";
  const data = lines.filter((line) => line.startsWith("data:")).map((line) => line.slice(5).trim()).join("\n");
  return { event, data: JSON.parse(data) };
}
