5. Actions, Rules, and Lifecycles
Fields describe state. Actions describe intent to change state. A lifecycle declares which state changes are legal.
Action request
-> availability and before-commit rules
-> legal lifecycle transition
-> state mutation
-> declared events and effects
-> audit and replay captureDeclare a lifecycle on its owning facet
enum AreaStatus { draft, active, retired }
abstract final class AreaFields {
static const status = StatusField(
'status',
AreaStatus.values,
dbEnumName: 'area_status',
);
}
final governanceFacet = Facet(
name: 'governance',
title: 'Governance',
lifecycle: Lifecycle(
stateField: AreaFields.status,
initialState: 'draft',
transitions: const [
Transition(from: 'draft', to: 'active', trigger: 'activate'),
Transition(from: 'active', to: 'retired', trigger: 'retire'),
],
),
actions: const [
// Added below.
],
);Transition.trigger is the name of an action on the same facet. The validator checks state and trigger references. Transition.anyState ('*') is available for deliberately global transitions.
Put the complete decision surface on the action
const activateArea = Action(
name: 'activate',
title: 'Activate',
description: 'Make the area available for controlled operations.',
scope: ActionScope.record,
consistency: ConsistencyMode.strict,
rules: [
Rule(
id: 'ops.area.activate.grant',
kind: RuleKind.access,
phase: RulePhase.availability,
condition: ActorHasGrantCondition(
grant: 'ops.area.approve',
),
error: ActionError(
code: 'ops.area.activate.not_allowed',
kind: ActionErrorKind.access,
message: 'The actor cannot activate areas in this scope.',
),
),
Rule(
id: 'ops.area.activate.lifecycle',
kind: RuleKind.lifecycle,
condition: StateIsCondition(
facet: 'governance',
state: 'draft',
),
),
],
audit: AuditEnvelope(
requireSignature: true,
requireReasonCode: true,
),
emits: [
ActionEvent(
name: 'ops.area.activated',
kind: ActionEventKind.stateChanged,
facet: 'governance',
),
],
capture: ActionCapture.standard,
ui: ActionUI(
icon: UIIcon('activate'),
confirmationMessage: 'Activate this area?',
),
);Attach it to governanceFacet.actions. An action belongs to the facet whose state and behavior it governs.
Conditions are typed data
The shipped condition vocabulary includes:
- constants:
AlwaysCondition,NeverCondition; - row predicates:
PredicateCondition,FieldEqualsCondition,ExistsCondition; - actors:
ActorInSetCondition,ActorHasGrantCondition,ActorQualifiedCondition; - policy and lifecycle:
PolicyAllowsCondition,StateIsCondition; - proof:
EvidencePresentCondition; - boolean composition:
AllCondition,AnyCondition,NotCondition; - an escape hatch:
EvaluatorCondition(ref: 'ops.area.release').
Prefer built-in conditions. A named evaluator remains explainable only when its reference and implementation version are captured at runtime.
Rule phase and severity are independent
Rule(
id: 'ops.area.capacity.warning',
kind: RuleKind.data,
phase: RulePhase.beforeCommit,
severity: RuleSeverity.warning,
condition: const ExistsCondition(path: 'hierarchy.capacity'),
)availabilitypowers disabled/hidden action affordances and preflight.beforeCommitguards the transaction.afterCommitverifies the committed outcome.asyncschedules deferred evaluation.
info and warning findings are still captured. blocking prevents commit.
Events are facts; effects are consequences
Action(
name: 'activate',
emits: const [
ActionEvent(
name: 'ops.area.activated',
kind: ActionEventKind.stateChanged,
required: true,
),
],
effects: const [
Effect(
targetEntity: 'equipment',
targetFacet: 'availability',
targetTrigger: 'refresh',
consistencyOverride: ConsistencyMode.eventual,
),
],
)The event says what happened. The effect asks another surface to react. Eventual effects leave through the durable outbox; strict effects participate in the same transaction.
Shipped compatibility boundary
Action is the public declaration. Today Action.toTrigger() lowers availability and before-commit rules to the compact trigger contract used by parts of the current runtime. Do not author new domains directly against Trigger unless you are implementing a low-level adapter.
Checkpoint
Add a retire action that:
- requires
ops.area.approve; - is legal only from
active; - requires a reason and signature;
- emits
ops.area.retired; - produces an eventual refresh effect.
Validate that the transition names match actions on the facet.
Next: Modules and assembly.
Reference: Vocabulary — actions and execution.