Skip to content
Registry StackDocsv0.34.0

Extend a registry with a module

For the data publisher

View as Markdown

If you finished Create and query your first registry, you are a data publisher with the installed binaries and a tutorial-work directory holding the project you generated. In this tutorial you change a field that a module in that project 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 registry 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 registry runs or not.

Outcome
An edited module re-pinned by content digest, a passing project check, and generated JSON Schema and OpenAPI that carry the changed field.
Time
About 20 minutes
Level
Local authoring with synthetic data
Prerequisites
The binaries and tutorial-work directory from Create and query your first registryAn editorPython 3

Open a terminal in the directory that holds tutorial-work, and confirm the binaries are still on your PATH:

Terminal window
bregctl --version

The version printed is the one the first tutorial installed. Nothing in this tutorial touches the registry’s keys or data under tutorial-work/project/.breg/, and nothing here needs Docker.

Check the project you generated:

Terminal window
bregctl check tutorial-work/project
Authoring check passed.
revision sha256:<digest>
finding access.profile.unrestricted_collection 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 access
finding access.profile.unrestricted_rows entities[id=record].accessProfiles[id=evidence-source].rowBoundaries
this profile has no claim-bound row restriction for its granted operations;
requestVisibility owner limits request reads only, and other lifecycle rules
still apply. Review this registry-wide access
0 errors, 2 findings.

This is the project the first tutorial started as a registry, so the entity, fields, and profiles are the ones you used over HTTP, in the files init wrote. 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. The first says the operator profile can list every row, which is intended for a registry-wide operations team, and the second says the evidence-source profile can look up any record by its code. Every command in this tutorial that succeeds repeats both findings, so treat them as expected and read past them to the result. A check that is refused prints its errors instead, and you will meet one when you change 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 and find its version and the one field it declares on the entity the project owns:

version: 0.1.0
- {id: internal-note, type: string, maxLength: 500, classification: internal}

A 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.

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.

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:

Terminal window
bregctl check tutorial-work/project

The command prints Authoring check passed., a new revision, and the findings. filterableFields is unchanged on purpose: a note is something an operator reads and writes, not something a list is filtered by.

Now change the module itself. In module.yaml, raise maxLength to 1000 and version to 0.2.0, then run the check again:

Terminal window
bregctl check tutorial-work/project
bregctl check refused.
error module.lock.digest_mismatch project.modules[].digest
an authored module does not match its locked digest
error module.lock.version_mismatch project.modules[].version
an authored module does not match its locked version
2 errors, 0 findings.

A 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.

Rewrite the lock entry from the module’s current content, then check again:

Terminal window
bregctl project lock tutorial-work/project
bregctl check tutorial-work/project

project lock prints Locked the project modules. 1 artifact written., the rewritten registry.yaml, the findings, 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 Authoring check passed. with the findings 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.

explain reports what the compiled project permits, without a database:

Terminal window
bregctl explain queries tutorial-work/project

After Explained the compiled inventory., 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 registry from the first tutorial is still running, it serves the package it started with and knows nothing of internalNote.

Generate JSON Schema and OpenAPI from the edited project:

Terminal window
bregctl generate schemas tutorial-work/project --output tutorial-work/schemas
bregctl generate openapi tutorial-work/project --output tutorial-work/api

Each command names what it wrote: two schema files, then one OpenAPI document. Read the record schema:

Terminal window
python3 -m json.tool tutorial-work/schemas/generated/schemas/record.schema.json

Find 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.

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 registry from the first tutorial, if it is still running, serves the package it started with. Testing a project against a database and packaging it come next.

SymptomNext move
generate reports output.destination.invalidThe 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_mismatchYou edited a module after its last lock. Run bregctl project lock tutorial-work/project, then check again.
check cannot parse registry.yaml after your editCompare the indentation of the lines you changed with their neighbours; the grant lists sit eight spaces in.
bregctl is not foundPut the installers’ directory, ~/.local/bin unless you changed it, back on PATH in this terminal.
The running registry does not show internalNoteExpected: it serves the package bregctl dev built when it first started. Activating a changed package is covered in Deploy a registry.
bregctl dev refuses to start the edited projectExpected while the registry holds records: dev keeps the package it started with. Run bregctl dev stop tutorial-work/project --remove to discard the records, then bregctl dev tutorial-work/project builds the edited project into a fresh registry. To keep the records, copy tutorial-work/project without its .breg/ directory to a new path and start that copy instead.