/*
 * In-memory Cloud runtime store implementation.
 * Public construction remains in runtime-store.ts.
 */
import { createHash, randomUUID } from "node:crypto";

import {
  ENVIRONMENT_DEV,
  ERROR_CODES,
  HwlabProtocolError,
  assertProtocolRecord
} from "../protocol/index.mjs";
import {
  CLOUD_API_SERVICE_ID,
  createProtocolAuditEvent,
  deriveProtocolActorFromMeta
} from "../audit/index.mjs";
import {
  CLOUD_RUNTIME_DURABLE_ADAPTER_SCHEMA_VERSION,
  CLOUD_RUNTIME_DURABLE_MIGRATION_TABLE,
  CLOUD_RUNTIME_DURABLE_TABLE_COLUMNS
} from "./schema.ts";


import * as runtimeCore from "./runtime-store-core.ts";

const {
  RUNTIME_STORE_KIND,
  RUNTIME_STORE_KIND_POSTGRES,
  RUNTIME_DURABLE_ADAPTER_MISSING,
  RUNTIME_DURABLE_ADAPTER_UNCONFIGURED,
  RUNTIME_DURABLE_ADAPTER_DRIVER_MISSING,
  RUNTIME_DURABLE_ADAPTER_SSL_BLOCKED,
  RUNTIME_DURABLE_ADAPTER_AUTH_BLOCKED,
  RUNTIME_DURABLE_ADAPTER_SCHEMA_BLOCKED,
  RUNTIME_DURABLE_ADAPTER_MIGRATION_BLOCKED,
  RUNTIME_DURABLE_ADAPTER_QUERY_BLOCKED,
  RUNTIME_DURABILITY_REQUIRED_EVIDENCE,
  RUNTIME_ADAPTER_ENV,
  RUNTIME_DURABLE_ENV,
  RUNTIME_DB_POOL_MAX_ENV,
  requiredPostgresSchema,
  postgresCountTables,
  postgresRuntimeReadIndexes,
  buildPostgresPoolConfig,
  RUNTIME_DB_QUERY_TIMEOUT_MS_ENV,
  RUNTIME_DB_QUERY_RETRY_MAX_ATTEMPTS_ENV,
  RUNTIME_DB_QUERY_RETRY_INITIAL_DELAY_MS_ENV,
  RUNTIME_DB_QUERY_RETRY_MAX_DELAY_MS_ENV,
  RUNTIME_DB_POOL_RESET_COOLDOWN_MS_ENV,
  DEFAULT_RUNTIME_DB_QUERY_RETRY_MAX_ATTEMPTS,
  DEFAULT_RUNTIME_DB_QUERY_RETRY_INITIAL_DELAY_MS,
  DEFAULT_RUNTIME_DB_QUERY_RETRY_MAX_DELAY_MS,
  DEFAULT_RUNTIME_DB_POOL_RESET_COOLDOWN_MS,
  normalizePositiveInteger,
  delayRuntimeDbQueryRetry,
  isReadyForDurableRuntimeRead,
  normalizeCapabilityInputs,
  normalizeShellInput,
  normalizeAgentTraceEvent,
  normalizeWorkbenchProjectionState,
  normalizeWorkbenchProjectionAllocation,
  normalizeWorkbenchFacts,
  normalizeWorkbenchAggregateEventsForFacts,
  appendWorkbenchFactEvent,
  workbenchAggregateForFact,
  normalizeWorkbenchAggregateEvent,
  workbenchAggregateEventType,
  workbenchInputDelivery,
  workbenchInputStatus,
  indexWorkbenchAggregateEvents,
  normalizeWorkbenchSessionInputFact,
  memoryWorkbenchSessionInputWithSeq,
  inputFactWithAggregateSeq,
  workbenchSessionInputWithSeq,
  workbenchSessionInputPromotedSeq,
  normalizeWorkbenchSessionFact,
  normalizeWorkbenchMessageFact,
  normalizeWorkbenchPartFact,
  normalizeWorkbenchTurnFact,
  assertWorkbenchTurnFinalResponseInvariant,
  normalizeWorkbenchFactStatus,
  workbenchFactFinalResponseText,
  normalizeWorkbenchTraceEventFact,
  normalizeWorkbenchProjectionCheckpoint,
  arrayInput,
  stableWorkbenchFactId,
  requiredWorkbenchFactError,
  compareTraceEventRecords,
  compareProjectionStateRecords,
  compareWorkbenchFactRecords,
  compareWorkbenchInputFactRecords,
  compareWorkbenchPartFactRecords,
  compareWorkbenchTraceEventFacts,
  sortWorkbenchFactRows,
  WORKBENCH_FACT_FAMILIES,
  workbenchFactFamilySet,
  workbenchFactFamilyForTable,
  workbenchSessionSummarySelectClause,
  workbenchSessionSummaryFactFromRow,
  workbenchFactOrder,
  traceEventLevel,
  positiveIntegerOrNull,
  nonNegativeInteger,
  nonNegativeIntegerOrNull,
  projectionStatusOr,
  projectionHealthOr,
  resultSyncStateOr,
  timestampOr,
  textOr,
  textList,
  asObject,
  requireString,
  requireProtocolId,
  normalizeJsonObject,
  pruneUndefined,
  makeId,
  stableJson,
  sha256Hex,
  sortJson,
  matchesQuery,
  matchesWorkbenchSessionFactQuery,
  limitResults,
  normalizeRuntimeAdapter,
  normalizePostgresSslMode,
  postgresSslModeFromConnectionString,
  postgresSslOption,
  normalizePostgresConnectionStringForSslMode,
  postgresAdapterContract,
  addRuntimeDurabilityContract,
  durabilityBlockedLayer,
  runtimeSafety,
  classifyRuntimeDbError,
  isRuntimeDbConnectTimeout,
  durableRuntimeReadBlockedError,
  durableRuntimeReadBlockedData,
  isRuntimeDbSslError,
  summarizeRuntimeSchema,
  notCheckedRuntimeMigration,
  blockedRuntimeMigration,
  runtimeGates,
  defaultRuntimeGates,
  defaultSchemaGate,
  defaultMigrationGate,
  readyGate,
  blockedGate,
  notCheckedGate,
  gateFromReadiness,
  snapshotMemory,
  newRecords,
  changedRecords,
  withPersistence,
  parseJsonColumn,
  postgresPoolStats,
  toCountKey,
  normalizeRuntimeTimeoutMs,
  normalizeRuntimePoolMax
} = runtimeCore;

export class CloudRuntimeStore {
  constructor({ now = () => new Date().toISOString() } = {}) {
    this.now = now;
    this.gatewaySessions = new Map();
    this.boxResources = new Map();
    this.boxCapabilities = new Map();
    this.hardwareOperations = new Map();
    this.auditEvents = new Map();
    this.evidenceRecords = new Map();
    this.agentTraceEvents = new Map();
    this.workbenchProjectionStates = new Map();
    this.workbenchSessions = new Map();
    this.workbenchMessages = new Map();
    this.workbenchParts = new Map();
    this.workbenchTurns = new Map();
    this.workbenchTraceEvents = new Map();
    this.workbenchEventSequences = new Map();
    this.workbenchEvents = new Map();
    this.workbenchSessionInputs = new Map();
    this.workbenchProjectionCheckpoints = new Map();
  }

  summary() {
    return addRuntimeDurabilityContract({
      adapter: RUNTIME_STORE_KIND,
      durable: false,
      status: "degraded",
      blocker: RUNTIME_DURABLE_ADAPTER_MISSING,
      reason: "L1 runtime writes are process-local only; no DB-backed durable runtime adapter is configured",
      adapterContract: {
        required: "DB-backed durable runtime adapter using HWLAB_CLOUD_DB_URL",
        sourceIssue: "pikasTech/HWLAB#164",
        secretMaterialRequiredInSource: false
      },
      counts: {
        gatewaySessions: this.gatewaySessions.size,
        boxResources: this.boxResources.size,
        boxCapabilities: this.boxCapabilities.size,
        hardwareOperations: this.hardwareOperations.size,
        auditEvents: this.auditEvents.size,
        evidenceRecords: this.evidenceRecords.size,
        agentTraceEvents: this.agentTraceEvents.size,
        workbenchProjectionStates: this.workbenchProjectionStates.size,
        workbenchSessions: this.workbenchSessions.size,
        workbenchMessages: this.workbenchMessages.size,
        workbenchParts: this.workbenchParts.size,
        workbenchTurns: this.workbenchTurns.size,
        workbenchTraceEvents: this.workbenchTraceEvents.size,
        workbenchEventSequences: this.workbenchEventSequences.size,
        workbenchEvents: this.workbenchEvents.size,
        workbenchSessionInputs: this.workbenchSessionInputs.size,
        workbenchProjectionCheckpoints: this.workbenchProjectionCheckpoints.size
      }
    });
  }

