import { describe, expect, it, vi } from "vitest";
import type { ConvexHttpClient } from "convex/browser";
import type { BridgeContext, CommandEnvelope } from "@/lib/documents/bridge";
import { recordBridgeCommandArtifacts } from "@/lib/documents/bridge-log";

vi.mock("@/lib/convex/api", () => ({
  api: {
    bridgeLogs: {
      recordCommandLog: "bridgeLogs.recordCommandLog",
      recordDomainEvent: "bridgeLogs.recordDomainEvent",
    },
  },
}));

vi.mock("@/lib/convex/route", () => ({
  getAuthedConvexClient: vi.fn(),
}));

vi.mock("@/lib/documents/bridge", () => ({
  DocumentBridgeError: class DocumentBridgeError extends Error {
    constructor(
      message: string,
      public readonly status: number,
      public readonly code: string,
      public readonly details?: unknown,
    ) {
      super(message);
      this.name = "DocumentBridgeError";
    }
  },
}));

const context: BridgeContext = {
  deploymentId: null,
  projectId: null,
  workspaceId: "ws_1",
  requestId: "req_1",
  traceId: "trace_1",
  actor: {
    actorType: "user",
    actorId: "user_1",
    sessionId: "sess_1",
  },
  source: {
    channel: "next-route",
    client: "vitest",
  },
  tenantId: null,
  authToken: null,
  idempotencyKey: "idem_1",
  validateOnly: false,
  dryRun: false,
};

const envelope: CommandEnvelope<{ documentId: string }> = {
  name: "tree.node.archive",
  commandId: "cmd_1",
  idempotencyKey: "idem_1",
  actor: context.actor,
  source: context.source,


authToken: null,
  idempotencyKey: "idem_1",
  validateOnly: false,
  dryRun: false,
};

const envelope: CommandEnvelope<{ documentId: string }> = {
  name: "tree.node.archive",
  commandId: "cmd_1",
  idempotencyKey: "idem_1",
  actor: context.actor,
  source: context.source,
  target: {
    workspaceId: "ws_1",
    pageId: "page_1",
  },
  payload: {
    documentId: "page_1",
  },
  reason: null,
  refs: ["test"],
  dryRun: false,
  validateOnly: false,
};

describe("bridge-log", () => {
  it("记录成功 artifact 时应把 streamDelta 同步写入 domain event payload", async () => {
    const mutation = vi.fn().mockResolvedValue({ ok: true });

    await recordBridgeCommandArtifacts({
      context,
      envelope,
      client: { mutation } as unknown as ConvexHttpClient,
      now: "2026-04-26T00:00:00.000Z",
      domainEventType: "tree.node.archived",
      commandPayload: {
        documentId: "page_1",
        streamDelta: {
          op: "remove_document",
          documentId: "page_1",
        },
      },
    });

    expect(mutation).toHaveBeenNthCalledWith(
      2,
      "bridgeLogs.recordDomainEvent",
      expect.objectContaining({
        eventType: "tree.node.archived",
        payload: expect.objectContaining({
          schema: "mnote.tree.domain_event",
          schemaVersion: 1,
          eventType: "tree.node.archived",
          aggregate: {
            type: "page",
            id: "page_1",
          },
          command_id: "cmd_1",
          command_name: "tree.node.archive",
          command: {


eventType: "tree.node.archived",
        payload: expect.objectContaining({
          schema: "mnote.tree.domain_event",
          schemaVersion: 1,
          eventType: "tree.node.archived",
          aggregate: {
            type: "page",
            id: "page_1",
          },
          command_id: "cmd_1",
          command_name: "tree.node.archive",
          command: {
            id: "cmd_1",
            name: "tree.node.archive",
            idempotencyKey: "idem_1",
          },
          trace: {
            requestId: "req_1",
            traceId: "trace_1",
          },
          error: null,
          streamDelta: {
            op: "remove_document",
            documentId: "page_1",
          },
        }),
      }),
    );
  });

  it("记录 artifact 时应优先消费 Rust domainEventPlan", async () => {
    const mutation = vi.fn().mockResolvedValue({ ok: true });

    await recordBridgeCommandArtifacts({
      context,
      envelope,
      client: { mutation } as unknown as ConvexHttpClient,
      now: "2026-04-26T00:00:00.000Z",
      domainEventPlan: {
        family: "tree",
        schema: "mnote.tree.domain_event",
        schemaVersion: 1,
        eventType: "tree.subtree.moved",
        streamDelta: {
          op: "move_document",
          documentId: "page_1",
          parentId: "parent_1",
          sortOrder: 0,
        },
      },
      domainEventType: "legacy.should_not_win",
      commandPayload: {
        documentId: "page_1",
      },
    });

    expect(mutation).toHaveBeenNthCalledWith(
      2,
      "bridgeLogs.recordDomainEvent",
      expect.objectContaining({


},
      },
      domainEventType: "legacy.should_not_win",
      commandPayload: {
        documentId: "page_1",
      },
    });

    expect(mutation).toHaveBeenNthCalledWith(
      2,
      "bridgeLogs.recordDomainEvent",
      expect.objectContaining({
        eventType: "tree.subtree.moved",
        payload: expect.objectContaining({
          schema: "mnote.tree.domain_event",
          schemaVersion: 1,
          eventType: "tree.subtree.moved",
          streamDelta: {
            op: "move_document",
            documentId: "page_1",
            parentId: "parent_1",
            sortOrder: 0,
          },
        }),
      }),
    );
  });
});


import { api } from "@/lib/convex/api";
import {
  DocumentBridgeError,
  type BridgeContext,
  type BridgeTarget,
  type CommandEnvelope,
} from "@/lib/documents/bridge";
import { getAuthedConvexClient } from "@/lib/convex/route";
import {
  recordRustBridgeCommandArtifacts as recordRustBridgeCommandArtifactsFromRuntime,
  type RustTreeDomainEventPlan,
} from "@/lib/documents/rust-runtime";
import type { ConvexHttpClient } from "convex/browser";

export const recordRustBridgeCommandArtifacts = recordRustBridgeCommandArtifactsFromRuntime;

export type BridgeCommandLogStatus = "pending" | "succeeded" | "failed" | "rolled_back";
export type BridgeDomainEventStatus = "pending" | "committed" | "rejected" | "failed";

function buildPayloadSummary(commandName: string, context: BridgeContext): string {
  return `command=${commandName};request_id=${context.requestId};trace_id=${context.traceId}`;
}

function normalizeWorkspaceId(context: BridgeContext, target?: BridgeTarget | null): string | null {
  return target?.workspaceId?.trim() || context.workspaceId?.trim() || null;
}

function isRecord(value: unknown): value is Record<string, unknown> {
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}

function readStreamDelta(payload: unknown): unknown {
  if (!isRecord(payload)) {
    return null;
  }
  return payload.streamDelta ?? payload.stream_delta ?? null;
}

function normalizeDomainEventType(raw: unknown): string | null {
  if (typeof raw !== "string") {
    return null;
  }
  const trimmed = raw.trim();
  return trimmed || null;
}

function buildTreeDomainEventPayload(input: {
  context: BridgeContext;
  commandName: string;
  commandId: string;
  idempotencyKey?: string | null;
  eventType: string;
  aggregateType: string;
  aggregateId: string;
  streamDelta: unknown;
  error?: string | null;
}) {
  const payload: Record<string, unknown> = {
    schema: "mnote.tree.domain_event",
    schemaVersion: 1,


commandName: string;
  commandId: string;
  idempotencyKey?: string | null;
  eventType: string;
  aggregateType: string;
  aggregateId: string;
  streamDelta: unknown;
  error?: string | null;
}) {
  const payload: Record<string, unknown> = {
    schema: "mnote.tree.domain_event",
    schemaVersion: 1,
    eventType: input.eventType,
    aggregate: {
      type: input.aggregateType,
      id: input.aggregateId,
    },
    trace: {
      requestId: input.context.requestId,
      traceId: input.context.traceId,
    },
    command: {
      id: input.commandId,
      name: input.commandName,
      idempotencyKey: input.idempotencyKey ?? null,
    },
    error: input.error ?? null,
    // 兼容既有观测与 SSE 解析字段，正式消费者应优先使用上面的结构化字段。
    request_id: input.context.requestId,
    trace_id: input.context.traceId,
    command_id: input.commandId,
    command_name: input.commandName,
    idempotency_key: input.idempotencyKey ?? null,
  };
  if (input.streamDelta) {
    payload.streamDelta = input.streamDelta;
  }
  return payload;
}

export async function recordBridgeCommandArtifacts<T>(input: {
  context: BridgeContext;
  envelope: CommandEnvelope<T>;
  client?: ConvexHttpClient;
  commandPayload?: unknown;
  domainEventPlan?: RustTreeDomainEventPlan | null;
  domainEventType?: string | null;
  status?: BridgeCommandLogStatus;
  eventStatus?: BridgeDomainEventStatus;
  error?: string | null;
  now?: string;
}): Promise<void> {
  const workspaceId = normalizeWorkspaceId(input.context, input.envelope.target);
  if (!workspaceId) return;

  const client = input.client ?? (await getAuthedConvexClient()).client;
  const commandLogId = `clog_${input.envelope.commandId}`;
  const eventId = `evt_${input.envelope.commandId}`;
  const now = input.now ?? new Date().toISOString();
  const payload = input.commandPayload ?? input.envelope.payload;


eventStatus?: BridgeDomainEventStatus;
  error?: string | null;
  now?: string;
}): Promise<void> {
  const workspaceId = normalizeWorkspaceId(input.context, input.envelope.target);
  if (!workspaceId) return;

  const client = input.client ?? (await getAuthedConvexClient()).client;
  const commandLogId = `clog_${input.envelope.commandId}`;
  const eventId = `evt_${input.envelope.commandId}`;
  const now = input.now ?? new Date().toISOString();
  const payload = input.commandPayload ?? input.envelope.payload;
  const status = input.status ?? "succeeded";
  const eventStatus =
    input.eventStatus ??
    (status === "pending" ? "pending" : status === "failed" || status === "rolled_back" ? "failed" : "committed");
  const aggregateType = input.envelope.target?.blockId ? "block" : input.envelope.target?.pageId ? "page" : "workspace";
  const aggregateId =
    input.envelope.target?.blockId ?? input.envelope.target?.pageId ?? workspaceId;
  const streamDelta = input.domainEventPlan?.streamDelta ?? readStreamDelta(payload);
  const eventType =
    normalizeDomainEventType(input.domainEventPlan?.eventType) ??
    normalizeDomainEventType(input.domainEventType) ?? `${input.envelope.name}.requested`;

  await client.mutation(api.bridgeLogs.recordCommandLog, {
    workspaceId,
    id: commandLogId,
    requestId: input.context.requestId,
    traceId: input.context.traceId,
    commandId: input.envelope.commandId,
    commandName: input.envelope.name,
    actorId: input.context.actor.actorId,
    actorType: input.context.actor.actorType,
    sourceChannel: input.context.source.channel,
    sourceClient: input.context.source.client,
    status,
    targetPageId: input.envelope.target?.pageId ?? null,
    targetBlockId: input.envelope.target?.blockId ?? null,
    payload,
    payloadSummary: buildPayloadSummary(input.envelope.name, input.context),
    refs: input.envelope.refs,
    idempotencyKey: input.envelope.idempotencyKey,
    error: input.error ?? null,
    createdAt: now,
    finishedAt: status === "pending" ? null : now,
  });

  await client.mutation(api.bridgeLogs.recordDomainEvent, {
    workspaceId,
    id: eventId,
    requestId: input.context.requestId,
    traceId: input.context.traceId,
    commandId: input.envelope.commandId,
    commandLogId,
    eventType,
    aggregateType,
    aggregateId,
    eventVersion: 1,
    status: eventStatus,
    actorType: input.context.actor.actorType,


workspaceId,
    id: eventId,
    requestId: input.context.requestId,
    traceId: input.context.traceId,
    commandId: input.envelope.commandId,
    commandLogId,
    eventType,
    aggregateType,
    aggregateId,
    eventVersion: 1,
    status: eventStatus,
    actorType: input.context.actor.actorType,
    payload: buildTreeDomainEventPayload({
      context: input.context,
      commandName: input.envelope.name,
      commandId: input.envelope.commandId,
      idempotencyKey: input.envelope.idempotencyKey,
      eventType,
      aggregateType,
      aggregateId,
      streamDelta,
      error: input.error ?? null,
    }),
    createdAt: now,
  });
}

function normalizeBridgeErrorMessage(error: unknown) {
  if (error instanceof Error) {
    return error.message;
  }
  if (typeof error === "string" && error.trim()) {
    return error.trim();
  }
  return "未知 bridge 错误";
}

function resolveFailureStatuses(error: unknown): {
  status: BridgeCommandLogStatus;
  eventStatus: BridgeDomainEventStatus;
} {
  const details =
    error instanceof DocumentBridgeError && error.details && typeof error.details === "object"
      ? (error.details as Record<string, unknown>)
      : null;
  const reason = typeof details?.reason === "string" ? details.reason.trim() : "";
  if (reason === "rolled_back" || reason === "compensation_applied") {
    return {
      status: "rolled_back",
      eventStatus: "failed",
    };
  }
  if (error instanceof DocumentBridgeError && error.code === "REJECTED") {
    return {
      status: "failed",
      eventStatus: "rejected",
    };
  }
  return {
    status: "failed",


status: "rolled_back",
      eventStatus: "failed",
    };
  }
  if (error instanceof DocumentBridgeError && error.code === "REJECTED") {
    return {
      status: "failed",
      eventStatus: "rejected",
    };
  }
  return {
    status: "failed",
    eventStatus: "failed",
  };
}

