Skip to content
Registry StackDocsDevelopment (unreleased)

Actions and Proposals

View as Markdown

If your Base Registry Engine (BReg) registry needs changes beyond field-by-field edits, declare them as Actions. Here you see what an Action declares, when it applies at once and when it becomes a reviewed Proposal, and how a Plan computes its effects. Change a registry with an Action runs both.

Terminal window
bregctl init --template facility-registry facility
bregctl check facility

The template declares three Actions: register-facility, correct-address, and deactivate-facility; bregctl explain actions facility shows each one compiled.

An Action has typed inputs, an execution mode, a plan of effects, and optional commitGuards. deactivate-facility holds all four:

registry.yaml
- id: deactivate-facility
display:
label: Deactivate facility
description: Submit a durable deactivation that requires no external review.
execution:
mode: proposal
proposal:
review: {type: none}
application: {mode: manual}
retention: {mode: retain}
commitGuards:
targets:
- {input: facility, revision: unchanged}
- {input: facility, field: status, equals: active}
inputs:
- {id: facility, apiName: facilityId, type: reference, target: facility, required: true, classification: internal}
plan:
type: fixed
effects:
- id: facility-status
target: {fromInput: facility}
operation: patch
set:
status: {value: inactive}

Commit guards are checked when the change commits: here, the facility must be unchanged since the Proposal froze it, and still active. The caller never chooses the execution mode.

Who may run an Action is an access-profile grant, invoke for an immediate Action and start for a Proposal:

registry.yaml
actions:
- action: correct-address
operations: [start]

Other Proposal operations, such as submit, cancel, or apply, are granted separately under permissions.proposals, so whoever drafts a change need not apply it.

register-facility declares execution: {mode: immediate}. One request checks the guards, writes every effect, and commits the new revisions, the receipt, the audit entry, and any outbox events together, or none of them. The answer is an ActionReceipt naming each changed Record and its new revision.

A Record type can require a Proposal for an operation:

registry.yaml
mutationPolicy:
patch: proposal-required

With this on facility, neither a direct patch nor an immediate Action may patch a facility; only a Proposal can. register-facility still creates facilities at once.

An Action in proposal mode creates a Proposal instead. correct-address requires review:

registry.yaml
execution:
mode: proposal
proposal:
review:
type: required
authority: casework
source: facility-registry

A Proposal starts as a draft its author can edit. Submitting it freezes the inputs, the computed effects and their digest, the target revisions, and the Action revision; review and application work on that frozen submission, so a later change to the Plan or policy cannot reinterpret it. Revise or Rebase returns it to a new draft, which must be submitted, and reviewed, again.

A Proposal ends in one of three states:

  • applied: the registry wrote the frozen effects, under the commit guards as they stand at that moment. With no review it follows submission; with required review it needs current approval of that exact submission.
  • rejected: the review authority rejected the submission. This is final. A reviewer who wants another attempt returns it with changes requested instead, which keeps it submitted and offers Revise.
  • cancelled: a caller with the cancel grant withdrew it before it applied.

Application is automatic, under a restricted system grant for that one submission, unless the Action declares application: {mode: manual}, as deactivate-facility does; then a caller with the apply grant applies it. The Proposal’s applicationStatus reports progress: awaiting-review, pending, applying while a worker is writing it, then applied, or blocked with a reason and a recovery hint.

The project names each review authority it relies on:

registry.yaml
reviewAuthorities:
- id: casework
protocol: id.registrystack.org/protocols/review/v1
display:
label: Casework review authority
description: Logical authenticated review authority resolved by deployment configuration.

The deployment binds that id to a running service. For local development, the template’s README gives the dev-clients.yaml binding:

README.md
reviewAuthorities:
casework:
endpoint: http://127.0.0.1:8100/
profile: requester
producerId: facility-registry
recoveryDays: 30
client: requester

In production the same map sits in the runtime configuration (runtime.yaml), with a tokenRef or privateKeyJwt credential instead of client. A missing binding disables only the operations that need it. An unreachable authority never counts as approval.

Registry Casework is one such authority. The registry sends it the frozen submission and applies only the correlated result; Review a Proposal in Casework pairs the two.

A Plan is fixed, like deactivate-facility’s, or computed by a Rhai Function. correct-address names its Function and bounds what it may write:

registry.yaml
plan:
type: function
function: correct-address-plan
inputs: [facility, address-line, city, postal-code]
writes:
- id: facility-address
target: {fromInput: facility}
operation: patch
fields: [address-line, city, postal-code]
refusals:
- {code: invalid-postal-code, label: Postal code is invalid}

The project’s functions entry names its script:

plans/correct-address.rhai
fn plan(ctx) {
let postal_code = ctx.inputs["postal-code"];
postal_code.trim();
if postal_code == "" {
return #{
refusal: #{
code: "invalid-postal-code",
field: "postal-code"
}
};
}

The planner receives ctx.inputs, holding only the inputs the Plan lists. It returns either a refusal, whose code the Action declares, or effects: a list of maps, each with the id of a declared write and its set or clear fields. The declaration fixes each write’s target and operation, so the script cannot redirect one. Omitting a write skips it; the caller still needs authority over its target.

Effects apply in authored order. A value fromEffect must name an earlier create, as register-facility’s assignment names its new facility. Two effects that land on the same Record are written together as one revision; two effects that write the same field fail bregctl check.

An Action with a Rhai Plan may also declare evidence: at most two capabilities, each naming an Evidence provider the project declares, a requirement, its subjects, and the outputs it may read. The script calls evidence::resolve("<capability id>", subjects) once per capability and receives only those outputs; a failed call refuses the operation. commitGuards.evidence instead checks a requirement at commit and never changes the effects. The provider’s endpoint and credentials belong to the runtime configuration; BReg inherits no Evidence authorization.