Portable workflow recipes¶
These patterns assume a portable pack, so they execute in-process and depend
only on hubuum-cli. Copy the complete, compile-checked versions from
examples/hubuum-recipes.
Find the right bindings for a run step¶
A command contract is the CLI's description of how a workflow may call one
of its built-in commands. It names the keys accepted by the step's with
object and describes each key's value type, whether it is required, and whether
it accepts one value or a repeated group. The contract also reports operational
properties such as whether the command needs authentication or may change data.
contract --list lists the built-ins that a run step may call. Pass one of
those command paths to inspect its contract:
The second command reports that where has repeated_fixed cardinality with
groups of three. Bind one clause as a flat array:
{
"id": "servers",
"kind": "run",
"run": ["object", "list"],
"with": {
"class": "Hosts",
"where": ["name", "contains", "server"],
"all": true
}
}
Pass typed values¶
Use the source that owns the value:
"with": {
"from_user": { "input": "class" },
"from_site": { "config": "hosts_class" },
"from_step": { "step": "classes" },
"one_field": { "step": "classes", "select": ".[0].name" },
"plain_text": "Hosts",
"object_data": { "literal": { "site": "oslo" } }
}
The keys on the left must exist on the called command or workflow. The example
uses descriptive placeholders; a real run uses IDs from extension contract.
Reuse a private workflow¶
Declare a workflow without a corresponding command, then call it:
Calls stay in the current pack. Input names, types, and repeatability are checked statically. The compiler rejects missing targets and call cycles.
Iterate with a hard bound¶
The target workflow declares an input matching as:
{
"id": "objects_by_class",
"kind": "for_each",
"items": { "input": "classes" },
"as": "class",
"call": "list_one",
"max_items": 10,
"when": ".input.enabled"
}
classes must resolve to an array. Runtime length must be no more than both
max_items and the host limit. Results preserve input order.
Transform and check values¶
Use let for a bounded transformation and assert for an invariant:
[
{
"id": "names",
"kind": "let",
"expr": ".steps.hosts | map(.name)"
},
{
"id": "has_hosts",
"kind": "assert",
"condition": "(.steps.names | length) > 0",
"message": "No Hosts were found"
}
]
An assertion condition and a when expression must yield exactly one Boolean.
Declare repeatable command input¶
The workflow input:
must match its public command option:
"classes": {
"kind": "string",
"long": "class",
"repeatable": true,
"help": "Class to visit; may be repeated"
}
Users can then pass --class Hosts --class Jacks.
Safely expose mutation¶
First inspect the built-in target:
Any workflow whose expanded calls may mutate data declares the capability:
Every calling workflow must make this acknowledgement. The compiler propagates
mutation and replay safety through call and for_each. This is an effects
declaration, not a transaction: completed writes are not rolled back after a
later failure.
Read an explain result¶
Run:
The workflow entry at .plan.workflows[0] contains these sections (irrelevant
fields are omitted here):
{
"name": "tour",
"effects": "read_only",
"requires_authentication": true,
"reauthentication_retry": "safe",
"call_depth": 2,
"worst_case_operations": 38,
"output": {
"shape": "detail",
"type": "json"
},
"steps": [
{
"id": "configured",
"kind": "run",
"run": "object list",
"with": {
"class": { "config": "fallback_class" }
}
}
]
}
effectsis expanded across nested calls, not copied from the outer step.requires_authenticationsays execution needs a configured server session; validate and explain themselves remain offline.reauthentication_retrydescribes whether the whole exposed command can be replayed safely after renewing a session.call_depthandworst_case_operationsare compiler results checked against the adjacentlimitsobject.stepsis the stableWorkflowPlan, with binding sources made explicit.
Use --workflow to keep a large pack readable. Without it, explain returns
all public and private workflow plans.
Debug validation failures¶
Use this order:
- Let the editor schema catch misspelled fields and wrong JSON shapes.
- Run
extension contractfor an unknownrunbinding or wrong cardinality. - Run
extension validatefor the complete cross-reference and type error. - Run
extension explain --workflow NAMEto verify effects, conditions, normalized bindings, call expansion, and limits. - For installed discovery failures, run
extension doctor --output json.
Forward step references, cross-pack calls, cycles, undeclared mutation, unbounded iteration, wrong output shapes, and incompatible command interfaces are all rejected before a portable command is registered.