  async workbenchTransactionalRealtimeReadiness() {
    return {
      ready: true,
      adapter: RUNTIME_STORE_KIND,
      durable: false,
      testRuntime: true,
      valuesRedacted: true
    };
  }

  async assertWorkbenchTransactionalRealtimeReady() {
    return this.workbenchTransactionalRealtimeReadiness();
  }

  registerGatewaySession(params = {}, requestMeta = {}) {
    const now = this.now();
    const input = asObject(params.gatewaySession ?? params, "gatewaySession");
    const gatewaySession = pruneUndefined({
      gatewaySessionId: input.gatewaySessionId || makeId("gws"),
      projectId: requireProtocolId(input, "projectId"),
      serviceId: input.serviceId || "hwlab-gateway",
      gatewayId: input.gatewayId || makeId("gtw"),
      endpoint: input.endpoint,
      status: input.status || "connected",
      environment: ENVIRONMENT_DEV,
      startedAt: input.startedAt || now,
      lastSeenAt: input.lastSeenAt || now,
      stoppedAt: input.stoppedAt,
      labels: normalizeJsonObject(input.labels)
    });

    assertProtocolRecord("gatewaySession", gatewaySession);
    this.gatewaySessions.set(gatewaySession.gatewaySessionId, gatewaySession);

    const auditEvent = this.recordAuditEvent({
      requestMeta,
      params,
      action: "gateway.session.register",
      targetType: "gateway_session",
      targetId: gatewaySession.gatewaySessionId,
      projectId: gatewaySession.projectId,
      gatewaySessionId: gatewaySession.gatewaySessionId,
      outcome: "accepted",
      metadata: {
        gatewayId: gatewaySession.gatewayId,
        serviceId: gatewaySession.serviceId
      }
    });

    return {
      registered: true,
      gatewaySession,
      auditEvent,
      persistence: this.summary()
    };
  }

  registerBoxResource(params = {}, requestMeta = {}) {
    const now = this.now();
    const input = asObject(params.resource ?? params.boxResource ?? params, "boxResource");
    const gatewaySessionId = requireProtocolId(input, "gatewaySessionId");
    const gatewaySession = this.requireGatewaySession(gatewaySessionId);
    const projectId = input.projectId || gatewaySession.projectId;
    const resource = pruneUndefined({
      resourceId: input.resourceId || makeId("res"),
      projectId,
      gatewaySessionId,
      boxId: input.boxId || makeId("box"),
      resourceType: input.resourceType || "simulator_endpoint",
      name: input.name,
      state: input.state || "available",
      environment: ENVIRONMENT_DEV,
      metadata: normalizeJsonObject(input.metadata),
      createdAt: input.createdAt || now,
      updatedAt: input.updatedAt || now
    });

    assertProtocolRecord("boxResource", resource);
    this.boxResources.set(resource.resourceId, resource);

    const auditEvent = this.recordAuditEvent({
      requestMeta,
      params,
      action: "box.resource.register",
      targetType: "box_resource",
      targetId: resource.resourceId,
      projectId: resource.projectId,
      gatewaySessionId: resource.gatewaySessionId,
      outcome: "accepted",
      metadata: {
        boxId: resource.boxId,
        resourceType: resource.resourceType
      }
    });

    return {
      registered: true,
      resource,
      auditEvent,
      persistence: this.summary()
    };
  }

  reportBoxCapabilities(params = {}, requestMeta = {}) {
    const now = this.now();
    const inputs = normalizeCapabilityInputs(params);
    const capabilities = [];

    for (const input of inputs) {
      const resourceId = requireProtocolId(input, "resourceId");
      const resource = this.requireBoxResource(resourceId);
      const capability = pruneUndefined({
        capabilityId: input.capabilityId || makeId("cap"),
        resourceId,
        projectId: input.projectId || resource.projectId,
        name: requireString(input, "name"),
        description: input.description,
        direction: input.direction || "bidirectional",
        valueType: input.valueType || "object",
        unit: input.unit,
        constraints: normalizeJsonObject(input.constraints),
        mutatesState: input.mutatesState ?? true,
        createdAt: input.createdAt || now,
        updatedAt: input.updatedAt || now
      });

      assertProtocolRecord("boxCapability", capability);
      this.boxCapabilities.set(capability.capabilityId, capability);
      capabilities.push(capability);
    }

    const first = capabilities[0];
    const auditEvent = this.recordAuditEvent({
      requestMeta,
      params,
      action: "box.capability.report",
      targetType: "box_capability",
      targetId: first.capabilityId,
      projectId: first.projectId,
      gatewaySessionId: this.boxResources.get(first.resourceId)?.gatewaySessionId,
      outcome: "accepted",
      metadata: {
        capabilityIds: capabilities.map((capability) => capability.capabilityId)
      }
    });

    return {
      reported: true,
      capabilities,
      auditEvent,
      persistence: this.summary()
    };
  }

  requestHardwareOperation(params = {}, requestMeta = {}) {
    const refs = this.requireOperationRefs(params, requestMeta);
    const now = this.now();
    const operation = pruneUndefined({
      operationId: params.operationId || makeId("op"),
      projectId: refs.projectId,
      gatewaySessionId: refs.gatewaySession.gatewaySessionId,
      agentSessionId: params.agentSessionId,
      workerSessionId: params.workerSessionId,
      resourceId: refs.resource.resourceId,
      capabilityId: refs.capability.capabilityId,
      requestedBy: refs.actor.actorId,
      input: normalizeJsonObject(params.input),
      status: "accepted",
      environment: ENVIRONMENT_DEV,
      requestedAt: params.requestedAt || now,
      updatedAt: now
    });

    assertProtocolRecord("hardwareOperation", operation);
    this.hardwareOperations.set(operation.operationId, operation);

    const auditEvent = this.recordAuditEvent({
      requestMeta,
      params,
      action: "hardware.operation.request",
      targetType: "hardware_operation",
      targetId: operation.operationId,
      projectId: operation.projectId,
      gatewaySessionId: operation.gatewaySessionId,
      workerSessionId: operation.workerSessionId,
      operationId: operation.operationId,
      outcome: "accepted",
      metadata: {
        resourceId: operation.resourceId,
        capabilityId: operation.capabilityId
      }
    });

    return {
      accepted: true,
      status: operation.status,
      operationId: operation.operationId,
      operation,
      auditEvent,
      persistence: this.summary()
    };
  }

  invokeHardwareShell(params = {}, requestMeta = {}) {
    const shellInput = normalizeShellInput(params);
    const operationResult = this.requestHardwareOperation(
      {
        ...params,
        input: {
          ...normalizeJsonObject(params.input),
          shell: shellInput
        }
      },
      requestMeta
    );
    const operation = {
      ...operationResult.operation,
      output: {
        shellExecuted: false,
        dispatchStatus: "not_connected",
        message: "cloud-api recorded the shell invoke request; no gateway shell adapter is connected in L1"
      },
      updatedAt: this.now()
    };
    assertProtocolRecord("hardwareOperation", operation);
    this.hardwareOperations.set(operation.operationId, operation);

    const evidenceRecord = this.writeEvidenceRecord(
      {
        projectId: operation.projectId,
        operationId: operation.operationId,
        kind: "trace",
        mimeType: "application/json",
        serviceId: CLOUD_API_SERVICE_ID,
        metadata: {
          traceId: requestMeta.traceId,
          gatewaySessionId: operation.gatewaySessionId,
          resourceId: operation.resourceId,
          capabilityId: operation.capabilityId,
          shellExecuted: false,
          dispatchStatus: "not_connected"
        },
        content: {
          operationId: operation.operationId,
          shell: shellInput,
          dispatchStatus: "not_connected"
        }
      },
      requestMeta
    ).evidenceRecord;

    const auditEvent = this.recordAuditEvent({
      requestMeta,
      params,
      action: "hardware.invoke.shell",
      targetType: "hardware_operation",
      targetId: operation.operationId,
      projectId: operation.projectId,
      gatewaySessionId: operation.gatewaySessionId,
      workerSessionId: operation.workerSessionId,
      operationId: operation.operationId,
      outcome: "accepted",
      metadata: {
        evidenceId: evidenceRecord.evidenceId,
        shellExecuted: false,
        dispatchStatus: "not_connected"
      }
    });

    return {
      accepted: true,
      status: operation.status,
      operationId: operation.operationId,
      operation,
      auditEvent,
      evidenceRecord,
      dispatch: operation.output,
      persistence: this.summary()
    };
  }

  writeAuditEvent(params = {}, requestMeta = {}) {
    const eventInput = params.event ?? params.auditEvent ?? params;
    const auditEvent = eventInput.auditId
      ? pruneUndefined({
          ...eventInput,
          environment: ENVIRONMENT_DEV
        })
      : this.buildAuditEventFromInput(eventInput, requestMeta);

    assertProtocolRecord("auditEvent", auditEvent);
    this.auditEvents.set(auditEvent.auditId, auditEvent);

    return {
      written: true,
      auditEvent,
      persistence: this.summary()
    };
  }

  queryAuditEvents(params = {}) {
    const events = [...this.auditEvents.values()].filter((event) => matchesQuery(event, params, [
      "auditId",
      "traceId",
      "projectId",
      "gatewaySessionId",
      "operationId",
      "action",
      "targetId"
    ]));

    return {
      events: limitResults(events, params.limit),
      count: events.length,
      persistence: this.summary()
    };
  }

  writeEvidenceRecord(params = {}, requestMeta = {}) {
    const now = this.now();
    const input = asObject(params.record ?? params.evidenceRecord ?? params, "evidenceRecord");
    const content = input.content ?? input.metadata ?? {};
    const sha256 = input.sha256 || sha256Hex(content);
    const evidenceId = input.evidenceId || makeId("evd");
    const evidenceRecord = pruneUndefined({
      evidenceId,
      projectId: requireProtocolId(input, "projectId"),
      operationId: requireProtocolId(input, "operationId"),
      agentSessionId: input.agentSessionId,
      workerSessionId: input.workerSessionId,
      kind: input.kind || "trace",
      uri: input.uri || `memory://hwlab/evidence/${evidenceId}`,
      mimeType: input.mimeType,
      sha256,
      sizeBytes: input.sizeBytes ?? Buffer.byteLength(stableJson(content), "utf8"),
      serviceId: input.serviceId || CLOUD_API_SERVICE_ID,
      environment: ENVIRONMENT_DEV,
      metadata: {
        ...normalizeJsonObject(input.metadata),
        ...(requestMeta.traceId ? { traceId: requestMeta.traceId } : {})
      },
      createdAt: input.createdAt || now
    });

    assertProtocolRecord("evidenceRecord", evidenceRecord);
    this.evidenceRecords.set(evidenceRecord.evidenceId, evidenceRecord);

    return {
      written: true,
      evidenceRecord,
      persistence: this.summary()
    };
  }

  queryEvidenceRecords(params = {}) {
    const records = [...this.evidenceRecords.values()].filter((record) => matchesQuery(record, params, [
      "evidenceId",
      "projectId",
      "operationId",
      "agentSessionId",
      "workerSessionId",
      "kind",
      "serviceId"
    ]));

    return {
      records: limitResults(records, params.limit),
      count: records.length,
      persistence: this.summary()
    };
  }

  writeAgentTraceEvent(params = {}, requestMeta = {}) {
    const traceEvent = normalizeAgentTraceEvent(params.traceEvent ?? params.event ?? params, requestMeta, this.now());
    this.agentTraceEvents.set(traceEvent.id, traceEvent);
    return {
      written: true,
      traceEvent,
      persistence: this.summary()
    };
  }

  queryAgentTraceEvents(params = {}) {
    const records = [...this.agentTraceEvents.values()].filter((record) => matchesQuery(record, params, [
      "id",
      "traceId",
      "agentSessionId",
      "workerSessionId",
      "level"
    ]));
    records.sort(compareTraceEventRecords);
    return {
      events: limitResults(records.map((record) => record.event), params.limit),
      count: records.length,
      persistence: this.summary()
    };
  }

  writeWorkbenchProjectionState(params = {}, requestMeta = {}) {
    const state = normalizeWorkbenchProjectionState(params.projectionState ?? params.state ?? params, requestMeta, this.now());
    this.workbenchProjectionStates.set(state.traceId, state);
    return {
      written: true,
      projectionState: state,
      persistence: this.summary()
    };
  }

  getWorkbenchProjectionState(params = {}) {
    const traceId = textOr(params.traceId, "");
    const state = traceId ? this.workbenchProjectionStates.get(traceId) ?? null : null;
    return {
      projectionState: state,
      found: Boolean(state),
      persistence: this.summary()
    };
  }

