A workflow can declare capabilities.max_spend_cents as a hard ceiling on how
much its live effects may spend; the engine documents this as "the hard
guarantee that makes max_spend_cents load-bearing rather than advisory", and it
is enforced at runtime by refusing any effect whose estimated amount would push
the run's running total past the cap.
The ceiling is not inherited across a subworkflow boundary. When the engine
reaches a subworkflow node it runs the nested workflow with a fresh recursive
call that forwards the live-effect and http-live flags but NOT the parent's
max_spend_cents and NOT the parent's accumulated spend. The nested run reads its
OWN capabilities.max_spend_cents (typically absent — no cap) and starts its own
spend counter at zero. So an effect the parent's cap would refuse at the top
level fires unchecked one subworkflow level down.
Concretely: a parent workflow with capabilities.max_spend_cents = 0 refuses a
top-level effect that the engine prices at $50.00, but the identical effect
placed inside a subworkflow node of that same parent executes and spends the
$50.00. Any spend ceiling an operator sets on a workflow can be bypassed by
moving the spend into a nested subworkflow — the plan's blast radius still shows
the aggregate cost, but nothing at runtime enforces the parent's ceiling on it.
Reproduction steps:
- Extract the station-v0.88 release tarball and work against workbench/.
- Pick or register an effect action the engine prices at a nonzero amount (an
effect whose args carry amount_cents, e.g. a charge/refund-shaped verb).
- TOP LEVEL — confirm the cap holds:
top = {"id":"top","capabilities":{"max_spend_cents":0},
"nodes":[<effect node with amount_cents=5000>]}
run_workflow(top, ws=WS, signing=signing, allow_live_effects=True)
-> the effect is refused (spend cap exceeded); it does not fire.
- NEST the identical effect one level down, parent cap still 0:
nested = {"id":"sub","nodes":[<same effect node>]}
parent = {"id":"parent","capabilities":{"max_spend_cents":0},
"nodes":[{"id":"sw","type":"subworkflow","workflow":nested}]}
run_workflow(parent, ws=WS, signing=signing, allow_live_effects=True)
Expected: the nested effect is refused by the parent's max_spend_cents just like
the top-level one — the cap governs the whole composed run.
Actual: the nested effect FIRES and spends the money; the parent's
max_spend_cents = 0 is not applied to anything inside the subworkflow.
Root cause: workflow_engine.py, run_workflow subworkflow branch — the recursive
run_workflow(sub_spec, ...) call does not pass the parent's max_spend_cents or
its running spend total, and the spend-cap check itself is scoped to the current
run's own capabilities and counter. The sub-run therefore enforces only the
nested spec's own (usually absent) ceiling.
Suggested fix: thread the effective remaining budget into the recursive call —
pass the parent's max_spend_cents (or the remaining headroom = cap minus spend
already committed) and have the sub-run charge its effects against that shared
ceiling, so the cap bounds the entire composed run regardless of nesting depth.