Skip to content

feat: resolve member contributors for unknown ecosystems via llm repo inference - #5000

Merged
mbani01 merged 24 commits into
mainfrom
feat/issue-502-llm-repo-resolution
Oct 16, 2026
Merged

mbani01 merged 24 commits into
mainfrom
feat/issue-502-llm-repo-resolution

Conversation

@mbani01

@mbani01 mbani01 commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Purls from ecosystems CDP does not track are now accepted by POST /akrites-external/packages/member-contributors/jobs and routed to a new collection path instead of 400. Malformed purls (pkg:, pkg:hackage) still get 400.
  • A new workflow collectMemberContributorsFromInferredRepo asks Claude Sonnet 5.5 on Bedrock for up to three ranked candidate GitHub repositories, keeps those with confidence ≥ 0.7, takes the first that exists on GitHub, fetches its contributors without storing them, resolves affiliations for new identities, and returns the sync body with repoUrlSource: "llm_inferred" as the workflow result. Below-threshold, unknown, non-GitHub or missing repositories end the job as unresolvable with a reason. Truncated or refused LLM answers are retried or surfaced, never recorded as a confident unresolvable.
  • The poll endpoint serves unresolvable, reads inferred-repo done bodies from Temporal, and answers failed with a resubmit reason once the result has left Temporal retention. A resubmission while a result is still readable gets 409 with error.context.jobId.
  • Job rows record the repository used, its source, LLM confidence, per-call token usage and estimated cost, and a submission counter for repeat requests. Each LLM call is also logged with tokens, cost and stop reason. At most two LLM calls per job.
  • No repos, packages, package_repos or repo_contributors row is created or updated from an LLM answer. Supported-ecosystem jobs are unchanged apart from counting repeat submissions.
  • The response body builder moved from the backend into @crowd/data-access-layer so the worker and the API share it.

Migration

V1792150054__member_contributor_jobs_llm_repo_resolution.sql widens the two CHECK constraints on member_contributor_jobs and adds typed columns. Apply it before deploying the backend: the poll handler now selects the new columns on every path. Constraint names were verified against prod.

Closes linuxfoundation/lfx-cdp-insights-ops#502

Copilot AI balanced review requested due to automatic review settings October 16, 2026 12:34
@cursor

cursor Bot commented Oct 16, 2026 •

Copy link
Copy Markdown

PR Summary

Medium Risk
Changes external job submit/poll semantics, adds LLM and Temporal-dependent result retention, and requires a DB migration before deploy; supported-ecosystem GitHub collection path is largely unchanged.

Overview
Member-contributors jobs now accept purls from ecosystems CDP does not track, routing them to a new llm_repo_resolution collection path instead of 400. Malformed purls are validated with isWellFormedPurl; only those still return 400.

For unknown ecosystems, submit creates jobs without a package/repo lookup, starts collectMemberContributorsFromInferredRepo, and tracks repeat submissions (incrementMemberContributorJobSubmissions). 409 responses use a ConflictError with error.context.jobId when a prior inferred-repo result is still readable in Temporal. Poll adds unresolvable, serves inferred done bodies from the workflow result (not DB tables), and maps expired/missing workflow results to failed with a resubmit reason.

The worker adds Bedrock-based repo inference (confidence threshold + GitHub existence checks), contributor fetch/affiliation for inferred repos, and job metadata writes (LLM usage, inferred repo_url, submission count). No package/repo/contributor rows are persisted for the LLM path beyond job state.

OpenAPI documents the new statuses, repoUrlSource: llm_inferred, and conflict shape. A migration widens member_contributor_jobs constraints and adds LLM/repo columns. Response shaping moves into @crowd/data-access-layer (memberContributorsBody) for API/worker sharing.

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

@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.

@mbani01 mbani01 self-assigned this Oct 16, 2026

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.

🟡 Changes recommended

Unresolved correctness issues affect inference outcomes, usage accounting, input validation, and duplicate-job prevention.

4 open findings
What changed in this PR

Extends member-contributor collection to unsupported ecosystems through LLM-based GitHub repository inference, without persisting inferred package/repository mappings or contributor rows.

Changes:

  • Adds inference, repository verification, contributor fetching, and affiliation resolution.
  • Extends job submission, polling, usage tracking, and result-retention handling.
  • Shares response builders between the API and worker, with tests and API documentation.
