agents/bundles: triggers/triggered:True invariant + self-healing

Two related lessons from the left4me integration:

1. The triggers/triggered:True invariant tripped three times in
   one session. When chown_src was promoted from triggered-only
   to self-healing-every-apply (drop triggered:True + add unless),
   bw rejected because it was still in git_deploy's triggers
   list. Same dance happened for pip_install.

2. Triggered actions can't recover from partial failure: once
   upstream succeeds, it's "in desired state" forever and the
   trigger never re-fires. For pip installs / chowns / migrations
   that must heal on every apply, the right shape is no
   triggered:True + unless:<fast-check>. unless semantics fold
   into the same bullet.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
CroneKorkN 2026-05-10 20:29:10 +02:00
parent 59788f315a
commit 69bcac421a
Signed by: cronekorkn
SSH key fingerprint: SHA256:v0410ZKfuO1QHdgKBsdQNF64xmTxOF8osF1LIqwTcVw

View file

@ -118,6 +118,20 @@ bundles/<name>/
when the reactor writes into another bundle's namespace — a static when the reactor writes into another bundle's namespace — a static
contribution to e.g. `nftables/output` belongs in `defaults`, where contribution to e.g. `nftables/output` belongs in `defaults`, where
bw merges it with other bundles' contributions. bw merges it with other bundles' contributions.
- **`triggers``triggered: True` invariant.** Any item listed in
another's `triggers` list must declare `triggered: True`. bw
enforces this at `bw test` time: *"…triggered by …, but missing
'triggered' attribute"*. Corollary: an action can't be both in an
upstream `triggers` list AND self-healing every apply — pick one.
- **Triggered actions don't recover from partial failure.** When an
upstream item's apply succeeds but its triggered downstream action
fails, subsequent applies can't recover via the trigger chain —
upstream is "already in desired state" and never re-triggers. For
actions that must self-heal (pip installs, chowns, migrations),
drop `triggered: True` and gate the command with `unless: <fast-check>`.
`unless` is a shell command on the target host whose exit status
decides whether the main command runs (exit 0 = skip); it's checked
at fire time, after `triggered:` filtering.
## Per-bundle README ## Per-bundle README