フロー定義リファレンス
klaus のリクエスト定義は素の YAML。1ファイル = 1フロー(複数ステップ)で、キャプチャとアサーションを定義に内包する。スキーマは zod で検証され、違反は exit 2(ParseError)になる。
ファイル構造
name: 認証フロー # 必須: フロー名
env: local # 任意: environments/local.yaml を参照
tags: [smoke, auth] # 任意: フロー単位のタグ。`klaus run --tags` / `--exclude-tags` で使う
steps: # 必須: 1件以上。name はフロー内で一意
- name: login
request: { ... } # request / ws / use のいずれか一方が必須(排他)
sse: { ... } # 任意: SSE 受信設定
capture: { ... } # 任意: レスポンスからの変数キャプチャ
assert: { ... } # 任意: アサーション- 環境ファイルは cwd から上方探索(
.gitを含む祖先ディレクトリ、またはファイルシステムルートで打ち切り)でenvironments/<name>.yamlを解決する。詳細は Getting Started を参照。klaus run --env <name>でフローのenv:を上書きできる。klaus run --env-file <path>は代わりに任意パスの環境ファイルを(上方探索なしで)直接読み込み、klaus run --var <key=value>はその上から個別の変数を追加・上書きする(CLI リファレンス参照) - 環境ファイルは
キー: 文字列値のフラットなマップ。値にはテンプレート({{env.X}}等)を使える - 予約キー
$protected: trueを環境ファイルに書くと、その環境へのklaus runはデフォルトで拒否される(exit 3)。--allow-protectedを明示した場合のみ実行できる。本番相当の環境を誤って実行しないためのガードレールで、$protectedはテンプレート変数({{...}})としては参照できない。klaus ui/ server API 経由の実行はこのフラグを渡さないため、保護環境は常に拒否される $protectedはファイル直接編集でのみ設定・解除する。klaus uiの環境エディタには表示されず、UI からの保存でも既存の$protectedの値は変更されずそのまま保持される
tags
name: 認証フロー
tags: [smoke, auth] # 任意: 空文字列を含まない文字列配列。一意性の制約は無い
steps: [ ... ]- フロー単位のみ — ステップ単位のタグは無い
klaus run --tags <list>/--exclude-tags <list>によるフロー選択専用(CLI リファレンス参照)。タグ自体は実行に他の影響を与えないklaus uiや server API には公開されない
request(HTTP ステップ)
request:
method: POST # graphql 指定時のみ省略可(省略時 POST)。大文字化して扱われる
url: "{{baseUrl}}/login" # 必須。テンプレート可
headers: # 任意。値はテンプレート可
Content-Type: application/json
query: # 任意。値はテンプレート可
page: "1" # url のクエリ文字列にマージされる。url に同名キーがあれば query 側で上書き
body: # 任意。object → JSON 送信(Content-Type 未指定なら application/json を自動付与)
email: "{{testEmail}}" # string → そのまま送信
timeoutMs: 30000 # 任意。デフォルト 30000。超過は RuntimeError(exit 3)レスポンスは Content-Type が JSON なら自動でパースされ、JSONPath アサーション / キャプチャの対象になる。それ以外はテキストとして保持され bodyText アサーションの対象になる。リダイレクト・TLS の挙動は undici のデフォルトに従う(klaus 側では制御しない)。
GraphQL
request.graphql を指定すると GraphQL リクエストの糖衣になる。body とは排他(両方指定は ParseError)。
request:
url: "{{baseUrl}}/graphql"
graphql:
query: 'query { user(id: "{{userId}}") { id name } }' # テンプレート可
variables: # 任意。テンプレート可
limit: 10- method 省略時は POST、Content-Type 未指定時は application/json
- 送信 body は
{ query, variables }(variables 未指定なら{ query }のみ) - レスポンスは通常の JSON として扱われるため、
$.data.…/$.errorsへの JSONPath アサーション・キャプチャがそのまま使える
SSE(Server-Sent Events)
Accept: text/event-stream ヘッダーがある、または sse: ブロックを書いたステップは SSE モードになる。
request:
method: GET
url: "{{baseUrl}}/events"
headers:
Accept: text/event-stream
sse:
maxEvents: 5 # デフォルト 100
maxDurationMs: 3000 # デフォルト 10000maxEvents/maxDurationMsのどちらかに達した時点で受信を打ち切り、正常終了する(打ち切りは失敗ではない)- 受信イベントは
{ event?, id?, data }の配列として結果のeventsに入る。response.bodyは undefined captureは SSE ステップでは無視される- アサーションは
eventCount/events(後述)を使う
WebSocket
ステップに request の代わりに ws: を書く(排他・どちらか必須)。
ws:
url: "{{wsBaseUrl}}/socket" # ws:// / wss://(http(s):// は ParseError)。テンプレート可
headers: # 任意
Authorization: "Bearer {{token}}"
send: # 任意: 接続後に順次送信。string はそのまま、object は JSON 化。テンプレート可
- "ping"
- { type: subscribe, channel: orders }
maxMessages: 50 # デフォルト 100
maxDurationMs: 5000 # デフォルト 10000- 受信メッセージが
maxMessages/maxDurationMsのどちらかに達したら打ち切って正常終了。相手からの正常 close も正常終了 - 接続失敗・異常 close は RuntimeError(exit 3)
- 受信メッセージは
{ data }の配列として結果のwsMessagesに入る。responseは持たない captureは WS ステップでは無視される- アサーションは
messageCount/messages(後述)を使う
use(ステップ参照)
ステップに request / ws の代わりに use: を書くと、他のフロー定義ファイル(1 ステップのみのもの)を参照して、その request / sse / assert を取り込める。request / ws / sse とは排他(併記は ParseError)。同じ API チェックを複数のフローからコピペせずに再利用するための機構で、参照先ファイル自体も従来どおり単体実行できる(api/ を「実行される API カタログ」として扱う設計。詳細は examples を参照)。
# api/login-check.yaml — 従来どおり単体実行可能
name: ログイン API 単体チェック
steps:
- name: login
request:
method: POST
url: "{{baseUrl}}/login"
body: { email: "{{testEmail}}" }
assert:
status: 200
body:
- path: "$.token"
exists: true# flows/auth-flow.yaml — login を書き直さず参照
name: 認証フロー
steps:
- name: login
use: ../api/login-check.yaml # このフローファイル基準の相対パス
capture:
token: "$.token"
- name: me
request:
method: GET
url: "{{baseUrl}}/me"
headers:
Authorization: "Bearer {{token}}"
assert:
status: 200- 解決タイミング: フローのロード時(
klaus run/klaus validate/ UI のフロー詳細取得)。参照先の唯一のステップからrequest/sse/assertを取り込んだ通常ステップに展開してから実行される name/captureは呼び出し側のものを使う。参照先の値は無視されるassertは加算マージ(置換ではない):headers/body/events/messagesは参照先→呼び出し側の順に配列を連結する。status/bodyText/duration/eventCount/messageCount/bodySchemaは両側で定義されていると、単体チェックの保証を弱める置換とみなして ParseError /klaus validateの FlowIssue になる(どちらか一方にのみ定義するか、参照先の定義に任せる)- 参照先の
env:は取り込まない(環境は常に実行するフロー側が決める)。プレースホルダ({{var}})は取り込んだ後、呼び出し側フローの env / capture で通常どおり解決される - パスはこのフローファイル基準の相対パス。絶対パスは拒否される。解決後のパスがプロジェクトディレクトリ(
klaus実行時の cwd)の外に出る場合も拒否される(../によるプロジェクト外参照はできない) - 参照先ステップがさらに
useを持つ場合は再帰的に解決される。循環参照は検出され拒否される
v1 の制限
- 参照先は1 ステップのフローファイルのみ(複数ステップの取り込み・継承・request フィールドの上書きはスコープ外)
- 参照先は HTTP request ステップのみ(
ws:ステップの参照は非対応) - 参照切れ・循環参照・複数ステップファイルへの参照・
assertのスカラー競合は、klaus validateでは hint 付きの構造化 issue、klaus runでは ParseError(exit 2)になる
テンプレート
{{...}} は以下の順で解決される。未解決の変数・未定義の OS 環境変数は RuntimeError(exit 3)になる(黙って空文字にはならない)。
| 記法 | 解決先 |
|---|---|
{{var}} | ①それまでのステップのキャプチャ変数 → ②環境ファイルの値(キャプチャ優先) |
{{env.X}} | OS 環境変数 X。シークレットは定義ファイルに直書きせずこれを使う |
{{newUuid}} | crypto.randomUUID() の UUID |
{{newDate}} | 現在時刻の ISO 8601 文字列 |
{{newTimestamp}} | 現在時刻の epoch ミリ秒 |
展開が適用される場所: request.url / request.headers の値 / request.query の値 / request.body(文字列値の深い展開)/ graphql.query / graphql.variables / ws.url / ws.headers / ws.send / アサーションの期待値(equals: "{{testEmail}}" 等)。
capture(変数キャプチャ)
capture:
token: "$.token" # 変数名: JSONPath
userId: "$.data.user.id" # ネストしたフィールド
firstId: "$.items[0].id" # 配列インデックス- JSON レスポンスに JSONPath を適用し、結果を後続ステップのテンプレート変数にする(ログイン → トークン → Authorization ヘッダーが代表ケース)
- マッチしない・レスポンスが JSON でない場合は RuntimeError になりステップは error(exit 3)。
Bearer undefinedのようなサイレント連鎖は起きない。値がnullのキャプチャは成功扱い - キャプチャした値はマスクされない。シークレットマスクの対象は
{{env.X}}で解決した値のみのため、ここでキャプチャしたトークンは履歴 JSONL・JUnit レポート・record カセットにそのまま書き込まれる。マスクの境界は SECURITY.md を参照 - SSE / WS ステップでは無視される
assert(アサーション)
すべて任意。複数書いた場合はすべて評価され、1つでも失敗すればステップは failed(exit 4)。1つのエントリに複数マッチャーを書くと、マッチャーごとに個別の結果(AssertionResult)が出る。
assert:
status: 200
headers:
- { name: content-type, contains: json }
body:
- { path: "$.token", exists: true }
- { path: "$.email", equals: "{{testEmail}}" }
bodyText:
contains: "ok"
bodySchema:
type: object
required: [id, email]
properties:
id: { type: integer }
email: { type: string, format: email }
duration:
maxMs: 1000
# SSE 用
eventCount: { min: 1, max: 10 }
events:
- { index: 0, path: "$.type", equals: "message" }
# WebSocket 用
messageCount: { min: 1 }
messages:
- { path: "$.type", contains: "order" }マッチャー一覧
| 対象 | フィールド | マッチャー |
|---|---|---|
| ステータス | status | 数値の完全一致 |
| ヘッダー | headers[] | name + equals / contains / regex / exists |
| ボディ(JSONPath) | body[] | path + exists / equals / contains / regex |
| ボディ(生テキスト) | bodyText | equals / contains / regex |
| ボディ(JSON Schema) | bodySchema | JSON Schema オブジェクト |
| 所要時間 | duration | maxMs |
| SSE イベント数 | eventCount | min / max / equals |
| SSE イベント | events[] | index? + path? + 上記マッチャー |
| WS メッセージ数 | messageCount | min / max / equals |
| WS メッセージ | messages[] | index? + path? + 上記マッチャー |
events / messages の共通セマンティクス:
index指定時: そのインデックスの受信データに対して評価index省略時: いずれかの受信データが一致すれば passpath指定時: 受信データ(data)を JSON parse して JSONPath を適用。省略時は生文字列にマッチャーを適用
bodySchema(JSON Schema によるボディ検証)
bodySchemaには JSON Schema オブジェクトを YAML に直接埋め込む(外部ファイル参照は現時点で非対応)- 検証は ajv の draft 2020-12(
Ajv2020)で行う。OpenAPI 3.1 由来のスキーマもおおむねそのまま使える - スキーマに複数の違反があった場合、違反ごとに個別の
AssertionResultが返る(1件目で打ち切らず一括報告される)。各結果のmessageには ajv のinstancePath(ルート違反の場合は(root))と違反内容が含まれる - body が存在しない SSE / WS ステップでは ok:false になる。body が存在するが JSON としてパースできない HTTP レスポンスは、生の文字列のままスキーマ検証にかけられる(例:
type: objectを要求するスキーマなら失敗し、type: stringなら通り得る) - スキーマ自体が不正で ajv がコンパイルできない場合も、例外にはならず ok:false のアサーション失敗として報告される
regex のパターンはテンプレート展開される
他のアサーション値と同様、regex マッチャー(assert.headers[].regex / assert.body[].regex / assert.bodyText.regex / assert.events[].regex / assert.messages[].regex)に渡すパターンもマッチング前にテンプレート展開される。そのためパターン中の {{...}} はリテラルに限らず、キャプチャや --var から解決され得る。キャプチャは検証対象 API のレスポンスボディから埋まるため、キャプチャした値を regex に流すフローでは、実質的にそのパターンを API 側が選べることになる。破滅的バックトラッキングを起こすパターン(例: ^(a+)+$)だと、入力文字数が1つ増えるごとにマッチング時間が指数的に伸び、数十文字程度でも1分を優に超えて固まる。アサーション評価には(HTTP リクエストのみをカバーする request.timeoutMs と異なり)タイムアウトが一切ない。klaus ui ではこれが実行だけでなく共有サーバープロセス自体をブロックする。
これは可用性のみへの影響であり(実行が固まるだけで、データの露出や改ざんは起きない)、そもそもフローの実行・編集権限(klaus ui ではセッショントークン)が前提になる。またキャプチャや --var の値をフローが意図的に regex に流し込んだ場合にのみ発生し、フローファイルに直書きしたリテラルパターンは影響を受けない。contains / equals はパターンを評価しないため、値の由来に関わらずこの影響を受けない。関連する regex タイムアウトのスコープ注記は SECURITY.md を参照。
if
steps:
- name: login
request: { method: POST, url: "{{env.baseUrl}}/login" }
assert: { status: 200 }
- name: cleanup
request: { method: DELETE, url: "{{env.baseUrl}}/session" }
if: steps.login.status == "passed" # login が passed のときだけ実行する
- name: use-token
request: { method: GET, url: "{{env.baseUrl}}/me", headers: { Authorization: "Bearer {{token}}" } }
if: captures.token != ""ifはステップを実行するかどうかのゲートになる。汎用の式言語ではなく、意図的に小さく留めた文法(src/core/condition.tsが評価する)を持つ:ref op literal(refはsteps.<name>.statusまたはcaptures.<name>、opは==/!=、literalはダブルクォート文字列・シングルクォート文字列・空白を含まないベアトークンのいずれか)stepName/captureNameに.や空白は含められない- クォート文字列はエスケープシーケンスに対応しない。値に反対側のクォート文字を含めたい場合は逆のクォート種別で囲む(例:
"を含めたいならシングルクォートで囲む) 'または"で始まるリテラルは、同じクォート文字で終端していなければならない。未終端のクォート(例:captures.token == "abc)はベアトークンとして黙って解釈されず、不正な式として拒否される(RuntimeError)- 比較は常に文字列として行う。
captures.<name>側の値は、テンプレート展開(例:{{token}})と同じ方法で文字列化する(オブジェクト・配列は JSON 文字列化、それ以外はString())。steps.<name>.statusは元々文字列
ifの中では{{...}}テンプレート展開は行われない。それまでのキャプチャは{{name}}ではなくcaptures.<name>で直接参照するsteps.<name>.statusは同じフロー内でそれより前にあり、かつ実行が完了しているステップのみ参照できる(continueOnErrorで継続したステップも、その実際のfailed/errorステータスが後続の条件式から見える。これがifとcontinueOnErrorを組み合わせる狙いそのもの — 例: setup が passed のときだけ後始末を実行する)- 条件式が false の場合、ステップは実行せず
skippedになる(リクエストは送られずretryも適用されない)。errorには"skipped because condition not met: <expression>"が入る。これはフローの残りステップをスキップしない — 後続ステップは通常どおり実行される(「前のステップの失敗」による skip とは別理由。ステップ失敗時のフロー挙動 を参照) - 条件式が例外を投げた場合(文法エラー、または未知のステップ/capture 名の参照)、そのステップは元のメッセージ(利用可能なステップ/capture 名一覧を含むことがある)とともに
errorになり、後述の通常のエラーセマンティクスに従う(continueOnErrorが無ければ残りステップは skipped になる) - それより前の未処理の失敗による
skipRest(後述)が優先する: フローが既に残りステップをスキップ中の場合、このステップのifはそもそも評価されない
retry
retry:
count: 3 # 必須。初回実行後のリトライ回数(1〜100)
intervalMs: 500 # 任意。試行間の固定待機時間(ミリ秒)。既定値は 1000countは初回実行「後」のリトライ回数なので、ステップは合計で最大count + 1回実行される- ステップの結果が
failed(アサーション失敗)またはerror(接続エラー・タイムアウト等の例外)になった場合にリトライする。passedになった時点でcountを使い切っていなくても即座にループを止める - 試行間の待機は
intervalMsによる固定値(バックオフや条件式は無い) request/sse/wsすべてのステップ種別に一律で適用される(ステップ全体、つまりリクエスト/レスポンスとアサーションをまとめて再実行する)- 記録されるのは最終試行のみ: ステップ結果・履歴エントリともに1件、
onStepStart/onStepCompleteもステップごとに1回ずつ呼ばれる。途中の failed / error な試行は保持されない retryを設定すると、結果と履歴エントリに実際に実行された試行回数(1 以上)を表すattemptsフィールドが付く。retry未設定時はattemptsは省略される。durationMsは従来どおり最終試行自体の所要時間のまま変わらない
continueOnError
steps:
- name: optional-check
request: { method: GET, url: "{{env.baseUrl}}/optional" }
assert: { status: 200 }
continueOnError: true # 任意。既定値は false
- name: next-step
request: { method: GET, url: "{{env.baseUrl}}/next" }- ステップに
continueOnError: trueを設定すると、そのステップの最終結果がfailedまたはerrorになっても以降のステップはスキップされず、次のステップから実行が継続される - 「最終結果」とは、
retryを併用している場合は retry を使い切った後を指す。retry は通常どおり先に実行され、リトライがすべて尽きて初めてcontinueOnErrorが効果を持つ - ステップ自体の状態は
failed/errorのまま変わらず、フロー(および run)全体の集約ステータスと exit code もこれまでどおり失敗として扱われる(詳細は CLI リファレンス) continueOnErrorが影響するのはそのステップ自身の失敗のみ。continueOnErrorを指定していない後続のステップが失敗した場合は、下記のとおりそれ以降のステップはスキップされる
ステップ失敗時のフロー挙動
- ステップが failed(アサーション失敗)または error(runtime エラー)になると、そのフローの残りステップは実行されず skipped(
error: "skipped because a previous step failed")として記録される。ただし失敗したステップにcontinueOnError: trueが設定されている場合は残りステップが実行される(continueOnError を参照) ifの条件式が false になったステップもskippedとして記録されるが、理由は別(error: "skipped because condition not met: <expression>")で、上記の「残りステップをスキップする」挙動は発生しない — いずれの場合もフローは次のステップから実行を続ける- 複数フローファイルを渡した場合、あるフローが失敗しても他のフローは実行される
- 最終 exit code は CLI リファレンス の優先ルールに従う
JSON Schema
フロー定義のスキーマは JSON Schema としても公開している。エディタの補完・バリデーションや、AI エージェントがフロー YAML を生成する際の参照に使える。
- 公開 URL:
https://almondoo.github.io/klaus/schema/flow.schema.json - npm パッケージ同梱パス:
node_modules/@almondoo/klaus/dist/schema/flow.schema.json
YAML ファイルの先頭に # yaml-language-server: $schema= コメントを書くと、対応エディタ(VS Code の YAML 拡張など)で補完・検証が効くようになる。
# yaml-language-server: $schema=https://almondoo.github.io/klaus/schema/flow.schema.json
name: 認証フロー
steps:
- name: login
request:
method: POST
url: "{{baseUrl}}/login"注意: request.body と request.graphql の排他、step.request / step.ws / step.use のどちらか一方が必須、ws.url のスキーム制約、ステップ名の一意性など、このページで説明した superRefine によるチェックは JSON Schema の構造そのものには表現されない(対象プロパティの description に注記としては含まれる)。これらは klaus validate / klaus run の実行時検証でのみ強制される。use: の参照解決(パス境界・循環参照・assert の加算マージなど)も同様に、スキーマ検証ではなく klaus validate / klaus run のロード時検証で行われる。