Skip to content
Registry StackDocsDevelopment (unreleased)

Derive a registry from PublicSchema

For the data publisher

View as Markdown

If you would rather start your registry from a published data model than type its fields yourself, derive one from PublicSchema with Base Registry Engine (BReg). In about 30 to 45 minutes you will have a registry of people, households, and household memberships running on your machine, with one person in it that your operator client can read in full and your reader client can read without the sensitive field. You will then edit the saved selection, compare the newly derived candidate with the running package, and carry a local field and its access policy into the intended successor.

Outcome
A running registry derived from PublicSchema, plus a checked successor that retains a local customization.
Time
About 30 to 45 minutes, plus image downloads
Level
Local evaluation only
Prerequisites
Registry Stack with the BReg componentRunning DockerA shell and an editor

PublicSchema is a published vocabulary for social protection and public-service data: people, households, memberships, programmes, and the code lists that describe them. The BReg component carries a copy of PublicSchema 0.3.0, so deriving from it needs no network.

Starting from the model gives you field names, types, and code lists that other institutions already use, so a ministry system or a partner’s data maps onto your registry without a round of naming meetings. It also settles the mechanical choices the same way every time: each concept you select becomes a Record type with an identifier the caller supplies, each property becomes a bounded field, an enumeration short enough to read becomes a closed list of codes, and a property the model marks sensitive becomes a restricted field.

The derivation is a starting point, not a governance decision. It cannot know who in your country may read a person’s marital status, or what a record means in law. Those stay with your institution: once derived, the project is ordinary source you edit like any other.

The household starter selects three PublicSchema concepts, Person, Household, and GroupMembership, with some of their properties:

Terminal window
rstack breg init household-registry --from publicschema --starter household
Initialized a registry project. 6 artifacts written.
revision sha256:<digest>
README.md
dev-clients.yaml
model/selection.yaml
registry.yaml
runtime.example.yaml
tests/journeys.yaml

It then reports two warnings. Each says a profile reads a field that is more sensitive than its Record type:

warning[breg.access.profile-higher-classification] recordTypes[id=group-membership].accessProfiles[id=operator].readableFields[field=person]
this field is more sensitive than its record type's classification; verify the profile's scope and purpose before disclosing it
next: Remove the field from readableFields, or confirm that the profile scopes and purposes admit only callers cleared for this classification.

The other warning is for a person’s marital status. It is followed by the count and the next steps:

warning[breg.access.profile-higher-classification] recordTypes[id=person].accessProfiles[id=operator].readableFields[field=marital-status]
this field is more sensitive than its record type's classification; verify the profile's scope and purpose before disclosing it
next: Remove the field from readableFields, or confirm that the profile scopes and purposes admit only callers cleared for this classification.
0 errors, 2 warnings
Next:
1. read household-registry/README.md, then run 'bregctl check household-registry'
2. review the generated access profiles; their required scopes and purposes bound
whole-collection access, and household-registry/README.md says where to add row
boundaries when callers need row-level separation
3. run 'bregctl dev household-registry' to start the registry locally and replay
household-registry/tests/journeys.yaml
4. replace canonicalBaseIri in household-registry/registry.yaml before you build a
production package; the derived value is a reserved .invalid name that never
resolves
5. keep household-registry/model/selection.yaml beside the project: edit it and pass
it back with --selection to derive a fresh project

household-registry is a registry project: the files that define your registry. It declares three Record types, person, household, and group-membership, and two access profiles, operator and reader, each naming what its callers may read and change.

The two warnings are about the operator profile reading fields PublicSchema marks sensitive. The generated README explains why they exist and how to align the Record type classifications. Before production, remove fields the operator must not read or confirm that admitted callers are cleared for them.

Open household-registry/README.md. It lists every Record type with the PublicSchema concept it came from, its route, and a table of its fields. The person table ends with the field the model marks sensitive:

| `phone-number` | `phone_number` | string, up to 255 characters | `internal` |
| `marital-status` | `marital_status` | vocabulary-code from `marital-status` | `restricted` |

The README opens with the attribution notice PublicSchema’s license requires:

Keep it with the project and with any package you build from it.

model/selection.yaml is the input the project was derived from. You edit it later when you derive a candidate.

Check the project:

Terminal window
rstack breg check household-registry
Authoring check passed; registry revision sha256:<digest>.

The same two warnings follow, each pointing at the line of registry.yaml it is about.

The selection records what came from PublicSchema, but the derived project is the source you own. Add an optional note to the person Record type for a local registration reference:

