Journal は、すべてのRunが生成する追記専用の記録です。各手順の出力と各外部効果が、digestによってcontent-addressedで保存されます。この記録は完全なので、同時に実行可能でもあります。Journalをエンジンに渡せば、ネットワークも、時計も、シークレットも、AIも介さずにRunを再現できます。だからJournalは、持ち運び可能で実行可能なテストfixtureになります。このページではその使い方を説明します。

要点: 「何か変わったか?」に1つのコマンドで答える

transform をリファクタリングする、Toolを更新する、Packのバージョンを差し替える。そのあとで、観測可能な振る舞いが変わったかどうかをエンジンに尋ねます。既知の良好なRunからJournalを記録しておけば、あとで --verify 付きでリプレイしたときに、何も変わっていなければ静かに通過し、出力が一致しなくなった最初の手順で明確に失敗します。
# record once, from a known-good run
browserflow run price.yaml --input query=headphones --record-journal ./fix/price.journal.json

# later, anywhere, offline — assert nothing changed
browserflow replay ./fix/price.journal.json --verify
これにより、「リファクタリングで何か変わったか?」を1コマンドの決定論的なチェックにでき、ライブの依存先なしでflowをCIでテストできます。このガイド全体では、標準的な daily-price-watch flowを使います。browser Tool search-price@1.2.0 が製品価格を読み取り、transform 手順がそれを整形し、条件付きの http.post がシークレットのwebhookに通知します。

Step 1 — Journalを記録する

任意の browserflow run--record-journal <path> を追加します。Runは通常どおり実行されます。browser Toolは実際のページを操作し、http.post は実際に発火します。そして、すべての読み取りと効果が、指定したJournalファイルに取り込まれます。
browserflow run flows/daily-price-watch.yaml \
  --input query="wireless headphones" \
  --record-journal ./fix/price.journal.json
ファイルに入るのは、content-addressedなイベントのストリームです。run_startedstep_startedstep_succeededoutput_digest を持つ)、effect_recordedresult_digest を持つ)、run_finished が、実行モデルで定義された形で記録されます。fixtureとしてコミットする前に、いくつかの性質を理解しておく価値があります。
  • シークレットは含まれません。 シークレット値は解決するToolの内部でだけ実体化し、recorderはリクエスト形状をJournalに書き込む前に、シークレット由来のバイト列を安定したプレースホルダー({secret:<slot>})にredactします。webhookの認証ヘッダーはfixtureに入りません。シークレットを漏らさず効果を記録するを参照してください。
  • 大きな本文はインラインではなくdigestで保存されるため、Journalは小さいままです。大きすぎるpayloadはartifact参照になります。
  • 生成されたartifactの既定の保存先は out/ です。 fixtureとして保持したいJournalをコミットするのは、たとえば ./fix/ 配下に置くような、意図的な選択です。偶然ツリーに入るものではありません。
fixtureにはflow名ではなく、固定したいシナリオに基づく名前を付けてください。price-on-sale.journal.jsonprice-no-notify.journal.json のような名前です。通常、1つのflowには、関心のある分岐ごとに複数のJournalが必要です。この例では、http.post が発火したrunと、when 条件によってスキップされたrunです。

Step 2 — hermeticにリプレイする

browserflow replay の既定は hermetic モードです。注入される netclockrandomfs-read、browser capabilityは、完全にJournalによってbackされます。外部呼び出しは一切行われず、シークレットも不要です。記録済みの効果は、単に記録済みの結果を返します。
browserflow replay ./fix/price.journal.json          # hermetic: reproduce, touch nothing
browserflow replay ./fix/price.journal.json --verify  # hermetic + assert every output matches
--verify を追加すると、エンジンはすべての手順出力が記録済みのdigestとまだ一致していることをassertします。Toolのアップグレード、transform の編集、依存関係の変更によって振る舞いがずれると、リプレイは最初に分岐した手順で明確に失敗します。pureなtransformは無料で再実行されてチェックされ、記録済みの読み取りはJournalから提供されてチェックされ、digestが一致しない瞬間にrunは停止します。どちらのhermetic形式も常に副作用がなく、promptも出さないため、どのpipelineにも安全に組み込めます。
Hermeticリプレイは、Journalが記録したものを再現します。ライブの世界には到達しませんsearch-price@1.2.0今日別の価格を返しても、hermeticな --verify はそれに気づきません。そして、それが目的です。これは凍結された現実に対してflowをテストし、世界の変化からあなたの変更を切り離します。ライブの振る舞いに対して確認したい場合は、下の --rerun を使います。

Hermeticと --rerun

