Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ status: current
owner: registry-docs
source_repos:
- registry-stack
last_reviewed: "2026-09-04"
last_reviewed: "2026-09-08"
Comment thread
jeremi marked this conversation as resolved.
doc_type: tutorial
persona:
- data publisher
Expand All @@ -28,7 +28,7 @@ from there.
outcome="A package built from your project, signed with a local key, and accepted by bregctl verify under a trust anchor you wrote."
time="About 30 minutes"
level="Production build with synthetic data and local keys"
prerequisites={['bregctl 0.26.1 from Create and query your first registry', 'Docker', 'OpenSSL 3']}
prerequisites={['The bregctl from Create and query your first registry', 'Docker', 'OpenSSL 3']}
/>

## Before you start
Expand Down Expand Up @@ -152,7 +152,7 @@ package, or a repository. They exist for this exercise and are deleted in the cl

The runtime connects to PostgreSQL over TLS only, and the schema test uses the same connection code,
so the container needs a server certificate. Create a small certificate authority and a certificate
for `localhost`, then start the PostgreSQL 17 image the quickstart launcher uses, on port 5433 so it
for `localhost`, then start a PostgreSQL 17 container on port 5433 so it
stays clear of a PostgreSQL you may already run:

```sh
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ status: current
owner: registry-docs
source_repos:
- registry-stack
last_reviewed: "2026-09-07"
last_reviewed: "2026-09-08"
doc_type: tutorial
persona:
- data publisher
Expand Down Expand Up @@ -73,42 +73,34 @@ Authoring check passed.
```

