Skip to content
docsv0.25.1

ActionContract

What a component ACTION is for, not only what its payload looks like. Until this existed, `ComponentContract::$actions` was action name → payload shape: a button that saves and a button that toggles a disclosure were indistinguishable to anything reading the contract, including the agent. This says what the action DOES, in the vocabulary that already governs Operations — no second one (greenhouse decisions/0214 point 2). ── `null` MEANS «NOT DECLARED», AND THAT IS LOAD-BEARING ─────────────────────────────────────────── The words are the same as an Operation's but the DEFAULT is deliberately the opposite, and mixing them up would invert this contract's whole promise. For an Operation, silence about effects means the CEILING: `Operation::effectCeiling()` answers `EffectProfile::unclassified()`, which admits nothing. For a component action, silence means «nobody said», and the house keeps behaving exactly as it did. So {@see $effects} is `?EffectProfile` and its absence is `null` — never `unclassified()`. If it ever defaulted to a profile, every existing undeclared button in every app would become maximally suspicious overnight, and an additive change would have become a breaking one. Anything that gates on this must ask «was it declared?» BEFORE it asks «what does the profile say?». ── AN ACTION THAT DELEGATES NAMES ITS OPERATION, IT DOES NOT COPY IT ────────────────────────────── Most component actions are their own act. Some are a BUTTON FOR AN OPERATION — a panel offering to install a capability is offering `capabilities:enable`, which already declares that it downloads third-party code, that its authority is privileged and that its reversibility is manual recovery. Restating that profile here would be two sources of truth about one act, and they would drift the first time the operation's own classification changed. {@see $invokes} names the operation instead: the action says WHAT IT RUNS, and the profile stays where it was declared and where the gate already reads it. ── WHAT IT REFUSES ──────────────────────────────────────────────────────────────────────────────── A declaration that contradicts itself is refused where it is written, not obeyed and puzzled over later: `mutating: false` beside a profile that mutates, or `mutating: true` beside `Mutation::None`. Saying both is not a nuance — one of the two is wrong, and the author is the only one who knows which.

ActionContract::__construct()

public function __construct(string $summary = '', bool $mutating = false, ?Milpa\Command\Effect\EffectProfile $effects = null, ?string $namedTarget = null, ?string $scopeBy = null, ?string $invokes = null, array $payload = []):

Parameters

Parameters of __construct()
NameTypeDescription
$summarystringWhat the action is for, in one sentence — the text an agent's catalogue shows. There is no second copy to keep in sync.
$mutatingboolWhether performing it changes anything.
$effects(EffectProfile | null)The five-axis profile, or `null` for NOT DECLARED. Never a default profile: see the note above.
$namedTarget(string | null)The payload field a human must name for this action to be meaningful (ADR-0044), when it has one.
$scopeBy(string | null)A payload field whose VALUE, not the action name, derives the authorization scope — one generic action carrying per-event authorization (greenhouse decisions/0096).
$invokes?string
$payloadarray<string, mixed>The payload shape, exactly as the bare-array form declared it.

ActionContract::declaresEffects()

public function declaresEffects(): bool

Whether anything at all was declared about this action's effects. The question every gate must ask FIRST. An undeclared action is not a safe action and it is not a dangerous one — it is one nobody described, and treating «nobody said» as either answer is the mistake this method exists to prevent.