export async function recordBridgeCommandFailureArtifacts<T>(input: {
  context: BridgeContext;
  envelope: CommandEnvelope<T>;
  client?: ConvexHttpClient;
  error: unknown;
}): Promise<void> {
  const { status, eventStatus } = resolveFailureStatuses(input.error);
  try {
    await recordBridgeCommandArtifacts({
      context: input.context,
      envelope: input.envelope,
      client: input.client,
      status,
      eventStatus,
      error: normalizeBridgeErrorMessage(input.error),
    });
  } catch (loggingError) {
    console.warn("[bridge-log] failure artifacts skipped:", loggingError);
  }
}


import { headers } from "next/headers";
import { ApiError } from "@/lib/api-utils";

type BridgeMeta = {
  requestId: string;
  traceId: string;
  queryName: string;
};

type DocumentMetaResponse<T> = {
  doc: T;
  meta: BridgeMeta;
};

function getHeaderFirstValue(value: string | null): string {
  return String(value || "")
    .split(",")[0]
    .trim();
}

function parseHostParts(host: string): { hostname: string; port: string } {
  try {
    const parsed = new URL(`http://${host}`);
    return {
      hostname: parsed.hostname,
      port: parsed.port,
    };
  } catch {
    return {
      hostname: host,
      port: "",
    };
  }
}

function isLocalHostname(hostname: string): boolean {
  const normalized = hostname.replace(/^\[(.*)\]$/, "$1").trim().toLowerCase();
  return (
    normalized === "localhost" ||
    normalized === "127.0.0.1" ||
    normalized === "::1" ||
    normalized === "0.0.0.0" ||
    normalized === "::"
  );
}

function buildServerRequestOriginCandidates(headerList: Headers): string[] {
  const forwardedProto = getHeaderFirstValue(headerList.get("x-forwarded-proto"));
  const forwardedHost = getHeaderFirstValue(headerList.get("x-forwarded-host"));
  const host = forwardedHost || getHeaderFirstValue(headerList.get("host"));

  if (!host) {
    throw new Error("缺少 host 头，无法构造 bridge 请求地址");
  }

  const { hostname, port } = parseHostParts(host);
  const candidates = new Set<string>();
  const addCandidate = (proto: string, candidateHost: string) => {
    const normalizedProto = String(proto || "http").trim().toLowerCase() || "http";
    const normalizedHost = String(candidateHost || "").trim();


const forwardedHost = getHeaderFirstValue(headerList.get("x-forwarded-host"));
  const host = forwardedHost || getHeaderFirstValue(headerList.get("host"));

  if (!host) {
    throw new Error("缺少 host 头，无法构造 bridge 请求地址");
  }

  const { hostname, port } = parseHostParts(host);
  const candidates = new Set<string>();
  const addCandidate = (proto: string, candidateHost: string) => {
    const normalizedProto = String(proto || "http").trim().toLowerCase() || "http";
    const normalizedHost = String(candidateHost || "").trim();
    if (!normalizedHost) return;
    candidates.add(`${normalizedProto}://${normalizedHost}`);
  };

  // 说明：优先保留浏览器实际请求的 origin，用于正常的同源自调用。
  addCandidate(forwardedProto || "http", host);

  // 说明：某些反代会把 x-forwarded-proto 设成 https，但本机 dev server 实际只监听 http。
  // 这里补一个 http 候选，避免 SSR 自请求被错误协议直接打挂。
  if ((forwardedProto || "").trim().toLowerCase() === "https") {
    addCandidate("http", host);
  }

  // 说明：localhost / 0.0.0.0 / ::1 这类本机地址在 Node 侧自调用时最容易踩解析差异，
  // 统一补 127.0.0.1 与 localhost 两个稳定候选，避免 fetch 因回环地址选择失败。
  if (isLocalHostname(hostname)) {
    const portSuffix = port ? `:${port}` : "";
    addCandidate("http", `127.0.0.1${portSuffix}`);
    addCandidate("http", `localhost${portSuffix}`);
  }

  return [...candidates];
}

function copyHeaderIfPresent(target: Headers, source: Headers, name: string) {
  const value = source.get(name);
  if (value) {
    target.set(name, value);
  }
}

async function fetchDocumentMetaResponse(url: URL, headersToSend: Headers): Promise<Response> {
  return await fetch(url, {
    method: "GET",
    headers: headersToSend,
    cache: "no-store",
  });
}

export async function fetchDocumentMetaViaBridge<T>(input: {
  documentId: string;
  workspaceId?: string | null;
}): Promise<DocumentMetaResponse<T> | null> {
  const headerList = await headers();
  const requestHeaders = new Headers();
  const originCandidates = buildServerRequestOriginCandidates(headerList);

  copyHeaderIfPresent(requestHeaders, headerList, "cookie");


});
}

export async function fetchDocumentMetaViaBridge<T>(input: {
  documentId: string;
  workspaceId?: string | null;
}): Promise<DocumentMetaResponse<T> | null> {
  const headerList = await headers();
  const requestHeaders = new Headers();
  const originCandidates = buildServerRequestOriginCandidates(headerList);

  copyHeaderIfPresent(requestHeaders, headerList, "cookie");
  copyHeaderIfPresent(requestHeaders, headerList, "authorization");
  copyHeaderIfPresent(requestHeaders, headerList, "x-request-id");
  copyHeaderIfPresent(requestHeaders, headerList, "x-trace-id");
  copyHeaderIfPresent(requestHeaders, headerList, "x-session-id");
  copyHeaderIfPresent(requestHeaders, headerList, "x-source-channel");
  copyHeaderIfPresent(requestHeaders, headerList, "x-source-client");
  copyHeaderIfPresent(requestHeaders, headerList, "user-agent");

  let response: Response | null = null;
  let lastError: unknown = null;
  const errorMessages: string[] = [];

  for (const origin of originCandidates) {
    const url = new URL("/api/documents/meta", origin);
    url.searchParams.set("documentId", input.documentId);
    if (input.workspaceId?.trim()) {
      url.searchParams.set("workspaceId", input.workspaceId.trim());
    }

    try {
      response = await fetchDocumentMetaResponse(url, requestHeaders);
      break;
    } catch (error) {
      lastError = error;
      const message = error instanceof Error ? error.message : String(error);
      errorMessages.push(`${url.toString()} => ${message}`);
    }
  }

  if (!response) {
    const message =
      errorMessages.length > 0
        ? `bridge 自请求失败：${errorMessages.join(" | ")}`
        : "bridge 自请求失败：未生成可用的请求地址";
    throw new Error(message, { cause: lastError instanceof Error ? lastError : undefined });
  }

  if (response.status === 404) {
    return null;
  }

  if (!response.ok) {
    let message = "加载页面元信息失败";
    try {
      const payload = (await response.json()) as { error?: string };
      if (typeof payload?.error === "string" && payload.error.trim()) {
        message = payload.error;
      }


if (response.status === 404) {
    return null;
  }

  if (!response.ok) {
    let message = "加载页面元信息失败";
    try {
      const payload = (await response.json()) as { error?: string };
      if (typeof payload?.error === "string" && payload.error.trim()) {
        message = payload.error;
      }
    } catch {
      // 说明：这里保留默认错误消息，避免 JSON 解析失败覆盖真实状态码。
    }
    throw new ApiError(message, response.status);
  }

  return (await response.json()) as DocumentMetaResponse<T>;
}


import { afterEach, describe, expect, it, vi } from "vitest";
import type { ConvexHttpClient } from "convex/browser";

vi.mock("@/lib/auth/authContext", () => ({
  HttpError: class HttpError extends Error {
    status: number;

    constructor(status: number, message: string) {
      super(message);
      this.status = status;
    }
  },
  requireAuthContext: vi.fn(),
}));

vi.mock("@/lib/api-utils", () => ({
  apiErrorResponse: vi.fn((message: string, status = 500, details?: unknown) => ({
    message,
    status,
    details,
  })),
}));

import {
  DocumentBridgeError,
  assertDocumentId,
  assertBlockId,
  assertNextBlock,
  assertOptionsPatch,
  assertStats,
  assertTitle,
  buildDocumentBridgeMutationRequest,
  buildDocumentBridgeContextWithActor,
  buildDocumentCommandEnvelope,
  buildDocumentQueryEnvelope,
  type BridgeContext,
} from "@/lib/documents/bridge";
import { executeMediaAssetWritebackBridgeCommand } from "@/lib/documents/media-asset-command-adapter";
import { executeMetadataBridgeCommand } from "@/lib/documents/metadata-command-adapter";
import { executePageLifecycleBridgeCommand } from "@/lib/documents/page-command-adapter";
import { executeSaveBridgeCommand } from "@/lib/documents/save-command-adapter";
import { buildDocumentSavePayload } from "@/lib/documents/save-contract";

vi.mock("@/lib/convex/route", () => ({
  getAuthedConvexClient: vi.fn(),
}));

vi.mock("@/lib/documents/bridge-log", () => ({
  recordBridgeCommandArtifacts: vi.fn(),
  recordBridgeCommandFailureArtifacts: vi.fn(),
  recordRustBridgeCommandArtifacts: vi.fn(),
}));

vi.mock("@/lib/documents/rust-runtime", () => ({
  resolveRustBridgeCommandPlan: vi.fn(),
  executeRustBridgeMutationTransport: vi.fn(),
  resolveRustBridgeQueryPlan: vi.fn(),
  executeRustBridgeQueryTransport: vi.fn(),
}));


recordBridgeCommandArtifacts: vi.fn(),
  recordBridgeCommandFailureArtifacts: vi.fn(),
  recordRustBridgeCommandArtifacts: vi.fn(),
}));

vi.mock("@/lib/documents/rust-runtime", () => ({
  resolveRustBridgeCommandPlan: vi.fn(),
  executeRustBridgeMutationTransport: vi.fn(),
  resolveRustBridgeQueryPlan: vi.fn(),
  executeRustBridgeQueryTransport: vi.fn(),
}));

const mockContext: BridgeContext = {
  deploymentId: null,
  projectId: null,
  workspaceId: "ws_1",
  requestId: "req_1",
  traceId: "trace_1",
  actor: {
    actorType: "user",
    actorId: "user_1",
    sessionId: "sess_1",
  },
  source: {
    channel: "next-route",
    client: "vitest",
  },
  tenantId: null,
  authToken: null,
  idempotencyKey: "idem_1",
  validateOnly: false,
  dryRun: false,
};

afterEach(() => {
  vi.clearAllMocks();
});

