Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,19 @@ every write.
crates/registry-breg/src/idempotency.rs; crates/registry-breg/src/lifecycle.rs;
crates/registry-breg/src/history_store.rs. */}

## Can I leave?

Yes, by three routes. Your project is YAML, plus any Rhai, SQL, or Wasm files it includes, in your
own version control, and it runs on the products without anything else. You can take your current
records out, with the fields you name, using
[`bregctl data export`](../../operate/breg-data/#export-with-a-checkpoint) through an access
profile that has `allowDataExport`. And the records live in a PostgreSQL database you run. Nothing
in this model needs a separate hosted service to keep working.

{/* Evidence: crates/registry-breg/src/wasm_handler.rs; crates/registry-breg/src/derived_sql.rs;
crates/registry-breg/src/tooling.rs, allowDataExport;
docs/site/src/content/docs/operate/breg-data.mdx. */}

## Next

- [Author a registry project](../../configure/breg/)
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,30 @@ one problem response with the same title and body shape, so a caller cannot use
whether a record exists. The outcomes stay distinct in the audit record; collapsing is a property
of what the caller sees, not of what the operator can review.

## Ask for the weakest answer that does the job

When you decide what crosses to or from another institution, start at the weakest answer and climb
only when it cannot do the job. In plain words, from weakest to strongest:

1. Nothing: refuse when the purpose, authority, or scope is missing.
2. A description of what exists, with no person-level data.
3. Counts, rates, or other totals.
4. A match, mismatch, or "cannot tell" on facts the caller already holds.
5. A computed fact, such as "is registered" or "is eligible", without the record behind it.
6. A fact the person states, labeled as self-stated.
7. A rendered document filtered to what is allowed.
8. A signed credential or receipt for a named purpose and validity period.
9. Selected fields of the record, only when the law allows a record-level look and the access is
scoped, filtered, and audited.

Registry Manifest publishes descriptions, Base Registry Engine (BReg) statistical datasets serve
totals, Registry Evidence serves matches, computed facts and signed credentials, Registry Render
produces documents, and a BReg access profile serves the last rung. Record the purpose or legal basis for each access grant and
for each partner relationship. Where the source is silent, write "not stated"; that is allowed, and
it is a finding to raise with whoever owns the question, not a reason to stop.

{/* Evidence: products/breg/STATISTICS.md; products/breg/native-patterns.md. */}

## Data stays at source: distributed custody

Underneath all of this is an architectural choice that matters for minimization at the system
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,29 @@ bregctl check facility

The excerpts below come from the template's `registry.yaml`.

## Write down the legal footing

A register has one legal owner. When two agencies both claim a register, that is joint control;
record which law settles it, or "not stated". For each register, write a short block, and write
"not stated" wherever your sources are silent; an honest gap is more useful than a guess:

- the law that creates it;
- the body that law mandates;
- the data controller;
- any operator distinct from the rule-maker;
- the legal effect of an entry: constitutive (the entry creates the status), declaratory (it
records a status that exists anyway), or informational.

Fill in a gap when you find the answer. Keep the block beside the register's description, so a reviewer sees who owns the data before
reading the fields.

## Combine starters by authority

When you combine starters, keep together what one authority keeps, and link the rest by reference.
A farmer and the land parcels they work belong in one project only if one authority keeps both. If
a land authority keeps the parcels, the farmer register holds the parcel's identifier with its scheme, not a
copy of it.

## Separate an object from its registration

Keep a thing separate from its registration when their lifecycles differ. A facility can change
Expand Down Expand Up @@ -139,6 +162,10 @@ one operator, several, or one primary beside other roles. A Record type's `tempo
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.

When your source states the last day in force, name it with `temporal.lastDayField` instead of
`endField`, so a period ending on 31 December includes that day. The
[API reference](../../reference/breg-api/) describes how the temporal routes read it.

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.
Expand All @@ -154,6 +181,63 @@ directly. An immediate Action commits several effects atomically, such as a faci
first assignment, and a Proposal Action keeps a durable draft for review and later application.
[Actions and Proposals](../../configure/breg-actions/) explains how to choose.

## Keep together what must be right together

Keep together what must be right together, change it through one Action, and link the rest by
reference. For example, a household and its memberships must agree about who belongs, so one Action
changes them together. The person stays a separate Record type that memberships point to, and an
enrolment in a programme is its own record, because a different body decides it and it changes on
a different schedule.

Name each Action after what happens in the programme, in the law's words: `register-birth` or
`decide-eligibility`, not `update-record`.

A short worked example, from a decision process to a recorded decision. Suppose the law says an
officer decides whether a household is enrolled. The process has three steps: the household applies,
an officer reviews, and the decision is recorded. Model the officer's decision as an
[Action](../../reference/glossary/#action), `decide-enrolment`, whose inputs carry the outcome and
the officer's reference and whose plan writes a decision record either way. If the law requires a
second look, make it a proposal-mode Action: a reviewer approves or rejects that exact submission
(a [Proposal](../../reference/glossary/#proposal)), and only applying it writes the record. The
recorded decision is then an ordinary record that other offices can read under their own access
profiles.

{/* Evidence: crates/registry-breg/src/contract.rs, ActionSource, ActionValueSource, ProposalApplicationSource;
docs/site/src/content/docs/configure/breg-actions.mdx. */}

## Model the law's lifecycle

Model the lifecycle the law describes, even when the engine cannot express all of it, and report
the gap. Never bend a legal lifecycle to fit the engine.

For example, a law may allow revoking a licence that is active or suspended. A commit guard
compares one field of one record with one literal or one input, so it cannot say "active or
suspended". The tempting workaround is to force revocation to pass through suspension first, which
adds a step the law does not have. Instead, keep revocation one step of the lifecycle and build it
as two Actions with the same effect, one guarded on `active` and one on `suspended`, and note in
the project that the pair exists only because of the engine gap. A visible, labelled gap can be
fixed later; a bent lifecycle hides in the data.

## Choose a licensing shape

Licensing has two shapes. Choose by the legal question: does your law issue a new licence at
renewal, or extend the same one?

- **Extends the same licence.** Keep one stable licence with dated terms, and record regulatory
decisions (suspension, renewal, revocation) as separate records.
- **Issues a new licence.** Create a successor licence for each renewal and link it to the previous
one.

If your sources do not say, write "not stated" for the legal question and ask the licensing
authority.

## Derive roles from the job

Roles come from the programme's job descriptions and the law's purposes, not from the product's
permissions. Start with who does what in the programme, then grant each role the reads and Actions
its job needs. Record why each grant exists, in a sentence ("the officer decides enrolment under
the programme's rules"); write "not stated" if you cannot yet say.

## Declarations, provenance, and documents

Keep a reported claim separate from the object it describes when the claim has its own reporter,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ products hold what happens around a record.
products/casework/README.md; products/messaging/README.md; products/scheduling/README.md;
crates/registry-breg/src/contract.rs (ACTION_PLAN_ABI_V1). */}

## Keep numbers that change in records, not in scripts

Keep a rate or fee table in a dated record, as the parameters row shows, so each change is a
reviewed record with history, not a code edit.

## Choosing a plan for an Action

Start with a `fixed` plan, which writes declared values and needs no code. Move to a Rhai Function
Expand Down
2 changes: 2 additions & 0 deletions docs/site/src/content/docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ Every tutorial names the role it is written for. One institution often holds sev
- The consumer or verifier is the service that calls a product API and verifies what it receives.
- The operator is whoever installs, configures, and runs a deployment.

Two roles outside the technical team shape a registry: the programme officer, who runs the programme and signs off the terms, labels, and refusal wording that officers and the public read, and the legal drafter, who confirms that rules, tables, formulas, and deadlines match the law and notes the legal basis beside each.

## Beyond the first run

- [How the products fit together](start/concepts/)
Expand Down
6 changes: 6 additions & 0 deletions docs/site/src/content/docs/reference/breg-configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,12 @@ Action inputs keep their existing `label` fallback to `apiName`; the separate op
`check` warns when two access profiles in the same namespace have equivalent labels,
naming both IDs. Use `check --deny-warnings` when that warning must block a review.

Take labels and refusal wording from the law and the forms your programme already uses, not from
product terms. Keep a short table of the programme's terms; author the labels from a table a programme officer
agrees is a good source. BReg labels are a single string each; for documents in more
than one language, put the translations in the per-locale label tables of the
[Render bundle](../render-configuration/).

{/* Evidence: crates/registry-bregctl/src/check.rs, Authoring and authored_paths();
crates/registry-bregctl/src/lib.rs, explain();
crates/registry-breg/src/artifacts.rs, inspect_registry() and action_input_metadata();
Expand Down
67 changes: 67 additions & 0 deletions docs/site/src/content/docs/start/adopt.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,73 @@ retries, tracing, and signed delivery. You may publish a static
others understand the module. Inclusion is optional metadata; it neither certifies availability
nor enables the module. Real data services use datasets; workers need no fictional dataset.

## Choose what goes in the registry

Put in the facts your law makes you the source of and the decisions you are accountable for.
Build the smallest version of supporting parts. Leave payroll, document management, and generic
workflow where they are, and reach them through their own systems.

Spend modeling time and legal review on rules, tables, formulas, and deadlines, because that is
where policy lives. Keep a rule that an approver signs in a table or formula rather than in code,
and note the legal basis beside each one in your own project notes. If a rule has no stated basis,
write "not stated" and raise it for review.

## Working with other institutions

Start from the partner and the situation, then pick the product. "Do not integrate" is always a
valid answer.

| Situation | What you do | Product |
| --- | --- | --- |
| Others need a fact about your records | Publish a narrow answer, not the record | [Evidence](../../explanation/how-evidence-works/) |
| A partner needs to read or change your records | Publish a governed record API | [BReg](../../explanation/configuration-defined-registry/) |
| You need a fact held by someone else | Ask for the narrowest answer that does the job | [Evidence, as the consumer](../../tutorials/verify-an-assertion-as-a-consumer/) |
| You must load another system's data | Copy it in through a reviewed translator | [Adapter](https://github.com/registrystack/registry-stack/blob/main/products/adapter/README.md) packs; its ingestion job is not yet released |
| A standard or national system fixes the wire contract | Serve it at the edge | Adapter facade profile |
| The person can carry the proof | Let them bring it as a credential | Evidence with [OID4VCI delivery](../../configure/evidence-oid4vci/) |
| Integration is not justified | Do not integrate, and write down why | None |

Keep a one-page relationship sheet per partner. Write "not stated" for any line the source is
silent on, and treat that as a finding to raise rather than a blocker.

- The partner.
- The legal basis.
- Who defines the question.
- What crosses.
- The product that serves it.
- Who owns changes.

Promise Read first. Promise Write only when the source can refuse a stale update (see `If-Match`
in the [BReg API](../../reference/breg-api/)), retry a create safely (`Idempotency-Key`), and
return a version.

Point to records another office keeps; do not copy them. Store their identifier with its scheme,
and ask through Evidence when a decision needs the fact. In BReg an event record's subject or
source can hold an identifier another system owns, declared with `external: {namespace}`; see the
[event records guide](https://github.com/registrystack/registry-stack/blob/main/products/breg/event-records.md).

Payment and treasury hand-offs are custom code today. Build them as an application or a module
(the third and fourth paths above); no Registry Stack product sends payments.

For how much to share, see [the disclosure ladder](../../explanation/data-minimization-and-purpose-limitation/#ask-for-the-weakest-answer-that-does-the-job).

{/* Evidence: products/adapter/README.md, adapter-job; products/adapter/EFFECTIVE-CONTRACT.md, facade;
products/breg/event-records.md, namespace;
docs/site/src/content/docs/reference/breg-api.mdx, If-Match and Idempotency-Key;
docs/site/src/content/docs/configure/evidence-oid4vci.mdx, OID4VCI. */}

## Signs part of your work is a process

Any one of these means part of your work is a process, not only a register. Check
[where each rule lives](../../explanation/where-each-rule-lives/) first; a timer, workflow or
review policy may already hold it.

- A rule needs facts from more than one record.
- A matter continues over weeks.
- Money is owed or paid.
- The law sets a clock.
- Two parties must both agree.

## Choose the next step

Combine the smallest set of paths that meets the job. A configured register can have one custom
Expand Down
4 changes: 4 additions & 0 deletions docs/site/src/content/docs/start/evidence-quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ such as `urn:example:requirement:adult-status:v1`, and the subject it is about.
only the values the question declares. Asked whether a person is an adult, Registry Evidence reads
a birth date from your system and returns `true`; the date stays with you.

A Requirement URI is minted by the body whose mandate defines the question, often the consuming
programme or a sector authority. There is no central registry of Requirement URIs; where a standard
such as SP-DCI already defines a code for the same question, reuse it.

## What you define and run

You write an [Evidence project](../../reference/glossary/#evidence-project), kept in version
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,14 @@ PublicSchema is a published vocabulary for social protection and public-service
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
PublicSchema is a starting vocabulary inside your registry and a contract at its edge. Starting
from the model gives you field names, types, and code lists that other institutions already use.
Your registry carries PublicSchema concept URIs (the `conceptUri` of each Record type under
`manifestProjection`, and `concepts` on each field the catalogue describes), so a mapping to a
partner starts from shared definitions. Your register may diverge where your mandate needs it. Naming is where the
programme's language gets settled, with the people who run it; the model gives you a draft to
settle it from, not a replacement for that conversation. Deriving 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.
Expand Down Expand Up @@ -147,6 +152,12 @@ the model. Keep this notice with the project and with any package built from it.

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

The generated catalogue names a placeholder body. Under `manifestProjection` in `registry.yaml`,
`catalog.publisher`, the `authorities` entry, and each dataset `owner` are `"{title} Authority"`
with an IRI under `https://<id>.example.invalid`, and `publicService.competentAuthority` points at
that authority's `id`. Replace them with the body your law names for the register. Where your sources do not say, write "not stated" in the
dataset description rather than inventing a body; you can fill it in later.

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

Expand Down
10 changes: 7 additions & 3 deletions products/platform/CONFIG-CONVENTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,13 @@ In scope:
checkpoints, development state); it follows every rule except CFG-SCHEMA-2
and CFG-SCHEMA-6, which cover the files people edit. An **output format**
is written for people or for tools we do not own and is never read back
(explain output, `--format json` reports); it is registered with
`reader: none`, follows CFG-ENV, CFG-NAME, CFG-ID, CFG-QTY, and CFG-EMPTY,
and is exempt from CFG-YAML-1, CFG-CHECK-1, CFG-SCHEMA-2, and CFG-SCHEMA-6.
by a tool we maintain (explain output, `--format json` reports); it is
registered with `reader: none`, follows CFG-ENV, CFG-NAME, CFG-ID, CFG-QTY,
and CFG-EMPTY, and is exempt from CFG-YAML-1, CFG-CHECK-1, CFG-SCHEMA-2, and
CFG-SCHEMA-6. A product output that a maintained tool reads is a registered
contract: it is a read-back format, registered with its schema and a `reader` entry
naming that tool's reading function, whatever command prints it. Explain output a
maintained tool reads belongs here, not under `reader: none`.
A **build artifact** is a read-back format that its own product's build
writes and reads again and that no one edits (the compiled package model in
BReg's `package.json`). It is registered with `audience: generated` and
Expand Down
Loading