Skip to content

Promotion: next → main for 0.9.0 - #649

Merged
sotashimozono merged 109 commits into
mainfrom
next
Sep 29, 2026
Merged

sotashimozono merged 109 commits into
mainfrom
next

Conversation

@sotashimozono

@sotashimozono sotashimozono commented Sep 29, 2026 •

Copy link
Copy Markdown
Member

Promotion of everything on next since 0.8.13, for review. Updated after four review rounds (#650, #651, #652, #653).

This PR ships 0.9.0. next carries the clean 0.9.0, cut in #653. That turns ## [Unreleased] into ## [0.9.0] - 2026-09-29, and release-version-gate.sh v0.9.0 passes G1–G6. Merging this makes main the 0.9.0 tree. The release itself is the signed v0.9.0 tag pushed afterwards (ADR-0025). The tracking files (.claude-plugin/*.json, .mcp.json, and the Homebrew formula) follow once the release exists.

The full list is in CHANGELOG.md, under ## [Unreleased] (#648).

What is in it

New commands and ref kinds

Preprint discovery for closed DOIs (#636, #640, #642, #644; ADR-0062–0065)

Sources tried:

  • Crossref has-preprint
  • OpenAlex
  • INSPIRE-HEP
  • NASA ADS, with the user's own token
  • arXiv title/author search
  • bioRxiv / medRxiv pubs

The resulting PDF-leg statuses are preprint_fallback and preprint_doi_fallback.

Sources

Citation output

Robustness

Distribution

Decisions (ADR only)

Fixes from the four review rounds of this PR

The security and LEGAL review found nothing.

Issues this closes on merge

Closes #500, closes #501, closes #507, closes #547, closes #587, closes #590, closes #594, closes #605, closes #606, closes #607, closes #608, closes #609, closes #610, closes #611, closes #613, closes #614, closes #636, closes #638, closes #640, closes #642, closes #644, closes #646, closes #462

Not closed by this PR:

Known gaps

  • Six earlier commits on next have no DCO Signed-off-by.
  • The bioRxiv live row was dropped from the nightly suite because GitHub runner IPs fail it (ADR-0063).

🤖 Generated with Claude Code

sotashimozono and others added 30 commits September 1, 2026 17:24
The four files that record the last published stable, bumped together as
CONTRIBUTING describes. They cannot be written before the release exists:
the formula pins the binaries by sha256, and .mcp.json pins the npm version
the plugin runs.

Formula regenerated from the v0.8.13 release's own .sha256 assets, not by
hand. Verified: update-homebrew-formula.test.sh passes, the release-sync
posture check reports all four agreeing on 0.8.13 with a CHANGELOG section
to match, and [email protected] is live on npm so the pin is not a 404.

Signed-off-by: Sota Shimozono <[email protected]>
…ctory with 6 updates

Bumps the actions-minor-and-patch group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [github/codeql-action/init](https://github.com/github/codeql-action) | `4.37.9` | `4.38.1` |
| [github/codeql-action/autobuild](https://github.com/github/codeql-action) | `4.37.9` | `4.38.1` |
| [github/codeql-action/analyze](https://github.com/github/codeql-action) | `4.37.9` | `4.38.1` |
| [taiki-e/install-action](https://github.com/taiki-e/install-action) | `2.87.0` | `2.87.15` |
| [codecov/codecov-action](https://github.com/codecov/codecov-action) | `7.0.0` | `7.1.1` |
| [crate-ci/typos](https://github.com/crate-ci/typos) | `1.50.0` | `1.50.2` |



Updates `github/codeql-action/init` from 4.37.9 to 4.38.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@cdf488f...1c5b675)

Updates `github/codeql-action/autobuild` from 4.37.9 to 4.38.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@cdf488f...1c5b675)

Updates `github/codeql-action/analyze` from 4.37.9 to 4.38.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](github/codeql-action@cdf488f...1c5b675)

Updates `taiki-e/install-action` from 2.87.0 to 2.87.15
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](taiki-e/install-action@37f7c57...4076c08)

Updates `codecov/codecov-action` from 7.0.0 to 7.1.1
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](codecov/codecov-action@fb8b358...303a32d)

Updates `crate-ci/typos` from 1.50.0 to 1.50.2
- [Release notes](https://github.com/crate-ci/typos/releases)
- [Changelog](https://github.com/crate-ci/typos/blob/main/CHANGELOG.md)
- [Commits](crate-ci/typos@4d9c206...512fc24)

---
updated-dependencies:
- dependency-name: github/codeql-action/init
  dependency-version: 4.38.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-minor-and-patch
- dependency-name: github/codeql-action/autobuild
  dependency-version: 4.38.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-minor-and-patch
- dependency-name: github/codeql-action/analyze
  dependency-version: 4.38.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-minor-and-patch
- dependency-name: taiki-e/install-action
  dependency-version: 2.87.15
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-minor-and-patch
- dependency-name: codecov/codecov-action
  dependency-version: 7.1.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions-minor-and-patch
- dependency-name: crate-ci/typos
  dependency-version: 1.50.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions-minor-and-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
…ctory with 9 updates

Bumps the cargo-minor-and-patch group with 9 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [clap](https://github.com/clap-rs/clap) | `4.6.6` | `4.6.7` |
| [thiserror](https://github.com/dtolnay/thiserror) | `2.0.20` | `2.0.21` |
| [reqwest](https://github.com/seanmonstar/reqwest) | `0.13.4` | `0.13.5` |
| [rustls](https://github.com/rustls/rustls) | `0.23.43` | `0.23.45` |
| [camino](https://github.com/camino-rs/camino) | `1.2.5` | `1.2.6` |
| [serde](https://github.com/serde-rs/serde) | `1.0.228` | `1.0.229` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.26.0` | `1.26.1` |
| [futures-util](https://github.com/rust-lang/futures-rs) | `0.3.32` | `0.3.34` |
| [rmcp](https://github.com/modelcontextprotocol/rust-sdk) | `3.1.4` | `3.4.1` |



Updates `clap` from 4.6.6 to 4.6.7
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/main/CHANGELOG.md)
- [Commits](clap-rs/clap@clap_complete-v4.6.6...clap_complete-v4.6.7)

Updates `thiserror` from 2.0.20 to 2.0.21
- [Release notes](https://github.com/dtolnay/thiserror/releases)
- [Commits](dtolnay/thiserror@2.0.20...2.0.21)

Updates `reqwest` from 0.13.4 to 0.13.5
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](seanmonstar/reqwest@v0.13.4...v0.13.5)

Updates `rustls` from 0.23.43 to 0.23.45
- [Release notes](https://github.com/rustls/rustls/releases)
- [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md)
- [Commits](rustls/rustls@v/0.23.43...v/0.23.45)

Updates `camino` from 1.2.5 to 1.2.6
- [Release notes](https://github.com/camino-rs/camino/releases)
- [Changelog](https://github.com/camino-rs/camino/blob/main/CHANGELOG.md)
- [Commits](camino-rs/camino@camino-1.2.5...camino-1.2.6)

Updates `serde` from 1.0.228 to 1.0.229
- [Release notes](https://github.com/serde-rs/serde/releases)
- [Commits](serde-rs/serde@v1.0.228...v1.0.229)

Updates `uuid` from 1.26.0 to 1.26.1
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](uuid-rs/uuid@v1.26.0...v1.26.1)

Updates `futures-util` from 0.3.32 to 0.3.34
- [Release notes](https://github.com/rust-lang/futures-rs/releases)
- [Changelog](https://github.com/rust-lang/futures-rs/blob/main/CHANGELOG.md)
- [Commits](rust-lang/futures-rs@0.3.32...0.3.34)

Updates `rmcp` from 3.1.4 to 3.4.1
- [Release notes](https://github.com/modelcontextprotocol/rust-sdk/releases)
- [Changelog](https://github.com/modelcontextprotocol/rust-sdk/blob/main/release-plz.toml)
- [Commits](modelcontextprotocol/rust-sdk@rmcp-v3.1.4...rmcp-v3.4.1)

---
updated-dependencies:
- dependency-name: clap
  dependency-version: 4.6.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-minor-and-patch
- dependency-name: thiserror
  dependency-version: 2.0.21
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-minor-and-patch
- dependency-name: reqwest
  dependency-version: 0.13.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-minor-and-patch
- dependency-name: rustls
  dependency-version: 0.23.45
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-minor-and-patch
- dependency-name: camino
  dependency-version: 1.2.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-minor-and-patch
- dependency-name: serde
  dependency-version: 1.0.229
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-minor-and-patch
- dependency-name: uuid
  dependency-version: 1.26.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-minor-and-patch
- dependency-name: futures-util
  dependency-version: 0.3.34
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: cargo-minor-and-patch
- dependency-name: rmcp
  dependency-version: 3.4.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: cargo-minor-and-patch
...

Signed-off-by: dependabot[bot] <[email protected]>
chore: back-merge main into next after 0.8.13
chore(release): tracking files for 0.8.13
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: sotashimozono <[email protected]>
…ctions-minor-and-patch-7e761db225' into chore/pending-dependency-bumps-0.8.14
…r-and-patch-af8bd5da41' into chore/pending-dependency-bumps-0.8.14
…p to 0.8.14-beta.2

rmcp 3.4 deprecates the ServerInfo alias in favour of ServerConfig (same
type, InitializeResult); under -D warnings that failed every clippy, test,
rustdoc and msrv job on #602. Raise the rmcp floor to 3.4, where the new
name first exists.

supply-chain: bump the existing exemptions to the new versions and add
base64 0.23.1 (new, via rmcp) and serde/serde_core/serde_derive 1.0.229
(previously covered by imported audits only).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: sotashimozono <[email protected]>
chore: retarget next to 0.8.14-beta.1 after the 0.8.13 promotion
…-0.8.14

chore(deps): the two pending Dependabot bumps, with the rmcp ServerConfig migration
…resolve

`codes.sota-shimozono.com` resolves to `sinkhole.paloaltonetworks.com` (198.135.184.22) and
answers nothing — not a 404, no connection at all. The documentation is served from GitHub Pages
at `qatlashub.github.io`, and has been all along.

Both targets resolve: `qatlashub.github.io/doiget/` and `/doiget/dev/` return 200.

Tracked org-wide in QAtlasHub/.github#23.

Co-Authored-By: Claude Opus 5 <[email protected]>
Signed-off-by: sotashimozono <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: sotashimozono <[email protected]>
…holed-host

docs: the README's documentation links point at a host that does not resolve
This cycle adds new subcommands (add, missing, coverage, sources), which
under the 0.x policy warrants a minor step (ADR-0033 D4 precedent).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: sotashimozono <[email protected]>
…ored (#613)

An exported-but-empty DOIGET_STORE_ROOT (export X=${X:-} in a script) is
already treated as unset since #369, on both the CLI and MCP resolvers.
What was left: the CLI resolver had no test pinning that, and
config doctor reported the fallback exactly as if nothing had been set,
so a script author could not tell their export had been dropped.

Extract usable_store_root_env() in the CLI resolver, test it, and have
doctor print a note naming the ignored value.

Closes #613

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: sotashimozono <[email protected]>
- --store-root rejects whitespace-only and ${...} values at parse time,
  as it already rejected "". The resolver treats them as unset, so a
  typed flag was silently dropped for the cwd default -- and because the
  flag is applied by writing DOIGET_STORE_ROOT, doctor's note blamed the
  environment for the flag's value. Rejected here, an ignored store root
  can only have come from the environment, which is what the note says.
- config show --mode json carries store_root_env_ignored, so a script
  can tell an export that was dropped from a variable never set.
- Tests: the resolver itself (not only the helper) under "", "   " and
  a placeholder; config doctor e2e with and without the empty export;
  --store-root rejecting both new shapes.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: sotashimozono <[email protected]>
…e author wrote (#609)

AIP and others deposit inline JATS / MathML onto lines of their own, so
Crossref serves 'the P\n    <scp>y</scp>\n    SCF program package'.
The render-time tag scrubber dropped the tags but kept the indentation,
and BibTeX printed 'P y SCF'.

Stripping tags is not enough: the pretty-printer's line-break runs say
nothing about whether the author wrote a space there (P<scp>y</scp>SCF
is one word, 'in <i>γ</i>-InSe' is two). New doiget_core::markup::
plain_title decides each run from its neighbours -- a word inside the
element is spaced from a neighbouring word, a symbol is joined unless
the neighbour is a lower-case word, sentence punctuation or an operator
-- pinned against 14 real 10.1063 deposits. It also decodes entities
(including Crossref's double-escaped &amp;gt;), and replaces the old
scrubber, which deleted everything between any '<' and the next '>'
(so 'a < b and c > d' lost ' b and c ').

Applied when metadata is built, so the store holds the clean title, and
again at BibTeX / CSL render, so entries already stored come out clean.
doiget lint gains a title_markup warning for .bib files pasted from an
older cite. $...$ is untouched.

A re-fetch also replaces a title / venue stored with raw markup, but
only with its own cleaned form: STORE.md §6 still preserves any value
that differs in content.

Closes #609

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: sotashimozono <[email protected]>
fix(config): config doctor says when DOIGET_STORE_ROOT is set but ignored (#613)
- markup::tag_len took '<' + a letter up to the NEXT '>' anywhere as a
  tag, so 'T<Tc in samples with applied field H>Hc2' became 'THc2' --
  and since #609 cleans titles at store time, that corrupted persisted
  metadata for titles with no markup at all. A tag now has to parse: a
  name, then only name="value" attributes before '>'; an opening tag
  must also be a known inline element, namespaced (mml:, jats:), or have
  its </name later. 'x<y>z' and 'a<b and c>d' stay text.
- The store merge exception applied whenever incoming == plain_title(
  existing); it now also requires existing to carry markup, so the
  cleaning rule can never be why a markup-free stored title is replaced.
- Tests: the reviewer's inequality titles through plain_title, BibTeX and
  the merge; CSL title/container cleaning (was untested); the venue half
  of the merge's preserve branch; and a store-level test that a
  pretty-printed Crossref title is written to the TOML as plain text,
  which render-time cleaning could not mask.
- is_cleaned_form had been inserted between merge_metadata's doc comment
  and its fn, so rustdoc gave that comment to the wrong function.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: sotashimozono <[email protected]>
…bled source, flag the rest (#608)

Some Crossref records carry U+FFFD where the publisher's deposit lost
its umlauts ('Zeitschrift f\u{FFFD}r Physik'). cite passed them through
silently; the entry compiles, so the damage showed only in the rendered
bibliography.

New doiget_core::metadata_quality:

- detects U+FFFD in title, authors, venue, publisher and abstract;
- repairs title and venue from Semantic Scholar / OpenAlex, and ONLY
  when that source is already enabled (DOIGET_ENABLE_S2 /
  DOIGET_ENABLE_OPENALEX) -- no new network by default;
- accepts a candidate only when it matches with each U+FFFD standing for
  one or two characters and every other character equal. OpenAlex
  ingests Crossref and carries the same loss, and its venue for these
  papers is the journal's later name ('The European Physical Journal
  A'); the guard refuses both rather than swapping in a different fact.
  Measured 2026-09-29: S2 has 'Näherungsmethode zur Lösung...' for
  10.1007/BF01340294;
- records repairs as [doiget].repaired_fields = { title = "semantic_scholar" }.

Surfaces: cite prints a note per repair and a warning naming what is
left (and the switch that would let it try); fetch_paper (CLI and MCP)
repairs before the store write and the MCP envelope gains
metadata_quality ['replacement_char:venue'] and repaired_fields; the
metadata-only store path repairs too; verify adds metadata_quality to a
valid record (informational, never an exit-code failure); lint gains a
replacement_char warning for .bib files checked offline.

Closes #608

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: sotashimozono <[email protected]>
fix(cite): reduce pretty-printed JATS markup in titles to the text the author wrote (#609)
- A later repair never reached the store. Fetched once with no repair
  source, the damaged title was stored; fetched again with S2 enabled,
  the repair succeeded in memory and merge_metadata kept the stored value
  (STORE.md 6) -- while repaired_fields, in the doiget-owned table, said
  it had been applied. is_cleaned_form now also accepts an incoming value
  that restores() the stored one; a different fact is still preserved.
- metadata_only_to_store repaired the file but told its caller nothing:
  its outcome carries the raw resolver payload and the only signal was a
  tracing warning on stderr, which the MCP stdio transport never shows an
  agent. MetadataOnlyOutcome gains metadata_quality / repaired_fields
  (serde-defaulted, so cached outcomes still read) and the MCP
  metadata-only envelope emits them; docs/MCP_TOOLS.md 11 says so.
- Tests for the surfaces that had none: the MCP fetch envelope (keys
  always present; flagged for a damaged title), the metadata-only store
  path, and through the real binary the fetch warning and verify's
  metadata_quality on a valid row.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: sotashimozono <[email protected]>
Store does synchronous filesystem I/O -- a write can poll the advisory
lock for up to 5 s in 50 ms std::thread::sleep steps, then fsync, which
on a Dropbox / OneDrive / SMB store root costs hundreds of milliseconds
uncontended. Ten call sites made those calls straight from an async fn
(the orchestrator's store write and nine MCP handlers), each holding a
tokio worker for the whole duration and delaying the rate limiter's
timers and every other in-flight tool call on it.

New doiget_core::store::blocking_section runs the call under
tokio::task::block_in_place on a multi-thread runtime (doiget serve, the
CLI), which hands the worker's other tasks to another thread first, and
inline elsewhere (no runtime, or current-thread, where block_in_place
would panic). block_in_place rather than spawn_blocking because the
orchestrator holds the store as &dyn Store: moving it into a 'static
closure would change fetch_paper's public signature, the same cost the
issue ruled out for an async trait.

Tests: on a one-worker runtime a 300 ms blocking call inside the helper
lets a ticking task run (>= 20 ticks) while the same call made directly
starves it (<= 3, the control that proves the test can fail); and a
source scan in each crate fails if any store.read / write /
write_user_authored / list_recent / search in non-test code is not
wrapped. docs/STORE.md 7a states the convention the trait cannot.

Closes #590

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: sotashimozono <[email protected]>
feat(cite): detect U+FFFD in resolver metadata, repair it from an enabled source, flag the rest (#608)
- Async CLI code still called the store directly: cite's store fallback
  (bib_from_store, reached from the async run after a failed resolve)
  and search's local path (run_local, from the async search::run). Both
  now go through blocking_section.
- The source scans covered only orchestrator.rs and the MCP server, and
  matched whitespace-collapsed text, so a method chain split across lines
  (store\n    .read() -- exactly how both CLI calls were written --
  slipped through. They now strip whitespace entirely, include
  search_by_tag, and a new doiget-cli scan walks src/commands/ at test
  time, requiring every store call in a file with async code to be
  wrapped; a new command file is covered without being listed.
- The responsiveness test asserted >= 20 ticks of a 5 ms ticker in
  300 ms, above what Windows' ~15.6 ms timer granularity allows. Now
  >= 8 in 500 ms; the direct-blocking control (<= 3) is what gives the
  bound its meaning.
- docs/STORE.md 7a says which call sites the scans cover.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: sotashimozono <[email protected]>
posture-lint forbids any `.matches(&` under crates/ so host adjudication
can only go through permits(); the #590 source-scan tests counted
substrings with str::matches(&format!(..)) and failed network-purity. The
count is the same through match_indices(&*format!(..)).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: sotashimozono <[email protected]>
- metadata_quality: a public doc linked the private CHECKED_FIELDS
  (rustdoc -D warnings), and a test's deliberately damaged word read as a
  typo. Both were merged with #608.
- site/content/ regenerated by scripts/sync_docs_to_site.sh for the
  docs/STORE.md and docs/MCP_TOOLS.md edits this stack made.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: sotashimozono <[email protected]>
fix(store): no Store call from async code holds a tokio worker (#590)
sotashimozono and others added 8 commits September 29, 2026 17:27
feat(fetch): INSPIRE-HEP as a preprint finder -- a closed DOI's arXiv id (#642, ADR-0064)
…644, ADR-0065)

Part of #474, Gap 2. ADS holds a published DOI's arXiv id among a
record's identifiers.

- A fifth ADR-0062 finder, after INSPIRE and before the arXiv title
  search: /v1/search/query?q=doi:"<doi>"&fl=identifier&rows=1, reading
  the arXiv:<id> identifier only (found_by: ads).
- The user's own token is the opt-in: DOIGET_ADS_TOKEN, sent only as
  Authorization: Bearer (preprint::logged_get_with -- header values go
  on the wire, never into the provenance row). No token, no request.
- Within ADS's API terms: one lookup per closed DOI the user fetches,
  nothing stored or redistributed but the arXiv id for that fetch, no
  ADS full-text link followed, one request a second (5,000/day quota).
- Primary sources quoted in #644 / ADR-0065, read from ADS's own
  published help and API sources (the web pages answer scripts 202 +
  empty body).
- Docs: ADR-0065, LEGAL 2a (a-ii), SOURCES, PRIVACY, CAPABILITY,
  REDIRECT_ALLOWLIST 4; doiget_capability_profile lists ads.

Closes #644

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: sotashimozono <[email protected]>
A 401/403 from ADS fell through to the next finder at info level, so a
bad DOIGET_ADS_TOKEN failed silently. It is now a warn that says the
token was refused and to check it; the fetch still continues with the
next finder.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: sotashimozono <[email protected]>
…its OA PDFs, doiget blocked them (#646, ADR-0066)

Part of #474, Gap 3. Six of eight J-STAGE DOIs sampled from J-STAGE's
search API have an Unpaywall gold/bronze location on
www.jstage.jst.go.jp, which J-STAGE serves to a scripted client -- and
doiget refused every one: the host was on no allowlist.

- www.jstage.jst.go.jp joins the opt-in trust_oa_registries set, not the
  default oa-publisher list. J-STAGE's terms permit private use and
  prohibit 'downloading a large amount of the Registered Data using
  mechanical or equivalent means'; the opt-in keeps a large batch the
  user's explicit choice, and CONFIG.md quotes the provision.
- The set's rule is widened in the open (ADR-0066): registries and
  repositories, or a national open-access platform whose default is
  free access -- never a commercial publisher platform. Only locations
  Unpaywall reports as open are followed, and a restricted J-STAGE
  article's login page fails the %PDF- check.
- Live row: 10.3327/jaesjb.66.4_185 fetches with trust_oa_registries.

Closes #646

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: sotashimozono <[email protected]>
feat(fetch): NASA ADS as a preprint finder, on the user's own token (#644, ADR-0065)
… passes again

Every PR this cycle has failed the advisory version-check at G5: the
CHANGELOG had no section for 0.9.0 and no Unreleased one. This adds the
Unreleased notes for betas 1-27 -- preprint discovery (#636, #640,
#642, #644), PubMed ids (#500, #638), @software citations (#614),
citekeys and short journals (#610, #611), missing / coverage / add
(#607, #605, #606), repeat suppression (#507), install staleness
(#594), binstall and Nix (#501), J-STAGE (#646), the live suite
(#462), the fixes and the ADRs. scripts/release-version-gate.sh passes
for v0.9.0-beta.27 locally.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: sotashimozono <[email protected]>
feat(allowlist): J-STAGE on trust_oa_registries (#646, ADR-0066)
docs(changelog): the 0.9.0 cycle under Unreleased
…tags, a real MSRV job

- doiget add stops on an unreadable store entry instead of writing over
  it; --from-dir and missing report one rather than dropping it silently.
- add's exit codes follow docs/ERRORS.md §4 (they were swapped).
- A GitHub tag with .., %, ?, # or whitespace is refused, from the URL
  and from the latest-release answer.
- PdfLegStatus found_by is the FoundBy enum (with Unpaywall), not a String.
- Declared MSRV 1.86 -> 1.88: rmcp 3.x already needed it, and the msrv
  job never saw it because rust-toolchain.toml overrode its toolchain.
  Both MSRV jobs now pin with RUSTUP_TOOLCHAIN.
- preprint_allowlist's doc names each finder's own gate.
- Tests: failed arXiv fallback keeps the block (404 / not a PDF), add's
  misuse / unreadable-entry / unresolved exits, coverage's error path.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: sotashimozono <[email protected]>
fix: #649 review round 1 -- add's store reads and exit codes, GitHub tags, a real MSRV job
github-actions Bot added a commit that referenced this pull request Sep 29, 2026
fix: #649 review round 1 -- add's store reads and exit codes, GitHub tags, a real MSRV job a9314c5
…e tool/doc text

- doiget batch: one lock per ref, so two copies of a DOI in the same
  concurrent window run in turn and the second is a repeat-suppression
  replay; the regression test fails without it.
- doiget_paper_pdf_path's tool description lists origin and license.
- ERRORS.md §4: exit 255 also caps add --from-dir --apply.
- PROVENANCE_LOG.md: `source` is a string naming the source key,
  including the lookups this cycle added.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: sotashimozono <[email protected]>
fix: #649 review round 2 -- batch fetched a duplicate ref twice, stale tool/doc text
github-actions Bot added a commit that referenced this pull request Sep 29, 2026
fix: #649 review round 2 -- batch fetched a duplicate ref twice, stale tool/doc text a6fbe66
…BLIC_API scope

- RepeatIndex sweeps entries past REPLAY_WINDOW when a new one lands,
  so doiget serve no longer keeps one per refused ref for its lifetime.
- config_fingerprint's config.toml read is a blocking_section.
- metadata_quality::restores refuses strings over 2,000 chars and
  lengths the U+FFFDs cannot explain, and reuses its two buffers.
- PUBLIC_API.md §1a: orchestrator / preprint / pubmed / software /
  user_pdf / repeat / metadata_quality are public for the binaries, not
  semver-locked; CHANGELOG notes the found_by type change.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: sotashimozono <[email protected]>
fix: #649 review round 3 -- bounded repeat index and repair match, PUBLIC_API scope
@sotashimozono sotashimozono changed the title Promotion: next → main (0.9.0-beta.27, no cut yet) Promotion: next → main (0.9.0-beta.30, no cut yet) Sep 29, 2026
github-actions Bot added a commit that referenced this pull request Sep 29, 2026
fix: #649 review round 3 -- bounded repeat index and repair match, PUBLIC_API scope 469370d
Review findings:
- cite / metadata_only never asked DataCite: metadata_only_doi went from
  Crossref straight to Unpaywall, so #614's Zenodo concept-DOI choice and
  --zenodo-version could not run. An enabled DataCite is asked when
  Crossref has no record; found by the new zenodo_cite_e2e test.
- Types keep what they checked: PreprintDoiFallback.preprint_doi is a Doi,
  PubmedId holds pubmed::Digits, GithubRef's fields are private.
- RepeatIndex sweeps only once the map has doubled (amortised O(1)).
- add --from-dir names unreadable / non-UTF-8 directory entries.
- Simplifications: GithubRef::repo_path, one landing_url in commands/mod,
  one has_pdf in add, missing::run split into row helpers.
- Tests: Zenodo cite (concept by default, --zenodo-version, DataCite off
  is not asked), INSPIRE asked before ADS, trust_oa_registries alone
  admits J-STAGE offline.

Cut: 0.9.0-beta.30 -> 0.9.0; [Unreleased] -> [0.9.0] - 2026-09-29.
release-version-gate.sh v0.9.0: G1-G6 PASS, G7 skipped (no tag yet).

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: sotashimozono <[email protected]>
fix: close the #649 review round 4, and cut 0.9.0
github-actions Bot added a commit that referenced this pull request Sep 29, 2026
fix: close the #649 review round 4, and cut 0.9.0 7344ec3
@sotashimozono sotashimozono changed the title Promotion: next → main (0.9.0-beta.30, no cut yet) Promotion: next → main for 0.9.0 Sep 29, 2026
@sotashimozono
sotashimozono merged commit ec4c5e9 into main Sep 29, 2026
55 checks passed
github-actions Bot added a commit that referenced this pull request Sep 29, 2026
Promotion: next → main for 0.9.0 ec4c5e9
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant