Skip to content

feat: add on-demand security-contacts ingest for a single purl (CM-1313) - #4312

Merged
mbani01 merged 10 commits into
mainfrom
feat/support_security_contacts_on_demand
Jul 8, 2026
Merged

feat: add on-demand security-contacts ingest for a single purl (CM-1313)#4312
mbani01 merged 10 commits into
mainfrom
feat/support_security_contacts_on_demand

Conversation

@mbani01

@mbani01 mbani01 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

This pull request introduces an on-demand workflow for ingesting security contacts for a package when requested via the API, especially for packages that have not yet been processed by the regular batch job. The main changes include new workflow and activity definitions to support single-package ingestion, integration of this workflow into the public API, and refactoring to share logic between batch and single-ingest paths.

On-demand security contacts ingestion:

  • Added a new API-side trigger in getPackage.ts that, when a package's security contacts have not been refreshed, invokes an on-demand Temporal workflow to ingest contacts for that package (ondemandWorkflowId, workflow execution logic).
  • Introduced a deterministic workflow ID scheme to ensure concurrent requests for the same package join the same workflow rather than starting multiple ingests (ondemandWorkflowId).

Workflow and activity changes:

  • Added ingestSecurityContactsForPurlWorkflow and supporting activity ingestSecurityContactsForPurlActivity to process a single package, with appropriate timeouts and retry policies for synchronous API use [1] [2] [3] [4].
  • Exported the new workflow from the worker entrypoint for registration.

Core ingestion logic refactoring:

  • Extracted and exported shared repo-processing logic (buildBaseDeps, processRepo) to allow both batch and single-ingest paths to reuse the same code [1] [2].
  • Added ingestSingle.ts to encapsulate the logic for finding the best repo for a purl and running the extraction pipeline for just that repo.

Supporting changes:

  • Updated imports to include new types and functions where needed [1] [2] [3] [4].

These changes enable the API to provide up-to-date security contact information for any package on demand, improving responsiveness for less critical or newly added packages.


Note

Medium Risk
Public package GET can synchronously wait on Temporal and external fetches (latency/timeouts), and misconfigured packages Temporal would break on-demand ingest while the endpoint degrades to cached data.

Overview
When get package sees a repo that has never had security contacts evaluated (contactsLastRefreshed is null), it now blocks up to ~60s on a packages-namespace Temporal workflow instead of returning empty contacts forever for non–batch-covered packages.

The API gets a dedicated getPackagesTemporalClient and packagesTemporal config (CROWD_PACKAGES_TEMPORAL_*), separate from the main API Temporal client. The security-contacts worker is wired to that namespace via CROWD_PACKAGES_TEMPORAL_NAMESPACE.

Worker side: new ingestSecurityContactsForPurlWorkflow / activity plus ingestSingle.ts (best-repo selection aligned with getPackageDetailByPurl, no is_critical gate). buildBaseDeps and processRepo are exported so batch and on-demand share the same extraction path. The scheduled batch sweep drops the GitHub-only host filter so non-GitHub repos can be retried on the normal cadence.

Concurrent requests for the same purl use a deterministic workflow id with USE_EXISTING so only one ingest runs; failures are logged and the handler still serves cached detail.

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

Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
@mbani01 mbani01 self-assigned this Jul 7, 2026
Copilot AI review requested due to automatic review settings July 7, 2026 10:06
@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.

Comment thread backend/src/api/public/v1/packages/getPackage.ts Outdated
Comment thread services/apps/packages_worker/src/security-contacts/ingestSingle.ts Outdated
Comment thread services/apps/packages_worker/src/security-contacts/workflows.ts

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 an on-demand path for ingesting security contacts for a single package (purl). Previously, security contacts were only populated by a daily batch sweep restricted to critical, GitHub-hosted repos. Now, when the public getPackage endpoint serves a package whose contacts have never been refreshed, it synchronously drives a Temporal workflow to ingest contacts for that package's best repo, then re-reads and returns the fresh detail. Shared repo-processing logic is extracted from the batch path so both paths reuse the same extractor pipeline.

