Skip to content

Publish protected nanopublications from local instances (#671) - #689

Merged
tkuhn merged 3 commits into
masterfrom
feat/671-publish-protected-nanopubs
Sep 7, 2026
Merged

Publish protected nanopublications from local instances (#671)#689
tkuhn merged 3 commits into
masterfrom
feat/671-publish-protected-nanopubs

Conversation

@tkuhn

@tkuhn tkuhn commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Second half of #671: Nanodash could already recognize protected nanopublications (the 🔒 flag, the 🔒 restricted marker); this lets users of a deployment connected to a local/private registry create them.

The marker

What registries look at is one triple, this: a npx:ProtectedNanopub. It is added by an ordinary pubinfo template, RAjTlfGJ… (published, unlisted, no placeholders), rather than emitted in code — so the statement has a nt:wasCreatedFromPubinfoTemplate link like every other one, and ValueFiller claims it when a protected nanopublication is superseded instead of sweeping it into the hand-coded catch-all.

The template is driven by a checkbox of its own rather than the "add element…" dropdown. That dropdown is behind "show more", and the generic "Nanopublication type" element produces npx:hasNanopubTypenot what registries check, so a nanopublication that looks protected in the form would go to the public network. PublishForm.checkProtectedMarker now refuses to build one of those.

One checkbox, not two

Consent is about open publication, so the consent checkbox is shown only for a nanopublication that will be openly published, and says so where protection is possible:

I understand that this will be openly published, that published data cannot be fully removed (only retracted or superseded by new versions), and that it will be publicly connected to my personal identifier.

Ticking the protected box takes that checkbox away rather than rewording it. The user has one box, and its note is a statement of fact: "This nanopublication will stay on the local instance this Nanodash is connected to." The public wording would not be true there — "cannot be fully removed" holds for the public network, not for one instance whose operators can take it off again.

The preview page shows the same box, ticked and disabled: by then the marker is signed into the content.

When it is not the user's choice

ProtectedNanopubs.getForcedReason — the fill source is protected (the new version repeats its content), a template the form is built on is protected (a public nanopublication would carry a link nobody outside can follow), or the assertion template lists the element in nt:hasRequiredPubinfoElement. The checkbox is then ticked and disabled with the reason shown. If protection is forced or defaulted on and the template cannot be loaded, publishing is refused rather than silently downgraded to public.

Defaults

NANODASH_PROTECTED_BY_DEFAULT (or protectedByDefault in the preferences file), because both kinds of local instance exist: mostly-public ones with occasional protected content, and private-by-default ones.

Second commit: where it is sent

PublishNanopub.publish picks from the library's own registry list, which knows nothing about NANODASH_MAIN_REGISTRY. A deployment configured only the Nanodash way read from its local instance but could not publish a protected nanopublication to it — "None of the available registries is a local instance", with the local instance sitting in the config. Utils.publishNanopub now addresses protected nanopublications to getMainRegistryUrl() directly.

Unprotected ones keep going to the library's list, deliberately: registries pull from their peers rather than pushing to them, so an openly published nanopublication sent only to a private registry would never reach the public network.

Verification

1323 tests, 0 failures (+21). Beyond the unit tests, driven end to end against a local stack (registry 1.12.1 + query 1.28.0, both LOCAL_INSTANCE=true), from a deployment deliberately configured without NANOPUB_REGISTRY_INSTANCES — the case that used to fail:

  • published https://w3id.org/np/RA5-gEQNLlun_V1QVV6pbzedbRBzSXY5_4x5fJXM8nCcU
  • log: Publishing protected nanopublication to the configured local instance: http://localhost:19292/
  • local registry 200; registry.knowledgepixels.com, registry.petapico.org and w3id.org all 404
  • stored pubinfo carries this: a npx:ProtectedNanopub and the template link; Nanodash renders it back with the 🔒 flag

Design notes in docs/protected-nanopublications.md.

Known gaps

  • A nanopublication governed by a protected space does not force protection: the governing space is a resource IRI, not the nanopublication defining it, so answering that needs a query. In practice the templates such a space governs are local-only, which the template rule catches.
  • On a private-by-default deployment the protected box starts ticked, so nothing is left for the user to tick before publishing — consent becomes implicit there. That follows from tying consent to open publication.

Closes #671

🤖 Generated with Claude Code

https://claude.ai/code/session_01XwZT7hRpsCLCaJw7GCkHWy

tkuhn and others added 3 commits September 7, 2026 15:09
…lications

A local/private Nanopub Registry accepts, stores and serves nanopublications
typed npx:ProtectedNanopub; the public network refuses them. Nanodash could
already recognize such nanopublications (#671's first half); this lets users of
such a deployment create them.

The marker is added by an ordinary pubinfo template, so that the statement has a
template behind it like every other one and round-trips through ValueFiller when
a protected nanopublication is superseded, instead of being swept into the
hand-coded catch-all. The template is unlisted and driven by a checkbox of its
own: where a nanopublication may be stored is not a description of its content,
the publication info section is behind "show more", and the generic
"Nanopublication type" element produces npx:hasNanopubType, which is not what
registries check -- a nanopublication that looks protected in the form would be
published to the public network. The form now refuses to build one of those.

Consent is about open publication, so the consent checkbox is shown only for a
nanopublication that will be openly published, and says so where protection is
possible. Ticking the protected box takes it away rather than rewording it: the
user has one box, whose note states plainly that the nanopublication stays on
the local instance. "Cannot be fully removed" would not be true there anyway --
the organization running the instance can take it off again.

Protection is not the user's to turn off when the fill source or one of the
templates is itself protected, or when the assertion template requires the
element: the new version of a protected nanopublication would be the thing that
exposes it, and a public nanopublication made with a local-only template would
carry a link nobody outside can follow. Whether it is on by default is a
deployment decision (NANODASH_PROTECTED_BY_DEFAULT), because both kinds of local
instance exist: mostly-public ones, and private-by-default ones.

The preview page shows the same box, ticked and disabled -- by then the marker
is signed into the content and cannot be taken back there.

Closes #671

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XwZT7hRpsCLCaJw7GCkHWy
… instance

PublishNanopub.publish picks a registry from the library's own list -- bootstrap
plus discovered instances, or NANOPUB_REGISTRY_INSTANCES -- which knows nothing
about NANODASH_MAIN_REGISTRY. A deployment configured only the Nanodash way
therefore reads from its local instance but could not publish a protected
nanopublication to it: the attempt failed with "None of the available registries
is a local instance" while the local instance sat right there in the config.

There is only one place a protected nanopublication can go, and by the time the
option is offered Nanodash has already checked that the main registry reports
itself as a local instance, so it is now addressed directly.

Everything else keeps going to the library's list, deliberately. Registries pull
from their peers rather than pushing to them, so an openly published
nanopublication sent only to a private registry would never reach the public
network -- the opposite of what publishing it unprotected means. The private
registry picks it up again through peering.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XwZT7hRpsCLCaJw7GCkHWy
theMarkerEndsUpInTheNanopublication builds the nanopublication the form would
publish, which includes the fixed "Creator" publication info element. That
element takes its value from the session user, and the session picks one up from
an ORCID in ~/.nanopub -- present on a developer machine, absent on CI, where
the statement stayed unfilled and NanopubCreator refused it with "Field of
statement not set".

The test now sets the user itself, so what it asserts no longer depends on the
machine it runs on, and checks that the creator ends up in the result.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XwZT7hRpsCLCaJw7GCkHWy
@tkuhn
tkuhn merged commit 14b106d into master Sep 7, 2026
8 checks passed
@tkuhn
tkuhn deleted the feat/671-publish-protected-nanopubs branch September 7, 2026 13:39
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 5.14.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support building upon restricted query service and recognize ProtectedNanopubs

1 participant