import { createServer, type IncomingMessage, type Server, type ServerResponse } from "node:http";
import { randomUUID, timingSafeEqual } from "node:crypto";
import { AsyncLocalStorage } from "node:async_hooks";
import { existsSync } from "node:fs";
import { readFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath, URL } from "node:url";
import { gunzipSync } from "node:zlib";
import { log } from "./logger.js";
import { EngramAccessInputError, type EngramAccessService, type EngramAccessMemoryResponse, type EngramAccessWriteResponse } from "./access-service.js";
import { CorrectionContractError } from "./correction/correction-contract.js";
import { WearablesInputError } from "./wearables/errors.js";
import { EngramMcpServer } from "./access-mcp.js";
import { validateRequest, type SchemaName, type SchemaTypeFor } from "./access-schema.js";
import {
  OFFLINE_SYNC_APPLY_MAX_BODY_BYTES,
  OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES,
  OFFLINE_SYNC_SNAPSHOT_BASE_MAX_BODY_BYTES,
} from "./offline-sync.js";
import type { RecallDisclosure, RecallPlanMode } from "./types.js";
import { isRecallDisclosure } from "./types.js";
import { isTrustZoneName, type TrustZoneName, type TrustZoneRecordKind, type TrustZoneSourceClass } from "./trust-zones.js";
import { AdapterRegistry, type ResolvedIdentity } from "./adapters/index.js";
import type { CitationEntry } from "./citations.js";
import {
  subscribeGraphEvents,
  type GraphEvent,
} from "./graph-events.js";
import { expandTildePath } from "./utils/path.js";
import { projectTagProjectId } from "./coding/coding-namespace.js";
import { getOperation } from "./access-boundary.js";
// Importing access-operations registers the pilot boundary operations
// (memory_get / memory_store) as a side effect; the HTTP handlers below
// dispatch the migrated routes through the registry (issue #1525).
import "./access-operations.js";
import { handleChatMessage, handleChatEventsSSE } from "./chat/chat-http.js";

export interface EngramAccessHttpServerOptions {
  service: EngramAccessService;
  host?: string;
  port?: number;
  authToken?: string;
  /** Additional valid tokens (for multi-connector auth). Checked alongside authToken. */
  authTokens?: string[];
  /** Dynamic token loader — called on each auth check so new/revoked tokens take effect without restart. */
  authTokensGetter?: () => string[];
  principal?: string;
  maxBodyBytes?: number;
  adminConsoleEnabled?: boolean;
  adminConsolePublicDir?: string;
  /** Inject the primary auth token into the admin console shell for trusted launch surfaces. */
  adminConsolePrefillToken?: boolean;
  trustPrincipalHeader?: boolean;
  /** Enable adapter-based identity resolution from request headers */
  enableAdapters?: boolean;
  /** Custom adapter registry (defaults to built-in adapters) */
  adapterRegistry?: AdapterRegistry;
  /** Enable oai-mem-citation blocks in recall responses (issue #379). */
  citationsEnabled?: boolean;
  /** Auto-enable citations for Codex adapter connections (issue #379). */
  citationsAutoDetect?: boolean;
  /** Advertise legacy engram.* tool aliases on tools/list (issue #1427). Default true. */
  emitLegacyTools?: boolean;
  /** Optional authenticated admin dashboard/config controls supplied by the host server. */
  adminControls?: RemnicAdminControls;
}

export interface EngramAccessHttpServerStatus {
  running: boolean;
  host: string;
  port: number;
  maxBodyBytes: number;
}

export interface RemnicAdminHarnessStatus {
  id: string;
  label: string;
  detected: boolean;
  enabled: boolean;
  source?: string;
  detail?: string;
}

export interface RemnicAdminModelOption {
  id: string;
  label: string;
  provider: string;
  detected: boolean;
  enabled: boolean;
  default?: boolean;
  source?: string;
  endpoint?: string;
}

export interface RemnicAdminFeatureStatus {
  key: string;
  label: string;
  enabled: boolean;
  writable: boolean;
  restartRequired?: boolean;
}

export interface RemnicAdminConfigStatus {
  path: string;
  exists: boolean;
  writable: boolean;
  restartRequired: boolean;
  values: Record<string, string | number | boolean | null>;
}

export interface RemnicAdminDashboardStatus {
  config: RemnicAdminConfigStatus;
  harnesses: RemnicAdminHarnessStatus[];
  providers?: RemnicAdminHarnessStatus[];
  models: RemnicAdminModelOption[];
  features: RemnicAdminFeatureStatus[];
}

export type RemnicAdminConfigPatch = Record<string, unknown>;

export interface RemnicAdminControls {
  status: () => Promise<RemnicAdminDashboardStatus>;
  update?: (patch: RemnicAdminConfigPatch) => Promise<RemnicAdminDashboardStatus>;
}

function resolveDefaultAdminConsolePublicDir(): string {
  const thisDir = path.dirname(fileURLToPath(import.meta.url));
  const candidates = [
    // Standard: admin-console sibling to src/ (development layout)
    path.resolve(thisDir, "../admin-console/public"),
    // Bundled: admin-console inside dist/ alongside the bundle
    path.resolve(thisDir, "./admin-console/public"),
    // Package root: walk up from dist/ to the package root
    path.resolve(thisDir, "../../admin-console/public"),
  ];
  return candidates.find((candidate) => existsSync(candidate)) ?? candidates[0];
}

const defaultAdminConsolePublicDir = resolveDefaultAdminConsolePublicDir();
const correlationIdStore = new AsyncLocalStorage<string>();

const WRITE_RATE_LIMIT_WINDOW_MS = 60_000;
const WRITE_RATE_LIMIT_MAX_REQUESTS = 30;
const TRUST_ZONE_RECORD_KINDS = ["memory", "artifact", "state", "trajectory", "external"] as const;
const TRUST_ZONE_SOURCE_CLASSES = ["tool_output", "web_content", "subagent_trace", "system_memory", "user_input", "manual"] as const;

class HttpError extends Error {
  readonly code: string;
  readonly details?: unknown;
  constructor(readonly status: number, message: string, code?: string, details?: unknown) {
    super(message);
    this.code = code ?? `http_${status}`;
    this.details = details;
  }
}

function hostToUrlAuthority(host: string): string {
  if (host.includes(":") && !host.startsWith("[") && !host.endsWith("]")) {
    return `[${host}]`;
  }
  return host;
}

function parseHttpServerPort(port: number | undefined): number {
  if (port === undefined) return 0;
  if (!Number.isInteger(port) || port < 0 || port > 65535) {
    throw new Error("access HTTP port must be an integer from 0 to 65535");
  }
  return port;
}

function parseTrustZoneKindFilter(raw: string | null): TrustZoneRecordKind | undefined {
  if (raw === null) return undefined;
  if ((TRUST_ZONE_RECORD_KINDS as readonly string[]).includes(raw)) {
    return raw as TrustZoneRecordKind;
  }
  throw new HttpError(400, `kind must be one of ${TRUST_ZONE_RECORD_KINDS.join("|")}`, "invalid_kind_filter");
}

function parseTrustZoneSourceClassFilter(raw: string | null): TrustZoneSourceClass | undefined {
  if (raw === null) return undefined;
  if ((TRUST_ZONE_SOURCE_CLASSES as readonly string[]).includes(raw)) {
    return raw as TrustZoneSourceClass;
  }
  throw new HttpError(400, `sourceClass must be one of ${TRUST_ZONE_SOURCE_CLASSES.join("|")}`, "invalid_source_class_filter");
}

function parseTrustZoneFilter(raw: string | null): TrustZoneName | undefined {
  if (raw === null) return undefined;
  if (isTrustZoneName(raw)) {
    return raw;
  }
  throw new HttpError(400, "zone must be one of quarantine|working|trusted", "invalid_zone_filter");
}

function summarizeHttpRequest(req: IncomingMessage): string {
  const method = req.method ?? "UNKNOWN";
  try {
    const parsed = new URL(req.url ?? "/", "http://localhost");
    return `${method} ${parsed.pathname}`;
  } catch {
    return `${method} ${(req.url ?? "/").split("?")[0]}`;
  }
}

function parseStrictIntegerQuery(
  raw: string | null,
  field: string,
  defaultValue: number,
  minValue: number,
): number {
  if (raw === null) return defaultValue;
  if (!/^(?:0|[1-9]\d*)$/.test(raw)) {
    throw new HttpError(400, `${field} must be an integer`, `invalid_${field}`);
  }
  const value = Number(raw);
  if (!Number.isInteger(value) || value < minValue) {
    throw new HttpError(400, `${field} must be an integer >= ${minValue}`, `invalid_${field}`);
  }
  return value;
}

function parseMemorySort(raw: string | null): "updated_desc" | "updated_asc" | "created_desc" | "created_asc" | undefined {
  if (raw === null) return undefined;
  if (
    raw === "updated_desc" ||
    raw === "updated_asc" ||
    raw === "created_desc" ||
    raw === "created_asc"
  ) {
    return raw;
  }
  throw new HttpError(400, "sort must be one of updated_desc|updated_asc|created_desc|created_asc", "invalid_sort");
}

/**
 * Decode a `:peerId` URL path segment, converting malformed percent-encoded
 * input (e.g., `%E0%A4%A`) into a 400 client error rather than letting
 * `URIError` bubble up as a 500 `internal_error`.
 */
function decodePeerIdSegment(raw: string): string {
  try {
    return decodeURIComponent(raw);
  } catch {
    throw new EngramAccessInputError("peerId path segment is not valid percent-encoded input");
  }
}

function codingContextFromProjectTag(projectTag: string): {
  projectId: string;
  branch: string | null;
  rootPath: string;
  defaultBranch: string | null;
} {
  const projectId = projectTagProjectId(projectTag);
  return {
    projectId,
    branch: null,
    rootPath: projectId,
    defaultBranch: null,
  };
}

export class EngramAccessHttpServer {
  private readonly service: EngramAccessService;
  private readonly host: string;
  private readonly requestedPort: number;
  private readonly authToken?: string;
  private readonly authTokens: string[];
  private readonly authTokensGetter?: () => string[];
  private readonly authenticatedPrincipal?: string;
  private readonly maxBodyBytes: number;
  private readonly adminConsoleEnabled: boolean;
  private readonly adminConsolePublicDir: string;
  private readonly adminConsolePrefillToken?: string;
  private readonly adminControls?: RemnicAdminControls;
  private readonly trustPrincipalHeader: boolean;
  private readonly adapterRegistry: AdapterRegistry | null;
  private readonly writeRequestTimestamps: number[] = [];
  private readonly mcpServer: EngramMcpServer;
  private server: Server | null = null;
  private boundPort = 0;
  /** Active SSE response objects for /engram/v1/graph/events. */
  private readonly sseClients = new Set<ServerResponse>();
  /** Throttle batch: pending SSE event batches per client. */
  private readonly sseBatchTimers = new Map<ServerResponse, ReturnType<typeof setTimeout>>();
  private readonly ssePendingBatches = new Map<ServerResponse, GraphEvent[]>();
  /**
   * Per-client cleanup callbacks: clear heartbeat interval, flush timer,
   * unsubscribe from bus, and end the response.  Stored here so `stop()`
   * can invoke them even when the client hasn't disconnected yet
   * (Cursor review thread `access-http.ts:232`).
   */
  private readonly sseCleanupFns = new Set<() => void>();

  constructor(options: EngramAccessHttpServerOptions) {
    this.service = options.service;
    this.host = options.host?.trim() || "127.0.0.1";
    this.requestedPort = parseHttpServerPort(options.port);
    this.authToken = options.authToken?.trim() || undefined;
    this.authTokens = (options.authTokens ?? []).map((t) => t.trim()).filter(Boolean);
    this.authTokensGetter = options.authTokensGetter;
    this.authenticatedPrincipal = options.principal?.trim() || undefined;
    this.maxBodyBytes = Number.isFinite(options.maxBodyBytes)
      ? Math.max(1, Math.floor(options.maxBodyBytes ?? 131072))
      : 131072;
    this.adminConsoleEnabled = options.adminConsoleEnabled !== false;
    this.adminConsolePublicDir = options.adminConsolePublicDir ?? defaultAdminConsolePublicDir;
    this.adminConsolePrefillToken = options.adminConsolePrefillToken === true ? this.authToken : undefined;
    this.adminControls = options.adminControls;
    this.trustPrincipalHeader = options.trustPrincipalHeader === true;
    this.adapterRegistry = options.enableAdapters !== false
      ? (options.adapterRegistry ?? new AdapterRegistry())
      : null;
    this.mcpServer = new EngramMcpServer(this.service, {
      principal: options.principal,
      citationsEnabled: options.citationsEnabled,
      citationsAutoDetect: options.citationsAutoDetect,
      emitLegacyTools: options.emitLegacyTools,
      codingDecisionVisible: this.service.decisionRecordSurfaceVisible,
      architectureCardVisible: this.service.architectureCardSurfaceVisible,
      codegraphVisible: this.service.codegraphSurfaceVisible,
      sessionDeltaVisible: this.service.sessionDeltaSurfaceVisible,
      chatVisible: this.service.configRef?.chat?.enabled === true,
      correctionVisible: this.service.correctionSurfaceVisible,
    });
  }

  async start(): Promise<EngramAccessHttpServerStatus> {
    if (!this.authToken && this.authTokens.length === 0 && !this.authTokensGetter) {
      throw new Error("engram access HTTP requires authToken or authTokens");
    }
    if (this.server) return this.status();

    const server = createServer((req, res) => {
      const correlationId = randomUUID();
      correlationIdStore.run(correlationId, () => {
        void this.handle(req, res, correlationId).catch((err) => {
          log.debug(`engram access HTTP request failed [${correlationId}]: ${err}`);
          if (err instanceof HttpError) {
            const payload: Record<string, unknown> = { error: err.message, code: err.code };
            if (err.details) payload.details = err.details;
            this.respondJson(res, err.status, payload);
            return;
          }
          if (err instanceof EngramAccessInputError) {
            this.respondJson(res, 400, { error: err.message, code: "input_error" });
            return;
          }
          if (err instanceof CorrectionContractError) {
            this.respondJson(res, 400, { error: err.message, code: "correction_contract_error" });
            return;
          }
          if (res.headersSent) {
            res.destroy(err as Error);
            return;
          }
          log.error(
            `engram access HTTP internal error [${correlationId}] ${summarizeHttpRequest(req)}`,
            err,
          );
          this.respondJson(res, 500, { error: "internal_error", code: "internal_error" });
        });
      });
    });

    try {
      await new Promise<void>((resolve, reject) => {
        const onError = (err: Error) => {
          server.off("listening", onListening);
          reject(err);
        };
        const onListening = () => {
          server.off("error", onError);
          resolve();
        };
        server.once("error", onError);
        server.once("listening", onListening);
        server.listen(this.requestedPort, this.host);
      });
    } catch (err) {
      server.close();
      throw err;
    }

    this.server = server;
    const address = server.address();
    this.boundPort = typeof address === "object" && address ? address.port : this.requestedPort;
    return this.status();
  }

