From 9a06130c5bb1bbaecbfc79420d6df4a79025c41d Mon Sep 17 00:00:00 2001 From: xCyanGrizzly Date: Thu, 23 Jul 2026 13:23:15 +0200 Subject: [PATCH] feat(worker): provenance-backfill orchestrator Implements tryProvenanceBackfill() per Task 6 of the provenance-backfill plan: looks up a placeholder candidate by fileName+fileSize, confirms ZIP candidates via a ranged central-directory CRC32 fingerprint, and falls back to name+size confidence for RAR/7z/failed listings. Co-Authored-By: Claude Opus 4.8 (1M context) --- worker/src/provenance-backfill.ts | 98 +++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 worker/src/provenance-backfill.ts diff --git a/worker/src/provenance-backfill.ts b/worker/src/provenance-backfill.ts new file mode 100644 index 0000000..da17dd6 --- /dev/null +++ b/worker/src/provenance-backfill.ts @@ -0,0 +1,98 @@ +import { childLogger } from "./util/logger.js"; +import { downloadFileRange } from "./tdlib/range-download.js"; +import { parseZipCentralDirectoryFromTail, MIN_ZIP_TAIL_BYTES } from "./archive/central-directory.js"; +import { fingerprintsMatch } from "./archive/fingerprint.js"; +import { + findPlaceholderCandidate, + getPackageFileCrcs, + backfillProvenance, +} from "./db/queries.js"; +import type { FileEntry } from "./archive/zip-reader.js"; +import type { Client } from "tdl"; + +const log = childLogger("provenance-backfill"); + +export interface BackfillArgs { + client: Client; + destChannelId: string; + scannedSourceChannelId: string; + fileName: string; + fileSize: bigint; + archiveType: string; + sourceMessageId: bigint; + sourceTopicId: bigint | null; + sourceCaption: string | null; + remoteUniqueId: string | null; + creator: string | null; + scannedFileId: string; + previewData?: Buffer | null; + previewMsgId?: bigint | null; +} + +async function readScannedZipListing( + client: Client, + fileId: string, + fileSize: bigint, +): Promise { + const total = Number(fileSize); + for (const tailBytes of [MIN_ZIP_TAIL_BYTES, MIN_ZIP_TAIL_BYTES * 4]) { + const start = Math.max(0, total - tailBytes); + try { + const tail = await downloadFileRange(client, fileId, start, Math.min(tailBytes, total), fileSize); + return parseZipCentralDirectoryFromTail(tail, start); + } catch (err) { + if (err instanceof RangeError) continue; // try a larger tail + log.warn({ err, fileId }, "ranged ZIP listing failed"); + return null; + } + } + return null; +} + +export async function tryProvenanceBackfill( + args: BackfillArgs, +): Promise<{ backfilled: boolean; confidence?: "fingerprint" | "name-size" }> { + const candidate = await findPlaceholderCandidate(args.destChannelId, args.fileName, args.fileSize); + if (!candidate) return { backfilled: false }; + + let entries: FileEntry[] | null = null; + let confidence: "fingerprint" | "name-size" = "name-size"; + + if (args.archiveType === "ZIP") { + entries = await readScannedZipListing(args.client, args.scannedFileId, args.fileSize); + if (entries) { + const candidateCrcs = await getPackageFileCrcs(candidate.id); + const candidateEntries: FileEntry[] = candidateCrcs.map((crc) => ({ + path: "", fileName: "", extension: null, compressedSize: 0n, uncompressedSize: 0n, crc32: crc, + })); + if (fingerprintsMatch(entries, candidateEntries)) { + confidence = "fingerprint"; + } else { + // Fingerprint mismatch: NOT the same content despite name+size. Do not backfill. + log.info({ candidateId: candidate.id, fileName: args.fileName }, "fingerprint mismatch — not backfilling"); + return { backfilled: false }; + } + } + } + + const ok = await backfillProvenance({ + packageId: candidate.id, + destChannelId: args.destChannelId, + sourceChannelId: args.scannedSourceChannelId, + sourceMessageId: args.sourceMessageId, + sourceTopicId: args.sourceTopicId, + sourceCaption: args.sourceCaption, + remoteUniqueId: args.remoteUniqueId, + creator: args.creator, + entries: candidate.fileCount === 0 && entries ? entries : undefined, + previewData: args.previewData ?? undefined, + previewMsgId: args.previewMsgId ?? undefined, + }); + + if (!ok) return { backfilled: false }; + log.info( + { candidateId: candidate.id, fileName: args.fileName, confidence, source: args.scannedSourceChannelId }, + "provenance backfilled", + ); + return { backfilled: true, confidence }; +}