コンテンツにスキップ

何を検査するか

dandori check は .flow と、それが使う規則を読みます。そして、すべての分岐、呼び出しが返しうるすべてのエラー、外部のサービスの側で起きうるすべてのイベントを考えに入れて、処理の流れを追います。診断には、そこへ至る短い実行の例が付きます。

終わっていない案件を残す

カードに与信を取ってチェックアウトの日に確定するホテルの予約 (tests/fixtures/hotel_naive.flow、例の最初の下書き)を --lang ja で検査すると、次の診断が出ます。

エラー[E020]: tests/fixtures/hotel_naive.flow:95:1: 案件 `pi` が requires_payment_method・processing のまま、ここでワークフローが終わることがあります(終わりの状態は succeeded・canceled)
    95 |   succeed outcome = stayed
  そうなる例:
      81  quote = hold(…)
      84  match quote.handling: auto
      84  create_intent: pi が requires_confirmation で始まる
      85  confirm_intent: pi が requires_confirmation → requires_capture
      90  match pi.status: requires_capture
      90  booking.check_out まで待つ
      93  capture_intent: pi が requires_capture → processing
          外部のサービスで `settle` が起きる: pi processing → requires_payment_method
      95  succeed

案件 pi は、ワークフローが状態を追いかける Stripe の PaymentIntent です。その遷移は payment_intent.rule に書いてあります。Stripe の文書から、PaymentIntent の状態の変わり方を rulec のステートマシンに転記したものです。ワークフローには、Stripe の側でひとりでに起きるイベントを external authenticate, settle, expire と宣言してあり、検査はそれが起きる場合もたどります。チェックアウトの日まで待つあいだに与信の有効期限が切れれば、確定の呼び出しは拒否されます。確定したあとでも、銀行の処理で支払いが通らないことがあります。

検査の項目

型と名前。 名前はどれも一度だけ宣言し、その型で使います。オプショナルな値は match で確かめてから使い、引数と出力はそろっていなければなりません(E001〜E006)。

分岐と文の置き場所。 match は、どの値にも分岐がなければならず(E010)、通ることのない分岐があってもいけません(E011)。変数を読めるのは、そこへ至るどの経路でも値が入っている場所だけです(E012)。yield、break、succeed、案件への呼び出しは、意味のある場所にしか書けません(E009)。

案件。 案件にタスクを呼べるのは案件を始めたあとだけで、始められるのは一度だけです(E013)。どの状態でも拒否されるイベントを送るのはエラーです(E021)。拒否されることがあるイベントを送るなら、拒否されたときの処理を書かなければなりません(E022)。ワークフローが終わるとき、始めた案件はどれも終わりの状態になっていなければなりません。例外は、fail … leaving で引き渡すときだけです(E020)。どこでも処理しないエラーで失敗して、案件が終わらないまま残ることもあります(W101)。これは on failure で片付けます。案件の状態での match が読むのは案件のレコードで、レコードはワークフローが最後に聞いた状態を言います。呼び出しが失敗しても、外部のサービスではイベントが起きていることがあり、そのとき案件はレコードより先へ進んでいます。検査はその両方を持っているので、match が絞るのはレコードだけで、案件がいるかもしれない状態は絞りません。そこでレコードが言うはずのない状態の分岐は、通ることのない分岐です(E011)。

日付と帳簿。 日付には、入力が受け取るものを渡します。日付か、カレンダーが UTC オフセットを言うなら時刻です(E003)。帳簿の操作をするタスクは、操作が受け取る引数を取り、仮押さえを返し、帳簿が拒否しうる理由だけをエラーに宣言します(E016、E007)。案件として追う仮押さえは、確定する前に期限が切れることがあり、そこで帳簿が拒否しうる理由は、どれも処理しなければなりません(E022)。日付と帳簿

リトライ。 外部のデータを変える呼び出しを冪等キー(key)なしでリトライすると、同じ変更を二度加えてしまうかもしれません(E030。変えるかもしれない、というだけなら W030)。案件を始めるタスクに key が無いのは警告です(W103)。

範囲。 規則、タスク、レコード、出力に渡す値は、そこの範囲に収まっていなければなりません(E014)。範囲の分からない値を渡すと警告になります(W104)。範囲

規則の前提。 規則には、入力の型だけでは書けない前提があります。ある入力が別の入力を超えないこと(constraint asked <= paid)や、日付が koyomi の日付のとる日のどれかであること(range from koyomi)です。ritsu check は、呼び出しが渡しうる値でその前提が保たれるかを確かめ、破る値があれば例を添えて E201 に、決められなければ W201 にします。決められない前提は、ritsu dandori build が書くコードが、ワークフローを走らせたときに、値ができたところですぐに確かめます。ふつうはその値を返したタスクの直後で、呼び出しまでにほかへ進むことがあれば、もうほかへ進まないところまで下げます。前提を破る実行は、どのプラットフォームでも、そこで Dandori.BrokenPrecondition で失敗します。

タスクが呼ぶもの。 ほかの .flow を走らせるタスクは子と(E015)、記述のある API を呼ぶタスクはその記述と(E016)照らし合わせます。タスクが呼ぶもの

Jev のタスクは、結果の型が Jev に答えられるものか(E007)、確信度を使うならモデルをバージョンで書いているか(W032)を確かめます。Jev

実装するサービス。 入口を proto のサービスとして書いたワークフローは、そのサービスと照らし合わせます。入力と出力が実行を始めるメソッドのリクエストとレスポンスに合うか、失敗の名前がすべて並んでいるか、サービスに書いたイベントとコールバックをフローが待っていて、送られてくる値を読めるかを確かめます(E017)。サービスを実装する

秘密の値と鍵。 契約が秘密と印を付けた値(.proto の debug_redact、OpenAPI の x-data-classification・x-sensitive-data・format: password、.flow の secret)が、入力・出力・結果・引数・fail の理由として実行の履歴に残ること(W904)と、タスクが discloses と書いていないのにプロジェクトの外へ送ること(E906)を言います。.flow に書いた鍵(W901)と、ほかのマシンへの暗号化しない HTTP(W902)は、行や URL に理由が書いてなければ警告になります。秘密の値

プラットフォーム。 プラットフォームごとに違うことは、dandori build が確かめます。一回の実行がプラットフォームの上限を超えうるか(E040)と、プラットフォームに要るものが欠けていないか、プラットフォームにできないことをしていないか(E050)です。

一回の実行の大きさ

一回の実行の大きさには、プラットフォームごとに上限があります。実行履歴は、Step Functions で 25,000 件、Temporal で 51,200 件、Lambda durable functions で 3,000 操作までです。Argo Workflows では Workflow のオブジェクトがすべてのノードを抱えるので、dandori は 10,000 ノードを目安にしています。ループには回数の上限を書き(repeat at most 12 times、for x in xs at most 50)、再帰も無いので、ビルドは一回の実行履歴が最も長くなる場合を数えられます。数え方は多めなので、上限に近いワークフローは、実際には収まる場合でもエラーになることがあります。

Temporal では、フローの一番外のループは、履歴が長くなったところで新しい実行に引き継ぎます(Continue-As-New)。そのため、そのループは引き継ぐまでの分だけを数えます。それでも上限を超えるのは、ループの外の部分か、ループの一回分が大きすぎるときです。診断は、そのどちらなのかを示します。

診断コードの一覧

診断コードに、35 種類すべてと、それぞれが何を見つけるかをまとめています。