Harden batch-wide recovery identity claims

Scope remote code ownership to normalized hoster and account identities while retaining title-only recovery serialization and canonical Unicode title matching.

Mark post-upload ambiguity and duplicate identities as uncertain so retries, account fallback, and later same-title jobs fail closed instead of reporting unsafe success.

Acquire recovery title leases before hoster and global semaphores, revalidate failed-account overrides before upload, and clear claim state at batch boundaries.

Add deterministic concurrent coverage for same-code rejection, distinct-code parallel success, uncertainty propagation, semaphore fairness, account isolation, Unicode equivalence, and registry lifetime.
This commit is contained in:
Sucukdeluxe
2026-08-13 23:09:03 +02:00
parent dd14381e43
commit c78160a521
7 changed files with 640 additions and 141 deletions
+38 -16
View File
@@ -3,6 +3,7 @@ const path = require('path');
const crypto = require('crypto');
const { request } = require('undici');
const { createTransportError, sanitizeRemoteText } = require('./hoster-transport-error');
const { normalizeRecoveryTitle } = require('./hosters');
const BASE_URL = 'https://vidmoly.me';
const USER_AGENT = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36';
@@ -275,13 +276,13 @@ class VidmolyUploader {
bodyTimeout: UPLOAD_TIMEOUT
});
} catch (err) {
if (signal && signal.aborted) throw err;
throw createTransportError('Vidmoly Upload konnte nicht übertragen werden', {
const error = signal && signal.aborted ? err : createTransportError('Vidmoly Upload konnte nicht übertragen werden', {
phase: 'upload-request',
endpoint: targetUrl,
retryable: true,
transientNetwork: true
});
throw this._markRemoteCommitUncertain(error);
}
const { body, statusCode, headers } = uploadResponse;
@@ -295,17 +296,25 @@ class VidmolyUploader {
// Always drain the original body to prevent connection leak
try { await body.text(); } catch {}
if (location) {
const resultRes = await this._fetch(new URL(location, uploadUrl).href);
resultHtml = await resultRes.text();
try {
const resultRes = await this._fetch(new URL(location, uploadUrl).href);
resultHtml = await resultRes.text();
} catch (err) {
throw this._markRemoteCommitUncertain(err);
}
} else {
resultHtml = '';
}
} else {
resultHtml = await body.text();
try {
resultHtml = await body.text();
} catch (err) {
throw this._markRemoteCommitUncertain(err);
}
}
if (statusCode >= 400) {
throw createTransportError('Vidmoly Upload fehlgeschlagen', {
const error = createTransportError('Vidmoly Upload fehlgeschlagen', {
phase: 'upload-response',
endpoint: targetUrl,
httpStatus: statusCode,
@@ -314,6 +323,7 @@ class VidmolyUploader {
retryable: statusCode === 429 || statusCode >= 500,
transientNetwork: statusCode >= 500
});
throw statusCode >= 500 ? this._markRemoteCommitUncertain(error) : error;
}
// Try JSON first. The current transit server returns
@@ -355,23 +365,35 @@ class VidmolyUploader {
} catch (primaryErr) {
if (primaryErr && primaryErr.remoteIdentityClaimed === true) throw primaryErr;
if (baselineCodes) {
const fallback = await this._resolveUploadedFileFromVmApi(fileName, baselineCodes, signal);
if (fallback) return fallback;
try {
const fallback = await this._resolveUploadedFileFromVmApi(fileName, baselineCodes, signal);
if (fallback) return fallback;
} catch (err) {
throw this._markRemoteCommitUncertain(err);
}
}
if (baselineError) {
baselineError.hosterTransient = true;
throw baselineError;
throw this._markRemoteCommitUncertain(baselineError);
}
throw primaryErr;
throw this._markRemoteCommitUncertain(primaryErr);
}
}
_normalizeTitle(value) {
return String(value || '')
.toLowerCase()
.normalize('NFKD')
.replace(/\.[a-z0-9]+$/i, '')
.replace(/[^a-z0-9]+/g, '');
return normalizeRecoveryTitle(value);
}
_markRemoteCommitUncertain(error) {
if (this.recoveryClaim && typeof this.recoveryClaim.markUncertain === 'function') {
return this.recoveryClaim.markUncertain(error);
}
const uncertainError = error && typeof error === 'object'
? error
: new Error('Vidmoly Upload-Ergebnis ist unsicher');
uncertainError.remoteCommitUncertain = true;
uncertainError.hosterTransient = true;
return uncertainError;
}
_buildUrlsFromCode(fileCode, phase = 'upload-result') {
@@ -387,7 +409,7 @@ class VidmolyUploader {
hosterTransient: true
});
error.remoteIdentityClaimed = true;
throw error;
throw this._markRemoteCommitUncertain(error);
}
return {