household-registry/registry.yaml
- {id: phone-number, type: string, maximumLength: 255, classification: internal}
- {id: marital-status, type: vocabulary-code, vocabulary: marital-status, classification: restricted}
- {id: registration-note, type: string, maximumLength: 120, classification: internal}
constraints:

Give the operator permission to read and write the field. Do not add it to the reader profile, because this registry’s reporting client does not need the local reference:

household-registry/registry.yaml
readableFields: [person-code, given-name, family-name, date-of-birth, sex, preferred-language, phone-number, marital-status]
writableFields: [person-code, given-name, family-name, date-of-birth, sex, preferred-language, phone-number, marital-status]
readableFields: [person-code, given-name, family-name, date-of-birth, sex, preferred-language, phone-number, marital-status, registration-note]
writableFields: [person-code, given-name, family-name, date-of-birth, sex, preferred-language, phone-number, marital-status, registration-note]
filterableFields: [person-code, given-name, family-name, sex, preferred-language, phone-number, marital-status]

Check after an ordinary source edit, before the local runtime packages it:

Terminal window
rstack breg check household-registry

The check still passes with the two classification warnings. A missing writable grant would instead report that the optional stored field has no writer; correct the field or its intended grant before you continue.

Terminal window
rstack breg dev start household-registry
bregctl dev succeeded.
status ready

The first start downloads PostgreSQL and the local token issuer, so allow a few minutes. Before it reports ready, dev replays the scenarios in tests/journeys.yaml against a throwaway database, then starts your registry on an empty one. The report also prints the registry’s address, breg url, and one client for each access profile, operator and reader. If dev start stops because a port is taken, Create your first registry shows how to pick other ports.

Save the person you will create. The API takes field names in camel case, personCode for the person-code field:

person.json
{
"data": {
"personCode": "P-0001",
"givenName": "Ama",
"familyName": "Mensah",
"dateOfBirth": "1990-04-12",
"maritalStatus": "married",
"registrationNote": "local-ref-17"
}
}

Create it as the local operator client. The API command finds the running project and obtains a fresh credential. Every write carries an idempotency key, so a retry of the same request never creates a second person:

Terminal window
rstack breg api /v1/records/persons \
--project household-registry \
--client operator \
--idempotency-key first-person \
--input person.json
{
"domainData": {
"dateOfBirth": "1990-04-12",
"familyName": "Mensah",
"givenName": "Ama",
"maritalStatus": "married",
"personCode": "P-0001",
"phoneNumber": null,
"preferredLanguage": null,
"registrationNote": "local-ref-17",
"sex": null
},
"recordIdentifier": "<uuid>",
"revisionIdentifier": "1"
}

The response carries the Record the registry now holds: your fields under domainData, an identifier the registry assigned, and revision 1. Fields you did not send are null, because the derivation makes no PublicSchema property required.

List people under the reader access profile:

Terminal window
rstack breg api '/v1/records/persons?accessProfile=reader' \
--project household-registry \
--client reader
{
"domainData": {
"dateOfBirth": "1990-04-12",
"familyName": "Mensah",
"givenName": "Ama",
"personCode": "P-0001",
"phoneNumber": null,
"preferredLanguage": null,
"sex": null
},
"recordIdentifier": "<uuid>",
"revisionIdentifier": "1"
}

The same person comes back without maritalStatus or registrationNote. The derivation left the sensitive PublicSchema field out of the reader grant, and your local policy did the same for the institution-specific field.

Edit the saved selection and derive a candidate

Section titled “Edit the saved selection and derive a candidate”

Open household-registry/model/selection.yaml and remove marital_status from Person:

household-registry/model/selection.yaml
- concept: Person
properties:
- name: given_name
- name: family_name
- name: date_of_birth
- name: sex
- name: preferred_language
- name: phone_number
- name: marital_status
- concept: Household

Derive a candidate from the edited file into a fresh directory. Do not derive over the project you run: a fresh directory keeps the generated result separate from source you have governed locally.

Terminal window
rstack breg init household-registry-2 --from publicschema --selection household-registry/model/selection.yaml

The report has a new revision and one warning, for the membership’s link to a person:

warning[breg.access.profile-higher-classification] recordTypes[id=group-membership].accessProfiles[id=operator].readableFields[field=person]
this field is more sensitive than its record type's classification; verify the profile's scope and purpose before disclosing it
next: Remove the field from readableFields, or confirm that the profile scopes and purposes admit only callers cleared for this classification.

Check the generated candidate, then compare its compiled result with the package the local runtime configuration binds:

Terminal window
rstack breg check household-registry-2
rstack breg diff household-registry-2 \
--runtime-config "$(pwd)/household-registry/.breg/dev/runtime.yaml"
baseline assurance runtime-bound
changes.person.marital-status
changes.person.registration-note