describe("documents bridge helpers", () => {
  it("assertDocumentId returns trimmed id", () => {
    expect(assertDocumentId("  doc_1  ")).toBe("doc_1");
  });

  it("assertDocumentId throws on empty value", () => {
    expect(() => assertDocumentId("   ")).toThrow(DocumentBridgeError);
  });

  it("assertTitle normalizes empty title", () => {
    expect(assertTitle("   ")).toBe("无标题");
  });

  it("assertBlockId returns trimmed block id", () => {
    expect(assertBlockId("  blk_1  ")).toBe("blk_1");
  });

  it("assertNextBlock accepts plain object", () => {
    expect(() => assertNextBlock({ id: "blk_1" })).not.toThrow();
  });

  it("assertStats accepts finite numeric stats", () => {


expect(assertTitle("   ")).toBe("无标题");
  });

  it("assertBlockId returns trimmed block id", () => {
    expect(assertBlockId("  blk_1  ")).toBe("blk_1");
  });

  it("assertNextBlock accepts plain object", () => {
    expect(() => assertNextBlock({ id: "blk_1" })).not.toThrow();
  });

  it("assertStats accepts finite numeric stats", () => {
    expect(() =>
      assertStats({
        wordCount: 1,
        characterCount: 2,
        blockCount: 3,
        todoTotal: 4,
        todoDone: 5,
      }),
    ).not.toThrow();
  });

  it("assertOptionsPatch accepts stable page options patch", () => {
    expect(() =>
      assertOptionsPatch({
        showToc: true,
        layoutDensity: "compact",
        embedDefaultBlockId: null,
      }),
    ).not.toThrow();
  });

  it("buildDocumentCommandEnvelope keeps context flags", () => {
    const envelope = buildDocumentCommandEnvelope({
      name: "documents.save",
      payload: { documentId: "doc_1" },
      context: mockContext,
      target: { workspaceId: "ws_1", pageId: "doc_1" },
    });

    expect(envelope.name).toBe("documents.save");
    expect(envelope.actor.actorId).toBe("user_1");
    expect(envelope.idempotencyKey).toBe("idem_1");
    expect(envelope.target?.pageId).toBe("doc_1");
  });

  it("buildDocumentQueryEnvelope keeps payload", () => {
    const envelope = buildDocumentQueryEnvelope({
      name: "documents.content.get",
      payload: { documentId: "doc_1" },
    });

    expect(envelope.payload).toEqual({ documentId: "doc_1" });
  });

  it("buildDocumentBridgeMutationRequest builds title update runtime request", () => {
    const envelope = buildDocumentCommandEnvelope({
      name: "documents.title.update",
      payload: {


const envelope = buildDocumentQueryEnvelope({
      name: "documents.content.get",
      payload: { documentId: "doc_1" },
    });

    expect(envelope.payload).toEqual({ documentId: "doc_1" });
  });

  it("buildDocumentBridgeMutationRequest builds title update runtime request", () => {
    const envelope = buildDocumentCommandEnvelope({
      name: "documents.title.update",
      payload: {
        documentId: "doc_1",
        workspaceId: "ws_1",
        title: "新标题",
      },
      context: mockContext,
      target: { workspaceId: "ws_1", pageId: "doc_1" },
    });

    const request = buildDocumentBridgeMutationRequest({
      context: mockContext,
      envelope,
      mapConvexArgs: (payload) => ({
        id: payload.documentId,
        title: payload.title,
      }),
    });

    expect(request.functionName).toBe("documents:updateTitle");
    expect(request.workspaceId).toBe("ws_1");
    expect(request.args).toEqual({
      id: "doc_1",
      title: "新标题",
    });
    expect(JSON.parse(request.payloadJson)).toEqual({
      kind: "command",
      name: "documents.title.update",
      request_id: "req_1",
      trace_id: "trace_1",
      deployment_id: null,
      project_id: null,
      workspace_id: "ws_1",
      tenant_id: null,
      idempotency_key: "idem_1",
      actor: {
        type: "user",
        id: "user_1",
        session_id: "sess_1",
      },
      source: {
        channel: "next-route",
        client: "vitest",
      },
    });
  });

  it("buildDocumentBridgeMutationRequest builds documents.save runtime request", () => {
    const payload = buildDocumentSavePayload({
      documentId: "doc_1",


session_id: "sess_1",
      },
      source: {
        channel: "next-route",
        client: "vitest",
      },
    });
  });

  it("buildDocumentBridgeMutationRequest builds documents.save runtime request", () => {
    const payload = buildDocumentSavePayload({
      documentId: "doc_1",
      workspaceId: "ws_1",
      revision: 7,
      content: [{ id: "block_1" }],
      conflictDetectionKey: "conflict_1",
      snapshotCapturedAt: "2026-04-14T14:30:00.000Z",
      blockCount: 1,
    });
    const envelope = buildDocumentCommandEnvelope({
      name: "documents.save",
      payload,
      context: mockContext,
      target: { workspaceId: "ws_1", pageId: "doc_1" },
    });

    const request = buildDocumentBridgeMutationRequest({
      context: mockContext,
      envelope,
      mapConvexArgs: (nextPayload) => ({
        id: nextPayload.documentId,
        content: nextPayload.content,
        expectedRevision: nextPayload.revision,
        conflictDetectionKey: nextPayload.conflictDetectionKey,
      }),
    });

    expect(request.functionName).toBe("documents:updateContent");
    expect(request.workspaceId).toBe("ws_1");
    expect(request.args).toEqual({
      id: "doc_1",
      content: [{ id: "block_1" }],
      expectedRevision: 7,
      conflictDetectionKey: "conflict_1",
    });
    expect(JSON.parse(request.payloadJson)).toMatchObject({
      kind: "command",
      name: "documents.save",
      workspace_id: "ws_1",
      request_id: "req_1",
      trace_id: "trace_1",
    });
  });

  it("buildDocumentBridgeMutationRequest builds documents.embed runtime request", () => {
    const payload = {
      ...buildDocumentSavePayload({
        documentId: "doc_2",
        workspaceId: "ws_1",
        revision: 3,


workspace_id: "ws_1",
      request_id: "req_1",
      trace_id: "trace_1",
    });
  });

  it("buildDocumentBridgeMutationRequest builds documents.embed runtime request", () => {
    const payload = {
      ...buildDocumentSavePayload({
        documentId: "doc_2",
        workspaceId: "ws_1",
        revision: 3,
        content: [{ id: "block_2", type: "pageReference" }],
        conflictDetectionKey: "conflict_3",
        blockCount: 1,
      }),
      sourceDocumentId: "doc_1",
      targetDocumentId: "doc_2",
      anchorBlockId: "anchor_1",
    };
    const envelope = buildDocumentCommandEnvelope({
      name: "documents.embed",
      payload,
      context: mockContext,
      target: { workspaceId: "ws_1", pageId: "doc_2" },
    });

    const request = buildDocumentBridgeMutationRequest({
      context: mockContext,
      envelope,
      mapConvexArgs: (nextPayload) => ({
        id: nextPayload.documentId,
        content: nextPayload.content,
        expectedRevision: nextPayload.revision,
        conflictDetectionKey: nextPayload.conflictDetectionKey,
        sourceDocumentId: nextPayload.sourceDocumentId,
        targetDocumentId: nextPayload.targetDocumentId,
        anchorBlockId: nextPayload.anchorBlockId,
      }),
    });

    expect(request.functionName).toBe("documents:updateContent");
    expect(request.workspaceId).toBe("ws_1");
    expect(request.args).toEqual({
      id: "doc_2",
      content: [{ id: "block_2", type: "pageReference" }],
      expectedRevision: 3,
      conflictDetectionKey: "conflict_3",
      sourceDocumentId: "doc_1",
      targetDocumentId: "doc_2",
      anchorBlockId: "anchor_1",
    });
    expect(JSON.parse(request.payloadJson)).toMatchObject({
      kind: "command",
      name: "documents.embed",
      workspace_id: "ws_1",
      request_id: "req_1",
      trace_id: "trace_1",
    });
  });


sourceDocumentId: "doc_1",
      targetDocumentId: "doc_2",
      anchorBlockId: "anchor_1",
    });
    expect(JSON.parse(request.payloadJson)).toMatchObject({
      kind: "command",
      name: "documents.embed",
      workspace_id: "ws_1",
      request_id: "req_1",
      trace_id: "trace_1",
    });
  });

  it("buildDocumentBridgeMutationRequest builds media asset writeback runtime request", () => {
    const envelope = buildDocumentCommandEnvelope({
      name: "media.assets.replace_storage",
      payload: {
        assetId: "asset_1",
        documentId: "doc_1",
        workspaceId: "ws_1",
        storageId: "storage_1",
        userId: "user_1",
      },
      context: mockContext,
      target: { workspaceId: "ws_1", pageId: "doc_1" },
    });

    const request = buildDocumentBridgeMutationRequest({
      context: mockContext,
      envelope,
      mapConvexArgs: (payload) => ({
        userId: payload.userId,
        id: payload.assetId,
        storageId: payload.storageId,
      }),
    });

    expect(request.functionName).toBe("mediaAssets:replaceStorageFromUpload");
    expect(request.workspaceId).toBe("ws_1");
    expect(request.args).toEqual({
      userId: "user_1",
      id: "asset_1",
      storageId: "storage_1",
    });
    expect(JSON.parse(request.payloadJson)).toMatchObject({
      kind: "command",
      name: "media.assets.replace_storage",
      workspace_id: "ws_1",
      request_id: "req_1",
      trace_id: "trace_1",
    });
  });

  it("buildDocumentBridgeMutationRequest builds page lifecycle runtime request", () => {
    const envelope = buildDocumentCommandEnvelope({
      name: "documents.create",
      payload: {
        documentId: "doc_1",
        workspaceId: "ws_1",
        parentId: "parent_1",


request_id: "req_1",
      trace_id: "trace_1",
    });
  });

  it("buildDocumentBridgeMutationRequest builds page lifecycle runtime request", () => {
    const envelope = buildDocumentCommandEnvelope({
      name: "documents.create",
      payload: {
        documentId: "doc_1",
        workspaceId: "ws_1",
        parentId: "parent_1",
        title: "无标题",
        accessScope: "private",
        content: [],
      },
      context: mockContext,
      target: { workspaceId: "ws_1", pageId: "doc_1" },
    });

    const request = buildDocumentBridgeMutationRequest({
      context: mockContext,
      envelope,
      mapConvexArgs: (payload) => ({
        id: payload.documentId,
        workspaceId: payload.workspaceId,
        parentId: payload.parentId,
        title: payload.title,
        accessScope: payload.accessScope,
        content: payload.content,
      }),
    });

    expect(request.functionName).toBe("documents:createWithParentReference");
    expect(request.args).toEqual({
      id: "doc_1",
      workspaceId: "ws_1",
      parentId: "parent_1",
      title: "无标题",
      accessScope: "private",
      content: [],
    });
  });

  it("buildDocumentBridgeContextWithActor keeps explicit actor and source", () => {
    const request = new Request("http://127.0.0.1:3001/api/onlyoffice/callback", {
      headers: {
        "x-request-id": "req_oo_1",
        "x-trace-id": "trace_oo_1",
      },
    });

    const context = buildDocumentBridgeContextWithActor({
      request,
      workspaceId: "ws_1",
      actor: {
        actorType: "service",
        actorId: "user_1",
        sessionId: "onlyoffice-callback",
      },


"x-trace-id": "trace_oo_1",
      },
    });

    const context = buildDocumentBridgeContextWithActor({
      request,
      workspaceId: "ws_1",
      actor: {
        actorType: "service",
        actorId: "user_1",
        sessionId: "onlyoffice-callback",
      },
      source: {
        channel: "onlyoffice-callback",
        client: "onlyoffice-document-server",
      },
      idempotencyKey: "asset_1",
    });

    expect(context.requestId).toBe("req_oo_1");
    expect(context.traceId).toBe("trace_oo_1");
    expect(context.actor).toEqual({
      actorType: "service",
      actorId: "user_1",
      sessionId: "onlyoffice-callback",
    });
    expect(context.source).toEqual({
      channel: "onlyoffice-callback",
      client: "onlyoffice-document-server",
    });
    expect(context.idempotencyKey).toBe("asset_1");
  });

  it("executeMetadataBridgeCommand routes title update through adapter", async () => {
    const { getAuthedConvexClient } = await import("@/lib/convex/route");
    const {
      resolveRustBridgeCommandPlan,
      executeRustBridgeMutationTransport,
    } = await import("@/lib/documents/rust-runtime");
    vi.mocked(getAuthedConvexClient).mockResolvedValue({
      auth: { userId: "user_1" },
      client: {
        mutation: vi.fn(),
      } as unknown as ConvexHttpClient,
    });
    vi.mocked(resolveRustBridgeCommandPlan).mockResolvedValue({
      kind: "command",
      commandName: "documents.title.update",
      commandId: "cmd_title_1",
      functionName: "documents:updateTitle",
      workspaceId: "ws_1",
      requestId: "req_1",
      traceId: "trace_1",
      actorId: "user_1",
      idempotencyKey: "idem_1",
      payloadJson: "{\"kind\":\"command\"}",
      argsJson: {
        id: "doc_1",
        title: "新标题",
      },


commandId: "cmd_title_1",
      functionName: "documents:updateTitle",
      workspaceId: "ws_1",
      requestId: "req_1",
      traceId: "trace_1",
      actorId: "user_1",
      idempotencyKey: "idem_1",
      payloadJson: "{\"kind\":\"command\"}",
      argsJson: {
        id: "doc_1",
        title: "新标题",
      },
    });
    vi.mocked(executeRustBridgeMutationTransport).mockResolvedValue({ ok: true });

    const result = await executeMetadataBridgeCommand({
      context: mockContext,
      envelope: buildDocumentCommandEnvelope({
        name: "documents.title.update",
        payload: {
          documentId: "doc_1",
          workspaceId: "ws_1",
          title: "新标题",
        },
        context: mockContext,
        target: { workspaceId: "ws_1", pageId: "doc_1" },
      }),
    });

    expect(resolveRustBridgeCommandPlan).toHaveBeenCalledWith({
      context: mockContext,
      envelope: expect.objectContaining({
        name: "documents.title.update",
      }),
    });
    expect(executeRustBridgeMutationTransport).toHaveBeenCalledWith({
      client: expect.any(Object),
      plan: expect.objectContaining({
        functionName: "documents:updateTitle",
        argsJson: {
          id: "doc_1",
          title: "新标题",
        },
      }),
    });
    expect(result.commandName).toBe("documents.title.update");
  });

  it("executeMetadataBridgeCommand routes options update through adapter", async () => {
    const { getAuthedConvexClient } = await import("@/lib/convex/route");
    const { recordRustBridgeCommandArtifacts } = await import("@/lib/documents/bridge-log");
    const {
      resolveRustBridgeCommandPlan,
      executeRustBridgeMutationTransport,
    } = await import("@/lib/documents/rust-runtime");
    vi.mocked(getAuthedConvexClient).mockResolvedValue({
      auth: { userId: "user_1" },
      client: {
        mutation: vi.fn(),
      } as unknown as ConvexHttpClient,


it("executeMetadataBridgeCommand routes options update through adapter", async () => {
    const { getAuthedConvexClient } = await import("@/lib/convex/route");
    const { recordRustBridgeCommandArtifacts } = await import("@/lib/documents/bridge-log");
    const {
      resolveRustBridgeCommandPlan,
      executeRustBridgeMutationTransport,
    } = await import("@/lib/documents/rust-runtime");
    vi.mocked(getAuthedConvexClient).mockResolvedValue({
      auth: { userId: "user_1" },
      client: {
        mutation: vi.fn(),
      } as unknown as ConvexHttpClient,
    });
    vi.mocked(resolveRustBridgeCommandPlan).mockResolvedValue({
      kind: "command",
      commandName: "documents.options.update",
      commandId: "cmd_options_1",
      functionName: "documents:updateOptions",
      workspaceId: "ws_1",
      requestId: "req_1",
      traceId: "trace_1",
      actorId: "user_1",
      idempotencyKey: "idem_1",
      payloadJson: "{\"kind\":\"command\"}",
      argsJson: {
        id: "doc_1",
        workspaceId: "ws_1",
        options: {
          showToc: true,
          layoutDensity: "compact",
          embedDefaultBlockId: null,
        },
      },
    });
    vi.mocked(executeRustBridgeMutationTransport).mockResolvedValue({
      ok: true,
      updated_at: "2026-04-26T00:00:00.000Z",
    });

    const result = await executeMetadataBridgeCommand({
      context: mockContext,
      envelope: buildDocumentCommandEnvelope({
        name: "documents.options.update",
        payload: {
          documentId: "doc_1",
          workspaceId: "ws_1",
          options: {
            showToc: true,
            layoutDensity: "compact",
            embedDefaultBlockId: null,
          },
        },
        context: mockContext,
        target: { workspaceId: "ws_1", pageId: "doc_1" },
      }),
    });

    expect(resolveRustBridgeCommandPlan).toHaveBeenCalledWith({
      context: mockContext,
      envelope: expect.objectContaining({


layoutDensity: "compact",
            embedDefaultBlockId: null,
          },
        },
        context: mockContext,
        target: { workspaceId: "ws_1", pageId: "doc_1" },
      }),
    });

    expect(resolveRustBridgeCommandPlan).toHaveBeenCalledWith({
      context: mockContext,
      envelope: expect.objectContaining({
        name: "documents.options.update",
      }),
    });
    expect(recordRustBridgeCommandArtifacts).toHaveBeenCalledWith({
      client: expect.any(Object),
      context: mockContext,
      envelope: expect.objectContaining({
        name: "documents.options.update",
      }),
      plan: expect.objectContaining({
        commandName: "documents.options.update",
        functionName: "documents:updateOptions",
      }),
      result: {
        ok: true,
        updated_at: "2026-04-26T00:00:00.000Z",
      },
    });
    expect(result.commandName).toBe("documents.options.update");
  });

  it("executeMetadataBridgeCommand routes stats update through rust runtime", async () => {
    const { getAuthedConvexClient } = await import("@/lib/convex/route");
    const { recordRustBridgeCommandArtifacts } = await import("@/lib/documents/bridge-log");
    const {
      resolveRustBridgeCommandPlan,
      executeRustBridgeMutationTransport,
    } = await import("@/lib/documents/rust-runtime");
    vi.mocked(getAuthedConvexClient).mockResolvedValue({
      auth: { userId: "user_1" },
      client: {
        mutation: vi.fn().mockResolvedValue({
          ok: true,
          updated_at: "2026-04-26T00:00:00.000Z",
        }),
      } as unknown as ConvexHttpClient,
    });
    vi.mocked(resolveRustBridgeCommandPlan).mockResolvedValue({
      kind: "command",
      commandName: "documents.stats.update",
      commandId: "cmd_stats_1",
      functionName: "documents:updateStats",
      workspaceId: "ws_1",
      requestId: "req_1",
      traceId: "trace_1",
      actorId: "user_1",
      idempotencyKey: "idem_1",
      payloadJson: "{\"kind\":\"command\"}",


});
    vi.mocked(resolveRustBridgeCommandPlan).mockResolvedValue({
      kind: "command",
      commandName: "documents.stats.update",
      commandId: "cmd_stats_1",
      functionName: "documents:updateStats",
      workspaceId: "ws_1",
      requestId: "req_1",
      traceId: "trace_1",
      actorId: "user_1",
      idempotencyKey: "idem_1",
      payloadJson: "{\"kind\":\"command\"}",
      argsJson: {
        id: "doc_1",
        workspaceId: "ws_1",
        wordCount: 12,
        characterCount: 34,
        blockCount: 5,
        todoTotal: 6,
        todoDone: 2,
      },
    });
    vi.mocked(executeRustBridgeMutationTransport).mockResolvedValue({
      ok: true,
      updated_at: "2026-04-26T00:00:00.000Z",
    });

    const result = await executeMetadataBridgeCommand({
      context: mockContext,
      envelope: buildDocumentCommandEnvelope({
        name: "documents.stats.update",
        payload: {
          documentId: "doc_1",
          workspaceId: "ws_1",
          stats: {
            wordCount: 12,
            characterCount: 34,
            blockCount: 5,
            todoTotal: 6,
            todoDone: 2,
          },
        },
        context: mockContext,
        target: { workspaceId: "ws_1", pageId: "doc_1" },
      }),
    });

    expect(resolveRustBridgeCommandPlan).toHaveBeenCalledWith({
      context: mockContext,
      envelope: expect.objectContaining({
        name: "documents.stats.update",
      }),
    });
    expect(recordRustBridgeCommandArtifacts).toHaveBeenCalledWith({
      client: expect.any(Object),
      context: mockContext,
      envelope: expect.objectContaining({
        name: "documents.stats.update",
      }),
      plan: expect.objectContaining({


context: mockContext,
      envelope: expect.objectContaining({
        name: "documents.stats.update",
      }),
    });
    expect(recordRustBridgeCommandArtifacts).toHaveBeenCalledWith({
      client: expect.any(Object),
      context: mockContext,
      envelope: expect.objectContaining({
        name: "documents.stats.update",
      }),
      plan: expect.objectContaining({
        commandName: "documents.stats.update",
        functionName: "documents:updateStats",
      }),
      result: {
        ok: true,
        updated_at: "2026-04-26T00:00:00.000Z",
      },
    });
    expect(result.commandName).toBe("documents.stats.update");
  });

  it("executeSaveBridgeCommand routes documents.save through adapter", async () => {
    const { getAuthedConvexClient } = await import("@/lib/convex/route");
    const { recordRustBridgeCommandArtifacts } = await import("@/lib/documents/bridge-log");
    const {
      resolveRustBridgeCommandPlan,
      executeRustBridgeMutationTransport,
    } = await import("@/lib/documents/rust-runtime");
    vi.mocked(getAuthedConvexClient).mockResolvedValue({
      auth: { userId: "user_1" },
      client: {
        mutation: vi.fn(),
      } as unknown as ConvexHttpClient,
    });
    vi.mocked(recordRustBridgeCommandArtifacts).mockResolvedValue(undefined);
    vi.mocked(resolveRustBridgeCommandPlan).mockResolvedValue({
      kind: "command",
      commandName: "documents.save",
      commandId: "cmd_save_1",
      functionName: "documents:updateContent",
      workspaceId: "ws_1",
      requestId: "req_1",
      traceId: "trace_1",
      actorId: "user_1",
      idempotencyKey: "idem_1",
      payloadJson: "{\"kind\":\"command\"}",
      argsJson: {
        id: "doc_1",
        content: [{ id: "block_1" }],
        expectedRevision: 7,
        conflictDetectionKey: "conflict_1",
      },
    });
    vi.mocked(executeRustBridgeMutationTransport).mockResolvedValue({
      revision: 7,
      conflict_detection_key: "conflict_1",
    });
    const previousBridgeArtifactCalls = vi.mocked(recordRustBridgeCommandArtifacts).mock.calls.length;


argsJson: {
        id: "doc_1",
        content: [{ id: "block_1" }],
        expectedRevision: 7,
        conflictDetectionKey: "conflict_1",
      },
    });
    vi.mocked(executeRustBridgeMutationTransport).mockResolvedValue({
      revision: 7,
      conflict_detection_key: "conflict_1",
    });
    const previousBridgeArtifactCalls = vi.mocked(recordRustBridgeCommandArtifacts).mock.calls.length;

    const payload = buildDocumentSavePayload({
      documentId: "doc_1",
      workspaceId: "ws_1",
      revision: 7,
      content: [{ id: "block_1" }],
      conflictDetectionKey: "conflict_1",
      snapshotCapturedAt: "2026-04-14T14:30:00.000Z",
      blockCount: 1,
    });

    const result = await executeSaveBridgeCommand({
      context: mockContext,
      envelope: buildDocumentCommandEnvelope({
        name: "documents.save",
        payload,
        context: mockContext,
        target: { workspaceId: "ws_1", pageId: "doc_1" },
      }),
    });

    expect(resolveRustBridgeCommandPlan).toHaveBeenCalledWith({
      context: mockContext,
      envelope: expect.objectContaining({
        name: "documents.save",
      }),
    });
    expect(executeRustBridgeMutationTransport).toHaveBeenCalledWith({
      client: expect.any(Object),
      plan: expect.objectContaining({
        functionName: "documents:updateContent",
        argsJson: {
          id: "doc_1",
          content: [{ id: "block_1" }],
          expectedRevision: 7,
          conflictDetectionKey: "conflict_1",
        },
      }),
    });
    expect(vi.mocked(recordRustBridgeCommandArtifacts).mock.calls.length).toBe(
      previousBridgeArtifactCalls + 1,
    );
    expect(recordRustBridgeCommandArtifacts).toHaveBeenCalledWith({
      client: expect.any(Object),
      context: mockContext,
      envelope: expect.objectContaining({
        name: "documents.save",
        target: { workspaceId: "ws_1", pageId: "doc_1" },


},
      }),
    });
    expect(vi.mocked(recordRustBridgeCommandArtifacts).mock.calls.length).toBe(
      previousBridgeArtifactCalls + 1,
    );
    expect(recordRustBridgeCommandArtifacts).toHaveBeenCalledWith({
      client: expect.any(Object),
      context: mockContext,
      envelope: expect.objectContaining({
        name: "documents.save",
        target: { workspaceId: "ws_1", pageId: "doc_1" },
        payload,
      }),
      plan: expect.objectContaining({
        commandName: "documents.save",
        functionName: "documents:updateContent",
      }),
      result: {
        revision: 7,
        conflict_detection_key: "conflict_1",
      },
    });
    expect(result.requestId).toBe("req_1");
    expect(result.traceId).toBe("trace_1");
    expect(result.commandName).toBe("documents.save");
  });

  it("executeSaveBridgeCommand 将冲突错误归一为 bridge rejected", async () => {
    const { getAuthedConvexClient } = await import("@/lib/convex/route");
    const {
      resolveRustBridgeCommandPlan,
      executeRustBridgeMutationTransport,
    } = await import("@/lib/documents/rust-runtime");
    vi.mocked(getAuthedConvexClient).mockResolvedValue({
      auth: { userId: "user_1" },
      client: {
        mutation: vi.fn(),
      } as unknown as ConvexHttpClient,
    });
    vi.mocked(resolveRustBridgeCommandPlan).mockResolvedValue({
      kind: "command",
      commandName: "documents.save",
      commandId: "cmd_save_1",
      functionName: "documents:updateContent",
      workspaceId: "ws_1",
      requestId: "req_1",
      traceId: "trace_1",
      actorId: "user_1",
      idempotencyKey: "idem_1",
      payloadJson: "{\"kind\":\"command\"}",
      argsJson: {
        id: "doc_1",
        content: [{ id: "block_1" }],
        expectedRevision: 7,
        conflictDetectionKey: "conflict_1",
      },
    });
    vi.mocked(executeRustBridgeMutationTransport).mockRejectedValue(
      new Error("正文内容已变更，请刷新后重试"),


actorId: "user_1",
      idempotencyKey: "idem_1",
      payloadJson: "{\"kind\":\"command\"}",
      argsJson: {
        id: "doc_1",
        content: [{ id: "block_1" }],
        expectedRevision: 7,
        conflictDetectionKey: "conflict_1",
      },
    });
    vi.mocked(executeRustBridgeMutationTransport).mockRejectedValue(
      new Error("正文内容已变更，请刷新后重试"),
    );

    const payload = buildDocumentSavePayload({
      documentId: "doc_1",
      workspaceId: "ws_1",
      revision: 7,
      content: [{ id: "block_1" }],
      conflictDetectionKey: "conflict_1",
    });

    await expect(
      executeSaveBridgeCommand({
        context: mockContext,
        envelope: buildDocumentCommandEnvelope({
          name: "documents.save",
          payload,
          context: mockContext,
          target: { workspaceId: "ws_1", pageId: "doc_1" },
        }),
      }),
    ).rejects.toMatchObject({
      name: "DocumentBridgeError",
      status: 409,
      code: "REJECTED",
    });
  });

  it("executePageLifecycleBridgeCommand routes page mutation through rust runtime", async () => {
    const { getAuthedConvexClient } = await import("@/lib/convex/route");
    const { recordRustBridgeCommandArtifacts } = await import("@/lib/documents/bridge-log");
    const {
      resolveRustBridgeCommandPlan,
      executeRustBridgeMutationTransport,
    } = await import("@/lib/documents/rust-runtime");
    vi.mocked(getAuthedConvexClient).mockResolvedValue({
      auth: { userId: "user_1" },
      client: {
        mutation: vi.fn(),
      } as unknown as ConvexHttpClient,
    });
    vi.mocked(recordRustBridgeCommandArtifacts).mockResolvedValue(undefined);
    vi.mocked(resolveRustBridgeCommandPlan).mockResolvedValue({
      kind: "command",
      commandName: "documents.create",
      commandId: "cmd_create_1",
      functionName: "documents:createWithParentReference",
      workspaceId: "ws_1",
      requestId: "req_1",


client: {
        mutation: vi.fn(),
      } as unknown as ConvexHttpClient,
    });
    vi.mocked(recordRustBridgeCommandArtifacts).mockResolvedValue(undefined);
    vi.mocked(resolveRustBridgeCommandPlan).mockResolvedValue({
      kind: "command",
      commandName: "documents.create",
      commandId: "cmd_create_1",
      functionName: "documents:createWithParentReference",
      workspaceId: "ws_1",
      requestId: "req_1",
      traceId: "trace_1",
      actorId: "user_1",
      idempotencyKey: "idem_1",
      payloadJson: "{\"kind\":\"command\"}",
      argsJson: {
        id: "doc_1",
        workspaceId: "ws_1",
        parentId: "parent_1",
        title: "无标题",
        accessScope: "private",
        content: [],
      },
    });
    vi.mocked(executeRustBridgeMutationTransport).mockResolvedValue({
      id: "doc_1",
      title: "无标题",
    });

    const result = await executePageLifecycleBridgeCommand({
      context: mockContext,
      envelope: buildDocumentCommandEnvelope({
        name: "documents.create",
        payload: {
          documentId: "doc_1",
          workspaceId: "ws_1",
          parentId: "parent_1",
          title: "无标题",
          accessScope: "private",
          content: [],
        },
        context: mockContext,
        target: { workspaceId: "ws_1", pageId: "doc_1" },
      }),
    });

    expect(resolveRustBridgeCommandPlan).toHaveBeenCalledWith({
      context: mockContext,
      envelope: expect.objectContaining({
        name: "documents.create",
      }),
    });
    expect(executeRustBridgeMutationTransport).toHaveBeenCalledWith({
      client: expect.any(Object),
      plan: expect.objectContaining({
        functionName: "documents:createWithParentReference",
      }),
    });
    expect(recordRustBridgeCommandArtifacts).toHaveBeenCalledWith({


context: mockContext,
      envelope: expect.objectContaining({
        name: "documents.create",
      }),
    });
    expect(executeRustBridgeMutationTransport).toHaveBeenCalledWith({
      client: expect.any(Object),
      plan: expect.objectContaining({
        functionName: "documents:createWithParentReference",
      }),
    });
    expect(recordRustBridgeCommandArtifacts).toHaveBeenCalledWith({
      client: expect.any(Object),
      context: mockContext,
      envelope: expect.objectContaining({
        name: "documents.create",
      }),
      plan: expect.objectContaining({
        commandName: "documents.create",
        functionName: "documents:createWithParentReference",
      }),
      result: {
        id: "doc_1",
        title: "无标题",
      },
    });
    expect(result.result).toEqual({
      id: "doc_1",
      title: "无标题",
    });
  });

  it("executeMediaAssetWritebackBridgeCommand routes callback writeback through adapter", async () => {
    const { recordRustBridgeCommandArtifacts } = await import("@/lib/documents/bridge-log");
    const {
      resolveRustBridgeCommandPlan,
      executeRustBridgeMutationTransport,
    } = await import("@/lib/documents/rust-runtime");
    vi.mocked(recordRustBridgeCommandArtifacts).mockResolvedValue(undefined);
    vi.mocked(resolveRustBridgeCommandPlan).mockResolvedValue({
      kind: "command",
      commandName: "media.assets.replace_storage",
      commandId: "cmd_asset_1",
      functionName: "mediaAssets:replaceStorageFromUpload",
      workspaceId: "ws_1",
      requestId: "req_1",
      traceId: "trace_1",
      actorId: "user_1",
      idempotencyKey: "idem_1",
      payloadJson: "{\"kind\":\"command\"}",
      argsJson: {
        userId: "user_1",
        id: "asset_1",
        documentId: "doc_1",
        workspaceId: "ws_1",
        storageId: "storage_1",
      },
    });
    vi.mocked(executeRustBridgeMutationTransport).mockResolvedValue({
      ok: true,


idempotencyKey: "idem_1",
      payloadJson: "{\"kind\":\"command\"}",
      argsJson: {
        userId: "user_1",
        id: "asset_1",
        documentId: "doc_1",
        workspaceId: "ws_1",
        storageId: "storage_1",
      },
    });
    vi.mocked(executeRustBridgeMutationTransport).mockResolvedValue({
      ok: true,
      fileUrl: "https://example.com/file.docx",
    });

    await executeMediaAssetWritebackBridgeCommand({
      client: {
        mutation: vi.fn(),
      } as unknown as ConvexHttpClient,
      context: mockContext,
      envelope: buildDocumentCommandEnvelope({
        name: "media.assets.replace_storage",
        payload: {
          assetId: "asset_1",
          documentId: "doc_1",
          workspaceId: "ws_1",
          storageId: "storage_1",
          userId: "user_1",
        },
        context: mockContext,
        target: { workspaceId: "ws_1", pageId: "doc_1" },
      }),
    });

    expect(resolveRustBridgeCommandPlan).toHaveBeenCalledWith({
      context: mockContext,
      envelope: expect.objectContaining({
        name: "media.assets.replace_storage",
      }),
    });
    expect(recordRustBridgeCommandArtifacts).toHaveBeenCalledWith({
      client: expect.any(Object),
      context: mockContext,
      envelope: expect.objectContaining({
        name: "media.assets.replace_storage",
      }),
      plan: expect.objectContaining({
        commandName: "media.assets.replace_storage",
        functionName: "mediaAssets:replaceStorageFromUpload",
      }),
      result: {
        ok: true,
        fileUrl: "https://example.com/file.docx",
      },
    });
  });
});


