Skip to content

feat(search-pipeline): extract root types from the dataset registry - #698

Merged
ddeboer merged 2 commits into
mainfrom
feat/registry-sourced-stages
Aug 7, 2026
Merged

feat(search-pipeline): extract root types from the dataset registry#698
ddeboer merged 2 commits into
mainfrom
feat/registry-sourced-stages

Conversation

@ddeboer

@ddeboer ddeboer commented Aug 5, 2026

Copy link
Copy Markdown
Member

Extract configured root types from the dataset registry instead of from each dataset’s own distribution.

A dataset’s description is governed by a different application profile from the objects it contains, and it lives in the register – nothing obliges a publisher to describe its own dataset inside its dump. So a Dataset root type extracted from the distribution yields documents for the few publishers that self-describe and nothing for the rest, leaving every reference resolved through it unlabelled.

Same CONSTRUCT generator, same framing, same projection, same writers – only the source differs. The extraction generator, the projection, the writers and SearchType are untouched.

Changes

@lde/pipeline

  • StageOptions.sourceFor – a per-stage source override, (dataset, distribution) => Distribution. One hook, applied once at the top of Stage.run, so the item selector and every reader are handed the same substitute: a stage that reads elsewhere must select there too, or its roots and its quads come from different sources.
  • SparqlItemSelector now honours Distribution.namedGraph, which only the reader applied. Without it a registry-sourced stage selects the whole catalogue instead of the dataset in hand. The graph scope and the page window are now derived per select() call rather than mutated on shared state, so concurrent selections over different datasets cannot see each other’s.

@lde/search-pipeline

  • registrySource(endpoint) – a sourceFor that reads a fixed registry endpoint, scoped to the graph the dataset in hand names.
  • SearchStageType.sourceFor passthrough, and searchIndexerPipeline’s registryTypes: { endpoint, names } for the convenience path. A name the schema does not declare throws at wiring time.

@lde/search-indexer

  • REGISTRY_ROOT_TYPES (whitespace- or comma-separated type names) alongside REGISTRY_ENDPOINT. Unset means every type reads the distribution, exactly as before.

Why graph scoping carries the design

A register holds every registration, so an unscoped registry stage would re-index the whole catalogue once per dataset processed. Scoped to the dataset’s graph, one pass sees exactly one registration.

Verified against the live NDE Dataset Register (2731 registrations):

  • every registration lives in a graph named exactly after its dataset IRI, so the dataset in hand names its own graph – no lookup, and FROM <dataset-iri> is the existing Distribution.namedGraph mechanism;
  • a registration is self-contained in that graph: 2731/2731 dataset→publisher pairs carry the publisher’s foaf:name inside it, and 2730/2731 type the publisher foaf:Organization there too – so selectByClass works unchanged for a Publisher root type, and the publisher hop resolves from the declared paths with no CBD rule or predicate list;
  • the register asserts dcat:Dataset in the dataset’s graph and schema:Dataset in the registrations graph, so a graph-scoped dcat:Dataset selector cannot reach the registrations graph at all – and excludes the 775 subjects that are schema:Dataset only (registrations never crawled), which a schema.org-rooted selector would ship as content-free documents.

Routing stays in configuration rather than in the schema: a SearchType is defined by its class, not by where its triples come from, so the same declaration serves a deployment that sources it differently.

Fix #695

ddeboer added 2 commits August 5, 2026 14:28
A dataset's own description is governed by a different application profile
from the objects it contains, and it lives in the register rather than in
the data a publisher ships – so a Dataset root type extracted from the
distribution yields documents only for the publishers that self-describe.

- add a per-stage source override to Stage: one hook substitutes the
  distribution for the item selector and every reader alike, so selection
  and extraction cannot read from different sources
- honour Distribution.namedGraph in SparqlItemSelector, which only the
  reader applied; without it a registry-sourced stage selects the whole
  catalogue instead of the dataset in hand. Scope and page window are now
  per call, so concurrent selections cannot see each other's
- add registrySource: a stage reading a fixed registry endpoint, scoped
  to the graph the dataset in hand names
- route named root types through searchIndexerPipeline's registryTypes
  and the indexer's REGISTRY_ROOT_TYPES; an undeclared name fails the
  boot rather than shipping an empty collection
Review findings on the registry-sourced stages:

- assert IRI safety in withDefaultGraph before it becomes a FROM <…>
  reference. A namedGraph is a plain string, typically carried in from
  third-party registry data; pipeline-void already guards the identical
  interpolation, so the reader and selector paths were the outliers
- reject a chained child stage declaring sourceFor: a child reads its
  parent's output as its distribution, so a substituted source would
  discard the chain it exists to continue. Stage now reports
  sourcesOwnData, which the parent's constructor checks
- cover the scope surviving pagination, which rewrites the query per page
- hoist the registry endpoint out of the per-type map and narrow
  registryTypeNames to what it reads
@ddeboer
ddeboer merged commit 1e387ca into main Aug 7, 2026
4 checks passed
@ddeboer
ddeboer deleted the feat/registry-sourced-stages branch August 7, 2026 09:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extract root types from the Dataset Register endpoint, not only from the distribution

1 participant