Skip to content

feat: implement rubygems registry [CM-1241]#4322

Merged
mbani01 merged 18 commits into
mainfrom
feat/implement_rubygems_registry
Jul 10, 2026
Merged

feat: implement rubygems registry [CM-1241]#4322
mbani01 merged 18 commits into
mainfrom
feat/implement_rubygems_registry

Conversation

@mbani01

@mbani01 mbani01 commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

This pull request introduces a new RubyGems worker to the packages_worker service, adding support for ingesting and processing RubyGems package data. The main changes include Docker Compose and script updates for the new worker, as well as the implementation of RubyGems batch processing logic for both core and critical packages. The code adds API clients, data normalization, and database integration for RubyGems packages, versions, and maintainers.

RubyGems Worker Integration:

  • Added Docker Compose configuration (rubygems-worker.yaml) to run and develop the new RubyGems worker service with appropriate environment variables, volumes, and network settings.
  • Updated package.json scripts to support starting and developing the RubyGems worker locally and in Docker.

RubyGems Batch Processing Implementation:

  • Added processRubyGemsCoreBatch and processRubyGemsCriticalBatch activities, which handle batch processing for core and critical RubyGems packages, respectively. [1] [2]
  • Implemented batch processing logic in runRubyGemsCoreLoop.ts and runRubyGemsCriticalLoop.ts for fetching, normalizing, and upserting package, version, and maintainer data into the database, including error handling and retry logic. [1] [2]

RubyGems Registry Integration:

  • Added API client methods to fetch package metadata, versions, and owners from the RubyGems registry, with error classification for common HTTP errors.
  • Introduced normalization utilities to convert RubyGems registry responses into the service's internal data format.

Configuration:

  • Added getRubyGemsConfig to manage batch size, concurrency, and delay settings for RubyGems batch processing, with environment variable support.

Note

Medium Risk
Touches shared packages DB write paths (versions upsert, downloads, maintainers) and runs high-concurrency external API ingestion; misconfiguration could stress rubygems.org or write incorrect package metadata at scale.

Overview
Adds a dedicated rubygems-worker Temporal service (Docker Compose, package scripts, build list) that registers three scheduled ingestion pipelines on the rubygems-worker task queue.

Core sync (daily) pulls gem metadata from rubygems.org, upserts package fields and download snapshots (daily deltas + rolling last-30d), with rate limiting, 404/rubygems_not_found handling, and a fail-the-batch backoff when every row is rate-limited.

Critical sync (daily) enriches is_critical gems with full version history (including published_at), owners/maintainers, and audit logging.

Dependents sync (weekly) walks all rubygems packages by id and stores reverse-dependency counts used to prioritize core sync ordering.

Shared data-access additions include RubyGems-specific list/update queries, a reusable recordDownloadSnapshot helper, and extending upsertVersionsBatch / IDbVersionUpsert to persist version publish times.

Reviewed by Cursor Bugbot for commit f87aa20. Bugbot is set up for automated code reviews on this repo. Configure here.

mbani01 added 8 commits July 9, 2026 15:12
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
@mbani01 mbani01 self-assigned this Jul 9, 2026
Copilot AI review requested due to automatic review settings July 9, 2026 14:37
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Copilot AI 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.

Pull request overview

This PR adds a new RubyGems registry ingestion capability to the packages_worker service, following the established multi-entry-point worker pattern (most closely mirroring the existing NuGet worker). It introduces an API client for rubygems.org, normalization utilities, two Temporal batch loops (a "core" metadata/downloads pass and a "critical" versions/maintainers pass), their activities/workflows/schedules, a dev/prod Docker Compose file, and supporting data-access-layer helpers.

Changes:

  • New rubygems/ worker module: client.ts, normalize.ts, types.ts, runRubyGemsCoreLoop.ts, runRubyGemsCriticalLoop.ts, activities.ts, workflows.ts, schedule.ts, and bin/rubygems-worker.ts, wired into activities.ts, workflows/index.ts, and getRubyGemsConfig.
  • New DAL helpers: osspckgs/rubygems.ts (package selection queries) and a generic osspckgs/downloads.ts (recordDownloadSnapshot), plus new root exports.
  • Ops wiring: scripts/services/rubygems-worker.yaml and package.json start/dev scripts (inspect port 9244).

