{
  "lang": "ja",
  "ページ": "https://x402-catalog-history.ai-business-surface.workers.dev/ja/x402-schema-change",
  "title": "x402 schema change — 差分で見えるのは402の3面まで、6回のGETは全て同一SHA（実測）",
  "h1": "x402 schema change：変更検知で比べられる3つの面と、比べられない1つの面 — 無課金GETの実測",
  "description": "x402 の変更検知で無料で比べられるのは、402本文の V1 accepts（10項目）、本文に埋め込まれた payment_required_v2（7項目）、payment-required ヘッダ（7項目）の3面です。当方の /compat-check・/changes・/contract を各2回、支払わずに GET すると、本文は経路ごとに2回とも同一 SHA-256（31,827 B／24,742 B／31,342 B）、ヘッダは 5,728 B／2,804 B／5,528 B で、3経路とも 0.02 USDC を V1 は maxAmountRequired=20000、V2 は amount=20000 と別名で宣言していました。無料の /contract/sample は 200・2,779 B で、弱点は error_shape_undeclared の1件 — 「失敗時に何が返るかは402から読めない」と自分で宣言しています。入力検証は URL 不正だけが課金前の 400（271 B）で、period=nope は 402 のまま同じ本文でした。",
  "検索語": "x402 schema change",
  "読者の問い": "x402 の API が変わったとき、払わずにどこまで差分で検知できるのか。402 の本文とヘッダは何項目を比べればよく、どこから先は有料本文を買わないと分からないのか。",
  "実測日": "2026-10-20 JST",
  "実測": {
    "caption": "x402 schema change を無課金GETで測った範囲（2026-10-20 JST・各2回）",
    "columns": [
      "面",
      "応答",
      "大きさ",
      "応答時間",
      "読み取れた数字"
    ],
    "rows": [
      [
        "/compat-check（未払い）",
        "402 ×2",
        "31,827 B",
        "744 / 751 ms",
        "0.02 USDC・V1 10項目・ヘッダ 5,728 B"
      ],
      [
        "/changes（未払い）",
        "402 ×2",
        "24,742 B",
        "203 / 229 ms",
        "0.02 USDC・V1 10項目・ヘッダ 2,804 B"
      ],
      [
        "/contract（未払い）",
        "402 ×2",
        "31,342 B",
        "222 / 237 ms",
        "0.02 USDC・V1 10項目・ヘッダ 5,528 B"
      ],
      [
        "/contract/sample",
        "200",
        "2,779 B",
        "184 ms",
        "弱点1件 error_shape_undeclared"
      ],
      [
        "/compat-check?url=not-a-url",
        "400",
        "271 B",
        "172 ms",
        "課金前に拒否"
      ],
      [
        "/compat-check?period=nope",
        "402",
        "31,827 B",
        "227 ms",
        "有効時と同一本文（検証されない）"
      ],
      [
        "/openapi.json",
        "200",
        "240,758 B",
        "184 ms",
        "82 paths"
      ],
      [
        "/status.json",
        "200",
        "27,576 B",
        "9,477 ms",
        "72経路・/changes は納品0件"
      ]
    ],
    "note": "応答時間は各1回の実測で、時間帯により変動します。402本文の V1 accepts は scheme=exact / network=base / maxAmountRequired=20000 / maxTimeoutSeconds=60。同じ 0.02 USDC を V2 側は amount=20000 と表記します。200 の有料本文は取得していません（支払っていない）。"
  },
  "見出し": [
    "結論",
    "実測",
    "402は3面ある",
    "差分が意味を持つ条件",
    "無料サンプルが認める弱点",
    "課金前に止まる入力・止まらない入力",
    "変更検知の手順（支払わずにできる上限）",
    "できないこと・未確認",
    "次の一歩"
  ],
  "本文": [
    {
      "見出し": "結論",
      "html": "<p><strong>x402 の変更検知で、払わずに比べられるのは 402 の3面だけです。</strong>V1 の <code>accepts</code>（10項目）、本文に埋め込まれた <code>payment_required_v2</code>（7項目）、<code>payment-required</code> ヘッダ（7項目）。当方の3経路を各2回 GET すると、<strong>本文は経路ごとに2回とも同一 SHA-256</strong> で、差分を取ってもノイズにならないことを確認しました。</p><p>逆に<strong>「200 が返すデータの形」は 402 からは見えません</strong>。無料の <code>/contract/sample</code> 自身が弱点として <code>error_shape_undeclared</code>（失敗時に何が返るか宣言が無い）を1件返しており、これは<strong>x402 の schema change 検知の限界を、売り手の側がサンプルで認めている形</strong>です。</p><p>実務では<strong>①V1本文・②V2本文・③V2ヘッダの3面を正規化して比べ、④無料サンプルの weakness を毎回取り、⑤入力検証の 400 境界だけを課金前のテストにする</strong>のが、支払わずにできる上限になります。</p>"
    },
    {
      "見出し": "実測",
      "html": "<table>\n<thead><tr><th>面</th><th>応答</th><th>大きさ</th><th>応答時間</th><th>読み取れた数字</th></tr></thead>\n<tbody>\n<tr><td><code>/compat-check</code>（未払い）</td><td>402 ×2</td><td>31,827 B</td><td>744 / 751 ms</td><td>0.02 USDC・ヘッダ 5,728 B</td></tr>\n<tr><td><code>/changes</code>（未払い）</td><td>402 ×2</td><td>24,742 B</td><td>203 / 229 ms</td><td>0.02 USDC・ヘッダ 2,804 B</td></tr>\n<tr><td><code>/contract</code>（未払い）</td><td>402 ×2</td><td>31,342 B</td><td>222 / 237 ms</td><td>0.02 USDC・ヘッダ 5,528 B</td></tr>\n<tr><td><code>/contract/sample</code></td><td>200</td><td>2,779 B</td><td>184 ms</td><td>弱点1件 <code>error_shape_undeclared</code></td></tr>\n<tr><td><code>/compat-check?url=not-a-url</code></td><td>400</td><td>271 B</td><td>172 ms</td><td>課金前に拒否</td></tr>\n<tr><td><code>/compat-check?period=nope</code></td><td>402</td><td>31,827 B</td><td>227 ms</td><td>有効時と同一本文</td></tr>\n<tr><td><code>/openapi.json</code></td><td>200</td><td>240,758 B</td><td>184 ms</td><td>82 paths</td></tr>\n<tr><td><code>/status.json</code></td><td>200</td><td>27,576 B</td><td>9,477 ms</td><td>72経路・/changes は納品0件</td></tr>\n</tbody>\n</table>\n<p>3経路の本文を合計すると <strong>87,911 B</strong>、<code>payment-required</code> ヘッダは合計 <strong>14,060 B</strong>。どれも<strong>支払い0・課金0</strong>で読めています。再現性は本文 SHA-256 の一致で確認しました（<code>c202124f…</code>／<code>6894030a…</code>／<code>7c98b772…</code>）。</p>"
    },
    {
      "見出し": "402は3面ある",
      "html": "<p>同じ 402 の中に、同じ価格を表すスキーマが3つ入っています。1つ目は本文の V1 形式です。</p>\n<pre><code>accepts[0] の10項目:\nscheme, network, maxAmountRequired, resource, description,\nmimeType, payTo, maxTimeoutSeconds, asset, extra</code></pre>\n<p>2つ目は本文に埋め込まれた <code>payment_required_v2</code>、3つ目は <code>payment-required</code> ヘッダ（Base64 の JSON）です。後者2つは<strong>同じ5項目の外枠・同じ7項目の accepts</strong> を持ち、価格は <code>maxAmountRequired</code> ではなく <code>amount</code> です。</p>\n<pre><code>V2（本文 payment_required_v2 とヘッダで共通）:\nx402Version, error, resource, accepts, extensions\naccepts[0]: scheme, network, amount, asset, payTo, maxTimeoutSeconds, extra</code></pre>\n<p>3経路とも <code>x402Version=1</code>（V1本文）で、V2 側の <code>amount=20000</code> は V1 の <code>maxAmountRequired=20000</code> と<strong>同額</strong>。つまり<strong>「金額が変わった」を検知したいなら、V1 と V2 の両方を見て、片方だけ更新されていないかを確かめる必要がある</strong>ということです。片方だけ 0.02 → 0.03 に変われば、クライアントによって請求額が食い違います。</p>"
    },
    {
      "見出し": "差分が意味を持つ条件",
      "html": "<p>変更検知の前提は<strong>「同じ入力なら同じ出力」</strong>です。3経路を各2回、続けて GET した結果は次の通りでした。</p>\n<ul>\n<li><code>/compat-check</code>：31,827 B・SHA-256 <code>c202124f0407eaa6…</code> が2回とも一致。</li>\n<li><code>/changes</code>：24,742 B・<code>6894030ac9ff4f57…</code> が2回とも一致。</li>\n<li><code>/contract</code>：31,342 B・<code>7c98b772de77e1ec…</code> が2回とも一致。</li>\n</ul>\n<p><strong>6回の GET で本文のバイト数も SHA もぶれませんでした。</strong>これは、402 の本文に時刻・nonce・リクエストID が入っていないためで、<strong>バイト単位の差分がそのまま「宣言の変更」として読める</strong>ことを意味します。逆に、nonce や expires を含む実装では同じ方法が使えず、JSON のキー単位に正規化して比べる必要があります。</p>\n<p>正規化して比べるべき最小の集合は、<strong>V1 の10キー・V2 の7キー・トップレベルの11キー</strong>です。今回の3経路は<strong>トップレベル11キーが完全に同一</strong>（<code>price</code>, <code>what_you_get</code>, <code>try_before_you_pay</code>, <code>x402Version</code>, <code>error</code>, <code>accepts</code>, <code>resource</code>, <code>extensions</code>, <code>payment_required_v2</code>, <code>receive</code>, <code>terms</code>）で、違いは <code>description</code> の長さ（498〜1,835文字）と本文サイズだけでした。</p>"
    },
    {
      "見出し": "無料サンプルが認める弱点",
      "html": "<p><code>/contract/sample</code> は <strong>200・2,779 B・184 ms</strong> で、<code>weakness</code> を<strong>1件</strong>返しました。</p>\n<pre><code>code: error_shape_undeclared\ndetail: No error/failure shape is declared: what a failed paid call\n        returns cannot be read from the 402.</code></pre>\n<p>これは<strong>「402 からは失敗時の形が読めない」という、変更検知の限界そのもの</strong>です。成功時のスキーマをどれだけ差分で追っても、<strong>課金後に 500 が返ったときの本文</strong>は 402 には宣言されていません。買い手が「壊れた」と気づくのは、払った後の 200/500 を見てからになります。</p>\n<p>さらに、この弱点コードは<strong>前日（2026-10-19）の記録では <code>declared_fields_are_schema_keywords</code> でした</strong>。無料サンプルの出力が<strong>1日で別の弱点に変わっている</strong>ので、<strong>サンプル自体も差分の対象</strong>です。「前回と同じはず」と決め打ちせず、毎回 <code>fetched_at</code> と <code>weakness</code> を取るべきです。</p>"
    },
    {
      "見出し": "課金前に止まる入力・止まらない入力",
      "html": "<p>変更検知のテストを課金前に走らせるとき、<strong>どこまでが無料で拒否されるか</strong>が効きます。実測では次の非対称がありました。</p>\n<ul>\n<li><code>/compat-check?url=not-a-url</code> は <strong>400・271 B・172 ms</strong> で、402 を返す前に止まります。URL の構文・内部アドレス・ポート・長さの検査は<strong>課金前</strong>です。</li>\n<li><code>/compat-check?period=nope</code> は <strong>402・31,827 B</strong> で、<strong>有効時と同一の本文</strong>でした。<code>period</code> の意味の検証は<strong>課金前ではありません</strong>。</li>\n</ul>\n<p>つまり<strong>「不正な入力を投げて 400 が返るか」を課金前の健全性テストに使えるのは、実装が明示的に検証している項目だけ</strong>です。すべてのパラメータが 400 になるわけではなく、<strong>402 が返った＝入力が正しい、ではありません</strong>。テスト設計では「400 になるはずの入力」を実装の説明から選び、<strong>402 が返ったら「未検証」として記録</strong>するのが安全です。</p>"
    },
    {
      "見出し": "変更検知の手順（支払わずにできる上限）",
      "html": "<ol>\n<li><strong>3面を保存する</strong>：未払い GET の本文（V1 <code>accepts</code> と <code>payment_required_v2</code>）と <code>payment-required</code> ヘッダを、経路ごとに保存。ヘッダは Base64 を復号して JSON にする。</li>\n<li><strong>同じ入力で2回取る</strong>：SHA が一致することを確認。一致しなければ nonce 等を除いてキー単位で比較する。今回は3経路とも2回一致でした。</li>\n<li><strong>キー集合を比べる</strong>：V1 10キー・V2 7キー・トップレベル11キーの<strong>増減</strong>を見る。値の変更より<strong>キーの消失</strong>が壊れやすい。</li>\n<li><strong>金額を両表記で突き合わせる</strong>：V1 <code>maxAmountRequired</code> と V2 <code>amount</code> が同額か。片方だけ変わっていないか。</li>\n<li><strong>無料サンプルと入力境界を毎回取る</strong>：<code>/contract/sample</code> の <code>weakness</code> と <code>fetched_at</code>、400 になる入力の一覧。<strong>200 の有料本文は買うまで分からない</strong>ので、そこは「未検知」として記録する。</li>\n</ol>\n<p>当方の <code>/compat-check</code> は、この3面を外部の 402 に対して1回の GET で読み、V1/V2 のどちらで買えるか・必須項目の欠落・金額・受取先を返す商品です（0.02 USDC）。<strong>自分の連携が壊れる前に、相手の宣言だけを先に差分で見る</strong>用途に向きます。</p>"
    },
    {
      "見出し": "できないこと・未確認",
      "html": "<ul>\n<li><strong>200 の有料本文は取得していません</strong>（支払っていない）。したがって<strong>成功時のレスポンススキーマの変更は検知できていません</strong>。今回分かるのは 402 の宣言までです。</li>\n<li><strong>失敗時の形は 402 にありません</strong>。<code>/contract/sample</code> 自身が <code>error_shape_undeclared</code> と返しており、課金後の 500 の本文は未取得です。</li>\n<li><code>/changes</code> は <code>/status.json</code> 上で <code>last_success=null</code>（納品0件）。<code>/compat-check</code> は最終成功 <strong>2026-10-15T19:18:00Z</strong>、<code>/contract</code> は <strong>2026-10-17T14:06:12Z</strong>、<code>/compat</code> は <strong>2026-10-13T06:37:24Z</strong> で、いずれも <code>success_24h=0</code>。納品実績は自己テストを含む可能性があり、需要の証拠ではありません。</li>\n<li>弱点コードが前日と変わった理由（<strong>実装の変更か、対象の違いか</strong>）は<strong>未特定</strong>です。サンプルが同じ入力で別の弱点を返すなら、サンプルもバージョン管理が必要です。</li>\n<li><code>period</code> が 402 のままだった理由（<strong>未実装か、仕様か</strong>）は未確認です。課金後に 400 になる可能性は否定できません。</li>\n<li>3経路・各2回の実測で、<strong>時間帯や Edge による差は見ていません</strong>。応答時間は目安です。</li>\n</ul>"
    },
    {
      "見出し": "次の一歩",
      "html": "<p>402 の読み方と生存確認は <a href=\"https://x402-catalog-history.ai-business-surface.workers.dev/ja/is-x402-endpoint-alive\"><code>/ja/is-x402-endpoint-alive</code></a>、互換判定4経路の実測は <a href=\"https://x402-catalog-history.ai-business-surface.workers.dev/ja/api-breaking-change-detector\"><code>/ja/api-breaking-change-detector</code></a>、課金実装での止め方は <a href=\"https://x402-catalog-history.ai-business-surface.workers.dev/ja/x402-billing-implementation\"><code>/ja/x402-billing-implementation</code></a> にあります。無料の市場データは <a href=\"https://x402-catalog-history.ai-business-surface.workers.dev/data.json\"><code>/data.json</code></a>、当方の全経路と価格は <a href=\"https://x402-catalog-history.ai-business-surface.workers.dev/product.json\"><code>/product.json</code></a>、稼働と納品の記録は <a href=\"https://x402-catalog-history.ai-business-surface.workers.dev/status.json\"><code>/status.json</code></a> で読めます。</p>"
    }
  ],
  "一覧": "https://x402-catalog-history.ai-business-surface.workers.dev/ja"
}
