Answers the question left at the end of #462: "Worth deciding whether that wants its own issue or belongs on #454." Its own issue — the shape is #454's, but the mechanism is different and it is in both client builders, not one.
What happens
build_http_client_for_fetch (doiget-mcp/src/lib.rs) and build_http_client (doiget-cli/src/commands/fetch.rs) are hand-maintained twins with the same two halves:
if arxiv.is_none() && arxiv_src.is_none() && crossref.is_none()
&& unpaywall.is_none() && oa_publisher.is_none()
&& openalex_base.is_none() && ar5iv_base.is_none()
{
// ... tier_1 + oa_publisher + tier_2 + fulltext + tier_3 + user-extension
return HttpClient::new(allowlists);
}
// otherwise: rebuild from scratch
for (source, base) in [
("arxiv", …), ("crossref", …), ("unpaywall", …),
("oa-publisher", …), ("openalex", …), ("ar5iv", …),
] { … }
Ok(HttpClient::new_for_tests_allow_http_multi(&entries))
Any one of the seven sends you down the second path, and the second path knows about six source keys. Everything else is gone — not overridden, absent from the client's map, so the next request under one of those keys is HttpError::UnknownSource.
Measured against what the production branch registers:
| dropped on override |
where it comes from |
datacite, hal, openaire, core, europe-pmc |
tier_2_allowlist() |
tdm-aps, tdm-elsevier, tdm-springer, tdm-ieee |
tier_3_allowlists() |
| every user-curated host |
ADR-0028 D2 config merge |
And the two halves have drifted from each other as well as from the orchestrator, which honours ten base overrides (grep -o 'optional_base("DOIGET_[A-Z_]*' crates/doiget-core/src/orchestrator.rs): APS, CORE, DATACITE, ELSEVIER, EUROPE_PMC, HAL, IEEE, OPENAIRE, OPENALEX, SPRINGER. DOIGET_APS_BASE appears in neither the condition nor the list, so it can be set and honoured by the source while the client has no allowlist for the key it will use.
How it surfaced
The #[ignore]d Tier-3 reproduction added by #577 (doiget-mcp/tests/fetch_paper_e2e.rs). It sets DOIGET_ARXIV_BASE and DOIGET_APS_BASE; the first flips to the override path, the second is not in it:
"detail": "network error: no allowlist registered for source tdm-aps"
That is why the suite cannot cover tdm_fetched — the one route the Tier-3 tier exists for.
Severity
Not a production break. Nothing sets a DOIGET_*_BASE in normal use, so the production branch runs and every key is registered. It bites (a) the e2e suite, which is where #462 wanted coverage, and (b) anyone pointing doiget at a mirror or proxy for one source, who silently loses every other source's allowlist.
The fix worth making
Not "add the missing keys to the list" — that leaves three hand-maintained lists to keep in agreement, which is the defect. The override should be additive: build the production allowlists as usual, then swap in the mock host (and the plain-HTTP-permitting client) for whichever sources have a base set. Nothing is dropped, so nothing has to be kept in sync.
That needs a constructor in doiget-core::http alongside new_for_tests_allow_http_multi, and both twins rewired to it. Expanding the condition instead would be a regression: today DOIGET_APS_BASE alone keeps the production path and works.
Answers the question left at the end of #462: "Worth deciding whether that wants its own issue or belongs on #454." Its own issue — the shape is #454's, but the mechanism is different and it is in both client builders, not one.
What happens
build_http_client_for_fetch(doiget-mcp/src/lib.rs) andbuild_http_client(doiget-cli/src/commands/fetch.rs) are hand-maintained twins with the same two halves:Any one of the seven sends you down the second path, and the second path knows about six source keys. Everything else is gone — not overridden, absent from the client's map, so the next request under one of those keys is
HttpError::UnknownSource.Measured against what the production branch registers:
datacite,hal,openaire,core,europe-pmctier_2_allowlist()tdm-aps,tdm-elsevier,tdm-springer,tdm-ieeetier_3_allowlists()And the two halves have drifted from each other as well as from the orchestrator, which honours ten base overrides (
grep -o 'optional_base("DOIGET_[A-Z_]*' crates/doiget-core/src/orchestrator.rs):APS,CORE,DATACITE,ELSEVIER,EUROPE_PMC,HAL,IEEE,OPENAIRE,OPENALEX,SPRINGER.DOIGET_APS_BASEappears in neither the condition nor the list, so it can be set and honoured by the source while the client has no allowlist for the key it will use.How it surfaced
The
#[ignore]d Tier-3 reproduction added by #577 (doiget-mcp/tests/fetch_paper_e2e.rs). It setsDOIGET_ARXIV_BASEandDOIGET_APS_BASE; the first flips to the override path, the second is not in it:That is why the suite cannot cover
tdm_fetched— the one route the Tier-3 tier exists for.Severity
Not a production break. Nothing sets a
DOIGET_*_BASEin normal use, so the production branch runs and every key is registered. It bites (a) the e2e suite, which is where #462 wanted coverage, and (b) anyone pointing doiget at a mirror or proxy for one source, who silently loses every other source's allowlist.The fix worth making
Not "add the missing keys to the list" — that leaves three hand-maintained lists to keep in agreement, which is the defect. The override should be additive: build the production allowlists as usual, then swap in the mock host (and the plain-HTTP-permitting client) for whichever sources have a base set. Nothing is dropped, so nothing has to be kept in sync.
That needs a constructor in
doiget-core::httpalongsidenew_for_tests_allow_http_multi, and both twins rewired to it. Expanding the condition instead would be a regression: todayDOIGET_APS_BASEalone keeps the production path and works.