bregctl dev starts an existing, explicitly authored local registry using the
installed breg binary, a source-pinned ThunderID image, and Docker PostgreSQL.
It needs no checkout, Python launcher, shell script or OpenSSL installation.
ThunderID is a local issuer chosen by this development tool; an operated BReg
runtime remains an independent OAuth resource server.
Prepare the project with bregctl init ./registry. The generated
dev-clients.yaml binds editor and foreign-editor to directory-editor
with distinct jurisdiction claims, so the project starts unchanged. Review the model,
access profiles, journey fixtures and local clients before the first start.
A later dedicated Evidence source can be prepared explicitly on a stopped session.
The generated local runtime uses the compiled registry's one common principal
claim. A project whose authenticated grants all use principalClaim: sub
therefore uses the issuer's verified subject, including native citizen accounts.
Projects with multiple principal claims are refused before local services start.
An access-free project keeps the existing registry_principal local default.
An operated runtime still explicitly sets authentication.authorityClaims.principal
to match its governed grants.
bregctl dev ./registry
bregctl dev stop ./registry
bregctl dev start ./registryThe project path defaults to the current directory, as it does for every other
bregctl command. A first start reads dev-clients.yaml inside the project;
--clients-file names another clients file instead. dev and dev start both
detach a resident supervisor. They return only after
PostgreSQL, ThunderID, schema-test rehearsal, package activation, BReg readiness and
explicit seed creation succeed. The preferred loopback ports are BReg 8090,
issuer 8091, and PostgreSQL 15432. When an unassigned preferred port is
occupied, the first start selects a free, distinct port. Override selection with
--breg-port, --issuer-port, and --database-port; an explicit collision or
duplicate assignment is refused with the responsible flag named. A restart
retains the selected ports and clients-file location. Session and container
ownership names remain UUID-based and do not depend on the selected ports.
The database runs the pinned image
docker.io/library/postgres:17.11@sha256:67f41722b7a8cbdb868a44a4995c846eddfdc2973bccb291ce937dce88ad5675,
or postgis/postgis@sha256:01a6a70e41e6c4467c8f55f6063555ed72db2d6662cd0d571040d42eadaeb6f6
when the compiled schema requires PostGIS. The first is the Docker Official
Images reference for
postgres:17.11@sha256:67f41722b7a8cbdb868a44a4995c846eddfdc2973bccb291ce937dce88ad5675,
at that digest; the PostGIS image also comes from Docker Hub. The
dev start help names both references, so an egress allowlist can admit
Docker Hub. Retained owned containers also accept the previous exact
public.ecr.aws/docker/library/postgres reference at that digest and the
short postgres reference. The selection is retained with the
owned database. Spatial setup grants the migration role permission to SET the
no-login bbox owner; the runtime role never receives that membership.
Each supervised
prerequisite command may run for 120 seconds. The owned database and BReg have
45 seconds each to answer readiness, and ThunderID discovery has a 120-second wait. A start
that passes a deadline fails, stops what it acquired and keeps its owner-only
diagnostics.
Before it inspects the owned container or launches the supervisor, a start runs
each resolved breg and docker with --version and records what they
answer. breg ships in the same release as bregctl, so a breg reporting
another version is refused by name: the refusal gives the file that answered,
the version it reported and the version bregctl reports.
Without that comparison the mismatch surfaces much later as a refused package or
an unready database, which reads as a fault in the authored project. Docker
belongs to no release of this stack and is never compared, and a prerequisite
that reports no version at all still serves the session. No flag skips the
comparison: install breg and bregctl from the same release, or put the
matching build first on PATH. --breg-bin chooses which file is resolved,
and that file is compared.
dev stop keeps everything it created: the owned container, its named data
volume, records, the audit files under .breg/dev/audit, keys, credentials and
the built package. Add --remove to reclaim the storage as well; it removes the
owned container and its breg-dev-<owner> data volume, discarding records,
event receipts and seed checkpoints. The audit files are kept. For an initial package at sequence 1, the next start builds an empty
database from the same authored project, ports, credentials and package. It also
lets the next start take edited
inputs: once no records are retained, a changed package, clients file or port
(what counts as changed)
replaces the session with a fresh one that keeps the previous ports and clients
file and generates new keys. A successor package prepared for retained records
cannot initialize an empty database after removal: start refuses before Docker.
Create a fresh project at package sequence 1 for a separate empty experiment.
Use --format json to consume the status, URLs, audience, package digest,
runtime configuration and private credential file references. Keys and access
tokens never appear in these reports. Use bregctl dev export-client to obtain
the client ID, assertion key, issuer and token endpoint handoff; an OAuth client
can obtain a fresh token with those fixed values. Keep token output owner-only.
For a client mapped through issuer.clientResources, the export's resource
and audience name that client's registered audience.
Declare an event with a webhook destination in the authored project before
the first start. By default, dev binds every compiled destination to its own
HMAC-verifying receiver on a free numeric loopback port. The receiver port,
signing key, and bindings are retained across restarts; a conflicting receiver
port is refused. To deliver to an external local receiver instead, bind every
compiled destination ID in dev-clients.yaml:
secretProviders:
file: {root: /absolute/owner-only/dev-secrets}
destinations:
hooks:
openfn:
origin: http://127.0.0.1:8088
path: /webhooks/registry
hmacSha256KeyRef: secret:file/openfn-webhook-keyThe key is a secret reference. The session copies it
into its private state and uses the same bounded outbox delivery and replay
rules.
The origin must be numeric loopback HTTP with an explicit port. An explicit
destination map starts no built-in receiver, so dev events has no inbox
receipts; inspect the receiving service and bregctl hook list instead.
For a governed action package that declares Evidence providers, bind every
required provider in the project's dev-clients.yaml before its first start:
evidenceProviders:
qualification:
baseUrl: http://127.0.0.1:8093
trustBindingId: exact-local-trust-v1
tokenRef: secret:file/evidence-token
trustedJwksRef: secret:file/evidence-jwks.json
revokedKeyIds: []The dev command accepts exact numeric loopback origins and
secret references, copies the token, trusted keys and
optional CA bundle (caBundleRef) into its
private state, and generates the corresponding evidenceProviders runtime
bindings. It does not relax package activation: provider IDs must match the
compiled action requirements, and the runtime still resolves and validates
every configured trust input before serving. Obtain the token after the shared
issuer is ready, then start the action-bearing BREG borrower with this binding.
For a Proposal Action whose execution.proposal.review.authority names Casework, add one logical
authority binding and one dedicated machine client before the first start:
clients:
- id: casework-producer
accessProfiles: []
scopes: [casework:reviews:request]
claims: {}
reviewAuthorities:
casework-a:
endpoint: http://127.0.0.1:8096/
profile: integration-requester
producerId: registry-producer
recoveryDays: 7
client: casework-producerThe authority ID must exactly match the compiled requirement. profile
selects the Casework requester access profile on every authority exchange.
producerId
must match the Casework producer connection, whose admitted source namespaces
include this Registry ID and whose admitted kinds include the authored review
policy ID. The client has no BReg profile. Its scope and token identity must
instead match Casework's requester profile and producer subject.
The dev issuer generates the client's assertion key. bregctl copies its
client ID and key into owner-only runtime secret files and emits a
privateKeyJwt authority credential with the dev issuer token endpoint,
assertion audience, resource, and declared scopes. BReg acquires and refreshes
short-lived Casework tokens for each outbound exchange. No token or private key
appears in dev-clients.yaml, runtime YAML, reports, or logs. An unmapped client
uses the session's generated default BReg audience, which supports an App Kit
composition where Casework and BReg deliberately share that resource audience.
If issuer.clientResources maps the client, the generated credential uses that
declared resource instead.
Proposal application defaults to automatic. The built-in executor uses the
compiled system grant for the exact frozen submission; it needs no local OAuth
client, self-HTTP endpoint, or reviewExecutors binding. To require a separate
caller, author execution.proposal.application.mode: manual and grant that
caller Proposal Apply. External recovery of automatic work requires a verified
human and an explicit operation grant.
A missing review-authority binding disables operations that require it. Generated CRUD, immediate Actions, and no-review Proposals remain available. Restore the exact binding to resume dependent work; an unavailable authority never implies approval.
Polling needs no callback credential. To test authenticated completion delivery, add both optional fields; declaring only one is refused:
completionTokenRef: secret:file/casework-completion-token
completionRecipient: registry-bregThe completion token is an independent sender credential, named by a
secret reference and copied into private state. It is never used for review requests or source application. Operated
runtime configuration also supports an explicit static tokenRef for an
opaque renewable credential supplied by the deployment, but bregctl dev
always generates the refreshing privateKeyJwt branch.
After a seed, example scenario, or API write triggers an event, inspect receipts:
bregctl dev events ./registry
bregctl --format json dev events ./registry
bregctl dev events ./registry --include-payloadThe default report shows the event UUID, authored event, entity, trigger,
compiled delivery and destination IDs, generation, attempt and received
status. Record IDs and projected values are omitted. --include-payload
explicitly displays the projected values from the development receiver.
Receipts remain available while the session is stopped. dev stop --remove
erases the receipt journal along with the records, including captured payloads.
The receiver keeps these development values in the owner-only
.breg/dev/events.jsonl journal, outside version control. Its 16 MiB bound
prevents unbounded storage; when full, it refuses further receipts and the
runtime follows its normal retry/dead-letter policy. While stopped, move the
journal to an owner-only location if it needs to be preserved, then start again
to create an empty inbox. Treat captured values as private development data.
A receipt means the receiver accepted that attempt. The runtime's pending and dead-letter queue is inspected separately. Supply the local PostgreSQL CA and the absolute runtime configuration path:
SSL_CERT_FILE="$PWD/registry/.breg/dev/tls/ca.pem" \
bregctl hook list --runtime-config "$PWD/registry/.breg/dev/runtime.yaml"Delivered rows leave that queue. Automatic retries keep the event UUID and
idempotency key; an eligible operator replay increments the generation. Use
the identifiers and generation from hook list with bregctl hook replay --help. Supply the same SSL_CERT_FILE and absolute runtime path for replay.
The normal event delivery, dead-letter and replay rules also apply to
this generated runtime. See Events and hooks.
The supervisor starts the receiver before BReg and stops it after BReg during normal shutdown or failed startup. It captures runtime deliveries, including seed writes; the disposable schema-test rehearsal does not dispatch webhooks. Projects declaring no webhook destinations start without a receiver and report an empty inbox. These bindings are local development configuration; operated deployments must bind their own destinations and signing keys.
The clients file is ordinary YAML with a closed format. It starts with
apiVersion: id.registrystack.org/formats/breg/dev-clients/v1alpha1 and
kind: BRegDevClients, and bregctl dev start reports every unknown or
removed member at its line and column before any service starts. It declares
local issuer registrations; it does not add or infer BReg access profiles.
Every protected journey step without an exact binding needs one default client
for its profile whose scopes and claims match the ordinary step. A maintained refusal step may
use another client for the same profile by naming that exact step with
testBindings. The closed binding contains both journeyId and stepId; stale,
profile-mismatched, duplicate, or ambiguous bindings are refused before any
service starts.
For a native journey using a profile with principalClaim: sub, use
"$localClientSubject" for the expected principal. Use the same marker in
directClaims.sub when the profile applies an equality row boundary to sub.
claims:
principal: "$localClientSubject"
scopes: [registry:records:read]
actorKind: human
requesterClient: local-reader
directClaims:
sub: "$localClientSubject"Each marker resolves to the selected local client's registered subject in the
private captured project. Exact testBindings select the client for their
named steps. A borrowed issuer uses its owner's registration identity. Use
literal values for other expected claims and request data. The rehearsal
verifies the token's claims against these expectations and applies the row
boundary to the records.
Set accessProfiles: [] for a machine client that carries only scopes or
claims for another product, such as Casework. The empty list gives that client
no BReg access-profile binding. It is registered with the local issuer but omitted
from the BReg runtime's allowedClients, so it cannot call BReg.
When no client is left to name in allowedClients (every client is profile-free
and none sets allowBregAccess), the session writes allowedClients: unrestricted, so the runtime accepts a token from every client of its local
issuer. The local issuer is the only issuer the session trusts, and governed
profiles and token scopes still authorize each call.
An integration client that must call BReg without becoming a journey or seed
binding must opt in with allowBregAccess: true. Use that flag only when its
scopes and authority claims are intentionally sufficient for the BReg profiles
it will select. For a Casework current-source review, configure the human
reviewer's purpose-specific get-review-context profile and the independent
service get-review-status profile explicitly in BReg. Pair the exact sealed
context export with caseworkctl provider pair; pairing adds no client or BReg
grant. Profile-free clients without allowBregAccess remain excluded.
apiVersion: id.registrystack.org/formats/breg/dev-clients/v1alpha1
kind: BRegDevClients
clients:
- id: operator
accessProfiles: [operator, reference-loader]
scopes: [registry:generic:operate, registry:generic:read]
claims:
registry_principal: generic-registry-operator
registry_purpose: [registry-operations, registry-reporting]
- id: reader
accessProfiles: [record-reader]
scopes: [registry:generic:read]
claims:
registry_principal: generic-registry-reader
registry_purpose: registry-reporting
registry_record_status: active
- id: reader-for-lifecycle-test
accessProfiles: [record-reader]
scopes: [registry:generic:read]
claims:
registry_principal: generic-registry-reader
registry_purpose: registry-reporting
registry_record_status: active
testBindings:
- journeyId: record-lifecycle
stepId: read-record-within-the-claim
- id: source
accessProfiles: [evidence-source]
scopes: [registry:evidence:lookup]
claims:
registry_principal: generic-registry-source
registry_purpose: evidence-source-read
seed:
- id: first-record
client: operator
recordType: record
accessProfile: operator
data:
code: synthetic-dev-record
label: Synthetic local record
status: active
- id: reference-record
client: operator
recordType: reference-record
accessProfile: reference-loader
operation: import
data:
code: synthetic-reference-record
label: Synthetic reference recordregistry_purpose may remain one string. A list declares the closed set of
purposes that one logical client may use in journey steps; the first remains
the purpose of its ordinary dev token. Each authenticated journey step names
an exact scope subset and, when the client declares several purposes, one
declared purpose. The rehearsal obtains a separate short-lived issuer token
for every distinct (scopes, purpose) claim set while keeping the same OAuth
client identity. An undeclared scope or purpose is refused before schema test.
Every multi-purpose client shares one generated first-party signer, so the
distinct claim names of all of them together, counting registry_actor_kind,
registry_purpose, and scope, may number at most 16, and a multi-purpose
client cannot also be listed on any authored exchange connection. That signer
makes the client first-party, so the issuer projects the purpose connection's
claims into its exchanged tokens: through an institutional-grant connection
they would lack the registry_grant_* claims the registry requires. Both are
refused when the clients file is read.
A seed defaults to operation: create. The illustrative reference-record
entity and reference-loader profile must be authored in the project, with an
Import route and the scope shown on the bound client. Dev writes one JSONL item, opens
an exact one-item authority bound to its digest, drives the normal ingestion
run, and closes the authority. An interrupted start retains the authority and
checkpoint, resumes that exact run, and records the completed seed in the same
state update that clears the authority. It can open a replacement when no
checkpoint exists: a sidecar may already name a zero-progress run, but the
missing checkpoint proves no chunk was submitted or committed. It can also
replace an incomplete run whose authority closed or expired before its sole
atomic chunk committed. Dev settles that terminal authority, retains its
checkpoint, and opens a fresh authority. Before asking to open an authority,
dev records in its state that the seed is about to open one, and when. An
interruption after the authority opened but before the session recorded it
leaves an open authority no local state names; the next start recognizes the
exact one-item, zero-progress authority this seed would have opened, opened no
earlier than that recorded intent, closes it, and opens a fresh one. Any other
open authority on the entity, including an identical one opened before the
intent or with no intent recorded, is refused by name and left to the operator.
A completed run is recorded first, and every other blocked or uncertain result
retains its linkage and fails closed.
A journey can likewise use a successful import step to load reference rows:
request:
operation: import
items:
- {code: GB, label: United Kingdom}
expect: {outcome: success, status: 200}Schema test plans the same JSONL chunks, opens a bounded authority, submits the production ingestion-run and chunk routes, verifies completion, and closes the authority before the next step. Import steps do not capture a record response; a later list or lookup step verifies the imported rows.
For an immediate Action in examples/scenarios.json, one result can use the
existing recordType, result, and capture members. To retain several
results from one invocation, omit those three members and map each disclosed
Action result alias to a distinct local capture alias:
"captureResults": {
"facility": "facility",
"assignment": "assignment"
}The completed receipt and all disclosed captures are replaced together in the private attempt state. A retry recovers the same idempotent Action receipt and reconstructs the same capture set before later steps run.
Collection journey expectations can name exact captured members and a subset of their exact returned domain values:
expect:
outcome: success
status: 200
members:
facility:
fields: {status: active, jurisdiction: zone-a}Each members key names a prior Record capture. The mapping is an unordered
exact set of record identities. Missing, extra, or duplicate returned
identities fail the step, as does a different declared field value. An exact
membership assertion also requires the response to be the terminal page, with
no next cursor. The ordinary readable-field ceiling is checked independently.
Omitting members keeps the earlier collection shape check; members: {}
explicitly requires an empty result. count may still be used and, when both
are present, must equal the number of declared members.
Each client has its own ES256 private key, generated by default. An existing
key can be imported with assertionKeyRef on that client, a
secret reference to its private JWK, for example when
Evidence already created its active access client. The dev
session validates the private key, copies it into retained state, and registers
only its public half. The service issuer,
operator and source workload use different keys. The reserved client ID issuer
is unavailable. Client and seed IDs contain lowercase ASCII letters, digits or
hyphens and have at most 64 bytes. There can be at most 32 clients and 100 seeds.
Plain init includes a dedicated source client for the existing lookup-only
evidence-source profile. That profile permits whole-registry exact lookups by
code, reads code and status, and grants no list or mutation operation. A supplied
code is not a row authorization rule. Custom models, including init --from, can start without an Evidence lookup or
client. Add one later through the explicit preparation below.
After creating Evidence, copy that existing source pair explicitly:
bregctl dev export-client ./registry --client source \
--client-id-file ./evidence/secrets/registry-client-id \
--assertion-key-file ./evidence/secrets/registry-client-keyBoth destination parents must already be owner-only directories. Relative paths are resolved from your current directory. Export reads the retained named client's pair, including while stopped; it does not create a client, rotate keys, rewrite registrations, or add persistent output bindings. Identical destination files are reusable. A retry can complete a pair after one complete file was published. Conflicting files, links, or unsafe permissions are refused without replacement; choose fresh output names and update the target if you intentionally replaced the session. The report contains file references and endpoints, never credentials.
Credentials remain under .breg/dev/credentials/<client-id>/, and
export-client is the only way a pair leaves the session. Export only the
dedicated source client when configuring Evidence. Evidence's caller credential
and BReg's operator credential retain separate authority.
A member that names a secret holds a secret reference, never a path:
assertionKeyRef, hmacSha256KeyRef, tokenRef, trustedJwksRef,
caBundleRef, privateKeyRef, completionTokenRef, clientSecretRef, and
passwordRef. secret:file/<name> names one file directly below
secretProviders.file.root, which must be absolute. secret:env/<NAME> names
an environment variable of the bregctl dev start process and is resolved only
when secretProviders.environment is declared:
secretProviders:
file: {root: /absolute/owner-only/dev-secrets}
environment: {}A referenced file must be an ordinary file you own, with mode 0400 or 0600 and a single link. A referenced value must be non-empty, contain no NUL byte, and fit the member's limit. The session copies each value into its private state when it is prepared, so editing a referenced file later changes nothing until the session is replaced. A refusal names the member, never the value, the file, or the variable.
A registry with Evidence guards needs the exact provider bindings declared by
its package. Put the binding in dev-clients.yaml before first start:
secretProviders:
file: {root: /absolute/private/dev-secrets}
evidenceProviders:
qualification:
baseUrl: http://127.0.0.1:8095
trustBindingId: reviewed-local-provider
trustedJwksRef: secret:file/evidence-signing-jwks.json
privateKeyJwt:
tokenEndpoint: http://127.0.0.1:8091/oauth2/token
assertionAudience: http://127.0.0.1:8091
clientId: qualification-reader
privateKeyRef: secret:file/qualification-client.jwk
resource: urn:example:evidence
scopes: [evidence:invoke]The provider URL and token endpoint use numeric loopback HTTP. Provision the
client at the issuer with precisely that resource and scope. The session copies
the referenced key and JWKS (secret references) to its
private state; it gives the runtime secret references. The ordinary BREG
credential provider refreshes expired service tokens. tokenRef is an
alternative for a pre-issued token and cannot be combined with privateKeyJwt.
An optional caBundleRef and revokedKeyIds retain their ordinary
relying-party meanings.
The same bindings apply to the disposable schema-test rehearsal and the local runtime. A journey that applies a guarded request needs a reachable configured Evidence provider and synthetic source facts. The development command does not skip guards or replace runtime activation checks. Edited provider bindings on a retained session follow the normal source-change refusal and recovery rules.
For a project with task-grant profiles, declare each Casework status service
before the first start. The source issuer must match the profiles' taskGrant
issuer. The status client has only the status scope, no BReg profile, and no
allowBregAccess:
clients:
- id: breg-status
accessProfiles: []
scopes: [casework:grants:status]
claims: {registry_actor_kind: service}
taskGrantStatus:
casework:
sourceIssuer: https://casework.local.example
baseUrl: http://127.0.0.1:8092/
client: breg-statusMerge these entries into the existing versioned clients file. Up to eight
bindings are supported, with unique source issuers and exact numeric loopback
HTTP endpoints. The generated runtime uses the session issuer for token
acquisition and client assertion audience, copies the client's assertion key
into an owner-only secret file, and selects that client's declared
issuer.clientResources audience or the session's default resource. Register
the same client and resource in Casework's taskAuthority.statusClients.
This binds the existing task-grant status contract; it grants
no access to source records and does not replace the governed task profile.
One BREG dev session can own the issuer registrations for several local
products. Its dev-clients.yaml declares the complete inventory before that
issuer starts. Add other audiences and the exact client role mapping under
issuer; the default BREG audience is the resource reported by the owner
session. Extra resources carry no client permission until a client is mapped
or receives an explicit resource grant.
clients:
- id: task-agent
accessProfiles: []
scopes: [casework:grants:assert]
claims: {registry_actor_kind: service}
grants:
- audience: urn:registrystack:scheduling:local:appointments
scopes: [scheduling:read]
issuer:
resources:
- audience: urn:registrystack:evidence:local:gateway
scopes: [evidence:invoke]
- audience: urn:registrystack:casework:local:inbox
scopes: [casework:grants:assert]
- audience: urn:registrystack:scheduling:local:appointments
scopes: [scheduling:read]
clientResources:
evidence-reader: urn:registrystack:evidence:local:gateway
task-agent: urn:registrystack:casework:local:inbox
exchangeIssuers:
- id: casework
issuer: https://casework.example.test
jwksEndpoint: http://host.docker.internal:8094/oauth2/jwks
mapping: institutional-grant
clients: [task-agent]
- id: portal
issuer: http://127.0.0.1:8095
jwksEndpoint: http://host.docker.internal:8095/oauth2/jwks
mapping: first-party
clients: [portal-exchange]
tokenAttributes:
registry_principal: string
evidence_tags: string-array
exchangeClients: [task-agent, portal-exchange]
interactiveApplications:
- id: staff-portal
clientSecretRef: secret:file/staff-portal-secret
origin: http://127.0.0.1:3000
redirectUris: [http://127.0.0.1:3000/callback]
grants:
- scopes: [registry:generic:operate]
tokenAttributes: [registry_actor_kind]
syntheticUsers:
- username: officer
email: officer@example.test
passwordRef: secret:file/officer-password
attributes: {registry_actor_kind: human}
grants:
- scopes: [registry:generic:operate]Every referenced client also needs its own ordinary clients entry with exact
scopes and claims. exchangeClients must have one bootstrap scope, and the
external authority must be pre-registered. List each exchange client under
every connection whose authority it may present: the local resource servers
read that pairing and refuse a token exchanged from any other authority, so a
client named by no connection is refused before startup. A first-party
connection's clients list also selects the claims that connection projects;
an institutional-grant connection projects none. Local browser applications use
authorization code with PKCE and explicit redirect URIs. Their secrets and
synthetic passwords are copied into the owner's private issuer state. Explicit
app and user grants render issuer role assignments for the matching
resource; requested scopes without both permissions are not granted. An app
or grant that omits audience uses the owner's default BREG audience; such an
app enters the local runtime's allowed client list, and an app mapped to
another resource does not. The app still needs a
token with the governed profile's actual scope and principal/purpose claims
to call BREG.
Machine clients may also declare grants with the same audience/scopes format.
Up to seven distinct audience grants are accepted, each with 1..32 distinct
scopes declared by that resource. Omit audience for the owner's default BREG
resource. Grants merge into that client's existing issuer role without adding
duplicate resource permissions. They do not change its primary scopes, default
token requests, exported resource, exchange bootstrap scope, or BREG admission.
For example, the task agent above may request an ordinary scheduling:read
token at the Scheduling resource using the same client and subject; committing
an appointment still requires Scheduling's independently verified task grant.
Unknown audiences, undeclared scopes and borrowed-client grant declarations
are refused. Author these grants on the issuer owner before first start.
For institutional exchange clients, extra ordinary grants may name only other
resources; the primary resource retains its single exact bootstrap permission.
The issuer tooling records those explicit extra permissions separately from
the bootstrap bound, without creating task-grant claims or widening exchange.
Start the owner first. Another BREG project can run
bregctl dev start ./registry-two --issuer-project ./issuer-owner when the
owner pre-registered that project's client IDs, exact scopes, and claims. The
borrower copies those client pairs and issuer keys, pins the owner session ID,
and never starts or stops the shared issuer. Its own BREG package, database,
profiles and outbox remain independently owned. A replaced or unavailable
owner is refused before the borrower starts.
Declare additional issuer resources, exchange connections and clients, browser
applications, and synthetic users on the owner. A borrower may list only
issuer.browserClients from that issuer inventory; owner-only declarations in
the borrower's clients file, including machine-client grants, are refused
before startup.
A borrower answers on the owner's BREG audience, so it applies the owner's
exchange-connection pairings: an exchanged token is refused unless the
presenting client is registered against the assertion authority that signed it,
by exactly the connection list the owner declared.
To use an owner-registered browser app at the borrower's BREG resource, list
its ID under issuer.browserClients in the borrower's clients file. The owner
must have registered that app for the same audience. The borrower admits only
those listed app IDs; its governed profiles and token scopes still decide each
API call.
Stop the retained registry normally, then use Evidence's guided local setup:
bregctl dev stop ./registry
evidencectl source add ./registry ./evidenceChoose the entity, an existing required unique scalar field, readable facts, and
record scope. The review distinguishes registry-wide exact lookups from an
explicit fixed binding on a string-valued row field. Without --apply, the
command reports the lookup, scope, and dedicated client it would prepare and
applies nothing. With --apply, it prepares one dedicated lookup-only client
and source contract; it imports the contract and copies that credential into
Evidence's private secrets directory. Local target endpoints and paths come from
the retained session and selected project, not from copied settings.
The owning BReg operation is bregctl dev prepare-source, which bregctl dev --help
omits because evidencectl source add is the documented path. Without --apply,
it reports the inventory or proposed change. --apply stages a policy-only successor
while the session is stopped. It adds the selected selector, narrow grant, and
separate client, then advances the package sequence. The next dev start activates
that successor while preserving record IDs, revisions, values, audit history,
and existing client credentials. It does not infer authority from field names.
This is not general retained-schema evolution. It refuses unrelated authored changes, a nonunique selector, or reused client/profile identifiers. Correct the named issue without removing retained data. For other model changes use the reviewed package lifecycle.
The private .breg/dev directory records a random ownership identifier, exact
Docker container ID, ports, captured authored closure, package digest, seed
checkpoints and, for each resolved breg and docker prerequisite, the
fully resolved path of the file that ran and the version it reported. It
contains generated configurations, separate database roles, local TLS material,
credentials and bounded private diagnostic logs. The first start writes a
.gitignore into .breg that keeps the whole directory, its lock file included,
out of version control. Preserve it with the retained database while the
exercise matters. It is local development material, not production key
provisioning.
The state document also pins what the session runs: the compiled registry
revision, the package sourceRevision, and the canonical JSON form of
tests/journeys.yaml and the clients file. dev start, dev examples run and
dev prepare-source compare the project against that pin, so an edit that
changes only comments, blank lines or key order in a YAML file is not a changed
input. The compiled revision carries the digest of every Rhai script, WASM
module and derived SQL file it ships, so any edit to one of those files, a
comment included, is a changed input. A session started by an earlier release
holds an earlier pin, which reads as changed inputs.
Records live in a named breg-dev-<owner> Docker volume, so the storage stays
identifiable and reclaimable once the container is gone.
| Action or condition | Behavior |
|---|---|
| First start | Capture the authored closure, prepare private identities, create the owned database, run normal schema-test/package/apply/verify commands, then seed through authenticated HTTP. |
| Already running | Return the existing ready session and credential references. |
| Stop, including repeated stop | Gracefully stop owned BReg and ThunderID, then stop the owned PostgreSQL container. Keep records, keys, package, seed checkpoints and the audit files. |
| Stop where no start ever ran | Refuse and name the absent session. Nothing is created, changed or removed, so a mistyped project path cannot read as a stopped session. |
| Start after stop | Reuse the same container, database, and existing credentials. Preserve record edits; activate the explicitly prepared source successor when present. Obtain fresh short-lived tokens. |
Stop with --remove, including a repeated one |
Stop as above, then remove the owned container and its named data volume, tolerating whatever an earlier reclamation already took. Discard records, event receipts and seed checkpoints. Keep the audit files, keys, credentials, ports, clients and the built package. |
Start after --remove |
At sequence 1, create an empty container and volume, activate the initial package, and replay authored seeds. A retained successor refuses before Docker because its predecessor records were removed; use a fresh project at sequence 1 for an empty experiment. |
| Seed request committed before checkpoint | Replay the same permanent BReg idempotency reservation. The original create result is returned without creating or overwriting a record. |
breg from another release |
Refuse before the owned container is inspected and before the supervisor launches, naming the file that answered, the version it reported and the version bregctl reports. Nothing is created, changed or removed. |
| Retained state this release cannot read | Refuse as invalid retained dev state before any mutation, and preserve it for inspection. |
| Partial start failure | Stop acquired service children and the owned container; retain private diagnostics and completed phases. Retry the same command after correcting the prerequisite. The separate schema-test database may be recreated for a failed rehearsal. |
| Missing or mismatched owned container | Refuse. Never silently initialize an empty replacement or stop another container. |
| Unreachable supervisor with an occupied service port | Refuse. Never signal a stored PID that could belong to another process. Inspect the process owning the port before recovery. |
| Authored package, clients, ports or issuer image changed while records are retained | Refuse before activation or record mutation. Restore the original inputs to restart with the records, run dev stop --remove to discard them and start again from the edited inputs, or copy the authored files to a new project directory, at package sequence 1, to keep the records. |
Authored package, clients or ports changed after --remove |
At sequence 1, replace the session from the edited inputs, keeping previous ports and clients file and generating new keys. A successor still requires retained predecessor records. |
Reclamation is explicit: only dev stop --remove discards records, only a start
after it replaces retained keys, and only for edited inputs; this command
implements no automatic reset or general retained-schema upgrade. The explicit
source preparation above is limited to its reviewed policy-only successor. An operated
successor uses BReg's normal reviewed package, migration, activation and
recovery procedure. Copy only authored files to start a separate local
experiment; copying private retained state does not clone its database.
Only a resident native supervisor may signal the children it created. Control
uses an owner-only Unix socket in a short, random directory below canonical
/tmp, avoiding platform socket-path limits in deeply nested projects. The
socket directory is removed after stop. Durable state stays in the project.
Catchable termination signals trigger owned cleanup; an uncatchable kill can
require inspection of surviving service owners before restart.
PostgreSQL binds only numeric loopback. Native setup generates a private local CA and configures TLS plus separate migration/runtime roles before BReg connects. Source and token HTTP clients use numeric loopback, disable ambient proxies and refuse redirects. Local synthetic credentials must not become operated service credentials.
When startup fails, the error names the check that failed and the retained
private logs directory. Native command and service diagnostics are bounded
per stream. Inspect them locally; do not publish logs or the generated state
as a support attachment. Schema-test setup failures identify destination
inventory or activation, authentication, cursor, audit, or Evidence
configuration. Correct that named configuration before retrying; recreating
the database cannot fix a missing destination binding or invalid secret.
On an initial start, the pre-apply doctor may report
startup.database.unready, startup.database.uninitialized, or
startup.package.not-active. Dev treats these expected not-yet-activated
results as the cue for the initial package application, preserves the raw
doctor report as private evidence/doctor-before-apply.json, and does not copy
it into failure logs. A standalone doctor
run and the post-apply doctor keep their ordinary success or failure behavior.
The rehearsal, the package build, activation and verification all run inside
the detached supervisor, whose own streams go to a private log. A phase that
refuses records its first failing diagnostic in the private state document,
and the start reports that diagnostic on the terminal that asked for it:
its code, the path it names, and its message, bounded to one sentence. The
retained report log holds every later diagnostic. A refused journey step is
reported as test.step.failed at journeys[<i>].steps[<j>], carrying the
fixture's own refusal sentence. A refused logical reference names its class:
a field the entity does not declare, a field the access profile permission does
not make writable, a request body with no field, a step identifier that is
not stable, a step naming both an entity and an action or neither, or a
capture no earlier step declares. None of those sentences carries an authored
value.