This is the project the first tutorial started as a registry, so the entity, fields, and profiles are
the ones you used over HTTP: `registry.yaml`, one module under `modules/`, `tests/journeys.yaml`,
`dev-clients.yaml`, `runtime.example.yaml`, and a `README.md` that lists them.
the ones you used over HTTP, in the files [init wrote](../first-breg/#what-init-wrote).
The `revision` line is the digest of the compiled project; it changes with every edit you make.

A finding is advice the compiler attaches to a result that succeeded: an error stops a command, a finding does not.
The first says the `operator` profile can list every row, which is intended for a registry-wide operations team,
and the second says the `evidence-source` profile can look up any record by its code;
the project's `record-reader` profile shows the claim-bound row restriction that closes either one.
Every command in this tutorial repeats both findings, so treat them as expected and read past them to the result.
and the second says the `evidence-source` profile can look up any record by its code.
Every command in this tutorial that succeeds repeats both findings, so treat them as expected and read past them to the result.
A check that is refused prints its errors instead, and you will meet one when you change the module.

{/* Evidence: crates/registry-bregctl/src/lib.rs, init_files() and check(); crates/registry-breg/src/access.rs. */}

## Read the module

A module is a separate file that contributes to the model, so a reusable part of a registry can be reviewed
and versioned apart from the project that adopts it.
Open `tutorial-work/project/modules/record-notes/module.yaml`.
Written out as block mappings (the file keeps each field on one line), it declares one optional field on the
entity the project owns:
Open `tutorial-work/project/modules/record-notes/module.yaml` and find its version and the one field it
declares on the entity the project owns:

```yaml
id: record-notes
version: 0.1.0
extendEntities:
- entity: record
fields:
- id: internal-note
type: string
maxLength: 500
classification: internal
```

```yaml
- {id: internal-note, type: string, maxLength: 500, classification: internal}
```

A field without `required: true` is optional, so existing records stay valid and a create may omit it.
Its classification matches the entity's default, `internal`.
A more sensitive value such as `restricted` adds a further finding, `access.profile.higher_classification`,
once a grant exposes the field; it asks you to confirm the profile's scope and purpose first.

Now open `tutorial-work/project/registry.yaml` and find the `modules` entry at the end of the file:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ status: current
owner: registry-docs
source_repos:
- registry-stack
last_reviewed: "2026-09-07"
last_reviewed: "2026-09-08"
doc_type: tutorial
persona:
- data publisher
Expand Down Expand Up @@ -127,11 +127,10 @@ display to **Extents** and confirm that the longitude span is less than `0.24` d
latitude span is less than `0.19` degrees.

The `installation-map-reader` profile caps a bounding box at `0.25` longitude degrees by `0.20`
latitude degrees. The cap limits how much of the map one query may ask for. It does not decide
which records you may see: that is the profile's row boundary, which compares each record's
`zone` with the `service_zones` claim in the installation credential. The smaller view leaves
room for QGIS decimal serialization and for canvas aspect-ratio changes. A world view exceeds the
cap and Base Registry Engine refuses the query with `query.invalid`.
latitude degrees, and a world view exceeds it, so Base Registry Engine refuses the query with
`query.invalid`. The smaller view leaves room for QGIS decimal serialization and for canvas
aspect-ratio changes. The cap limits how much of the map one query may ask for, not which records
you may see; that is the profile's row boundary, which you meet in the next steps.

{/* Evidence: products/breg/acceptance/spatial-service-sites/registry.yaml;
crates/registry-breg/src/query.rs, parse_bbox();
Expand Down Expand Up @@ -180,15 +179,11 @@ messages. The quickstart creates the secret file with owner-only permissions.
QGIS draws Point features near the map center. Press `F6` to open the attribute table. The
table includes `id`, `label`, `mapLabel`, `nativeId`, `serviceKind`, `siteCode`, and `zone`.

The installation credential carries a `service_zones: central` claim, so the collection returns
only records whose `zone` is `central`. The layer is read-only, requests at most 25 records per
page, and sends the visible extent as a bounding box. Base Registry Engine applies the same scope,
purpose, row, and field rules to the QGIS-compatible route as it applies to native reads.

The bounding box is a filter inside that row boundary, not the boundary itself. If you leave
**Only request features overlapping the view extent** off, QGIS sends no bounding box and pages
through every record the row boundary allows, 25 at a time; the server allows at most 100 records
per page on this collection.
Every `zone` value is `central`: the installation credential carries a `service_zones: central`
claim, and the profile's row boundary returns only records whose `zone` matches it. The bounding
box is a filter inside that boundary, not the boundary itself. Base Registry Engine applies the
same scope, purpose, row, and field rules to the QGIS-compatible route as to native reads; the
[API reference](../../reference/breg-api/#spatial-adapter) covers the route and its paging limits.

{/* Evidence: crates/registry-breg/src/api/gis.rs, collections(), collection(), items(),
authorize_gis_collection(), parse_items_query() and query_options();
Expand Down Expand Up @@ -272,8 +267,8 @@ Imported the input. 1 item committed.
Run the block as one unit: every token the spatial quickstart mints, including the operator token
this block requests, expires after 60 seconds. The checkpoint records each committed chunk, so an
interrupted import resumes without sending a completed chunk again. If you run the import a
second time with the same checkpoint, it sends nothing and reports `0 items committed` with
`complete` at `true`. The operator token stays in an owner-only file and is never copied into QGIS.
second time with the same checkpoint, it sends nothing and prints the same summary, because the
counts come from the checkpoint. The operator token stays in an owner-only file and is never copied into QGIS.

{/* Evidence: crates/registry-bregctl/src/lib.rs;
products/breg/quickstart/support/quickstart.py, store_token();
Expand Down
33 changes: 16 additions & 17 deletions docs/site/src/content/docs/tutorials/query-breg-client.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ status: current
owner: registry-docs
source_repos:
- registry-stack
last_reviewed: "2026-09-07"
last_reviewed: "2026-09-08"
doc_type: tutorial
persona:
- consumer or verifier
Expand All @@ -16,14 +16,12 @@ standards_referenced:

import QuickstartMeta from '../../../components/QuickstartMeta.astro';

This tutorial needs a registry on Registry Stack v0.26.1 or later and a client package at exactly
that registry's version. The unified client packages start at v0.26.1: against a v0.26.0 registry
there is no Base Registry Engine client package to install. The unified Registry Stack client gives
an application one method for each Base Registry Engine operation, in Python and in Node. In this
tutorial you install the published package, read the record you created in the first tutorial,
create a record with an idempotency key, page through the collection with a continuation, and
inspect a refused write. Every step shows both languages; follow the one your application uses, or run both against
the same registry.
The unified Registry Stack client gives an application one method for each Base Registry Engine
operation, in Python and in Node. In this tutorial you install the published package at your
registry's version, read the record you created in the first tutorial, create a record with an
idempotency key, page through the collection with a continuation, and inspect a refused write.
Every step shows both languages; follow the one your application uses, or run both against the
same registry.

<QuickstartMeta
outcome="A Python program and a Node program that read, page, and write your local registry through the unified client and handle a refusal explicitly."
Expand Down Expand Up @@ -59,6 +57,7 @@ Success prints nothing. The file is readable only by your user; the programs rea

## Install the client

The package version must equal the registry's, and the packages start at v0.26.1.
Work in a directory of its own and read the version from the `breg` binary rather than typing it:

```sh
Expand Down Expand Up @@ -273,7 +272,9 @@ while True:
}
```

The run adds one line per record, across two pages:
The run adds one line per record, across two pages.
A list without `orderby` is sorted by the identifier the registry assigned, so the two lines come
in either order:

```text
DEMO-002
Expand Down Expand Up @@ -322,12 +323,9 @@ problem
mutation.conflict
```

Other kinds arrive the same way. The ones you are most likely to meet are `transport` when the
connection or the exchange failed below HTTP, `configuration` and `invalid_request` when the client
refused an input before sending anything, `metadata_selection` when a binding could not be
selected, and `protocol` when a response violated the contract. Every kind carries a message
(`str(error)` in Python, `error.message` in Node); a kind that comes from a response also carries
the trace identifier to quote when you ask the registry operator about it. The
Other kinds arrive the same way, each with a message (`str(error)` in Python, `error.message` in
Node), and a kind that comes from a response also carries the trace identifier to quote when you
ask the registry operator about it. The
[Registry Stack client API reference](../../reference/client-api/#breg-error-contract) names
every kind the bindings raise and the members each one sets.

Expand All @@ -354,7 +352,8 @@ inspect a refused write through one error type.
| Symptom | Cause | Fix |
| --- | --- | --- |
| `pip` or `npm` finds no matching version | The registry is older than v0.26.1, or `version` does not match `breg --version` | Install a v0.26.1 or later release, start the registry with it, and reinstall at its exact version |
| `KeyError: 'REGISTRY_URL'`, or a `TypeError` naming the `path` argument | `REGISTRY_URL` or `REGISTRY_TOKEN_FILE` is not set | Prefix the command with both assignments as shown in [Construct a client and probe the registry](#construct-a-client-and-probe-the-registry) |
| `KeyError: 'REGISTRY_URL'` in Python, or `configuration` in Node | `REGISTRY_URL` is not set | Prefix the command with both assignments as shown in [Construct a client and probe the registry](#construct-a-client-and-probe-the-registry) |
| `KeyError: 'REGISTRY_TOKEN_FILE'` in Python, or a `TypeError` naming the `path` argument in Node | `REGISTRY_TOKEN_FILE` is not set | Prefix the command with both assignments |
| `problem` with status `401` and code `authentication.refused` | The token is older than five minutes | Renew it with the `mint token` command in [Before you start](#before-you-start) and rerun |
| `transport` with a `connect` transport kind | The registry is not running | Start it again with the first tutorial's `bregctl dev` command and renew the token |
| `problem` with status `400` and code `query.invalid` | A filter names a field the profile does not allow | Filter on `code` or `status` only |
Expand Down
Loading