import { randomUUID } from "crypto";
import type { ConvexHttpClient } from "convex/browser";
import { HttpError, requireAuthContext } from "@/lib/auth/authContext";
import { apiErrorResponse } from "@/lib/api-utils";
import type { PageOptionsState } from "@/types/page-options";

export type BridgeActor = {
  actorType: string;
  actorId: string;
  sessionId: string | null;
};

export type BridgeSource = {
  channel: string;
  client: string;
};

export type BridgeTarget = {
  workspaceId?: string | null;
  pageId?: string | null;
  blockId?: string | null;
};

export type BridgeRequestMeta = {
  idempotencyKey: string | null;
  validateOnly: boolean;
  dryRun: boolean;
};

export type BridgeContext = {
  deploymentId: string | null;
  projectId: string | null;
  workspaceId: string | null;
  requestId: string;
  traceId: string;
  actor: BridgeActor;
  source: BridgeSource;
  tenantId: string | null;
  authToken: string | null;
  idempotencyKey: string | null;
  validateOnly: boolean;
  dryRun: boolean;
};

export type BridgeErrorCode =
  | "VALIDATION_ERROR"
  | "UNAUTHORIZED"
  | "FORBIDDEN"
  | "NOT_FOUND"
  | "TRANSPORT_ERROR"
  | "REJECTED";

