Skip to content

Fix/pipeline robustness - #32

Merged
flvvius merged 3 commits into
mainfrom
fix/pipeline_robustness
May 16, 2026
Merged

Fix/pipeline robustness#32
flvvius merged 3 commits into
mainfrom
fix/pipeline_robustness

Conversation

@flvvius

@flvvius flvvius commented May 16, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

Release Notes

  • New Features

    • Redesigned admin pipeline dashboard with Freshness SLO metrics and Pipeline Doctor diagnostics for monitoring feed health.
    • Feed "Latest" sorting now prioritizes recently updated content over first publication date.
  • Documentation

    • Added pipeline operations runbook for managing production feed incidents.
  • Chores

    • Made share asset generation configurable.
    • Adjusted background job schedules for improved performance.

Review Change Stack

flvvius added 2 commits May 16, 2026 12:09
…e, queue ages, expired leases, almost-publishable events, enrichment failure rate and slowed down the pipeline a bit
@vercel

vercel Bot commented May 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
news Error Error May 16, 2026 0:04am
news-web Ready Ready Preview, Comment May 16, 2026 0:04am

@coderabbitai

coderabbitai Bot commented May 16, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@flvvius has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 47 minutes and 41 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3c81f5e4-b00b-4149-bcdd-2816de9dfe39

📥 Commits

Reviewing files that changed from the base of the PR and between c9ec631 and 2e65faa.

📒 Files selected for processing (7)
  • apps/web/src/routes/admin.pipeline.tsx
  • docs/pipeline-operations-runbook.md
  • packages/backend/convex/events.ts
  • packages/backend/convex/lib/publicEventPreviews.ts
  • packages/backend/convex/pipeline.ts
  • packages/backend/convex/shareAssets.ts
  • packages/backend/convex/shareAssetsNode.ts

Walkthrough

This PR adds comprehensive pipeline observability and monitoring infrastructure by introducing a new "Pipeline Doctor" admin query, restructuring run logging across ingestion/enrichment/clustering, switching feed "recent" sort to use lastUpdatedAt, implementing stale-processing event cleanup, adding share asset generation gating, and updating the admin dashboard with freshness metrics, published breakdown, and operational diagnostics.

Changes

Pipeline Observability and Ordering System