The check passes with the one warning. The diff says baseline assurance runtime-bound and reports both the intended PublicSchema removal and the unintended loss of registration-note and its operator grants. Do not seek migration approval for this result: first compare the source and restore the local decision.

To build a selection by answering questions instead, run rstack breg init <directory> --from publicschema at a terminal. The PublicSchema wizard prompts describes each question.

Compare the source and reconcile the customization

Section titled “Compare the source and reconcile the customization”

Compare the authored registry files before treating the generated directory as a successor:

Terminal window
diff -u household-registry/registry.yaml household-registry-2/registry.yaml

diff exits 1 when it finds differences and 2 when the comparison itself fails. Read the whole comparison. It includes the intended removal of marital-status, but it also shows that the fresh derivation has no registration-note field or operator grants. The selection can reproduce model-derived choices; it does not own local fields, classifications, grants, Actions, or other policy that you added to the project.

Reconcile those decisions manually. Add the field to the candidate:

household-registry-2/registry.yaml
- {id: phone-number, type: string, maximumLength: 255, classification: internal}
- {id: registration-note, type: string, maximumLength: 120, classification: internal}
constraints:

Restore only the operator grants, preserving the decision not to disclose the field to the reader:

household-registry-2/registry.yaml
readableFields: [person-code, given-name, family-name, date-of-birth, sex, preferred-language, phone-number]
writableFields: [person-code, given-name, family-name, date-of-birth, sex, preferred-language, phone-number]
readableFields: [person-code, given-name, family-name, date-of-birth, sex, preferred-language, phone-number, registration-note]
writableFields: [person-code, given-name, family-name, date-of-birth, sex, preferred-language, phone-number, registration-note]
filterableFields: [person-code, given-name, family-name, sex, preferred-language, phone-number]

Do not use an automatic merge tool for this step. Generated source and governed local source have different owners, so review each difference and copy only the intended model and local decisions into the candidate.

Recheck the reconciled project:

Terminal window
rstack breg check household-registry-2

The check passes with one warning, for the operator reading the membership’s link to a person. Inspect the compiled model and access policy as well as the YAML:

Terminal window
rstack breg explain model household-registry-2
rstack breg explain access household-registry-2
registration-note: internal, optional
readable fields ["date-of-birth","family-name","given-name","person-code","phone-number","preferred-language","registration-note","sex"]
writable fields ["date-of-birth","family-name","given-name","person-code","phone-number","preferred-language","registration-note","sex"]
readable fields ["date-of-birth","family-name","given-name","person-code","phone-number","preferred-language","sex"]
writable fields none

Confirm that registration-note remains internal, the operator can read and write it, and the reader cannot read it. If any of those checks disagree with the policy you intended, edit the candidate and repeat check and both explanations before comparing packages.

Repeat the package comparison. rstack breg diff compares the compiled candidate with a package, not with another source directory. Use the absolute local runtime path so the report verifies the runtime configuration and its exact package digest binding:

Terminal window
rstack breg diff household-registry-2 \
--runtime-config "$(pwd)/household-registry/.breg/dev/runtime.yaml"
baseline assurance runtime-bound
finding diff.history.removed-values-retained changes.person.marital-status

Read all changes and findings again. The report still says baseline assurance runtime-bound, but the removal of registration-note and its operator grants is gone. This candidate removes marital-status and its grants while retaining registration-note and its operator-only policy.

If you have the active package directory but no runtime file, use --package <absolute-directory> instead. That path verifies the package’s checksums and re-derives its contents, but the report says integrity-only because no runtime configuration binds that directory and digest.

The three comparisons answer different questions:

  • The source comparison exposes generated and local edits that need manual reconciliation.
  • The package diff classifies the effective candidate against the deployed or otherwise verified package.
  • Migration approval is a later decision. diff neither approves a destructive or access change nor authorizes activation.

When the package diff matches your intent, continue with Change an active registry. That procedure explains which classifications need a reviewed migration, how to test and package the successor with the active package as its baseline, and how the migration credential holder reviews and activates it. If the diff contains an unexpected removal, disclosure change, or unsupported change, keep the running project unchanged, correct the candidate, then run check, the explanations, and diff again.

--remove deletes the registry’s database with the person you created. Leave it off to keep the records for a later dev start:

Terminal window
rstack breg dev stop household-registry --remove
bregctl dev succeeded.
status stopped

The next tutorial continues from the contact directory of Create your first registry, not from this household registry, so create that one first if you have not; it takes about fifteen minutes. You can declare the same kind of Actions on this project later, as Declare Actions and Proposals describes.