(function initOperationTiming(root, factory) { const api = factory(); if (typeof module === 'object' && module.exports) module.exports = api; root.LTJTOperationTiming = api; })(typeof self !== 'undefined' ? self : globalThis, function createOperationTiming() { 'use strict'; const SCHEMA_VERSION = 'ltjt-operation-timing-v1'; const MAX_STAGES = 64; const MAX_DETAILS_PER_STAGE = 48; const MAX_COUNTERS = 24; const MAX_DURATION_MS = 7 * 24 * 60 * 60 * 1000; const CODE_PATTERN = /^[a-z][a-z0-9_.:-]{0,79}$/i; const TERMINAL_STATUSES = new Set([ 'completed', 'dry_run', 'blocked', 'failed', 'cancelled', 'saved_unverified', 'execution_uncertain', 'reconciliation_pending' ]); function safeCode(value, fallback = '') { const normalized = String(value || '').trim(); return CODE_PATTERN.test(normalized) ? normalized : fallback; } function safeDuration(value) { const number = Number(value); if (!Number.isFinite(number) || number <= 0) return 0; return Math.min(MAX_DURATION_MS, Math.round(number)); } function safeTimestamp(value, fallback) { const number = Number(value); return Number.isFinite(number) && number > 0 ? number : fallback; } function sanitizeCounters(value) { if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined; const output = {}; for (const [key, entry] of Object.entries(value).slice(0, MAX_COUNTERS)) { const safeKey = safeCode(key); const number = Number(entry); if (!safeKey || !Number.isFinite(number) || number < 0) continue; output[safeKey] = Math.min(Number.MAX_SAFE_INTEGER, Math.round(number)); } return Object.keys(output).length ? output : undefined; } function sanitizeDetail(value) { if (!value || typeof value !== 'object' || Array.isArray(value)) return null; const step = safeCode(value.step); if (!step) return null; const counters = sanitizeCounters(value.counters); return { step, status: safeCode(value.status, 'completed'), duration_ms: safeDuration(value.duration_ms), ...(counters ? { counters } : {}) }; } function mergeDetails(existing = [], incoming = []) { const output = []; let truncated = 0; const add = (candidate) => { const detail = sanitizeDetail(candidate); if (!detail) return; const matched = output.find((item) => item.step === detail.step); if (matched) { matched.duration_ms = safeDuration(matched.duration_ms + detail.duration_ms); if (matched.status !== detail.status) matched.status = 'mixed'; if (detail.counters) { matched.counters = matched.counters || {}; for (const [key, value] of Object.entries(detail.counters)) { matched.counters[key] = Math.min( Number.MAX_SAFE_INTEGER, Math.round(Number(matched.counters[key] || 0) + Number(value || 0)) ); } } return; } if (output.length >= MAX_DETAILS_PER_STAGE) { truncated += 1; return; } output.push(detail); }; for (const detail of Array.isArray(existing) ? existing : []) add(detail); for (const detail of Array.isArray(incoming) ? incoming : []) add(detail); return { details: output, truncated }; } function sanitizeStage(value) { if (!value || typeof value !== 'object' || Array.isArray(value)) return null; const stage = safeCode(value.stage); if (!stage) return null; const merged = mergeDetails([], value.details); const truncatedDetailCount = Math.max( 0, Math.round(Number(value.truncated_detail_count || 0)) + merged.truncated ); return { stage, status: safeCode(value.status, 'unknown'), duration_ms: safeDuration(value.duration_ms), ...(merged.details.length ? { details: merged.details } : {}), ...(truncatedDetailCount ? { truncated_detail_count: truncatedDetailCount } : {}) }; } function sanitizeSnapshot(value) { if (!value || typeof value !== 'object' || Array.isArray(value)) return null; if (value.schema_version !== SCHEMA_VERSION) return null; const stages = (Array.isArray(value.stages) ? value.stages : []) .map(sanitizeStage) .filter(Boolean) .slice(0, MAX_STAGES); const truncatedStageCount = Math.max(0, Math.round(Number(value.truncated_stage_count || 0))); return { schema_version: SCHEMA_VERSION, status: safeCode(value.status, 'unknown'), total_ms: safeDuration(value.total_ms), stage_count: Math.max(stages.length, Math.round(Number(value.stage_count || stages.length))), stages, ...(truncatedStageCount ? { truncated_stage_count: truncatedStageCount } : {}) }; } function createState({ now_ms, started_at_ms, stage = 'auto_executor', status = 'accepted' } = {}) { const now = safeTimestamp(now_ms, Date.now()); const startedAt = Math.min(now, safeTimestamp(started_at_ms, now)); return { schema_version: SCHEMA_VERSION, base_total_ms: 0, started_at_ms: startedAt, active_stage: safeCode(stage, 'auto_executor'), active_stage_started_at_ms: startedAt, active_status: safeCode(status, 'accepted'), active_details: [], active_truncated_detail_count: 0, stages: [], truncated_stage_count: 0, final_status: '' }; } function stateFromSnapshot(snapshotValue, update, now) { const snapshot = sanitizeSnapshot(snapshotValue); if (!snapshot) return null; const updatedAt = safeTimestamp(update.updated_at_ms, now); const gap = Math.max(0, now - updatedAt); const terminal = TERMINAL_STATUSES.has(snapshot.status); if (terminal) { return { schema_version: SCHEMA_VERSION, base_total_ms: snapshot.total_ms, started_at_ms: now, active_stage: '', active_stage_started_at_ms: now, active_status: '', active_details: [], active_truncated_detail_count: 0, stages: snapshot.stages, truncated_stage_count: snapshot.truncated_stage_count || 0, final_status: snapshot.status }; } const active = snapshot.stages[snapshot.stages.length - 1] || null; const activeDuration = safeDuration(active?.duration_ms) + gap; return { schema_version: SCHEMA_VERSION, base_total_ms: 0, started_at_ms: Math.max(0, now - snapshot.total_ms - gap), active_stage: safeCode(active?.stage, safeCode(update.stage, 'auto_executor')), active_stage_started_at_ms: Math.max(0, now - activeDuration), active_status: safeCode(active?.status, safeCode(update.status, 'running')), active_details: Array.isArray(active?.details) ? active.details : [], active_truncated_detail_count: Math.max(0, Math.round(Number(active?.truncated_detail_count || 0))), stages: active ? snapshot.stages.slice(0, -1) : snapshot.stages, truncated_stage_count: snapshot.truncated_stage_count || 0, final_status: '' }; } function sanitizeState(value, update, now) { if (!value || typeof value !== 'object' || value.schema_version !== SCHEMA_VERSION) { return stateFromSnapshot(update.existing_timing, update, now) || createState({ now_ms: now, started_at_ms: update.started_at_ms, stage: update.stage, status: update.status }); } const stages = (Array.isArray(value.stages) ? value.stages : []) .map(sanitizeStage) .filter(Boolean) .slice(-MAX_STAGES); const merged = mergeDetails([], value.active_details); return { schema_version: SCHEMA_VERSION, base_total_ms: safeDuration(value.base_total_ms), started_at_ms: Math.min(now, safeTimestamp(value.started_at_ms, now)), active_stage: safeCode(value.active_stage), active_stage_started_at_ms: Math.min(now, safeTimestamp(value.active_stage_started_at_ms, now)), active_status: safeCode(value.active_status, 'running'), active_details: merged.details, active_truncated_detail_count: Math.max( 0, Math.round(Number(value.active_truncated_detail_count || 0)) + merged.truncated ), stages, truncated_stage_count: Math.max(0, Math.round(Number(value.truncated_stage_count || 0))), final_status: safeCode(value.final_status) }; } function appendClosedStage(state, stage) { // Reserve one public slot for the currently active stage so the newest // work is never hidden by the bounded history. if (state.stages.length >= MAX_STAGES - 1) { state.stages.shift(); state.truncated_stage_count += 1; } state.stages.push(stage); } function closeActiveStage(state, now, status) { if (!state.active_stage) return; appendClosedStage(state, { stage: state.active_stage, status: safeCode(status, state.active_status || 'completed'), duration_ms: safeDuration(now - state.active_stage_started_at_ms), ...(state.active_details.length ? { details: state.active_details } : {}), ...(state.active_truncated_detail_count ? { truncated_detail_count: state.active_truncated_detail_count } : {}) }); state.active_stage = ''; state.active_stage_started_at_ms = now; state.active_status = ''; state.active_details = []; state.active_truncated_detail_count = 0; } function openStage(state, stage, status, now) { state.active_stage = safeCode(stage, 'unknown_stage'); state.active_stage_started_at_ms = now; state.active_status = safeCode(status, 'running'); state.active_details = []; state.active_truncated_detail_count = 0; state.final_status = ''; } function snapshot(stateValue, nowValue = Date.now()) { const now = safeTimestamp(nowValue, Date.now()); const state = sanitizeState(stateValue, {}, now); const stages = state.stages.map((stage) => ({ ...stage })); if (state.active_stage) { stages.push({ stage: state.active_stage, status: state.active_status || 'running', duration_ms: safeDuration(now - state.active_stage_started_at_ms), ...(state.active_details.length ? { details: state.active_details } : {}), ...(state.active_truncated_detail_count ? { truncated_detail_count: state.active_truncated_detail_count } : {}) }); } const stageCount = stages.length + state.truncated_stage_count; return sanitizeSnapshot({ schema_version: SCHEMA_VERSION, status: state.active_stage ? (state.active_status || 'running') : (state.final_status || 'unknown'), total_ms: state.base_total_ms + Math.max(0, now - state.started_at_ms), stage_count: stageCount, stages, truncated_stage_count: state.truncated_stage_count }); } function advance(stateValue, update = {}) { const now = safeTimestamp(update.now_ms, Date.now()); const state = sanitizeState(stateValue, update, now); const nextStage = safeCode(update.stage, state.active_stage || 'unknown_stage'); const nextStatus = safeCode(update.status, state.active_status || 'running'); const incoming = mergeDetails([], update.details); if (state.active_stage) { const merged = mergeDetails(state.active_details, incoming.details); state.active_details = merged.details; state.active_truncated_detail_count += incoming.truncated + merged.truncated; if (state.active_stage !== nextStage) { closeActiveStage(state, now, 'completed'); openStage(state, nextStage, nextStatus, now); } } else { openStage(state, nextStage, nextStatus, now); const merged = mergeDetails([], incoming.details); state.active_details = merged.details; state.active_truncated_detail_count += incoming.truncated + merged.truncated; } state.active_status = nextStatus; if (TERMINAL_STATUSES.has(nextStatus)) { closeActiveStage(state, now, nextStatus); state.final_status = nextStatus; } return { state, snapshot: snapshot(state, now), terminal: TERMINAL_STATUSES.has(nextStatus) }; } return { SCHEMA_VERSION, MAX_STAGES, MAX_DETAILS_PER_STAGE, TERMINAL_STATUSES, safeCode, sanitizeCounters, sanitizeDetail, mergeDetails, sanitizeSnapshot, createState, snapshot, advance }; });