2つのリプレイモードは決して混同されません。HermeticリプレイはJournalから再現し、何にも触れません。--rerun はJournalから読み取りをリプレイしますが、効果はライブで再実行し、実際のシステムに対してwriteを再発火します。
browserflow replay ./fix/price.journal.json --verify
  • 読み取りnetclockrandomfs-read、browser): Journalから提供されます。
  • 書き込み / 効果http.post): 発火しません。記録済みの結果が返されます。
  • シークレット: 不要です。Journalにも存在しません。
  • 外部呼び出し: どの種類もありません。
  • ネットワーク / 接続性: 不要です。完全にair-gappedで実行されます。
  • 副作用: ありません。promptも出ません。
  • 用途: runが何をしたかを監査する、リファクタリングを回帰テストする、隔離環境で再現する。
同じ対比を参照表にすると、次のようになります。
観点Hermetic(replay / --verify--rerun
読み取り(net/clock/random/fs-read/browser)JournalからJournalから
効果 / 書き込み(例: http.postJournalから返され、発火しないライブで再実行
外部呼び出しなしあり(効果に対して)
必要なシークレットなしあり。実行時に再解決され、Journalからは読まれない
接続性なし(air-gapped可能)必須
prompt / 確認なしrun と同じように効果を確認
--verify の出力assertionありn/a
--rerun でもシークレットが必要な理由: 記録済みのJournalは、すべてのシークレット由来のバイト列を {secret:<slot>} プレースホルダーにredactするため、値は本当に存在しません。writeを再発火するには、エンジンが設定済みのシークレットプロバイダーから実行時にシークレットを新しく再解決します。これは通常の run とまったく同じであり、resumeともまったく同じです。

Step 3 — JournalをCI回帰fixtureとして固定する

Hermeticリプレイに必要なのはJournalだけなので、理想的なCI gateになります。flowと一緒にJournalをコミットし、すべての変更で --verify によって静かな振る舞いのドリフトを防ぎます。
1

既知の良好なJournalを記録してコミットする

flowを一度現実に対して実行し、Journalを取り込み、意図的なfixtureとしてリポジトリにチェックインします(Journalの既定の出力先は、それ以外ではgitignoreされた out/ です)。
browserflow run flows/daily-price-watch.yaml \
  --input query="wireless headphones" \
  --record-journal ./fix/price.journal.json
git add ./fix/price.journal.json
2

すべてのpushで検証する

pipelineにhermeticな --verify を追加します。シークレットも、ネットワークも、browserも不要なので、どのmachineでも高速かつ同一に実行されます。
browserflow replay ./fix/price.journal.json --verify
3

失敗を読む

振る舞いが分岐すると、リプレイは非ゼロで終了し、最初に分岐した手順の名前を示します。daily-price-watchでは通常、shape(transform)または notifyhttp.post payload)です。browserflow inspect と組み合わせて問題のframeに直接移動し、回帰を修正するか、変更が意図したものであればfixtureを再記録します。
緑の --verify は期待ではなく証明です。同じ入力と同じ記録済み効果が与えられれば、flowは今も同じ出力を生成します。周辺のpipeline、つまりキャッシュ、pinされたTool、compile --check については、CIで実行するを参照してください。
browserflow replay は、<journal> ファイルパスまたはローカルストアの <run-id> のどちらも受け付けます。id形式は、このmachineですでに実行したrunをリプレイします。ファイル形式は、machine間で移動してコミットする持ち運び可能なfixtureです。どちらも --verify--rerun を同じように扱います。
browserflow replay $(browserflow runs last) --verify   # the run you just did
browserflow replay ./fix/price.journal.json      # the fixture you committed
step_succeeded イベントは output_digest を持ち、各 effect_recorded イベントは result_digest を持ちます。リプレイ時、エンジンは各手順の出力を再計算し、そのdigestを記録済みのdigestと比較します。pureな手順(capabilityを宣言しない transform)は最初から再実行されてチェックされます。capabilityにbackされた読み取りはJournalから提供され、そのdownstream出力がチェックされます。最初の不一致でrunは停止します。
いいえ。Hermeticリプレイは browser の読み取りをJournalから提供するため、flowが見るのは記録済みの価格です。Hermeticな --verify は、あなたのコード変更を世界の変化から切り離します。Webサイトではなく、flowをテストします。ライブサイトに対して確認したい場合は、効果を再発火しながら読み取りは引き続きJournalから提供する --rerun を使うか、単に新しい browserflow run を実行してください。
同じ仕組みを共有しますが、意図が異なります。Resume は、中断されたrunをそのfrontierから継続し、すでに記録済みのものは何も再発火しません。Replayはresumeのofflineな親戚です。監査、回帰、air-gappedな再現のために、完了済みrunを再現します。どちらでも、シークレットだけはJournalから決して読まれません。ライブで再解決されます。

関連

CIで実行する

compile --check とpinされたToolとともに、--verify をpipelineに組み込みます。

実行と再現性

Journal、決定論性、resume、2つのリプレイモードが実際にどのように機能するか。

CLIリファレンス

browserflow runbrowserflow replaybrowserflow inspect の完全なflagと例。

シークレット

--rerun でシークレットが再解決され、Journalには決して記録されない理由。