  async stop(): Promise<void> {
    if (!this.server) return;
    const server = this.server;
    this.server = null;
    this.boundPort = 0;
    // Invoke each SSE client's cleanup callback so heartbeat intervals,
    // batch timers, and graph-bus subscriptions are all released before the
    // HTTP server closes.  Without this, long-running SSE connections leak
    // setInterval handles and EventEmitter listeners (Cursor review thread
    // `access-http.ts:232`).
    for (const cleanup of this.sseCleanupFns) {
      try { cleanup(); } catch { /* ignore */ }
    }
    this.sseCleanupFns.clear();
    // Belt-and-suspenders: clear any state not yet reached by cleanup fns.
    for (const [res, timer] of this.sseBatchTimers.entries()) {
      clearTimeout(timer);
      this.sseBatchTimers.delete(res);
    }
    this.ssePendingBatches.clear();
    for (const res of this.sseClients) {
      try { res.end(); } catch { /* ignore */ }
    }
    this.sseClients.clear();
    await new Promise<void>((resolve, reject) => {
      server.close((err) => (err ? reject(err) : resolve()));
    });
  }

  status(): EngramAccessHttpServerStatus {
    return {
      running: this.server !== null,
      host: this.host,
      port: this.boundPort,
      maxBodyBytes: this.maxBodyBytes,
    };
  }

  /**
   * Resolve the adapter identity for the incoming request.
   * Includes MCP clientInfo from the last initialize handshake if available.
   * Returns null if no adapter matches or adapters are disabled.
   */
  resolveAdapterIdentity(req: IncomingMessage): ResolvedIdentity | null {
    if (!this.adapterRegistry) return null;
    // Look up clientInfo for this specific MCP session to avoid cross-session leaks.
    // Non-MCP requests (no mcp-session-id header) get undefined clientInfo and
    // rely on HTTP headers for adapter matching.
    const sessionId = (() => {
      const raw = req.headers["mcp-session-id"];
      return typeof raw === "string" ? raw.trim() : undefined;
    })();
    return this.adapterRegistry.resolve({
      headers: req.headers as Record<string, string | string[] | undefined>,
      clientInfo: this.mcpServer.getClientInfo(sessionId),
    });
  }

  /** Cache for per-request identity resolution (avoids double adapter resolution) */
  private identityCache = new WeakMap<IncomingMessage, { principal?: string; namespace?: string; sessionKey?: string }>();

  /** Resolve principal, namespace, and session key from request headers and adapter identity */
  private resolveRequestIdentity(req: IncomingMessage): { principal?: string; namespace?: string; sessionKey?: string } {
    const cached = this.identityCache.get(req);
    if (cached) return cached;
    let principal: string | undefined;
    let namespace: string | undefined;
    let sessionKey: string | undefined;

    // Explicit header override takes priority for principal
    if (this.trustPrincipalHeader) {
      const headerVal = req.headers["x-engram-principal"];
      const raw = Array.isArray(headerVal) ? headerVal[0] : headerVal;
      if (typeof raw === "string") {
        const trimmed = raw.trim();
        if (trimmed.length > 0) {
          principal = trimmed;
        }
      }
    }

    if (!principal) {
      principal = this.authenticatedPrincipal;
    }

    // Try adapter-based identity resolution for namespace and, only when no
    // server principal is configured, an adapter-owned default principal.
    const adapterIdentity = this.resolveAdapterIdentity(req);
    if (adapterIdentity) {
      if (!principal) {
        principal = adapterIdentity.principal;
      }
      namespace = adapterIdentity.namespace;
      sessionKey = adapterIdentity.sessionKey;
    }

    const result = { principal, namespace, sessionKey };
    this.identityCache.set(req, result);
    return result;
  }

  private resolveRequestPrincipal(req: IncomingMessage): string | undefined {
    return this.resolveRequestIdentity(req).principal;
  }

  /** Resolve namespace: only use the explicit body value. Adapter-inferred namespace
   *  is intentionally NOT used as a fallback for REST requests — omitting namespace
   *  should default to the server's global namespace, not silently scope to an adapter. */
  private resolveNamespace(_req: IncomingMessage, bodyNamespace?: string): string | undefined {
    return bodyNamespace || undefined;
  }

  /**
   * Resolve the recall disclosure depth from the request (issue #677 PR
   * 2/4).  Explicit body value wins; otherwise we accept a
   * `?disclosure=...` query parameter so curl/browser tooling can use the
   * three-tier surface without rewriting JSON.  Invalid query values
   * throw `EngramAccessInputError` (CLAUDE.md rule 51 — no silent
   * fallback).  An absent body field AND an absent query param yields
   * `undefined`, which the service maps to `DEFAULT_RECALL_DISCLOSURE`.
   */
  private resolveRecallDisclosure(
    bodyDisclosure: RecallDisclosure | undefined,
    parsed: URL,
  ): RecallDisclosure | undefined {
    if (bodyDisclosure !== undefined) {
      return bodyDisclosure;
    }
    const queryDisclosure = parsed.searchParams.get("disclosure");
    if (queryDisclosure === null) {
      return undefined;
    }
    if (!isRecallDisclosure(queryDisclosure)) {
      throw new EngramAccessInputError(
        `disclosure must be one of: chunk, section, raw (got: ${queryDisclosure})`,
      );
    }
    return queryDisclosure;
  }

