Skip to content
Registry StackDocsDevelopment (unreleased)

Base Registry Engine client capabilities

View as Markdown

The Base Registry Engine (BReg) client is one Rust implementation in three languages: the registry-breg-client crate (also re-exported as registry_stack_client::breg), the breg namespace of @registrystack/client in Node, and the breg module of registry-stack-client in Python. Rust does every validation and protocol check, so the three behave the same.

Construction, the shared outcome shape, and the same-key retry are in the client API reference.

Python uses the Rust names. The table is generated from the client capability inventory, which a gate checks against the server contracts and each language’s exports.

CapabilityRust and PythonNode
metadata
registry_contract
registryContract
direct reads
get_record
list_records
continue_list
lookup_record
getRecord
listRecords
continueList
lookupRecord
native gis
get_geojson_record
list_geojson_records
continue_geojson_list
getGeoJsonRecord
listGeoJsonRecords
continueGeoJsonList
current
list_current_records
continue_current_list
listCurrentRecords
continueCurrentList
as of
list_records_as_of
continue_as_of_list
listRecordsAsOf
continueAsOfList
snapshot
list_snapshot_records
continue_snapshot_list
listSnapshotRecords
continueSnapshotList
revisions
record_revisions
get_record_revision
recordRevisions
getRecordRevision
relationships
list_relationship_records
continue_relationship_list
listRelationshipRecords
continueRelationshipList
direct writes
create_record
patch_record
createRecord
patchRecord
batch
batch_records
batchRecords
ingestion runs
create_ingestion_run
list_ingestion_runs
read_ingestion_run
submit_ingestion_chunk
cancel_ingestion_run
ingestion_chunk_receipt
createIngestionRun
listIngestionRuns
readIngestionRun
submitIngestionChunk
cancelIngestionRun
ingestionChunkReceipt
attachments
upload_proposal_attachment
download_proposal_attachment
delete_proposal_attachment
uploadProposalAttachment
downloadProposalAttachment
deleteProposalAttachment
create recovery
prepare_create
recover_create
prepareCreate
recoverCreate
immediate actions
action_target_conditions
action_availability
invoke_action
actionTargetConditions
actionAvailability
invokeAction
action recovery
prepare_action
recover_action
prepareAction
recoverAction
statistical datasets
statistics_live
statistics_releases
statistics_latest_release
statistics_release_version
statistics_release_series
statistics_publish
statistics_withdraw
statisticsLive
statisticsReleases
statisticsLatestRelease
statisticsReleaseVersion
statisticsReleaseSeries
statisticsPublish
statisticsWithdraw
tombstone
tombstone_record
tombstoneRecord
proposals
start_proposal
get_proposal
read_proposal
read_proposal_submission
list_proposals
startProposal
getProposal
readProposal
readProposalSubmission
listProposals
proposal recovery
prepare_proposal
recover_proposal
execute_proposal
prepareProposal
recoverProposal
executeProposal
proposal review
read_proposal_review_context_batch
read_proposal_review_context
read_proposal_review_status
read_proposal_review_by_id
download_proposal_review_document
readProposalReviewContextBatch
readProposalReviewContext
readProposalReviewStatus
readProposalReviewById
downloadProposalReviewDocument
event record view
event_record_view
event_record
event_record_creation_revision
eventRecordView
eventRecordDescriptor
eventRecordCreationRevision

A few methods sit outside the inventory:

Method (Node)Purpose
health, readyProbes, sent without a bearer token
openapi, registryMetadata, and the schema read for one Record typeCaller-filtered raw documents for the selected profile
executeRecoveredCreate, executeRecoveredAction, executeRecoveredProposalStartSend a recovered attempt; see Recovery after a restart
verifyHookDeliveryAuthenticate one hook request; see Verify a hook delivery

Python spells each one in snake case. Read options are a request type in Rust, such as BRegListRequest, keyword arguments in Python, and one object in Node. Mutations take positional arguments in all three.

Three Node methods take plain JSON objects: startProposal(binding, inputs, key), listProposals(collection, query), and prepareProposal(proposal, operation, body, key). Python takes dicts for the same arguments, and Rust takes typed values.

const proposal = await client.startProposal(
binding,
{ reason: 'relocation' },
'start-2026-10-17-0001',
);

Many Node methods have a *Json twin, such as getRecordJson or invokeActionJson, that returns the validated response as exact text in valueJson.

Each key a Proposal list accepts, and the Rust query member that carries it. Node and Python take the keys as one query object, named below the table.

Query keyRust
action
BRegProposalQuery.action
owner
BRegProposalQuery.owner_is_caller
target
BRegProposalQuery.target
state
BRegProposalQuery.state
cursor
BRegProposalQuery.cursor
limit
BRegProposalQuery.limit
turn
BRegProposalQuery.turn
pendingManualApplication
BRegProposalQuery.pending_manual_application

Node passes these keys in BaseRegistryClient.listProposals.query; Python passes them in BaseRegistryClient.list_proposals.query.

