Bot identity, pubinfo tags, and migrate-pipeline fixes (v0.2.0) - #4
Merged
Conversation
A published nanopub is immutable, so baking in a reference to a term that does not exist (a dangling foreign key, e.g. left behind when a duplicate compound was deduplicated upstream) is worse than omitting it: the link can never resolve. Previously such references were kept with their old ids and logged as a warning. Now split_references drops dangling references from the minted graph, and _resolve_all does the same in the superseding pass so a superseding nanopub never reintroduces a broken link. The term still mints with its remaining triples. migrate.py logs each dropped reference at ERROR (with the offending target) plus a batch summary telling the curator to fix the source and re-run; the run still completes (non-fatal), so a known-bad source link does not block publishing the rest. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NnygThDJDwSVZg3kYujJKz
serialize_nanopub now binds a curated set of vocabulary prefixes (schema, pehterms, biochementity, obo, skos, prov, dcterms) before serializing, so namespaces render as meaningful prefixes instead of rdflib's auto-numbered ns1:/ns2:. The header is ordered with this: and sub: first (the nanopub and its sub-graphs anchor every file), then the rest alphabetically, so the .trig reads top-down. Callers can extend or override via a new `prefixes` argument. Purely a rendering change: binding happens at write time, after signing, so trusty artifact codes and signatures are unaffected (verified is_valid). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NnygThDJDwSVZg3kYujJKz
Add a generic, identity-agnostic way to bootstrap a publishing bot's cryptographic identity, mirroring the nanopub-community declaredBy pattern: - introduction.py: build_introduction() builds the self-signed key-declaration nanopub. The agent is a fresh URI minted inside the introduction itself, typed npx:Bot/npx:SoftwareAgent, frbr:owner-ed by a human ORCID, with its RSA public key declared. The agent self-URI only exists post-sign, so it (and npx:signedBy) use the ~~~ARTIFACTCODE~~~ placeholder, resolved at sign time like scheme A. Tagged nt:wasCreatedFromTemplate so Nanodash renders it nicely. - cli/bootstrap_identity.py: pubmate-bootstrap-identity. Generates (or reuses) an RSA keypair and writes the signed introduction to --output-dir. Signs offline by default: does NOT publish and never touches a secret store, so the private key (the bot's identity) can be generated locally and reviewed before anything goes out. Refuses to overwrite an existing key; chmod 600 on the private key. - utils.py: bind foaf/frbr/nt prefixes so introductions render readably. Tests cover validity, self-signing, agent typing/ownership, the key declaration, owner attribution, and the template tag (incl. opt-out). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NnygThDJDwSVZg3kYujJKz
A term often carries an untagged label plus localized variants (e.g. "Lead" and "Lood"@NL-BE). The nanopub's own rdfs:label (pubinfo) was taken from the first rdfs:label rdflib happened to yield, so it sometimes picked a localized one. Add preferred_label() (untagged > English > any, ties broken by text for determinism) and use it for both the defining (term_input_from_assertion) and superseding (migrate._label) labels. The assertion keeps every label variant — only the nanopub's headline label changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NnygThDJDwSVZg3kYujJKz
Source assertions use anonymous blank nodes ([ a pehterms:ContextAlias; … ]), whose rdflib ids are long (e.g. ndf301ab1d67…b1) and regenerated each parse. nanopub-py skolemizes each blank node to a sub:_<id> URI, so those long, non-reproducible ids leaked into the published nanopub (sub:_ndf301ab1d67…b1). Add relabel_blank_nodes(): rename blank nodes to short, content-ordered labels (b1, b2, …) before each builder wraps the assertion, so they serialize as sub:_b1. Wired into the defining and superseding builders. Pure relabeling (the graph stays isomorphic; nanopub-py already skolemized these), and the labels are assigned by a content key rather than the source's random ids, so the same input always yields the same labels — and therefore reproducible artifact codes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NnygThDJDwSVZg3kYujJKz
Add pubmate-migrate --default-suggester <orcid>, attributing prov:wasAttributedTo on every term that carries none — the case when migrating an existing batch all contributed by one person. Threads through to both the defining nanopubs (via the minter's existing default_suggester_orcid) and the superseding ones. The suggester goes in provenance, distinct from the bot signer; per-term wasAttributedTo (when present) still wins. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NnygThDJDwSVZg3kYujJKz
resolve_signing passed the CLI key-file path strings straight to nanopub.Profile, which only reads a key file when given a pathlib.Path — a str is taken as the raw key material, so signing crashed with "RSA key format is not supported". This hit every real-key signing path (pubmate-migrate and pubmate-mint-publish); only the testsuite-key path was exercised before. Pass Path objects so the files are read. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NnygThDJDwSVZg3kYujJKz
…omTemplate Add generic support for two Nanodash pubinfo hints on the defining and supersession builders: npx:hasNanopubType (repeatable, the kind of thing the nanopub asserts) and nt:wasCreatedFromTemplate (the assertion template it follows, so Nanodash renders it with the matching form). Shared via a new add_pubinfo_tags() helper; threaded through pubmate-migrate as --nanopub-type/--template. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New features since v0.1.1: bootstrap-identity / introduction CLIs, pubinfo hasNanopubType + wasCreatedFromTemplate tags, readable prefixes, and the dangling-reference / blank-node / label fixes in the migrate pipeline. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GertjanBisschop
approved these changes
Jun 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Brings the fork's
mainup to v0.2.0. Clean fast-forward overeu-parc/main(9 commits ahead, 0 behind), tests included and passing (96 passed).Features
feat(introduction): a self-signed agent-introduction (key-declaration) nanopub builder +pubmate-bootstrap-identityCLI. Generates a bot keypair offline and declares the publishing agent, binding it to its signing key (mirrors the communitydeclaredBypattern).feat(pubinfo): defining and supersession builders can tag every nanopub withnpx:hasNanopubType(repeatable) andnt:wasCreatedFromTemplate, threaded throughpubmate-migrateas--nanopub-type/--template(so Nanodash renders minted nanopubs with the matching assertion-template form).feat(migrate):--default-suggesterattributes any term lacking its own suggester viaprov:wasAttributedTo.feat(utils): bind well-known prefixes and lead withthis:/sub:.Fixes
fix(migrate): drop dangling references and report them as errors (a clean migration expects zero).fix(bnode): short, deterministic blank-node labels (sub:_b1) instead of long random hashes.fix(label): prefer the untagged literal for a nanopub'srdfs:label(e.g. "Lead" over "Lood"@NL-BE).fix(signing): read key material from--private-key/--public-keyas file paths.Follow-up
After merge + a
v0.2.0release tag, thebiochementity-vocabularypubmatepin bumps fromv0.1.0tov0.2.0(itsmake migratetarget now uses--nanopub-type/--template).🤖 Generated with Claude Code