  private async handle(req: IncomingMessage, res: ServerResponse, correlationId: string): Promise<void> {
    const parsed = new URL(req.url ?? "/", `http://${hostToUrlAuthority(this.host)}`);
    const pathname = parsed.pathname;

    if (this.adminConsoleEnabled && await this.handleAdminConsole(req, res, pathname)) {
      return;
    }

    if (!this.isAuthorized(req, pathname)) {
      const body = JSON.stringify({ error: "unauthorized", code: "unauthorized" });
      res.writeHead(401, {
        "content-type": "application/json; charset=utf-8",
        "www-authenticate": "Bearer",
        "x-request-id": correlationId,
      });
      res.end(body);
      return;
    }

    if (req.method === "POST" && pathname === "/mcp") {
      await this.handleMcpRequest(req, res);
      return;
    }

    if (req.method === "GET" && pathname === "/engram/v1/health") {
      this.respondJson(res, 200, await this.service.health());
      return;
    }

    if (req.method === "GET" && pathname === "/engram/v1/adapters") {
      const identity = this.resolveAdapterIdentity(req);
      this.respondJson(res, 200, {
        adaptersEnabled: this.adapterRegistry !== null,
        registered: this.adapterRegistry?.list() ?? [],
        resolved: identity,
      });
      return;
    }

    if (
      req.method === "GET" &&
      (pathname === "/engram/v1/admin/dashboard" || pathname === "/remnic/v1/admin/dashboard")
    ) {
      if (!this.adminControls) {
        this.respondJson(res, 404, { error: "admin_controls_unavailable", code: "admin_controls_unavailable" });
        return;
      }
      this.respondJson(res, 200, await this.adminControls.status());
      return;
    }

    if (
      req.method === "PATCH" &&
      (pathname === "/engram/v1/admin/config" || pathname === "/remnic/v1/admin/config")
    ) {
      if (!this.adminControls?.update) {
        this.respondJson(res, 404, { error: "admin_controls_unavailable", code: "admin_controls_unavailable" });
        return;
      }
      try {
        this.respondJson(res, 200, await this.adminControls.update(await this.readJsonBody(req)));
      } catch (error) {
        throw new HttpError(
          400,
          error instanceof Error ? error.message : "invalid_admin_config_patch",
          "invalid_admin_config_patch",
        );
      }
      return;
    }

    if (req.method === "POST" && pathname === "/engram/v1/recall") {
      void getOperation("recall"); // boundary dispatch (issue #1525)
      const body = await this.readValidatedBody(req, "recall");
      // Preserve the distinction between `codingContext: null` (explicit
      // clear) and `codingContext` missing from the JSON payload
      // (untouched). The previous `?? undefined` collapsed both into
      // undefined, so callers lost the ability to clear the session's
      // attached context through the recall endpoint.
      const codingContext =
        "codingContext" in body ? body.codingContext : undefined;
      // Disclosure resolution (issue #677 PR 2/4): accept the value from
      // the validated body OR the `?disclosure=` query parameter, with
      // the body taking precedence so an explicit JSON payload is never
      // silently overridden by a stale URL.  CLAUDE.md rule 51: invalid
      // query-param values throw, never fall back silently.
      const disclosure = this.resolveRecallDisclosure(body.disclosure, parsed);
      // Issue #680 — historical recall pin (`asOf`). Body field wins
      // over `?as_of=` query param. Empty query is rejected only when
      // the body didn't supply a valid pin (codex P2 + cursor Medium).
      const asOfQueryRaw = parsed.searchParams.get("as_of");
      const bodyHasAsOf =
        typeof body.asOf === "string" && body.asOf.length > 0;
      if (
        !bodyHasAsOf &&
        asOfQueryRaw !== null &&
        asOfQueryRaw.length === 0
      ) {
        throw new EngramAccessInputError(
          "as_of must be a non-empty timestamp (got empty value)",
        );
      }
      const asOf =
        body.asOf ??
        (asOfQueryRaw !== null && asOfQueryRaw.length > 0
          ? asOfQueryRaw
          : undefined);
      // Tag filter (issue #689). Body presence wins over query params
      // — explicit `tags: []` in body clears the filter even with
      // stale `?tag=` URLs.
      const bodyHasTagsField =
        body !== null &&
        typeof body === "object" &&
        "tags" in (body as Record<string, unknown>);
      const bodyTagsValue = bodyHasTagsField
        ? (body as { tags?: unknown }).tags
        : undefined;
      const bodyTags = Array.isArray(bodyTagsValue)
        ? (bodyTagsValue as string[])
        : undefined;
      const queryTags = parsed.searchParams.getAll("tag");
      const tags = bodyHasTagsField
        ? bodyTags
        : queryTags.length > 0
          ? queryTags
          : undefined;
      const bodyTagMatch = (body as { tagMatch?: unknown }).tagMatch;
      let tagMatch: "any" | "all" | undefined;
      if (bodyTagMatch !== undefined) {
        if (bodyTagMatch === "any" || bodyTagMatch === "all") {
          tagMatch = bodyTagMatch;
        }
      } else {
        const queryTagMatch = parsed.searchParams.get("tag_match");
        if (queryTagMatch !== null) {
          if (queryTagMatch !== "any" && queryTagMatch !== "all") {
            throw new EngramAccessInputError(
              `tag_match must be one of: any, all (got: ${queryTagMatch})`,
            );
          }
          tagMatch = queryTagMatch;
        }
      }
      // Issue #681 — `?include_low_confidence=true|false` mirrors the CLI
      // `--include-low-confidence` flag. Body field wins so a JSON payload can
      // explicitly clear a stale query parameter.
      const bodyIncludeLowConfidence =
        (body as { includeLowConfidence?: unknown }).includeLowConfidence;
      const queryIncludeLowConfidence = parsed.searchParams.get("include_low_confidence");
      if (
        bodyIncludeLowConfidence === undefined &&
        queryIncludeLowConfidence !== null &&
        queryIncludeLowConfidence !== "true" &&
        queryIncludeLowConfidence !== "false"
      ) {
        throw new EngramAccessInputError(
          `include_low_confidence must be one of: true, false (got: ${queryIncludeLowConfidence})`,
        );
      }
      const includeLowConfidence =
        bodyIncludeLowConfidence === true ||
        (bodyIncludeLowConfidence === undefined &&
          queryIncludeLowConfidence === "true");
      const response = await this.service.recall({
        query: body.query ?? "",
        sessionKey: body.sessionKey,
        authenticatedPrincipal: this.resolveRequestPrincipal(req),
        idempotencyKey: body.idempotencyKey,
        namespace: this.resolveNamespace(req, body.namespace),
        topK: body.topK,
        mode: body.mode as RecallPlanMode | "auto" | undefined,
        includeDebug: body.includeDebug === true,
        // Forward the validated disclosure depth to the service layer
        // (issue #677).  The zod schema accepts/rejects body values;
        // `resolveRecallDisclosure()` validates the query-param fallback.
        disclosure,
        codingContext,
        // Forward cwd/projectTag for auto git-context resolution (issue #569).
        cwd: body.cwd,
        projectTag: body.projectTag,
        ...(asOf !== undefined ? { asOf } : {}),
        ...(tags !== undefined ? { tags } : {}),
        ...(tagMatch !== undefined ? { tagMatch } : {}),
        ...(includeLowConfidence ? { includeLowConfidence: true } : {}),
      });
      this.respondJson(res, 200, response);
      return;
    }

    // Attach / clear coding-agent context for a session (issue #569 PR 5).
    // Mirrors `setCodingContext` on the access service. Connectors call this
    // at session start after resolving a git context for the cwd; `remnic
    // doctor` (PR 8) surfaces the attached context.
    if (req.method === "POST" && pathname === "/engram/v1/coding-context") {
      void getOperation("set_coding_context"); // boundary dispatch (issue #1525)
      const body = await this.readValidatedBody(req, "setCodingContext");
      const codingContext =
        body.codingContext !== undefined
          ? body.codingContext
          : typeof body.projectTag === "string"
            ? codingContextFromProjectTag(body.projectTag)
            : (() => {
                throw new EngramAccessInputError("codingContext or projectTag is required");
              })();
      this.service.setCodingContext({
        sessionKey: body.sessionKey,
        codingContext,
      });
      this.respondJson(res, 200, { ok: true });
      return;
    }

    if (
      req.method === "POST" &&
      (pathname === "/engram/v1/capsules/export" || pathname === "/remnic/v1/capsules/export")
    ) {
      void getOperation("capsule_export"); // boundary dispatch (issue #1525)
      const body = await this.readValidatedBody(req, "capsuleExport");
      this.ensureWriteRateLimitAvailable();
      const result = await this.service.capsuleExport({
        name: body.name,
        namespace: this.resolveNamespace(req, body.namespace),
        principal: this.resolveRequestPrincipal(req),
        since: body.since,
        includeKinds: body.includeKinds,
        peerIds: body.peerIds,
        includeTranscripts: body.includeTranscripts,
        encrypt: body.encrypt,
      });
      this.recordWriteRateLimitHit();
      this.respondJson(res, 200, result);
      return;
    }

    if (
      req.method === "POST" &&
      (pathname === "/engram/v1/capsules/import" || pathname === "/remnic/v1/capsules/import")
    ) {
      void getOperation("capsule_import"); // boundary dispatch (issue #1525)
      const body = await this.readValidatedBody(req, "capsuleImport");
      this.ensureWriteRateLimitAvailable();
      const result = await this.service.capsuleImport({
        archivePath: expandTildePath(body.archivePath),
        namespace: this.resolveNamespace(req, body.namespace),
        principal: this.resolveRequestPrincipal(req),
        mode: body.mode,
        passphrase: body.passphrase,
      });
      this.recordWriteRateLimitHit();
      this.respondJson(res, 200, result);
      return;
    }

    if (
      req.method === "GET" &&
      (pathname === "/engram/v1/offline-sync/snapshot" || pathname === "/remnic/v1/offline-sync/snapshot")
    ) {
      void getOperation("offline_sync_snapshot"); // boundary dispatch (issue #1525)
      const includeTranscriptsRaw = parsed.searchParams.get("include_transcripts");
      const includeContentRaw = parsed.searchParams.get("content");
      if (
        includeTranscriptsRaw !== null &&
        includeTranscriptsRaw !== "true" &&
        includeTranscriptsRaw !== "false"
      ) {
        throw new EngramAccessInputError(
          `include_transcripts must be one of: true, false (got: ${includeTranscriptsRaw})`,
        );
      }
      if (
        includeContentRaw !== null &&
        includeContentRaw !== "true" &&
        includeContentRaw !== "false"
      ) {
        throw new EngramAccessInputError(
          `content must be one of: true, false (got: ${includeContentRaw})`,
        );
      }
      const namespaceParam = parsed.searchParams.get("namespace");
      const result = await this.service.offlineSyncSnapshot({
        namespace: this.resolveNamespace(
          req,
          namespaceParam && namespaceParam.length > 0 ? namespaceParam : undefined,
        ),
        principal: this.resolveRequestPrincipal(req),
        includeTranscripts: includeTranscriptsRaw !== "false",
        includeContent: includeContentRaw !== "false",
      });
      this.respondJson(res, 200, result);
      return;
    }

    if (
      req.method === "GET" &&
      (pathname === "/engram/v1/offline-sync/snapshot-stream" ||
        pathname === "/remnic/v1/offline-sync/snapshot-stream")
    ) {
      void getOperation("offline_sync_snapshot_stream"); // boundary dispatch (issue #1525)
      const includeTranscriptsRaw = parsed.searchParams.get("include_transcripts");
      const includeContentRaw = parsed.searchParams.get("content");
      if (
        includeTranscriptsRaw !== null &&
        includeTranscriptsRaw !== "true" &&
        includeTranscriptsRaw !== "false"
      ) {
        throw new EngramAccessInputError(
          `include_transcripts must be one of: true, false (got: ${includeTranscriptsRaw})`,
        );
      }
      if (
        includeContentRaw !== null &&
        includeContentRaw !== "false"
      ) {
        throw new EngramAccessInputError("snapshot-stream content must be false");
      }
      const namespaceParam = parsed.searchParams.get("namespace");
      const result = await this.service.offlineSyncSnapshotStream({
        namespace: this.resolveNamespace(
          req,
          namespaceParam && namespaceParam.length > 0 ? namespaceParam : undefined,
        ),
        principal: this.resolveRequestPrincipal(req),
        includeTranscripts: includeTranscriptsRaw !== "false",
        includeContent: false,
        signal: this.createRequestAbortSignal(req, res),
      });
      await this.respondOfflineSnapshotStream(res, result);
      return;
    }

    if (
      req.method === "POST" &&
      (pathname === "/engram/v1/offline-sync/snapshot" || pathname === "/remnic/v1/offline-sync/snapshot")
    ) {
      void getOperation("offline_sync_snapshot"); // boundary dispatch (issue #1525)
      const body = await this.readValidatedBody(
        req,
        "offlineSyncSnapshot",
        OFFLINE_SYNC_SNAPSHOT_BASE_MAX_BODY_BYTES,
      );
      const result = await this.service.offlineSyncSnapshot({
        namespace: this.resolveNamespace(req, body.namespace),
        principal: this.resolveRequestPrincipal(req),
        includeTranscripts: body.includeTranscripts,
        includeContent: body.includeContent,
        baseFiles: body.baseFiles,
        ...(body.baseCapturedAt ? { baseCapturedAt: new Date(body.baseCapturedAt) } : {}),
        signal: this.createRequestAbortSignal(req, res),
      });
      this.respondJson(res, 200, result);
      return;
    }

    if (
      req.method === "POST" &&
      (pathname === "/engram/v1/offline-sync/files" || pathname === "/remnic/v1/offline-sync/files")
    ) {
      void getOperation("offline_sync_files"); // boundary dispatch (issue #1525)
      const body = await this.readValidatedBody(req, "offlineSyncFiles");
      const result = await this.service.offlineSyncFiles({
        namespace: this.resolveNamespace(req, body.namespace),
        principal: this.resolveRequestPrincipal(req),
        includeTranscripts: body.includeTranscripts,
        paths: body.paths,
      });
      this.respondJson(res, 200, result);
      return;
    }

    if (
      req.method === "POST" &&
      (
        pathname === "/engram/v1/offline-sync/file-content" ||
        pathname === "/remnic/v1/offline-sync/file-content"
      )
    ) {
      void getOperation("offline_sync_file_content"); // boundary dispatch (issue #1525)
      const body = await this.readValidatedBody(req, "offlineSyncFileContent");
      const result = await this.service.offlineSyncFileContent({
        namespace: this.resolveNamespace(req, body.namespace),
        principal: this.resolveRequestPrincipal(req),
        includeTranscripts: body.includeTranscripts,
        path: body.path,
        offset: body.offset,
        length: body.length,
      });
      this.respondBinary(res, 200, result.content, {
        "x-remnic-namespace": encodeURIComponent(result.namespace),
        "x-remnic-file-path": encodeURIComponent(result.path),
        "x-remnic-file-bytes": String(result.bytes),
        "x-remnic-file-mtime-ms": String(result.mtimeMs),
        "x-remnic-chunk-offset": String(result.offset),
        "x-remnic-chunk-bytes": String(result.chunkBytes),
        ...(result.sha256 ? { "x-remnic-file-sha256": result.sha256 } : {}),
      });
      return;
    }

    if (
      req.method === "POST" &&
      (
        pathname === "/engram/v1/offline-sync/apply-file-content" ||
        pathname === "/remnic/v1/offline-sync/apply-file-content"
      )
    ) {
      void getOperation("offline_sync_apply_file_content"); // boundary dispatch (issue #1525)
      const namespaceParam = parsed.searchParams.get("namespace");
      const bytes = this.readRequiredIntegerHeader(req, "x-remnic-file-bytes");
      const offset = this.readOptionalIntegerHeader(req, "x-remnic-chunk-offset") ?? 0;
      const content = await this.readBinaryBody(req, OFFLINE_SYNC_FILE_CONTENT_MAX_CHUNK_BYTES);
      const result = await this.service.offlineSyncApplyFileContent({
        namespace: this.resolveNamespace(
          req,
          namespaceParam && namespaceParam.length > 0 ? namespaceParam : undefined,
        ),
        principal: this.resolveRequestPrincipal(req),
        includeTranscripts: this.parseOptionalBooleanHeader(
          req,
          "x-remnic-include-transcripts",
          true,
        ),
        sourceId: this.readRequiredDecodedHeader(req, "x-remnic-source-id"),
        path: this.readRequiredDecodedHeader(req, "x-remnic-file-path"),
        sha256: this.readRequiredHeader(req, "x-remnic-file-sha256"),
        bytes,
        mtimeMs: this.readRequiredNumberHeader(req, "x-remnic-file-mtime-ms"),
        offset,
        baseSha256: this.readOptionalHeader(req, "x-remnic-base-sha256"),
        content,
      });
      this.respondJson(res, 200, result);
      return;
    }

    if (
      req.method === "POST" &&
      (pathname === "/engram/v1/offline-sync/apply" || pathname === "/remnic/v1/offline-sync/apply")
    ) {
      void getOperation("offline_sync_apply"); // boundary dispatch (issue #1525)
      const body = await this.readValidatedBody(req, "offlineSyncApply", OFFLINE_SYNC_APPLY_MAX_BODY_BYTES);
      const result = await this.service.offlineSyncApply({
        namespace: this.resolveNamespace(req, body.namespace),
        principal: this.resolveRequestPrincipal(req),
        changeset: body.changeset,
        returnCurrentFiles: body.returnCurrentFiles,
      });
      this.respondJson(res, 200, result);
      return;
    }

    if (req.method === "POST" && pathname === "/engram/v1/recall/explain") {
      void getOperation("recall_explain"); // boundary dispatch (issue #1525)
      const body = await this.readValidatedBody(req, "recallExplain");
      const response = await this.service.recallExplain({
        sessionKey: body.sessionKey,
        namespace: this.resolveNamespace(req, body.namespace),
        authenticatedPrincipal: this.resolveRequestPrincipal(req),
      });
      this.respondJson(res, 200, response);
      return;
    }

    if (
      req.method === "POST" &&
      (pathname === "/engram/v1/action-confidence" || pathname === "/remnic/v1/action-confidence")
    ) {
      void getOperation("action_confidence"); // boundary dispatch (issue #1525)
      const body = await this.readValidatedBody(req, "actionConfidence");
      this.respondJson(res, 200, await this.service.actionConfidence(body));
      return;
    }

    // Tier-explain (issue #518): structured per-result annotation from
    // the direct-answer retrieval tier.  Orthogonal to /recall/explain
    // above, which returns a graph-path explanation document.
    if (req.method === "GET" && pathname === "/engram/v1/recall/tier-explain") {
      void getOperation("recall_tier_explain"); // boundary dispatch (issue #1525)
      const sessionParam = parsed.searchParams.get("session");
      const sessionKey = sessionParam && sessionParam.length > 0 ? sessionParam : undefined;
      const namespaceParam = parsed.searchParams.get("namespace");
      const namespace = this.resolveNamespace(
        req,
        namespaceParam && namespaceParam.length > 0 ? namespaceParam : undefined,
      );
      const payload = await this.service.recallTierExplain(
        sessionKey,
        namespace,
        this.resolveRequestPrincipal(req),
      );
      this.respondJson(res, 200, payload);
      return;
    }

    // Recall X-ray (issue #570 PR 4): unified per-result attribution
    // snapshot.  Requires bearer auth (same as every other endpoint
    // here) and enforces namespace scope before the recall fires
    // (CLAUDE.md rule 42).  Query comes from the `q` search param so
    // GET stays cacheable; `namespace` / `session` / `budget` are
    // optional.
    if (req.method === "GET" && pathname === "/engram/v1/recall/xray") {
      void getOperation("recall_xray"); // boundary dispatch (issue #1525)
      const queryParam = parsed.searchParams.get("q");
      if (!queryParam || queryParam.trim().length === 0) {
        this.respondJson(res, 400, {
          error: "missing_query",
          code: "missing_query",
          message: "q search parameter is required and must be non-empty",
        });
        return;
      }
      const sessionParam = parsed.searchParams.get("session");
      const sessionKey = sessionParam && sessionParam.length > 0
        ? sessionParam
        : undefined;
      const namespaceParam = parsed.searchParams.get("namespace");
      const namespace = this.resolveNamespace(
        req,
        namespaceParam && namespaceParam.length > 0
          ? namespaceParam
          : undefined,
      );
      const budgetParam = parsed.searchParams.get("budget");
      // Reject invalid `budget` with 400 rather than silently
      // defaulting (CLAUDE.md rules 14 + 51).
      let budget: number | undefined;
      if (budgetParam !== null && budgetParam !== "") {
        const parsedBudget = Number(budgetParam);
        if (
          !Number.isFinite(parsedBudget)
          || parsedBudget <= 0
          || !Number.isInteger(parsedBudget)
        ) {
          this.respondJson(res, 400, {
            error: "invalid_budget",
            code: "invalid_budget",
            message:
              "budget expects a positive integer",
          });
          return;
        }
        budget = parsedBudget;
      }
      // Disclosure depth (issue #677 PR 3/4 telemetry plumbing).  When
      // present, must match the chunk|section|raw allow-list; invalid
      // values surface as a 400 (CLAUDE.md rule 51 — no silent
      // fallback) rather than silently disabling the per-disclosure
      // summary table.
      const disclosureParam = parsed.searchParams.get("disclosure");
      let disclosure: RecallDisclosure | undefined;
      if (disclosureParam !== null && disclosureParam.length > 0) {
        if (!isRecallDisclosure(disclosureParam)) {
          this.respondJson(res, 400, {
            error: "invalid_disclosure",
            code: "invalid_disclosure",
            message:
              "disclosure must be one of: chunk, section, raw",
          });
          return;
        }
        disclosure = disclosureParam;
      }
      // Only translate validation errors (empty query, bad budget)
      // into 400s.  Backend faults (timeouts, storage errors,
      // unexpected orchestrator failures) must bubble to the global
      // `handle()` error handler so they return 500 and get logged
      // properly.  `service.recallXray` prefixes its validation
      // errors with "recallXray:" so we key off that prefix rather
      // than catching everything.
      let payload: Awaited<ReturnType<typeof this.service.recallXray>>;
      try {
        payload = await this.service.recallXray({
          query: queryParam,
          sessionKey,
          namespace,
          budget,
          authenticatedPrincipal: this.resolveRequestPrincipal(req),
          ...(disclosure !== undefined ? { disclosure } : {}),
        });
      } catch (err) {
        // Only surface the message for the deliberately-prefixed recallXray
        // input-validation errors, and only when it is a real Error.message —
        // never String(err) of an arbitrary throw, which CodeQL flags as
        // stack-trace exposure (js/stack-trace-exposure). Validation errors are
        // always thrown as Error instances (see access-service.ts), so this is
        // behavior-preserving; anything else is a server-side fault and is
        // rethrown so the outer `handle()` catch returns 500 + logs it.
        if (err instanceof Error && err.message.startsWith("recallXray:")) {
          this.respondJson(res, 400, {
            error: "invalid_request",
            code: "invalid_request",
            message: err.message,
          });
          return;
        }
        throw err;
      }
      this.respondJson(res, 200, payload);
      return;
    }

    // -- Wearables (Limitless / Bee / Omi transcript ingestion). All
    //    behavior + validation lives in WearablesService; these routes
    //    translate transport shape only. Service validation errors map
    //    to 400 via respondWearablesError; backend faults bubble to the
    //    global 500 handler.
    if (
      req.method === "GET" &&
      (pathname === "/engram/v1/wearables/status" || pathname === "/remnic/v1/wearables/status")
    ) {
      void getOperation("wearables_status"); // boundary dispatch (issue #1525)
      this.respondJson(res, 200, await this.service.wearablesStatus());
      return;
    }

    if (
      req.method === "POST" &&
      (pathname === "/engram/v1/wearables/sync" || pathname === "/remnic/v1/wearables/sync")
    ) {
      void getOperation("wearables_sync"); // boundary dispatch (issue #1525)
      const body = (await this.readJsonBody(req)) as Record<string, unknown>;
      const source = optionalQueryString(body.source, "source");
      const date = optionalQueryString(body.date, "date");
      let days: number | undefined;
      if (body.days !== undefined && body.days !== null) {
        if (
          typeof body.days !== "number" ||
          !Number.isInteger(body.days) ||
          body.days < 1
        ) {
          throw new EngramAccessInputError(
            `days must be a positive integer (got ${JSON.stringify(body.days)})`,
          );
        }
        days = body.days;
      }
      if (body.forceMemories !== undefined && typeof body.forceMemories !== "boolean") {
        throw new EngramAccessInputError(
          `forceMemories must be a boolean (got ${JSON.stringify(body.forceMemories)})`,
        );
      }
      try {
        const summaries = await this.service.wearablesSync({
          source,
          date,
          days,
          forceMemories: body.forceMemories === true,
        });
        this.respondJson(res, 200, { summaries });
      } catch (err) {
        if (this.respondWearablesError(res, err)) return;
        throw err;
      }
      return;
    }

    if (
      req.method === "GET" &&
      (pathname === "/engram/v1/wearables/transcript" || pathname === "/remnic/v1/wearables/transcript")
    ) {
      void getOperation("transcript_day"); // boundary dispatch (issue #1525)
      const date = parsed.searchParams.get("date");
      if (!date || date.trim().length === 0) {
        throw new EngramAccessInputError(
          "date query parameter is required (YYYY-MM-DD)",
        );
      }
      const sourceParam = parsed.searchParams.get("source");
      try {
        const transcripts = await this.service.wearablesTranscriptDay({
          date,
          source: sourceParam && sourceParam.length > 0 ? sourceParam : undefined,
        });
        this.respondJson(res, 200, { transcripts });
      } catch (err) {
        if (this.respondWearablesError(res, err)) return;
        throw err;
      }
      return;
    }

    if (
      req.method === "GET" &&
      (pathname === "/engram/v1/wearables/transcripts/search" ||
        pathname === "/remnic/v1/wearables/transcripts/search")
    ) {
      void getOperation("transcript_search"); // boundary dispatch (issue #1525)
      const queryParam = parsed.searchParams.get("q");
      if (!queryParam || queryParam.trim().length === 0) {
        throw new EngramAccessInputError(
          "q query parameter is required and must be non-empty",
        );
      }
      try {
        const results = await this.service.wearablesTranscriptSearch({
          query: queryParam,
          source: nonEmptyQueryParam(parsed.searchParams.get("source")),
          from: nonEmptyQueryParam(parsed.searchParams.get("from")),
          to: nonEmptyQueryParam(parsed.searchParams.get("to")),
          limit: positiveIntQueryParam(parsed.searchParams.get("limit"), "limit"),
        });
        this.respondJson(res, 200, { results });
      } catch (err) {
        if (this.respondWearablesError(res, err)) return;
        throw err;
      }
      return;
    }

    if (
      req.method === "GET" &&
      (pathname === "/engram/v1/wearables/memories" || pathname === "/remnic/v1/wearables/memories")
    ) {
      void getOperation("transcript_memories"); // boundary dispatch (issue #1525)
      try {
        const memories = await this.service.wearablesTranscriptMemories({
          source: nonEmptyQueryParam(parsed.searchParams.get("source")),
          date: nonEmptyQueryParam(parsed.searchParams.get("date")),
          limit: positiveIntQueryParam(parsed.searchParams.get("limit"), "limit"),
        });
        this.respondJson(res, 200, { memories });
      } catch (err) {
        if (this.respondWearablesError(res, err)) return;
        throw err;
      }
      return;
    }

    if (req.method === "POST" && pathname === "/engram/v1/observe") {
      void getOperation("observe"); // boundary dispatch (issue #1525)
      const body = await this.readValidatedBody(req, "observe");
      const response = await this.service.observe(
        {
          sessionKey: body.sessionKey,
          messages: body.messages.map((message) => ({
            role: message.role,
            content: message.content,
            sourceFormat: message.sourceFormat ?? undefined,
            rawContent: message.rawContent ?? undefined,
            parts: message.parts ?? undefined,
          })),
          namespace: this.resolveNamespace(req, body.namespace),
          authenticatedPrincipal: this.resolveRequestPrincipal(req),
          skipExtraction: body.skipExtraction === true,
          // Issue #1649: optional server-side dedup key for retried POSTs.
          idempotencyKey: body.idempotencyKey,
          // Forward cwd/projectTag for auto git-context resolution (issue #569).
          cwd: body.cwd,
          projectTag: body.projectTag,
        },
        // Enforce the write-quota INSIDE the service's idempotency lock
        // (beforeExecute, only on a real miss). A retried observe that hits the
        // replay path must NOT be rejected with 429 — that is exactly the
        // response-lost-after-process case the dedup exists for. The original
        // attempt may have consumed the last quota slot; the retry returns the
        // cached response without requiring another. Matches memory_store's
        // hook-in-the-lock pattern (#1434 invariant).
        { enforceWriteQuota: () => this.ensureWriteRateLimitAvailable() },
      );
      // A replayed (deduplicated) observe must not consume a second write-quota
      // slot — same invariant as memory_store/suggestion_submit (#1434).
      if (this.shouldCountWriteRateLimit(response as { dryRun?: boolean; idempotencyReplay?: boolean })) {
        this.recordWriteRateLimitHit();
      }
      this.respondJson(res, 202, response);
      return;
    }

    if (req.method === "POST" && pathname === "/engram/v1/lcm/search") {
      void getOperation("lcm_search"); // boundary dispatch (issue #1525)
      const body = await this.readValidatedBody(req, "lcmSearch");
      const response = await this.service.lcmSearch({
        query: body.query,
        sessionKey: body.sessionKey,
        sessionPrefix: body.sessionPrefix,
        namespace: this.resolveNamespace(req, body.namespace),
        authenticatedPrincipal: this.resolveRequestPrincipal(req),
        limit: body.limit,
      });
      this.respondJson(res, 200, response);
      return;
    }

    if (
      req.method === "POST" &&
      (pathname === "/engram/v1/lcm/compaction/flush" || pathname === "/remnic/v1/lcm/compaction/flush")
    ) {
      void getOperation("lcm_compaction_flush"); // boundary dispatch (issue #1525)
      const body = await this.readValidatedBody(req, "lcmCompactionFlush");
      this.ensureWriteRateLimitAvailable();
      const response = await this.service.lcmCompactionFlush({
        sessionKey: body.sessionKey,
        namespace: this.resolveNamespace(req, body.namespace),
        authenticatedPrincipal: this.resolveRequestPrincipal(req),
      });
      this.recordWriteRateLimitHit();
      this.respondJson(res, 200, response);
      return;
    }

    if (
      req.method === "POST" &&
      (pathname === "/engram/v1/lcm/compaction/record" || pathname === "/remnic/v1/lcm/compaction/record")
    ) {
      void getOperation("lcm_compaction_record"); // boundary dispatch (issue #1525)
      const body = await this.readValidatedBody(req, "lcmCompactionRecord");
      this.ensureWriteRateLimitAvailable();
      const response = await this.service.lcmCompactionRecord({
        sessionKey: body.sessionKey,
        namespace: this.resolveNamespace(req, body.namespace),
        tokensBefore: body.tokensBefore,
        tokensAfter: body.tokensAfter,
        authenticatedPrincipal: this.resolveRequestPrincipal(req),
      });
      this.recordWriteRateLimitHit();
      this.respondJson(res, 200, response);
      return;
    }

    if (req.method === "GET" && pathname === "/engram/v1/lcm/status") {
      void getOperation("lcm_status"); // boundary dispatch (issue #1525)
      this.respondJson(res, 200, await this.service.lcmStatus());
      return;
    }

    if (req.method === "POST" && pathname === "/engram/v1/memories") {
      // Migrated through the access boundary (issue #1525): the registry
      // entry owns schema validation, normalization, and service dispatch.
      // The HTTP transport resolves the request-scoped namespace and principal
      // BEFORE the boundary re-validates the cleaned envelope. The write-quota
      // hook is forwarded via ctx.hooks so it still fires atomically inside
      // the service's idempotent-write lock — never before, never on a replay
      // (#1434 invariant preserved by the boundary migration).
      const body = await this.readValidatedBody(req, "memoryStore");
      const envelope = {
        ...body,
        namespace: this.resolveNamespace(req, body.namespace),
      };
      const op = getOperation("memory_store");
      if (!op) {
        throw new EngramAccessInputError("access-boundary: operation not registered: memory_store");
      }
      const output = (await op.run(envelope, {
        service: this.service,
        authenticatedPrincipal: this.resolveRequestPrincipal(req),
        hooks: { enforceWriteQuota: () => this.ensureWriteRateLimitAvailable() },
      })) as { result: EngramAccessWriteResponse };
      const response = output.result;
      if (this.shouldCountWriteRateLimit(response as { dryRun?: boolean; idempotencyReplay?: boolean })) {
        this.recordWriteRateLimitHit();
      }
      this.respondJson(res, this.writeResponseStatus(response), response);
      return;
    }

    if (req.method === "POST" && pathname === "/engram/v1/coding/decisions") {
      // Migrated through the access boundary (issue #1525/#1548): the
      // registry entry owns schema validation and service dispatch. HTTP
      // resolves the request principal; the boundary re-validates the
      // cleaned envelope. record/supersede persist decision memories, so they
      // are gated by the same 30/min write quota as /engram/v1/memories,
      // suggestions, and observe; list/get are pure reads and stay uncounted
      // (review P2: apply write quotas to decision writes).
      const body = await this.readJsonBody(req);
      const isWriteSubcommand =
        body.subcommand === "record" || body.subcommand === "supersede";
      if (isWriteSubcommand) {
        this.ensureWriteRateLimitAvailable();
      }
      const op = getOperation("coding_decision");
      if (!op) {
        throw new EngramAccessInputError("access-boundary: operation not registered: coding_decision");
      }
      const output = (await op.run(body, {
        service: this.service,
        authenticatedPrincipal: this.resolveRequestPrincipal(req),
      })) as { result: unknown };
      if (isWriteSubcommand) {
        this.recordWriteRateLimitHit();
      }
      this.respondJson(res, 200, output.result);
      return;
    }

    if (req.method === "POST" && pathname === "/engram/v1/coding/architecture") {
      // Migrated through the access boundary (issue #1525/#1548 PR3):
      // refresh persists the card, so it is gated by the 30/min write quota;
      // get is a pure read and stays uncounted.
      const body = await this.readJsonBody(req);
      const isWriteSubcommand = body.subcommand === "refresh";
      if (isWriteSubcommand) {
        this.ensureWriteRateLimitAvailable();
      }
      const op = getOperation("coding_architecture");
      if (!op) {
        throw new EngramAccessInputError("access-boundary: operation not registered: coding_architecture");
      }
      const output = (await op.run(body, {
        service: this.service,
        authenticatedPrincipal: this.resolveRequestPrincipal(req),
      })) as { result: unknown };
      if (isWriteSubcommand) {
        this.recordWriteRateLimitHit();
      }
      this.respondJson(res, 200, output.result);
      return;
    }

    if (req.method === "POST" && pathname === "/engram/v1/coding/delta") {
      // Migrated through the access boundary (issue #1525/#1548 PR4):
      // get is read-only with respect to user memory content. It DOES write
      // the operator-side state file (last-seen-head marker), but that is
      // bookkeeping — the same way calibration.ts writes are uncounted. No
      // write-quota enforcement.
      const body = await this.readJsonBody(req);
      const op = getOperation("coding_delta");
      if (!op) {
        throw new EngramAccessInputError("access-boundary: operation not registered: coding_delta");
      }
      const output = (await op.run(body, {
        service: this.service,
        authenticatedPrincipal: this.resolveRequestPrincipal(req),
      })) as { result: unknown };
      this.respondJson(res, 200, output.result);
      return;
    }

    // ── Correction Contract (issue #1580) — plan / apply / pending ─────────
    // All three routes dispatch through the boundary operations so schema
    // validation + namespace policy reach every correction path (rule 22/39).
    if (req.method === "POST" && pathname === "/engram/v1/correction/plan") {
      // Planning persists a pending plan JSON file on every successful call
      // (review thread UhA), so it is a state write for rate-limit purposes —
      // mirror the apply route's precheck + accounting to bound files under
      // state/corrections/pending instead of letting an HTTP client create
      // unbounded plan files without consuming write quota.
      this.ensureWriteRateLimitAvailable();
      const body = await this.readJsonBody(req);
      const op = getOperation("memory_correct_plan");
      if (!op) {
        throw new EngramAccessInputError("access-boundary: operation not registered: memory_correct_plan");
      }
      const output = (await op.run(body, {
        service: this.service,
        authenticatedPrincipal: this.resolveRequestPrincipal(req),
      })) as { result: unknown };
      this.recordWriteRateLimitHit();
      this.respondJson(res, 200, output.result);
      return;
    }

    if (req.method === "POST" && pathname === "/engram/v1/correction/apply") {
      this.ensureWriteRateLimitAvailable();
      const body = await this.readJsonBody(req);
      const op = getOperation("memory_correct_apply");
      if (!op) {
        throw new EngramAccessInputError("access-boundary: operation not registered: memory_correct_apply");
      }
      const output = (await op.run(body, {
        service: this.service,
        authenticatedPrincipal: this.resolveRequestPrincipal(req),
      })) as { result: unknown };
      this.recordWriteRateLimitHit();
      this.respondJson(res, 200, output.result);
      return;
    }

    if (req.method === "GET" && pathname === "/engram/v1/correction/pending") {
      const op = getOperation("correction_pending");
      if (op) {
        const output = (await op.run(
          { namespace: parsed.searchParams.get("namespace") ?? undefined, sessionKey: parsed.searchParams.get("sessionKey") ?? undefined },
          { service: this.service, authenticatedPrincipal: this.resolveRequestPrincipal(req) },
        )) as { result: unknown };
        this.respondJson(res, 200, output.result);
      } else {
        const namespace = parsed.searchParams.get("namespace") ?? undefined;
        const sessionKey = parsed.searchParams.get("sessionKey") ?? undefined;
        const plans = await this.service.correctionListPending({
          ...(namespace ? { namespace } : {}),
          ...(sessionKey ? { sessionKey } : {}),
          principal: this.resolveRequestPrincipal(req),
        });
        this.respondJson(res, 200, plans);
      }
      return;
    }

    if (req.method === "POST" && pathname === "/engram/v1/suggestions") {
      void getOperation("suggestion_submit"); // boundary dispatch (issue #1525)
      const body = await this.readValidatedBody(req, "suggestionSubmit");
      const request = {
        schemaVersion: body.schemaVersion,
        idempotencyKey: body.idempotencyKey,
        dryRun: body.dryRun === true,
        sessionKey: body.sessionKey,
        authenticatedPrincipal: this.resolveRequestPrincipal(req),
        content: body.content,
        category: body.category,
        confidence: body.confidence,
        namespace: this.resolveNamespace(req, body.namespace),
        tags: body.tags,
        entityRef: body.entityRef,
        ttl: body.ttl,
        sourceReason: body.sourceReason,
        cwd: body.cwd,
        projectTag: body.projectTag,
      };
      // Quota enforcement is solely authoritative inside suggestionSubmit
      // (enforceWriteQuota), atomic with the real miss and never on a replay; no
      // HTTP pre-check, so a stale peek can't 429 a safe replay (#1434 Codex review).
      const response = await this.service.suggestionSubmit(request, {
        enforceWriteQuota: () => this.ensureWriteRateLimitAvailable(),
      });
      if (this.shouldCountWriteRateLimit(response as { dryRun?: boolean; idempotencyReplay?: boolean })) {
        this.recordWriteRateLimitHit();
      }
      this.respondJson(res, this.writeResponseStatus(response), response);
      return;
    }

    if (req.method === "GET" && pathname === "/engram/v1/memories") {
      void getOperation("memory_list"); // boundary dispatch (issue #1525)
      const limit = parseStrictIntegerQuery(parsed.searchParams.get("limit"), "limit", 50, 1);
      const offset = parseStrictIntegerQuery(parsed.searchParams.get("offset"), "offset", 0, 0);
      const sort = parseMemorySort(parsed.searchParams.get("sort"));
      const response = await this.service.memoryBrowse({
        query: parsed.searchParams.get("q") ?? undefined,
        status: parsed.searchParams.get("status") ?? undefined,
        category: parsed.searchParams.get("category") ?? undefined,
        namespace: parsed.searchParams.get("namespace") ?? undefined,
        authenticatedPrincipal: this.resolveRequestPrincipal(req),
        sort,
        limit,
        offset,
      });
      this.respondJson(res, 200, response);
      return;
    }

    const memoryMatch = pathname.match(/^\/engram\/v1\/memories\/([^/]+)$/);
    if (req.method === "GET" && memoryMatch) {
      const memoryId = decodeURIComponent(memoryMatch[1] ?? "");
      const namespace = parsed.searchParams.get("namespace") ?? undefined;
      // Issue #1582 — thread the transport session key so a `[m:xxxx]` handle in
      // the path resolves against this session's recall history (codex review).
      // resolveRequestIdentity reads it from adapter identity / request headers.
      const sessionKey =
        this.resolveRequestIdentity(req).sessionKey
        ?? parsed.searchParams.get("session")
        ?? parsed.searchParams.get("sessionKey")
        ?? undefined;
      // Migrated through the access boundary (issue #1525): the registry
      // entry owns memoryId presence/shape validation (rule 51: reject empty
      // ids loudly instead of silently passing "" into the service) and the
      // service dispatch.
      const op = getOperation("memory_get");
      if (!op) {
        throw new EngramAccessInputError("access-boundary: operation not registered: memory_get");
      }
      const output = (await op.run(
        { memoryId, namespace: namespace ?? null, sessionKey: sessionKey ?? null },
        { service: this.service, authenticatedPrincipal: this.resolveRequestPrincipal(req) },
      )) as { result: EngramAccessMemoryResponse };
      const response = output.result;
      this.respondJson(res, response.found ? 200 : 404, response);
      return;
    }

    const timelineMatch = pathname.match(/^\/engram\/v1\/memories\/([^/]+)\/timeline$/);
      void getOperation("memory_timeline"); // boundary dispatch (issue #1525)
    if (req.method === "GET" && timelineMatch) {
      const memoryId = decodeURIComponent(timelineMatch[1] ?? "");
      const namespace = parsed.searchParams.get("namespace") ?? undefined;
      const limit = parseStrictIntegerQuery(parsed.searchParams.get("limit"), "limit", 200, 1);
      const response = await this.service.memoryTimeline(memoryId, namespace, limit, this.resolveRequestPrincipal(req));
      this.respondJson(res, response.found ? 200 : 404, response);
      return;
    }

    if (req.method === "GET" && pathname === "/engram/v1/entities") {
      void getOperation("entity_list"); // boundary dispatch (issue #1525)
      const limit = parseStrictIntegerQuery(parsed.searchParams.get("limit"), "limit", 50, 1);
      const offset = parseStrictIntegerQuery(parsed.searchParams.get("offset"), "offset", 0, 0);
      const response = await this.service.entityList({
        namespace: parsed.searchParams.get("namespace") ?? undefined,
        query: parsed.searchParams.get("q") ?? undefined,
        limit,
        offset,
      });
      this.respondJson(res, 200, response);
      return;
    }

    const entityMatch = pathname.match(/^\/engram\/v1\/entities\/([^/]+)$/);
      void getOperation("entity_get"); // boundary dispatch (issue #1525)
    if (req.method === "GET" && entityMatch) {
      const entityName = decodeURIComponent(entityMatch[1] ?? "");
      const namespace = parsed.searchParams.get("namespace") ?? undefined;
      const response = await this.service.entityGet(entityName, namespace);
      this.respondJson(res, response.found ? 200 : 404, response);
      return;
    }

    if (req.method === "GET" && pathname === "/engram/v1/review-queue") {
      void getOperation("review_queue_list"); // boundary dispatch (issue #1525)
      const response = await this.service.reviewQueue(
        parsed.searchParams.get("runId") ?? undefined,
        parsed.searchParams.get("namespace") ?? undefined,
        this.resolveRequestPrincipal(req),
      );
      this.respondJson(res, 200, response);
      return;
    }

    if (req.method === "GET" && pathname === "/engram/v1/maintenance") {
      void getOperation("maintenance_status"); // boundary dispatch (issue #1525)
      this.respondJson(res, 200, await this.service.maintenance(parsed.searchParams.get("namespace") ?? undefined, this.resolveRequestPrincipal(req)));
      return;
    }

    if (req.method === "GET" && pathname === "/engram/v1/quality") {
      void getOperation("quality_status"); // boundary dispatch (issue #1525)
      this.respondJson(res, 200, await this.service.quality(parsed.searchParams.get("namespace") ?? undefined, this.resolveRequestPrincipal(req)));
      return;
    }

    if (req.method === "GET" && pathname === "/engram/v1/trust-zones/status") {
      void getOperation("trust_zones_status"); // boundary dispatch (issue #1525)
      this.respondJson(
        res,
        200,
        await this.service.trustZoneStatus(parsed.searchParams.get("namespace") ?? undefined, this.resolveRequestPrincipal(req)),
      );
      return;
    }

    // Procedural memory stats (issue #567 PR 5/5). Read-only; namespace is
    // scoped via the same resolver used by recall/trust-zones so cross-
    // tenant reads aren't possible (CLAUDE.md rule 42).
    if (req.method === "GET" && pathname === "/engram/v1/procedural/stats") {
      void getOperation("procedural_stats"); // boundary dispatch (issue #1525)
      const namespaceParam = parsed.searchParams.get("namespace");
      this.respondJson(
        res,
        200,
        await this.service.procedureStats(
          {
            namespace: this.resolveNamespace(
              req,
              namespaceParam && namespaceParam.length > 0
                ? namespaceParam
                : undefined,
            ),
          },
          this.resolveRequestPrincipal(req),
        ),
      );
      return;
    }

    if (req.method === "GET" && pathname === "/engram/v1/trust-zones/records") {
      void getOperation("trust_zones_records"); // boundary dispatch (issue #1525)
      const limit = parseStrictIntegerQuery(parsed.searchParams.get("limit"), "limit", 25, 1);
      const offset = parseStrictIntegerQuery(parsed.searchParams.get("offset"), "offset", 0, 0);
      const response = await this.service.trustZoneBrowse({
        query: parsed.searchParams.get("q") ?? undefined,
        zone: parseTrustZoneFilter(parsed.searchParams.get("zone")),
        kind: parseTrustZoneKindFilter(parsed.searchParams.get("kind")),
        sourceClass: parseTrustZoneSourceClassFilter(parsed.searchParams.get("sourceClass")),
        namespace: parsed.searchParams.get("namespace") ?? undefined,
        limit,
        offset,
      }, this.resolveRequestPrincipal(req));
      this.respondJson(res, 200, response);
      return;
    }

    if (req.method === "POST" && pathname === "/engram/v1/review-disposition") {
      void getOperation("review_disposition"); // boundary dispatch (issue #1525)
      const body = await this.readValidatedBody(req, "reviewDisposition");
      this.ensureWriteRateLimitAvailable();
      const response = await this.service.reviewDisposition({
        memoryId: body.memoryId,
        status: body.status,
        reasonCode: body.reasonCode,
        namespace: this.resolveNamespace(req, body.namespace),
        authenticatedPrincipal: this.resolveRequestPrincipal(req),
      });
      if (this.shouldCountWriteRateLimit(response as unknown as { dryRun?: boolean; idempotencyReplay?: boolean })) {
        this.recordWriteRateLimitHit();
      }
      this.respondJson(res, 200, response);
      return;
    }

    if (req.method === "POST" && pathname === "/engram/v1/trust-zones/promote") {
      void getOperation("trust_zones_promote"); // boundary dispatch (issue #1525)
      const body = await this.readValidatedBody(req, "trustZonePromote");
      const dryRun = body.dryRun === true;
      if (!dryRun) {
        this.ensureWriteRateLimitAvailable();
      }
      const response = await this.service.trustZonePromote({
        recordId: body.recordId,
        targetZone: body.targetZone,
        promotionReason: body.promotionReason,
        recordedAt: body.recordedAt,
        summary: body.summary,
        dryRun,
        namespace: this.resolveNamespace(req, body.namespace),
        authenticatedPrincipal: this.resolveRequestPrincipal(req),
      });
      if (this.shouldCountWriteRateLimit(response as unknown as { dryRun?: boolean; idempotencyReplay?: boolean })) {
        this.recordWriteRateLimitHit();
      }
      this.respondJson(res, response.dryRun ? 200 : 201, response);
      return;
    }

    if (req.method === "POST" && pathname === "/engram/v1/trust-zones/demo-seed") {
      void getOperation("trust_zones_demo_seed"); // boundary dispatch (issue #1525)
      const body = await this.readValidatedBody(req, "trustZoneDemoSeed");
      const dryRun = body.dryRun === true;
      if (!dryRun) {
        this.ensureWriteRateLimitAvailable();
      }
      const response = await this.service.trustZoneDemoSeed({
        scenario: body.scenario,
        recordedAt: body.recordedAt,
        dryRun,
        namespace: this.resolveNamespace(req, body.namespace),
        authenticatedPrincipal: this.resolveRequestPrincipal(req),
      });
      if (this.shouldCountWriteRateLimit(response as unknown as { dryRun?: boolean; idempotencyReplay?: boolean })) {
        this.recordWriteRateLimitHit();
      }
      this.respondJson(res, response.dryRun ? 200 : 201, response);
      return;
    }

    // Citation usage tracking (issue #379)
    if (req.method === "POST" && pathname === "/v1/citations/observed") {
      void getOperation("citations_observed"); // boundary dispatch (issue #1525)
      const body = await this.readJsonBody(req);
      if (!body || typeof body !== "object" || Array.isArray(body)) {
        throw new HttpError(400, "request body must be a JSON object", "invalid_body");
      }
      const payload = body as Record<string, unknown>;
      const sessionId = typeof payload.sessionId === "string" ? payload.sessionId : undefined;
      const namespace = typeof payload.namespace === "string" ? payload.namespace : undefined;
      const citationsRaw = payload.citations;
      if (!citationsRaw || typeof citationsRaw !== "object" || Array.isArray(citationsRaw)) {
        throw new HttpError(400, "citations must be a JSON object with entries and rolloutIds", "invalid_citations");
      }
      const citObj = citationsRaw as Record<string, unknown>;
      const entries: CitationEntry[] = [];
      if (Array.isArray(citObj.entries)) {
        for (const raw of citObj.entries) {
          if (raw && typeof raw === "object" && !Array.isArray(raw)) {
            const e = raw as Record<string, unknown>;
            if (
              typeof e.path === "string" &&
              typeof e.lineStart === "number" &&
              typeof e.lineEnd === "number"
            ) {
              entries.push({
                path: e.path,
                lineStart: e.lineStart,
                lineEnd: e.lineEnd,
                note: typeof e.note === "string" ? e.note : "",
              });
            }
          }
        }
      }
      const rolloutIds: string[] = [];
      if (Array.isArray(citObj.rolloutIds)) {
        for (const id of citObj.rolloutIds) {
          if (typeof id === "string" && id.length > 0) {
            rolloutIds.push(id);
          }
        }
      }

      // Record usage: for each citation entry, try to increment usage on the
      // matching memory. The service exposes recordAccess for this purpose.
      // Pass authenticatedPrincipal so namespace ACL checks use the same
      // identity resolution as other write endpoints (Finding #1, issue #379).
      let matched = 0;
      let submitted = 0;
      if (typeof this.service.recordCitationUsage === "function") {
        const result = await this.service.recordCitationUsage({
          sessionId,
          namespace: this.resolveNamespace(req, namespace),
          authenticatedPrincipal: this.resolveRequestPrincipal(req),
          entries,
          rolloutIds,
        });
        submitted = result.submitted;
        matched = result.matched;
      }

      this.respondJson(res, 200, {
        ok: true,
        submitted,
        matched,
        entriesReceived: entries.length,
        rolloutIdsReceived: rolloutIds.length,
      });
      return;
    }

    // ── Contradiction Review (issue #520) ─────────────────────────────────────
    if (req.method === "GET" && pathname === "/engram/v1/review/contradictions") {
      void getOperation("review_list"); // boundary dispatch (issue #1525)
      const VALID_FILTERS = new Set(["all", "unresolved", "contradicts", "independent", "duplicates", "needs-user"]);
      const rawFilter = parsed.searchParams.get("filter") ?? "unresolved";
      if (!VALID_FILTERS.has(rawFilter)) {
        this.respondJson(res, 400, { error: `Invalid filter '${rawFilter}'. Valid: ${[...VALID_FILTERS].join(", ")}` });
        return;
      }
      const namespace = parsed.searchParams.get("namespace") ?? undefined;
      const limit = parseStrictIntegerQuery(parsed.searchParams.get("limit"), "limit", 50, 1);
      const {
        isDefaultReviewNamespace,
        listPairs,
      } = await import("./contradiction/contradiction-review.js");
      const principal = this.resolveRequestPrincipal(req);
      const resolved = await this.service.getReadableStorageForNamespace(namespace, principal);
      const reviewNamespace = this.service.configRef.namespacesEnabled ? resolved.namespace : undefined;
      const includeUnscopedForNamespace = Boolean(
        reviewNamespace && isDefaultReviewNamespace(this.service.configRef.defaultNamespace, namespace, reviewNamespace),
      );
      const result = listPairs(this.service.memoryDir, {
        filter: rawFilter as "all" | "unresolved" | "contradicts" | "independent" | "duplicates" | "needs-user",
        namespace: reviewNamespace,
        includeUnscopedForNamespace,
        limit,
      });
      this.respondJson(res, 200, result);
      return;
    }

    if (req.method === "GET" && pathname.startsWith("/engram/v1/review/contradictions/")) {
      void getOperation("contradiction_detail"); // boundary dispatch (issue #1525)
      const pairId = pathname.split("/").pop() ?? "";
      const { readPair } = await import("./contradiction/contradiction-review.js");
      const pair = readPair(this.service.memoryDir, pairId);
      if (!pair) {
        this.respondJson(res, 404, { error: "pair_not_found" });
        return;
      }
      try {
        await this.service.getReadableStorageForNamespace(pair.namespace, this.resolveRequestPrincipal(req));
      } catch {
        this.respondJson(res, 404, { error: "pair_not_found" });
        return;
      }
      this.respondJson(res, 200, pair);
      return;
    }

    if (req.method === "POST" && pathname === "/engram/v1/review/resolve") {
      void getOperation("review_resolve"); // boundary dispatch (issue #1525)
      const body = await this.readJsonBody(req) as Record<string, unknown>;
      const pairId = typeof body.pairId === "string" ? body.pairId : "";
      const verb = typeof body.verb === "string" ? body.verb : "";
      if (!pairId || !verb) {
        this.respondJson(res, 400, { error: "pairId and verb are required" });
        return;
      }
      const { isValidResolutionVerb, executeResolution } = await import("./contradiction/resolution.js");
      if (!isValidResolutionVerb(verb)) {
        this.respondJson(res, 400, { error: `Invalid verb: ${verb}. Must be one of: keep-a, keep-b, merge, both-valid, needs-more-context` });
        return;
      }
      const principal = this.resolveRequestPrincipal(req);
      const result = await executeResolution(this.service.memoryDir, this.service.storageRef, pairId, verb, {
        mergedMemoryId: typeof body.mergedMemoryId === "string" ? body.mergedMemoryId : undefined,
        mergedContent: typeof body.mergedContent === "string" ? body.mergedContent : undefined,
        storageForNamespace: async (namespace) => {
          const resolved = await this.service.getWritableStorageForNamespace(namespace, principal);
          return resolved.storage;
        },
        // Catalog write touch (issue #1499 sweep): a contradiction merge writes a
        // new memory directly to the resolved namespace storage, bypassing the
        // extraction write path. Record it so QMD maintenance / writtenSince
        // don't miss the write. Best-effort and failure-tolerant.
        onMergedMemoryWritten: (namespace, storageDir) => {
          // #1522: catalog touch handled at the storage chokepoint.
        },
      });
      this.respondJson(res, 200, result);
      return;
    }

    // Graph snapshot (issue #691 PR 2/5) — read-only adjacency view used by
    // the admin-pane scaffold shipped in PR 1/5.  All filters are query
    // params so the surface stays cacheable; invalid values yield 400 with
    // a descriptive body (CLAUDE.md rule 51 — never silently default).
    if (req.method === "GET" && pathname === "/engram/v1/graph/snapshot") {
      void getOperation("graph_snapshot"); // boundary dispatch (issue #1525)
      const limitRaw = parsed.searchParams.get("limit");
      let limit: number | undefined;
      if (limitRaw !== null && limitRaw.length > 0) {
        const parsedLimit = Number(limitRaw);
        if (
          !Number.isFinite(parsedLimit)
          || !Number.isInteger(parsedLimit)
          || parsedLimit <= 0
        ) {
          this.respondJson(res, 400, {
            error: "invalid_limit",
            code: "invalid_limit",
            message: "limit must be a positive integer",
          });
          return;
        }
        limit = parsedLimit;
      }
      const sinceRaw = parsed.searchParams.get("since");
      let since: string | undefined;
      if (sinceRaw !== null && sinceRaw.length > 0) {
        // Validate up-front so the access service can stay focused on the
        // pure snapshot logic (parser also runs there as a defense in
        // depth, but rejecting at the boundary preserves the
        // "invalid_since" error code instead of leaking a generic 500).
        if (!Number.isFinite(Date.parse(sinceRaw))) {
          this.respondJson(res, 400, {
            error: "invalid_since",
            code: "invalid_since",
            message: "since must be a parseable ISO timestamp",
          });
          return;
        }
        since = sinceRaw;
      }
      const focusNodeIdRaw = parsed.searchParams.get("focusNodeId");
      const focusNodeId = focusNodeIdRaw && focusNodeIdRaw.length > 0
        ? focusNodeIdRaw
        : undefined;
      const categoriesRaw = parsed.searchParams.get("categories");
      let categories: string[] | undefined;
      if (categoriesRaw !== null && categoriesRaw.length > 0) {
        categories = categoriesRaw
          .split(",")
          .map((value) => value.trim())
          .filter((value) => value.length > 0);
        if (categories.length === 0) {
          this.respondJson(res, 400, {
            error: "invalid_categories",
            code: "invalid_categories",
            message:
              "categories must be a comma-separated list with at least one non-empty value",
          });
          return;
        }
      }
      const namespaceParam = parsed.searchParams.get("namespace");
      const namespace = this.resolveNamespace(
        req,
        namespaceParam && namespaceParam.length > 0 ? namespaceParam : undefined,
      );
      try {
        const snapshot = await this.service.graphSnapshot(
          {
            namespace,
            ...(limit !== undefined ? { limit } : {}),
            ...(since !== undefined ? { since } : {}),
            ...(focusNodeId !== undefined ? { focusNodeId } : {}),
            ...(categories !== undefined ? { categories } : {}),
          },
          this.resolveRequestPrincipal(req),
        );
        this.respondJson(res, 200, snapshot);
      } catch (err) {
        // As with recallXray above: surface only the deliberately-prefixed
        // graphSnapshot validation Error.message, never String(err) of an
        // arbitrary throw (CodeQL js/stack-trace-exposure). Validation errors are
        // always Error instances; anything else is rethrown as a 500.
        if (err instanceof Error && err.message.startsWith("graphSnapshot:")) {
          this.respondJson(res, 400, {
            error: "invalid_request",
            code: "invalid_request",
            message: err.message,
          });
          return;
        }
        throw err;
      }
      return;
    }

    if (req.method === "POST" && pathname === "/engram/v1/contradiction-scan") {
      void getOperation("contradiction_scan_run"); // boundary dispatch (issue #1525)
      const body = await this.readJsonBody(req) as Record<string, unknown>;
      const { runContradictionScan } = await import("./contradiction/contradiction-scan.js");
      const principal = this.resolveRequestPrincipal(req);
      const result = await runContradictionScan({
        storage: this.service.storageRef,
        config: this.service.configRef,
        memoryDir: this.service.memoryDir,
        embeddingLookupFactory: this.service.embeddingLookupFactoryRef,
        storageForNamespace: (namespace) =>
          this.service.getWritableStorageForNamespace(namespace, principal),
        localLlm: this.service.localLlmRef,
        fallbackLlm: this.service.fallbackLlmRef,
        namespace: typeof body.namespace === "string" ? body.namespace : undefined,
      });
      this.respondJson(res, 200, result);
      return;
    }

    // ── Graph mutation event stream (issue #691 PR 5/5) ──────────────────────
    //
    // GET /engram/v1/graph/events
    //
    // Server-Sent Events stream that emits graph mutation events in real time.
    // Event types: node-added, node-updated, edge-added, edge-updated, edge-removed.
    //
    // Auth: same Bearer token scheme as every other endpoint (checked above).
    //
    // The SSE handler subscribes to the in-process graph event bus for the
    // resolved memory dir.  Events are batched within a 200 ms window so a
    // burst of writes (e.g. extraction of a large turn) doesn't overwhelm
    // the admin UI canvas with individual re-renders.
    //
    // The client receives a `data: <json>\n\n` line per batch.  Each batch
    // payload is { events: GraphEvent[] }.
    //
    // The stream sends a heartbeat `data: {"type":"heartbeat"}\n\n` every
    // 25 s so load balancers and proxies don't time out idle connections.
    if (req.method === "GET" && pathname === "/engram/v1/graph/events") {
      void getOperation("graph_events"); // boundary dispatch (issue #1525)
      await this.handleGraphEventsSSE(req, res);
      return;
    }

    // ── Chat endpoints (issue #1583) ────────────────────────────────────────
    if (req.method === "POST" && pathname === "/engram/v1/chat/message") {
      void getOperation("chat_message"); // boundary dispatch (issue #1525)
      const body = await this.readJsonBody(req) as Record<string, unknown>;
      await handleChatMessage(
        req, res, body,
        { service: this.service, config: this.service.configRef?.chat, memoryDir: this.service.memoryDir },
        this.resolveRequestPrincipal(req),
      );
      return;
    }
    const chatEventsMatch = /^\/engram\/v1\/chat\/events\/([^/]+)$/.exec(pathname);
    if (req.method === "GET" && chatEventsMatch) {
      void getOperation("chat_events"); // boundary dispatch (issue #1525)
      await handleChatEventsSSE(
        req, res, chatEventsMatch[1] ?? "",
        { service: this.service, config: this.service.configRef?.chat, memoryDir: this.service.memoryDir },
        this.resolveRequestPrincipal(req),
      );
      return;
    }

    // ── Peer Registry endpoints (issue #679) ─────────────────────────────────
    // GET /engram/v1/console/state — operator console engine-state snapshot (issue #688 PR 2/3).
    // Read-only; namespace-aware via resolveRequestPrincipal so cross-tenant
    // reads are not possible (CLAUDE.md rule 42).
    if (req.method === "GET" && pathname === "/engram/v1/console/state") {
      void getOperation("console_state"); // boundary dispatch (issue #1525)
      const namespace = parsed.searchParams.get("namespace") ?? undefined;
      const snapshot = await this.service.consoleState(namespace, this.resolveRequestPrincipal(req));
      this.respondJson(res, 200, snapshot);
      return;
    }

    //   GET    /engram/v1/peers              — list all peers
    //   GET    /engram/v1/peers/:id          — get one peer
    //   PUT    /engram/v1/peers/:id          — upsert (create/update)
    //   DELETE /engram/v1/peers/:id          — delete identity only (idempotent)
    //   DELETE /engram/v1/peers/:id?forget=true — destructive full purge (issue #679 completion)
    //   GET    /engram/v1/peers/:id/profile  — get peer profile
    if (req.method === "GET" && pathname === "/engram/v1/peers") {
      void getOperation("peer_list"); // boundary dispatch (issue #1525)
      const result = await this.service.peerList();
      this.respondJson(res, 200, result);
      return;
    }

    const peerProfileMatch = /^\/engram\/v1\/peers\/([^/]+)\/profile$/.exec(pathname);
    if (peerProfileMatch) {
      void getOperation("peer_profile_get"); // boundary dispatch (issue #1525)
      if (req.method !== "GET") {
        this.respondJson(res, 405, { error: "method_not_allowed", code: "method_not_allowed" });
        return;
      }
      const peerId = decodePeerIdSegment(peerProfileMatch[1] ?? "");
      const result = await this.service.peerProfileGet(peerId);
      if (!result.found) {
        this.respondJson(res, 404, { error: "peer_profile_not_found", code: "peer_profile_not_found" });
        return;
      }
      this.respondJson(res, 200, result);
      return;
    }

    const peerIdMatch = /^\/engram\/v1\/peers\/([^/]+)$/.exec(pathname);
    if (peerIdMatch) {
      const peerId = decodePeerIdSegment(peerIdMatch[1] ?? "");

      if (req.method === "GET") {
        void getOperation("peer_get"); // boundary dispatch (issue #1525)
        const result = await this.service.peerGet(peerId);
        if (!result.found) {
          this.respondJson(res, 404, { error: "peer_not_found", code: "peer_not_found" });
          return;
        }
        this.respondJson(res, 200, result);
        return;
      }

      if (req.method === "PUT") {
        void getOperation("peer_set"); // boundary dispatch (issue #1525)
        const body = await this.readJsonBody(req) as Record<string, unknown>;
        // Reject malformed types up front rather than silently dropping them
        // to undefined and letting peerSet fall back to defaults
        // (CLAUDE.md rule 51: no silent defaults on bad input).
        if ("kind" in body && body.kind !== undefined && typeof body.kind !== "string") {
          throw new EngramAccessInputError("kind must be a string when provided");
        }
        if (
          "displayName" in body &&
          body.displayName !== undefined &&
          typeof body.displayName !== "string"
        ) {
          throw new EngramAccessInputError("displayName must be a string when provided");
        }
        if ("notes" in body && body.notes !== undefined && typeof body.notes !== "string") {
          throw new EngramAccessInputError("notes must be a string when provided");
        }
        const result = await this.service.peerSet({
          id: peerId,
          kind: typeof body.kind === "string" ? body.kind : undefined,
          displayName: typeof body.displayName === "string" ? body.displayName : undefined,
          notes: typeof body.notes === "string" ? body.notes : undefined,
        });
        this.respondJson(res, result.created ? 201 : 200, result);
        return;
      }

      if (req.method === "DELETE") {
        void getOperation("peer_delete"); // boundary dispatch (issue #1525)
        // `?forget=true` triggers the destructive full-purge path (issue #679
        // completion). The caller must also pass `confirm=yes` in the request
        // body; absent confirmation yields 400. Plain DELETE (no ?forget) keeps
        // the existing soft-delete behaviour (identity.md only).
        const forgetParam = parsed.searchParams.get("forget");
        if (forgetParam === "true") {
          const body = await this.readJsonBody(req) as Record<string, unknown>;
          const confirm = typeof body.confirm === "string" ? body.confirm : "";
          if (confirm !== "yes") {
            this.respondJson(res, 400, {
              error: "confirm_required",
              code: "confirm_required",
              message: "DELETE ?forget=true requires { confirm: 'yes' } in the request body",
            });
            return;
          }
          const result = await this.service.peerForget(peerId, { confirm: "yes" });
          this.respondJson(res, 200, result);
          return;
        }
        const result = await this.service.peerDelete(peerId);
        this.respondJson(res, 200, result);
        return;
      }

      this.respondJson(res, 405, { error: "method_not_allowed", code: "method_not_allowed" });
      return;
    }

    // ── Dreams telemetry (issue #678 PR 3+4) ──────────────────────────────────

    if (req.method === "GET" && pathname === "/engram/v1/dreams/status") {
      void getOperation("dreams_status"); // boundary dispatch (issue #1525)
      const { normalizeDreamsStatusWindowHours } = await import("./maintenance/dreams-ledger.js");
      const windowHoursRaw = parsed.searchParams.get("windowHours");
      let windowHours: number;
      try {
        windowHours = normalizeDreamsStatusWindowHours(
          windowHoursRaw !== null ? Number(windowHoursRaw) : undefined,
        );
      } catch {
        this.respondJson(res, 400, { error: "windowHours must be a positive integer" });
        return;
      }
      const namespaceParam = parsed.searchParams.get("namespace");
      const namespace = namespaceParam && namespaceParam.length > 0 ? namespaceParam : undefined;
      const result = await this.service.dreamsStatus({
        windowHours,
        namespace,
        principal: this.resolveRequestPrincipal(req),
      });
      this.respondJson(res, 200, result);
      return;
    }

    if (req.method === "POST" && pathname === "/engram/v1/dreams/run") {
      void getOperation("dreams_run"); // boundary dispatch (issue #1525)
      const body = await this.readJsonBody(req) as Record<string, unknown>;
      const VALID_PHASES = ["lightSleep", "rem", "deepSleep"] as const;
      const phase = typeof body.phase === "string" ? body.phase : undefined;
      if (!phase || !(VALID_PHASES as readonly string[]).includes(phase)) {
        this.respondJson(res, 400, {
          error: `phase is required and must be one of: ${VALID_PHASES.join(", ")}`,
        });
        return;
      }
      if (
        "dryRun" in body &&
        body.dryRun !== undefined &&
        typeof body.dryRun !== "boolean"
      ) {
        this.respondJson(res, 400, {
          error: "dryRun must be a boolean when provided",
        });
        return;
      }
      if (
        "namespace" in body &&
        body.namespace !== undefined &&
        typeof body.namespace !== "string"
      ) {
        this.respondJson(res, 400, {
          error: "namespace must be a string when provided",
        });
        return;
      }
      const dryRun = body.dryRun === true;
      const namespace =
        typeof body.namespace === "string" ? body.namespace : undefined;
      if (!dryRun) {
        this.ensureWriteRateLimitAvailable();
      }
      const result = await this.service.dreamsRun({
        phase: phase as import("./types.js").DreamsPhase,
        dryRun,
        namespace,
        authenticatedPrincipal: this.resolveRequestPrincipal(req),
      });
      if (this.shouldCountWriteRateLimit(result as { dryRun?: boolean; idempotencyReplay?: boolean })) {
        this.recordWriteRateLimitHit();
      }
      this.respondJson(res, 200, result);
      return;
    }

    // ── Admin console surfaces (issue #1502) ──────────────────────────────
    //
    // All five routes live under /engram/v1/admin/* which the access-surface
    // catalog treats as infrastructure (no boundary migration required; the
    // static-completeness fitness test excludes this prefix). They still
    // require bearer auth (enforced above) and delegate to core APIs via the
    // pure admin-surfaces module — the dashboard never re-resolves scope or
    // re-lists namespaces.
    if (pathname.startsWith("/engram/v1/admin/scope/inspect")) {
      if (req.method !== "GET" && req.method !== "POST") {
        this.respondJson(res, 405, { error: "method_not_allowed", code: "method_not_allowed" });
        return;
      }
      const principal = this.resolveRequestPrincipal(req);
      let sessionKey: string | undefined;
      let namespaceOverride: string | undefined;
      let operation: string | undefined;
      if (req.method === "GET") {
        sessionKey = parsed.searchParams.get("session") ?? undefined;
        namespaceOverride = parsed.searchParams.get("namespace") ?? undefined;
        operation = parsed.searchParams.get("operation") ?? undefined;
      } else {
        const body = await this.readJsonBody(req) as Record<string, unknown>;
        sessionKey = typeof body.sessionKey === "string" ? body.sessionKey : undefined;
        namespaceOverride = typeof body.namespace === "string" ? body.namespace : undefined;
        operation = typeof body.operation === "string" ? body.operation : undefined;
      }
      const inspection = await this.service.adminInspectScope({
        sessionKey: sessionKey && sessionKey.length > 0 ? sessionKey : undefined,
        namespace: namespaceOverride && namespaceOverride.length > 0 ? namespaceOverride : undefined,
        principalOverride: principal,
        operation: operation as "recall" | "observe" | "memory_store" | "maintenance" | "dashboard" | undefined,
      });
      this.respondJson(res, 200, inspection);
      return;
    }

    if (pathname === "/engram/v1/admin/namespaces") {
      if (req.method !== "GET") {
        this.respondJson(res, 405, { error: "method_not_allowed", code: "method_not_allowed" });
        return;
      }
      const kind = parsed.searchParams.get("kind");
      const principal = parsed.searchParams.get("principal");
      const projectId = parsed.searchParams.get("projectId");
      const discoveredBy = parsed.searchParams.get("discoveredBy");
      const result = await this.service.adminListNamespaces({
        ...(kind ? { kind: kind as "default" | "self" | "shared" | "project" | "branch" | "team-project" | "explicit" | "legacy" } : {}),
        ...(principal && principal.length > 0 ? { principal } : {}),
        ...(projectId && projectId.length > 0 ? { projectId } : {}),
        ...(discoveredBy
          ? { discoveredBy: discoveredBy as "config" | "write" | "read" | "scan" | "migration" }
          : {}),
      });
      this.respondJson(res, 200, result);
      return;
    }

    if (pathname === "/engram/v1/admin/maintenance-health") {
      if (req.method !== "GET") {
        this.respondJson(res, 405, { error: "method_not_allowed", code: "method_not_allowed" });
        return;
      }
      const report = await this.service.adminMaintenanceHealth();
      this.respondJson(res, 200, report);
      return;
    }

    if (pathname === "/engram/v1/admin/transcript-audit") {
      if (req.method !== "GET") {
        this.respondJson(res, 405, { error: "method_not_allowed", code: "method_not_allowed" });
        return;
      }
      const report = await this.service.adminTranscriptAudit();
      this.respondJson(res, 200, report);
      return;
    }

    if (pathname === "/engram/v1/admin/promote") {
      if (req.method !== "POST") {
        this.respondJson(res, 405, { error: "method_not_allowed", code: "method_not_allowed" });
        return;
      }
      const body = await this.readJsonBody(req) as Record<string, unknown>;
      if (typeof body.sourceMemoryId !== "string" || body.sourceMemoryId.trim().length === 0) {
        this.respondJson(res, 400, {
          error: "invalid_request",
          code: "invalid_request",
          message: "sourceMemoryId is required",
        });
        return;
      }
      if (typeof body.reason !== "string" || body.reason.trim().length === 0) {
        this.respondJson(res, 400, {
          error: "invalid_request",
          code: "invalid_request",
          message: "reason is required for promotion",
        });
        return;
      }
      if (!Array.isArray(body.targets) || body.targets.length === 0) {
        this.respondJson(res, 400, {
          error: "invalid_request",
          code: "invalid_request",
          message: "targets must be a non-empty array",
        });
        return;
      }
      const VALID_TARGETS = ["teamProject", "serverShared", "userProject", "userGlobal", "explicit"];
      const targets = [];
      for (const entry of body.targets) {
        if (typeof entry !== "object" || entry === null || typeof entry.kind !== "string") {
          this.respondJson(res, 400, {
            error: "invalid_request",
            code: "invalid_request",
            message: "each target must be an object with a 'kind' string",
          });
          return;
        }
        if (!VALID_TARGETS.includes(entry.kind)) {
          this.respondJson(res, 400, {
            error: "invalid_request",
            code: "invalid_request",
            message: `target.kind must be one of: ${VALID_TARGETS.join(", ")}`,
          });
          return;
        }
        targets.push({
          kind: entry.kind as "teamProject" | "serverShared" | "userProject" | "userGlobal" | "explicit",
          namespace: typeof entry.namespace === "string" ? entry.namespace : undefined,
        });
      }
      try {
        // Rate-limit the promotion write, matching every other write route
        // (observe, trust-zone promotion, etc.). The check throws on limit
        // exceeded; the hit is recorded after the write succeeds.
        this.ensureWriteRateLimitAvailable();
        const result = await this.service.adminPromoteMemory({
          sourceMemoryId: body.sourceMemoryId,
          namespace: typeof body.namespace === "string" ? body.namespace : undefined,
          principal: this.resolveRequestPrincipal(req),
          sessionKey: typeof body.sessionKey === "string" ? body.sessionKey : undefined,
          targets,
          reason: body.reason,
        });
        // actor is derived from the authenticated principal inside adminPromoteMemory;
        this.recordWriteRateLimitHit();
        this.respondJson(res, 200, result);
      } catch (err) {
        if (err instanceof EngramAccessInputError) {
          this.respondJson(res, 400, {
            error: "invalid_request",
            code: "invalid_request",
            message: err.message,
          });
          return;
        }
        throw err;
      }
      return;
    }

    this.respondJson(res, 404, { error: "not_found", code: "not_found" });
  }