Layer / File(s) Summary
Schema and Config Infrastructure
packages/backend/convex/schema.ts, packages/backend/convex/config.ts
Extended publicEventPreviews with optional createdAt timestamp and index, broadened articles.archivedReason to allow `"stale_singleton"
Feed Recent/Latest Ordering Switch
packages/backend/convex/events.ts, packages/backend/convex/enrichment.ts, packages/backend/convex/lib/publicEventPreviews.ts
Changed "recent" feed sort to use lastUpdatedAt instead of firstPublishedAt in rankedPayload scoring and getFeedCandidates index selection; updated claimUnprocessedArticles to use by_status_published index with descending order; and ensured consistent timestamp capture in syncPublicEventPreview by capturing now once and reusing it for updatedAt and createdAt.
Pipeline Run Logging Integration
packages/backend/convex/ingestion.ts, packages/backend/convex/enrichmentNode.ts, packages/backend/convex/clustering.ts
Added structured internal.pipeline.insertRunLog calls throughout pipeline execution: ingestion now logs skipped (lock_held, pipeline_paused) and final (ok/degraded/error) outcomes with feed/insert/retry counters; enrichment now logs paused, budget-exhausted, no-articles, and execution outcomes with claimed/enriched/failed/token counters; clustering now conditionally schedules merge and recluster jobs via ctx.scheduler.runAfter with configured delays when articles clustered or new events created.
Pipeline Doctor Query and Freshness Metrics
packages/backend/convex/pipeline.ts
Introduced FRESHNESS_SLO_MS (60 minutes) constant; reworked getPipelineFunnelToday to compute published-breakdown grid (first-published, created, updated, latest-visible, trending-top-100 counts) and freshness object (latest preview age in minutes, SLO comparison, isFresh flag); added new getPipelineDoctor admin query aggregating queue state per article status, identifying expired-processing articles via enrichment lease cutoff, detecting "almost publishable" recent-processing patterns, providing feed visibility metrics with trending-based hiding, and reporting failure-reason aggregation from recent logs; and added countVisiblePreviewsSince internal query counting previews with lastUpdatedAt >= since.
Enhanced Alert Rules and Job Scheduling
packages/backend/convex/pipeline.ts
Extended checkPipelineAlerts with feed-visibility-drought warning (raises alert when no previews visible since freshness SLO), enrichment-failure-rate warning (alerts when enrichment attempts are high and failure ratio >20% in last 30 min), and replaced vector-budget burn-rate logic with UTC-day-progress-based projected-exhaustion rule; updated triggerPipelineJob jobName union to include "deleteStaleProcessingEvents" and "checkPipelineAlerts" and added branching to schedule those jobs and remaining clusterEnrichedArticles.
Stale Processing Event Cleanup System
packages/backend/convex/singletonCleanup.ts
Added dedicated lock key and three exports for processing-event cleanup: getStaleProcessingCandidates internal query fetching from two indexes (lastArticleAt, firstPublishedAt) filtered to status="processing" with deduplication; archiveStaleProcessingEvent internal mutation conditionally archiving older processing events and cascading deletion of articles and related child records; and deleteStaleProcessingEvents internal action coordinating locked, logged, incremental batch cleanup with optional scheduler auto-continuation. Updated getArchiveSettings to clamp singleton_cleanup_batch_size to new default (75) and range (max 300).
Share Asset Generation Feature Flag Gating
packages/backend/convex/shareAssets.ts, packages/backend/convex/shareAssetsNode.ts, packages/backend/convex/config.ts
Added event_share_asset_generation_enabled config key (default false) to config defaults; updated ensureEventShareAssetQueued mutation to short-circuit with { queued: false, reason: "disabled" } when flag is false or with { reason: "already_queued" } when asset already exists in pending/ready; and updated generateEventShareAsset action handler return type to include reason: "disabled" and early-return when feature flag is not true.
Admin Dashboard UI: Doctor, Freshness, Breakdown, Actions
apps/web/src/routes/admin.pipeline.tsx
Added query to getPipelineDoctor and gated loading on it; extended action-button row to include Ingest/Enrich/Prune/Check buttons mapped to new job types via triggerPipelineJob; replaced "Archived 24h" card with "Freshness SLO" card showing OK/Late status from funnel.freshness.isFresh and latest-visible timestamp; changed "Today's Funnel" metrics section to render published-breakdown grid displaying first-published, preview rows, updated, latest-visible, and trending-top-100 counts; and inserted new "Pipeline Doctor" card rendering queue ages per status, conditional "Almost Publishable" list, and "Why Feed Looks Quiet" diagnostics (latest time, hidden-by-trending count, recent failure summaries).
Pipeline Operations Runbook Documentation
docs/pipeline-operations-runbook.md
New operational runbook documenting production feed incident response procedures, covering Feed Stalled, Enrichment Failing, Vector Budget Exhausting, Processing Singleton Overload, Storage Growing, and Source Ingestion Degraded, with concrete initial workflow entrypoint (/admin/pipeline), checks, and mitigation steps for each incident category.
Cron Job Schedule Tuning
packages/backend/convex/crons.ts
Adjusted multiple pipeline cron intervals: article enrichment and clustering now run every 40 minutes (was 30), near-duplicate merge every 20 minutes (was 10), recent singleton recluster every 30 minutes (was 15), event summarization every 45 minutes (was 30), claim divergence detection every 45 minutes (was 30), and pipeline alert checks every 20 minutes (was 15).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • flvvius/news#31: Extended existing pipeline dashboard/alerts infrastructure with new getPipelineDoctor query, freshness metrics, and enhanced alert logic.
  • flvvius/news#20: Both PRs modify packages/backend/convex/clustering.ts near-duplicate merge behavior via parameter tuning and delayed follow-up scheduling.
  • flvvius/news#13: Both PRs modify packages/backend/convex/events.ts around lastUpdatedAt-based "recent" ordering for feed ranking and preview visibility.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Fix/pipeline robustness' is vague and uses non-descriptive terms that don't convey meaningful information about the extensive changes across multiple systems. Replace with a more specific title that reflects the main focus, such as 'Improve pipeline resilience with enhanced monitoring, scheduling, and stale event cleanup' or break into multiple focused PRs.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/pipeline_robustness

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/backend/convex/events.ts (1)

224-228: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Recent sort fallback still uses old publish-time ordering.

When sort === "recent" without topicId, Line 226 still paginates on by_first_published_at. That makes “Latest” inconsistent with the updated lastUpdatedAt logic used elsewhere.

Proposed fix
-      events = await ctx.db
-        .query("publicEventPreviews")
-        .withIndex("by_first_published_at")
-        .order("desc")
-        .paginate(paginationOpts);
+      events = await ctx.db
+        .query("publicEventPreviews")
+        .withIndex("by_last_updated_at")
+        .order("desc")
+        .paginate(paginationOpts);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/backend/convex/events.ts` around lines 224 - 228, The recent-sort
branch still paginates using the old "by_first_published_at" index, causing
"Latest" to ignore lastUpdatedAt; update the query that sets events to use the
appropriate index when sort === "recent" (and topicId is not provided) by
switching .withIndex("by_first_published_at") to
.withIndex("by_last_updated_at") for the "recent" case (leave the existing index
for other sorts), ensuring the same paginationOpts and .order("desc") are used
so latest results follow the lastUpdatedAt logic used elsewhere.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/pipeline-operations-runbook.md`:
- Line 36: Update the sentence containing "qGB" so the abbreviation is defined
on first use (e.g., "qGB (query gigabytes)" or the project's exact full term) —
change the line "Enter observed last-24h qGB only when the Convex dashboard
materially differs from the estimate." to include the full phrase for qGB and,
if helpful, add a brief parenthetical clarifying that it refers to the Convex
dashboard vector/query budget metric; keep the rest of the instruction
unchanged.

In `@packages/backend/convex/lib/publicEventPreviews.ts`:
- Around line 161-167: The patch branch currently updates existing rows via
ctx.db.patch(existing._id, payload) but never sets createdAt, so
existing.createdAt can remain undefined; modify the patch logic in
publicEventPreviews.ts so that before calling ctx.db.patch you ensure
payload.createdAt is set to existing.createdAt if present, otherwise to now (the
same timestamp used for inserts), e.g., compute a createdAt value from
existing.createdAt || now and merge it into the payload when calling
ctx.db.patch to avoid losing or leaving createdAt undefined.

In `@packages/backend/convex/pipeline.ts`:
- Around line 534-537: The code currently builds latestHiddenByTrending by
filtering latestRows against trendingIds and slicing to 10, which makes its
.length a sampled count rather than the true hidden total; change this by
computing a separate totalHiddenCount (e.g., const latestHiddenTotal =
latestRows.filter(row => !trendingIds.has(row.eventId)).length) and also keep a
sampled array for display (e.g., const latestHiddenByTrending =
latestRows.filter(...).slice(0,10).map(...)); update any downstream references
to use latestHiddenTotal for counts and latestHiddenByTrending for the displayed
items so the UI receives both the true count and the sample.

In `@packages/backend/convex/shareAssetsNode.ts`:
- Around line 412-415: The early-return branch that returns { generated: false,
reason: "disabled" } leaves the queued record in "pending" so a future re-enable
is blocked by the already_queued short-circuit in ensureEventShareAssetQueued;
update the branch to transition or clear the queued entry when returning
disabled (e.g., update the queued record status or remove the pending queue
entry) and/or make ensureEventShareAssetQueued aware of the "disabled" outcome
so it doesn't treat that signature as already_queued; touch the code paths that
emit { generated: false, reason: "disabled" and the ensureEventShareAssetQueued
check for already_queued to ensure the queue is cleared or the status updated
before returning.

---

Outside diff comments:
In `@packages/backend/convex/events.ts`:
- Around line 224-228: The recent-sort branch still paginates using the old
"by_first_published_at" index, causing "Latest" to ignore lastUpdatedAt; update
the query that sets events to use the appropriate index when sort === "recent"
(and topicId is not provided) by switching .withIndex("by_first_published_at")
to .withIndex("by_last_updated_at") for the "recent" case (leave the existing
index for other sorts), ensuring the same paginationOpts and .order("desc") are
used so latest results follow the lastUpdatedAt logic used elsewhere.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8ac4d360-0690-4768-a436-7c9d92c63407

📥 Commits

Reviewing files that changed from the base of the PR and between 1d06f7a and c9ec631.

📒 Files selected for processing (16)
  • apps/web/src/lib/i18n/strings.ts
  • apps/web/src/routes/admin.pipeline.tsx
  • docs/pipeline-operations-runbook.md
  • packages/backend/convex/clustering.ts
  • packages/backend/convex/config.ts
  • packages/backend/convex/crons.ts
  • packages/backend/convex/enrichment.ts
  • packages/backend/convex/enrichmentNode.ts
  • packages/backend/convex/events.ts
  • packages/backend/convex/ingestion.ts
  • packages/backend/convex/lib/publicEventPreviews.ts
  • packages/backend/convex/pipeline.ts
  • packages/backend/convex/schema.ts
  • packages/backend/convex/shareAssets.ts
  • packages/backend/convex/shareAssetsNode.ts
  • packages/backend/convex/singletonCleanup.ts

Comment thread docs/pipeline-operations-runbook.md Outdated
Comment thread packages/backend/convex/lib/publicEventPreviews.ts
Comment thread packages/backend/convex/pipeline.ts Outdated
Comment thread packages/backend/convex/shareAssetsNode.ts
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant