Registry stack documentation: machine-readable Markdown.
Index of all pages: https://docs.registrystack.org/v/0.38.0/llms.txt
Full corpus: https://docs.registrystack.org/v/0.38.0/llms-full.txt

# Package and run a Registry Discovery index

> Package a bounded Registry Discovery index from approved provider descriptions and run its read-only service.

Use this guide when you operate a Registry Discovery catalog for known Evidence Gateway and
Registry Relay providers. You maintain provider URLs and evidence-type mappings, package one
immutable index on demand, then restart the read-only service with that package.

{/* Evidence: products/discovery/README.md describes the operator flow as offline `check`, one
    explicit `package`, immutable-package deployment, then restart. `crates/registry-discoveryctl/src/lib.rs`
    exposes only `check` and `package`; `crates/registry-discovery/src/server.rs` fixes the public routes. */}

## When to use this

Use Registry Discovery to curate public advertisements from providers your catalog operator has
already chosen to index. It is useful when an application must find an Evidence Gateway by evidence
type, or a Registry Relay by its public semantics, before it performs its normal product-specific
connection.

{/* Evidence: `crates/registry-discovery/src/query.rs` matches Evidence type, Relay semantic-class,
    and Relay operation-family filters. `products/discovery/ACCEPTANCE-JOURNEYS.md` defines direct
    native Evidence and Relay invocation after exact selection. */}

## Before you start

You need these inputs:

- A provider-supplied public description URL, such as
  `https://<provider-host>/catalog.jsonld`. Record the URL out of band with the provider.
- A local directory for the catalog project, a new output directory for the package, and a separate
  deployment runtime file.
- The `discoveryctl` and `discovery` binaries.

The provider description uses the closed Registry Discovery JSON-LD profile. The profile is a
selected alignment with Data Catalog Vocabulary (DCAT) 3, DCAT-AP 3.0.1, and BRegDCAT-AP terms.
It does not claim complete DCAT-AP or BRegDCAT-AP conformance.

{/* Evidence: `crates/registry-discoveryctl/src/project.rs` accepts explicit `catalogUrl` values in
    `origins.yaml`; `products/discovery/DECISIONS.md`, ADR-002 and
    `products/discovery/contracts/standards-profile.yaml` pin the profile and selected standards
    subset. */}

## Install the authoring tool and runtime

Registry Stack releases publish the `discovery-<tag>-linux-amd64` runtime asset. The first release
eligible to publish a matching `discoveryctl-<tag>-linux-amd64` authoring asset is v0.38.0. For a
published v0.38.0 or later release, choose `<tag>` from the
[latest release](https://github.com/registrystack/registry-stack/releases/latest) and authenticate
both assets through the shared `SHA256SUMS`
procedure at `https://github.com/registrystack/registry-stack/blob/<tag>/release/VERIFY.md`, then
install the exact pair:

```sh
tag="${TAG:?set TAG to the selected published tag}"
mkdir -p ~/.local/bin
for binary in discovery discoveryctl; do
  install -m 0755 "${binary}-${tag}-linux-amd64" "$HOME/.local/bin/${binary}"
done
export PATH="$HOME/.local/bin:$PATH"
discovery --version
discoveryctl --version
```

Earlier releases contain the Linux runtime asset but require a source build for `discoveryctl`.
For another platform, build both binaries from the same Registry Stack checkout:

```sh
cargo build --release --locked -p registry-discovery -p registry-discoveryctl
export PATH="$PWD/target/release:$PATH"
```

The release also publishes `ghcr.io/registrystack/discovery:<tag>`. Authenticate its digest through
the release manifest and deploy that digest instead of the mutable tag. The image runs
`/usr/local/bin/discovery` as the Distroless `cc-debian13` nonroot user, with
`/etc/registry-discovery/runtime.yaml` as its default runtime file. Mount that file and the complete
package directory read-only. Discovery keeps no writable local state.

{/* Evidence: release/scripts/release_candidate.py, _relay_v2_payload_inventory;
    release/docker/Dockerfile.discovery; release/VERIFY.md. */}

## Add the approved provider URLs

Create `<discovery-project>/origins.yaml`. Each enabled origin is fetched only during packaging.

```yaml
schemaVersion: registry-discovery/origins/v1alpha1
origins:
  - originId: evidence-provider
    catalogUrl: https://<provider-host>/catalog.jsonld
    profile: registry-discovery-v1alpha1
    enabled: true
```

Use a unique `originId` for each approved URL. Production origins use HTTPS. The
`--allow-loopback` option exists only for a local development origin.

{/* Evidence: `crates/registry-discoveryctl/src/project.rs`, `check_project()` validates the closed
    origin shape, profile identifier, unique origin IDs and URLs, and HTTPS policy; its test
    `loopback_http_requires_the_explicit_development_switch` covers the development exception. */}

## Add evidence-type mappings

Create `<discovery-project>/mappings/adult-status.yaml` for each requirement that needs
evidence-type resolution. A mapping can offer alternatives, but every ID in one alternative is
required together.

```yaml
schemaVersion: registry-discovery/evidence-mapping/v1alpha1
mappingId: urn:example:mapping:adult-status
mappingAuthorityId: urn:example:authority
requirementId: urn:example:requirement:adult-status
jurisdiction: urn:example:jurisdiction
alternatives:
  - evidenceTypeListId: urn:example:list:adult-status
    evidenceTypeIds:
      - urn:example:evidence-type:adult-status
```

`jurisdiction` is optional. Use the same exact identifier in a resolve request when your mapping
declares one.

{/* Evidence: `crates/registry-discoveryctl/src/project.rs`, `AuthoredEvidenceMapping` and
    `AuthoredEvidenceTypeAlternative`, define the closed mapping fields. `crates/registry-discovery/src/query.rs`,
    `resolver_preserves_and_within_lists_or_across_alternatives_and_refuses_over_bound`, proves
    conjunction within an alternative and alternatives across the response. */}

## Check the authoring project

Run the offline check before packaging.

```sh
discoveryctl check --project "<discovery-project>"
```

Expected output reports the validated origin and mapping counts:

```text
valid origins=1 mappings=1
```

The check reads the two authoring inputs but does not contact the provider URL.

{/* Evidence: `crates/registry-discoveryctl/src/lib.rs`, `Command::Check`, prints this output.
    `crates/registry-discoveryctl/src/project.rs`, test
    `check_is_offline_and_accepts_an_unreachable_https_origin`, proves that validation is offline. */}

## Package one bounded index

Package into a new directory when the approved provider descriptions are available.

```sh
discoveryctl package \
  --project "<discovery-project>" \
  --output "<deployment-directory>/discovery-package" \
  --revision "<source-revision>"
```

Expected output reports the package digest and independent semantic revisions:

```text
packaged packageDigest=sha256:<64-lowercase-hex-digits> catalogRevision=sha256:<64-lowercase-hex-digits> mappingRevision=sha256:<64-lowercase-hex-digits>
```

The command fetches every enabled origin once, records the exact fetched-byte digest and fetch time,
and records `builtAt` after every origin has been collected and the semantic revisions compile. It
writes `discovery-index.json`, optional `REVISION`, and `SHA256SUMS` into a directory that must not
already exist. The package digest is the SHA-256 digest of the exact `SHA256SUMS` bytes.

Packaging the same compiled index bytes and revision twice produces the same package digest. A new
collection records new `originFetchedAt` and `builtAt` provenance, so its package digest may change
even when `catalogRevision` and `mappingRevision` stay the same. Build a new directory and deploy it
as a unit. Do not edit a package in place.

{/* Evidence: `crates/registry-discoveryctl/src/build.rs`, `fetch_origins()`, `write_index_package()`,
    and `packaging_the_same_compiled_index_twice_is_repeatable`;
    `crates/registry-discoveryctl/tests/build.rs`,
    `package_fetches_each_origin_once_and_preserves_semantic_revisions` and
    `production_package_time_is_captured_after_origin_collection`. */}

## Deploy and restart the service

Place this `runtime.yaml` outside the package directory. Set `package.root` to its absolute path and
copy the reported package digest into `package.expectedDigest` when you want to pin the deployment.

```yaml
apiVersion: registry.registrystack.org/discovery-runtime/v1alpha1
kind: DiscoveryRuntimeConfig
listener:
  bind: 127.0.0.1:8080
package:
  root: /srv/registry-discovery/package
  expectedDigest: sha256:<digest-of-SHA256SUMS>
limits:
  maximumRequestBytes: 65536
  maximumResponseBytes: 1048576
  maximumResultRecords: 100
  maximumResultAlternatives: 100
  requestTimeoutSeconds: 10
  shutdownTimeoutSeconds: 10
logLevel: info
```

Start the new revision after deploying the complete package directory.

```sh
discovery --runtime-config "<absolute-deployment-directory>/runtime.yaml"
```

The runtime file is read through the shared runtime configuration loader, as the other Registry
Stack runtimes read theirs. Its path must be absolute and free of symbolic links, `listener.bind` is
required and takes an IP address host, and a string value may name an environment variable as
`${NAME}`, `${NAME:-default}`, or `${NAME:?message}`. A key from the earlier grammar, such as
`schemaVersion`, `listener.address`, or `indexPath`, is refused with the name of its replacement.

Before binding the listener, the runtime recomputes every `SHA256SUMS` entry, refuses changed,
missing, or extra files by name, checks `package.expectedDigest` when set, then parses the exact
verified `discovery-index.json` bytes. The process remains running with that captured index. Verify
readiness from a network location that can reach the listener.

```sh
curl --fail-with-body "http://127.0.0.1:8080/ready"
```

```json
{"status":"ready"}
```

The runtime accepts only its listener, package location and optional pin, limits, and log level. It does not carry
provider credentials, origin-fetch configuration, mappings, or application trust configuration.
The runtime acquires one of four request-body permits before reading a body and holds the permit
through request handling. It also rejects malformed percent escapes, invalid UTF-8 query values,
duplicate media-type headers, and request media parameters other than UTF-8 JSON.

{/* Evidence: `crates/registry-discovery/src/startup.rs`, `RuntimeConfig`, `load_runtime()`, and
    tests `runtime_is_closed_and_contains_no_origin_mapping_trust_or_fetch_configuration`,
    `the_runtime_file_is_read_through_the_shared_loader`,
    `removed_runtime_keys_name_their_replacements`,
    `the_listener_bind_is_required_and_substitutes_from_the_environment`,
    `startup_verifies_package_and_refuses_expected_digest_mismatch_with_common_shape`, and
    `exact_consumed_index_bytes_remain_bound_to_the_verified_package`;
    `crates/registry-discovery/src/server.rs`, tests
    `request_body_capacity_is_acquired_before_buffering_and_recovers` and
    `request_media_type_accepts_only_bare_or_utf8_json`; `crates/registry-discovery/src/query.rs`,
    test `malformed_percent_encoding_and_invalid_utf8_are_refused`. */}

## Verify resolution and exact search

Resolve an application requirement to its evidence-type alternatives.

```sh
curl --fail-with-body \
  -H 'Content-Type: application/json' \
  --data '{"requirementId":"urn:example:requirement:adult-status","jurisdiction":"urn:example:jurisdiction"}' \
  "http://127.0.0.1:8080/v1/evidence-types/resolve"
```

The response contains `mappingRevision` and the configured alternative with its mapping authority.

```json
{"requirementId":"urn:example:requirement:adult-status","jurisdiction":"urn:example:jurisdiction","mappingRevision":"sha256:<64-lowercase-hex-digits>","alternatives":[{"evidenceTypeListId":"urn:example:list:adult-status","evidenceTypeIds":["urn:example:evidence-type:adult-status"],"mappingId":"urn:example:mapping:adult-status","mappingAuthorityId":"urn:example:authority"}]}
```

Search the catalog for a public Evidence Gateway advertisement. Copy the selected `recordId`,
`endpointUrl`, and origin provenance from the result into your application selection record.

```sh
curl --fail-with-body --get \
  --data-urlencode 'serviceKind=evidence' \
  --data-urlencode 'evidenceType=urn:example:evidence-type:adult-status' \
  "http://127.0.0.1:8080/v1/services"
```

The response has a `catalogRevision` and an `items` array. Each item carries the provider endpoint,
its advertised capability IDs, and the origin URL, content digest, and fetch time.

For Registry Relay, use `serviceKind=relay` and one or both of `semanticClass` and
`operationFamily` as repeated query filters. A protected-only Relay can advertise neither public
semantic class nor operation family.

{/* Evidence: `crates/registry-discovery/openapi.json` defines both calls and the query fields.
    `crates/registry-discovery/tests/http_journey.rs`,
    `immutable_index_supports_resolution_and_exact_service_search`, exercises these exact request
    shapes. `crates/registry-discovery/src/model.rs`, `ServiceRecord`, defines provenance fields.
    `products/discovery/ACCEPTANCE-JOURNEYS.md` records the protected-only Relay case. */}

## Hand off to native product trust

Discovery selection is metadata only. Before an application creates a native request, use its own
Evidence Gateway or Registry Relay trust configuration to accept or reject the selected provider,
then invoke the selected `endpointUrl` directly with that product's native client. Do not send
credentials or native request data to Registry Discovery.

{/* Evidence: `crates/registry-discovery-client/src/selection.rs`,
    `discovery_metadata_has_no_trust_or_native_io_capability`, proves that a selection excludes
    trust anchors, credentials, native request data, and responses. `products/discovery/DECISIONS.md`,
    ADR-001, assigns provider trust and invocation to the native product. */}

## Troubleshooting

| Symptom | Cause | Fix |
| --- | --- | --- |
| `discoveryctl check` fails | The origins or mapping document is not in the closed authoring shape. | Correct the schema version, identifiers, and duplicate values, then run the offline check again. |
| `discoveryctl package` fails | An enabled provider URL could not be fetched safely, did not return the exact profile media type, or the output directory already exists. | Confirm the provider's public description deployment and choose a new output directory. Keep the previous package running until packaging succeeds. |
| Startup says the package does not match `SHA256SUMS` | A listed file changed, is missing, or an extra file appeared in the package directory. | Redeploy the whole directory produced by `discoveryctl package`. |
| Startup reports different expected and found package digests | `package.expectedDigest` pins a different package. | Deploy the pinned package or update the pin to the reviewed `packageDigest`. |
| A search returns no items | The index contains no advertisement that matches every supplied filter. | Inspect the advertised capability IDs and use a narrower or correct filter set. |
| Native connection is refused | The application did not accept the selected provider under its local product trust configuration. | Resolve the native Evidence Gateway or Registry Relay trust decision before retrying the direct connection. |

{/* Evidence: `crates/registry-discoveryctl/src/build.rs`, `BuildError`; `crates/registry-discovery/src/query.rs`,
    `service_matches_filters`; `crates/registry-discovery-client/src/selection.rs`, exact selection
    errors and no-native-I/O test. */}

## Next

- [Configure Evidence Gateway](../evidence/) to set the native trust an application uses once
  Discovery has selected an Evidence provider.
- [Author a Registry Relay project](../relay/) to set the native trust an application uses once
  Discovery has selected a Relay provider.