  private createRequestAbortSignal(req: IncomingMessage, res: ServerResponse): AbortSignal {
    const controller = new AbortController();
    const abort = () => {
      if (!controller.signal.aborted) controller.abort();
    };
    req.once("aborted", abort);
    res.once("close", () => {
      if (!res.writableEnded) abort();
    });
    return controller.signal;
  }

  /**
   * SSE handler for /engram/v1/graph/events.
   *
   * Lifecycle:
   *  1. Write SSE headers (Content-Type: text/event-stream).
   *  2. Register this response in `sseClients`.
   *  3. Resolve the namespace from the request and subscribe to THAT
   *     namespace's graph event bus (Codex P1: in multi-namespace
   *     deployments each namespace has its own bus keyed by its storage
   *     dir — subscribing to the global root leaks events across tenants).
   *  4. On each event, add to a 200 ms batch; flush batch as a single SSE frame.
   *  5. Send heartbeat every 25 s.
   *  6. On client disconnect (req "close"), clean up timers and unsubscribe.
   *  7. Register the cleanup callback in `sseCleanupFns` so `stop()` can
   *     release the heartbeat interval and bus subscription even when the
   *     client never disconnects (Cursor review thread `access-http.ts:232`).
   */
  private async handleGraphEventsSSE(req: IncomingMessage, res: ServerResponse): Promise<void> {
    // Resolve namespace from the ?namespace= query parameter (same pattern
    // as graphSnapshot and other read endpoints).  Falls back to the
    // default namespace when absent.
    const parsed = new URL(req.url ?? "/", `http://${hostToUrlAuthority(this.host)}`);
    const namespaceParam = parsed.searchParams.get("namespace");
    const namespace = namespaceParam && namespaceParam.length > 0 ? namespaceParam : undefined;
    // Resolve to the per-namespace storage directory so the bus subscription
    // is scoped to the correct tenant (CLAUDE.md rule 42).
    // Pass the request principal so namespace ACL is enforced — without it,
    // resolveReadableNamespace throws when namespacesEnabled=true (Cursor
    // thread PRRT_kwDORJXyws59snoR / Codex thread PRRT_kwDORJXyws59soGJ).
    const principal = this.resolveRequestPrincipal(req);
    const memoryDir = await this.service.getMemoryDirForNamespace(namespace, principal);

    res.writeHead(200, {
      "content-type": "text/event-stream; charset=utf-8",
      "cache-control": "no-cache, no-store, must-revalidate",
      "connection": "keep-alive",
      "x-accel-buffering": "no",     // prevent nginx buffering
      "transfer-encoding": "chunked",
    });

    // Send initial "connected" frame so the client knows the stream is live.
    const writeSSE = (payload: unknown): void => {
      try {
        res.write(`data: ${JSON.stringify(payload)}\n\n`);
      } catch {
        // client already gone — cleanup will fire via "close"
      }
    };

    writeSSE({ type: "connected" });

    this.sseClients.add(res);

    // --- 200 ms batch throttle -----------------------------------------------
    const flushBatch = (): void => {
      const batch = this.ssePendingBatches.get(res);
      if (!batch || batch.length === 0) return;
      this.ssePendingBatches.delete(res);
      this.sseBatchTimers.delete(res);
      writeSSE({ type: "batch", events: batch });
    };

    const unsubscribe = subscribeGraphEvents(memoryDir, (event: GraphEvent) => {
      let batch = this.ssePendingBatches.get(res);
      if (!batch) {
        batch = [];
        this.ssePendingBatches.set(res, batch);
      }
      batch.push(event);
      if (!this.sseBatchTimers.has(res)) {
        this.sseBatchTimers.set(res, setTimeout(flushBatch, 200));
      }
    });

    // --- 25 s heartbeat -------------------------------------------------------
    const heartbeatInterval = setInterval(() => {
      writeSSE({ type: "heartbeat" });
    }, 25_000);

    // --- Cleanup on client disconnect -----------------------------------------
    const cleanup = (): void => {
      clearInterval(heartbeatInterval);
      const timer = this.sseBatchTimers.get(res);
      if (timer !== undefined) {
        clearTimeout(timer);
        this.sseBatchTimers.delete(res);
      }
      this.ssePendingBatches.delete(res);
      unsubscribe();
      this.sseClients.delete(res);
      this.sseCleanupFns.delete(cleanup);
      try { res.end(); } catch { /* ignore */ }
    };

    // Register so stop() can invoke cleanup even when the client is still
    // connected (releases the heartbeat interval and bus subscription
    // before the HTTP server is torn down).
    this.sseCleanupFns.add(cleanup);

    req.once("close", cleanup);
    req.once("error", cleanup);
  }

