Released docs. You are viewing the documentation published with v0.46.0. Development docs are available at Latest.
If you are deciding who may read and change what in a Base Registry Engine (BReg) registry, this page shows you how, using the facility template. You will see how a token reaches one access profile, how a profile limits fields and rows, how related Records are disclosed, and which stricter read checks and grants a project can add.
Start from the facility template
Section titled “Start from the facility template”Create the facility project in an empty directory and check it:
bregctl init --template facility-registry facilitybregctl check facilityAuthoring check passed; registry revision sha256:<digest>.The starter check passes without warnings. It keeps operator access on direct, jurisdiction-bound routes. Disclose related Records shows the warning you get when you add its optional related read.
How a token reaches a profile
Section titled “How a token reaches a profile”The runtime verifies every bearer token against the one issuer its runtime configuration names,
and admits only the clients listed in allowedClients. A request without a verified token is
refused before any profile is considered. The same configuration names the token claims that
carry the caller’s principal and purpose; What a token must
carry covers that side.
Each request then uses exactly one profile. The caller names it with the accessProfile query
option; without one, the route’s default applies: the only profile granting that route, or the
one marked default: true. Profiles are never merged, and naming a profile grants nothing. The
profile admits the request only when the token carries its principalClaim, every scope in
requiredScopes, and one of its requiredPurposes, and matches actorKind and
requesterClients when the profile sets them. Here is the profile that maintains operators:
accessProfiles: - id: operator-editor principalClaim: registry_principal requiredScopes: ["registry:operator:write"] requiredPurposes: [facility-administration] permissions: records: - recordType: operator operations: [create, get, list, patch] readableFields: [operator-code, status, jurisdiction] writableFields: [operator-code, status, jurisdiction] rowBoundaries: - {field: jurisdiction, claim: jurisdiction, operator: equals}When the token fails any of these checks, or the profile does not grant the operation, BReg answers exactly as it does for a Record that does not exist, and records the refusal in the audit journal. A caller cannot tell a forbidden Record from a missing one.
BReg has no built-in notion of jurisdiction, region, or office. In the template, jurisdiction
is an ordinary token claim, and the row boundary compares it with the Record’s jurisdiction
field. The local test clients show two readers that differ only in that claim:
- id: reader accessProfiles: [facility-reader] scopes: [registry:facility:read] claims: {registry_principal: synthetic-reader, registry_purpose: facility-administration, jurisdiction: zone-a} - id: foreign-reader accessProfiles: [facility-reader] scopes: [registry:facility:read] claims: {registry_principal: synthetic-foreign-reader, registry_purpose: facility-administration, jurisdiction: zone-b}dev-clients.yaml only mints local tokens that ask for these profiles; it adds no authority. In
production the claim comes from your issuer, so the issuer must be one you trust to assert it.
Grant fields and rows
Section titled “Grant fields and rows”A record permission names a Record type and its operations. readableFields limits what a
response discloses and writableFields what a create or patch may set; filterableFields,
sortableFields, and allowCount each enable one query feature. Field lists use the field id,
not its apiName. A write never reveals a field the profile cannot read.
A row boundary compares a Record field with a token claim.
equals compares with one scalar claim value. in requires a claim whose value is a JSON array;
in dev-clients.yaml, write the array as a YAML sequence:
claims: registry_principal: synthetic-transfer-clerk registry_purpose: permit-administration allowed_jurisdictions: [north-district, south-district]A missing in claim or a scalar in its place fails closed. When journeys need another claim
variant for a refusal, bind that client to the exact step with
testBindings.
Every boundary on a permission must hold. Reads leave other rows out, and writes check both the
stored row and the row as it would be after the change. A permission that creates must keep its
boundary field writable, otherwise every create is refused, and bregctl check says so. A patch
under an in boundary can move a row between the caller’s allowed values, which bregctl check
points out. Write
rowBoundaries: unrestricted only for deliberately registry-wide authority.
An agent is held to a narrower rule. A token from an agent, or one acting for another principal, cannot create, patch, or delete Records directly, with or without a task grant; it reaches them through Actions and Proposals instead.
For a task-grant token, row-boundary claims other than sub resolve only from the grant’s signed
identity object, never from other token claims. To pin a grant to its subject, author a row
boundary on that subject key. Without one, the profile’s ceiling applies to every row, as for
standing agents, and bregctl check reports breg.access-profile.task-grant-rows-unbound.
Disclose related Records
Section titled “Disclose related Records”A Record type can declare a read path to related Records. The facility reaches its operators through the assignment that links them:
readPaths: - {id: operators, through: facility-assignment, to: operator, route: operators}The starter leaves that path ungranted. Its facility-reader profile grants operators directly,
and the facility grant has no readPaths entry:
- recordType: facility operations: [get, list] readableFields: [facility-code, name, address-line, city, postal-code, status, jurisdiction] rowBoundaries: - {field: jurisdiction, claim: jurisdiction, operator: equals}Direct operator reads apply the operator permission’s jurisdiction boundary. A related read uses the facility permission’s authority instead. Add the optional path grant to see that distinction:
operations: [get, list]readableFields: [facility-code, name, address-line, city, postal-code, status, jurisdiction]readPaths: - {path: operators, readableFields: [operator-code, status, jurisdiction]}rowBoundaries: - {field: jurisdiction, claim: jurisdiction, operator: equals}Check the edited project:
bregctl check facilitywarning[breg.access.profile-related-disclosure] <project-dir>/registry.yaml:237:15 /accessProfiles/1/permissions/records/1/readPaths/0 profile `facility-reader` can read `operator` records linked to `facility` through `facility-assignment` on read path `operators`; it sees fields jurisdiction, operator-code, status under its own `facility` row boundaries, and access profiles declared for `operator` do not apply next: Remove fields that these readers should not see from this read path's readableFields, or remove the read path; use a direct permission on the related record type when its own row boundaries should apply.0 errors, 1 warning in 3 filesListing a facility’s operators is authorized by facility-reader’s grant
on the facility, the root of the path. The row boundaries of facility-reader’s own grant on
operator do not apply to the operators that come back. A reader who may see a facility sees
every operator assigned to it, with the path’s readable fields, whatever that operator’s
jurisdiction.
The warning remains even though the direct operator grant names the same three fields, because its
row boundary does not govern the related read. Ask the same two questions of your own paths: can a
related Record sit outside the reader’s rows, and do the path’s fields disclose more than a direct
grant would? If either answer exposes more than you intend, narrow the path’s readableFields, or
remove the path grant and use the direct target grant. The shipped starter takes that latter,
narrower route. When a target, or the Record type a path passes through, must never be disclosed
without its own row check, give that Record type accessRequirements with row boundaries: the
compiler then refuses any read path grant that reaches it.
Require a current membership
Section titled “Require a current membership”A row boundary works when the Record carries the claim value it is compared with. Sometimes the right to read follows an affiliation that changes over time: a facility belongs to an organization, and whoever is currently a member of that organization may read it. Copying members into tokens would leave access in place until each token expired.
membershipBoundaries on a record permission puts that rule into the registry. Each entry names
the Record’s reference field, a membershipRecordType, and that type’s membershipKeyField,
principalField, and activeField. A row is readable only while a membership Record exists with
the same key, the caller’s verified principal, and active: true. Memberships are ordinary
Records that a separate steward profile creates and deactivates. A request that starts after a
deactivation commits no longer sees the Records; a read already running may finish. Historical
reads compare against current memberships too, so a past membership cannot restore access.
Membership boundaries apply to reads only: get, lookup, list, snapshot, and revisions. Give writes
to a separate profile. Declare rowBoundaries beside them, even if only unrestricted; every
membership and row boundary must hold.
Require the subject’s consent
Section titled “Require the subject’s consent”A membership boundary asks whom the caller works for. A consent check asks what the Record’s subject decided: that this recipient may read their Record, for this purpose, under this profile.
requireConsent on a record permission lists checks, each naming a consent record type and
on, the field that identifies the subject (id for the row itself). The project declares
recipients, the organizations that read and the clients each one reads through. BReg derives the
caller’s recipient from the verified client, never from a token claim, so a gated profile must
list its requesterClients. A row is disclosed only while a current consent covers it; a
withdrawal takes effect for every request that starts after it commits, with no token change or
restart. Gated permissions
are read-only, cannot export, and need their own clients, because any profile without
requireConsent reads without the check.
The profile id is the scope the subject agreed to. Removing requireConsent or widening a gated
profile in place would read under a scope the subject never saw, so publish a new profile and list
the old id under retiredConsentScopes.
What a reader meets when a check fails
Section titled “What a reader meets when a check fails”Membership and consent refusals look like every other access refusal. A get on a Record the
caller may not see answers 404, exactly as for a Record that does not exist; a list or count
leaves the Record out. The caller learns nothing about why, or whether the Record exists. The
problem documents in the API reference list the
codes, and the configuration reference carries every key
named on this page.
Grant statistical datasets
Section titled “Grant statistical datasets”A project that publishes counts declares statistical datasets, and a profile reaches them through
permissions.datasets. Each entry names a dataset and its operations: read-live for current
counts, publish to publish or withdraw a release, read-releases to read published releases.
A standing agent profile, one with actorKind: agent and no taskGrant, cannot hold publish:
a release takes effect at once, with no draft for a person to confirm, so the compiler refuses it.
Publish a statistical series covers the datasets themselves.
Read the compiled access
Section titled “Read the compiled access”bregctl explain access facility profile facility-reader actor kind not declared requester clients any principal claim registry_principal task grant none operations ["get","list"] required scopes (all) ["registry:facility:read"] allowed purposes (any) ["facility-administration"]The human report shows the actor kind, requester clients and task-grant requirements beside
each profile’s fields and row restrictions. Action and Proposal permissions include their target
rows, inputs, attachments and ownership. Omitted ownership and writable-input defaults are
explicit. Caller ownership limits which Proposal documents can be managed; it does not bound
the target Records. The report also shows the built-in executor’s system application ceiling. Each
read-path grant names its target Record type, through Record type, and route. rowReach also lists
the target under a read_path surface with the root profile’s row boundaries, so a review that
starts from the disclosed Record type does not miss incoming paths. Add --scenario with a file of
synthetic claims to preview a decision without a token or a database. To prove the refusals against a running registry, write
journeys that read as both readers.
Cookies on Registry Stack
We use optional cookies to understand how you use the main Registry Stack website and this documentation. If you contact us, we may link this activity to your enquiry. You can change your choice at any time. Read the privacy policy.
Your browser asks sites not to track you, so optional tracking stays off.
We could not use or save that choice. Optional tracking remains off.