Suggest a publication from its DOI or the person's ORCID record (#688) #1062

Merged
tiagoagueda merged 1 commit from 688-publication-lookup into main 2026-10-28 01:58:56 +00:00
Owner

Typing a paper's authors, venue, volume and pages by hand is the part of adding a
publication nobody wants to do. Two buttons now ask a public registry, and only when
pressed:

  • Look up, beside a publication's DOI, asks doi.org for that one record by content
    negotiation (CSL JSON); doi.org passes it to Crossref, DataCite or mEDRA. The button
    posts the whole form (formaction, so it works with scripts off) and the form is drawn
    back unsaved with only the empty boxes filled, saying which and from which service.
    A new entry still on its opening type takes the record's type; a saved one never does.
  • Suggest from my ORCID record, on the Publications section, is shown only to a person
    who stored an ORCID, and always asks about that one: one request to ORCID's Anonymous
    API for the works summary, a review page that marks what is held (same DOI, else title
    and year), ticks none, lists at most 100 and adds at most 20, from the summary alone.

resume/lookups.py makes both requests through the public-only client inside until(),
read with read_body: fixed hosts, the identifier percent-encoded into the path alone,
three https redirects at most, each approved by the guard, 2 MiB or 4 MiB and ten
seconds, no retry, no email address sent. What is kept is a short list read as untrusted
text: markup, control and bidirectional characters stripped, cut to each column, up to
100 authors then "and others"; the abstract, references, funders and the record's URL
are never read into anything. Crossref's, CSL's and ORCID's type names all map, and an
unknown one is Other.

Each press spends POSTULO_LOOKUP_RATE (20/h by default), before the request, so a
failure costs one too. A feature plugin, publication-lookups, on by default, hides both
buttons and makes both addresses answer 404 when switched off. The threat model says
what leaves the instance and when.

Closes #688

Notes for review

  • Decisions the issue left open:
    • A new entry still on the default type takes the record's type; a saved entry's type is never changed.
    • Hosts after the doi.org redirect are not allow-listed. They must be https and approved by the guard, so mEDRA and other agencies still work.
    • An ORCID DOI is used only when its relationship is self.
    • Ticking more than 20 works adds nothing; the page says so.
    • CSL thesis maps to phdthesis.
    • A hidden Save button comes first in the form, so Enter saves rather than looking up.
  • The plugin publication_lookups ships its own catalogues, per the plugin locale rule. Its Weblate component still has to be added.

🤖 Generated with Claude Code

Typing a paper's authors, venue, volume and pages by hand is the part of adding a publication nobody wants to do. Two buttons now ask a public registry, and only when pressed: - Look up, beside a publication's DOI, asks doi.org for that one record by content negotiation (CSL JSON); doi.org passes it to Crossref, DataCite or mEDRA. The button posts the whole form (formaction, so it works with scripts off) and the form is drawn back unsaved with only the empty boxes filled, saying which and from which service. A new entry still on its opening type takes the record's type; a saved one never does. - Suggest from my ORCID record, on the Publications section, is shown only to a person who stored an ORCID, and always asks about that one: one request to ORCID's Anonymous API for the works summary, a review page that marks what is held (same DOI, else title and year), ticks none, lists at most 100 and adds at most 20, from the summary alone. resume/lookups.py makes both requests through the public-only client inside until(), read with read_body: fixed hosts, the identifier percent-encoded into the path alone, three https redirects at most, each approved by the guard, 2 MiB or 4 MiB and ten seconds, no retry, no email address sent. What is kept is a short list read as untrusted text: markup, control and bidirectional characters stripped, cut to each column, up to 100 authors then "and others"; the abstract, references, funders and the record's URL are never read into anything. Crossref's, CSL's and ORCID's type names all map, and an unknown one is Other. Each press spends POSTULO_LOOKUP_RATE (20/h by default), before the request, so a failure costs one too. A feature plugin, publication-lookups, on by default, hides both buttons and makes both addresses answer 404 when switched off. The threat model says what leaves the instance and when. Closes #688 ## Notes for review - **Decisions the issue left open:** - A new entry still on the default type takes the record's type; a saved entry's type is never changed. - Hosts after the doi.org redirect are not allow-listed. They must be https and approved by the guard, so mEDRA and other agencies still work. - An ORCID DOI is used only when its relationship is `self`. - Ticking more than 20 works adds nothing; the page says so. - CSL `thesis` maps to `phdthesis`. - A hidden Save button comes first in the form, so Enter saves rather than looking up. - **The plugin `publication_lookups`** ships its own catalogues, per the plugin locale rule. Its Weblate component still has to be added. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Suggest a publication from its DOI or the person's ORCID record (#688)
Some checks failed
CI / Unit tests and coverage (Python 3.14) (push) Failing after 12m36s
CI / Checks: lint, migrations, catalogues, settings, built files (push) Successful in 3m40s
CI / Database tests on PostgreSQL (push) Successful in 4m40s
CI / Security audit (push) Successful in 44s
CI / Browser tests (Chromium) (push) Successful in 32m7s
Every Python / Unit tests (Python 3.12) (push) Failing after 11m43s
Every Python / Unit tests (Python 3.13) (push) Failing after 13m0s
CI / Browser tests (Chromium) (pull_request) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (pull_request) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (pull_request) Has been cancelled
CI / Security audit (pull_request) Has been cancelled
0de307b8db
Typing a paper's authors, venue, volume and pages by hand is the part of adding a
publication nobody wants to do. Two buttons now ask a public registry, and only when
pressed:

