export const ENVIRONMENT_DEV = "dev"; export const DEV_ENDPOINT = "http://74.48.78.17:6667"; export const JSON_RPC_VERSION = "2.0"; export const SERVICE_IDS = Object.freeze([ "hwlab-cloud-api", "hwlab-cloud-web", "hwlab-agent-mgr", "hwlab-agent-worker", "hwlab-gateway", "hwlab-gateway-simu", "hwlab-box-simu", "hwlab-patch-panel", "hwlab-router", "hwlab-tunnel-client", "hwlab-edge-proxy", "hwlab-cli", "hwlab-agent-skills" ]); export const TABLES = Object.freeze([ "projects", "gateway_sessions", "box_resources", "box_capabilities", "wiring_configs", "patch_panel_status", "hardware_operations", "audit_events", "agent_sessions", "worker_sessions", "agent_trace_events", "evidence_records" ]); export const ERROR_CODES = Object.freeze({ parseError: -32700, invalidRequest: -32600, methodNotFound: -32601, invalidParams: -32602, internalError: -32603, hwlabUnknown: -32000, projectNotFound: -32010, sessionNotFound: -32011, capabilityUnavailable: -32020, resourceLocked: -32021, wiringInvalid: -32022, operationRejected: -32023, evidenceMissing: -32030, auditRequired: -32040 }); const methodPattern = /^[a-z][a-z0-9]*(\.[a-z][a-z0-9_]*)+$/; export function isFrozenServiceId(serviceId) { return SERVICE_IDS.includes(serviceId); } export function validateMeta(meta) { if (!meta || typeof meta !== "object" || Array.isArray(meta)) { throw new Error("meta must be an object"); } if (!meta.traceId) { throw new Error("meta.traceId is required"); } if (!isFrozenServiceId(meta.serviceId)) { throw new Error(`unknown serviceId ${JSON.stringify(meta.serviceId)}`); } if (meta.environment !== ENVIRONMENT_DEV) { throw new Error(`unsupported environment ${JSON.stringify(meta.environment)}`); } } export function validateRequest(envelope) { if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) { throw new Error("request must be an object"); } if (envelope.jsonrpc !== JSON_RPC_VERSION) { throw new Error(`jsonrpc must be ${JSON_RPC_VERSION}`); } if (envelope.id === undefined || envelope.id === null) { throw new Error("id is required"); } if (!methodPattern.test(envelope.method)) { throw new Error(`invalid method ${JSON.stringify(envelope.method)}`); } if ( envelope.params !== undefined && (!envelope.params || typeof envelope.params !== "object" || Array.isArray(envelope.params)) ) { throw new Error("params must be an object when present"); } validateMeta(envelope.meta); } export function validateResponse(envelope) { if (!envelope || typeof envelope !== "object" || Array.isArray(envelope)) { throw new Error("response must be an object"); } if (envelope.jsonrpc !== JSON_RPC_VERSION) { throw new Error(`jsonrpc must be ${JSON_RPC_VERSION}`); } if (envelope.id === undefined || envelope.id === null) { throw new Error("id is required"); } const hasResult = Object.hasOwn(envelope, "result"); const hasError = Object.hasOwn(envelope, "error"); if (hasResult === hasError) { throw new Error("response must contain exactly one of result or error"); } if (hasError) { validateRpcError(envelope.error); } validateMeta(envelope.meta); } export function validateRpcError(error) { if (!error || typeof error !== "object" || Array.isArray(error)) { throw new Error("error must be an object"); } if (!Number.isInteger(error.code)) { throw new Error("error.code must be an integer"); } if (!error.message || typeof error.message !== "string") { throw new Error("error.message is required"); } }