  allocateWorkbenchProjectedSeq(params = {}, requestMeta = {}) {
    const allocation = normalizeWorkbenchProjectionAllocation(params, requestMeta, this.now());
    const existing = [...this.workbenchTraceEvents.values()].find((record) =>
      record.traceId === allocation.traceId && record.sourceEventId === allocation.sourceEventId
    );
    if (existing) {
      return {
        ...allocation,
        projectedSeq: nonNegativeInteger(existing.projectedSeq),
        reused: true,
        persistence: this.summary(),
        valuesPrinted: false
      };
    }
    const eventMax = [...this.workbenchTraceEvents.values()]
      .filter((record) => record.traceId === allocation.traceId)
      .reduce((max, record) => Math.max(max, nonNegativeInteger(record.projectedSeq)), 0);
    const checkpointMax = nonNegativeInteger(this.workbenchProjectionCheckpoints.get(allocation.traceId)?.projectedSeq);
    return {
      ...allocation,
      projectedSeq: Math.max(eventMax, checkpointMax) + 1,
      reused: false,
      persistence: this.summary(),
      valuesPrinted: false
    };
  }

  queryWorkbenchProjectionStates(params = {}) {
    const dueAt = textOr(params.dueAt, "");
    const statuses = Array.isArray(params.projectionStatuses) ? new Set(params.projectionStatuses.map((item) => textOr(item, "")).filter(Boolean)) : null;
    const records = [...this.workbenchProjectionStates.values()]
      .filter((record) => matchesQuery(record, params, ["traceId", "sessionId", "runId", "commandId", "sourceRunId", "sourceCommandId"]))
      .filter((record) => !statuses || statuses.has(record.projectionStatus))
      .filter((record) => !dueAt || !record.nextRetryAt || String(record.nextRetryAt) <= dueAt)
      .sort(compareProjectionStateRecords);
    return {
      states: limitResults(records, params.limit),
      count: records.length,
      persistence: this.summary()
    };
  }

  writeWorkbenchFacts(params = {}, requestMeta = {}) {
    const facts = normalizeWorkbenchFacts(params.facts ?? params, requestMeta, this.now());
    const inputFacts = facts.inputs.map((fact) => memoryWorkbenchSessionInputWithSeq(fact, this.workbenchSessionInputs));
    for (const fact of inputFacts) this.workbenchSessionInputs.set(fact.inputId, fact);
    for (const fact of facts.sessions) this.workbenchSessions.set(fact.sessionId, fact);
    for (const fact of facts.messages) {
      const previous = this.workbenchMessages.get(fact.messageId) ?? null;
      this.workbenchMessages.set(fact.messageId, previous && (nonNegativeInteger(previous.projectedSeq) > 0 || previous.sealed === true) ? previous : fact);
    }
    for (const fact of facts.parts) this.workbenchParts.set(fact.partId, fact);
    for (const fact of facts.turns) {
      const previous = this.workbenchTurns.get(fact.turnId) ?? null;
      this.workbenchTurns.set(fact.turnId, previous && (nonNegativeInteger(previous.projectedSeq) > 0 || previous.sealed === true) ? previous : fact);
    }
    for (const fact of facts.traceEvents) this.workbenchTraceEvents.set(fact.id, fact);
    for (const fact of facts.checkpoints) {
      const previous = this.workbenchProjectionCheckpoints.get(fact.traceId) ?? null;
      if (!previous || nonNegativeInteger(fact.projectedSeq) >= nonNegativeInteger(previous.projectedSeq)) {
        this.workbenchProjectionCheckpoints.set(fact.traceId, fact);
      }
    }
    return {
      written: true,
      facts: { ...facts, inputs: inputFacts },
      persistence: this.summary()
    };
  }

  writeWorkbenchSessionAdmissionFact(params = {}, requestMeta = {}) {
    const facts = normalizeWorkbenchFacts({ sessions: [params.fact ?? params.session ?? params] }, requestMeta, this.now());
    const fact = facts.sessions[0];
    if (!fact?.sessionId) throw new Error("Workbench session admission fact requires sessionId.");
    const previous = this.workbenchSessions.get(fact.sessionId) ?? null;
    const projected = previous && (nonNegativeInteger(previous.projectedSeq) > 0 || previous.sealed === true);
    const ownership = {
      ownerUserId: previous?.ownerUserId ?? fact.ownerUserId ?? null,
      ownerRole: previous?.ownerRole ?? fact.ownerRole ?? null,
      projectId: previous?.projectId ?? fact.projectId ?? null,
      conversationId: previous?.conversationId ?? fact.conversationId ?? null,
      threadId: previous?.threadId ?? fact.threadId ?? null
    };
    const stored = projected
      ? { ...previous, ...ownership }
      : { ...previous, ...fact, ...ownership, createdAt: previous?.createdAt ?? fact.createdAt };
    this.workbenchSessions.set(fact.sessionId, stored);
    return { written: true, admissionOnly: true, fact: stored, persistence: this.summary(), valuesPrinted: false };
  }

