Unreleased documentation. These pages follow the main branch and can change before the next release. For supported guidance, use v0.26.1.
If you finished Create and query your first registry, you are a data publisher with the
checkout, the installed binaries, and a tutorial-work directory.
In this tutorial you create a project of your own with bregctl init, change a field that a module
contributes, re-pin the module so the project accepts the change, and generate the JSON Schema and OpenAPI
that applications read.
Every command reads and writes files in tutorial-work, so the launcher from the first tutorial may be stopped.
The one place a running registry matters is a comparison at the end of the explain step,
and that comparison holds whether the launcher runs or not.
Before you start
Section titled “Before you start”Open a terminal at the root of the breg-tutorial checkout, where tutorial-work already exists,
and confirm the binaries are still on your PATH:
bregctl --versionThe version printed is the one the first tutorial installed.
Nothing in this tutorial touches the launcher’s disposable directory under products/breg/quickstart/.run/,
and nothing here needs Docker.
Create a project
Section titled “Create a project”Create an editable project inside the directory you kept:
bregctl init tutorial-work/projectinit succeededrevision: sha256:<digest>finding access.profile.unrestricted_collection at entities[id=record].accessProfiles[id=operator].rowBoundaries: this profile can list all rows, subject only to query bounds; caller filters are not authorization. Add a claim-bound row restriction or review this registry-wide accessartifacts: 5next: read tutorial-work/project/README.md, then run 'bregctl check tutorial-work/project'next: leave the finding above as it is; the example operator profile lists a whole collection on purpose, and tutorial-work/project/README.md says where to narrow itnext: replace canonicalBaseIri in tutorial-work/project/registry.yaml before you build a production package; the example value is a reserved .invalid name that never resolvesThe five artifacts are registry.yaml, one module under modules/, tests/journeys.yaml,
a runtime.example.yaml, and a README.md that lists them.
This is the same project the quickstart launcher started from, so the entity, fields, and profiles are the
ones you used over HTTP.
The revision line is the digest of the compiled project; it changes with every edit you make.
A finding is advice the compiler attaches to a result that succeeded: an error stops a command, a finding does not.
This one says the operator profile can list every row, which is intended for a registry-wide operations team;
the project’s second profile, record-reader, shows the claim-bound row restriction that closes it.
Every command in this tutorial repeats the finding, so treat it as expected and read past it to the result.
The next: lines close the same gap for the rest of the output: they name the command to run after
the README, the finding the example keeps on purpose, and the placeholder canonicalBaseIri that has
to be replaced before a production package. Only init prints them.
Read the module
Section titled “Read the module”A module is a separate file that contributes to the model, so a reusable part of a registry can be reviewed
and versioned apart from the project that adopts it.
Open tutorial-work/project/modules/record-notes/module.yaml.
Written out as block mappings (the file keeps each field on one line), it declares one optional field on the
entity the project owns:
id: record-notesversion: 0.1.0extendEntities: - entity: record fields: - id: internal-note type: string maxLength: 500 classification: internalA field without required: true is optional, so existing records stay valid and a create may omit it.
Its classification matches the entity’s default, internal.
A more sensitive value such as restricted adds a second finding, access.profile.higher_classification,
once a grant exposes the field; it asks you to confirm the profile’s scope and purpose first.
Now open tutorial-work/project/registry.yaml and find the modules entry at the end of the file:
modules: - id: "record-notes" version: "0.1.0" digest: "sha256:<digest>"The digest is a content digest of the module file. The project pins the exact module content it was reviewed with, and the compiler refuses a module whose content or version no longer matches.
Grant access to the field
Section titled “Grant access to the field”Adding a field to the model grants nobody access to it, and no profile lists internal-note yet.
In tutorial-work/project/registry.yaml, find the operator grant for record and add the field to its
readable and writable lists, keeping the indentation:
readableFields: [code, label, group, status, internal-note]writableFields: [code, label, group, status, internal-note]filterableFields: [code, status]Check the project:
bregctl check tutorial-work/projectThe command prints check succeeded, a new revision, and the finding.
filterableFields is unchanged on purpose: a note is something an operator reads and writes,
not something a list is filtered by.
Change the module
Section titled “Change the module”Now change the module itself.
In module.yaml, raise maxLength to 1000 and version to 0.2.0, then run the check again:
bregctl check tutorial-work/projecterror module.lock.digest_mismatch at project.modules[].digest: an authored module does not match its locked digesterror module.lock.version_mismatch at project.modules[].version: an authored module does not match its locked versionA stale digest is an error, not a finding.
The project still pins version 0.1.0 and the old content, and the compiler refuses to build a model from a
module that differs from what was reviewed.
Re-pin the module
Section titled “Re-pin the module”Rewrite the lock entry from the module’s current content, then check again:
bregctl project lock tutorial-work/projectbregctl check tutorial-work/projectproject lock prints project lock succeeded and a report of what it rewrote:
{ "changed": true, "modules": [ { "digest": "sha256:<digest>", "id": "record-notes", "status": "updated", "version": "0.2.0" } ]}check then prints check succeeded with the finding and nothing else.
Open the modules entry in registry.yaml again: both the version and the digest changed.
Rerun project lock after every module edit, and review a changed digest together with the module source it now pins.
Inspect the query permissions
Section titled “Inspect the query permissions”explain reports what the compiled project permits, without a database:
bregctl explain queries tutorial-work/projectAfter explain succeeded, the report lists one operation per profile and entity.
In records.record.operator.list, the operator’s list operation over record, find internalNote among the apiFields:
{ "apiName": "internalNote", "field": "internal-note", "fieldType": { "maxLength": 1000, "minLength": 0, "type": "string" }, "sourceKind": "stored"}The same operation’s filterable entries name only code and status, each with examples such as $filter=code eq 'example'.
That is the refusal you met in the first tutorial, explained from the project instead of by a 400.
This report describes your edited project; if the quickstart launcher is still running, its registry
serves the original package and knows nothing of internalNote.
Generate the API schema
Section titled “Generate the API schema”Generate JSON Schema and OpenAPI from the edited project:
bregctl generate schemas tutorial-work/project --output tutorial-work/schemasbregctl generate openapi tutorial-work/project --output tutorial-work/apiEach command prints generate succeeded and an artifacts count: two schema files, then one OpenAPI document.
Read the record schema:
python3 -m json.tool tutorial-work/schemas/generated/schemas/record.schema.jsonFind internalNote under properties.
An optional field accepts null, so the generator wraps its type in anyOf:
{ "internalNote": { "anyOf": [ { "maxLength": 1000, "minLength": 0, "type": "string" }, { "type": "null" } ] }}The authored internal-note becomes internalNote in JSON, and the length you raised in the module reached the schema.
The required list still contains only code and label.
Open tutorial-work/api/generated/openapi.json to see the same field in the create and PATCH request
shapes under /v1/records/records.
Generated files are outputs: edit YAML and regenerate into a fresh directory to change them,
because generate refuses an output directory that already exists.
What you built
Section titled “What you built”You have a project of your own whose module contributes a longer internal-note, pinned by content digest,
granted to the operator, and reflected in the JSON Schema and OpenAPI an application would read.
You compiled an extension, not installed one: no database changed, and the quickstart’s registry,
if it is still running, serves the package it started with.
Testing a project against a database and packaging it come next.
Troubleshooting
Section titled “Troubleshooting”| Symptom | Next move |
|---|---|
init or generate reports output.destination.invalid | The output directory must not exist yet. Choose a new path, or remove the one left by an earlier run. |
check reports module.lock.digest_mismatch or module.lock.version_mismatch | You edited a module after its last lock. Run bregctl project lock tutorial-work/project, then check again. |
check cannot parse registry.yaml after your edit | Compare the indentation of the lines you changed with their neighbours; the grant lists sit eight spaces in. |
bregctl is not found | Put the installers’ directory, ~/.local/bin unless you changed it, back on PATH in this terminal. |
The running quickstart does not show internalNote | Expected: it serves the package the launcher compiled at start. Activating a changed package is covered in Deploy a registry. |
- Review changes before updating a registry to test a configurable approval workflow on a copied project.
- Author a registry project for the full model: entities, relationships, time, and events.
- Build a production candidate to test, package, sign, and verify a project like this one.