- Look up, beside a publication's DOI, asks doi.org for that one record by content
  negotiation (CSL JSON); doi.org passes it to Crossref, DataCite or mEDRA. The button
  posts the whole form (formaction, so it works with scripts off) and the form is drawn
  back unsaved with only the empty boxes filled, saying which and from which service.
  A new entry still on its opening type takes the record's type; a saved one never does.
- Suggest from my ORCID record, on the Publications section, is shown only to a person
  who stored an ORCID, and always asks about that one: one request to ORCID's Anonymous
  API for the works summary, a review page that marks what is held (same DOI, else title
  and year), ticks none, lists at most 100 and adds at most 20, from the summary alone.

resume/lookups.py makes both requests through the public-only client inside until(),
read with read_body: fixed hosts, the identifier percent-encoded into the path alone,
three https redirects at most, each approved by the guard, 2 MiB or 4 MiB and ten
seconds, no retry, no email address sent. What is kept is a short list read as untrusted
text: markup, control and bidirectional characters stripped, cut to each column, up to
100 authors then "and others"; the abstract, references, funders and the record's URL
are never read into anything. Crossref's, CSL's and ORCID's type names all map, and an
unknown one is Other.

Each press spends POSTULO_LOOKUP_RATE (20/h by default), before the request, so a
failure costs one too. A feature plugin, publication-lookups, on by default, hides both
buttons and makes both addresses answer 404 when switched off. The threat model says
what leaves the instance and when.

Closes #688

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tiagoagueda force-pushed 688-publication-lookup from 0de307b8db
Some checks failed
CI / Unit tests and coverage (Python 3.14) (push) Failing after 12m36s
CI / Checks: lint, migrations, catalogues, settings, built files (push) Successful in 3m40s
CI / Database tests on PostgreSQL (push) Successful in 4m40s
CI / Security audit (push) Successful in 44s
CI / Browser tests (Chromium) (push) Successful in 32m7s
Every Python / Unit tests (Python 3.12) (push) Failing after 11m43s
Every Python / Unit tests (Python 3.13) (push) Failing after 13m0s
CI / Browser tests (Chromium) (pull_request) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (pull_request) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (pull_request) Has been cancelled
CI / Security audit (pull_request) Has been cancelled
to 24b4afb5b4
Some checks failed
CI / Checks: lint, migrations, catalogues, settings, built files (push) Successful in 2m50s
CI / Unit tests and coverage (Python 3.14) (push) Successful in 12m42s
CI / Database tests on PostgreSQL (push) Successful in 3m57s
CI / Security audit (push) Successful in 58s
Every Python / Unit tests (Python 3.12) (push) Successful in 13m30s
CI / Browser tests (Chromium) (push) Successful in 32m55s
Every Python / Unit tests (Python 3.13) (push) Has been cancelled
CI / Browser tests (Chromium) (pull_request) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (pull_request) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (pull_request) Has been cancelled
CI / Security audit (pull_request) Has been cancelled
2026-10-27 20:48:35 +00:00
Compare
tiagoagueda force-pushed 688-publication-lookup from 24b4afb5b4
Some checks failed
CI / Checks: lint, migrations, catalogues, settings, built files (push) Successful in 2m50s
CI / Unit tests and coverage (Python 3.14) (push) Successful in 12m42s
CI / Database tests on PostgreSQL (push) Successful in 3m57s
CI / Security audit (push) Successful in 58s
Every Python / Unit tests (Python 3.12) (push) Successful in 13m30s
CI / Browser tests (Chromium) (push) Successful in 32m55s
Every Python / Unit tests (Python 3.13) (push) Has been cancelled
CI / Browser tests (Chromium) (pull_request) Has been cancelled
CI / Unit tests and coverage (Python 3.14) (pull_request) Has been cancelled
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Has been cancelled
CI / Database tests on PostgreSQL (pull_request) Has been cancelled
CI / Security audit (pull_request) Has been cancelled
to 39813c73db
All checks were successful
CI / Unit tests and coverage (Python 3.14) (push) Successful in 14m12s
CI / Checks: lint, migrations, catalogues, settings, built files (push) Successful in 3m14s
CI / Database tests on PostgreSQL (push) Successful in 4m11s
CI / Security audit (push) Successful in 1m19s
CI / Browser tests (Chromium) (push) Successful in 33m56s
Every Python / Unit tests (Python 3.12) (push) Successful in 13m19s
Every Python / Unit tests (Python 3.13) (push) Successful in 14m25s
CI / Unit tests and coverage (Python 3.14) (pull_request) Successful in 12m55s
CI / Checks: lint, migrations, catalogues, settings, built files (pull_request) Successful in 3m9s
CI / Browser tests (Chromium) (pull_request) Successful in 31m50s
CI / Database tests on PostgreSQL (pull_request) Successful in 4m18s
CI / Security audit (pull_request) Successful in 1m24s
2026-10-28 01:55:29 +00:00
Compare
tiagoagueda deleted branch 688-publication-lookup 2026-10-28 01:59:00 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo!1062
No description provided.