Reviewed changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
services/libs/data-access-layer/src/osspckgs/rubygems.ts Keyset/priority selection queries for core and critical RubyGems packages.
services/libs/data-access-layer/src/osspckgs/downloads.ts Generic download-snapshot recorder (duplicate of the NuGet variant).
services/libs/data-access-layer/src/index.ts Exports new osspckgs/types, downloads, rubygems modules.
services/apps/packages_worker/src/workflows/index.ts Exports RubyGems workflows.
services/apps/packages_worker/src/rubygems/workflows.ts Core/critical continue-as-new workflows.
services/apps/packages_worker/src/rubygems/types.ts Fetch/normalized RubyGems type definitions.
services/apps/packages_worker/src/rubygems/schedule.ts Daily cron schedules for both workflows.
services/apps/packages_worker/src/rubygems/runRubyGemsCriticalLoop.ts Versions/maintainers batch loop (non-draining on skip/error).
services/apps/packages_worker/src/rubygems/runRubyGemsCoreLoop.ts Metadata/downloads batch loop with error marking.
services/apps/packages_worker/src/rubygems/normalize.ts Pure normalization helpers (untested).
services/apps/packages_worker/src/rubygems/client.ts Axios client with 404/429 error classification.
services/apps/packages_worker/src/rubygems/activities.ts Activity wrappers over the two loops.
services/apps/packages_worker/src/config.ts Adds getRubyGemsConfig (used only by the core loop).
services/apps/packages_worker/src/bin/rubygems-worker.ts Worker entrypoint registering both schedules.
services/apps/packages_worker/src/activities.ts Registers the new RubyGems activities.
services/apps/packages_worker/package.json start/dev scripts for the RubyGems worker.
scripts/services/rubygems-worker.yaml Compose service (omits CROWD_TEMPORAL_NAMESPACE).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread services/libs/data-access-layer/src/osspckgs/downloads.ts
Comment thread services/apps/packages_worker/src/rubygems/runRubyGemsCriticalLoop.ts Outdated
Comment thread scripts/services/rubygems-worker.yaml
Comment thread services/apps/packages_worker/src/rubygems/normalize.ts
Comment thread services/apps/packages_worker/src/rubygems/runRubyGemsCriticalLoop.ts Outdated
mbani01 added 2 commits July 9, 2026 16:15
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
…l loop

Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
Copilot AI review requested due to automatic review settings July 9, 2026 15:18

Copilot AI 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.

Pull request overview

Copilot reviewed 17 out of 18 changed files in this pull request and generated 2 comments.

Comment on lines +59 to +74
if (isRubyGemsFetchError(versionsResult)) {
if (versionsResult.kind === 'NOT_FOUND') {
log.warn({ purl: pkg.purl }, 'Package not found on RubyGems versions endpoint — skipping')
return 'skipped'
}
if (versionsResult.kind === 'RATE_LIMIT') {
log.warn({ purl: pkg.purl }, 'Rate limited fetching RubyGems versions — will retry next pass')
return 'error'
}
throw new Error(`Transient error fetching versions for ${pkg.purl}: ${versionsResult.message}`)
}

