fix: harden automation reconciliation and pause lifecycle

Keep full folder scans recoverable under duplicate protection and classify every discovered file once before automatic admission.

Separate manual previews from automatic capacity limits, serialize renderer intake, normalize automation counters and intervals, and make Main authoritative for runtime timestamps.

Enforce persistent pause across startup, import, close recovery, resume failures, and reconciliation while preserving read-only paused scans and exactly one activation reconciliation.

Restore failed asynchronous rotation chunks, complete automation localization and unlimited queue accessibility, and tighten hidden integration cleanup coverage.
This commit is contained in:
Sucukdeluxe
2026-08-26 15:47:31 +02:00
parent ca2d1f8fda
commit afd3b70cf3
12 changed files with 812 additions and 157 deletions
+16 -7
View File
@@ -25,13 +25,22 @@
return Math.max(1, Math.floor(number));
}
function normalizeReconcileInterval(value) {
return typeof value === 'number' && Number.isFinite(value) && allowedIntervals.has(value) ? value : 5;
}
function normalizeCounter(value) {
const number = Number(value);
if (!Number.isFinite(number) || number <= 0) return 0;
return Math.min(Number.MAX_SAFE_INTEGER, Math.floor(number));
}
function normalizeAutomationSettings(value = {}) {
const settings = asObject(value);
const queueLimitJobs = normalizeQueueLimit(settings.queueLimitJobs);
const rawInterval = Number(settings.reconcileIntervalMinutes);
return {
queueLimitJobs,
reconcileIntervalMinutes: allowedIntervals.has(rawInterval) ? rawInterval : 5,
reconcileIntervalMinutes: normalizeReconcileInterval(settings.reconcileIntervalMinutes),
paused: settings.paused === true,
pausedAt: settings.paused === true && Number.isFinite(Number(settings.pausedAt)) ? Number(settings.pausedAt) : null
};
@@ -100,10 +109,10 @@
...emptyTelemetry(dateKey),
...telemetry,
dateKey,
detected: Math.max(0, Number(telemetry.detected) || 0),
queued: Math.max(0, Number(telemetry.queued) || 0),
skipped: Math.max(0, Number(telemetry.skipped) || 0),
deferred: Math.max(0, Number(telemetry.deferred) || 0)
detected: normalizeCounter(telemetry.detected),
queued: normalizeCounter(telemetry.queued),
skipped: normalizeCounter(telemetry.skipped),
deferred: normalizeCounter(telemetry.deferred)
};
}
@@ -111,7 +120,7 @@
const changes = asObject(delta);
const next = rollDailyTelemetry(value, nowMs);
for (const key of ['detected', 'queued', 'skipped', 'deferred']) {
next[key] += Math.max(0, Number(changes[key]) || 0);
next[key] = Math.min(Number.MAX_SAFE_INTEGER, next[key] + normalizeCounter(changes[key]));
}
if (changes.lastDetectedName) {
next.lastDetectedName = String(changes.lastDetectedName);