Write Once
MySQLのbinlogをパースするCDCエンジンを書いた。C++のコアにNode.jsのN-APIバインディングをかぶせて、npmで配布する。ただしN-APIはネイティブアドオンだから、linux-x64、darwin-arm64、win32-x64と、プラットフォームごとにバイナリをビルドして同梱しなければならない。GitHub Actionsのマトリクスビルドが走り、prebuildifyがバイナリを生成し、OSとアーキテクチャの数だけCIのパイプラインが増える。
WASMにしたかった。.wasmファイル1つですべてのプラットフォームに配布できる。マトリクスビルドも不要になる。コアのパーサー部分はWASM互換で設計してある。だがBinlogClientがTCPソケットを開き、スレッドを立て、ブロッキングI/Oでストリームを読む。WASMにソケットはない。配布を楽にしたかった技術が、入力を受け取れないという理由で使えなかった。
この夢は何度も見てきた。
Javaが最初に言い出した。Write Once, Run Anywhere。アプレットでブラウザに乗り込み、灰色の矩形と数十秒の起動時間を置き土産にして去った。Goはシングルバイナリで配布の悩みを消すはずだったが、CGOを使った瞬間にクロスコンパイルが壊れる。C#はMonoから.NET Core、MAUIへと何度も仕切り直して、まだ道半ばだ。WASMはその最新版で、WASIがソケットやスレッドの抽象化を進めているが、仕様はまだプレビューだ。
どの世代も「今度こそ」と言って、同じ壁にぶつかる。OSの違い、ランタイムの違い、I/Oモデルの違い。抽象化の層を一枚かぶせるたびに、その層が漏れる場所が変わるだけだ。
仮にWASMで配布できたとして、別の問題がある。.wasmも.nodeもバイナリだ。GitHubにソースコードがあっても、npm installで降ってくるバイナリがそのソースからビルドされた保証はない。手元で再現ビルドしようにも、CIの環境を完全に再現するのは現実的ではない。
2018年、npmのevent-streamにマルウェアが仕込まれた。メンテナーが信頼した第三者にリポジトリの権限を渡し、そこから悪意のあるコードが混入した事件だ。あれはJavaScriptだったからソースを読めば気づけた。バイナリで配布されるネイティブアドオンやWASMモジュールに同じことが起きたら、誰が検証できるのか。
SigstoreやSLSA、Artifact Attestationsといった仕組みは出てきている。だがnpm installのたびにビルドの出所証明を検証している開発者を、わたしは知らない。
「どこでも動く」を追い求めた先に、「何が動いているかわからない」が待っていた。
わたしのCDCエンジンは結局、6つのプラットフォーム向けにバイナリをビルドしている。npm provenanceもつけた。ビルドの出所をSigstoreで証明し、パッケージに署名を同梱している。正しいことをしている、はずだ。npm installするときにそれを確認している人がいるかは知らない。