File Description
services/​libs/​data-access-layer/​src/​osspckgs/​memberContributorsBody.ts Shares response builders and adds inferred-repo bodies.
services/​libs/​data-access-layer/​src/​osspckgs/​memberContributorsBody.test.ts Tests inferred response formatting.
services/​libs/​data-access-layer/​src/​osspckgs/​memberContributorJobs.ts Adds inference metadata and job operations.
services/​libs/​data-access-layer/​src/​osspckgs/​memberContributorJobs.test.ts Tests new job reads and writes.
services/​libs/​data-access-layer/​src/​osspckgs/​index.ts Exports shared response builders.
services/​libs/​data-access-layer/​src/​osspckgs/​identityAffiliations.ts Looks up affiliations by identity keys.
services/​libs/​data-access-layer/​src/​osspckgs/​identityAffiliations.test.ts Tests identity-key affiliation queries.
services/​libs/​data-access-layer/​src/​index.ts Exports shared response builders.
services/​apps/​packages_worker/​src/​workflows/​index.ts Registers the inferred-repo workflow.
services/​apps/​packages_worker/​src/​member-contributors/​workflows.ts Orchestrates inferred-repo collection.
services/​apps/​packages_worker/​src/​member-contributors/​job/​startInferredRepo.ts Starts inferred-repo jobs.
services/​apps/​packages_worker/​src/​member-contributors/​job/​resolveRepoViaLlm.ts Selects and verifies repository candidates.
services/​apps/​packages_worker/​src/​member-contributors/​job/​llmRepoInference.ts Requests and parses LLM candidates.
services/​apps/​packages_worker/​src/​member-contributors/​job/​finish.ts Records unresolvable outcomes.
services/​apps/​packages_worker/​src/​member-contributors/​job/​fetchGithubRepoContributors.ts Fetches contributors without storing them.
services/​apps/​packages_worker/​src/​member-contributors/​job/​canonicalGithubUrl.ts Normalizes candidate GitHub URLs.
services/​apps/​packages_worker/​src/​member-contributors/​job/​buildInferredRepoBody.ts Builds affiliated contributor responses.
services/​apps/​packages_worker/​src/​member-contributors/​job/​__tests__/​startInferredRepo.test.ts Tests inferred-job startup.
services/​apps/​packages_worker/​src/​member-contributors/​job/​__tests__/​resolveRepoViaLlm.test.ts Tests repository selection and failures.
services/​apps/​packages_worker/​src/​member-contributors/​job/​__tests__/​llmRepoInference.test.ts Tests model responses and usage logging.
services/​apps/​packages_worker/​src/​member-contributors/​job/​__tests__/​fetchGithubRepoContributors.test.ts Tests fetching and heartbeats.
services/​apps/​packages_worker/​src/​member-contributors/​job/​__tests__/​canonicalGithubUrl.test.ts Tests URL normalization.
services/​apps/​packages_worker/​src/​member-contributors/​job/​__tests__/​buildInferredRepoBody.test.ts Tests inferred response construction.
services/​apps/​packages_worker/​src/​member-contributors/​github/​mapContributors.ts Extracts reusable contributor-login mapping.
services/​apps/​packages_worker/​src/​member-contributors/​activities.ts Exports new collection activities.
services/​apps/​packages_worker/​src/​member-contributors/​__tests__/​workflows.test.ts Tests orchestration and retry settings.
services/​apps/​packages_worker/​src/​llm/​bedrockClient.ts Adds timed Bedrock invocation.
services/​apps/​packages_worker/​src/​config.ts Reads Bedrock credentials.
services/​apps/​packages_worker/​src/​activities.ts Registers new worker activities.
services/​apps/​packages_worker/​package.json Adds Zod dependency.
pnpm-lock.yaml Locks the worker’s Zod dependency.
backend/​src/​osspckgs/​migrations/​V1792150054__member_contributor_jobs_llm_repo_resolution.sql Extends job constraints and metadata columns.
backend/​src/​api/​public/​v1/​packages/​submitMemberContributorsJob.ts Routes inferred jobs and handles resubmissions.
backend/​src/​api/​public/​v1/​packages/​submitMemberContributorsJob.test.ts Tests inferred submissions and conflicts.
backend/​src/​api/​public/​v1/​packages/​memberContributorsJob.ts Extends job response types.
backend/​src/​api/​public/​v1/​packages/​memberContributorCollectionPaths.ts Routes unsupported ecosystems and validates purls.
backend/​src/​api/​public/​v1/​packages/​memberContributorCollectionPaths.test.ts Tests routing and validation.
backend/​src/​api/​public/​v1/​packages/​getMemberContributorsJob.ts Reads workflow results and handles expiration.
backend/​src/​api/​public/​v1/​packages/​getMemberContributorsJob.test.ts Tests inferred-job polling.
backend/​src/​api/​public/​v1/​packages/​getAkritesExternalMemberContributors.ts Uses shared response builders.
backend/​src/​api/​public/​v1/​packages/​getAkritesExternalMemberContributors.test.ts Adjusts shared-library mocks.
backend/​src/​api/​public/​v1/​akrites-external/​openapi.yaml Documents inferred jobs and response states.
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread backend/src/api/public/v1/packages/memberContributorCollectionPaths.ts Outdated
Comment thread backend/src/api/public/v1/packages/submitMemberContributorsJob.ts Outdated
Comment thread services/apps/packages_worker/src/member-contributors/job/llmRepoInference.ts Outdated
Comment on lines +91 to +92
if (inference.status === 'unparseable') {
return unresolvable(inference.reason)

@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.

Stale Bugbot comment from a previous run.

Comment thread backend/src/api/public/v1/packages/submitMemberContributorsJob.ts
Copilot AI balanced review requested due to automatic review settings October 16, 2026 13:07

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.

🔵 Needs a closer look

Terminal Temporal failures after database completion can leave job polling returning HTTP 500 instead of a recoverable failed status.

1 open finding
3 resolved since last review
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file
Previously missed (1)

In code that hasn't changed since last review

Medium severity Handle terminal workflow failures after committing done

backend/​src/​api/​public/​v1/​packages/​getMemberContributorsJob.ts:71

The workflow commits done before returning its Temporal result. If it is terminated or cancelled in that interval, the row stays done because failMemberContributorJob excludes done rows. result() then rejects with WorkflowFailedError, so subsequent polls return HTTP 500 instead of a job status. Catch terminal workflow failures and return failed with a resubmit reason, consistent with submission allowing replacement of these executions. Continue propagating transient Temporal errors, and add a polling test for this case.

🧠 Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 16, 2026 13: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.

🔵 Needs a closer look

Bedrock refusals still become unresolvable results rather than following the promised retry or failure path.

1 open finding
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

🧠 Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 16, 2026 13:51
@mbani01
mbani01 merged commit e43d35b into main Oct 16, 2026
18 checks passed
@mbani01
mbani01 deleted the feat/issue-502-llm-repo-resolution branch October 16, 2026 13:57

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.

🟡 Changes recommended

GitHub rate-limit waits can exhaust inference retries, repeat paid calls, and fail otherwise resolvable jobs.

2 open findings
Files not reviewed (1)
  • pnpm-lock.yaml: Generated file

🧠 Review effort: Balanced

Comment on lines +104 to +105
startToCloseTimeout: '3 minutes',
retry: { ...RETRY, maximumAttempts: 2 },

@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

Reviewed by Cursor Bugbot for commit dbf88c8. Configure here.

collectionPath: LLM_REPO_RESOLUTION_PATH,
packageId: null,
repoId: null,
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Purl type case breaks job dedup

Medium Severity

Active-job reuse and the still-readable 409 both key inferred-repo jobs on the raw purl string, while purlType already treats the type as case-insensitive. pkg:hackage/aeson and pkg:Hackage/aeson therefore miss each other, so a second inference can start while the first job is pending or its Temporal result is still readable.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit dbf88c8. Configure here.

@mbani01 mbani01 Oct 16, 2026 •

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.

Fixed in #5010 (commit 744e699 there): on the inferred-repo path the purl type is lowercased before the active-job lookup, the readable-result check, the row insert and the workflow id. The fix was pushed to this branch after it had been merged, so it ships in that follow-up PR rather than in #5000.

throw new Error(`GitHub contributors fetch failed for ${input.repoUrl}`)
}
const logins = toGithubContributorLogins(fetched.contributors)
return { status: logins.length === 0 ? 'empty' : 'fetched', logins }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Large repos fail inferred jobs

Medium Severity

fetchGithubRepoContributors turns every error from fetchRepoContributors into a retryable failure, including the helper’s 10-page cap. A correctly inferred repo with more than 1000 contributors never completes: the job fails after retries, and a resubmit spends another LLM call and fails the same way.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit dbf88c8. Configure here.

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.

Not reachable in practice: the GitHub contributors endpoint links only the first 500 author emails to GitHub users and omits anonymous contributors unless anon=1 is passed, which this code never does (docs: https://docs.github.com/en/rest/repos/repos#list-repository-contributors). With per_page=100 that is at most 5 pages, so the 10-page cap cannot trigger for this call. The same helper and cap already serve the supported-ecosystem path.

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