Chapter 1 / 3 ― 入門編

最小組込 ― WASM ロードと最初の write/read

SlimeTree-RLM の WASM を静的ホストから読込み、write() / read() / verify_audit_chain() を 1 つの HTML ファイルで動かすのがゴール。SharedArrayBuffer なし、single-thread。Chapter 2 で SAB を入れる前の足場固め。

1.1 配布形態 ― どこから WASM を入手するか

SlimeTree-RLM の WASM ポートは 3 つの配布形態で提供されます (ライセンス契約後)。

静的ファイル一式slimetree_rlm.js + slimetree_rlm_bg.wasm の 2 ファイル。任意の HTTPS 静的ホストに置けば動作。最小入門に推奨
npm パッケージ@javatel/slimetree-rlm (限定配布)。bundler (Vite/webpack/esbuild) 統合向け。import init from '@javatel/slimetree-rlm';
Rust crateslimetree-rlm ソース crate (NDA + ライセンス)。自前で WASM をビルドする中級編 (Chapter 2) で使用
本章では静的ファイル一式を使います。配布リクエスト前に手元で API を理解する場合は、後述のモック実装でも一通りの操作は試せます。

1.2 ファイル配置と HTTP server 要件

以下のディレクトリ構成を想定。HTTPS 必須は Chapter 2 以降 (SAB) ですが、本章でも localhost または HTTPS を強く推奨 (ブラウザ拡張機能との互換性のため)。

my-rlm-app/
├── index.html
├── slimetree_rlm.js       # 配布ファイル (ES module loader)
└── slimetree_rlm_bg.wasm  # 配布ファイル (WASM binary)

最小 HTTP server (Python):

cd my-rlm-app/
python3 -m http.server 8000
# → http://localhost:8000/

Node.js なら:

npx http-server -p 8000 --cors

1.3 最小サンプル ― 1 つの HTML で動かす

以下を index.html として保存し、上記 server で開く。

