const OPERATION_TIMING_SCHEMA_VERSION = 'ltjt-operation-timing-v1'; const CODE_PATTERN = /^[a-z][a-z0-9_.:-]{0,79}$/i; const MAX_STAGES = 64; const MAX_DETAILS = 48; const MAX_COUNTERS = 24; const MAX_DURATION_MS = 7 * 24 * 60 * 60 * 1000; const ALLOWED_STAGE_CODES = new Set([ 'auto_executor', 'automation_disabled', 'batch_fallback', 'batch_fallback_error', 'browser_dry_run', 'browser_execution', 'cancelled', 'capability_check', 'erp_keepalive_degraded', 'erp_resolution', 'execution_gate', 'execution_timeout', 'extension_error', 'lifecycle_business_blocked', 'lifecycle_completed', 'lifecycle_live', 'lifecycle_live_submit', 'lifecycle_preflight', 'lifecycle_reconciliation', 'lifecycle_route_preparation', 'live_submit', 'open_order_form', 'open_team_batch_form', 'post_resolution_execution_gate', 'preflight', 'queued', 'reconciliation', 'unknown_stage', 'validation', 'verification', 'wait_order_form', 'wait_team_batch_form' ]); const ALLOWED_STATUSES = new Set([ 'accepted', 'blocked', 'cancelled', 'completed', 'dry_run', 'execution_uncertain', 'failed', 'mixed', 'reconciliation_pending', 'running', 'saved_unverified', 'skipped', 'unknown' ]); const ALLOWED_ACTION_CODES = new Set([ 'cleanup_completed_lifecycle_dialog', 'create_split_child_live', 'create_split_parent_live', 'export_confirmation_sources', 'inspect_operation_context', 'live_submit_approved', 'live_submit_lifecycle_operation', 'live_submit_team_batch_approved', 'open_order_form', 'open_team_batch_form', 'ping_order_frame', 'ping_team_batch_frame', 'preflight_lifecycle_operation', 'preflight_raw_instruction', 'preflight_team_batch_native', 'prepare_lifecycle_operation', 'resolve_lifecycle_operation', 'return_to_order_list', 'unknown_action', 'verify_lifecycle_operation', 'verify_order_marker', 'verify_team_batch_receipts' ]); const ALLOWED_DETAIL_CODES = new Set([ 'executor.erp_keepalive_setup', 'preflight_customer_restore', 'preflight_lookup.customer', 'preflight_lookup.parallel_wall', 'preflight_lookup.product', 'preflight_lookup.route', 'preflight_lookup.staff', 'preflight_product_linkage', 'preflight_submit_intercept', 'result_publish.prepare', 'storage_prune.local_history', 'storage_queue.accept_execution', 'storage_queue.execution_state', 'storage_queue.result_state', 'storage_read.accept_execution', 'storage_read.automation_setting', 'storage_read.execution_record', 'storage_read.execution_state', 'storage_read.result_state', 'storage_read.task_payload', 'storage_write.accept_execution', 'storage_write.execution_state', 'storage_write.last_frame', 'storage_write.last_live_submit', 'storage_write.last_preflight', 'storage_write.last_return_to_list', 'storage_write.last_verification', 'storage_write.result_state', 'tab_lookup.cache_hit', 'tab_lookup.create', 'tab_lookup.find_or_open', 'tab_lookup.host_permission', 'tab_lookup.query', 'tab_lookup.wait_ready' ]); const ALLOWED_COUNTERS = new Set([ 'cache_hit_count', 'call_count', 'created_count', 'execution_count', 'failure_count', 'frame_count', 'protected_root_count', 'quiet_wait_ms', 'removed_execution_count', 'removed_result_count', 'removed_root_count', 'removed_task_count', 'removed_timing_count', 'request_count', 'result_count', 'retained_root_count', 'sample_count', 'stable_sample_count', 'submit_count', 'tab_count', 'task_count', 'timing_count' ]); function record(value: unknown): Record | null { return value && typeof value === 'object' && !Array.isArray(value) ? value as Record : null; } function code(value: unknown, fallback = ''): string { const normalized = String(value || '').trim(); return CODE_PATTERN.test(normalized) ? normalized : fallback; } function statusCode(value: unknown, fallback = 'unknown'): string { const normalized = code(value); return ALLOWED_STATUSES.has(normalized) ? normalized : fallback; } function stageCode(value: unknown): string { const normalized = code(value); return ALLOWED_STAGE_CODES.has(normalized) ? normalized : ''; } function stepCode(value: unknown): string { const normalized = code(value); if (normalized === 'script_injection') return normalized; if (ALLOWED_DETAIL_CODES.has(normalized)) return normalized; const matched = normalized.match(/^(action_roundtrip|action_runtime|network_wait)\.(.+)$/u); return matched && ALLOWED_ACTION_CODES.has(matched[2]) ? normalized : ''; } function duration(value: unknown): number { const number = Number(value); if (!Number.isFinite(number) || number <= 0) return 0; return Math.min(MAX_DURATION_MS, Math.round(number)); } function count(value: unknown): number { const number = Number(value); if (!Number.isFinite(number) || number <= 0) return 0; return Math.min(Number.MAX_SAFE_INTEGER, Math.round(number)); } function sanitizeCounters(value: unknown): Record | null { const input = record(value); if (!input) return null; const output: Record = {}; for (const [rawKey, rawValue] of Object.entries(input).slice(0, MAX_COUNTERS)) { const key = code(rawKey); const number = Number(rawValue); if (!ALLOWED_COUNTERS.has(key) || !Number.isFinite(number) || number < 0) continue; output[key] = Math.min(Number.MAX_SAFE_INTEGER, Math.round(number)); } return Object.keys(output).length ? output : null; } function sanitizeDetail(value: unknown): Record | null { const input = record(value); if (!input) return null; const step = stepCode(input.step); if (!step) return null; const counters = sanitizeCounters(input.counters); return { step, status: statusCode(input.status), duration_ms: duration(input.duration_ms), ...(counters ? { counters } : {}) }; } function sanitizeStage(value: unknown): Record | null { const input = record(value); if (!input) return null; const stage = stageCode(input.stage); if (!stage) return null; const details = (Array.isArray(input.details) ? input.details : []) .map(sanitizeDetail) .filter((detail): detail is Record => Boolean(detail)) .slice(0, MAX_DETAILS); const truncatedDetailCount = count(input.truncated_detail_count); return { stage, status: statusCode(input.status), duration_ms: duration(input.duration_ms), ...(details.length ? { details } : {}), ...(truncatedDetailCount ? { truncated_detail_count: truncatedDetailCount } : {}) }; } export function sanitizeOperationTiming(value: unknown): Record | null { const input = record(value); if (!input || input.schema_version !== OPERATION_TIMING_SCHEMA_VERSION) return null; const stages = (Array.isArray(input.stages) ? input.stages : []) .map(sanitizeStage) .filter((stage): stage is Record => Boolean(stage)) .slice(0, MAX_STAGES); const truncatedStageCount = count(input.truncated_stage_count); return { schema_version: OPERATION_TIMING_SCHEMA_VERSION, status: statusCode(input.status), total_ms: duration(input.total_ms), stage_count: Math.max(stages.length, count(input.stage_count)), stages, ...(truncatedStageCount ? { truncated_stage_count: truncatedStageCount } : {}) }; } export { OPERATION_TIMING_SCHEMA_VERSION };