SECTOR 02 // HOW-TO GUIDES
Compose a domain provider
Give your system under test its own vocabulary (submit, await, count) with zero Go.
Goal: scenarios that read app.submit / app.await instead of raw
http.post plumbing.
1. Declare a kind: Provider resource #
Create providers/app.yml:
apiVersion: shinari/v1
kind: Provider
name: app
verbs:
submit:
params: [job, "inputs?"]
do:
- run: http.post
with:
path: "/jobs/${.params.job}"
form: "${.params.inputs}"
read: ".id"
await:
params: [of, timeout]
do:
- run: wait_until
with:
probe:
run: http.get
with:
path: "/jobs/${.params.of}"
read: ".state"
in: [SUCCESS, FAILED]
timeout: "${.params.timeout}"
count:
params: [job]
probe:
run: http.get
with:
path: "/jobs?type=${.params.job}"
read: ".items | length"
A trailing
?marks a param optional, and it must be quoted ("inputs?"): bareinputs?]is invalid YAML flow syntax.
2. Configure it #
In project.yml, point an instance at the definition and give it config:
providers:
http:
config:
baseUrl: http://localhost:8080
app:
use: ./providers/app
The instance name (app) becomes the verb namespace: app.submit,
app.count. The body’s leaf verbs (http.post, http.get) resolve against
the instances configured in the same project, so the http instance’s
baseUrl decides which deployment the vocabulary talks to.
3. Use the vocabulary #
method:
- phase: "Submit a long job"
steps:
- run: app.submit
with:
job: sleep
inputs:
seconds: 30
as: job
verify:
- run: app.await
with:
of: "${.outputs.job.value}"
timeout: 420
- run: app.count
with: sleep
as: total
- run: assert
with:
of: "${.outputs.total.value}"
equals: 1
desc: "exactly once"
A composed verb’s result is its last body step’s value, after that step’s
read: transform. app.submit ends in read: ".id", so the verb returns the
job id; as: job then wraps it in the usual {value, output, meta} envelope,
which is why the scenario passes ${.outputs.job.value} (the id itself) to
app.await.
What the engine infers for you #
- Kind: a verb whose body mutates (
http.post) is an action; a body ending inassertis an assertion; otherwise it’s a probe. Kind drives dry-run, steadyState re-runs, and wherefinding:is allowed. - Args:
params:become the verb’s arg spec: required unless marked"name?", first param doubles as the scalar shorthand (with: sleep≡with: { job: sleep }). - Scoping:
${.params.NAME}references and body captures are macro-local; they never leak into the scenario’soutputsnamespace.
Rules to know #
- Composed verbs may call native verbs and builtins freely, but another
composed verb only one level deep:
validaterule 4 rejects deeper nesting. Vocabulary, not an inner platform. - If a step needs logic YAML can’t express, don’t fight it: wrap a script with
exec.runinside the verb body.