registryContract(accessProfile) returns BRegMetadata, the registry’s metadata filtered for the caller and that profile. Every write, Action, and Proposal call needs an opaque binding selected from it:

ToNode and Python (snake case)Rust
Create a RecordselectCreate(operationIdentifier, expectedProfile)select_direct_write, which returns BRegDirectWrite::Create
Patch a RecordselectPatch(operationIdentifier, expectedProfile)select_direct_write, which returns BRegDirectWrite::Patch
Invoke an Action or start a ProposalselectAction(actionIdentifier, expectedProfile)select_action
Tombstone a RecordselectTombstone(recordTypeIdentifier, expectedProfile)select_tombstone
Send a batchselectBatch(recordTypeIdentifier, expectedProfile)select_batch
List ProposalsselectProposals(profile)select_proposals
Get one ProposalselectProposalGet(profile)select_proposal_get
Read Proposal reviewsselectProposalReviewContext(profile, action), selectProposalReviewStatus(profile, action)select_proposal_review_context, select_proposal_review_status

A binding ties the call to the client’s origin, the package revision, the profile, the route, and the request contract. It exposes nothing and grants nothing the server would not already allow. When the metadata does not offer the operation, selection fails with kind metadata-selection.

Upgrade the client and server together: an older client’s strict decoder can reject metadata a newer server adds, even for operations the application never calls.

A direct list accepts bbox beside the filters the profile permits. A box is four decimal strings in west, south, east, north order, in CRS84 longitude and latitude. The client refuses reversed bounds, out-of-range values, boxes that cross the antimeridian, and boxes over the 256-byte wire limit. Zero-width boxes are valid. The server enforces the profile’s geometry grant and maximum span.

page = client.list_records(
"establishments", access_profile="map-reader",
bbox=("100", "13", "101", "14"), top=20,
)
const page = await client.listRecords('establishments', {
accessProfile: 'map-reader', bbox: ['100', '13', '101', '14'], top: 20,
});
use registry_breg_client::{BRegBoundingBox, BRegListRequest, BRegRecordOptions};
let request = BRegListRequest::default()
.options(BRegRecordOptions::default().access_profile("map-reader")?)
.bbox(BRegBoundingBox::new("100", "13", "101", "14")?)
.top(20)?;
let page = client.list_records("establishments", &request).await?;

registry_stack_client::breg does not re-export BRegBoundingBox; depend on registry-breg-client for it.

Use the GeoJSON methods when you need a Feature or FeatureCollection. Leaving the geometry field out of select yields null geometry. The /v1/gis adapter routes serve QGIS directly and have no client methods.

Temporal, revision, and relationship reads

Section titled “Temporal, revision, and relationship reads”
ReadNodeNotes
Current statelistCurrentRecordsA separate route from the direct list
State on a date or at an instantlistRecordsAsOfRequires asOf (as_of in Python): a calendar date for a date interval, a UTC RFC 3339 instant for a timestamp interval
A committed snapshotlistSnapshotRecordsTakes snapshot or validAt; keep the returned snapshot reference to repeat the read
Revision listrecordRevisionsThe newest 100 retained revisions, with no continuation
One revisiongetRecordRevisionReturns the revision as bytes (Buffer or bytes)
Related RecordslistRelationshipRecords(route, recordIdentifier, pathRoute, options)Takes the path route from the metadata

A relationship path has its own authority and projection, and refuses bbox and temporal options.

Pass a continuation unchanged to the matching continue* method. It keeps the route, profile, and snapshot identity across later writes. Current authority and retained history still govern every page.

Start a Proposal with startProposal(binding, inputs, key), using a binding selected for an Action whose mode is Proposal Start. Then:

  • getProposal(binding, identifier) finds one by identifier; listProposals(collection, query) pages through them with the query keys above.
  • readProposal(proposal) refreshes one you already hold.
  • prepareProposal(proposal, operation, body, key) prepares a transition (edit, submit, revise, rebase, retry, cancel, or apply), and executeProposal(prepared) sends it.
  • readProposalSubmission(proposal, version) returns one frozen submission exactly as it was submitted: its Action, version, effect digest, review, application status, and decision. Once retention has removed it, the call fails with 410 proposal.submission_expired.
  • readProposalReviewContext(proposal), readProposalReviewStatus(proposal), readProposalReviewById(binding, identifier), and downloadProposalReviewDocument(binding, context, field) read the review. The review context carries no attachment bytes.
  • uploadProposalAttachment, downloadProposalAttachment, and deleteProposalAttachment work on one declared slot. Submitting freezes the attachment digests, so changed bytes cannot inherit an earlier approval.

Each transition has its own grant, and each link carries the Proposal’s ifMatch condition, never a Record ETag. Read the Proposal again after every transition. A later read does not settle an uncertain Start or Submit; recover the original attempt instead.

An immediate Action can run without the general right to read the Record. Select its binding, fetch the target conditions with actionTargetConditions(binding, inputs), then call invokeAction with the same binding, inputs, conditions, and your idempotency key. The client never refreshes the conditions for you. A declared refusal arrives as kind problem with code action.refused and the Action’s refusalCode.