  private async handleMcpRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {
    const body = await this.readJsonBody(req);
    const request = body as {
      jsonrpc?: string;
      id?: string | number | null;
      method?: string;
      params?: Record<string, unknown>;
    };

    // Enforce write rate limiting for MCP tool calls that mutate state,
    // matching the same protection applied to the REST write endpoints.
    // Pre-check ensures capacity; post-check skips counting dry runs and
    // idempotency replays, consistent with the REST handlers.
    const toolName = typeof request.params?.name === "string" ? request.params.name : "";
    const toolArgs = request.params?.arguments;
    const dreamsRunDryRun =
      (toolName === "engram.dreams_run" || toolName === "remnic.dreams_run") &&
      toolArgs !== null &&
      typeof toolArgs === "object" &&
      !Array.isArray(toolArgs) &&
      (toolArgs as { dryRun?: unknown }).dryRun === true;
    const memoryActionApplyDryRun =
      (toolName === "engram.memory_action_apply" || toolName === "remnic.memory_action_apply") &&
      toolArgs !== null &&
      typeof toolArgs === "object" &&
      !Array.isArray(toolArgs) &&
      (toolArgs as { dryRun?: unknown }).dryRun === true;
    const toolArgsSubcommand =
      toolArgs !== null && typeof toolArgs === "object" && !Array.isArray(toolArgs) && "subcommand" in toolArgs
        ? toolArgs.subcommand
        : undefined;
    const codingDecisionWrite =
      (toolName === "engram.coding_decision" || toolName === "remnic.coding_decision") &&
      (toolArgsSubcommand === "record" || toolArgsSubcommand === "supersede");
    const codingArchitectureWrite =
      (toolName === "engram.coding_architecture" || toolName === "remnic.coding_architecture") &&
      toolArgsSubcommand === "refresh";
    // codegraph parity tools (issue #1554): only mutating tools count as
    // writes — index, delete_project, ingest_traces, and manage_adr
    // (record|supersede). Read-only tools (search_graph, get_schema,
    // list_projects, etc.) must NOT hit the write quota.
    const CODEGRAPH_WRITE_TOOLS = new Set([
      "engram.codegraph_index", "remnic.codegraph_index",
      "engram.codegraph_delete_project", "remnic.codegraph_delete_project",
      "engram.codegraph_ingest_traces", "remnic.codegraph_ingest_traces",
    ]);
    const isCodegraphManageAdr =
      toolName === "engram.codegraph_manage_adr" || toolName === "remnic.codegraph_manage_adr";
    const codegraphWrite =
      CODEGRAPH_WRITE_TOOLS.has(toolName) ||
      (isCodegraphManageAdr && (toolArgsSubcommand === "record" || toolArgsSubcommand === "supersede"));
    const isMcpWrite =
      request.method === "tools/call" &&
      (
        toolName === "engram.memory_store" ||
        toolName === "remnic.memory_store" ||
        toolName === "engram.suggestion_submit" ||
        toolName === "remnic.suggestion_submit" ||
        toolName === "engram.observe" ||
        toolName === "remnic.observe" ||
        toolName === "engram.lcm_compaction_flush" ||
        toolName === "remnic.lcm_compaction_flush" ||
        toolName === "engram.lcm_compaction_record" ||
        toolName === "remnic.lcm_compaction_record" ||
        toolName === "engram.capsule_export" ||
        toolName === "remnic.capsule_export" ||
        toolName === "engram.capsule_import" ||
        toolName === "remnic.capsule_import" ||
        (
          !dreamsRunDryRun &&
          (toolName === "engram.dreams_run" || toolName === "remnic.dreams_run")
        ) ||
        (
          !memoryActionApplyDryRun &&
          (
            toolName === "engram.memory_action_apply" ||
            toolName === "remnic.memory_action_apply"
          )
        ) ||
        (
          toolName === "engram.memory_correct_apply" ||
          toolName === "remnic.memory_correct_apply" ||
          toolName === "engram.memory_correct_plan" ||
          toolName === "remnic.memory_correct_plan"
        ) ||
        codingDecisionWrite ||
        codingArchitectureWrite ||
        codegraphWrite
      );
    // observe self-enforces quota INSIDE its idempotency lock via the
    // enforceWriteQuota hook (issue #1649) — mirroring the direct
    // /engram/v1/observe route — so it must NOT be pre-checked here: a
    // pre-check would 429 a response-lost replay. It still counts as a write
    // for post-recording (the replay guard in shouldCountWriteRateLimit skips
    // the record). Other write tools keep the coarse pre-check.
    const observeSelfEnforcesQuota =
      toolName === "engram.observe" || toolName === "remnic.observe";
    if (isMcpWrite && !observeSelfEnforcesQuota) {
      this.ensureWriteRateLimitAvailable();
    }

    const sessionId = (() => {
      const raw = req.headers["mcp-session-id"];
      return typeof raw === "string" ? raw.trim() : undefined;
    })();
    const mcpCorrelationId = correlationIdStore.getStore() ?? randomUUID();
    const requestIdentity = this.resolveRequestIdentity(req);
    const response = await this.mcpServer.handleRequest(request, {
      principalOverride: requestIdentity.principal,
      namespaceOverride: requestIdentity.namespace,
      sessionKeyOverride: requestIdentity.sessionKey,
      sessionId,
      correlationId: mcpCorrelationId,
      enforceWriteQuota: observeSelfEnforcesQuota
        ? () => this.ensureWriteRateLimitAvailable()
        : undefined,
    });

    if (isMcpWrite && response !== null) {
      const result = (response as Record<string, unknown>).result as Record<string, unknown> | undefined;
      const isError = result?.isError === true;
      const structured = result?.structuredContent as
        | { dryRun?: boolean; idempotencyReplay?: boolean; ok?: boolean }
        | undefined;
      // Rejected codegraph calls carry { ok: false } in structuredContent
      // (confirm_required, package_missing, runtime_unavailable, ...). The
      // MCP layer sets isError:false for these, so without this guard they
      // would consume the write quota despite no mutation occurring
      // (issue #1554 review thread: don't bill rejected calls as writes).
      const isRejectedCodegraph = structured?.ok === false;
      // A write tool that succeeded without structuredContent (e.g.
      // memory_correct_apply, which returns a CorrectionOutcome) still
      // consumed a write — count it. Tools WITH structuredContent use the
      // dryRun/idempotencyReplay guards.
      const counts = structured ? this.shouldCountWriteRateLimit(structured) : true;
      if (!isError && !isRejectedCodegraph && counts) {
        this.recordWriteRateLimitHit();
      }
    }
    if (response === null) {
      res.statusCode = 202;
      res.end();
      return;
    }
    // If this was an initialize response, pop the session ID keyed by
    // correlation ID (unique per HTTP request, not client-chosen JSON-RPC id).
    const assignedSessionId = this.mcpServer.popInitSessionId(mcpCorrelationId);
    if (assignedSessionId) {
      res.setHeader("mcp-session-id", assignedSessionId);
    }
    this.respondJson(res, 200, response);
  }

