AIREITER

書き直すべきか、Browser Bridgeを許容すべきか:リバースコードを着地させる3層の判断基準

最終更新日: 2026-07-31 07:19:23

リバースエンジニアリングの前半で行うのは、コードの機械的な分割、アルゴリズム系統の特定、差分テストによる検証だ。ここまで終えれば、「何を計算しているか」は理解できる。しかし、その結論だけでは出荷できない。ロジックはまだ元の実行環境に埋め込まれており、CIで回せる純粋関数にするのか、それとも誰かが面倒を見る外部プロセスとして残すのかを決める必要がある。この選択を誤ると、解析で短縮した時間を、運用で利息付きで返すことになる。

4段階の全体像では、この工程を「Stage 4、トランスポート層まで劣化させる」と一行で表した。この記事ではそこを掘り下げる。要点はシンプルだ。1層下がるごとに依存範囲、故障モード、デプロイの負担は桁違いに増える。だから、原則として上位層へ押し上げるために戦う。

実装の着地点は3層。順番は固定する

ロジックの着地先は、上から順にこの3つだけだ。「先に動いた方法」をその場で選ぶのではなく、この優先順位をチームの規約として明文化しておきたい。

  1. ネイティブ実装。対象言語で書き直し、元のランタイムから切り離す。依存先は標準ライブラリだけだ。前提となるのはアルゴリズム系統を正しく特定できていること。フィンガープリント特定の工程を通過していれば、90%は公開実装を移植でき、残りの差分点だけを個別に処理すればよい。最終的には純粋関数として収まる。

  2. 最小断片をローカルJSエンジンで動かす。短期的には純化のコストが高すぎるロジックもある。その場合は元のJSを必要最小限だけ残し、ページ全体ではなく、必要な数十行だけをローカルのNode/V8で実行する。

  3. パッシブBrowser Bridge。実際にログイン済みのページランタイムにしか存在しない状態がある。たとえば、実行時に配信される署名やセッションに紐づく動的IDだ。静的な再構成では再現できず、現時点ではブラウザ内から読むしかない。この方式は暫定措置であり、インターフェースの注記で明示し、デフォルト実装として展開してはならない。

層が下がると、コストは線形では増えない

この順序を固定すべき理由は、3層のコスト差が段階的ではないからだ。1層下がるたびに、ほぼ一桁ずつ重くなる。

層

依存範囲

故障モード

CIで実行可能か

ネイティブ実装

標準ライブラリのみ。外部プロセスは不要

出力不一致。assertひとつで特定できる

可能。純粋関数だから

ローカルJSエンジン

Nodeランタイムが1つ増える。V8コンテキストはスレッドセーフではなく、並行処理にはロックが要る

エンジンのバージョン差、断片が依存するグローバル変数の欠落

かろうじて可能。エンジンの導入が必要

パッシブBrowser Bridge

実Chrome、拡張機能、人が維持するセッション、ローカルのループバックチャネル

ページが開いていない、セッション切れ、構造変更、タブが閉じられた

不可能。生身の人間が必要

第1層の障害はユニットテストで捕捉できる。一方、第3層の障害は「今日はユーザーがそのタブを閉じていた」だ。本来なら純粋関数にできるものを第3層に落とすと、呼び出しのたびに人間を縛ることになる。目安として、アルゴリズム系統を特定できれば、難読化された署名SDKでも組み込みのcryptoだけに依存する600行未満の単体実装として第1層へ落とせることがある。Browser Bridgeでしか扱えないと思っていたものの多くは、単にアルゴリズムをまだ十分に特定できていないだけだ。

パッシブBridgeの境界線:スナップショット1回、能動操作はしない

パッシブBridgeが制御不能になるきっかけはひとつしかない。「手伝い始める」ことだ。自動更新、自動ログイン、ページ読み込みの自動待機。こうした「自動」を足すたび、単なる転送役からクローラへ近づいていく。だから境界は徹底的に狭く取る必要がある。以下の制約は、痛い目を見て学んだものだ。

取得はスナップショット1回だけ。ページは絶対に変更しない。すでに開かれているページについて、Cookie、セッション状態、ページランタイムをそれぞれ一度だけ確認する。タブを作らない、更新しない、遷移しない、フォーカスしない、ポーリングして待たない。ページがなければ、それはページがないということだ。ユーザーの代わりに開いてはいけない。

欠けているものがあれば、直ちに明示的なエラーを返す。該当タブがなければtab_unavailable、ページはあるが未ログインならnot_logged_in、ログイン済みでもランタイムが準備できていなければruntime_unavailableを返す。この3つのエラーコードは、それぞれ現実の状態と次の操作に対応する。ページを待つ、ログインする、対象を切り替える、のいずれかだ。呼び出し側に推測させる「失敗しました」という一括エラーにはしない。

