Context
portfolio-queue.js's #5563 migration gave the store a composite primary key (api_base_url, repo_full_name, identifier) specifically so two forge hosts serving a same-named owner/repo never collide. But two WIP-cap/selection code paths built on top of that store never picked up the host dimension:
entriesToPortfolioQueue (packages/loopover-miner/lib/portfolio-queue-manager.js:48-76) buckets active rows by const repoKey = repoFullName.toLowerCase() (line 59) — no apiBaseUrl in the key — before handing buckets to the engine's nextEligibleItems for queue claim-batch.
selectNextEligibleTarget (packages/loopover-miner/lib/portfolio-queue-cli.js:113-126) computes repoActiveCount via entries.filter((entry) => entry.status === "in_progress" && entry.repoFullName === topQueued.repoFullName) (lines 121-123) — again no apiBaseUrl filter — for queue next --per-repo-wip.
Verified empirically: with two items on acme/widgets, one on https://api.github.com and one on https://ghe.example.com/api/v3, and perRepoWipCap: 1:
selectEligibleBatch(entries, { globalWipCap: 4, perRepoWipCap: 1 }) returns only 1 of the 2 items (should be 2 — they're on different hosts).
selectNextEligibleTarget(entries, { globalWipCap: 10, perRepoWipCap: 1 }), given host A has an in_progress row on acme/widgets and host B has a queued row on acme/widgets, returns [] — host B's completely independent, unclaimed item is blocked by unrelated in-progress work on host A.
This isn't a cold-path edge case: queue claim-batch's own default caps are { globalWipCap: 1, perRepoWipCap: 1 } (portfolio-queue-cli.js:409), so this triggers on the out-of-the-box default the moment an operator tracks the same-named repo on two forge hosts — exactly the scenario #5563 was built to support. Precedent: #6764 (closed, milestone "Miner Wave 4.5 — AMS Hardening Round 2") fixed this exact bug class (an apiBaseUrl/forge-host scoping gap left behind by #5563) in manage-poll.js.
Requirements
entriesToPortfolioQueue's bucket key must incorporate apiBaseUrl (e.g. a compound key of apiBaseUrl + lowercased repoFullName), not repoFullName alone.
selectNextEligibleTarget's repoActiveCount filter must additionally match entry.apiBaseUrl === topQueued.apiBaseUrl.
- Existing single-host behavior (the overwhelming common case) must be byte-identical — these are additive filter/key changes only.
Deliverables
Test Coverage Requirements
packages/loopover-miner/lib/**/*.js is covered by Codecov's 99% patch-coverage gate. Both changed functions are pure and synchronous — 100% line+branch coverage on the new/changed conditionals is achievable with the two regression tests above plus the existing single-host tests continuing to pass unchanged.
Expected Outcome
An operator tracking a same-named repo across two forge hosts (e.g. github.com + a GitHub Enterprise instance) gets independent, correctly-scoped WIP-cap enforcement and diversification per host — unrelated in-progress/queued work on one host never throttles or starves the other.
Links & Resources
Context
portfolio-queue.js's #5563 migration gave the store a composite primary key(api_base_url, repo_full_name, identifier)specifically so two forge hosts serving a same-named owner/repo never collide. But two WIP-cap/selection code paths built on top of that store never picked up the host dimension:entriesToPortfolioQueue(packages/loopover-miner/lib/portfolio-queue-manager.js:48-76) buckets active rows byconst repoKey = repoFullName.toLowerCase()(line 59) — noapiBaseUrlin the key — before handing buckets to the engine'snextEligibleItemsforqueue claim-batch.selectNextEligibleTarget(packages/loopover-miner/lib/portfolio-queue-cli.js:113-126) computesrepoActiveCountviaentries.filter((entry) => entry.status === "in_progress" && entry.repoFullName === topQueued.repoFullName)(lines 121-123) — again noapiBaseUrlfilter — forqueue next --per-repo-wip.Verified empirically: with two items on
acme/widgets, one onhttps://api.github.comand one onhttps://ghe.example.com/api/v3, andperRepoWipCap: 1:selectEligibleBatch(entries, { globalWipCap: 4, perRepoWipCap: 1 })returns only 1 of the 2 items (should be 2 — they're on different hosts).selectNextEligibleTarget(entries, { globalWipCap: 10, perRepoWipCap: 1 }), given host A has anin_progressrow onacme/widgetsand host B has aqueuedrow onacme/widgets, returns[]— host B's completely independent, unclaimed item is blocked by unrelated in-progress work on host A.This isn't a cold-path edge case:
queue claim-batch's own default caps are{ globalWipCap: 1, perRepoWipCap: 1 }(portfolio-queue-cli.js:409), so this triggers on the out-of-the-box default the moment an operator tracks the same-named repo on two forge hosts — exactly the scenario #5563 was built to support. Precedent: #6764 (closed, milestone "Miner Wave 4.5 — AMS Hardening Round 2") fixed this exact bug class (an apiBaseUrl/forge-host scoping gap left behind by #5563) inmanage-poll.js.Requirements
entriesToPortfolioQueue's bucket key must incorporateapiBaseUrl(e.g. a compound key ofapiBaseUrl+ lowercasedrepoFullName), notrepoFullNamealone.selectNextEligibleTarget'srepoActiveCountfilter must additionally matchentry.apiBaseUrl === topQueued.apiBaseUrl.Deliverables
entriesToPortfolioQueuebuckets by(apiBaseUrl, repoFullName)selectNextEligibleTarget's per-repo active count is host-scopedperRepoWipCap: 1with two same-named-repo items on different hosts both become eligible (mirrors the existingclaimNextBatchScope local ledger keys by forge host, not bare repoFullName #5563 regression test attest/unit/miner-portfolio-queue-manager.test.ts:155-171, but with a cap that actually binds — that existing test usesperRepoWipCap: 2, which passes regardless of whether hosts are conflated, so it doesn't catch this)selectNextEligibleTargetwith an in-progress item on host A does not block a queued item with the samerepoFullNameon host BTest Coverage Requirements
packages/loopover-miner/lib/**/*.jsis covered by Codecov's 99% patch-coverage gate. Both changed functions are pure and synchronous — 100% line+branch coverage on the new/changed conditionals is achievable with the two regression tests above plus the existing single-host tests continuing to pass unchanged.Expected Outcome
An operator tracking a same-named repo across two forge hosts (e.g. github.com + a GitHub Enterprise instance) gets independent, correctly-scoped WIP-cap enforcement and diversification per host — unrelated in-progress/queued work on one host never throttles or starves the other.
Links & Resources
packages/loopover-miner/lib/portfolio-queue-manager.js:48-76packages/loopover-miner/lib/portfolio-queue-cli.js:103-126,408-409test/unit/miner-portfolio-queue-manager.test.ts:155-171(the existing, non-binding Scope local ledger keys by forge host, not bare repoFullName #5563 regression test)manage-poll.js