  private respondJson(res: ServerResponse, status: number, payload: unknown): void {
    const body = JSON.stringify(payload, null, 2);
    res.statusCode = status;
    res.setHeader("content-type", "application/json; charset=utf-8");
    res.setHeader("content-length", String(Buffer.byteLength(body)));
    const cid = correlationIdStore.getStore();
    if (cid) {
      res.setHeader("x-request-id", cid);
    }
    res.end(body);
  }

  private async respondOfflineSnapshotStream(
    res: ServerResponse,
    snapshot: Awaited<ReturnType<EngramAccessService["offlineSyncSnapshotStream"]>>,
  ): Promise<void> {
    res.statusCode = 200;
    res.setHeader("content-type", "application/x-ndjson; charset=utf-8");
    res.setHeader("cache-control", "no-store");
    const cid = correlationIdStore.getStore();
    if (cid) {
      res.setHeader("x-request-id", cid);
    }
    const waitForDrainOrClose = async (): Promise<boolean> => new Promise((resolve, reject) => {
      const cleanup = () => {
        res.off("drain", onDrain);
        res.off("close", onClose);
        res.off("error", onError);
      };
      const onDrain = () => {
        cleanup();
        resolve(true);
      };
      const onClose = () => {
        cleanup();
        resolve(false);
      };
      const onError = (error: Error) => {
        cleanup();
        reject(error);
      };
      res.once("drain", onDrain);
      res.once("close", onClose);
      res.once("error", onError);
    });
    const writeLine = async (payload: unknown): Promise<boolean> => {
      if (res.destroyed || res.writableEnded) return false;
      if (res.write(`${JSON.stringify(payload)}\n`)) return true;
      if (res.destroyed || res.writableEnded) return false;
      return waitForDrainOrClose();
    };
    if (!await writeLine({
      type: "snapshot",
      namespace: snapshot.namespace,
      format: snapshot.format,
      schemaVersion: snapshot.schemaVersion,
      createdAt: snapshot.createdAt,
      sourceId: snapshot.sourceId,
      includeTranscripts: snapshot.includeTranscripts,
    })) return;
    for await (const file of snapshot.files) {
      if (!await writeLine({ type: "file", file })) return;
    }
    if (!res.destroyed && !res.writableEnded) {
      res.end();
    }
  }

