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
+34 -15
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://voe.sx';
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';
@@ -352,22 +353,27 @@ class VoeUploader {
bodyTimeout: UPLOAD_TIMEOUT
});
} catch (err) {
if (signal && signal.aborted) throw err;
throw createTransportError('VOE Upload konnte nicht übertragen werden', {
const error = signal && signal.aborted ? err : createTransportError('VOE Upload konnte nicht übertragen werden', {
phase: 'upload-request',
endpoint: uploadServer,
retryable: true,
transientNetwork: true
});
throw this._markRemoteCommitUncertain(error);
}
const { body, headers, statusCode } = uploadResponse;
this._parseCookiesFromHeaders(headers || {});
const rawBody = await body.text();
let rawBody;
try {
rawBody = await body.text();
} catch (err) {
throw this._markRemoteCommitUncertain(err);
}
if (statusCode < 200 || statusCode >= 300) {
throw createTransportError('VOE Upload fehlgeschlagen', {
const error = createTransportError('VOE Upload fehlgeschlagen', {
phase: 'upload-response',
endpoint: uploadServer,
httpStatus: statusCode,
@@ -376,6 +382,7 @@ class VoeUploader {
retryable: statusCode === 429 || statusCode >= 500,
transientNetwork: statusCode >= 500
});
throw statusCode >= 500 ? this._markRemoteCommitUncertain(error) : error;
}
// Try JSON response
@@ -407,23 +414,27 @@ class VoeUploader {
// Fallback: poll the file list to find the newly uploaded file
if (baselineCodes) {
const result = await this._resolveUploadedFile(fileName, baselineCodes, signal);
if (result) return result;
try {
const result = await this._resolveUploadedFile(fileName, baselineCodes, signal);
if (result) return result;
} catch (err) {
throw this._markRemoteCommitUncertain(err);
}
}
if (baselineError) {
baselineError.hosterTransient = true;
throw baselineError;
throw this._markRemoteCommitUncertain(baselineError);
}
throw createTransportError('VOE Upload: Kein file_code in der Antwort gefunden', {
throw this._markRemoteCommitUncertain(createTransportError('VOE Upload: Kein file_code in der Antwort gefunden', {
phase: 'upload-result',
endpoint: uploadServer,
contentType: headers && headers['content-type'],
body: rawBody,
hosterTransient: true,
retryable: true
});
}));
}
async _resolveUploadedFile(fileName, baselineCodes, signal) {
@@ -478,11 +489,19 @@ class VoeUploader {
}
_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('VOE Upload-Ergebnis ist unsicher');
uncertainError.remoteCommitUncertain = true;
uncertainError.hosterTransient = true;
return uncertainError;
}
_buildUrls(fileCode, phase = 'upload-result') {
@@ -498,7 +517,7 @@ class VoeUploader {
hosterTransient: true
});
error.remoteIdentityClaimed = true;
throw error;
throw this._markRemoteCommitUncertain(error);
}
return {
download_url: `${BASE_URL}/${code}`,