export class DocumentBridgeError extends Error {
  constructor(
    message: string,
    public readonly status: number,
    public readonly code: BridgeErrorCode,
    public readonly details?: unknown,
  ) {
    super(message);


| "NOT_FOUND"
  | "TRANSPORT_ERROR"
  | "REJECTED";

export class DocumentBridgeError extends Error {
  constructor(
    message: string,
    public readonly status: number,
    public readonly code: BridgeErrorCode,
    public readonly details?: unknown,
  ) {
    super(message);
    this.name = "DocumentBridgeError";
  }
}

export type CommandEnvelope<T> = {
  name: string;
  commandId: string;
  idempotencyKey: string | null;
  actor: BridgeActor;
  source: BridgeSource;
  target: BridgeTarget | null;
  payload: T;
  preflightData?: Record<string, unknown> | null;
  reason: string | null;
  refs: string[];
  dryRun: boolean;
  validateOnly: boolean;
};

export type QueryEnvelope<T> = {
  name: string;
  payload: T;
};

export type DocumentBridgeMutationRequest<
  TArgs extends Record<string, unknown> = Record<string, unknown>,
> = {
  functionName: string;
  deploymentId: string | null;
  projectId: string | null;
  workspaceId: string | null;
  requestId: string;
  traceId: string;
  idempotencyKey: string | null;
  actorId: string;
  payloadJson: string;
  args: TArgs;
};

export type DocumentBridgeQueryRequest<
  TArgs extends Record<string, unknown> = Record<string, unknown>,
> = {
  functionName: string;
  deploymentId: string | null;
  projectId: string | null;
  workspaceId: string | null;
  requestId: string;
  traceId: string;


args: TArgs;
};

export type DocumentBridgeQueryRequest<
  TArgs extends Record<string, unknown> = Record<string, unknown>,
> = {
  functionName: string;
  deploymentId: string | null;
  projectId: string | null;
  workspaceId: string | null;
  requestId: string;
  traceId: string;
  actorId: string;
  payloadJson: string;
  args: TArgs;
};

const DOCUMENT_BRIDGE_QUERY_FUNCTIONS = {
  "documents.content.get": "documents:getContent",
  "documents.meta.get": "documents:getMeta",
  "blocks.get": "documents:getContent",
} as const satisfies Record<string, string>;

const DOCUMENT_BRIDGE_MUTATION_FUNCTIONS = {
  "documents.create": "documents:createWithParentReference",
  "documents.move": "documents:move",
  "documents.delete": "documents:softDelete",
  "documents.restore": "documents:restore",
  "documents.duplicate": "documents:duplicateWithMindmaps",
  "documents.copy_tree": "documents:copyTree",
  "documents.template": "documents:setTemplate",
  "documents.emptyTrashByWorkspace": "documents:emptyTrashByWorkspace",
  "documents.purge": "documents:purge",
  "documents.embed": "documents:updateContent",
  "blocks.patch": "documents:updateContent",
  "blocks.move": "documents:updateContent",
  "blocks.embed": "documents:updateContent",
  "documents.title.update": "documents:updateTitle",
  "documents.stats.update": "documents:updateStats",
  "documents.options.update": "documents:updateOptions",
  "documents.save": "documents:updateContent",
  "page.head.updateTitle": "documents:updateTitle",
  "page.layout.updateOptions": "documents:updateOptions",
  "page.body.save": "documents:updateContent",
  "mindmaps.delete": "mindmaps:softDelete",
  "mindmaps.restore": "mindmaps:restore",
  "mindmaps.purge": "mindmaps:purge",
  "mindmaps.emptyTrashByWorkspace": "mindmaps:emptyTrashByWorkspace",
  "media.assets.replace_storage": "mediaAssets:replaceStorageFromUpload",
} as const satisfies Record<string, string>;

function readHeaderValue(headerList: Headers, ...candidates: string[]): string | null {
  for (const candidate of candidates) {
    const value = headerList.get(candidate);
    if (typeof value === "string" && value.trim()) {
      return value.trim();
    }
  }
  return null;
}


"media.assets.replace_storage": "mediaAssets:replaceStorageFromUpload",
} as const satisfies Record<string, string>;

function readHeaderValue(headerList: Headers, ...candidates: string[]): string | null {
  for (const candidate of candidates) {
    const value = headerList.get(candidate);
    if (typeof value === "string" && value.trim()) {
      return value.trim();
    }
  }
  return null;
}

function toBooleanFlag(raw: string | null): boolean {
  if (!raw) return false;
  return raw === "1" || raw.toLowerCase() === "true";
}

function makeFallbackId(prefix: string): string {
  return `${prefix}_${randomUUID()}`;
}

export async function buildDocumentBridgeContext(input: {
  request: Request;
  workspaceId?: string | null;
  idempotencyKey?: string | null;
  validateOnly?: boolean;
  dryRun?: boolean;
}): Promise<BridgeContext> {
  let auth;
  try {
    auth = await requireAuthContext();
  } catch (error) {
    if (error instanceof HttpError) {
      throw new DocumentBridgeError(error.message || "未登录", error.status, "UNAUTHORIZED");
    }
    throw new DocumentBridgeError("未登录", 401, "UNAUTHORIZED");
  }

  const headerList = input.request.headers;
  const requestId =
    readHeaderValue(headerList, "x-request-id", "x-mnote-request-id") ?? makeFallbackId("req");
  const traceId =
    readHeaderValue(headerList, "x-trace-id", "x-mnote-trace-id", "x-request-id") ?? makeFallbackId("trace");
  const sessionId = readHeaderValue(headerList, "x-session-id", "x-mnote-session-id");
  const sourceChannel = readHeaderValue(headerList, "x-source-channel") ?? "next-route";
  const sourceClient = readHeaderValue(headerList, "x-source-client", "user-agent") ?? "wolai-frontend";
  const deploymentId =
    readHeaderValue(headerList, "x-deployment-id") ?? process.env.VERCEL_DEPLOYMENT_ID ?? null;
  const projectId = readHeaderValue(headerList, "x-project-id") ?? process.env.VERCEL_PROJECT_ID ?? null;
  const tenantId = readHeaderValue(headerList, "x-tenant-id");
  const authToken = readHeaderValue(headerList, "authorization");
  const idempotencyKey =
    input.idempotencyKey ?? readHeaderValue(headerList, "idempotency-key", "x-idempotency-key");
  const validateOnly = input.validateOnly ?? toBooleanFlag(readHeaderValue(headerList, "x-validate-only"));
  const dryRun = input.dryRun ?? toBooleanFlag(readHeaderValue(headerList, "x-dry-run"));

  return {
    deploymentId,
    projectId,


readHeaderValue(headerList, "x-deployment-id") ?? process.env.VERCEL_DEPLOYMENT_ID ?? null;
  const projectId = readHeaderValue(headerList, "x-project-id") ?? process.env.VERCEL_PROJECT_ID ?? null;
  const tenantId = readHeaderValue(headerList, "x-tenant-id");
  const authToken = readHeaderValue(headerList, "authorization");
  const idempotencyKey =
    input.idempotencyKey ?? readHeaderValue(headerList, "idempotency-key", "x-idempotency-key");
  const validateOnly = input.validateOnly ?? toBooleanFlag(readHeaderValue(headerList, "x-validate-only"));
  const dryRun = input.dryRun ?? toBooleanFlag(readHeaderValue(headerList, "x-dry-run"));

  return {
    deploymentId,
    projectId,
    workspaceId: input.workspaceId ?? null,
    requestId,
    traceId,
    actor: {
      actorType: "user",
      actorId: auth.userId,
      sessionId,
    },
    source: {
      channel: sourceChannel,
      client: sourceClient,
    },
    tenantId,
    authToken,
    idempotencyKey,
    validateOnly,
    dryRun,
  };
}

export function buildDocumentBridgeContextWithActor(input: {
  request: Request;
  actor: BridgeActor;
  workspaceId?: string | null;
  idempotencyKey?: string | null;
  validateOnly?: boolean;
  dryRun?: boolean;
  source?: Partial<BridgeSource>;
  authToken?: string | null;
}): BridgeContext {
  const headerList = input.request.headers;
  const requestId =
    readHeaderValue(headerList, "x-request-id", "x-mnote-request-id") ?? makeFallbackId("req");
  const traceId =
    readHeaderValue(headerList, "x-trace-id", "x-mnote-trace-id", "x-request-id") ?? makeFallbackId("trace");
  const sourceChannel = input.source?.channel?.trim() || readHeaderValue(headerList, "x-source-channel") || "next-route";
  const sourceClient =
    input.source?.client?.trim() ||
    readHeaderValue(headerList, "x-source-client", "user-agent") ||
    "wolai-frontend";
  const deploymentId =
    readHeaderValue(headerList, "x-deployment-id") ?? process.env.VERCEL_DEPLOYMENT_ID ?? null;
  const projectId = readHeaderValue(headerList, "x-project-id") ?? process.env.VERCEL_PROJECT_ID ?? null;
  const tenantId = readHeaderValue(headerList, "x-tenant-id");
  const authToken = input.authToken ?? readHeaderValue(headerList, "authorization");
  const idempotencyKey =
    input.idempotencyKey ?? readHeaderValue(headerList, "idempotency-key", "x-idempotency-key");
  const validateOnly = input.validateOnly ?? toBooleanFlag(readHeaderValue(headerList, "x-validate-only"));


const sourceClient =
    input.source?.client?.trim() ||
    readHeaderValue(headerList, "x-source-client", "user-agent") ||
    "wolai-frontend";
  const deploymentId =
    readHeaderValue(headerList, "x-deployment-id") ?? process.env.VERCEL_DEPLOYMENT_ID ?? null;
  const projectId = readHeaderValue(headerList, "x-project-id") ?? process.env.VERCEL_PROJECT_ID ?? null;
  const tenantId = readHeaderValue(headerList, "x-tenant-id");
  const authToken = input.authToken ?? readHeaderValue(headerList, "authorization");
  const idempotencyKey =
    input.idempotencyKey ?? readHeaderValue(headerList, "idempotency-key", "x-idempotency-key");
  const validateOnly = input.validateOnly ?? toBooleanFlag(readHeaderValue(headerList, "x-validate-only"));
  const dryRun = input.dryRun ?? toBooleanFlag(readHeaderValue(headerList, "x-dry-run"));

  return {
    deploymentId,
    projectId,
    workspaceId: input.workspaceId ?? null,
    requestId,
    traceId,
    actor: input.actor,
    source: {
      channel: sourceChannel,
      client: sourceClient,
    },
    tenantId,
    authToken,
    idempotencyKey,
    validateOnly,
    dryRun,
  };
}

export function buildDocumentCommandEnvelope<T>(input: {
  name: string;
  payload: T;
  context: BridgeContext;
  target?: BridgeTarget | null;
  preflightData?: Record<string, unknown> | null;
  reason?: string | null;
  refs?: string[];
}): CommandEnvelope<T> {
  return {
    name: input.name,
    commandId: makeFallbackId("cmd"),
    idempotencyKey: input.context.idempotencyKey,
    actor: input.context.actor,
    source: input.context.source,
    target: input.target ?? null,
    payload: input.payload,
    preflightData: input.preflightData ?? null,
    reason: input.reason ?? null,
    refs: input.refs ?? [],
    dryRun: input.context.dryRun,
    validateOnly: input.context.validateOnly,
  };
}

export function buildDocumentQueryEnvelope<T>(input: { name: string; payload: T }): QueryEnvelope<T> {
  return {


target: input.target ?? null,
    payload: input.payload,
    preflightData: input.preflightData ?? null,
    reason: input.reason ?? null,
    refs: input.refs ?? [],
    dryRun: input.context.dryRun,
    validateOnly: input.context.validateOnly,
  };
}

export function buildDocumentQueryEnvelope<T>(input: { name: string; payload: T }): QueryEnvelope<T> {
  return {
    name: input.name,
    payload: input.payload,
  };
}

function getDocumentBridgeMutationFunctionName(commandName: string): string {
  const functionName =
    DOCUMENT_BRIDGE_MUTATION_FUNCTIONS[
      commandName as keyof typeof DOCUMENT_BRIDGE_MUTATION_FUNCTIONS
    ];
  if (!functionName) {
    throw new DocumentBridgeError(
      `未注册文档 bridge mutation: ${commandName}`,
      500,
      "TRANSPORT_ERROR",
    );
  }
  return functionName;
}

function buildDocumentCommandPayloadJson(input: {
  context: BridgeContext;
  commandName: string;
  workspaceId: string | null;
  idempotencyKey: string | null;
}): string {
  return JSON.stringify({
    kind: "command",
    name: input.commandName,
    request_id: input.context.requestId,
    trace_id: input.context.traceId,
    deployment_id: input.context.deploymentId,
    project_id: input.context.projectId,
    workspace_id: input.workspaceId,
    tenant_id: input.context.tenantId,
    idempotency_key: input.idempotencyKey,
    actor: {
      type: input.context.actor.actorType,
      id: input.context.actor.actorId,
      session_id: input.context.actor.sessionId,
    },
    source: {
      channel: input.context.source.channel,
      client: input.context.source.client,
    },
  });
}


actor: {
      type: input.context.actor.actorType,
      id: input.context.actor.actorId,
      session_id: input.context.actor.sessionId,
    },
    source: {
      channel: input.context.source.channel,
      client: input.context.source.client,
    },
  });
}