const normalizedVersions = normalizeRubyGemsVersions(versionsResult)
const latest = pickLatestRubyGemsVersion(normalizedVersions)
const ownersFetchFailed = isRubyGemsFetchError(ownersResult)
const owners = ownersFetchFailed ? [] : normalizeRubyGemsOwners(ownersResult)
Comment on lines +5 to +13
export async function recordDownloadSnapshot(
qx: QueryExecutor,
params: {
packageId: number
purl: string
totalDownloads: number
today: string // YYYY-MM-DD
},
): Promise<string[]> {
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
Copilot AI review requested due to automatic review settings July 10, 2026 10:43

Copilot AI 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.

Pull request overview

Copilot reviewed 18 out of 19 changed files in this pull request and generated 8 comments.

qx.tx(async (t) => {
const changed = new Set<string>()

const { id: packageDbId, changedFields: pkgChanged } = await upsertPackage(t, {

export async function ingestRubyGemsCriticalDetails(): Promise<void> {
const result = await acts.processRubyGemsCriticalBatch()
if (result.processed + result.skipped + result.error + result.unchanged === 0) {
pkgChanged.forEach((f) => changed.add(f))

if (normalizedVersions.length > 0) {
const versionRows: IDbVersionUpsert[] = normalizedVersions.map((v) => ({
Comment on lines +107 to +108
batchSize: parseInt(process.env.RUBYGEMS_DEPENDENTS_BATCH_SIZE ?? '50000', 10),
concurrency: parseInt(process.env.RUBYGEMS_DEPENDENTS_CONCURRENCY ?? '50', 10),
): Promise<void> {
await qx.result(
`UPDATE packages
SET dependent_count = $(dependentCount)
import { upsertLast30dDownload } from '../packages/downloadsLast30d'
import { QueryExecutor } from '../queryExecutor'

export async function recordDownloadSnapshot(
SERVICE: rubygems-worker
SHELL: /bin/sh
SUPPRESS_NO_CONFIG_WARNING: 'true'
CROWD_TEMPORAL_TASKQUEUE: rubygems-worker
Comment on lines +46 to +48
export function pickLatestRubyGemsVersion(
versions: NormalizedRubyGemsVersion[],
): NormalizedRubyGemsVersion | null {
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
Copilot AI review requested due to automatic review settings July 10, 2026 11:00

Copilot AI 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.

Pull request overview

Copilot reviewed 19 out of 20 changed files in this pull request and generated 6 comments.

Comment thread services/libs/data-access-layer/src/osspckgs/rubygems.ts Outdated
Comment thread services/libs/data-access-layer/src/osspckgs/rubygems.ts Outdated
Comment thread services/apps/packages_worker/src/rubygems/workflows.ts Outdated
Comment thread services/apps/packages_worker/src/rubygems/normalize.ts
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
Copilot AI review requested due to automatic review settings July 10, 2026 11:25

Copilot AI 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.

Pull request overview

Copilot reviewed 18 out of 19 changed files in this pull request and generated 9 comments.

Comment thread services/libs/data-access-layer/src/osspckgs/rubygems.ts Outdated
Comment thread services/libs/data-access-layer/src/osspckgs/rubygems.ts Outdated
Comment thread services/libs/data-access-layer/src/osspckgs/rubygems.ts Outdated
Comment thread services/libs/data-access-layer/src/osspckgs/rubygems.ts Outdated
Comment thread scripts/services/rubygems-worker.yaml
Comment thread services/apps/packages_worker/src/rubygems/normalize.ts Outdated
Comment thread services/apps/packages_worker/src/rubygems/client.ts Outdated
Comment thread services/apps/packages_worker/src/rubygems/runRubyGemsCoreLoop.ts Outdated
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
Copilot AI review requested due to automatic review settings July 10, 2026 16:30

Copilot AI 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.

Pull request overview

Copilot reviewed 21 out of 22 changed files in this pull request and generated 5 comments.

import { QueryExecutor } from '../queryExecutor'

export type RubyGemsPackageToSync = {
id: number
Comment on lines +88 to +91
if (gemResult.kind === 'RATE_LIMIT') {
log.warn({ purl: pkg.purl }, 'Rate limited by RubyGems registry — will retry next pass')
return 'error'
}
Comment thread services/apps/packages_worker/src/rubygems/normalize.ts Outdated
Comment on lines +63 to +66
for (let batchStart = 0; batchStart < packages.length; batchStart += config.concurrency) {
const group = packages.slice(batchStart, batchStart + config.concurrency)

await Promise.all(
Comment thread scripts/services/rubygems-worker.yaml
Copilot AI review requested due to automatic review settings July 10, 2026 16:37

Copilot AI 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.

Pull request overview

Copilot reviewed 21 out of 22 changed files in this pull request and generated 4 comments.

Comment on lines +88 to +91
if (gemResult.kind === 'RATE_LIMIT') {
log.warn({ purl: pkg.purl }, 'Rate limited by RubyGems registry — will retry next pass')
return 'error'
}
AND (
p.ingestion_source IS NULL
OR p.ingestion_source <> ALL($(workerOutcomes)::text[])
OR p.last_synced_at < date_trunc('day', NOW())
SERVICE: rubygems-worker
SHELL: /bin/sh
SUPPRESS_NO_CONFIG_WARNING: 'true'
CROWD_TEMPORAL_TASKQUEUE: rubygems-worker
Comment thread services/libs/data-access-layer/src/osspckgs/rubygems.ts Outdated
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
Copilot AI review requested due to automatic review settings July 10, 2026 16:48

Copilot AI 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.

Pull request overview

Copilot reviewed 21 out of 22 changed files in this pull request and generated 4 comments.

Comment thread services/apps/packages_worker/src/rubygems/client.ts
Comment on lines +5 to +7
const acts = proxyActivities<typeof activities>({
startToCloseTimeout: '30 minutes',
retry: { initialInterval: '30 seconds', backoffCoefficient: 2, maximumAttempts: 5 },
Comment on lines +3 to +5
export type RubyGemsPackageToSync = {
id: number
purl: string
Comment thread services/apps/packages_worker/src/rubygems/normalize.ts
mbani01 added 2 commits July 10, 2026 18:00
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
Copilot AI review requested due to automatic review settings July 10, 2026 17:05
@mbani01 mbani01 marked this pull request as ready for review July 10, 2026 17:06

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f87aa20. Configure here.

): NormalizedRubyGemsVersion | null {
if (versions.length === 0) return null
const stable = versions.find((v) => !v.isPrerelease)
return stable ?? versions[0]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Critical latest version mismatch

High Severity

pickLatestRubyGemsVersion picks the first non-prerelease entry in the versions list, while the core ingest path sets latestVersion from the gem JSON version field. When the registry’s current release is a prerelease, critical sync can persist an older stable as latest_version and mark the wrong row is_latest, diverging from core ingest and from RubyGems.org.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f87aa20. Configure here.

}

counts.lastId = packages[packages.length - 1].id
return counts

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pagination skips failed packages

Medium Severity

Dependents and critical workflows always set lastId to the final row in the batch, including packages that returned error without updating the database. continueAsNew then resumes after that id, so those failures are not retried until the next scheduled run (weekly for dependents).

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f87aa20. Configure here.

Copilot AI 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.

Pull request overview

Copilot reviewed 21 out of 22 changed files in this pull request and generated 6 comments.

Comment on lines +50 to +53
export function fetchOwners(name: string): Promise<RubyGemsFetchResult<RubyGemsOwner[]>> {
return rubyGemsGet<RubyGemsOwner[]>(
`https://rubygems.org/api/v1/gems/${encodeURIComponent(name)}/owners.json`,
)
SERVICE: rubygems-worker
SHELL: /bin/sh
SUPPRESS_NO_CONFIG_WARNING: 'true'
CROWD_TEMPORAL_TASKQUEUE: rubygems-worker
import { upsertLast30dDownload } from '../packages/downloadsLast30d'
import { QueryExecutor } from '../queryExecutor'

export async function recordDownloadSnapshot(
number: v.number,
isLatest: v.number === latest?.number,
isPrerelease: v.isPrerelease,
license: v.licenses && v.licenses.length > 0 ? v.licenses[0] : null,
Comment on lines +35 to +38
export function normalizeRubyGemsVersions(
items: RubyGemsVersionItem[],
): NormalizedRubyGemsVersion[] {
return items.map((item) => ({
Comment on lines +1 to +2
const MAX_RPS = Math.max(1, parseInt(process.env.RUBYGEMS_MAX_RPS ?? '10', 10))
const INTERVAL_MS = 1000 / MAX_RPS
@mbani01 mbani01 merged commit 97d617b into main Jul 10, 2026
19 checks passed
@mbani01 mbani01 deleted the feat/implement_rubygems_registry branch July 10, 2026 17:20
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.

3 participants