import {BOT_EVENTS, RELAY_METHODS, WORKFLOW_ACTIONS, WORKFLOW_METHODS, WORLD_METHODS} from '../types.ts'; import type { AuthorizeGrantCommand, BotEvent, ForwardAck, ForwardErrorCode, InfoUpdate, OpenSessionCommand, RelayLimits, RelayMethod, RelayRequest, SessionScope, } from '../types.ts'; const ERROR_CODES: readonly ForwardErrorCode[] = [ 'INVALID_REQUEST', 'UNAUTHORIZED', 'FORBIDDEN', 'NODE_OFFLINE', 'RELAY_UNAVAILABLE', 'SESSION_STALE', 'TOKEN_EXPIRED', 'TIMEOUT', 'CANCELLED', 'NO_CONTEXT', 'BOT_NOT_ONLINE', 'RATE_LIMITED', 'RESYNC_REQUIRED', 'INTERNAL_ERROR', ]; const STATUSES = ['offline', 'ready', 'connecting', 'online', 'disconnecting', 'reconnecting']; const STYLES = ['normal', 'italic', 'bold', 'light', 'underline', 'url']; export class RelayError extends Error { code: ForwardErrorCode; retryable: boolean; constructor(code: ForwardErrorCode, message: string, retryable = false) { super(message); this.name = 'RelayError'; this.code = code; this.retryable = retryable; } } export function fail(code: ForwardErrorCode, message: string, retryable = false): never { throw new RelayError(code, message, retryable); } export function success(data: T): ForwardAck { return {status: 'success', data}; } export function error_ack(error: unknown): ForwardAck { if (error instanceof RelayError && ERROR_CODES.includes(error.code) && typeof error.message === 'string' && Buffer.byteLength(error.message) <= 1024 && typeof error.retryable === 'boolean') { return {status: 'error', data: { code: error.code, message: error.message, retryable: error.retryable, }}; } return {status: 'error', data: { code: 'INTERNAL_ERROR', message: 'Internal relay error', retryable: false, }}; } export function is_record(value: unknown): value is Record { if (value === null || typeof value !== 'object' || Array.isArray(value)) return false; const prototype = Object.getPrototypeOf(value); return prototype === Object.prototype || prototype === null; } export function is_uuid(value: unknown): value is string { return typeof value === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value); } function require_valid(condition: unknown, message: string): asserts condition { if (!condition) fail('INVALID_REQUEST', message); } function exact(value: unknown, required: string[], optional: string[] = []): asserts value is Record { require_valid(is_record(value), 'Expected an object'); const keys = Reflect.ownKeys(value); require_valid(keys.every(key => typeof key === 'string' && (required.includes(key) || optional.includes(key))), 'Unknown DTO field'); require_valid(required.every(key => Object.hasOwn(value, key)), 'Missing DTO field'); for (const key of keys) { const descriptor = Object.getOwnPropertyDescriptor(value, key)!; require_valid('value' in descriptor && descriptor.enumerable, 'Expected JSON data fields'); } } function integer(value: unknown, min = 0): value is number { return typeof value === 'number' && Number.isSafeInteger(value) && value >= min; } function text(value: unknown, max = 1024, nonempty = false): value is string { return typeof value === 'string' && (!nonempty || value.trim().length > 0) && Buffer.byteLength(value, 'utf8') <= max; } // Inspect JSON before stringify: bound traversal and reject values serialization would omit or coerce. export function check_size(value: unknown, max: number): void { check_json_size(value, max, false); } function check_json_size(value: unknown, max: number, info: boolean | 'files' | 'workflow' | 'world'): void { require_valid(integer(max, 1), 'Invalid byte limit'); let entries = 0; let bytes = 0; const ancestors = new Set(); function visit(item: unknown, depth: number): void { require_valid(depth <= (info === 'world' ? 16 : info ? 68 : 16) && ++entries <= (info === true ? 260_000 : info === 'world' ? 850_000 : 65_536), 'JSON complexity limit exceeded'); if (item === null || typeof item === 'boolean') { bytes += item === null ? 4 : item ? 4 : 5; } else if (typeof item === 'string') { require_valid(Buffer.byteLength(item, 'utf8') <= max, 'Payload byte limit exceeded'); bytes += Buffer.byteLength(JSON.stringify(item), 'utf8'); } else if (typeof item === 'number') { require_valid(Number.isFinite(item), 'Expected a finite JSON number'); bytes += Buffer.byteLength(JSON.stringify(item)); } else { require_valid(Array.isArray(item) || is_record(item), 'Expected JSON data'); require_valid(!ancestors.has(item), 'Cyclic JSON data'); ancestors.add(item); bytes += 2; const keys = Reflect.ownKeys(item); if (Array.isArray(item)) { if (info === 'workflow') require_valid(Object.getPrototypeOf(item) === Array.prototype, 'Expected a plain JSON array'); require_valid(item.length <= (info === true ? 65_536 : info === 'world' ? 8192 : 4096) && keys.length === item.length + 1, 'Expected a dense JSON array'); for (let index = 0; index < item.length; index++) { const descriptor = Object.getOwnPropertyDescriptor(item, String(index)); require_valid(descriptor && 'value' in descriptor && descriptor.enumerable, 'Expected JSON array data'); if (index) bytes++; visit(descriptor.value, depth + 1); } } else { require_valid(keys.length <= (info === true ? 65_536 : 4096), 'JSON key limit exceeded'); for (let index = 0; index < keys.length; index++) { const key = keys[index]; require_valid(typeof key === 'string', 'Expected JSON string keys'); if (info === 'workflow') require_valid(!['__proto__', 'prototype', 'constructor'].includes(key), 'Unsafe workflow JSON key'); const descriptor = Object.getOwnPropertyDescriptor(item, key)!; require_valid('value' in descriptor && descriptor.enumerable, 'Expected JSON data fields'); require_valid(Buffer.byteLength(key) <= max, 'Payload byte limit exceeded'); bytes += Buffer.byteLength(JSON.stringify(key)) + 1 + (index ? 1 : 0); visit(descriptor.value, depth + 1); } } ancestors.delete(item); } require_valid(bytes <= max, 'Payload byte limit exceeded'); } visit(value, 0); } export function check_permissions(value: unknown): asserts value is RelayMethod[] { check_size(value, 1024); require_valid(Array.isArray(value) && value.length <= RELAY_METHODS.length && value.every(item => RELAY_METHODS.includes(item)) && new Set(value).size === value.length, 'Invalid relay permissions'); } export function validate_scope(value: unknown): asserts value is SessionScope { require_valid(is_record(value), 'Invalid session scope'); require_valid(integer(value.forward_id) && integer(value.node_id, 1) && integer(value.bot_id, 1), 'Invalid scope IDs'); require_valid(is_uuid(value.forward_session_id), 'Invalid forward session ID'); require_valid(text(value.node_session_id, 256, true), 'Invalid node session ID'); } function events(value: unknown): void { require_valid(Array.isArray(value) && value.length <= BOT_EVENTS.length && value.every(item => BOT_EVENTS.includes(item)) && new Set(value).size === value.length, 'Invalid bot events'); } function workflow_id(value: unknown): value is string { return typeof value === 'string' && /^[a-zA-Z0-9_-]{1,100}$/.test(value) && !['__proto__', 'prototype', 'constructor'].includes(value); } function workflow_input(value: unknown): void { check_json_size(value, 64 * 1024, 'workflow'); require_valid(is_record(value), 'Invalid workflow input'); } export function validate_request(value: unknown, limits: RelayLimits): asserts value is RelayRequest { exact(value, ['request_id', 'method', 'payload']); check_json_size(value, limits.max_payload_bytes, WORKFLOW_METHODS.includes(value.method as typeof WORKFLOW_METHODS[number]) ? 'workflow' : false); require_valid(is_uuid(value.request_id), 'Invalid request ID'); require_valid(value.method === 'get_log_history' || value.method === 'tab_complete' || value.method === 'get_context_info' || value.method === 'get_data_file_list' || value.method === 'get_file_data' || WORKFLOW_METHODS.includes(value.method as typeof WORKFLOW_METHODS[number]) || WORLD_METHODS.includes(value.method as typeof WORLD_METHODS[number]) || (value.method !== 'bot.files' && RELAY_METHODS.includes(value.method as RelayMethod)), 'Invalid relay method'); const payload = value.payload; switch (value.method) { case 'world.snapshot': exact(payload, ['radius', 'known'], ['world_generation', 'watch_id']); require_valid(integer(payload.radius, 1) && payload.radius <= 3, 'Invalid world radius'); world_revisions(payload.known); for (const field of ['world_generation', 'watch_id']) { if (Object.hasOwn(payload, field)) require_valid(is_uuid(payload[field]), 'Invalid world watch scope'); } break; case 'world.chunk': exact(payload, ['x', 'z', 'world_generation', 'watch_id']); require_valid(world_coordinate(payload.x) && world_coordinate(payload.z) && is_uuid(payload.world_generation) && is_uuid(payload.watch_id), 'Invalid world chunk request'); break; case 'world.unwatch': exact(payload, ['watch_id']); require_valid(is_uuid(payload.watch_id), 'Invalid world watch ID'); break; case 'bot.snapshot': case 'get_log_history': case 'get_context_info': case 'get_data_file_list': case 'workflow.catalog': case 'workflow.runtime': exact(payload, []); break; case 'workflow.start': exact(payload, ['workflow_id'], ['trigger']); require_valid(workflow_id(payload.workflow_id), 'Invalid workflow ID'); if (Object.hasOwn(payload, 'trigger')) workflow_input(payload.trigger); break; case 'workflow.stop': case 'workflow.snapshot': exact(payload, value.method === 'workflow.stop' ? ['run_id'] : [], value.method === 'workflow.snapshot' ? ['run_id', 'include_branches'] : []); if (Object.hasOwn(payload, 'run_id')) require_valid(is_uuid(payload.run_id), 'Invalid workflow run ID'); if (Object.hasOwn(payload, 'include_branches')) { require_valid(Object.hasOwn(payload, 'run_id') && typeof payload.include_branches === 'boolean', 'Workflow branch selection requires a run ID and boolean flag'); } break; case 'workflow.command': case 'workflow.control': exact(payload, ['command_id', 'context_generation', ...(value.method === 'workflow.control' ? ['action'] : [])], value.method === 'workflow.control' ? ['workflow_id', 'run_id', 'expected_generation', 'input'] : []); require_valid(is_uuid(payload.command_id) && is_uuid(payload.context_generation), 'Invalid workflow command context'); if (value.method === 'workflow.command') break; require_valid(WORKFLOW_ACTIONS.includes(payload.action as typeof WORKFLOW_ACTIONS[number]), 'Invalid workflow action'); exact(payload, ['command_id', 'context_generation', 'action', payload.action === 'stop_run' ? 'run_id' : 'workflow_id'], [...(payload.action !== 'stop_run' ? ['expected_generation'] : []), ...(payload.action === 'run_once' ? ['input'] : [])]); if (Object.hasOwn(payload, 'workflow_id')) require_valid(workflow_id(payload.workflow_id), 'Invalid workflow ID'); if (Object.hasOwn(payload, 'run_id')) require_valid(is_uuid(payload.run_id), 'Invalid workflow run ID'); require_valid(payload.action === 'stop_run' ? is_uuid(payload.run_id) : workflow_id(payload.workflow_id), 'Missing workflow target'); if (Object.hasOwn(payload, 'expected_generation')) require_valid(integer(payload.expected_generation, 1), 'Invalid workflow generation'); if (Object.hasOwn(payload, 'input')) workflow_input(payload.input); break; case 'get_file_data': exact(payload, ['id'], ['offset', 'length']); require_valid(is_uuid(payload.id), 'Invalid file ID'); if (Object.hasOwn(payload, 'offset')) require_valid(integer(payload.offset), 'Invalid file offset'); if (Object.hasOwn(payload, 'length')) require_valid(integer(payload.length, 1) && payload.length <= 262_144, 'Invalid file length'); break; case 'bot.logs': exact(payload, ['limit'], ['stream_id', 'after_seq']); require_valid(integer(payload.limit, 1) && payload.limit <= 200, 'Invalid logs limit'); if (Object.hasOwn(payload, 'stream_id')) require_valid(is_uuid(payload.stream_id), 'Invalid stream ID'); if (Object.hasOwn(payload, 'after_seq')) { require_valid(integer(payload.after_seq) && is_uuid(payload.stream_id), 'Invalid log cursor'); } break; case 'bot.subscribe': case 'bot.unsubscribe': exact(payload, ['events']); events(payload.events); break; case 'tab_complete': exact(payload, ['text']); require_valid(typeof payload.text === 'string' && payload.text.length <= 256 && !/[\r\n\0]/.test(payload.text), 'Invalid completion text'); break; case 'bot.chat': exact(payload, ['message']); require_valid(integer(limits.max_chat_length, 1) && typeof payload.message === 'string' && payload.message.trim().length > 0 && payload.message.length <= limits.max_chat_length, 'Invalid chat message'); break; case 'execute_function': exact(payload, ['function', 'name', 'args']); require_valid(typeof payload.function === 'string' && typeof payload.name === 'string' && Array.isArray(payload.args), 'Invalid function execution payload'); break; } } function generation(value: unknown): boolean { return value === null || is_uuid(value); } function context_state(status: unknown, exists: boolean, context_generation: unknown): void { require_valid(exists ? is_uuid(context_generation) : context_generation === null && status === 'offline', 'Bot context and status are inconsistent'); } function log_records(value: unknown): void { require_valid(Array.isArray(value) && value.length <= 200, 'Invalid log records'); let previous_seq = -1; const ids = new Set(); for (const record of value) { exact(record, ['id', 'timestamp', 'context_generation', 'seq', 'segments']); require_valid(is_uuid(record.id) && integer(record.timestamp) && generation(record.context_generation) && integer(record.seq), 'Invalid log record'); require_valid(record.seq > previous_seq && !ids.has(record.id), 'Log records must be ordered and unique'); previous_seq = record.seq; ids.add(record.id); require_valid(Array.isArray(record.segments), 'Invalid log segments'); for (const segment of record.segments) { exact(segment, ['message', 'font_color', 'font_style'], ['hoverEvent', 'clickEvent', 'insertion']); require_valid(typeof segment.message === 'string' && text(segment.font_color, 128, true) && Array.isArray(segment.font_style) && segment.font_style.length <= STYLES.length && segment.font_style.every(style => STYLES.includes(style)), 'Invalid log segment'); for (const key of ['hoverEvent', 'clickEvent']) { if (!Object.hasOwn(segment, key)) continue; const event = segment[key]; exact(event, ['action', 'value'], key === 'hoverEvent' ? ['segments'] : []); const actions = key === 'hoverEvent' ? ['show_text', 'show_item', 'show_entity'] : ['open_url', 'open_file', 'run_command', 'suggest_command', 'change_page', 'copy_to_clipboard', 'show_dialog', 'custom']; require_valid(actions.includes(event.action as string) && text(event.value, 4096, true), 'Invalid log event'); if (Object.hasOwn(event, 'segments')) { require_valid(event.action === 'show_text' && Array.isArray(event.segments) && event.segments.length <= 64, 'Invalid hover segments'); let length = 0; for (const part of event.segments) { exact(part, ['message', 'font_color', 'font_style']); require_valid(text(part.message, 4096, true) && text(part.font_color, 128, true) && Array.isArray(part.font_style) && part.font_style.length <= STYLES.length && part.font_style.every(style => STYLES.includes(style)), 'Invalid hover segment'); length += (part.message as string).length; } require_valid(length <= 4096, 'Hover text too long'); } } if (Object.hasOwn(segment, 'insertion')) require_valid(text(segment.insertion, 4096, true), 'Invalid log insertion'); } } } export function validate_event(value: unknown, limits: RelayLimits): asserts value is BotEvent { check_size(value, limits.max_payload_bytes); exact(value, ['forward_session_id', 'bot_id', 'context_generation', 'stream_id', 'seq', 'emitted_at', 'name', 'payload']); require_valid(is_uuid(value.forward_session_id) && integer(value.bot_id, 1) && generation(value.context_generation) && is_uuid(value.stream_id) && integer(value.seq) && integer(value.emitted_at), 'Invalid bot event envelope'); const payload = value.payload; switch (value.name) { case 'status_update': exact(payload, ['status', 'context_exists']); require_valid(STATUSES.includes(payload.status as string) && typeof payload.context_exists === 'boolean', 'Invalid bot status'); context_state(payload.status, payload.context_exists, value.context_generation); break; case 'log': exact(payload, ['records']); log_records(payload.records); require_valid((payload.records as {seq: number}[]).every(record => record.seq <= (value.seq as number)), 'Log record exceeds event watermark'); break; case 'message': exact(payload, ['text', 'position'], ['json']); require_valid(typeof payload.text === 'string' && text(payload.position, 128, true), 'Invalid bot message'); break; case 'bot_error': exact(payload, ['message'], ['code']); require_valid(text(payload.message, 1024), 'Invalid bot error'); if (Object.hasOwn(payload, 'code')) require_valid(text(payload.code, 128, true), 'Invalid bot error code'); break; default: fail('INVALID_REQUEST', 'Invalid bot event'); } } function info_name(value: unknown): boolean { return text(value, 128) && /^[a-zA-Z0-9_-]+$/.test(value); } export function validate_info_update(value: unknown, limits: RelayLimits): asserts value is InfoUpdate { check_json_size(value, limits.max_payload_bytes, true); exact(value, ['forward_session_id', 'bot_id', 'context_generation', 'stream_id', 'seq', 'emitted_at', 'data']); require_valid(is_uuid(value.forward_session_id) && integer(value.bot_id, 1) && is_uuid(value.context_generation) && is_uuid(value.stream_id) && integer(value.seq) && integer(value.emitted_at), 'Invalid info update envelope'); exact(value.data, ['name', 'value']); require_valid(info_name(value.data.name), 'Invalid info name'); } function validate_info_i18n(value: unknown): void { exact(value, ['lang', 'name', 'description'], ['fields', 'values']); require_valid(text(value.lang, 128) && text(value.name, 256) && text(value.description, 4096), 'Invalid info translation'); for (const key of ['fields', 'values']) { if (!Object.hasOwn(value, key)) continue; const children = value[key]; require_valid(is_record(children), 'Invalid info translation map'); for (const [name, metadata] of Object.entries(children)) { require_valid(text(name, 128), 'Invalid info translation key'); validate_info_i18n(metadata); } } } function workflow_start_result(value: unknown): void { exact(value, ['status'], ['run_id', 'request_id']); require_valid(['started', 'queued', 'skipped'].includes(value.status as string), 'Invalid workflow start result'); if (Object.hasOwn(value, 'run_id')) require_valid(is_uuid(value.run_id), 'Invalid workflow run ID'); if (Object.hasOwn(value, 'request_id')) require_valid(is_uuid(value.request_id), 'Invalid workflow request ID'); require_valid(value.status !== 'started' || is_uuid(value.run_id), 'Missing started run ID'); require_valid(value.status !== 'queued' || is_uuid(value.request_id), 'Missing queued request ID'); } function workflow_error(value: unknown, node = false): void { exact(value, ['code', 'message', ...(node ? ['node_id'] : [])]); require_valid(text(value.code, 128, true) && text(value.message, 1024), 'Invalid workflow error'); if (node) require_valid(text(value.node_id, 100), 'Invalid workflow error node'); } function workflow_command(value: unknown): void { exact(value, ['command_id', 'action', 'workflow_id', 'state'], ['result', 'error']); require_valid(is_uuid(value.command_id) && WORKFLOW_ACTIONS.includes(value.action as typeof WORKFLOW_ACTIONS[number]) && (value.workflow_id === null || workflow_id(value.workflow_id)) && ['accepted', 'completed', 'failed'].includes(value.state as string), 'Invalid workflow command'); if (Object.hasOwn(value, 'error')) workflow_error(value.error); if (Object.hasOwn(value, 'result')) { require_valid(is_record(value.result), 'Invalid workflow command result'); if (Object.hasOwn(value.result, 'status')) workflow_start_result(value.result); else if (Object.hasOwn(value.result, 'stopped')) { exact(value.result, ['stopped']); require_valid(value.result.stopped === true, 'Invalid workflow stop result'); } else { exact(value.result, ['generation']); require_valid(integer(value.result.generation, 1), 'Invalid workflow result generation'); } } } function workflow_runtime(value: unknown): void { exact(value, ['source_config_revision', 'workflows', 'commands']); require_valid(integer(value.source_config_revision) && Array.isArray(value.workflows) && value.workflows.length <= 4096 && Array.isArray(value.commands) && value.commands.length <= 256, 'Invalid workflow runtime snapshot'); for (const state of value.workflows) { exact(state, ['workflow_id', 'generation', 'enabled', 'start_type', 'trigger_state', 'accepted_count', 'max_runs', 'next_fire_at', 'active_run_ids', 'queued_count', 'last_error']); require_valid(workflow_id(state.workflow_id) && integer(state.generation, 1) && typeof state.enabled === 'boolean' && ['common', 'event', 'timer'].includes(state.start_type as string) && ['stopped', 'listening', 'scheduled', 'exhausted', 'reloading', 'error'].includes(state.trigger_state as string) && integer(state.accepted_count) && (state.max_runs === null || integer(state.max_runs, 1)) && (state.next_fire_at === null || integer(state.next_fire_at)) && integer(state.queued_count) && Array.isArray(state.active_run_ids) && state.active_run_ids.every(is_uuid), 'Invalid workflow runtime state'); if (state.last_error !== null) workflow_error(state.last_error); } for (const command of value.commands) workflow_command(command); } function workflow_run(value: unknown): void { exact(value, ['run_id', 'workflow_id', 'status', 'steps', 'active_nodes', 'started_at', 'ended_at', 'stop_reason', 'error'], ['branches', 'trigger', 'trigger_available']); require_valid(is_uuid(value.run_id) && workflow_id(value.workflow_id) && ['running', 'stopping', 'completed', 'failed', 'cancelled', 'timed_out'].includes(value.status as string) && integer(value.steps) && Array.isArray(value.active_nodes) && value.active_nodes.every(id => text(id, 100, true)) && integer(value.started_at) && (value.ended_at === null || integer(value.ended_at)) && (value.stop_reason === null || ['manual', 'disconnect', 'timeout', 'disposed', 'updated', 'reloaded'].includes(value.stop_reason as string)), 'Invalid workflow run'); if (value.error !== null) workflow_error(value.error, true); if (Object.hasOwn(value, 'trigger_available')) { require_valid(typeof value.trigger_available === 'boolean', 'Invalid workflow trigger availability'); } if (Object.hasOwn(value, 'trigger')) { workflow_input(value.trigger); require_valid(value.trigger_available !== false, 'Workflow trigger availability is inconsistent'); } if (Object.hasOwn(value, 'branches')) { require_valid(Array.isArray(value.branches), 'Invalid workflow branches'); for (const branch of value.branches) { exact(branch, ['trigger', 'vars', 'outputs', 'last_result', 'inputs', 'retry_counts']); require_valid(['trigger', 'vars', 'outputs', 'inputs', 'retry_counts'].every(key => is_record(branch[key])) && (branch.last_result === null || is_record(branch.last_result)), 'Invalid workflow branch'); for (const output of Object.values(branch.outputs as Record)) workflow_node_result(output, 'execution'); for (const count of Object.values(branch.retry_counts as Record)) require_valid(integer(count), 'Invalid workflow retry count'); if (branch.last_result !== null) workflow_node_result(branch.last_result, 'node_id'); } } } function workflow_node_result(value: unknown, field: 'execution' | 'node_id'): void { exact(value, ['status', 'output', field], ['error']); require_valid(value.status === 'success' || value.status === 'failure', 'Invalid workflow node result'); require_valid(field === 'execution' ? integer(value.execution, 1) : text(value.node_id, 100, true), 'Invalid workflow node result identity'); if (Object.hasOwn(value, 'error')) workflow_error(value.error, true); } function world_coordinate(value: unknown): value is number { return integer(value, -1_875_000) && value <= 1_875_000; } function world_revisions(value: unknown): asserts value is Array<{x: number; z: number; revision: string}> { require_valid(Array.isArray(value) && value.length <= 49, 'Invalid world revisions'); const seen = new Set(); for (const entry of value) { exact(entry, ['x', 'z', 'revision']); require_valid(world_coordinate(entry.x) && world_coordinate(entry.z) && is_uuid(entry.revision), 'Invalid world chunk revision'); const key = `${entry.x},${entry.z}`; require_valid(!seen.has(key), 'Duplicate world coordinates'); seen.add(key); } } function world_cells(value: unknown, max: number): Buffer { require_valid(typeof value === 'string' && value.length === 684, 'Invalid world encoded length'); const decoded = Buffer.from(value, 'base64'); require_valid(decoded.length === 512 && decoded.toString('base64') === value, 'Invalid world encoded cells'); for (let offset = 0; offset < 512; offset += 2) { require_valid(decoded.readUInt16LE(offset) <= max, 'Invalid world cell value'); } return decoded; } function world_snapshot(value: unknown, payload: unknown): void { exact(value, ['world_generation', 'context_generation', 'stream_id', 'watch_id', 'version', 'dimension', 'min_y', 'height', 'center', 'position', 'chunks', 'loaded', 'partial']); require_valid(['world_generation', 'context_generation', 'stream_id', 'watch_id'].every(key => is_uuid(value[key])) && text(value.version, 64, true) && text(value.dimension, 256, true) && integer(value.min_y, -4096) && value.min_y <= 4096 && integer(value.height, 1) && value.height <= 4096 && typeof value.partial === 'boolean', 'Invalid world snapshot metadata'); exact(value.center, ['x', 'z']); require_valid(world_coordinate(value.center.x) && world_coordinate(value.center.z), 'Invalid world center'); exact(value.position, ['x', 'y', 'z', 'yaw']); require_valid(Object.values(value.position).every(number => typeof number === 'number' && Number.isFinite(number)), 'Invalid world position'); const radius = is_record(payload) ? payload.radius : 3; require_valid(integer(radius, 1) && radius <= 3, 'Invalid world request radius'); const center = value.center; const in_radius = (x: number, z: number) => Math.abs(x - (center.x as number)) <= radius && Math.abs(z - (center.z as number)) <= radius; world_revisions(value.loaded); const loaded = new Map(value.loaded.map(entry => [`${entry.x},${entry.z}`, entry.revision.toLowerCase()])); require_valid(value.loaded.every(entry => in_radius(entry.x, entry.z)), 'Loaded world chunk outside radius'); require_valid(Array.isArray(value.chunks) && value.chunks.length <= 49, 'Invalid world chunks'); const seen = new Set(); for (const chunk of value.chunks) { exact(chunk, ['x', 'z', 'revision', 'palette', 'heights', 'blocks'], ['terrain', 'fluids', 'spikes', 'features']); require_valid(world_coordinate(chunk.x) && world_coordinate(chunk.z) && is_uuid(chunk.revision) && in_radius(chunk.x, chunk.z), 'Invalid world chunk scope'); const key = `${chunk.x},${chunk.z}`; require_valid(!seen.has(key) && loaded.get(key) === chunk.revision.toLowerCase(), 'Invalid world chunk membership'); seen.add(key); require_valid(Array.isArray(chunk.palette) && chunk.palette.length >= 1 && chunk.palette.length <= 512, 'Invalid world palette'); for (const block of chunk.palette) { exact(block, ['name', 'color']); require_valid(text(block.name, 128) && integer(block.color) && block.color <= 0xffffff, 'Invalid world palette entry'); } // Heights encode topY - min_y + 1; zero represents an empty column. const heights = world_cells(chunk.heights, value.height as number); const blocks = world_cells(chunk.blocks, chunk.palette.length - 1); require_valid(Object.hasOwn(chunk, 'terrain') === Object.hasOwn(chunk, 'fluids'), 'World terrain and fluids must appear together'); if (Object.hasOwn(chunk, 'fluids')) { exact(chunk.terrain, ['heights', 'blocks']); exact(chunk.fluids, ['heights', 'blocks', 'levels']); const terrain_heights = world_cells(chunk.terrain.heights, value.height as number); world_cells(chunk.terrain.blocks, chunk.palette.length - 1); const fluid_heights = world_cells(chunk.fluids.heights, value.height as number); const fluid_blocks = world_cells(chunk.fluids.blocks, chunk.palette.length - 1); world_cells(chunk.fluids.levels, 15); for (let offset = 0; offset < 512; offset += 2) { const fluid_height = fluid_heights.readUInt16LE(offset); if (fluid_height === 0) continue; const name = chunk.palette[fluid_blocks.readUInt16LE(offset)].name; require_valid(['water', 'lava', 'minecraft:water', 'minecraft:lava'].includes(name), 'Invalid world fluid block'); require_valid(terrain_heights.readUInt16LE(offset) <= fluid_height, 'World terrain exceeds fluid height'); require_valid(heights.readUInt16LE(offset) === fluid_height, 'World fluid differs from visible height'); } } if (Object.hasOwn(chunk, 'features')) { exact(chunk.features, ['heights', 'blocks', 'entries']); const bases = world_cells(chunk.features.heights, value.height as number); world_cells(chunk.features.blocks, chunk.palette.length - 1); require_valid(Array.isArray(chunk.features.entries) && chunk.features.entries.length > 0 && chunk.features.entries.length <= 512, 'Invalid world features'); const pairs = new Set(), counts = new Map(); const enums: Record = {facing: ['north', 'south', 'east', 'west', 'up', 'down'], half: ['top', 'bottom'], type: ['top', 'bottom', 'double', 'single', 'left', 'right'], shape: ['straight', 'inner_left', 'inner_right', 'outer_left', 'outer_right'], axis: ['x', 'y', 'z'], vertical_direction: ['up', 'down'], thickness: ['tip', 'tip_merge', 'frustum', 'middle', 'base']}; for (const entry of chunk.features.entries) { exact(entry, ['index', 'height', 'block', 'properties']); require_valid(integer(entry.index) && entry.index <= 255 && integer(entry.height, 1) && entry.height <= (value.height as number) && integer(entry.block) && entry.block < chunk.palette.length, 'Invalid feature position'); exact(entry.properties, [], ['facing', 'rotation', 'half', 'type', 'shape', 'open', 'attached', 'lit', 'waterlogged', 'east', 'west', 'north', 'south', 'axis', 'up', 'vertical_direction', 'thickness']); for (const [key, item] of Object.entries(entry.properties)) require_valid(key === 'rotation' ? integer(item) && item <= 15 : enums[key] ? typeof item === 'string' && enums[key]!.includes(item) : ['north', 'south', 'east', 'west', 'up'].includes(key) ? typeof item === 'boolean' || ['none', 'low', 'tall'].includes(item as string) : typeof item === 'boolean', 'Invalid feature property'); const index = entry.index as number, height = entry.height as number, pair = `${index}:${height}`; const name = chunk.palette[entry.block as number].name.replace(/^minecraft:/, ''); require_valid(entry.properties.waterlogged === true || /(?:^|_)(?:wall_)?torch$|_(?:sign|slab|stairs|fence|pane|trapdoor|wall)$/.test(name), 'Invalid feature block'); require_valid(!pairs.has(pair) && height <= heights.readUInt16LE(index * 2) && height > bases.readUInt16LE(index * 2), 'Invalid feature column'); pairs.add(pair); counts.set(index, (counts.get(index) ?? 0) + 1); require_valid(counts.get(index)! <= 16, 'Feature column exceeds limit'); } } if (Object.hasOwn(chunk, 'spikes')) { exact(chunk.spikes, ['heights', 'blocks', 'segments']); const bases = world_cells(chunk.spikes.heights, value.height as number); world_cells(chunk.spikes.blocks, chunk.palette.length - 1); require_valid(Array.isArray(chunk.spikes.segments) && chunk.spikes.segments.length >= 1 && chunk.spikes.segments.length <= 512, 'Invalid world spike segments'); const visible = Object.hasOwn(chunk, 'terrain') ? world_cells((chunk.terrain as Record).blocks, chunk.palette.length - 1) : blocks; const pairs = new Set(), counts = new Map(); for (const segment of chunk.spikes.segments) { exact(segment, ['index', 'height', 'direction', 'thickness']); require_valid(integer(segment.index) && segment.index <= 255 && integer(segment.height, 1) && segment.height <= (value.height as number) && ['up', 'down'].includes(segment.direction as string) && ['tip', 'tip_merge', 'frustum', 'middle', 'base'].includes(segment.thickness as string), 'Invalid world spike state'); const index = segment.index as number, height = segment.height as number, pair = `${index}:${height}`; require_valid(!pairs.has(pair) && height <= heights.readUInt16LE(index * 2) && height > bases.readUInt16LE(index * 2) && /^(?:minecraft:)?pointed_dripstone$/.test(chunk.palette[visible.readUInt16LE(index * 2)].name), 'Invalid world spike column'); pairs.add(pair); counts.set(index, (counts.get(index) ?? 0) + 1); require_valid(counts.get(index)! <= 16, 'World spike column exceeds segment limit'); } } } } function world_voxel_chunk(value: unknown, payload: unknown): void { exact(value, ['x', 'z', 'revision', 'world_generation', 'min_y', 'height', 'palette', 'sections']); require_valid(world_coordinate(value.x) && world_coordinate(value.z) && is_uuid(value.revision) && is_uuid(value.world_generation) && integer(value.min_y, -4096) && value.min_y <= 4096 && integer(value.height, 1) && value.height <= 4096, 'Invalid world voxel metadata'); require_valid(is_record(payload) && value.x === payload.x && value.z === payload.z && is_uuid(payload.world_generation) && value.world_generation.toLowerCase() === payload.world_generation.toLowerCase(), 'World chunk does not match the request'); require_valid(Array.isArray(value.palette) && value.palette.length >= 1 && value.palette.length <= 8192, 'Invalid world voxel palette'); for (const block of value.palette) { exact(block, ['name', 'color', 'properties', 'occluding']); require_valid(text(block.name, 128, true) && integer(block.color) && block.color <= 0xffffff && typeof block.occluding === 'boolean' && is_record(block.properties) && Object.keys(block.properties).length <= 32, 'Invalid world voxel palette entry'); for (const [key, item] of Object.entries(block.properties)) { require_valid(key.length >= 1 && key.length <= 32 && !/[^a-z0-9_]/.test(key) && (typeof item === 'boolean' || text(item, 64) || integer(item, -65_536) && item <= 65_536), 'Invalid world voxel property'); } } require_valid(Array.isArray(value.sections) && value.sections.length <= 256, 'Invalid world voxel sections'); let previous_y = -1; for (const section of value.sections) { exact(section, ['y', 'blocks']); require_valid(integer(section.y) && section.y < Math.ceil(value.height / 16) && section.y > previous_y, 'Invalid world voxel section order'); previous_y = section.y; require_valid(typeof section.blocks === 'string' && section.blocks.length === 10_924, 'Invalid world voxel encoded length'); const decoded = Buffer.from(section.blocks, 'base64'); require_valid(decoded.length === 8192 && decoded.toString('base64') === section.blocks, 'Invalid world voxel encoded blocks'); for (let offset = 0; offset < decoded.length; offset += 2) { require_valid(decoded.readUInt16LE(offset) < value.palette.length, 'Invalid world voxel palette index'); } } } export function validate_node_ack(method: RelayRequest['method'], value: unknown, bot_id: number, limits: RelayLimits, payload?: unknown): asserts value is ForwardAck { const workflow = WORKFLOW_METHODS.includes(method as typeof WORKFLOW_METHODS[number]); check_json_size(value, limits.max_payload_bytes, workflow ? 'workflow' : method === 'world.snapshot' || method === 'world.chunk' ? 'world' : method === 'get_data_file_list' ? 'files' : method === 'get_context_info'); require_valid((method === 'get_log_history' || method === 'tab_complete' || method === 'get_context_info' || method === 'get_data_file_list' || method === 'get_file_data' || WORLD_METHODS.includes(method as typeof WORLD_METHODS[number]) || workflow || (method !== 'bot.files' && RELAY_METHODS.includes(method as RelayMethod))) && integer(bot_id, 1), 'Invalid ACK context'); exact(value, ['status', 'data']); const data = value.data; if (value.status === 'error') { exact(data, ['code', 'message', 'retryable']); require_valid(ERROR_CODES.includes(data.code as ForwardErrorCode) && text(data.message, 1024) && typeof data.retryable === 'boolean', 'Invalid error ACK'); return; } require_valid(value.status === 'success', 'Invalid ACK status'); switch (method) { case 'world.snapshot': world_snapshot(data, payload); break; case 'world.chunk': world_voxel_chunk(data, payload); break; case 'world.unwatch': exact(data, ['released']); require_valid(data.released === true, 'Invalid world unwatch result'); break; case 'workflow.start': workflow_start_result(data); break; case 'workflow.stop': exact(data, ['stopped']); require_valid(data.stopped === true, 'Invalid workflow stop result'); break; case 'workflow.catalog': check_json_size(data, 100 * 1024, 'workflow'); exact(data, ['catalog_version', 'nodes', 'start_nodes', 'events', 'queries', 'common']); require_valid(data.catalog_version === 1 && ['nodes', 'start_nodes', 'events', 'queries'].every(key => Array.isArray(data[key]) && data[key].every(is_record)), 'Invalid workflow catalog'); require_valid(is_record(data.common) && Array.isArray(data.common.node_fields) && Array.isArray(data.common.policy_fields) && is_record(data.common.result) && is_record(data.common.edge_input) && Array.isArray(data.common.references), 'Invalid workflow catalog common metadata'); break; case 'workflow.snapshot': if (data !== null) { if (Array.isArray(data)) for (const run of data) workflow_run(run); else workflow_run(data); } break; case 'workflow.control': workflow_command(data); break; case 'workflow.command': if (data !== null) workflow_command(data); break; case 'workflow.runtime': workflow_runtime(data); break; case 'get_data_file_list': { exact(data, ['listing_id', 'expires_at', 'partial', 'children']); require_valid(is_uuid(data.listing_id) && integer(data.expires_at, 1) && typeof data.partial === 'boolean', 'Invalid file listing'); check_json_size(data.children, 512 * 1024, 'files'); let nodes = 0; function visit(children: unknown, depth: number): void { require_valid(Array.isArray(children), 'Invalid file children'); for (const child of children) { require_valid(depth <= 32 && ++nodes <= 2000, 'File tree complexity limit exceeded'); require_valid(is_record(child), 'Invalid file node'); if (child.type === 'directory') { exact(child, ['type', 'name', 'children']); require_valid(typeof child.name === 'string', 'Invalid directory name'); visit(child.children, depth + 1); } else { exact(child, ['type', 'name', 'id', 'size', 'modified_at']); require_valid(child.type === 'file' && typeof child.name === 'string' && is_uuid(child.id) && integer(child.size) && integer(child.modified_at), 'Invalid file node'); } } } visit(data.children, 1); break; } case 'get_file_data': { exact(data, ['id', 'name', 'encoding', 'offset', 'next_offset', 'total_bytes', 'eof', 'data']); require_valid(is_uuid(data.id) && typeof data.name === 'string' && data.encoding === 'base64' && integer(data.offset) && integer(data.next_offset) && integer(data.total_bytes) && data.offset <= data.next_offset && data.next_offset <= data.total_bytes && typeof data.eof === 'boolean' && data.eof === (data.next_offset === data.total_bytes) && typeof data.data === 'string' && data.data.length <= 4 * Math.ceil(262_144 / 3), 'Invalid file data'); const decoded = Buffer.from(data.data, 'base64'); require_valid(decoded.length <= 262_144 && decoded.toString('base64') === data.data && decoded.length === data.next_offset - data.offset, 'Invalid file bytes'); break; } case 'get_context_info': require_valid(Array.isArray(data) && data.length <= 256, 'Invalid context info result'); for (const info of data) { exact(info, ['name', 'i18n', 'value']); require_valid(info_name(info.name), 'Invalid info name'); validate_info_i18n(info.i18n); } break; case 'bot.snapshot': exact(data, ['bot_id', 'bot_status', 'context_exists', 'context_generation', 'stream_id', 'seq', 'captured_at']); require_valid(data.bot_id === bot_id && STATUSES.includes(data.bot_status as string) && typeof data.context_exists === 'boolean' && generation(data.context_generation) && is_uuid(data.stream_id) && integer(data.seq) && integer(data.captured_at), 'Invalid bot snapshot'); context_state(data.bot_status, data.context_exists, data.context_generation); break; case 'bot.logs': case 'get_log_history': exact(data, ['stream_id', 'seq', 'records', 'truncated']); require_valid(is_uuid(data.stream_id) && integer(data.seq) && typeof data.truncated === 'boolean', 'Invalid logs result'); log_records(data.records); if (method === 'get_log_history') require_valid((data.records as unknown[]).length <= 20, 'History exceeds 20 records'); require_valid((data.records as {seq: number}[]).every(record => record.seq <= (data.seq as number)), 'Log record exceeds response watermark'); break; case 'tab_complete': exact(data, ['matches', 'start', 'length']); require_valid(integer(data.start) && data.start <= 256 && integer(data.length) && data.length <= 256 && data.start + data.length <= 256, 'Invalid completion range'); require_valid(Array.isArray(data.matches) && data.matches.length <= 100 && data.matches.every(match => typeof match === 'string' && match.length > 0 && match.length <= 256) && new Set(data.matches).size === data.matches.length, 'Invalid completion matches'); break; case 'bot.chat': exact(data, ['accepted']); require_valid(data.accepted === true, 'Invalid chat result'); break; case 'execute_function': require_valid(typeof data === 'boolean', 'Invalid function execution result'); break; case 'bot.subscribe': case 'bot.unsubscribe': exact(data, ['events']); events(data.events); break; } } function expiry(value: unknown, limits: RelayLimits): void { require_valid(integer(limits.max_lease_ms, 1) && integer(limits.clock_skew_ms), 'Invalid lease limits'); const now = Date.now(); require_valid(integer(value, 1) && value > now - limits.clock_skew_ms && value - now <= limits.max_lease_ms + limits.clock_skew_ms, 'Invalid lease expiry'); } export function validate_open(value: unknown, forward_id: number, limits: RelayLimits): asserts value is OpenSessionCommand { check_size(value, limits.max_payload_bytes); exact(value, ['forward_id', 'forward_session_id', 'node_session_id', 'node_id', 'bot_id', 'lease_expires_at']); validate_scope(value); require_valid(value.forward_id === forward_id, 'Invalid forward audience'); expiry(value.lease_expires_at, limits); } export function validate_grant(value: unknown, limits: RelayLimits): asserts value is AuthorizeGrantCommand { check_size(value, limits.max_payload_bytes); exact(value, ['forward_session_id', 'grant_id', 'user_id', 'permissions', 'expires_at']); require_valid(is_uuid(value.forward_session_id) && is_uuid(value.grant_id) && integer(value.user_id, 1), 'Invalid grant identity'); check_permissions(value.permissions); expiry(value.expires_at, limits); }