function buildDocumentQueryPayloadJson(input: {
  context: BridgeContext;
  queryName: string;
  workspaceId: string | null;
}): string {
  return JSON.stringify({
    kind: "query",
    name: input.queryName,
    request_id: input.context.requestId,
    trace_id: input.context.traceId,
    deployment_id: input.context.deploymentId,
    project_id: input.context.projectId,
    workspace_id: input.workspaceId,
    tenant_id: input.context.tenantId,
    actor_id: input.context.actor.actorId,
    source: {
      channel: input.context.source.channel,
      client: input.context.source.client,
    },
  });
}

export function buildDocumentBridgeMutationRequest<
  TPayload,
  TArgs extends Record<string, unknown>,
>(input: {
  context: BridgeContext;
  envelope: CommandEnvelope<TPayload>;
  mapConvexArgs: (payload: TPayload) => TArgs;
}): DocumentBridgeMutationRequest<TArgs> {
  const workspaceId = input.envelope.target?.workspaceId ?? input.context.workspaceId ?? null;
  const idempotencyKey = input.envelope.idempotencyKey ?? input.context.idempotencyKey;

  return {
    functionName: getDocumentBridgeMutationFunctionName(input.envelope.name),
    deploymentId: input.context.deploymentId,
    projectId: input.context.projectId,
    workspaceId,
    requestId: input.context.requestId,
    traceId: input.context.traceId,
    idempotencyKey,
    actorId: input.context.actor.actorId,
    payloadJson: buildDocumentCommandPayloadJson({
      context: input.context,
      commandName: input.envelope.name,
      workspaceId,
      idempotencyKey,
    }),


projectId: input.context.projectId,
    workspaceId,
    requestId: input.context.requestId,
    traceId: input.context.traceId,
    idempotencyKey,
    actorId: input.context.actor.actorId,
    payloadJson: buildDocumentCommandPayloadJson({
      context: input.context,
      commandName: input.envelope.name,
      workspaceId,
      idempotencyKey,
    }),
    args: input.mapConvexArgs(input.envelope.payload),
  };
}

function getDocumentBridgeQueryFunctionName(queryName: string): string {
  const functionName =
    DOCUMENT_BRIDGE_QUERY_FUNCTIONS[
      queryName as keyof typeof DOCUMENT_BRIDGE_QUERY_FUNCTIONS
    ];
  if (!functionName) {
    throw new DocumentBridgeError(
      `未注册文档 bridge query: ${queryName}`,
      500,
      "TRANSPORT_ERROR",
    );
  }
  return functionName;
}

export function buildDocumentBridgeQueryRequest<
  TPayload,
  TArgs extends Record<string, unknown>,
>(input: {
  context: BridgeContext;
  envelope: QueryEnvelope<TPayload>;
  mapConvexArgs: (payload: TPayload) => TArgs;
}): DocumentBridgeQueryRequest<TArgs> {
  const workspaceId = input.context.workspaceId ?? null;
  return {
    functionName: getDocumentBridgeQueryFunctionName(input.envelope.name),
    deploymentId: input.context.deploymentId,
    projectId: input.context.projectId,
    workspaceId,
    requestId: input.context.requestId,
    traceId: input.context.traceId,
    actorId: input.context.actor.actorId,
    payloadJson: buildDocumentQueryPayloadJson({
      context: input.context,
      queryName: input.envelope.name,
      workspaceId,
    }),
    args: input.mapConvexArgs(input.envelope.payload),
  };
}

export async function executeDocumentBridgeMutationRequest<
  TArgs extends Record<string, unknown>,
  TResult,


payloadJson: buildDocumentQueryPayloadJson({
      context: input.context,
      queryName: input.envelope.name,
      workspaceId,
    }),
    args: input.mapConvexArgs(input.envelope.payload),
  };
}

export async function executeDocumentBridgeMutationRequest<
  TArgs extends Record<string, unknown>,
  TResult,
>(input: {
  client: ConvexHttpClient;
  mutation: unknown;
  request: DocumentBridgeMutationRequest<TArgs>;
}): Promise<TResult> {
  const mutate = input.client.mutation.bind(input.client) as (
    mutation: unknown,
    args: TArgs,
  ) => Promise<TResult>;
  return mutate(input.mutation, input.request.args);
}

