Released docs. You are viewing the documentation published with v0.20.0. Development docs are available at Latest.
Use relayctl to author one institution-owned Registry from reviewed SQLite views.
The governed contract names the Registry, its mandatory Registry Core context, published properties,
finite access profiles, wire formats, query capabilities, and disclosure.
The database remains a source binding, not an API model.
When to use this
Section titled “When to use this”Use this guide after the synthetic Relay tutorial works and the Registry Authority has selected an authoritative SQLite source. The result is a deployment candidate for institutional and operator review, not a running service.
Relay serves one Registry per process. A resource is a governed Record type in that Registry, not a SQLite table and not a second Registry.
Before you start
Section titled “Before you start”Prepare a non-writable SQLite inspection copy, narrow reviewed views, a stable Registry identifier, the Authority and operational roles, and synthetic fixtures. Also agree which callers, purposes, row boundaries, fields, and query capabilities each operation may use. Choose wire formats separately from those access decisions. Keep production Records, identifiers, credentials, and database files out of the project.
Inspect structure before assigning meaning
Section titled “Inspect structure before assigning meaning”Initialize the project, then inspect the source copy:
relayctl init ./business-registryrelayctl inspect ./business-inspection.sqlite --starters ./business-registry/inspectioninspect records only SQLite structure: objects, columns, declared types, nullability, key
membership, and a schema fingerprint.
It does not read row values.
The starter and the generated identification candidates use that structure, authored roles,
codelist bindings, and the embedded digest-pinned core rule pack.
They are suggestions, never approved classification or publication truth.
Copy only accepted view bindings and the schema fingerprint into registry.yaml.
Unconfigured tables, columns, joins, expressions, and sort order remain unavailable to callers.
Keep Registry Core and published properties separate
Section titled “Keep Registry Core and published properties separate”Every successful Record always carries the Registry Core context:
registryIdentifierandrecordIdentifier.revisionIdentifier,lifecycleState, and source-ownedrecordedAt.authorityIdentifier,schemaReference, andsemanticModelReference.domainData, containing only serializable published properties.
Bind the Record identifier, revision, lifecycle, and recorded time to reviewed view columns.
recordedAt is when the Authority recorded the revision, not Relay startup, snapshot, or response
time.
Declare a published property separately from its source column, even for a one-to-one binding.
Its public name, type, semantic term, source requiredness, and output classification are part of
the contract.
Review classification as one governed input
Section titled “Review classification as one governed input”Every published property has an output classification. Every source-view column Relay processes also has a source-column classification, including hidden Registry Core, selector, filter, order, row-binding, and transform-input columns. Resource defaults reduce repetition, but compilation expands them to a complete effective classification.
Generate the deterministic, value-free review inputs beneath generated/:
relayctl generate ./business-registryThe command writes:
reports/identification-report.jsonreports/classification-inventory.jsonreports/access-profile-report.jsonreports/contextual-review-findings.jsongovernance/classification-review-starter.yaml
The classification-review.yaml sidecar is strict and names the inventory it approves.
Use generated when the review accepts an identification report, imported for reviewed material
from another process, or manual for an institutional review without generated identification.
Only generated review binds an accepted copied report and its rule-pack identity.
apiVersion: relay.registrystack.org/classification-review/v1kind: ClassificationReviewregistryIdentifier: urn:example:registry:registered-businessesclassificationInventoryDigest: sha256:<inventory-digest>method: manualreviewer: urn:example:institution:company-registrarreviewDate: 2026-08-10status: reviewedrationaleRef: governance/classification-review-rationale.mdProduction compilation refuses a missing, non-reviewed, stale, or digest-mismatched sidecar. A relevant contract, schema, source-column, or classification change invalidates review. A rule-pack change matters only where that pack informed the review.
Define reviewed access profiles
Section titled “Define reviewed access profiles”Each list, read, named exact-lookup, or named search operation has a finite ordered map of named
access profiles and exactly one defaultAccessProfile.
If an operation has any public access profile, its default must also be public. This keeps omission
truthful for anonymous callers and the generated public OpenAPI.
Each access profile selects one access rule and one disclosure profile. The disclosure profile
defines the maximum property set that can reach domainData.
Callers may omit accessProfile to select the declared default, or supply one named access
profile. Relay authorizes the supplied access profile exactly as requested.
A syntactically valid unknown name and a scope-hidden name receive the same
404 resource.not_found response. An invalid bearer is 401. Relay never falls back to the
default or another access profile.
The fields parameter can only select a non-empty subset of the selected profile’s disclosed
properties.
It cannot add a property, select a source column, change a transform, weaken handling, or bypass
an access or row boundary.
Registry Core remains present.
The Accept header selects JSON, JSON for Linked Data (JSON-LD), or GeoJSON serialization after
access and disclosure are decided. formatProfile refines GeoJSON where Relay exposes a finite
profile choice. A wire format is never an access right.
Add a bounded Point profile
Section titled “Add a bounded Point profile”Declare one Point property when a resource publishes reviewed longitude and latitude columns in
Coordinate Reference System 84 (CRS84) order. Keep existing scalar properties unchanged and name
the Point as the resource’s primaryGeometry.
properties: location: label: Premises location description: Reviewed premises Point in CRS84 longitude-latitude order type: point crs: http://www.opengis.net/def/crs/OGC/0/CRS84 source: {longitudeColumn: longitude, latitudeColumn: latitude} sourceRequired: true semanticTerm: local:location classification: {privacy: non-personal, institutional: public, handling: public, status: reviewed}primaryGeometry: locationdisclosureProfiles: public-premises: {properties: [premisesName, location]} registrar-premises: {properties: [businessRegistrationNumber, premisesName, location]}operations: list: defaultAccessProfile: registrar-premises accessProfiles: registrar-premises: access: {scope: registry:business:premises-list} disclosureProfile: registrar-premises orderBy: [premisesIdentifier] pagination: {defaultPageSize: 50, maximumPageSize: 200} searches: - id: within-bbox query: kind: point-bbox maximumLongitudeSpanDegrees: 2 maximumLatitudeSpanDegrees: 2 defaultAccessProfile: public-premises accessProfiles: public-premises: {access: public, disclosureProfile: public-premises} registrar-premises: access: {scope: registry:business:premises-search-registrar} disclosureProfile: registrar-premises orderBy: [premisesIdentifier] pagination: {defaultPageSize: 50, maximumPageSize: 200}The named search makes the query shape and its authorization independently reviewable. A client with only the list scope cannot search, and a client with only the protected search scope cannot list. The request is explicit:
GET /v2/resources/registered-premises/searches/within-bbox?bbox=100,13,101,14Accept: application/geo+jsonJSON and JSON-LD are available for every selected access profile. GeoJSON is available only when
the selected operation has a primary geometry and the access profile discloses location.
Accept: application/geo+json selects the wire format. Omit formatProfile, or use
formatProfile=rfc7946, for the RFC 7946 profile. Use formatProfile=jsonfg for the bounded JSON-FG
profile. Neither parameter grants another access right or adds a property.
The point-bbox query enables one inclusive, bounded Point-containment search. Relay refuses
non-finite values, coordinates outside CRS84, decreasing bounds, antimeridian crossing, and spans
larger than the authored limits. Relay does not expose a spatial expression language, reprojection,
joins, dynamic SQLite extensions, or an Open Geospatial Consortium API Features service. The
synthetic Relay tutorial executes the tracked
business-registry example.
Publish a bounded statistical dataset
Section titled “Publish a bounded statistical dataset”Use statisticalDatasets for a reviewed, pre-aggregated statistical view. Do not model the view as
a Record resource or an operation with access profiles. Each statistical dataset has one fixed
access rule, one explicit time granularity, bounded query behavior, and an explicit SDMX binding.
Version 1 accepts only a snapshot source for this profile.
The following excerpt assumes that the source, codelists, classifications, and governance paths are declared elsewhere in the same project:
statisticalDatasets: - id: labour-force-participation title: Labour force participation rate description: Reviewed quarterly participation rates from a pre-aggregated view. publication: {releaseAt: 2026-08-10T00:00:00Z} source: {source: labour-statistics, view: relay_labour_force_rates} classificationDefaults: {privacy: non-personal, institutional: public, handling: public, status: reviewed} dimensions: refArea: label: Reference area description: Reviewed statistical reference area. column: ref_area type: code vocabulary: codelists/areas.yaml concept: local:referenceArea time: label: Time period description: Quarterly observation period. column: time_period granularity: quarterly concept: local:timePeriod measure: id: participationRate label: Participation rate description: Labour force participation rate. column: obs_value type: decimal concept: local:participationRate access: public query: {allowUnfiltered: true, maximumObservations: 1000, maximumOffset: 10000} bindings: {sdmx: {}} processingDescriptions: - id: official-statistics-publication operationRefs: [statistics:read] purpose: official-statistics-publication recipientClass: public legalBasisRef: governance/legal-basis.yaml dpvProfileRef: governance/legal-basis.yaml safeguards: [pre-aggregated-view, bounded-query]Choose annual, quarterly, monthly, or daily once for each dataset. Source values, exact
selectors, and time bounds must use that grammar. Relay does not infer granularity from rows or
allow a dataset to mix time-period shapes.
The empty sdmx: {} object selects the binding while leaving REST and message-format versions to
the compiler. Institutions that already govern SDMX identities may supply the supported identity
overrides. Those overrides do not move SDMX terms into the format-neutral dimensions, measure,
access, or query model.
The resulting profile exposes dataflow data, dataflow structure, and data structure definition (DSD) structure reads. Data uses SDMX-JSON 2.1.0 or SDMX-CSV 2.1.0. Structure uses SDMX-JSON 2.1.0. The profile does not expose schema or availability placeholders and does not claim SDMX conformance or certification.
A complete worked statistical project is tracked in the repository at
products/relay-v2/acceptance/labour-statistics.
Read it beside your own contract: it carries the full configuration, a synthetic snapshot,
codelists, the expected HTTP exchanges, and the generated baselines CI holds it to.
Account for processing and disclosure
Section titled “Account for processing and disclosure”Technical handling is ordered from public through internal and confidential to restricted.
Processing handling is the most restrictive level across every column used to answer the request.
Disclosure handling is the most restrictive level among the properties an access profile can
serialize.
Compiler validity, audit context, cache eligibility, and source processing account for the
processing level, even where the released response is less restrictive.
A public access profile cannot transform a non-public source column. Instead, bind it to a reviewed pre-derived public SQLite view column. Classifications can restrict an operation, but cannot create a route, scope, purpose, consent, lawful basis, or row authority.
Relay supports only two compiled, deterministic transforms:
partial-stringemits the fixed Relay marker***and a configured bounded prefix or suffix. A value no longer than the reveal length emits only***.date-precisionaccepts a canonicaldateordate-timeand emits ayearoryear-monthoutput property with its own type, semantic term, and classification.
Transforms are response-only. A transformed property cannot be a list filter or fixed-order key, because the SQLite query would otherwise compare its undisclosed raw input. Expose a separately reviewed pre-derived view column when the derived value must be queryable.
Invalid, noncanonical, oversized, or required missing transform input fails the complete selected
Record as value-free 503 source.unavailable without releasing a value.
Hashing, pseudonyms, encryption, regular-expression replacement, caller-defined masks or
expressions, dynamic per-request masking, and a free-form policy engine are not supported.
Check, test, and package the complete revision
Section titled “Check, test, and package the complete revision”Run the production gate, generate reviewable artifacts, and replay synthetic HTTP fixtures before handoff:
relayctl check ./business-registry --productionrelayctl generate ./business-registryrelayctl test ./business-registryrelayctl diff ./approved-business-registry ./business-registryrelayctl package ./business-registry --output ./business-registry-packagecheck --production and package refuse incomplete governance and unsafe package inputs.
The package contains the contract’s governed file closure and generated artifacts, but not SQLite
data or fixtures.
Relay loads one complete package at startup and does not merge, reload, overlay, or fall back to a
different interpretation.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Cause | Fix |
|---|---|---|
inspect refuses the source | The inspection database is unsafe or writable | Create a non-writable physical-path copy and retry. |
| Production checking reports a review error | The sidecar is missing, not reviewed, stale, or does not match the inventory | Complete the institutional review and regenerate the affected report. |
| A field is rejected | It is not in the selected access profile’s disclosure profile | Review and change the governed access profile, then repeat the full workflow. |
| Packaging refuses the destination | The output directory exists or its closure is unsafe | Select a new empty revisioned directory. |