/* * 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; } }