export function assertDocumentId(documentId: string | null | undefined): string {
  const normalized = typeof documentId === "string" ? documentId.trim() : "";
  if (!normalized) {
    throw new DocumentBridgeError("缺少 documentId", 400, "VALIDATION_ERROR", [
      { field: "documentId", reason: "required" },
    ]);
  }
  return normalized;
}

export function assertTitle(title: string | null | undefined): string {
  if (typeof title !== "string") {
    throw new DocumentBridgeError("缺少 title", 400, "VALIDATION_ERROR", [
      { field: "title", reason: "required" },
    ]);
  }
  const normalized = title.trim() || "无标题";
  return normalized;
}

export function assertBlockId(blockId: string | null | undefined): string {
  const normalized = typeof blockId === "string" ? blockId.trim() : "";
  if (!normalized) {
    throw new DocumentBridgeError("缺少 blockId", 400, "VALIDATION_ERROR", [
      { field: "blockId", reason: "required" },
    ]);
  }
  return normalized;
}

export function assertNextBlock(nextBlock: unknown): asserts nextBlock is Record<string, unknown> {
  if (!nextBlock || typeof nextBlock !== "object" || Array.isArray(nextBlock)) {
    throw new DocumentBridgeError("缺少 nextBlock", 400, "VALIDATION_ERROR", [
      { field: "nextBlock", reason: "required object" },
    ]);
  }


{ field: "blockId", reason: "required" },
    ]);
  }
  return normalized;
}

export function assertNextBlock(nextBlock: unknown): asserts nextBlock is Record<string, unknown> {
  if (!nextBlock || typeof nextBlock !== "object" || Array.isArray(nextBlock)) {
    throw new DocumentBridgeError("缺少 nextBlock", 400, "VALIDATION_ERROR", [
      { field: "nextBlock", reason: "required object" },
    ]);
  }
}

export function assertStats(stats: unknown): asserts stats is {
  wordCount: number;
  characterCount: number;
  blockCount: number;
  todoTotal: number;
  todoDone: number;
} {
  if (!stats || typeof stats !== "object") {
    throw new DocumentBridgeError("缺少 stats", 400, "VALIDATION_ERROR", [
      { field: "stats", reason: "required" },
    ]);
  }
  const record = stats as Record<string, unknown>;
  const fields = ["wordCount", "characterCount", "blockCount", "todoTotal", "todoDone"] as const;
  for (const field of fields) {
    if (typeof record[field] !== "number" || !Number.isFinite(record[field] as number)) {
      throw new DocumentBridgeError("stats 字段非法", 400, "VALIDATION_ERROR", [
        { field, reason: "must be finite number" },
      ]);
    }
  }
}

const PAGE_FONT_VALUES = new Set<PageOptionsState["pageFont"]>(["default", "song", "kai"]);
const PAGE_LAYOUT_DENSITY_VALUES = new Set<PageOptionsState["layoutDensity"]>(["compact", "normal", "spacious"]);
const PAGE_OPTION_BOOLEAN_FIELDS = [
  "wideLayout",
  "smallText",
  "showHeadingNumbers",
  "showToc",
  "showStructure",
  "protectEditing",
  "showWordCount",
  "collapseBacklinks",
  "hideChildPages",
  "showBlockRefCount",
] as const satisfies readonly (keyof PageOptionsState)[];
const PAGE_OPTION_ALLOWED_FIELDS = new Set<keyof PageOptionsState>([
  ...PAGE_OPTION_BOOLEAN_FIELDS,
  "pageFont",
  "layoutDensity",
  "embedDefaultBlockId",
]);

export function assertOptionsPatch(
  options: unknown,


"hideChildPages",
  "showBlockRefCount",
] as const satisfies readonly (keyof PageOptionsState)[];
const PAGE_OPTION_ALLOWED_FIELDS = new Set<keyof PageOptionsState>([
  ...PAGE_OPTION_BOOLEAN_FIELDS,
  "pageFont",
  "layoutDensity",
  "embedDefaultBlockId",
]);

export function assertOptionsPatch(
  options: unknown,
): asserts options is Partial<Pick<PageOptionsState, keyof PageOptionsState>> {
  if (!options || typeof options !== "object" || Array.isArray(options)) {
    throw new DocumentBridgeError("缺少 options", 400, "VALIDATION_ERROR", [
      { field: "options", reason: "required object" },
    ]);
  }

  const record = options as Record<string, unknown>;
  const entries = Object.entries(record);
  if (entries.length === 0) {
    throw new DocumentBridgeError("缺少 options", 400, "VALIDATION_ERROR", [
      { field: "options", reason: "must not be empty" },
    ]);
  }

  for (const [field, value] of entries) {
    if (!PAGE_OPTION_ALLOWED_FIELDS.has(field as keyof PageOptionsState)) {
      throw new DocumentBridgeError("options 字段非法", 400, "VALIDATION_ERROR", [
        { field, reason: "unexpected field" },
      ]);
    }

    if ((PAGE_OPTION_BOOLEAN_FIELDS as readonly string[]).includes(field)) {
      if (typeof value !== "boolean") {
        throw new DocumentBridgeError("options 字段非法", 400, "VALIDATION_ERROR", [
          { field, reason: "must be boolean" },
        ]);
      }
      continue;
    }

    if (field === "pageFont") {
      if (typeof value !== "string" || !PAGE_FONT_VALUES.has(value as PageOptionsState["pageFont"])) {
        throw new DocumentBridgeError("options 字段非法", 400, "VALIDATION_ERROR", [
          { field, reason: "must be one of default/song/kai" },
        ]);
      }
      continue;
    }

    if (field === "layoutDensity") {
      if (
        typeof value !== "string" ||
        !PAGE_LAYOUT_DENSITY_VALUES.has(value as PageOptionsState["layoutDensity"])
      ) {
        throw new DocumentBridgeError("options 字段非法", 400, "VALIDATION_ERROR", [
          { field, reason: "must be one of compact/normal/spacious" },
        ]);


}
      continue;
    }

    if (field === "layoutDensity") {
      if (
        typeof value !== "string" ||
        !PAGE_LAYOUT_DENSITY_VALUES.has(value as PageOptionsState["layoutDensity"])
      ) {
        throw new DocumentBridgeError("options 字段非法", 400, "VALIDATION_ERROR", [
          { field, reason: "must be one of compact/normal/spacious" },
        ]);
      }
      continue;
    }

    if (field === "embedDefaultBlockId" && value !== null && typeof value !== "string") {
      throw new DocumentBridgeError("options 字段非法", 400, "VALIDATION_ERROR", [
        { field, reason: "must be string or null" },
      ]);
    }
  }
}

export function documentBridgeErrorResponse(error: unknown) {
  if (error instanceof DocumentBridgeError) {
    return apiErrorResponse(error.message, error.status, {
      code: error.code,
      details: error.details,
    });
  }
  if (error instanceof HttpError) {
    return apiErrorResponse(error.message || "未登录", error.status);
  }
  return apiErrorResponse(error instanceof Error ? error.message : "服务器错误", 500);
}


import { describe, expect, it } from "vitest";
import {
  assertDocumentMoveWriteOperationMatches,
  buildDocumentMoveOrderPlanFromDocuments,
} from "../../../convex/_utils/documentMoveOrder";

describe("documentMoveOrder Convex helper", () => {
  it("assertDocumentMoveWriteOperationMatches 应只接受 Rust tree write operation，并返回内部 move plan", () => {
    const actual = buildDocumentMoveOrderPlanFromDocuments({
      documents: [
        {
          id: "doc_a",
          parent_id: "source",
          sort_order: 0,
          created_at: "2026-04-25T00:00:01Z",
        },
        {
          id: "doc_b",
          parent_id: "source",
          sort_order: 1,
          created_at: "2026-04-25T00:00:02Z",
        },
      ],
      documentId: "doc_b",
      parentId: null,
      sortOrder: 0,
    });

    const normalized = assertDocumentMoveWriteOperationMatches(
      {
        family: "tree",
        schema: "mnote.tree.write_operation",
        schemaVersion: 1,
        operation: "tree.subtree.move.write",
        workspaceId: "ws_1",
        documentId: "doc_b",
        fromParentId: "source",
        toParentId: null,
        requestedSortOrder: 0,
        normalizedSortOrder: 0,
        patches: [
          {
            documentId: "doc_b",
            parentId: null,
            sortOrder: 0,
            moved: true,
          },
        ],
      },
      actual,
    );

    expect(normalized).toEqual({
      documentId: "doc_b",
      fromParentId: "source",
      toParentId: null,
      requestedSortOrder: 0,
      normalizedSortOrder: 0,
      patches: [
        {


},
      actual,
    );

    expect(normalized).toEqual({
      documentId: "doc_b",
      fromParentId: "source",
      toParentId: null,
      requestedSortOrder: 0,
      normalizedSortOrder: 0,
      patches: [
        {
          documentId: "doc_b",
          parentId: null,
          sortOrder: 0,
          moved: true,
        },
      ],
    });

    expect(() => assertDocumentMoveWriteOperationMatches({ ...actual, operation: "documents.move" }, actual)).toThrow(
      "Rust move write operation 与 Convex 当前排序状态不一致",
    );
  });

  it("assertDocumentMoveWriteOperationMatches 应拒绝缺少正式 write operation 外壳的旧 move plan", () => {
    const actual = buildDocumentMoveOrderPlanFromDocuments({
      documents: [
        {
          id: "doc_a",
          parent_id: "source",
          sort_order: 0,
          created_at: "2026-04-25T00:00:01Z",
        },
        {
          id: "doc_b",
          parent_id: "source",
          sort_order: 1,
          created_at: "2026-04-25T00:00:02Z",
        },
      ],
      documentId: "doc_b",
      parentId: null,
      sortOrder: 0,
    });

    expect(() => assertDocumentMoveWriteOperationMatches(actual, actual)).toThrow(
      "Rust move write operation 与 Convex 当前排序状态不一致",
    );
  });
});


import { describe, expect, it } from "vitest";
import {
  assertDocumentMoveWriteOperationMatches,
  buildDocumentMoveOrderPlanFromDocuments,
} from "../../../convex/_utils/documentMoveOrder";

describe("documentMoveOrder", () => {
  it("按 Rust canonical move order plan 计算跨父移动和越界 clamp", () => {
    const plan = buildDocumentMoveOrderPlanFromDocuments({
      documents: [
        {
          id: "target",
          parent_id: null,
          sort_order: 0,
          created_at: "2026-04-25T00:00:00Z",
        },
        {
          id: "doc_a",
          parent_id: "source",
          sort_order: 0,
          created_at: "2026-04-25T00:00:01Z",
        },
        {
          id: "doc_b",
          parent_id: "source",
          sort_order: 1,
          created_at: "2026-04-25T00:00:02Z",
        },
        {
          id: "doc_c",
          parent_id: "target",
          sort_order: 0,
          created_at: "2026-04-25T00:00:03Z",
        },
      ],
      documentId: "doc_b",
      parentId: "target",
      sortOrder: -2,
    });

    expect(plan).toEqual({
      documentId: "doc_b",
      fromParentId: "source",
      toParentId: "target",
      requestedSortOrder: -2,
      normalizedSortOrder: 0,
      patches: [
        {
          documentId: "doc_b",
          parentId: "target",
          sortOrder: 0,
          moved: true,
        },
        {
          documentId: "doc_c",
          parentId: "target",
          sortOrder: 1,
          moved: false,
        },
      ],


documentId: "doc_b",
          parentId: "target",
          sortOrder: 0,
          moved: true,
        },
        {
          documentId: "doc_c",
          parentId: "target",
          sortOrder: 1,
          moved: false,
        },
      ],
    });
  });

  it("treeWriteOperation 与当前排序状态不一致时拒绝执行", () => {
    const actual = buildDocumentMoveOrderPlanFromDocuments({
      documents: [
        {
          id: "doc_a",
          parent_id: "source",
          sort_order: 0,
          created_at: "2026-04-25T00:00:01Z",
        },
        {
          id: "doc_b",
          parent_id: "source",
          sort_order: 1,
          created_at: "2026-04-25T00:00:02Z",
        },
        {
          id: "doc_c",
          parent_id: "target",
          sort_order: 0,
          created_at: "2026-04-25T00:00:03Z",
        },
      ],
      documentId: "doc_b",
      parentId: "target",
      sortOrder: 0,
    });

    expect(() =>
      assertDocumentMoveWriteOperationMatches(
        {
          family: "tree",
          schema: "mnote.tree.write_operation",
          schemaVersion: 1,
          operation: "tree.subtree.move.write",
          workspaceId: "ws_1",
          ...actual,
          patches: actual.patches.map((patch) =>
            patch.documentId === "doc_c" ? { ...patch, sortOrder: 9 } : patch,
          ),
        },
        actual,
      ),
    ).toThrow("Rust move write operation 与 Convex 当前排序状态不一致");
  });
});