<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>SlimeTree-RLM 入門</title>
<style>
  body { font-family: system-ui; max-width: 720px; margin: 2rem auto; padding: 0 1rem; }
  pre  { background: #0d0d0d; color: #e0e0e0; padding: 0.8rem; border-radius: 6px; }
  button { padding: 0.6em 1.2em; margin: 0.3em; cursor: pointer; }
</style>
</head>
<body>
  <h1>SlimeTree-RLM 最小組込</h1>
  <p><button id="write">1. write 5 records</button>
     <button id="read">2. read all</button>
     <button id="verify">3. verify audit chain</button></p>
  <pre id="log">準備中...</pre>

  <script type="module">
    import init, { SlimeTreeRLM } from './slimetree_rlm.js';

    const log = document.getElementById('log');
    const print = (msg) => { log.textContent += '\n' + msg; };

    // 1. WASM モジュールの初期化 (非同期)
    await init();
    print('[ok] WASM 初期化完了');

    // 2. RLM インスタンスを生成 (single-thread, no-SAB)
    //    capacity = 1 MiB の linear memory を確保
    const rlm = new SlimeTreeRLM({
      capacity: 1024 * 1024,
      audit:    true,         // SHA-256 audit chain を有効化
      mode:     'in-memory',  // 永続化なし
    });
    print('[ok] RLM インスタンス生成 (capacity=1MiB)');

    // --- write ---
    document.getElementById('write').onclick = () => {
      const samples = [
        { key: 'fact:tokyo:capital_of_japan', payload: 'Tokyo is the capital of Japan' },
        { key: 'fact:fuji:height_m',         payload: '3776' },
        { key: 'fact:yen:currency_of_japan', payload: 'JPY' },
        { key: 'fact:tokyo:population_2024', payload: '13929280' },
        { key: 'fact:osaka:population_2024', payload: '2752412' },
      ];
      for (const s of samples) {
        const id = rlm.write({
          semantic_key: s.key,
          payload:      s.payload,
          source:       'tutorial_ch1_' + new Date().toISOString(),
        });
        print('[write] id=' + id + '  key=' + s.key);
      }
    };

    // --- read ---
    document.getElementById('read').onclick = () => {
      const keys = [
        'fact:tokyo:capital_of_japan',
        'fact:fuji:height_m',
        'fact:yen:currency_of_japan',
      ];
      for (const k of keys) {
        const rec = rlm.read(k);
        print('[read] ' + k + ' -> ' + (rec ? rec.payload : '(not found)'));
      }
    };

    // --- audit chain 検証 ---
    document.getElementById('verify').onclick = () => {
      const result = rlm.verify_audit_chain();
      print('[audit] verified=' + result.verified
            + '  records=' + result.record_count
            + '  head_hash=' + result.head_hash.slice(0, 16) + '...');
    };
  </script>
</body>
</html>

1.4 動作確認 ― 期待される出力

順番に 3 ボタンを押すと、以下のようなログが <pre> 内に出ます。

準備中...
[ok] WASM 初期化完了
[ok] RLM インスタンス生成 (capacity=1MiB)
[write] id=0  key=fact:tokyo:capital_of_japan
[write] id=1  key=fact:fuji:height_m
[write] id=2  key=fact:yen:currency_of_japan
[write] id=3  key=fact:tokyo:population_2024
[write] id=4  key=fact:osaka:population_2024
[read]  fact:tokyo:capital_of_japan -> Tokyo is the capital of Japan
[read]  fact:fuji:height_m         -> 3776
[read]  fact:yen:currency_of_japan -> JPY
[audit] verified=true  records=5  head_hash=a3f1d2e8b4c97f5e...

verified=true + head_hash がブラウザリロードで毎回同じになることが、SHA-256 chain が決定論的に動作している証拠です (同一入力 → 同一 chain head)。

1.5 API リファレンス (Chapter 1 で使う 3 つ)

new SlimeTreeRLM(options)

capacity整数 bytes。linear memory の上限。既定 1024 * 1024 (1 MiB)。超えると RlmCapacityError
audittrue で SHA-256 audit chain を有効化 (既定 true)。false にすると性能は上がるが Chapter 3 の監査機能が無効に
mode'in-memory' (既定) / 'persistent' (Chapter 3 で扱う)

rlm.write({ semantic_key, payload, source })

semantic_key文字列。意味駆動型の参照キー。fact:tokyo:capital_of_japan のような category:entity:attribute 命名を推奨
payload文字列 or Uint8Array。値本体
source文字列。由来 (LLM 出力なら model_id + prompt_hash 等)。audit chain に同時記録
戻り値整数 record_id。後の read_by_id() 等で使用

rlm.read(semantic_key) / rlm.verify_audit_chain()

read 戻り値{ id, semantic_key, payload, source, ts, prev_hash } または null (未登録時)
verify_audit_chain 戻り値{ verified: bool, record_count: int, head_hash: hex_string, broken_at: int|null }

1.6 ハマる点 ― 入門で詰まる 5 つ

(1) import 文で 404 / MIME type エラー

ローカルファイル (file://) では ES module が動かない。必ず HTTP server (http://localhost:8000) 経由で開く。

(2) await init() を忘れる

init() は WASM bytes の fetch + WebAssembly.instantiate を行う非同期処理。完了前に new SlimeTreeRLM を呼ぶと TypeError: undefined is not a constructor

(3) capacity 超過時の対応

1 MiB は約 5000〜20000 レコード (payload サイズ依存)。本番想定なら capacity: 64 * 1024 * 1024 (64 MiB) 程度を最初から指定。RlmCapacityError は try/catch で捕捉。

(4) ブラウザリロードで全データ消える

Chapter 1 は mode: 'in-memory' のため、ページ遷移/リロードで状態消失は仕様。永続化は Chapter 3 (OPFS / IndexedDB) で扱う。

(5) verify_audit_chainverified: false を返す

同一 RLM インスタンス内では発生しないが、永続化済みデータを別バージョンの WASM で読込んだ場合に発生しうる。broken_at フィールドで破壊された record_id を特定可能。

注意: Chapter 1 の構成は single-thread・single-RLM 専用です。WebWorker から SAB 越しに同じ RLM を共有する Chapter 3 のシナリオには そのままでは到達できません。次章で SAB と shared-memory WASM を導入します。

1.7 入門編はここで完了 ― 次章への橋渡し

Chapter 1 で以下が動くようになりました。

  • WASM モジュールの非同期初期化 (init())
  • RLM インスタンス生成 (new SlimeTreeRLM({...}))
  • 意味キー付き書込 (write())、参照 (read())
  • SHA-256 audit chain の決定論的検証 (verify_audit_chain())

しかし以下はまだ 不可能 です。

  • LLM 推論 Worker と main thread が 同じ RLM linear memory を同時操作
  • 監査 Worker が backend で audit chain を 並行検証
  • ページリロード後に 同じ audit chain を引き継ぐ

これらは SharedArrayBuffer + cross-origin isolation + shared-memory WASM が前提です。Chapter 2 で順に組み立てます。