Changes:

  • New ingestSingle.ts selects the same "best" GitHub repo the read side surfaces (no is_critical filter) and runs the extractor pipeline for that single repo, plus a new activity and short-timeout workflow (ingestSecurityContactsForPurlWorkflow) for synchronous API use.
  • getPackage.ts triggers the on-demand workflow (deterministic SHA-1 workflow id + USE_EXISTING conflict policy to dedupe concurrent requests) when contactsLastRefreshed is null, then re-reads package detail; failures are caught and cached detail is served.
  • Refactored processBatch.ts to export shared buildBaseDeps / processRepo; wired new exports through the activities/workflows barrels.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
backend/src/api/public/v1/packages/getPackage.ts Adds synchronous on-demand ingest trigger + deterministic workflow-id helper on the public package-detail endpoint.
services/apps/packages_worker/src/security-contacts/ingestSingle.ts New single-purl ingest: best-repo selection SQL + extractor pipeline invocation.
services/apps/packages_worker/src/security-contacts/workflows.ts Adds a short-timeout activity proxy and ingestSecurityContactsForPurlWorkflow.
services/apps/packages_worker/src/security-contacts/activities.ts New ingestSecurityContactsForPurlActivity wrapping the single-purl ingest.
services/apps/packages_worker/src/security-contacts/processBatch.ts Extracts and exports buildBaseDeps / processRepo for reuse.
services/apps/packages_worker/src/activities.ts Exports the new activity for worker registration.
services/apps/packages_worker/src/workflows/index.ts Exports the new workflow for worker registration.

Key review notes:

  • The on-demand guard (contactsLastRefreshed == null) will re-fire on every request for packages whose best repo is non-GitHub or absent, since nothing ever marks those as attempted — turning a public read into a repeated no-op Temporal round-trip.
  • The synchronous execute call has no workflowExecutionTimeout/scheduleToStartTimeout, so the public request can hang if the worker/queue is unavailable.
  • A doc comment references a non-existent security-contacts.md.

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

Comment thread backend/src/api/public/v1/packages/getPackage.ts Outdated
Comment on lines +57 to +63
await req.temporal.workflow.execute('ingestSecurityContactsForPurlWorkflow', {
taskQueue: 'packages-worker',
workflowId: ondemandWorkflowId(purl),
workflowIdReusePolicy: WorkflowIdReusePolicy.ALLOW_DUPLICATE,
workflowIdConflictPolicy: WorkflowIdConflictPolicy.USE_EXISTING,
args: [purl],
})
Comment thread services/apps/packages_worker/src/security-contacts/ingestSingle.ts Outdated
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
workflowId: ondemandWorkflowId(purl),
workflowIdReusePolicy: WorkflowIdReusePolicy.ALLOW_DUPLICATE,
workflowIdConflictPolicy: WorkflowIdConflictPolicy.USE_EXISTING,
args: [purl],

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Shared repo concurrent ingest race

Medium Severity

On-demand workflow IDs are hashed per purl, so two packages sharing the same GitHub repo can run separate workflows at once. Both call processRepo / writeContacts on the same repoId, while writeContacts assumes a single writer per repo.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 471ad1d. Configure here.

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

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 13 out of 13 changed files in this pull request and generated 3 comments.

Comment on lines +13 to +17
if (!PACKAGES_TEMPORAL_CONFIG?.serverUrl) {
throw new Error(
'Packages Temporal is not configured — set CROWD_PACKAGES_TEMPORAL_NAMESPACE',
)
}
Comment thread backend/src/api/public/v1/packages/getPackage.ts Outdated
Comment thread services/apps/packages_worker/src/security-contacts/ingestSingle.ts
mbani01 added 2 commits July 7, 2026 15:33
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 7, 2026 14:34
workflowIdReusePolicy: WorkflowIdReusePolicy.ALLOW_DUPLICATE,
workflowIdConflictPolicy: WorkflowIdConflictPolicy.USE_EXISTING,
args: [purl],
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Concurrent repo contact writes