operation: "tree.subtree.move.write",
          workspaceId: "ws_1",
          ...actual,
          patches: actual.patches.map((patch) =>
            patch.documentId === "doc_c" ? { ...patch, sortOrder: 9 } : patch,
          ),
        },
        actual,
      ),
    ).toThrow("Rust move write operation 与 Convex 当前排序状态不一致");
  });
});


import { describe, expect, it } from "vitest";
import {
  compareDocumentCanonicalOrder,
  getCanonicalDocumentByBusinessId,
  getCanonicalParentDocumentId,
  pickCanonicalDocumentRecordsByBusinessId,
  pickCanonicalDocumentRecord,
} from "../../../convex/_utils/documentRecord";

describe("pickCanonicalDocumentRecord", () => {
  it("同一 business id 出现重复记录时优先选择最新未删除记录", () => {
    const selected = pickCanonicalDocumentRecord([
      {
        _id: "doc_old",
        deleted_at: null,
        created_at: "2026-04-14T00:00:00.000Z",
        updated_at: "2026-04-14T00:00:01.000Z",
      },
      {
        _id: "doc_deleted",
        deleted_at: "2026-04-14T00:00:03.000Z",
        created_at: "2026-04-14T00:00:02.000Z",
        updated_at: "2026-04-14T00:00:03.000Z",
      },
      {
        _id: "doc_new",
        deleted_at: null,
        created_at: "2026-04-14T00:00:02.000Z",
        updated_at: "2026-04-14T00:00:04.000Z",
      },
    ]);

    expect(selected?._id).toBe("doc_new");
  });

  it("全部已删除时仍稳定选择最新记录，避免 first() 命中漂移", () => {
    const selected = pickCanonicalDocumentRecord([
      {
        _id: "doc_deleted_old",
        deleted_at: "2026-04-14T00:00:01.000Z",
        created_at: "2026-04-14T00:00:00.000Z",
        updated_at: "2026-04-14T00:00:01.000Z",
      },
      {
        _id: "doc_deleted_new",
        deleted_at: "2026-04-14T00:00:03.000Z",
        created_at: "2026-04-14T00:00:02.000Z",
        updated_at: "2026-04-14T00:00:03.000Z",
      },
    ]);

    expect(selected?._id).toBe("doc_deleted_new");
  });

  it("时间戳相同时用 _id 稳定打破平手，避免 collect 后结果不稳定", () => {
    const selected = pickCanonicalDocumentRecord([
      {
        _id: "doc_b",
        deleted_at: null,
        created_at: "2026-04-14T00:00:00.000Z",


},
    ]);

    expect(selected?._id).toBe("doc_deleted_new");
  });

  it("时间戳相同时用 _id 稳定打破平手，避免 collect 后结果不稳定", () => {
    const selected = pickCanonicalDocumentRecord([
      {
        _id: "doc_b",
        deleted_at: null,
        created_at: "2026-04-14T00:00:00.000Z",
        updated_at: "2026-04-14T00:00:00.000Z",
      },
      {
        _id: "doc_a",
        deleted_at: null,
        created_at: "2026-04-14T00:00:00.000Z",
        updated_at: "2026-04-14T00:00:00.000Z",
      },
    ]);

    expect(selected?._id).toBe("doc_a");
  });
});

describe("compareDocumentCanonicalOrder", () => {
  it("未删除记录始终排在已删除记录前面，供页面权限/内容读链复用", () => {
    const records = [
      {
        _id: "deleted",
        deleted_at: "2026-04-14T00:00:03.000Z",
        created_at: "2026-04-14T00:00:02.000Z",
        updated_at: "2026-04-14T00:00:03.000Z",
      },
      {
        _id: "alive",
        deleted_at: null,
        created_at: "2026-04-14T00:00:01.000Z",
        updated_at: "2026-04-14T00:00:01.000Z",
      },
    ];

    const sorted = [...records].sort(compareDocumentCanonicalOrder);
    expect(sorted.map((record) => record._id)).toEqual(["alive", "deleted"]);
  });
});

describe("canonical document helper", () => {
  const createCtx = (records: Array<Record<string, unknown>>) => ({
    db: {
      query: () => ({
        withIndex: () => ({
          collect: async () => records,
        }),
      }),
    },
  });

  it("按 business id 查询时返回 canonical 记录，避免高风险读链命中旧记录", async () => {


describe("canonical document helper", () => {
  const createCtx = (records: Array<Record<string, unknown>>) => ({
    db: {
      query: () => ({
        withIndex: () => ({
          collect: async () => records,
        }),
      }),
    },
  });

  it("按 business id 查询时返回 canonical 记录，避免高风险读链命中旧记录", async () => {
    const ctx = createCtx([
      {
        _id: "doc_old",
        id: "doc_1",
        parent_id: "parent_old",
        deleted_at: null,
        created_at: "2026-04-14T00:00:00.000Z",
        updated_at: "2026-04-14T00:00:01.000Z",
      },
      {
        _id: "doc_new",
        id: "doc_1",
        parent_id: "parent_new",
        deleted_at: null,
        created_at: "2026-04-14T00:00:02.000Z",
        updated_at: "2026-04-14T00:00:03.000Z",
      },
    ]);

    const doc = await getCanonicalDocumentByBusinessId<any>(ctx, "doc_1");
    expect(doc?._id).toBe("doc_new");
  });

  it("父链辅助函数返回 canonical 父页面 id，供共享/评论祖先扫描复用", async () => {
    const ctx = createCtx([
      {
        _id: "doc_old",
        id: "doc_1",
        parent_id: "parent_old",
        deleted_at: null,
        created_at: "2026-04-14T00:00:00.000Z",
        updated_at: "2026-04-14T00:00:01.000Z",
      },
      {
        _id: "doc_new",
        id: "doc_1",
        parent_id: "parent_new",
        deleted_at: null,
        created_at: "2026-04-14T00:00:02.000Z",
        updated_at: "2026-04-14T00:00:03.000Z",
      },
    ]);

    const parentId = await getCanonicalParentDocumentId(ctx, "doc_1");
    expect(parentId).toBe("parent_new");
  });

  it("父链辅助函数命中已删除旧记录时仍回退到最新未删除父级，供收藏祖先权限复用", async () => {


parent_id: "parent_new",
        deleted_at: null,
        created_at: "2026-04-14T00:00:02.000Z",
        updated_at: "2026-04-14T00:00:03.000Z",
      },
    ]);

    const parentId = await getCanonicalParentDocumentId(ctx, "doc_1");
    expect(parentId).toBe("parent_new");
  });

  it("父链辅助函数命中已删除旧记录时仍回退到最新未删除父级，供收藏祖先权限复用", async () => {
    const ctx = createCtx([
      {
        _id: "doc_deleted",
        id: "doc_2",
        parent_id: "parent_deleted",
        deleted_at: "2026-04-14T00:00:04.000Z",
        created_at: "2026-04-14T00:00:00.000Z",
        updated_at: "2026-04-14T00:00:04.000Z",
      },
      {
        _id: "doc_alive",
        id: "doc_2",
        parent_id: "parent_alive",
        deleted_at: null,
        created_at: "2026-04-14T00:00:01.000Z",
        updated_at: "2026-04-14T00:00:03.000Z",
      },
    ]);

    const parentId = await getCanonicalParentDocumentId(ctx, "doc_2");
    expect(parentId).toBe("parent_alive");
  });
});

describe("pickCanonicalDocumentRecordsByBusinessId", () => {
  it("同一 workspace 扫描结果里 business id 重复时应先折叠成 canonical 记录，供树命令写链复用", () => {
    const records = pickCanonicalDocumentRecordsByBusinessId([
      {
        _id: "doc_old",
        id: "doc_1",
        parent_id: "parent_old",
        deleted_at: null,
        created_at: "2026-04-14T00:00:00.000Z",
        updated_at: "2026-04-14T00:00:01.000Z",
      },
      {
        _id: "doc_new",
        id: "doc_1",
        parent_id: "parent_new",
        deleted_at: null,
        created_at: "2026-04-14T00:00:02.000Z",
        updated_at: "2026-04-14T00:00:03.000Z",
      },
      {
        _id: "doc_2",
        id: "doc_2",
        parent_id: null,
        deleted_at: null,


import { describe, expect, it } from "vitest";
import { buildParentById, isAncestorOf } from "../../../convex/_utils/documentTree";

describe("document tree helper", () => {
  it("构建父链映射时保留页面与父页面关系，供树命令 legality 复用", () => {
    const parentById = buildParentById([
      { id: "root", parent_id: null },
      { id: "child", parent_id: "root" },
      { id: "leaf", parent_id: "child" },
    ]);

    expect(parentById.get("root")).toBeNull();
    expect(parentById.get("child")).toBe("root");
    expect(parentById.get("leaf")).toBe("child");
  });

  it("祖先判断应能识别多级后代，避免把页面移动到自己的子树下面", () => {
    const parentById = buildParentById([
      { id: "root", parent_id: null },
      { id: "child", parent_id: "root" },
      { id: "leaf", parent_id: "child" },
    ]);

    expect(isAncestorOf("root", "leaf", parentById)).toBe(true);
    expect(isAncestorOf("child", "leaf", parentById)).toBe(true);
    expect(isAncestorOf("leaf", "root", parentById)).toBe(false);
    expect(isAncestorOf("missing", "leaf", parentById)).toBe(false);
  });
});


import { describe, expect, it } from "vitest";
import { buildVisibleDocumentIds } from "../../../convex/_utils/documentVisibility";

describe("buildVisibleDocumentIds", () => {
  const docs = [
    { id: "owner-root", user_id: "u1", parent_id: null, access_scope: "private", deleted_at: null },
    { id: "public-root", user_id: "u2", parent_id: null, access_scope: "public", deleted_at: null },
    { id: "shared-root", user_id: "u2", parent_id: null, access_scope: "private", deleted_at: null },
    { id: "shared-child", user_id: "u2", parent_id: "shared-root", access_scope: "private", deleted_at: null },
    { id: "group-root", user_id: "u3", parent_id: null, access_scope: "private", deleted_at: null },
    { id: "group-child", user_id: "u3", parent_id: "group-root", access_scope: "private", deleted_at: null },
    { id: "deleted-public", user_id: "u4", parent_id: null, access_scope: "public", deleted_at: "2026-05-12T00:00:00.000Z" },
  ] as const;

  it("允许 owner、public、直接分享与继承分享的文档可见", () => {
    const visible = buildVisibleDocumentIds({
      userId: "u1",
      docs,
      directShares: [{ document_id: "shared-root", include_descendants: true }],
      directGroupShares: [],
    });

    expect(Array.from(visible).sort()).toEqual([
      "owner-root",
      "public-root",
      "shared-child",
      "shared-root",
    ]);
  });

  it("允许群组分享向下继承，但不会包含已删除文档", () => {
    const visible = buildVisibleDocumentIds({
      userId: "u9",
      docs,
      directShares: [],
      directGroupShares: [{ document_id: "group-root", include_descendants: true }],
      includeDeletedDocuments: false,
    });

    expect(Array.from(visible).sort()).toEqual([
      "group-child",
      "group-root",
      "public-root",
    ]);
    expect(visible.has("deleted-public")).toBe(false);
  });
});


import type { ConvexHttpClient } from "convex/browser";
import {
  type CommandEnvelope,
  type BridgeContext,
} from "@/lib/documents/bridge";
import {
  recordBridgeCommandFailureArtifacts,
  recordRustBridgeCommandArtifacts,
} from "@/lib/documents/bridge-log";
import {
  executeRustBridgeMutationTransport,
  resolveRustBridgeCommandPlan,
} from "@/lib/documents/rust-runtime";

export type MediaAssetReplaceStoragePayload = {
  assetId: string;
  documentId: string;
  workspaceId: string | null;
  storageId: string;
  userId: string;
};

export type MediaAssetWritebackExecutionResult = {
  requestId: string;
  traceId: string;
  commandId: string;
  commandName: string;
};

export async function executeMediaAssetWritebackBridgeCommand(input: {
  client: ConvexHttpClient;
  context: BridgeContext;
  envelope: CommandEnvelope<MediaAssetReplaceStoragePayload>;
}): Promise<MediaAssetWritebackExecutionResult> {
  const plan = await resolveRustBridgeCommandPlan({
    context: input.context,
    envelope: input.envelope,
  });
  try {
    const transportResult = await executeRustBridgeMutationTransport({
      client: input.client,
      plan,
    });
    await recordRustBridgeCommandArtifacts({
      client: input.client,
      context: input.context,
      envelope: input.envelope,
      plan,
      result: transportResult,
    });
  } catch (error) {
    await recordBridgeCommandFailureArtifacts({
      client: input.client,
      context: input.context,
      envelope: input.envelope,
      error,
    });
    throw error;
  }


result: transportResult,
    });
  } catch (error) {
    await recordBridgeCommandFailureArtifacts({
      client: input.client,
      context: input.context,
      envelope: input.envelope,
      error,
    });
    throw error;
  }

  return {
    requestId: input.context.requestId,
    traceId: input.context.traceId,
    commandId: input.envelope.commandId,
    commandName: input.envelope.name,
  };
}