1. Rider のシグネチャ
createRider(config))、あらゆる config をクロージャに保持します。class でも singleton でもありません(このコードベースのスタイル)。
2. スケジューリング
Rider は 03 §6 で導出された DAG をたどります。ディスパッチ
ready な手順は並行実行され、
config.maxConcurrency によって上限が設定されます(launcher が選択し、local ではデフォルト = CPU-bound)。foreach
foreach を持つ手順は、配列に対して N 個の子呼び出しへ fan out し、それぞれが独立した context(c.item, c.index)を持ちます。それらの出力は配列として集約され、その手順の出力になります。子の並行数にも上限があります。決定論性の注記: 独立した手順は 並行 実行されますが、Journal は結果が commit される順序に従って effect の total order を記録し、
foreach の集約は完了時刻ではなく index によって行われます。この記録された順序は 表示上のもの です。リプレイと再開は、journal position ではなく step ids と冪等性キーのみを厳密に key とするため、2つの Run が並行手順を異なる順序で commit しても、同一にリプレイされます。並行性は記録された結果を変えず、wall-clock だけを変えます(tenet T2)。3. 手順のライフサイクル
各手順は小さな状態機械です。すべての遷移は Journal entry です。4. リトライ、タイムアウト、冪等性
- リトライポリシー は手順ごとに設定されます(
retry: { max, backoff: "exp"|"fixed", base_ms, jitter })。Tool のkindから導かれる妥当なデフォルトがあります(network tools は retry し、pure transforms は retry しません)。 - タイムアウト(
timeout_ms)は注入されたAbortSignalを通して手順を abort します。abort された手順は retryable failure です。 - 冪等性: 各手順呼び出しは、安定した 冪等性キー =
hash(run_id, step_id, inputs_digest)を持ちます。これは意図的に attempt number を含みません。そのため、同じ論理呼び出しのすべての retry は 同じ key を運びます。この安定性こそが目的です。外部 write を行う Tool(例:http.post)はこの key を受け取り、それを通過させることができます(例:Idempotency-Keyheader として)。これにより、retry された Run が二重発火しません。エンジンは、記録済み effect が再開時に再実行されないことを保証します(§6)。
5. 決定論性: 非決定性を許す場所
同じ入力と同じ記録済み effect が与えられたときに同じ出力を生成するなら、その Flow は決定論的です。Rider は、非決定性のすべての源を注入された capability に閉じ込めます。| 発生源 | Capability | 再現可能性を保つ方法 |
|---|---|---|
| 現在時刻 | clock | clock.now() は初回 read 時に記録され、replay は記録済み value を返す |
| 乱数 | random | run id から seed され、replay は同一に re-seed する |
| ネットワーク | net | request(secret-redacted、§5a)+ response が Journal に記録され、replay は記録済み response を返す |
| ブラウザー | browser | browser Tool 自体が決定論的(記録済み locators)であり、その reads は記録される |
| ファイルシステム | fs | reads は記録され、writes は冪等性によって key 付けされた effects になる |
| 環境 | env | allow-listed keys のみ、Run 開始時に snapshot される |
| シークレット | secrets | 各 Run で fresh に解決される(記録されない)。宣言した tools にのみ注入される |
transform)は pure function です。エンジンはそれを自由に再実行でき、記録する必要もありません。
5a. シークレットを漏らさずに effect を記録する
secret は expression layer を決して通過しないため(taint rule、01 §7)、secret value が materialize する唯一の場所は、解決する tool の 内部 です。そしてそこは、まさに effect を記録する layer でもあります。そのため recorder は、どの bytes が secret-derived かを把握し、それらを redact します。secret-bearing な header、query parameter、body field は stable placeholder({secret:<slot>})として journaled され、value は記録されません。hermetic replay では、記録済み response は redacted request shape と idempotency key によって key 付けされるため、secret は不要であり、Journal にも存在しません。effect が記録されても、boundary invariant は保たれます。
大きな body や binary body は inline ではなく、blob store に digest によって保存されます。Journal は digest を保持し、エンジンは必要に応じて body を stream します。設定可能な size cap により Journal は小さく保たれ、上限を超える payload は artifact reference になります。
6. 再開
Run は中断されることがあり(crash、deploy、SIGTERM、明示的な cancel)、その Journal から再開できます。
context を再構築する
step_succeeded entry と effect_recorded entry を replay して context を再構築します。完了済み手順は 記録済み出力 を返し、記録済み effect は 記録済み結果 を返します。外部のものは何も再発火しません。7. リプレイ
リプレイは再開の offline cousin であり、再現性の中核です(tenet T3)。2つの mode があり、決して混同されません。net、clock、random、fs-read、browser capability は 完全に Journal によって裏付けられます。外部 call は行われず、secret も不要です。記録済み effect は記録済み結果を返します。これにより、3つの具体的な力が得られます。
- 監査 — 何か月後でも、Run が何をしたかを決定論的に証明する。
- 回帰テスト — Journal を fixture として pin する。Tool upgrade によって behavior が変わると、
--verifyは失敗する。 - エアギャップ — FlowSpec + その Pack + 記録済み Journal を隔離環境へ移し、connectivity ゼロで結果を再現する。
--rerun mode は異なります。これは Journal から reads を replay しますが、effects は live に再実行 するため、http.post は実際にもう一度発火します。この mode には関連する secrets が必要です。secrets は実行時に再解決され、Journal から読まれることはありません。secrets は記録されないためです(§5a)。既知の開始点から live system に対して Flow を再駆動するには --rerun を使い、何にも触れない hermetic reproduction には通常の replay / --verify を使います。
8. エラー
エラーはDomainError lineage に従います(このコードベースのスタイルで class syntax が許される唯一の場所: Error subclasses)。それぞれが stable な code、問題のある step、(masked)inputs、Journal frame を持つため、failures は actionable であり、secret を漏らすこともありません。
9. エンジンが意図的に行わないこと
- エンジンは control flow を決めるためにモデルを呼びません。(そのような code path は存在しません。)
- エンジンは ambient globals に手を伸ばしません。注入された
EnvViewの外でDate.now()、Math.random()、process.envreads を行わず、barefetchも使いません。 - エンジンは secrets を永続化しません。
- エンジンは cloud SDK を import しません。storage/queue/transport は
RunDepsです。
Launcher
local、server、air-gapped environment をまたいで、同じ FlowSpec を実行・再現します。
Tool と Pack
ToolContracts がどのように解決され、呼び出され、portable Packs に bundle されるか。