  promoteWorkbenchTurnAdmission(params = {}, requestMeta = {}) {
    const facts = normalizeWorkbenchFacts(params.facts ?? params, requestMeta, this.now());
    const sessionFact = facts.sessions[0];
    const inputFact = facts.inputs[0];
    const turnFact = facts.turns[0];
    if (!sessionFact?.sessionId || !inputFact?.inputId || !turnFact?.turnId) throw new Error("Workbench turn admission promotion requires session, input, and turn facts.");
    const storedSession = this.writeWorkbenchSessionAdmissionFact({ fact: sessionFact }, requestMeta).fact;
    const storedInput = memoryWorkbenchSessionInputWithSeq(inputFact, this.workbenchSessionInputs);
    this.workbenchSessionInputs.set(storedInput.inputId, storedInput);
    for (const fact of facts.messages) {
      const previous = this.workbenchMessages.get(fact.messageId) ?? null;
      if (!previous || (nonNegativeInteger(previous.projectedSeq) <= 0 && previous.sealed !== true)) this.workbenchMessages.set(fact.messageId, fact);
    }
    for (const fact of facts.parts) {
      const previous = this.workbenchParts.get(fact.partId) ?? null;
      if (!previous || (nonNegativeInteger(previous.projectedSeq) <= 0 && previous.sealed !== true)) this.workbenchParts.set(fact.partId, fact);
    }
    for (const fact of facts.turns) {
      const previous = this.workbenchTurns.get(fact.turnId) ?? null;
      if (!previous || (nonNegativeInteger(previous.projectedSeq) <= 0 && previous.sealed !== true)) this.workbenchTurns.set(fact.turnId, fact);
    }
    return {
      written: true,
      promoted: true,
      outboxCommitted: true,
      facts: { ...facts, sessions: [storedSession], inputs: [storedInput] },
      persistence: this.summary(),
      valuesPrinted: false
    };
  }

  queryWorkbenchFacts(params = {}) {
    const families = workbenchFactFamilySet(params);
    const sessions = [...this.workbenchSessions.values()].filter((record) => matchesWorkbenchSessionFactQuery(record, params));
    const inputs = [...this.workbenchSessionInputs.values()].filter((record) => matchesQuery(record, params, ["inputId", "sessionId", "turnId", "traceId", "messageId", "commandId", "delivery", "status"]));
    const messages = [...this.workbenchMessages.values()].filter((record) => matchesQuery(record, params, ["messageId", "sessionId", "turnId", "traceId", "role", "status"]));
    const parts = [...this.workbenchParts.values()].filter((record) => matchesQuery(record, params, ["partId", "messageId", "sessionId", "turnId", "traceId", "partType", "status"]));
    const turns = [...this.workbenchTurns.values()].filter((record) => matchesQuery(record, params, ["turnId", "sessionId", "traceId", "messageId", "status"]));
    const afterProjectedSeq = nonNegativeInteger(params.afterProjectedSeq);
    const traceEvents = [...this.workbenchTraceEvents.values()].filter((record) => matchesQuery(record, params, ["id", "traceId", "sessionId", "turnId", "messageId", "eventType"]) && (afterProjectedSeq <= 0 || nonNegativeInteger(record.projectedSeq) > afterProjectedSeq));
    const checkpoints = [...this.workbenchProjectionCheckpoints.values()].filter((record) => matchesQuery(record, params, ["traceId", "sessionId", "turnId", "runId", "commandId", "projectionStatus", "projectionHealth"]));
    const facts = {
      inputs: families.has("inputs") ? limitResults(sortWorkbenchFactRows(inputs, params, "inputs", compareWorkbenchFactRecords), params.limit) : [],
      sessions: families.has("sessions") ? limitResults(sortWorkbenchFactRows(sessions, params, "sessions", compareWorkbenchFactRecords), params.limit) : [],
      messages: families.has("messages") ? limitResults(sortWorkbenchFactRows(messages, params, "messages", compareWorkbenchFactRecords), params.limit) : [],
      parts: families.has("parts") ? limitResults(sortWorkbenchFactRows(parts, params, "parts", compareWorkbenchPartFactRecords), params.limit) : [],
      turns: families.has("turns") ? limitResults(sortWorkbenchFactRows(turns, params, "turns", compareWorkbenchFactRecords), params.limit) : [],
      traceEvents: families.has("traceEvents") ? limitResults(sortWorkbenchFactRows(traceEvents, params, "traceEvents", compareWorkbenchTraceEventFacts), params.limit) : [],
      checkpoints: families.has("checkpoints") ? limitResults(sortWorkbenchFactRows(checkpoints, params, "checkpoints", compareWorkbenchFactRecords), params.limit) : []
    };
    return {
      facts,
      count: Object.values(facts).reduce((sum, rows) => sum + rows.length, 0),
      persistence: this.summary()
    };
  }

