6.1 KiB
6.1 KiB
2026-06-29 01 — Scheduled Remote Conflict Detection
Status: Shipped Scope: Backend, frontend Settings UI, ops env, docs
What we set out to do
Resolve the "Dead Code — YouTubeSyncService.detectConflict()" backlog entry. Before this session, the method was fully implemented but never called; conflict handling as a user-facing flow was effectively broken:
- Detection only happened on manual "Refresh from YouTube" per video.
- Resolution either meant running a full channel refresh (clobbers every video's local fields) or re-pushing local (silently overwrites remote). Neither offered a per-video accept/reject choice and neither showed a diff.
Decision: wire detectConflict() up as a scheduled sweep AND build a proper per-video accept-remote path. Direction "push local" reuses the existing POST /videos/:id/sync.
Design decisions (locked in during the session)
| Question | Decision |
|---|---|
| Granularity | Per-video accept/reject (v1). Field-level merge deferred. |
| Settings location | Per-team, with cadence global via env var |
| Skip already-conflicted videos in the sweep? | Yes — no point re-detecting an unresolved conflict |
Add a POST /videos/:id/force-local endpoint? |
No — reuse existing POST /videos/:id/sync |
| Scheduler mechanism | BullMQ repeatable job (no new dependency) |
| Selection strategy | Stalest-first per team, filtered by lastSyncedAt < now - minAgeDays, remoteConflict = false, youtubeDeletedAt = null |
What changed
Backend
- Schema — new migration
20260629000000_add_conflict_detection_settings:Team.conflictDetectionEnabled Boolean @default(false)Team.conflictDetectionBatchSize Int @default(50)Team.conflictDetectionMinAgeDays Int @default(7)Video.pendingRemoteSnapshot Json?Video.pendingRemoteDescription String?
YouTubeSyncService.detectConflict()(backend/src/modules/youtube-sync/youtube-sync.service.ts) — on hash mismatch now persistspendingRemoteSnapshot(mirrorsyoutubeSnapshotshape) andpendingRemoteDescription. On hash match with a stale flag, self-heals (clears flag + pending fields). No more dead code.- New queue
CONFLICT_DETECTIONinbackend/src/queues/queues.constants.ts. - New processor
backend/src/queues/processors/conflict-detection.processor.ts— iterates enabled teams, stalest-first selection, per-call quota guard, per-team batch cap. Logs summary. Stops the whole sweep on quota exhaustion. - New scheduler
backend/src/queues/schedulers/conflict-detection.scheduler.ts—OnModuleInitregisters a BullMQ repeatable driven byCONFLICT_DETECTION_CRON(default0 3 * * *), gated byCONFLICT_DETECTION_ENABLED. Wipes stale repeatables on boot so config changes take effect. - Wired both into
backend/src/worker.module.ts. - New endpoint
POST /videos/:id/accept-remote(EDITOR role) inbackend/src/modules/videos/videos.controller.ts+.service.ts. Zero YouTube API calls — promotes storedpendingRemoteSnapshotinto live columns andyoutubeSnapshot, setsrenderedDescription = youtubeDescription = pendingRemoteDescription, recomputeslastSyncedHash, setslastSyncedAt = now, clears pending + flag. Emits audit log entryaction: 'accept-remote'. - Team settings — three new fields added to
GET/PATCH /teams/:teamId/settingswith bounds validation (batch 1–500, min-age ≥ 0).
Frontend
TeamSettingsinterface (frontend/src/lib/api.ts) extended with the three new fields.- New "Remote Conflict Detection" section on
/settings: Enable toggle, "Videos per run" (number, 1–500), "Only check videos older than (days)" (number, ≥0). Admin-only edit. Own Save button (kept separate from the schedule section to avoid mixed-scope saves).
Ops / env
CONFLICT_DETECTION_ENABLEDandCONFLICT_DETECTION_CRONadded to:backend/.env(locally set totrue/ default cron)backend/.env.example(defaultfalse)infrastructure/.env.exampleinfrastructure/docker-compose.ymlworker service (uses:-defaults)
Documentation
- Backlog entries removed from
06 - Backlog/01 - Technical Debt and Future Work.md:- "Dead Code —
YouTubeSyncService.detectConflict()" - "Improvements Worth Considering — Automatic Remote Conflict Detection"
- "Dead Code —
- Docs brought back in sync (11 findings across 8 files identified by an audit agent, all applied):
- Rewrote the two aggressively-wrong sections (Gotchas
remoteConflict Is Not Automatically Detected, SchemaremoteConflictparagraph) which literally claimed the opposite of the new behavior. - Filled content gaps in
04 - Database Schema,05 - Queue System,13 - Team Settings,02 - Videos API,09 - Teams API,02 - Environment Variables,05 - Deployment and Operations.
- Rewrote the two aggressively-wrong sections (Gotchas
- Vault gained a new
07 - Daily Notes/section (this note is the first entry).
Verification
- Backend
tsc --noEmitclean afterprisma generate. - Frontend
tsc --noEmitclean for the touched files (settings/page.tsx,api.ts); pre-existing errors elsewhere untouched. - Migration file present but not yet applied on any prod DB — user ran
prisma migratelocally.
Follow-ups worth flagging
- UI for the conflict itself — the video editor should show a per-field diff between the current local state and
pendingRemoteSnapshotwhenremoteConflict === true, with "Accept remote" and "Keep local" buttons. Backend is ready; frontend diff view doesn't exist yet. - Field-level merge — declined for v1; if requested later, the storage layer already supports it (fields are individually addressable in
pendingRemoteSnapshot). - Multi-team frequency — if teams later want different cadences,
frequencyneeds to move from env-var to a per-team column and the scheduler must manage a repeatable-per-team.
Related
- 04 - Database Schema — new Team + Video columns
- 05 - Queue System — processor details, quota model
- 13 - Team Settings — per-team knobs, validation
- 02 - Videos API —
accept-remoteendpoint - 04 - Gotchas — resolution options and self-heal
- 02 - Environment Variables —
CONFLICT_DETECTION_*