機微な状態をブラウザの外へ出さない。拡張機能はcookiesやwebRequestの権限を要求しない。開かれている一致タブだけを照会し、そのページのコンテキスト内でリクエストを完了させ、返却前にフィールドを除去する。Cookieやページ上の署名状態がChromeの外へ一歩でも出ることはない。チャネルの接続先も、デフォルトではローカルのループバックアドレスにする。Bridgeが運ぶのは認証情報ではなく、結果だけだ。

プラットフォーム数の割にスコープが多い理由

パッシブBridgeが転送するのは、アダプタによってパス、パラメータ、refererまで制限されたホワイトリスト付きリクエストだ。最も直感に反するのは、その粒度だろう。対象プラットフォームは数えるほどなのに、スコープは合計15個ある。スコープは「プラットフォーム単位」ではなく、「ページコンテキスト単位」で切るからだ。TikTokだけを見ても、Creative Center、Top Ads、Creator platform、インフルエンサーライブラリ、Ads Managerは5つの独立したスコープになる。セッション状態もページランタイムも別々であり、広告バックエンドへログインしてもCreator platformのランタイムは使えない。プラットフォームごとに1スコープだけ切ると、「サブサイトAにはログイン済みだがサブサイトBには応答できない」という最初のケースですぐに破綻する。Xiaohongshuも、メインサイト、アプリ相当のパス、クリエイターマーケットプレイスで3スコープに分かれる。

スケジューリングの粒度も同じ考え方になる。ロックをかけるのはプラットフォームファミリー単位だけだ。同一ファミリー内、たとえばDouyinファミリーのリクエストは、同じ実タブを再利用するため直列に処理する。同じページコンテキストへ並行して投げると、互いに踏み荒らすからだ。異なるファミリー、たとえばDouyinとXiaohongshuは別々のタブなので並列に実行できる。ファミリー内では、さらに最小リクエスト間隔を設ける。粒度が粗すぎれば並列化できる処理まで直列化し、細かすぎれば同じタブを共有するリクエストが衝突する。ページランタイムを共有する範囲、つまりプラットフォームファミリーこそが自然な境界になる。

人が介入してよいのは、明示的なログイン引き継ぎだけ

パッシブBridgeはページを操作しない。ただし、セッションは切れる。その対処では、人間の介入を一度きりの明示的な操作へ閉じ込める。対話型コマンドが引き継ぎに入ると、プログラムはOS経由で対応する業務ページを開く。ユーザーが手動でログインし、ページの準備が整うのを待った後、元のリクエストを再実行する。この間も拡張機能はボタンを押さず、フォームを入力せず、Cookieをエクスポートしない。実ブラウザでログインするのはユーザー自身であり、プログラムは完了後にリクエストを再開するだけだ。

重要なのは、これを暗黙に発火させないことだ。非対話型コマンド、つまりCIやスケジュールタスクがブラウザを開くことはない。セッションエラーをそのまま返し、上位層に判断を委ねる。引き継ぎでは誤検知にも注意が必要だ。ログイン成功後、ランタイムに与える追加待機時間は短くする。実装では最大20秒であり、その後には必ず判定を出す。業務ページがすでに対象インターフェースのコンテキストを持たないアカウントページへリダイレクトされているなら、「まだ読み込み中」と誤認して無意味に待つのではなく、そのステージを即座に終了する。劣化を許容するフローなら、このステージをunavailableとして記録し、全体を失敗させずに先へ進める。

ステージの結果は「成功か失敗か」では足りない

ここまでの設計を支える共通原則がある。どのステージも「成功」か「失敗」だけを返してはならない。オーケストレーションパイプラインでは、各ステージの結果は6種類に分ける。completed(完了)、empty(実行したがデータなし)、ready(準備済みで送信待ち)、skipped(ルールにより意図的にスキップ)、unavailable(現時点では利用不可。通常はセッションが必要)、blocked(前提条件を満たしていない)だ。

「意図的にスキップした」「セッションが必要だ」「本当にデータが空だった」は、まったく別のシグナルである。不透明な結果をひとつ返すだけでは、その空が「空であるべきだった」のか、「セッションが切れているのに誰も気付かなかった」のか判断できない。これではパイプラインを運用できない。ステージ状態を有限のenumとしてモデル化しておけば、オーケストレーション層はスクリプトでもモデルでも、劣化させるか、再認証するか、中止するかを判断できる。パッシブBridgeの3つのエラーコードを、フロー全体のレベルまで引き上げた考え方だ。

機能をどの層に置くか、モデルに判断させる方法

モデルの出番はここだけでよい。役割も限定する。モデルに純化を代行させるのではなく、純化すべきか、どこまで進めるべきかを判断させる。これは署名クラックではなく、アーキテクチャ判断だ。中心となる問いはひとつ。依存する状態は静的に再構成できるのか、それとも実行時にしか取得できないのか。そのうえで、純化に必要な工数と変更頻度を比較する。工程ごとに、モデルへ求める能力は異なる。