Medium Severity

On-demand ingest can run processRepo for a repo while the scheduled batch sweep processes that same repo in parallel. writeContacts assumes a single writer per repoId and soft-deletes then upserts without locking, so overlapping runs can interleave and produce inconsistent security_contacts or repo policy fields.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 59fdeee. 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 13 out of 13 changed files in this pull request and generated 4 comments.

Comment on lines +94 to +99
try {
await processRepo(target, baseDeps, qx)
} catch (err) {
log.error({ repoId: target.repoId, errMsg: (err as Error).message }, 'Repo processing failed')
await markRepoAttempted(qx, target.repoId).catch(() => undefined)
}
Comment on lines +59 to +65
await packagesTemporal.workflow.execute('ingestSecurityContactsForPurlWorkflow', {
taskQueue: 'packages-worker',
workflowId: ondemandWorkflowId(purl),
workflowIdReusePolicy: WorkflowIdReusePolicy.ALLOW_DUPLICATE,
workflowIdConflictPolicy: WorkflowIdConflictPolicy.USE_EXISTING,
args: [purl],
})
throw new NotFoundError()
}

if (pkg.contactsLastRefreshed == null) {

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.

@mbani01 does this make sense ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Indeed, it makes sense!
Will keep it in mind when implementing the security contacts endpoint, as we won't enable the on-demand feature for now on the packages api

Comment thread services/apps/packages_worker/src/security-contacts/ingestSingle.ts Outdated
mbani01 added 3 commits July 7, 2026 15:44
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>
Copilot AI review requested due to automatic review settings July 7, 2026 14:47

@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 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

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 24bb6c1. Configure here.

Comment thread backend/src/api/public/v1/packages/getPackage.ts Outdated

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 13 out of 13 changed files in this pull request and generated 2 comments.

Comment thread backend/src/api/public/v1/packages/getPackage.ts Outdated
Comment thread backend/src/db/packagesTemporal.ts
@mbani01
mbani01 requested a review from ulemons July 7, 2026 15:06
ulemons
ulemons previously approved these changes Jul 8, 2026
Signed-off-by: Mouad BANI <mouad-mb@outlook.com>
Copilot AI review requested due to automatic review settings July 8, 2026 10: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 12 out of 12 changed files in this pull request and generated 2 comments.

// Separate connection from the API's default req.temporal client — packages_worker
// (npm/maven/pypi/osv/security-contacts/...) polls task queues in its own Temporal
// namespace (CROWD_PACKAGES_TEMPORAL_NAMESPACE), not the API's default namespace.
export function getPackagesTemporalClient(): Promise<Client> {
export function getPackagesTemporalClient(): Promise<Client> {
if (!_init) {
if (!PACKAGES_TEMPORAL_CONFIG?.serverUrl) {
throw new Error('Packages Temporal is not configured — set CROWD_PACKAGES_TEMPORAL_NAMESPACE')
Copilot AI review requested due to automatic review settings July 8, 2026 10:52
@mbani01
mbani01 merged commit fd93162 into main Jul 8, 2026
12 checks passed
@mbani01
mbani01 deleted the feat/support_security_contacts_on_demand branch July 8, 2026 10:52

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 12 out of 12 changed files in this pull request and generated 2 comments.

Comment on lines +11 to +15
export function getPackagesTemporalClient(): Promise<Client> {
if (!_init) {
if (!PACKAGES_TEMPORAL_CONFIG?.serverUrl) {
throw new Error('Packages Temporal is not configured — set CROWD_PACKAGES_TEMPORAL_NAMESPACE')
}
export function getPackagesTemporalClient(): Promise<Client> {
if (!_init) {
if (!PACKAGES_TEMPORAL_CONFIG?.serverUrl) {
throw new Error('Packages Temporal is not configured — set CROWD_PACKAGES_TEMPORAL_NAMESPACE')
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.

4 participants