From ff846b8e8e212dd1b0f50df51c3abe11692212cd Mon Sep 17 00:00:00 2001 From: xCyanGrizzly Date: Tue, 26 May 2026 19:52:28 +0200 Subject: [PATCH] feat(db): add scan-state columns to AccountChannelMap + TopicProgress MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three new fields on each table: - lastScannedAt — when the worker last touched this scope - lastScanFoundArchives — true if last scan had archives OR pending retryables; tracks "work might need revisit" - consecutiveEmptyScans — counter for cold-channel backoff Schema change only. Worker logic in follow-up commits. Migration is a metadata-only ALTER (NOT NULL with default) so it runs in ms even on 21k+ Package rows. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../migration.sql | 11 +++++++++++ prisma/schema.prisma | 19 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 prisma/migrations/20260526000000_channel_scan_state/migration.sql diff --git a/prisma/migrations/20260526000000_channel_scan_state/migration.sql b/prisma/migrations/20260526000000_channel_scan_state/migration.sql new file mode 100644 index 0000000..90477c3 --- /dev/null +++ b/prisma/migrations/20260526000000_channel_scan_state/migration.sql @@ -0,0 +1,11 @@ +-- AlterTable: per-channel scan-state columns +ALTER TABLE "account_channel_map" + ADD COLUMN "lastScannedAt" TIMESTAMP(3), + ADD COLUMN "lastScanFoundArchives" BOOLEAN NOT NULL DEFAULT false, + ADD COLUMN "consecutiveEmptyScans" INTEGER NOT NULL DEFAULT 0; + +-- AlterTable: per-topic scan-state columns (forum channels) +ALTER TABLE "topic_progress" + ADD COLUMN "lastScannedAt" TIMESTAMP(3), + ADD COLUMN "lastScanFoundArchives" BOOLEAN NOT NULL DEFAULT false, + ADD COLUMN "consecutiveEmptyScans" INTEGER NOT NULL DEFAULT 0; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index a3b9d94..4988f8e 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -450,6 +450,17 @@ model AccountChannelMap { channelId String role ChannelRole @default(READER) lastProcessedMessageId BigInt? + /// When this channel was last scanned (any reason, including skipped scans + /// that bumped the timestamp). Used by the recency-skip guard. + lastScannedAt DateTime? + /// True if the last scan found archives OR left retryable SkippedPackages + /// pending. Tracks "this channel has work I might need to revisit" — not + /// just "I uploaded something this cycle". + lastScanFoundArchives Boolean @default(false) + /// Number of consecutive cycles where this channel was trulyIdle (no + /// archives, no failures, no retryables). Drives the backoff that lets + /// cold channels skip cycles entirely. + consecutiveEmptyScans Int @default(0) createdAt DateTime @default(now()) account TelegramAccount @relation(fields: [accountId], references: [id], onDelete: Cascade) @@ -587,6 +598,14 @@ model TopicProgress { topicId BigInt topicName String? lastProcessedMessageId BigInt? + /// When this topic was last scanned (any reason). Used by recency-skip. + lastScannedAt DateTime? + /// True if the last scan found archives OR has retryable SkippedPackages + /// pending for this topic. See AccountChannelMap doc for details. + lastScanFoundArchives Boolean @default(false) + /// Number of consecutive cycles where this topic was trulyIdle. Drives + /// backoff for cold topics. + consecutiveEmptyScans Int @default(0) accountChannelMap AccountChannelMap @relation(fields: [accountChannelMapId], references: [id], onDelete: Cascade)