工程

必要な能力

選択肢

model id

モジュール全体を読み、依存範囲を把握する

長いコンテキスト。呼び出しグラフを一度に読める

Kimi K3

kimi-k3

層の選択を両面から議論し、「まず動かすだけ」への反論を求める

強い推論力。純粋関数化のために追加で1日使う根拠を示せる

Claude Opus 5

claude-opus-5

数十〜数百機能の一次トリアージをまとめて行う

低コスト、高並行性

Claude Sonnet 5

claude-sonnet-5

劣化後の原因帰属

中程度の推論力。障害ログをもとに説明できる

GPT-5.6 Sol

gpt-5.6-sol

とくに重要なのは2番目だ。層を決めるときに最も起きやすい失敗は、「とにかく動かして」という調子にモデルが合わせてしまい、「Browser Bridgeが一番簡単です」と返すことだ。それは長期コストを計算しているのではなく、こちらに同調しているだけだ。推論力の高い層なら、こう反論できる。「この部分は標準ハッシュに定数による摂動が1つ加わっただけだ。純粋関数として着地させるために1日使う価値があり、Bridgeへ載せるべきではない」。鵜呑みにせず、実際に試してほしい。ロジックを3件選び、そのうち少なくとも1件は正しい配置をすでに把握しているものを対照として含める。同じ「配置案を出し、根拠を論じ、早すぎるBrowser Bridge化には反論してほしい」というプロンプトをclaude-opus-5とgpt-5.6-solへ与え、ひとつだけを見る。より上位の層へ引き上げようと戦うか、それとも安易に第3層を既定値にするかだ。

本当に厄介なのは、モデルを切り替えるコスト

3ベンダーから4つの層を使うなら、SDKは3つ、認証方式も3つ、エラー形式も3つになる。工程ごとにモデルを切り替えるためにクライアントを書き換えるのは割に合わない。その結果、多くの人はすべてを1モデルで処理し、最も推論力が必要な配置判断でさえ、ただ同調するモデルを使うことになる。

AIReiterはこの層を平坦化する。キーは1つ、インターフェースはOpenAI互換で、4つの層をその背後にまとめている。切り替えはリクエストボディのmodelフィールドを変えるだけだ。

# 配置を議論する場合:推論層
curl https://aireiter.com/api/v1/chat/completions \
  -H "Authorization: Bearer $AIREITER_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-opus-5",
    "messages": [{"role": "user", "content": "<配置判断用プロンプト + リバースしたロジック断片 + 依存関係リスト>"}]
  }'

# 一次トリアージをまとめて行う場合:フィールドを1つ変更
#   "model": "claude-sonnet-5"
# 劣化後の原因帰属:
#   "model": "gpt-5.6-sol"

すでにOpenAI SDKを使っているなら、base_urlをhttps://aireiter.com/api/v1に向ければよい。Anthropic SDKなら、同じキーでPOST /api/v1/messagesを呼ぶ。価格面では、Claudeモデルは定価から30%オフ、GPTモデルは半額だ。このワークフローのコストが集中するのは2か所ある。数十〜数百機能を対象にした大量の一次トリアージ(Sonnet、高い呼び出し数)と、モジュール全体を入力して依存範囲を読む長文コンテキスト処理(Kimi、1回あたりのトークン数が多い)だ。大量トリアージはClaudeモデルで動くため、最も密度の高い工程に割引がそのまま効く。推論層による配置判断もClaudeモデルであり、30%オフになる。長文コンテキスト向けのKimi K3も同じキーで利用できる。

  • APIキーを取得する

  • 登録せずに試す — まずはロジック断片を数件手で渡し、「Browser Bridgeに置くべきか」という問いに対して、2つのモデルが同調するのか反論するのかを見てほしい。

まとめ

リバースエンジニアリングしたロジックを着地させる作業は、技術の問題というよりコストの問題だ。ネイティブ実装 > ローカルJSエンジン > パッシブBrowser Bridgeという3層の順序は逆にできない。1層下がるたびに、純粋関数が外部依存を持つプロセスへ変わり、人の介入と生きたタブが必要になるからだ。パッシブBridgeは禁じ手ではない。ただし、厳しい境界を持つ暫定コンポーネントである。スナップショットは1回だけ、能動操作はしない。ページがなければ即座に明示的なエラーを返す。機微な状態をブラウザ外へ出さない。ログインは明示的な引き継ぎでのみ行う。ステージ状態は常に判読可能にする。この条件を守れば信頼できるつなぎになるが、ひとつでも落とせば、誰も保守したがらないブラックボックスになる。モデルは、機能をどの層に置くかを判断し、「とにかく動かすだけ」という慣性に抗うために使う。そして各リライトが正しいかを判定するのはモデルではなく、差分テストだ。