• 日本語
  • はじめての再現を書く

    // GUIDE · 再現を書く

    ブラウザだけで動く Layer 1 レシピを 1 つ書く。

    既存レシピをコピーしてスラッグと再現スクリプトを差し替えるだけで、Vivarium 互換の再現ページが手元で動く。完成したらアップストリームへ PR を送るか、自分のフォークに置き続けるかを選べる。

    // 0 · このガイドが対象にするもの

    Layer 1(ブラウザ内 WASM)の最小レシピ。

    Layer 1 は Pyodide、 Ruby.wasm、php-wasm、 Rust の wasm32-wasip1 ターゲットなどで上流コードを ブラウザに持ち込み、ページが開いた瞬間に再現スクリプトを走らせて verdict を返す層です。インストール不要・サーバー不要なので、 Vivarium に最初に追加するレシピとして最もコストが低いです。

    Layer 2(Docker)の役割は アーキテクチャ に役割が書かれていますが、書き始めるならまず Layer 1 一択です。

    このガイドはレシピを書くところまでを対象にします。 その先「どこに公開するか」は 2 分岐で、 自分のフォークで動かす または上流リポジトリへの PR、のどちらかです。

    // 1 · 再現したいバグを決める

    スラッグは <project>-<issue> 形式。

    上流 Issue がある場合は <project>-<issue> (例: cpython-137205)が標準。 ない場合は説明的なケバブケース(例: bash-local-shadows-exit)。 これがディレクトリ名でもあり、Manifest v1 の slug でもあります。

    バグを選ぶときは、Layer 1 で完結するか確認するのがコツです:

    • 外部 I/O(ネットワーク、ファイル、サブプロセス)を踏まない。
    • verdict が 1 つの真偽値に落ちる(例: 「2 つの値が等しいか」)。
    • 必要なライブラリが対象ランタイムの WASM ビルドに乗っている(Pyodide / Ruby.wasm / php-wasm の同梱パッケージ一覧)。

    // 2 · クローンして雛形をコピーする

    一番近い既存レシピを丸ごとコピーするのが最短。

    01
    リポジトリを用意する。

    アップストリームに PR したいなら aletheia-works/vivarium を直接クローン、自分のフォークに置きたいなら フォーク手順 でフォークしてからクローンします。

    02
    mise でツールを揃える。

    mise install でリポジトリ直下から bun など必要なツールが入ります。Layer 1 用には cd src/layer1_wasm 配下で bun install も走らせてください。

    03
    一番近い既存レシピをコピーする。

    言語が同じレシピを丸ごとコピーするのが楽です。Python なら src/layer1_wasm/cpython-137205/、Ruby なら ruby-21709/、PHP なら <php-slug>/、Rust なら <rust-slug>/。cp -r src/layer1_wasm/cpython-137205 src/layer1_wasm/<あなたの slug> でディレクトリごと複製。

    // 3 · index.html を書き換える

    差し替えるのは title、見出し、lede の 3 か所。

    コピーした index.html の構造はそのまま使い、以下を上流バグに合わせて差し替えます:

    • <title> — タブに出るタイトル(例: Vivarium · Reproducing <project>#<issue>)。
    • <h1> — 見出し。上流 Issue へのリンクを含めます。
    • <p class="lede"> — 1〜2 文でバグを説明する lede。

    触ってはいけないもの: <meta name="vivarium-contract" content="v1">、 #verdict 要素、../_shared/style.css へのリンク。 これらが Contract v1 を構成しているので、外すと verdict が機械的に読めなくなります。

    Contract v1 の正典は Contract v1 にあります。 data-verdict の取りうる値、VIVARIUM_RESULT envelope のスキーマは spec ページが正です。

    // 4 · repro.ts を書き換える

    再現ロジックだけ差し替える。helper はそのまま。

    コピー元の repro.ts は ../_shared/verdict.js の verdict helper と、Python レシピなら ../_shared/pyodide-worker-client.js の startPyodideWorker を import している部分が共通骨格です (Ruby / PHP / Rust はそれぞれ ruby_loader.js / php_loader.js / rust_loader.js)。 ここはそのまま残して、再現コード本体だけ差し替えます。

    ページを読めるものにするための規則が 2 つあり、どちらも重要です:

    • 普通のコードとして書き、起きたことを print する。 出力ペインにはスクリプトの実 stdout がそのまま出るので、訪問者はなぜその答えになるのかを読めます。ページに拾わせるための裸の式でスクリプトを終わらせないこと — 誰も読みません。
    • 機械可読な値は result という名前のグローバルに残す(行の並びなら results)。verdict と Contract v1 envelope はここから組み立てられます。
    const REPRO_CODE = `
    import pandas as pd
    # ↑ 上流バグを表す最小コード
    
    left = ...   # 比較する値 1
    right = ...  # 比較する値 2
    mismatch = left != right
    
    result = {
      "left": left,
      "right": right,
      "mismatch": mismatch,
    }
    
    print("left ".ljust(10) + str(left))
    print("right".ljust(10) + str(right))
    print()
    if mismatch:
      print("The two disagree.")
    else:
      print("The two agree.")
    `.trim();
    

    driver は毎回の実行前に result を削除します。訪問者がブラウザ上でスクリプトを編集して代入を消した場合、前回の答えではなく unreproduced になります。

    verdict 設定は helper が面倒を見ます: 比較が true(バグが再現した)なら setVerdict("reproduced", "...")、そうでなければ setVerdict("unreproduced", "...")。 setResult({ contract: "v1", bug, runtime, result, timing }) で VIVARIUM_RESULT envelope も書き出されます。

    Pyodide は全 Python レシピが共有する Web Worker で動くので、起動が ページを固めることはありません。worker ファイルを書く必要もありません。 startPyodideWorker がそれを起動し、run / evaluate / install を返します。

    // 5 · ローカルで動かす

    bun run build → 静的サーバ → ブラウザで開く。

    # src/layer1_wasm/ から
    bun install
    bun run build         # repro.ts → repro.js を生成
    
    # レシピディレクトリを直接サーブ
    python -m http.server -d src/layer1_wasm/<あなたの slug> 8765
    # ブラウザで http://localhost:8765/ を開く
    

    ページが開き、上流ライブラリが CDN からロードされ、 verdict バッジが pending → reproduced / unreproduced のどちらかに落ちます。 DevTools コンソールで VIVARIUM_VERDICT と VIVARIUM_RESULT も同じ値が入っていれば、 Contract v1 上は OK です。

    Pyodide / Ruby.wasm / php-wasm はいずれも SharedArrayBuffer を使わない構成にしてあるので、COOP/COEP ヘッダは不要です。 普通の静的サーバ(python -m http.server や bunx serve)で動きます。

    // 6 · ネイティブ再検証(任意)

    ブラウザ外でも同じバグが出ることを 1 行で確かめる。

    Pyodide / Ruby.wasm 経由で観測できたバグが、本物の CPython / MRI Ruby でも同じく再現することを示すと、レシピの説得力が上がります。 Python なら uv + PEP 723 inline metadata で 1 ファイル完結に書けます:

    # /// script
    # requires-python = ">=3.14"
    # dependencies = ["pandas==2.3.3"]
    # ///
    """… どんなバグで、このファイルをどう走らせるか …"""
    
    # ← ここの本体は REPRO_CODE と 1 文字も違わない同じテキスト
    
    if mismatch:
      print("verdict=reproduced — …", file=sys.stderr)
      sys.exit(0)
    else:
      print("verdict=unreproduced — …", file=sys.stderr)
      sys.exit(1)
    

    本体は REPRO_CODE と同一に保ちます。 追加してよいのは PEP 723 ヘッダ、docstring、verdict= 行と exit code だけです。 これがあって初めて native 実行はブラウザ側の検算になり、似ているだけの別スクリプトになりません。 native ファイルにリポジトリの formatter をかけ、その結果を文字列リテラル側にも反映してください。 さもないと次の autofix で 2 つが乖離します。

    mise exec uv -- uv run repro.py で ephemeral venv が組まれて走ります。他の言語も考え方は同じですが、仕組みは共有していません:

    • Ruby — インタプリタがそのまま走らせる repro.rb で、PEP 723 に相当するものはありません。 mise exec ruby ではなく mise run repro:native:ruby で実行してください。 バージョンのピンがタスク側にあるためです。 ruby-21709 を参照。
    • Rust — コンパニオンファイルはありません。repro.wasm にコンパイルされる crate そのものが native CLI なので、 mise run repro:native:rust はページが走らせるのと同じ src/repro.rs を実行します。 <rust-slug> を参照。

    mise run repro:native で全部まとめて走ります。 php-wasm のレシピはまだ書かれていないので、雛形にする repro.php は存在しません。 _shared/php_loader.ts は最初の 1 本を書く人のために用意されています。

    // 7 · recipes.json を再生成

    ギャラリーに自分のレシピを並べる。

    ギャラリー(/repro/)と MCP サーバが読む recipes.json は、ビルド時に src/layer*/ をスキャンして自動生成されます。 手元での確認用には:

    mise run recipes:index
    # あるいは
    cd docs && bun run generate
    

    生成された docs/site/public/api/recipes.json に 自分のスラッグが入っていれば成功です。 docs サイトをローカルで動かすと、ギャラリーにカードが現れます:

    cd docs && bun run dev
    # http://localhost:3000/vivarium/ja/repro/

    // 8 · どこに公開するか

    上流 PR か、自分のフォークか。

    01
    アップストリーム カタログに contribute する。

    aletheia-works/vivarium 上で feat(layer1): add <slug> reproduction のような Conventional Commits で PR を立てます。レビュー基準は既存レシピを見れば分かりますが、最低限 Contract v1 に従っていれば CI が緑になります。

    02
    自分のフォークに置く。

    レシピを自分の vivarium フォークの main に push し、フォーク手順 の deploy 設定を有効化すると、自分の <you>.github.io/vivarium/ に並びます。共有は URL を渡すだけ。

    03
    自分のリポジトリに統合する。

    自分のプロジェクトリポジトリ側で「このレシピを CI で監視する」だけしたい場合は、レシピを書く必要はありません。自分のリポジトリに統合する に Manifest v1 と consumer-workflow.yml を使う最短手順があります。

    手順のどこかで詰まったら、それは write-your-first-reproduction のバグです。詰まったステップ番号を Issue に残してください。

    // NEXT

    用語集を読む →

    ここで出てきた slug / verdict / contract / Layer といった単語は、用語集に固定的な意味でまとめてあります。

    VIVARIUM は ALETHEIA-WORKS の一部 · GitHub でソースを見る →