Released docs. You are viewing the documentation published with v0.46.0. Development docs are available at Latest.
If you are designing your own Base Registry Engine (BReg) project, start from the questions your registry must answer and the changes callers may make, then choose Record types, fields, and references to fit. BReg has no built-in facility, business, or permit model: every pattern here is ordinary configuration you can read in a starter and adapt.
Start from the facility template
Section titled “Start from the facility template”bregctl init --template facility-registry facilitybregctl check facilityThe excerpts below come from the template’s registry.yaml.
Separate an object from its registration
Section titled “Separate an object from its registration”Keep a thing separate from its registration when their lifecycles differ. A facility can change operator without becoming a different facility, and a permit’s expiry need not end the business that held it. The template keeps the link in its own Record type, written once and never patched:
- id: facility-assignment route: facility-assignments primaryDataset: facility-registry mutationMode: create-onlyChoose a field kind
Section titled “Choose a field kind”Pick the narrowest kind that says what the value is. A string carries a bounded line such as a
name or code; text carries long prose. A closed list of states is a vocabulary-code, so a
status cannot drift into spelling variants. A date is a full calendar day and a timestamp an
instant; decimal takes a declared precision for quantities. When several fields share one shape,
declare it once as a value type and refer to it with typeRef:
- {id: postal-code, apiName: postalCode, typeRef: postal-code, required: true, classification: internal} - {id: status, type: vocabulary-code, vocabulary: facility-status, required: true, classification: internal}A string or text field can also declare pattern, a PostgreSQL regular expression the
database enforces on every write path. It is not anchored for you: [0-9]{13} matches any value
containing thirteen digits, so write ^[0-9]{13}$ when you mean the whole value. A pattern checks
one row, so a rule that depends on another record belongs in an Action’s commitGuards. Adding a
pattern to a running registry checks every existing row first; changing or removing one is a
reviewed destructive change. Mark a restricted value encrypted when a database reader must not
see it; Field encryption explains what that seals.
The limits on each kind are in the configuration reference.
Identifiers and uniqueness
Section titled “Identifiers and uniqueness”Keep a scheme identifier, such as a business registration number, separate from the server’s
record ID. The issuing scheme decides what the number means and whether it can be reassigned. Use
a string for values such as 0017, since a number type loses the leading zeroes.
For a number that Base Registry Engine (BReg) issues, put generate on a stored string field.
Choose type: sequence with minimumDigits for a padded number, or type: random with length
for a code. Both accept a literal prefix. For example, generate: {type: sequence, prefix: "APP-", minimumDigits: 4, scope: [jurisdiction]} issues APP-0001 separately within each
jurisdiction. Only a sequence takes scope; leave it out for one namespace across the Record
type. A random code uses a fixed alphabet without easily misread characters. BReg adds the database
uniqueness constraint, and the caller omits the generated field from writes. The Record’s UUID
still identifies it in routes and references.
A scope field must be a public or internal value that is not a reference, because BReg keeps
issued values with their scopes after erasure. It must be supplied on create and cannot change
after issuance. BReg reserves values transactionally and never reuses an issued value. Proposal
Submit allocates and freezes the value for review, and Apply and Retry use that frozen value, so a
rejected or cancelled Proposal still consumes its number. A register that needs gapless numbers
cannot issue them with a sequence through a Proposal Action. A sequence value is also guessable and
shows issuance volume, so use a random code for anything people outside the registry see. If you
enable generation on a field that already exists, a reviewed migration must backfill any missing
values before BReg seeds its allocation state and checks retained values for uniqueness. The
repository’s products/breg/acceptance/generated-identifiers/registry.yaml shows both strategies
and a Proposal Action.
Use a separate identifier Record type when one holder can have several identifiers, or assignments
have their own issuer, validity, or replacement history. Derive uniqueness from the scheme’s rules: two authorities may both issue 0017, while a
shared namespace may forbid the duplicate. A unique constraint can span several fields, such as
issuer and number, and can apply only when a condition holds. Normalization is not proof that two
records describe the same real-world object.
References between Record types
Section titled “References between Record types”A reference field holds the ID of a record of the named Record type, and the database refuses a
value that names no such record. inverseName names the reverse direction, so a facility exposes
its assignments without a field of its own:
- {id: facility, type: reference, target: facility, required: true, classification: internal, relationship: {inverseName: assignments}} - {id: operator, type: reference, target: operator, required: true, classification: internal, relationship: {inverseName: facilityAssignments}}Use a relationship record whenever the link has information of its own. A read path follows the link in one request; the reader’s access profile still decides what it returns:
readPaths: - {id: operators, through: facility-assignment, to: operator, route: operators}Dates and validity
Section titled “Dates and validity”The template records the initial assignment and imposes no dated transfer policy. For dated
assignments, decide separately when a link applies and which concurrent links your policy allows:
one operator, several, or one primary beside other roles. A Record type’s temporal declaration
names its start and end fields; exclusivity needs a separate temporal-non-overlap constraint with
the right scopeFields. A date or role field alone imposes nothing.
Keep effective time separate from recording time: “who operated the facility on June 1?” differs from “what had the registry recorded on June 1?”. Test adjacent and open-ended periods and a correction to an earlier period.
Choose how changes reach records
Section titled “Choose how changes reach records”Generated record routes suit directory maintenance where authorized callers patch each record directly. An immediate Action commits several effects atomically, such as a facility and its first assignment, and a Proposal Action keeps a durable draft for review and later application. Actions and Proposals explains how to choose.
Declarations, provenance, and documents
Section titled “Declarations, provenance, and documents”Keep a reported claim separate from the object it describes when the claim has its own reporter, period, or measurement. An inspection and an operator’s declaration can disagree without one overwriting the other. Decide whether a correction patches the claim or adds a successor that references it.
For imported data, retain the source system, source record ID and version, and mapping revision. Record-level provenance cannot explain individual values unless you also keep the source content.
For a document stored elsewhere, keep its identity, immutable version, media type, and digest. A digest compares bytes; it does not identify the issuer. Permission to read that metadata does not grant retrieval of the file. A document submitted with a Proposal uses a bounded attachment slot, not a string field:
attachments: - {id: supporting-document, required: false, maximumBytes: 1048576, contentTypes: [application/pdf], classification: restricted}Preserve meaning when values are incomplete
Section titled “Preserve meaning when values are incomplete”Missing, unknown, not applicable, and withheld are different answers. Use an explicit reason code
when the distinction matters, rather than turning absence into zero or false: “no declaration
received” differs from “declared quantity is zero”. A known year is not a known January 1; since a
date is a full calendar day, less precise information needs a different representation.
Adapt a pattern without inheriting its policy
Section titled “Adapt a pattern without inheriting its policy”Modules share definitions across projects. An extension adds fields and constraints; it cannot remove an inherited constraint or replace a field, so change the owning definition when its policy does not fit. Review access separately from structure: record reads, Action invocation, and Proposal transitions have separate grants, and a classification label is not an authorization boundary. Test the model with allowed and refused operations, including a case your policy permits that a stricter model might reject.
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.