Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,51 @@ flag changes and `doiget-mcp` tool spec changes will be called out explicitly he
Also: `disposition` was inserted at six envelope sites and asserted at two -
deleting one insert failed no test. The batch site now asserts it.

- **[bib]** A PubMed-exported bibliography entry was reported as having **no
identifier**. It has a PMID (#500).

`entry has no DOI / arXiv id` is accurate about what the parser did and wrong
about the entry. A user reading it goes and edits a `.bib` that was fine; the
missing piece is on doiget's side. An existing test pinned exactly that claim,
with the comment "the entry has no resolvable identifier" - about an entry
carrying `eprinttype = {pubmed}`.

Both PubMed shapes are now named: the `pmid = {...}` field that PubMed's own
BibTeX export writes, and the BibLaTeX `eprint` + `eprinttype = {pubmed}` pair
that `arxiv_eligible` already refused, correctly and until now silently.
`pmcid` too.

It surfaces as **`NOT_IMPLEMENTED`, not `INVALID_REF`** - the input is valid
and the support is absent, and the two carry opposite advice ("wait for a
release" versus "correct your input"). Its disposition is `terminal`
accordingly (ADR-0055).

An entry with genuinely no identifier still reports `NoIdentifier`, and a DOI
alongside a PMID still wins: the check runs only after every supported
identifier has been tried, so it cannot divert an entry doiget could have
resolved.

This is the reporting half of #500. `Ref::Pmid` itself is blocked on something
the issue did not anticipate - see below.

The sentence the user reads lives in one place now
(`refs::unsupported_identifier_claim`). It had been hand-copied to the CLI
`verify` row and the MCP `batch_from_bibliography` envelope, and both copies
had been wrapped across source lines and re-joined with the indentation still
in them - shipping `which doiget cannot resolve yet` to a
reader. Nothing asserted the text, so nothing failed. `#[error]` already
carried the same claim; the copies existed only to drop the `entry_key`
prefix, which each site puts in a field of its own.

Three more of the same, pre-existing and found while looking: two `= note:`
lines in `fetch fetch`'s widening advice and the `config.toml could not be
read` warning in `commands/mod.rs`. A workspace-wide lint for the pattern was
written and abandoned - it flags `config doctor`'s aligned two-column output
and test assertion messages at every space threshold from four to ten, and a
lint that cries wolf is a lint that gets deleted. Removing the duplication is
the durable half.


- **[mcp]** `error.disposition` was **missing from five failure envelopes**,
including the two most common failures an agent sees. The change that
introduced it converted the envelopes built by `error_object` and missed the
Expand Down
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ exclude = [
]

[workspace.package]
version = "0.8.13-beta.21"
version = "0.8.13-beta.22"
edition = "2021"
rust-version = "1.86"
license = "MIT"
Expand All @@ -41,9 +41,9 @@ features-doc-link = "docs/SOURCES.md"

[workspace.dependencies]
# Core
doiget-core = { path = "crates/doiget-core", version = "0.8.13-beta.21" }
doiget-cli = { path = "crates/doiget-cli", version = "0.8.13-beta.21" }
doiget-mcp = { path = "crates/doiget-mcp", version = "0.8.13-beta.21" }
doiget-core = { path = "crates/doiget-core", version = "0.8.13-beta.22" }
doiget-cli = { path = "crates/doiget-cli", version = "0.8.13-beta.22" }
doiget-mcp = { path = "crates/doiget-mcp", version = "0.8.13-beta.22" }

# Async runtime — features are deliberately limited (avoid `full`).
tokio = { version = "1", default-features = false, features = [
Expand Down
14 changes: 14 additions & 0 deletions crates/doiget-cli/src/commands/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,20 @@ pub async fn run_with_options(
match entry {
Ok(p) => inputs.push(p.ref_.as_input_str().to_string()),
Err(ParseError::InvalidRef { raw, .. }) => inputs.push(raw),
// #500: a distinct placeholder, so the JSONL line says the entry
// HAS an identifier doiget cannot use rather than implying it has
// none. Same mechanism as the arm below; different claim.
Err(ParseError::UnsupportedIdentifier {
kind,
value,
entry_key,
}) => {
let placeholder = match entry_key {
Some(k) => format!("<unsupported-{kind}:{value}:{k}>"),
None => format!("<unsupported-{kind}:{value}>"),
};
inputs.push(placeholder);
}
Err(ParseError::NoIdentifier { entry_key }) => {
// Synthesise a recognisable placeholder so Step 7's
// `Ref::parse` rejects this entry as `INVALID_REF`
Expand Down
4 changes: 2 additions & 2 deletions crates/doiget-cli/src/commands/fetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1388,7 +1388,7 @@ fn not_found_trace_lines(ref_: &Ref, attempts: &[SourceAttempt]) -> Vec<String>

if !already_set.is_empty() {
out.push(format!(
" = note: {} already set, but the source is still off -- this binary was built without the Cargo feature that provides it. Widening needs a differently-built binary, not another variable.",
" = note: {} already set, but the source is still off -- this binary was built without the Cargo feature that provides it. Widening needs a differently-built binary, not another variable.",
already_set.join(", ")
));
}
Expand Down Expand Up @@ -1419,7 +1419,7 @@ fn not_found_trace_lines(ref_: &Ref, attempts: &[SourceAttempt]) -> Vec<String>
// reading as an ordering it is not.
if !middle.is_empty() {
out.push(
" = note: the middle is unordered because nothing in this run distinguishes those sources -- venue, affiliation and funder would, and none of them reach here. An invented order would read as information."
" = note: the middle is unordered because nothing in this run distinguishes those sources -- venue, affiliation and funder would, and none of them reach here. An invented order would read as information."
.to_string(),
);
}
Expand Down
2 changes: 1 addition & 1 deletion crates/doiget-cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ fn store_root_from_config() -> Option<Utf8PathBuf> {
tracing::warn!(
path = %path,
error = %e,
"config.toml could not be read; [store] root ignored and the default store root used instead. Run `doiget config doctor` to see which root is in effect."
"config.toml could not be read; [store] root ignored and the default store root used instead. Run `doiget config doctor` to see which root is in effect."
);
return None;
}
Expand Down
21 changes: 21 additions & 0 deletions crates/doiget-cli/src/commands/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,27 @@ pub async fn run(path: String, format: String, cli_strict: bool, mode: OutputMod
"error": { "code": "INVALID_REF", "message": source.to_string() },
}),
),
// #500: still unverifiable, but for a reason the reader can act on
// -- and the action is not "fix the bibliography".
Err(ParseError::UnsupportedIdentifier {
kind,
value,
entry_key,
}) => (
VerifyStatus::Unverifiable,
serde_json::json!({
"ok": false,
"ref": serde_json::Value::Null,
"status": VerifyStatus::Unverifiable.as_wire(),
"entry_key": entry_key,
"error": {
"code": "NOT_IMPLEMENTED",
"message": doiget_core::refs::unsupported_identifier_claim(
kind, &value,
),
},
}),
),
Err(ParseError::NoIdentifier { entry_key }) => (
VerifyStatus::Unverifiable,
serde_json::json!({
Expand Down
Loading