  private respondBinary(
    res: ServerResponse,
    status: number,
    body: Buffer,
    headers: Record<string, string> = {},
  ): void {
    res.statusCode = status;
    res.setHeader("content-type", "application/octet-stream");
    res.setHeader("content-length", String(body.length));
    for (const [key, value] of Object.entries(headers)) {
      res.setHeader(key, value);
    }
    const cid = correlationIdStore.getStore();
    if (cid) {
      res.setHeader("x-request-id", cid);
    }
    res.end(body);
  }

  private async handleAdminConsole(
    req: IncomingMessage,
    res: ServerResponse,
    pathname: string,
  ): Promise<boolean> {
    if (req.method !== "GET") return false;
    if (pathname === "/remnic/ui" || pathname === "/engram/ui") {
      res.statusCode = 301;
      res.setHeader("location", pathname + "/");
      res.end();
      return true;
    }
    if (pathname === "/remnic/ui/" || pathname === "/engram/ui/") {
      await this.respondAdminConsoleShell(req, res, pathname);
      return true;
    }
    if (pathname === "/remnic/ui/app.js" || pathname === "/engram/ui/app.js") {
      await this.respondStatic(res, path.join(this.adminConsolePublicDir, "app.js"), "application/javascript; charset=utf-8");
      return true;
    }
    return false;
  }

  private async respondAdminConsoleShell(
    req: IncomingMessage,
    res: ServerResponse,
    pathname: string,
  ): Promise<void> {
    try {
      let body = await readFile(path.join(this.adminConsolePublicDir, "index.html"), "utf-8");
      const canPrefillToken = this.adminConsolePrefillToken && this.isAuthorized(req, pathname);
      if (canPrefillToken) {
        const script = `<script>window.__REMNIC_ADMIN_CONSOLE_PREFILL_TOKEN__=${JSON.stringify(this.adminConsolePrefillToken)};</script>`;
        body = body.includes("</head>")
          ? body.replace("</head>", `${script}</head>`)
          : `${script}${body}`;
        res.setHeader("cache-control", "private, no-store");
      }
      res.setHeader("vary", "authorization");
      res.statusCode = 200;
      res.setHeader("content-type", "text/html; charset=utf-8");
      res.setHeader("content-length", String(Buffer.byteLength(body)));
      res.end(body);
    } catch {
      this.respondJson(res, 404, { error: "not_found" });
    }
  }

  private async respondStatic(res: ServerResponse, filePath: string, contentType: string): Promise<void> {
    try {
      const body = await readFile(filePath, "utf-8");
      res.statusCode = 200;
      res.setHeader("content-type", contentType);
      res.setHeader("content-length", String(Buffer.byteLength(body)));
      res.end(body);
    } catch {
      this.respondJson(res, 404, { error: "not_found" });
    }
  }

  private async readJsonBody(
    req: IncomingMessage,
    maxBodyBytes = this.maxBodyBytes,
  ): Promise<Record<string, unknown>> {
    const encoding = (this.readOptionalHeader(req, "content-encoding") ?? "identity").toLowerCase();
    if (encoding !== "identity" && encoding !== "gzip") {
      throw new HttpError(415, "unsupported_content_encoding", "unsupported_content_encoding");
    }
    const chunks: Buffer[] = [];
    let total = 0;
    for await (const chunk of req) {
      const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
      total += buffer.length;
      if (total > maxBodyBytes) {
        throw new HttpError(413, "request_body_too_large", "request_body_too_large");
      }
      chunks.push(buffer);
    }
    if (chunks.length === 0) return {};
    let body = Buffer.concat(chunks, total);
    if (encoding === "gzip") {
      try {
        body = gunzipSync(body, { maxOutputLength: maxBodyBytes });
      } catch (error) {
        if ((error as NodeJS.ErrnoException).code === "ERR_BUFFER_TOO_LARGE") {
          throw new HttpError(413, "request_body_too_large", "request_body_too_large");
        }
        throw new HttpError(400, "invalid_gzip_body", "invalid_gzip_body");
      }
      if (body.byteLength > maxBodyBytes) {
        throw new HttpError(413, "request_body_too_large", "request_body_too_large");
      }
    }
    const raw = body.toString("utf-8").trim();
    if (raw.length === 0) return {};
    let parsed: unknown;
    try {
      parsed = JSON.parse(raw);
    } catch {
      throw new HttpError(400, "invalid_json", "invalid_json");
    }
    if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
      throw new HttpError(400, "invalid_json_object", "invalid_json_object");
    }
    return parsed as Record<string, unknown>;
  }

  private async readBinaryBody(req: IncomingMessage, maxBytes: number): Promise<Buffer> {
    const chunks: Buffer[] = [];
    let total = 0;
    for await (const chunk of req) {
      const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
      total += buffer.length;
      if (total > maxBytes) {
        throw new HttpError(413, "request_body_too_large", "request_body_too_large");
      }
      chunks.push(buffer);
    }
    return Buffer.concat(chunks, total);
  }

  private readRequiredHeader(req: IncomingMessage, name: string): string {
    const value = this.readOptionalHeader(req, name);
    if (value === undefined || value.length === 0) {
      throw new EngramAccessInputError(`${name} header is required`);
    }
    return value;
  }

  private readOptionalHeader(req: IncomingMessage, name: string): string | undefined {
    const raw = req.headers[name.toLowerCase()];
    if (Array.isArray(raw)) return raw[0]?.trim() || undefined;
    return raw?.trim() || undefined;
  }

  private readRequiredDecodedHeader(req: IncomingMessage, name: string): string {
    const raw = this.readRequiredHeader(req, name);
    try {
      return decodeURIComponent(raw);
    } catch {
      throw new EngramAccessInputError(`${name} header is not valid percent-encoded input`);
    }
  }

  private readRequiredIntegerHeader(req: IncomingMessage, name: string): number {
    const raw = this.readRequiredHeader(req, name);
    const parsed = Number(raw);
    if (!Number.isInteger(parsed) || parsed < 0) {
      throw new EngramAccessInputError(`${name} header must be a non-negative integer`);
    }
    return parsed;
  }

  private readOptionalIntegerHeader(req: IncomingMessage, name: string): number | undefined {
    const raw = this.readOptionalHeader(req, name);
    if (raw === undefined) return undefined;
    const parsed = Number(raw);
    if (!Number.isInteger(parsed) || parsed < 0) {
      throw new EngramAccessInputError(`${name} header must be a non-negative integer`);
    }
    return parsed;
  }

  private readRequiredNumberHeader(req: IncomingMessage, name: string): number {
    const raw = this.readRequiredHeader(req, name);
    const parsed = Number(raw);
    if (!Number.isFinite(parsed) || parsed < 0) {
      throw new EngramAccessInputError(`${name} header must be a non-negative finite number`);
    }
    return parsed;
  }

  private parseOptionalBooleanHeader(
    req: IncomingMessage,
    name: string,
    defaultValue: boolean,
  ): boolean {
    const raw = this.readOptionalHeader(req, name);
    if (raw === undefined) return defaultValue;
    if (raw === "true") return true;
    if (raw === "false") return false;
    throw new EngramAccessInputError(`${name} header must be one of: true, false`);
  }

  private async readValidatedBody<S extends SchemaName>(
    req: IncomingMessage,
    schemaName: S,
    maxBodyBytes?: number,
  ): Promise<SchemaTypeFor<S>> {
    const raw = await this.readJsonBody(req, maxBodyBytes);
    const result = validateRequest(schemaName, raw);
    if (!result.success) {
      throw new HttpError(400, result.error.error, "validation_error", result.error.details);
    }
    return result.data as SchemaTypeFor<S>;
  }

  private isAuthorized(req: IncomingMessage, pathname?: string): boolean {
    if (!this.authToken && this.authTokens.length === 0 && !this.authTokensGetter) return false;
    // Primary path: Authorization: Bearer <token> header.
    const raw = req.headers.authorization;
    let candidate: string | null = null;
    if (raw) {
      const separator = raw.indexOf(" ");
      if (separator > 0) {
        const scheme = raw.slice(0, separator).toLowerCase();
        if (scheme === "bearer") {
          candidate = raw.slice(separator + 1).trim();
        }
      }
    }
    // Fallback: ?token= query parameter — ONLY accepted for the SSE
    // endpoint (/engram/v1/graph/events).  EventSource cannot set request
    // headers, so SSE clients must pass the token via the query string.
    // Allowing this fallback on every endpoint would let a CSRF attacker
    // embed a credentialed URL anywhere — restricting it to SSE limits the
    // attack surface (Codex P2 review thread `access-http.ts:1406`; Cursor
    // review thread `access-http.ts:1412`).  Authorization header always
    // wins; timing-safe compare used below.
    if (!candidate && pathname === "/engram/v1/graph/events") {
      try {
        const parsed = new URL(req.url ?? "/", `http://${hostToUrlAuthority(this.host)}`);
        const queryToken = parsed.searchParams.get("token");
        if (queryToken && queryToken.length > 0) {
          candidate = queryToken;
        }
      } catch {
        // Malformed URL — don't authenticate
      }
    }
    if (!candidate) return false;
    const token = candidate;
    // Check primary token
    if (this.authToken && this.timingSafeStringEqual(token, this.authToken)) return true;
    // Check static multi-connector tokens
    for (const valid of this.authTokens) {
      if (this.timingSafeStringEqual(token, valid)) return true;
    }
    // Check dynamic tokens (reloaded per request for generate/revoke without restart)
    if (this.authTokensGetter) {
      for (const valid of this.authTokensGetter()) {
        if (this.timingSafeStringEqual(token, valid)) return true;
      }
    }
    return false;
  }

  private timingSafeStringEqual(a: string, b: string): boolean {
    const left = this.encodeSecret(a);
    const right = this.encodeSecret(b);
    if (!left || !right) return false;
    return timingSafeEqual(left, right);
  }

  private encodeSecret(value: string): Buffer | null {
    const encoded = Buffer.from(value, "utf-8");
    if (encoded.length > 1024) return null;
    const out = Buffer.alloc(2 + 1024);
    out.writeUInt16BE(encoded.length, 0);
    encoded.copy(out, 2);
    return out;
  }

  private writeResponseStatus(response: { dryRun: boolean; status: string }): number {
    if (response.dryRun === true) return 200;
    if (response.status === "stored" || response.status === "queued_for_review") return 201;
    return 200;
  }

  private ensureWriteRateLimitAvailable(): void {
    const now = Date.now();
    while (
      this.writeRequestTimestamps.length > 0 &&
      now - (this.writeRequestTimestamps[0] ?? 0) > WRITE_RATE_LIMIT_WINDOW_MS
    ) {
      this.writeRequestTimestamps.shift();
    }
    if (this.writeRequestTimestamps.length >= WRITE_RATE_LIMIT_MAX_REQUESTS) {
      throw new HttpError(429, "write_rate_limited", "write_rate_limited");
    }
  }

  private recordWriteRateLimitHit(): void {
    this.writeRequestTimestamps.push(Date.now());
  }

  private shouldCountWriteRateLimit(response: { dryRun?: boolean; idempotencyReplay?: boolean }): boolean {
    return response.dryRun !== true && response.idempotencyReplay !== true;
  }

  /**
   * Map wearables validation errors (WearablesInputError — invalid
   * params, unknown/disabled sources, missing connector packages) to a
   * 400 response. Returns false for everything else so backend faults
   * keep flowing to the global 500 handler.
   */
  private respondWearablesError(res: ServerResponse, err: unknown): boolean {
    if (err instanceof WearablesInputError) {
      this.respondJson(res, 400, {
        error: "invalid_request",
        code: "invalid_request",
        message: err.message,
      });
      return true;
    }
    return false;
  }
}

/** Optional string field from a JSON body: absent/null/"" → undefined. */
function optionalQueryString(value: unknown, label: string): string | undefined {
  if (value === undefined || value === null || value === "") return undefined;
  if (typeof value !== "string") {
    throw new EngramAccessInputError(
      `${label} must be a string (got ${JSON.stringify(value)})`,
    );
  }
  return value;
}

/** Optional non-empty query param: null/"" → undefined. */
function nonEmptyQueryParam(value: string | null): string | undefined {
  return value !== null && value.length > 0 ? value : undefined;
}

/** Optional positive-integer query param; rejects invalid values. */
function positiveIntQueryParam(value: string | null, label: string): number | undefined {
  if (value === null || value.length === 0) return undefined;
  const parsed = Number(value);
  if (!Number.isFinite(parsed) || parsed <= 0 || !Number.isInteger(parsed)) {
    throw new EngramAccessInputError(`${label} expects a positive integer`);
  }
  return parsed;
}
