Metadata-Version: 2.4
Name: ckanext-actor-registry
Version: 0.3.1
Summary: Reusable actors and contact points for CKAN metadata
Home-page: https://github.com/bjornhagstrom/ckanext-actor-registry
Author: Bjorn Hagstrom
Author-email: bjorn@hagstrom.nu
License: MIT
Project-URL: Changelog, https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/CHANGELOG.md
Project-URL: Issue Tracker, https://github.com/bjornhagstrom/ckanext-actor-registry/issues
Classifier: Development Status :: 3 - Alpha
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: author
Dynamic: author-email
Dynamic: classifier
Dynamic: description
Dynamic: description-content-type
Dynamic: home-page
Dynamic: license
Dynamic: license-file
Dynamic: project-url
Dynamic: requires-python
Dynamic: summary

# ckanext-actor-registry

[![PyPI](https://img.shields.io/pypi/v/ckanext-actor-registry.svg)](https://pypi.org/project/ckanext-actor-registry/)

Reusable publishers and contact points for CKAN metadata.

> **Status: 0.3.1 alpha.** This version is intended for evaluation and feedback.
> It has an automated test suite that runs against CKAN 2.11 and CKAN 2.12. The
> user interface is in English by default, with a Swedish translation catalog
> following the installation's locale settings. Do not treat it as
> production-ready without your own review and tests.

## Documentation

- **[Installation and supported versions](https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/docs/INSTALLATION.md)**: which CKAN and
  ckanext-scheming versions are tested, configuration, the database migration and the
  pitfalls to avoid;
- **[Run it with Docker Compose](https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/examples/ckan-2.12/README.md)**: a complete example (CKAN
  2.11 or 2.12, PostgreSQL, Solr, Redis) with a smoke test, for trying the extension or as
  a starting point;
- [Changelog](https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/CHANGELOG.md) and [release notes](https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/docs/RELEASE_NOTES_0.2.0.md);
- [Testing](https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/TESTING.md): how to run the automated suite on each supported CKAN version;
- [RDF and interoperability](https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/docs/RDF_AND_INTEROPERABILITY.md): the optional DCAT-AP profile;
- [Translations](https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/docs/TRANSLATIONS.md): how the English and Swedish catalogues are maintained;
- **[Common setups](https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/docs/CATALOGUE_SETUPS.md)**: how organizations, publishers and
  contact points relate in different catalogues, and what we recommend for each;
- [Choosing your fields](#choosing-your-fields-organization-publisher-and-contact-points):
  organization, publisher and contact points, and what happens to CKAN's own fields;
- [Contributing](https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/CONTRIBUTING.md), [security policy](https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/SECURITY.md) and
  [AI assistance disclosure](https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/AI_ASSISTANCE.md).

## Why this extension exists

CKAN datasets often repeat publisher and contact details. Repetition is easy to
introduce but expensive to maintain: a changed email address or organization
name must be corrected on every affected dataset.

This extension stores publishers and contact points once and lets datasets
reference them by stable internal IDs. Registry changes are resolved when a
dataset is displayed or exported, so one central update can be reflected
wherever the record is used. This isn't just a convenience: DCAT-AP itself
expects a dataset's publisher and contact point to be addressable entities in
their own right, not text -- see "Data model and DCAT-AP mapping" below for how
the registry implements that.

A **publisher** is an organization or a person that publishes datasets. A
**contact point** is a reusable contact function; it can belong to an
organization (a publisher) but doesn't have to. Neither concept replaces a CKAN
organization: CKAN organizations continue to control dataset ownership and
permissions.

> **Terminology.** In the interface these records are called *publishers*. The
> word *actor* survives only in technical places: the URLs (`/actors`), the
> dataset field `publisher_actor_id`, the Python package and the action names
> (`actor_registry_*`).

## Publishers vs. CKAN organizations

CKAN already has an "Organization" concept, so it's fair to ask why this
extension adds new records instead of extending it.

A CKAN organization is primarily an access-control and ownership unit: every
dataset belongs to exactly one organization, and membership in it determines
who may create, edit or delete the dataset. That's a different question from
who publishes a dataset or who to contact about it. The two often coincide in
practice, but not always: a dataset can be maintained by editors who aren't
themselves the responsible publisher, a single dataset commonly needs more
than one contact point (technical, legal, general enquiries) where an
organization has only the one dataset-to-organization relationship, a publisher
can be a person rather than an organization at all, and the same publisher can
publish datasets that live under different owner organizations on the same
catalog -- none of which map cleanly onto extending the organization model.

Extending CKAN's organization type to carry this metadata would also mean
changing a core concept that already governs permissions, for a purely
descriptive need. Publishers and contact points are deliberately independent
instead: additive tables with no CKAN user account, group membership or
permission of their own, and no effect on how organizations already control
dataset ownership.

## Choosing your fields: organization, publisher and contact points

You do not have to use both registries. Which fields a dataset has is decided
by the [ckanext-scheming](https://github.com/ckan/ckanext-scheming) schema you
configure. The extension does not add or remove a field in the schema; the optional
snippets for CKAN's own contact fields (see "Migrate to Actor Registry") can hide
Maintainer and Author on forms and pages, and only if your schema uses them.

**Why choose deliberately.** CKAN core stores contact details as free text on
each dataset (Author, Author email, Maintainer, Maintainer email). When the
same contact appears on many datasets, every copy has to be kept up to date
separately, and copies drift apart. The organization, on the other hand, is
mainly about ownership and who may edit, and is often also the right
publisher, but not always (see above). So the two needs are different:

| Your catalogue | Use | Example schema |
|---|---|---|
| The owner organization is the publisher; contact details are repeated across datasets | **Organization + contact points**, no publisher field | `contact_points_only_schema.yaml` |
| The publisher can differ from the owner organization (another organization, a person) | **Organization + publisher + contact points** | `actor_registry_schema.yaml` |

Catalogues differ more than one expects: the organization can be a department
while the city is the publisher, or the other way round. [Common
setups](https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/docs/CATALOGUE_SETUPS.md) describes the setups we know of and how to
handle each one.

Both ship in `ckanext/actor_registry/examples/` and are exercised by the test
suite. Configure one of them (they both define the `dataset` type), or copy
one into your own schema.

**How CKAN's own fields map:**

| CKAN core | With the registry |
|---|---|
| Organization (`owner_org`) | unchanged: ownership and permissions |
| Author, Author email, Maintainer, Maintainer email | replaced by **Contact points** (`contact_point_ids`) if your schema uses that field instead |
| (no equivalent) | **Publisher** (`publisher_actor_id`), optional |

**What happens to CKAN's own fields.** Nothing, by the extension. Without
Scheming the registry still works (its pages, lists and merging), but no
dataset fields appear and CKAN's form is unchanged. With Scheming, the form
and dataset page show exactly the fields in your schema, as with any Scheming
schema: the example schemas leave out Author, Maintainer, licence, tags and
version to stay short, so those do not appear. Add them back to your schema
if you need them, for example during a transition. Values already stored on
existing datasets stay in the database and in the API; they are only not
shown when the schema lacks the field, and they are **not** copied into the
registry automatically (see "Activating on an existing installation with
datasets" below).

**In the optional DCAT export** (ckanext-dcat with this extension's profile):
a dataset without a publisher record is exported with its organization as
`dct:publisher` (ckanext-dcat's own fallback), and with its registry contact
points as `dcat:contactPoint`; without those, ckanext-dcat falls back to
Maintainer and then Author. A dataset with registry contact points
never exports Maintainer or Author as well, and the setting
`ckanext.actor_registry.legacy_contact_fields = false` stops exporting them
altogether (see [Installation](https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/docs/INSTALLATION.md#ckans-own-contact-fields-in-the-dcat-export)).
The stored values are never changed.

## What visitors see is what is exported

A ground rule for this extension: the dataset page is designed first, and the
DCAT export follows what the page shows, never the other way round. The page
and the export use the same resolution of publishers and contact points:

- an inactive publisher or contact point is still what the dataset says until
  someone replaces it, so it is shown and exported (the forms no longer offer
  it; see *Cleanup*);
- a merged publisher is replaced by the one it was merged into, on both;
- a reference to a record that no longer exists is on neither;
- CKAN's own Maintainer and Author are exported only as far as the page
  shows them: never when the dataset has registry contact points or the
  migration is closed, and otherwise only the fields your schema shows
  (without Scheming, CKAN's own page shows all of them). A shown Author is
  exported as `dct:creator`, which DCAT-AP-SE calls *producent*.

Deliberate exceptions, decided and documented:

- **No publisher:** the page shows no publisher (the organization is already
  shown on the page), while ckanext-dcat exports the organization as
  `dct:publisher`.
## Features

- central registries for publishers and contact points;
- stable URIs, identifiers (unique per identifier scheme) and optional
  controlled publisher types;
- active/inactive records without breaking existing references; merging of
  duplicates and, for sysadmins, permanent deletion;
- alphabetical selectors with previews;
- inline creation and editing while editing a dataset, available to any
  logged-in editor;
- the current user's last publisher preselected on a new dataset;
- the current user's three most recently used contact points shown first;
- readable display on the dataset page (name and contact details, never internal
  IDs), and a page per publisher and contact point that lists its datasets;
- a worklist of datasets that are missing a publisher and/or a contact point;
- one shared validation layer (lengths, email, web addresses, URIs, phone
  numbers) and opt-in validators that keep datasets from pointing at records that
  no longer exist;
- Scheming helpers, form and display snippets, and a localised example schema;
- an English interface with Swedish (`sv`, `sv_SE`) translations;
- optional DCAT integration: publishers become `foaf:Agent` values and contact
  points become `vcard:Kind` values;
- optional `vcard:hasTelephone` serialization for the European DCAT-AP 3
  profile (a `vcard:Voice` with a `tel:` URI, the usual vCard form);
- validation that prevents a publisher and a contact point from sharing the same
  RDF URI.

## Data model and DCAT-AP mapping

None of this matters if you don't use DCAT: the RDF profile is entirely
opt-in (enabled only by setting `ckanext.dcat.rdf.profiles =
actor_registry_euro_dcat_ap_3` after installing ckanext-dcat). Without it,
publishers and contact points work exactly like a plain metadata-management
feature -- centrally maintained records, picked from a dropdown, editable in
place -- and you never need to think about `foaf:Agent` or `vcard:Kind`. The
rest of this section only matters once RDF/DCAT-AP export enters the
picture.

DCAT-AP does not treat a dataset's publisher or contact point as free text:
`dct:publisher` is expected to be a `foaf:Agent` with its own identity, and
`dcat:contactPoint` a `vcard:Kind` -- both addressable, reusable entities, not
strings duplicated on every dataset. This extension's registry gives CKAN
real, database-backed records with stable URIs for exactly that purpose;
`publisher_actor_id` / `contact_point_ids` on the dataset are resolved into
the `foaf:Agent` / `vcard:Kind` RDF shape only when a dataset is serialized
for DCAT export -- nothing is duplicated onto the dataset itself.

A publisher's `Kind` (Organization or Person) determines the specific agent
type used on export. A contact point's phone number is additionally serialized
as a `vcard:hasTelephone` / `vcard:Voice` node carrying a `tel:` URI, the
usual vCard form. ckanext-dcat itself does not export phone numbers. The extension builds on ckanext-dcat's
`EuropeanDCATAP3Profile`, so a DCAT-AP 3 consumer gets correctly typed,
dereferenceable publisher and contact point data without extra configuration.

## Screenshots

Everything below follows from one idea: publishers and contact points are
maintained once, centrally, instead of being retyped on every dataset -- which
is manageable for a handful of datasets and increasingly unmanageable as a
catalog grows.

> **The screenshots are being retaken for 0.2.0.** Each image link below is
> marked; the images will be added in a later commit.

### Publishers: organizations and people

A publisher can represent either an organization or a person -- the `Kind` field
is the only difference, and everything else in the registry (the dataset
pickers, the quick-create widget, merging) treats both the same way. An
identifier and its scheme (for example `SE:ORGNR`) are optional but must be
given together, and the pair is unique among active publishers: a clash names
the existing publisher instead of failing with a generic error.

*(Screenshot to be added later.)*

*(Screenshot to be added later.)*

### Contact points

Contact points are a separate, parallel register. A contact point can optionally
belong to an organization ("Belongs to organization"), but doesn't have to -- a
shared mailbox or a role-based contact works just as well as a named person.

*(Screenshot to be added later.)*

### Linking a dataset to the registry

When editing a dataset, the publisher (a single publisher) and one or more
contact points are chosen from the same registry used everywhere else in CKAN:

*(Screenshot to be added later.)*

If the publisher or contact point you need doesn't exist yet, either can be
created without leaving the dataset form. And if one already exists but needs a
correction, it can be edited from right here too -- the change applies
everywhere it is used, not just on this dataset:

*(Screenshot to be added later.)*

### What visitors see

On the public dataset page the publisher and each contact point are shown with
their readable details -- name, web address, email and so on, one per line --
never internal IDs. A publisher that has been merged into another shows as the
surviving publisher; inactive or missing records are simply left out.

*(Screenshot to be added later.)*

Publisher and contact point pages list the datasets they are linked to: the
first ten, with the total and a "Show all" link to a paginated list. The lists
respect dataset visibility, so private datasets are only shown to people who may
see them.

*(Screenshot to be added later.)*

### Merging two publishers

Merging two duplicate publishers -- select exactly two, choose which value wins
per field, confirm, and the publisher that isn't kept is soft-retired and points
to the remaining record. Datasets that pointed to the retired publisher are
re-pointed automatically, so the surviving publisher's dataset count grows to
reflect the merge:

*(Screenshot to be added later.)*

Soft-retiring rather than deleting exists for one reason: a merged publisher's
web address (its URI) may already be linked to, or cached, outside this
catalogue -- in a harvested copy, an external DCAT graph, a bookmark. A retired
publisher's own page keeps responding and points at the record it was merged
into, instead of going dead. Retired publishers are hidden from `/actors` by
default so the list stays focused on what's still in use, with a checkbox to
show them again. From there, a sysadmin can also permanently delete one --
useful for cleaning up test data or duplicates that were never referenced
outside this catalogue -- but doing so gives up that permanent-address
guarantee for good, which is why the extension asks for that decision
explicitly rather than doing it automatically after a merge.

### A dataset with no publisher set

A newly created dataset with neither a publisher nor a contact point shows an
inline warning on its own page, with pickers for both fields. This is also
exactly what an editor sees, dataset by dataset, right after the extension is
activated on a catalog that already has data -- see "Activating on an existing
installation with datasets" below for the full picture, including the separate
worklist view:

*(Screenshot to be added later.)*

The same dataset also shows up on the registry's own worklist, so it can be
found and fixed from either place:

*(Screenshot to be added later.)*

Saving resolves the warning immediately, without leaving the page:

*(Screenshot to be added later.)*

## Requirements and compatibility

- CKAN 2.11.1 or later 2.11 releases, or CKAN 2.12; the full automated suite (including
  the official DCAT-AP 3 SHACL validation) runs on all of them;
- PostgreSQL, as required by CKAN;
- `ckanext-scheming` for the included dataset form widgets; 3.1.0 is
  recommended (see [docs/INSTALLATION.md](https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/docs/INSTALLATION.md) for the versions that
  are tested and a known deprecation warning on CKAN 2.12);
- `ckanext-dcat` and RDFLib only when the optional RDF profile is enabled.

CKAN 2.11.0 and 2.10 and older are not supported (2.11.0 could not be tested: its official
image has no working pytest plugin).

## Installation

The quickest way to try the extension is the [Docker Compose example](https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/examples/ckan-2.12/README.md).
For an existing CKAN, follow [docs/INSTALLATION.md](https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/docs/INSTALLATION.md); in short, install
the package from [PyPI](https://pypi.org/project/ckanext-actor-registry/) in the same Python
environment as CKAN:

```console
pip install ckanext-actor-registry
```

To work on the extension, install a source checkout instead: `pip install -e .` in the
checkout.

Add `actor_registry` to `ckan.plugins`:

```ini
ckan.plugins = ... actor_registry
```

Create or upgrade the extension's database tables:

```console
ckan -c /etc/ckan/default/ckan.ini db upgrade -p actor_registry
```

Restart CKAN. Logged-in users can then manage publishers at `/actors` and contact
points at `/contactpoints`.

## Scheming integration

The extension supplies choice helpers, two form snippets, two display snippets and
two optional validators. A minimal schema configuration is:

```yaml
- field_name: publisher_actor_id
  label: Publisher
  preset: select
  required: true
  form_snippet: actors_multiple_select.html
  display_snippet: actor.html
  choices_helper: actors_choices
  sorted_choices: true
  validators: scheming_required actor_registry_actor_exists

- field_name: contact_point_ids
  label: Contact points
  preset: multiple_select
  form_snippet: contactpoints_multiple_select.html
  display_snippet: contactpoints.html
  choices_helper: contactpoints_choices
  sorted_choices: true
  validators: ignore_missing scheming_multiple_choice actor_registry_contactpoints_exist
```

A complete, localised (`en`, `sv`, `sv_SE`) example schema ships with the extension
and is used by the tests: `ckanext.actor_registry.examples:actor_registry_schema.yaml`.
It is an integration example, not a universal metadata schema.

The field names shown above are part of the 0.x data contract. Datasets store
registry UUIDs in these fields (`publisher_actor_id` as a string,
`contact_point_ids` as a list). The display snippets resolve them to readable
details on the dataset page. For RDF export the profile resolves them into the
`publisher` and `contact` shapes that the Scheming profiles in `ckanext-dcat`
expect, at serialization time only; nothing extra is written onto the dataset.
The two `actor_registry_*_exist` validators are opt-in: they make a dataset
refuse a reference to a record that does not exist or is no longer active.

## Optional DCAT profile

The core registry is independent of DCAT, country and organization type. For
RDF export with telephone support, enable the profile entry point after
installing `ckanext-dcat`:

```ini
ckanext.dcat.rdf.profiles = actor_registry_euro_dcat_ap_3
```

The profile subclasses `EuropeanDCATAP3Profile`. It delegates normal publisher
and contact serialization to `ckanext-dcat` and adds telephone values as
`vcard:hasTelephone` / `vcard:Voice` / `vcard:hasValue` with a `tel:` URI.
Theme and publisher-type URIs are also typed as `skos:Concept` so standalone
DCAT-AP 3 validation can identify their intended range. Store publisher types
as controlled-vocabulary URIs rather than local display labels.
This is an interoperability aid, not a claim that installing this extension
alone makes a catalog conformant with DCAT-AP or a national profile based on
it. Conformance also
depends on the complete dataset schema, validation and profile configuration.

## Access control and data handling

- registry list and edit pages, and inline publisher/contact creation, are
  available to any logged-in CKAN user, not just sysadmins -- the registry
  holds shared, catalog-wide data meant for editors generally;
- merging two publishers is the one exception and stays restricted to CKAN
  sysadmins, since it is destructive and re-points datasets across the
  whole catalog rather than just the acting user's own organization;
- deployments must set `ckan.csrf_protection.ignore_extensions = false` so
  CKAN validates the CSRF tokens included by the management and inline forms;
- individual publisher and contact point display pages are readable wherever
  the CKAN site itself permits access;
- input is validated on the server by one shared layer, for the forms, the inline
  dialogs and the merge action alike: length limits that match the database
  columns, email addresses (checked with CKAN's own validator), web addresses,
  URIs and phone numbers. Stored web addresses and URIs are only rendered as
  links when CKAN's `h.is_url` accepts them; anything else is shown as plain text;
- per-user convenience data stores only CKAN user IDs, the most recent
  publisher ID and recently used contact point IDs;
- the dataset lists on a publisher's or contact point's page, on their "Show all"
  pages and in the `/actors` worklist only include private datasets that the
  visitor may see: sysadmins, and members of the dataset's organization. The
  totals shown follow the same rule, so a private dataset is never revealed by
  a count. FYI: a user who was given access to a single private dataset through
  CKAN's *dataset collaborators* feature (`ckan.auth.allow_dataset_collaborators`,
  off by default) but is not a member of its organization does not see that
  dataset in these lists; the dataset itself can still be opened directly;
- a publisher can also be permanently deleted (`/actors/<id>/delete`), sysadmin-
  only like merging. Datasets that had it as publisher have that field
  cleared; contact points it owned become standalone rather than being
  deleted themselves. This is a real delete, not a soft-retire -- see
  "Merging two publishers" above for why that distinction matters;
- a contact point can likewise be permanently deleted
  (`/contactpoints/<id>/delete`), sysadmin-only. It is removed from the
  `contact_point_ids` of any dataset that listed it (other contact points
  on the same dataset are left alone); an owning publisher, if any, is
  unaffected.

## Activating on an existing installation with datasets

Enabling this extension on a CKAN instance that already has datasets does not
retroactively link anything: every existing dataset starts out with no
publisher and no contact point in the registry, because those are new,
separate fields the extension adds via Scheming (see "Scheming integration"
above). Three features exist specifically to help you find and fix these
datasets afterwards; none runs automatically, so plan to use them as a
one-time cleanup pass after activation.

**Migrate to Actor Registry (`/actor-registry/migrate`).** If your datasets
already hold contact details in CKAN's own free-text fields, this page turns
them into shared contact points. It reads Maintainer, and Author only when a
dataset has no maintainer, on the datasets that have no contact point yet,
and groups them by email address (or by name when there is no valid
address), showing the spellings used and any existing contact point with the
same address. For each group you create a contact point, link to an existing
one, or skip; clear the datasets that belong elsewhere and they stay for a
later round. One email address may back several contact points. The page
shows how many datasets are linked, skipped and remaining.

While the old fields are still in use, editors may keep changing them. When
Maintainer or Author changes on a dataset after it was linked, the page lists
it under *Changed since linking*, with the old and new values, and you choose:
update the contact point (for every dataset that uses it), link this dataset
to another contact point, create a new one, or keep it as it is.

**Closing the migration.** When you are done, a sysadmin closes the migration
on the same page. Maintainer and Author are then hidden in forms and on
dataset pages, for everyone including sysadmins, and are no longer exported;
their values stay stored, and saving a dataset keeps them. It takes effect at
once, without a restart, and the migration can be reopened. If datasets are
still without a contact point, or have unhandled changes, the page says how
many and asks you to confirm. Closing sets
`ckanext.actor_registry.legacy_contact_fields` to false at runtime (see
[Installation](https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/docs/INSTALLATION.md#ckans-own-contact-fields-in-the-dcat-export)).

Hiding needs one thing in your schema: give the old fields the extension's
`legacy_contact.html` snippets, which show them while the migration is open:

```yaml
- field_name: maintainer
  label: Maintainer
  form_snippet: legacy_contact.html
  display_snippet: legacy_contact.html
# ... and the same for maintainer_email, author and author_email
```

A schema without these fields has nothing to hide. A theme that overrides
Scheming's `additional_info.html` block itself must leave out rows with
`display_snippet: legacy_contact.html` while the migration is closed, as the
extension's own version does.

The original fields are never changed: linking only sets the contact points,
through a partial update that checks `package_update` for each dataset. A
dataset that fails your schema's validation is reported and the others are
still linked. Editors see and migrate only the datasets they may edit;
sysadmins all of them. Every decision is logged in the
`actor_registry_migration_decision` table (run `ckan db upgrade -p
actor_registry`).

**A worklist on `/actors`.** The registry management page (*Publishers*) lists
the active datasets that are missing something, with a direct link to each one.
By default it shows the datasets missing *both* a publisher *and* any contact
point ("Datasets missing both a publisher and a contact point (N)"), which
surfaces the datasets nobody has touched yet. The *Missing:* buttons above the
list widen or change that: *Publisher* (every dataset without a publisher),
*Contact point* (every dataset without a contact point) or *Either* (missing at
least one of the two). The page shows the first ten with the total and a "Show
all" link to a paginated list, and private datasets only appear for people who
may see them.

**An inline fix on the dataset's own page.** Opening any dataset that is
missing a publisher and/or a contact point shows a warning box near the top
of the page with the same selector widgets (including "create new...") used
in the full dataset edit form, so editors recognize the control instead of
having to find the right field inside the advanced edit view. Saving it
performs a partial update -- only the field(s) you actually filled in are
touched, the rest of the dataset is left alone -- and requires the normal
`package_update` permission for that dataset, not sysadmin. The prompt
follows your schema: it asks for a contact point when the schema has that
field, and for a publisher only when the schema requires one.

**Deactivating and cleaning up.** Deactivating a publisher or contact point
stops new use of it: it is no longer offered in the forms, and choosing it
anyway gives a message that names it. Datasets that already use it keep it and
can still be saved; the form shows it marked *inactive* instead of dropping
it. The *Cleanup* page (`/actor-registry/cleanup`, a tab, also hinted on the
registry pages) lists the inactive records still in use, and datasets that
point to records that no longer exist, and replaces or removes such a
reference on the datasets you may edit (editors their own, sysadmins all; a
merged publisher suggests the one it was merged into).

All three rely on `ckanext-scheming`, so they only cover dataset types managed
by Scheming (the migration only those whose schema has the contact points
field).

## Upgrade note for the prototype

The migration history retains the table name
`contactpoints_alembic_version`. This is intentional so installations upgraded
from the earlier, unpublished `ckanext-contactpoints` prototype preserve their
existing data. New installations should use only the `actor_registry` plugin
and migration command.

Upgrading from the 0.1.0 source snapshot? See the "Upgrading" section of the
[0.2.0 release notes](https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/docs/RELEASE_NOTES_0.2.0.md): run the migration, and note that the
translation keys changed with the UI wording.

## Known limitations

- no Action API is provided for registry administration;
- there is no import/export command for registry data;
- browser-based (end-to-end) tests and a formal security review are still
  pending;
- selection and validation of identifier schemes and publisher-type
  vocabularies is left to the deploying catalog;
- the dataset lists on publisher and contact point pages do not include private
  datasets that a user can only see as a *dataset collaborator* (see "Access
  control and data handling").

See [CHANGELOG.md](https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/CHANGELOG.md) for release contents and
[CONTRIBUTING.md](https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/CONTRIBUTING.md) for how to help. Please report security
issues according to [SECURITY.md](https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/SECURITY.md).

The automated and manual test strategy is documented in
[TESTING.md](https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/TESTING.md).

## Development transparency

The implementation and documentation were developed by Björn Hagström with
assistance from OpenAI's ChatGPT and Codex and, from version 0.2.0, from
Anthropic's Claude Code. The human maintainer directs, reviews and accepts
responsibility for the released work, including its correctness, security,
licensing and provenance. See [AI_ASSISTANCE.md](https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/AI_ASSISTANCE.md).

## License

MIT. See [LICENSE](https://github.com/bjornhagstrom/ckanext-actor-registry/blob/master/LICENSE).
