diff --git a/docs/site/src/content/docs/explanation/configuration-defined-registry.mdx b/docs/site/src/content/docs/explanation/configuration-defined-registry.mdx index 50129d8fc8..17f35b2427 100644 --- a/docs/site/src/content/docs/explanation/configuration-defined-registry.mdx +++ b/docs/site/src/content/docs/explanation/configuration-defined-registry.mdx @@ -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/) diff --git a/docs/site/src/content/docs/explanation/data-minimization-and-purpose-limitation.mdx b/docs/site/src/content/docs/explanation/data-minimization-and-purpose-limitation.mdx index 56d19debfa..f0d17d27a1 100644 --- a/docs/site/src/content/docs/explanation/data-minimization-and-purpose-limitation.mdx +++ b/docs/site/src/content/docs/explanation/data-minimization-and-purpose-limitation.mdx @@ -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 diff --git a/docs/site/src/content/docs/explanation/registry-modeling-patterns.mdx b/docs/site/src/content/docs/explanation/registry-modeling-patterns.mdx index 0f7a59fbf0..ba2e465b6b 100644 --- a/docs/site/src/content/docs/explanation/registry-modeling-patterns.mdx +++ b/docs/site/src/content/docs/explanation/registry-modeling-patterns.mdx @@ -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 @@ -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. @@ -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, diff --git a/docs/site/src/content/docs/explanation/where-each-rule-lives.mdx b/docs/site/src/content/docs/explanation/where-each-rule-lives.mdx index b3eb6433aa..2e71afd9bc 100644 --- a/docs/site/src/content/docs/explanation/where-each-rule-lives.mdx +++ b/docs/site/src/content/docs/explanation/where-each-rule-lives.mdx @@ -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 diff --git a/docs/site/src/content/docs/index.mdx b/docs/site/src/content/docs/index.mdx index 2a7a47252b..35815e55d3 100644 --- a/docs/site/src/content/docs/index.mdx +++ b/docs/site/src/content/docs/index.mdx @@ -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/) diff --git a/docs/site/src/content/docs/reference/breg-configuration.mdx b/docs/site/src/content/docs/reference/breg-configuration.mdx index 931f6eaaee..6561417b70 100644 --- a/docs/site/src/content/docs/reference/breg-configuration.mdx +++ b/docs/site/src/content/docs/reference/breg-configuration.mdx @@ -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(); diff --git a/docs/site/src/content/docs/start/adopt.mdx b/docs/site/src/content/docs/start/adopt.mdx index eec948e5b6..f05569e9b0 100644 --- a/docs/site/src/content/docs/start/adopt.mdx +++ b/docs/site/src/content/docs/start/adopt.mdx @@ -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 diff --git a/docs/site/src/content/docs/start/evidence-quickstart.mdx b/docs/site/src/content/docs/start/evidence-quickstart.mdx index 3ef4722170..b257e714f2 100644 --- a/docs/site/src/content/docs/start/evidence-quickstart.mdx +++ b/docs/site/src/content/docs/start/evidence-quickstart.mdx @@ -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 diff --git a/docs/site/src/content/docs/tutorials/derive-a-registry-from-publicschema.mdx b/docs/site/src/content/docs/tutorials/derive-a-registry-from-publicschema.mdx index 55095a0c31..86669f2017 100644 --- a/docs/site/src/content/docs/tutorials/derive-a-registry-from-publicschema.mdx +++ b/docs/site/src/content/docs/tutorials/derive-a-registry-from-publicschema.mdx @@ -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. @@ -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://.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. diff --git a/products/platform/CONFIG-CONVENTIONS.md b/products/platform/CONFIG-CONVENTIONS.md index dfa6044fa1..acd9718381 100644 --- a/products/platform/CONFIG-CONVENTIONS.md +++ b/products/platform/CONFIG-CONVENTIONS.md @@ -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