batchRecords(binding, request, key) sends create and patch items for one Record type as one atomic request. A stale item or a failed constraint rolls back every item, and interval constraints are checked on the final state. An optional change context records why: { kind: 'change' } or { kind: 'correction', reasonCode }, each with optional reasonText and sourceReferences (snake case in Python). Rust builds the request with BRegBatchBuilder and BRegChangeContext. The receipt carries a snapshot and per-item results.

tombstoneRecord(binding, recordIdentifier, etag, key) removes a Record from live lists and returns the new revision. Permitted retained history stays readable.

The statistics* methods read live figures and releases, and publish or withdraw a release. A withdrawn version answers 410 with code statistical_dataset.version_withdrawn and the withdrawal reason in reasonCode. A refused release carries refusalCode. Datasets are declared in the statistics configuration.

An ingestion run loads one large input in chunks, each committed on its own, so a load survives a restart. createIngestionRun(route, request) announces the whole input: operation, profile, package revision, schema fingerprint, input digest and length, item and chunk counts, and chunk algorithm version.

submitIngestionChunk(route, runId, chunk, profileId) sends one chunk. Each chunk carries a prefix digest, the SHA-256 of the raw input through the end of that chunk. Build it with BRegIngestionPrefixDigest and encodeIngestionChunk (encode_ingestion_chunk in Python). A resubmitted chunk replays its saved receipt instead of running twice.

An erased chunk receipt answers ingestion.receipt_erased. Cancelling a run stops further chunks; committed chunks stay.

If a process dies between sending a write and reading the answer, replay the same attempt under the same key after a restart. Prepare it before the first send and save its bytes. Node names:

AttemptBefore the first sendAfter a restartSend
Create a RecordprepareCreate(binding, data, key)BRegPreparedCreate.fromBytes, then recoverCreate(binding, prepared)executeRecoveredCreate
Invoke an ActionprepareAction(binding, inputs, key, conditions)BRegPreparedAction.fromBytes, then recoverAction(binding, prepared, inputs, key)executeRecoveredAction
Start a ProposalprepareAction(binding, inputs, key)as for an ActionexecuteRecoveredProposalStart
Proposal transitionprepareProposal(proposal, operation, body, key)BRegPreparedProposal.fromBytes, then recoverProposal(current, prepared)executeProposal

Save the bytes from toBytes(). Python uses snake case, with from_bytes and to_bytes; Rust uses from_slice and as_bytes. In Rust, recover_create returns the request, key, and format to send with create_record, and recover_action returns a request to send with invoke_action or start_proposal.

Action recovery needs the original inputs and key as well as the saved bytes. Recovery checks the saved attempt against a binding freshly selected under the original principal, and reuses the saved target conditions exactly. A restored Proposal transition cannot be executed until recoverProposal has checked it against the current Proposal:

const restored = breg.BRegPreparedProposal.fromBytes(saved);
const current = await client.getProposal(getBinding, proposalIdentifier);
const attempt = client.recoverProposal(current, restored);
const updated = await client.executeProposal(attempt);

Every failure is a BaseRegistryClientError: an Error in Node, an Exception in Python. An attribute the failure does not carry is absent in Node and None in Python.

NodePythonCarries
kindkindOne of the kinds below; always set
outcomeUnknownoutcome_unknowntrue when a write may have committed
statusstatusHTTP status
codecodeThe BReg Problem code, or a bounded code for the kinds that set one
traceIdtrace_idValidated trace identifier
fieldPathfield_pathThe field a Problem names
constraintconstraintA row-local constraint conflict
refusalCoderefusal_codeThe declared reason for action.refused or statistical_dataset.release_refused
reasonCodereason_codeThe reason for statistical_dataset.version_withdrawn
transportKindtransport_kindThe transport failure category
tokenKindtoken_kindThe token failure category
KindMeaningAlso set
configurationConstruction inputs were refusednone
invalid-requestAn input was refused before any exchangenone
tokenThe token provider could not supply a bearertokenKind, and transportKind, code, or status depending on the failure
transportThe exchange failed below HTTPtransportKind
not-foundThe server answered resource.not_foundstatus, code, traceId
problemThe server answered with any other Problemstatus, traceId; a known code adds code and any fieldPath, constraint, refusalCode, or reasonCode
protocolThe response broke the contractstatus, traceId when valid, and code: header-bounds, trace-context, media-type, body, problem, entity-tag, profile-link, location, cache-policy, representation-digest, status, or protocol
metadata-selectionNo binding matchedcode: not-found, unbound-source, profile-mismatch, unsupported-operation, required-capability, or contract-mismatch
hook-verificationA hook delivery failed authenticationcode: missing-header, invalid-delivery-time, duplicate-header, body-too-large, invalid-key, malformed-signature, signature-mismatch, or unsupported-version
clientAny other client-side failurenone

A Problem code this client version does not know arrives as kind problem with only status and traceId; decide on the status. An unknown 4xx is a refusal; an unknown 5xx may have committed. The API reference lists every Problem code.