  recordAuditEvent(input) {
    return this.writeAuditEvent(this.buildAuditEventFromInput(input, input.requestMeta), input.requestMeta).auditEvent;
  }

  buildAuditEventFromInput(input = {}, requestMeta = {}) {
    const actor = deriveProtocolActorFromMeta(requestMeta, input.params?.audit ?? input);
    return createProtocolAuditEvent({
      auditId: input.auditId,
      traceId: requestMeta.traceId || input.traceId,
      actorType: actor.actorType,
      actorId: actor.actorId,
      action: input.action,
      targetType: input.targetType,
      targetId: input.targetId,
      projectId: input.projectId,
      gatewaySessionId: input.gatewaySessionId,
      workerSessionId: input.workerSessionId,
      operationId: input.operationId,
      serviceId: CLOUD_API_SERVICE_ID,
      outcome: input.outcome,
      reason: input.reason || input.params?.audit?.reason,
      metadata: input.metadata,
      occurredAt: input.occurredAt
    });
  }

  requireOperationRefs(params, requestMeta = {}) {
    const projectId = requireProtocolId(params, "projectId");
    const gatewaySession = this.requireGatewaySession(requireProtocolId(params, "gatewaySessionId"));
    const resource = this.requireBoxResource(requireProtocolId(params, "resourceId"));
    const capability = this.requireBoxCapability(requireProtocolId(params, "capabilityId"));

    if (gatewaySession.projectId !== projectId || resource.projectId !== projectId || capability.projectId !== projectId) {
      throw new HwlabProtocolError("hardware operation references must belong to one project", {
        code: ERROR_CODES.invalidParams,
        data: {
          projectId,
          gatewaySessionId: gatewaySession.gatewaySessionId,
          resourceId: resource.resourceId,
          capabilityId: capability.capabilityId
        }
      });
    }
    if (resource.gatewaySessionId !== gatewaySession.gatewaySessionId) {
      throw new HwlabProtocolError("resource is not registered under the requested gateway session", {
        code: ERROR_CODES.resourceLocked,
        data: {
          gatewaySessionId: gatewaySession.gatewaySessionId,
          resourceGatewaySessionId: resource.gatewaySessionId,
          resourceId: resource.resourceId
        }
      });
    }
    if (capability.resourceId !== resource.resourceId) {
      throw new HwlabProtocolError("capability is not registered on the requested resource", {
        code: ERROR_CODES.capabilityUnavailable,
        data: {
          resourceId: resource.resourceId,
          capabilityId: capability.capabilityId
        }
      });
    }

    return {
      projectId,
      gatewaySession,
      resource,
      capability,
      actor: deriveProtocolActorFromMeta(requestMeta, params.audit ?? {})
    };
  }

  requireGatewaySession(gatewaySessionId) {
    const gatewaySession = this.gatewaySessions.get(gatewaySessionId);
    if (!gatewaySession) {
      throw new HwlabProtocolError(`gateway session ${gatewaySessionId} is not registered`, {
        code: ERROR_CODES.sessionNotFound,
        data: { gatewaySessionId }
      });
    }
    return gatewaySession;
  }

  requireBoxResource(resourceId) {
    const resource = this.boxResources.get(resourceId);
    if (!resource) {
      throw new HwlabProtocolError(`box resource ${resourceId} is not registered`, {
        code: ERROR_CODES.capabilityUnavailable,
        data: { resourceId }
      });
    }
    return resource;
  }

  requireBoxCapability(capabilityId) {
    const capability = this.boxCapabilities.get(capabilityId);
    if (!capability) {
      throw new HwlabProtocolError(`capability ${capabilityId} is not reported`, {
        code: ERROR_CODES.capabilityUnavailable,
        data: { capabilityId }
      });
    }
    return capability;
  }
}
