From 4551c8f1ff78cc3f6713f4da41c11153cc380fbc Mon Sep 17 00:00:00 2001 From: dwebxr Date: Thu, 24 Sep 2026 12:28:48 +0900 Subject: [PATCH] =?UTF-8?q?refactor(directory):=20Monitor=20=E3=81=AE=20ch?= =?UTF-8?q?angelog=20=E3=83=87=E3=83=BC=E3=82=BF=E3=82=92=E5=88=86?= =?UTF-8?q?=E9=9B=A2=E3=81=97=20teaser=20=E3=81=A8=20delta=20=E3=81=AE?= =?UTF-8?q?=E9=87=8D=E8=A4=87=E3=82=92=E5=85=B1=E9=80=9A=E5=8C=96=20(R9a/R?= =?UTF-8?q?9b)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 第 6 回レビュー Phase 6 の R9a/R9b (E16)。serviceMonitor.ts (946 行) から MANUAL_CHANGELOG と BASELINE_DATE をデータ専用の lib/directory/changelogData.ts に、 語彙の型と定数を changelogTypes.ts に移す (serviceMonitor は同じ path で再 export)。 両 teaser route の重複を lib/directory/monitorTeaser.ts の teaserChangesFor(scope) に、 両 paid monitor の delta 判定を takeDeltaSince に共通化する。挙動は不変。 実データで 6 route × 多数の query の応答 hash が分割前と同一であることを確認し、 fixture の golden (両 rail の byte 一致を含む) を commit した。 Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01HeizmagJBgL5peL5mQpxkc --- app/api/jpyc/services/teaser/route.ts | 24 +- app/api/stablecoin-payments/teaser/route.ts | 15 +- lib/directory/changelogData.ts | 574 +++++++++++++++++ lib/directory/changelogTypes.ts | 74 +++ lib/directory/data.ts | 2 +- lib/directory/monitorTeaser.ts | 38 ++ lib/directory/paymentMonitor.ts | 11 +- lib/directory/serviceMonitor.ts | 675 +------------------- tests/app/api/monitor-wire-golden.test.ts | 471 ++++++++++++++ tests/lib/directory/serviceMonitor.test.ts | 2 +- 10 files changed, 1212 insertions(+), 674 deletions(-) create mode 100644 lib/directory/changelogData.ts create mode 100644 lib/directory/changelogTypes.ts create mode 100644 lib/directory/monitorTeaser.ts create mode 100644 tests/app/api/monitor-wire-golden.test.ts diff --git a/app/api/jpyc/services/teaser/route.ts b/app/api/jpyc/services/teaser/route.ts index 3c3136e0..43499332 100644 --- a/app/api/jpyc/services/teaser/route.ts +++ b/app/api/jpyc/services/teaser/route.ts @@ -5,21 +5,17 @@ import { NextResponse } from 'next/server'; import { env } from '@/lib/env'; +import { teaserChangesFor } from '@/lib/directory/monitorTeaser'; import { JPYC_SERVICES_RESOURCE } from '@/lib/directory/paidResources'; import { createServiceMonitorEnvelope, - deltaEffectiveDate, - scopedChangelog, SERVICE_MONITOR_MAX_LIMIT, - sortByDeltaEffectiveDate, } from '@/lib/directory/serviceMonitor'; import { USDC_SERVICE_MONITOR } from '@/lib/directory/usdcResource'; export const runtime = 'nodejs'; export const dynamic = 'force-dynamic'; -const TEASER_EVENTS = 3; - export async function GET(): Promise { if (!env.enableWeb3Directory) { return NextResponse.json({ ok: false, error: 'not_found' }, { status: 404 }); @@ -31,22 +27,18 @@ export async function GET(): Promise { {}, new Date().toISOString(), ); - // snapshot の changes は limit で切られた view (末尾 200 件・date 順)。件数が上限を超えると、date は古いが - // 記録は最新の backfill が先に落ちて latestRecordedAt を過小に出す → changelog 全体から計算する。 - const recorded = sortByDeltaEffectiveDate(scopedChangelog('jpyc-services')); + // changes は limit で切られた snapshot view ではなく changelog 全体から (lib/directory/monitorTeaser.ts)。 + const teaser = teaserChangesFor('jpyc-services'); return NextResponse.json( { schemaVersion: full.schemaVersion, product: 'jpyc-service-monitor', teaser: true, - // 「買う前に確かめる」は記録日で判定する (有料 delta と同じ実効日)。date だけだと、後から記録した - // 古い date のイベントが「新しい変更なし」に見えて買い控えが起きる (2026-09-23)。 - latestChanges: recorded.slice(-TEASER_EVENTS).map(({ scopes: _scopes, ...event }) => event), - latestRecordedAt: recorded.length > 0 ? deltaEffectiveDate(recorded[recorded.length - 1]) : null, - // full.changes は limit で切られた view なので総数の権威にならない (件数が - // SERVICE_MONITOR_MAX_LIMIT を超えると開示が過少になる)。changelog の実数を使う - // (payments teaser が full.totalEvents を使うのと同じ意味)。 - totalEvents: scopedChangelog('jpyc-services').length, + // 「買う前に確かめる」は記録日で判定する (有料 delta と同じ実効日)。 + latestChanges: teaser.latestChanges, + latestRecordedAt: teaser.latestRecordedAt, + // full.changes は limit で切られた view なので総数の権威にならない。changelog の実数を使う。 + totalEvents: teaser.totalEvents, totalServices: full.totalServices, generatedAt: full.generatedAt, fullFeed: { diff --git a/app/api/stablecoin-payments/teaser/route.ts b/app/api/stablecoin-payments/teaser/route.ts index 7f4f3477..248dbd88 100644 --- a/app/api/stablecoin-payments/teaser/route.ts +++ b/app/api/stablecoin-payments/teaser/route.ts @@ -3,16 +3,15 @@ import { NextResponse } from 'next/server'; import { env } from '@/lib/env'; +import { teaserChangesFor } from '@/lib/directory/monitorTeaser'; import { JPYC_PAYMENTS_RESOURCE } from '@/lib/directory/paidResources'; import { createPaymentMonitorEnvelope } from '@/lib/directory/paymentMonitor'; -import { deltaEffectiveDate, scopedChangelog, SERVICE_MONITOR_MAX_LIMIT, sortByDeltaEffectiveDate } from '@/lib/directory/serviceMonitor'; +import { SERVICE_MONITOR_MAX_LIMIT } from '@/lib/directory/serviceMonitor'; import { USDC_PAYMENT_MONITOR } from '@/lib/directory/usdcResource'; export const runtime = 'nodejs'; export const dynamic = 'force-dynamic'; -const TEASER_EVENTS = 3; - export async function GET(): Promise { if (!env.enableWeb3Directory) { return NextResponse.json({ ok: false, error: 'not_found' }, { status: 404 }); @@ -21,16 +20,18 @@ export async function GET(): Promise { { limit: SERVICE_MONITOR_MAX_LIMIT }, new Date().toISOString(), ); - const recorded = sortByDeltaEffectiveDate(scopedChangelog('stablecoin-payments')); + // latestChanges は有料版の provider 投影行ではなく raw の changelog イベント (scope だけ除く)。 + const teaser = teaserChangesFor('stablecoin-payments'); return NextResponse.json( { schemaVersion: full.schemaVersion, product: 'japan-stablecoin-payment-monitor', teaser: true, // limit で切られた snapshot view ではなく changelog 全体から (backfill が最新でも取りこぼさない)。 - latestChanges: recorded.slice(-TEASER_EVENTS).map(({ scopes: _scopes, ...event }) => event), - latestRecordedAt: recorded.length > 0 ? deltaEffectiveDate(recorded[recorded.length - 1]) : null, - totalEvents: full.totalEvents, + latestChanges: teaser.latestChanges, + latestRecordedAt: teaser.latestRecordedAt, + // = full.totalEvents (どちらも決済スコープの changelog 全件数)。 + totalEvents: teaser.totalEvents, generatedAt: full.generatedAt, fullFeed: { jpyc: 'https://open-pay.jp/api/paid/stablecoin-payments', diff --git a/lib/directory/changelogData.ts b/lib/directory/changelogData.ts new file mode 100644 index 00000000..db77d486 --- /dev/null +++ b/lib/directory/changelogData.ts @@ -0,0 +1,574 @@ +// JPYC Service Monitor / Japan Stablecoin Payment Monitor の共通 changelog データ (データのみ・2026-09-24 R9a)。 +// 契約とロジック (baseline の導出・並び・delta) は serviceMonitor.ts。ここには手書きの changelog と +// baseline の固定日だけを置き、型は changelogTypes.ts から type-only で読む。 + +import type { ServiceChangeEvent } from './changelogTypes'; + +// 週次運用で追記する手書き changelog (新しいものを**末尾**に追加する — 日付昇順を保つ)。 +// 掟: 事実のみ・一次ソース URL 必須級・エントリ本体 (data.ts) の変更と同一 PR で追記する。 +// removed の場合は data.ts の status を 'archived' にし、ここに removed イベントを足す。 +// 新規エントリは必ず 'added' イベントをここに書く (baseline の自動 added から除外される)。 +// 記入ルール (2026-09-23): 2026-09-24 以降の行は collectedAt 必須 (テストがフェンス)。collectedAt は「本番に +// merge した日 (JST)」以上。merge が遅れたら merge 直前に更新する — delta の cursor (generatedAt の UTC 日付) が +// 実効日を追い越すと、その買い手には永久に届かなくなる。 +export const MANUAL_CHANGELOG: readonly ServiceChangeEvent[] = [ + // ── stablecoin-payments backfill (2026-08-27 収集・一次ソース確認済み。初回購入者が + // 空フィードを掴まないよう、決済スコープの過去イベントを遡って積む) ── + { + date: '2025-11-14', + scopes: ['stablecoin-payments'], + provider: 'TIS / JPYC', + changeType: 'added', + changeCategory: 'partnership', + assets: ['JPYC'], + summary: + 'TIS and JPYC signed a basic agreement toward real-world deployment of JPY-stablecoin payments.', + summaryJa: 'TIS と JPYC が、日本円ステーブルコイン決済の社会実装に向けた基本合意書を締結。', + sourceUrl: 'https://www.tis.co.jp/news/2025/tis_news/20251114_1.html', + }, + { + date: '2026-02-19', + scopes: ['stablecoin-payments'], + provider: 'Digital Garage / JCB / Resona HD', + changeType: 'added', + changeCategory: 'pilot', + assets: ['USDC', 'JPYC'], + summary: + 'Digital Garage, JCB and Resona HD announced an in-store stablecoin payment pilot using USDC and JPYC.', + summaryJa: + 'デジタルガレージ・JCB・りそな HD が、USDC と JPYC を用いた実店舗ステーブルコイン決済の実証実験を発表。', + sourceUrl: 'https://www.garage.co.jp/pr/release/20260219/', + }, + // ── JPYC / JPYC EX の Kaia 対応 (PR TIMES 2026-05-15 発表・2026-08-27 に第 1 回週次更新で + // 収集。2026-09-03 の日付訂正で date を収集日から発表日へ直し collectedAt を分離) ── + { + date: '2026-05-15', + collectedAt: '2026-08-27', + scopes: ['jpyc-services'], + slug: 'jpyc', + changeType: 'updated', + changeCategory: 'chains_change', + summary: + 'JPYC now also circulates on Kaia; issuance and circulation cover 4 chains (Polygon, Ethereum, Avalanche, Kaia).', + summaryJa: + 'JPYC が Kaia にも対応し、発行・流通は 4 チェーン (Polygon/Ethereum/Avalanche/Kaia) に。', + sourceUrl: 'https://prtimes.jp/main/html/rd/p/000000315.000054018.html', + diffs: [ + { + field: 'chains', + previousValue: ['polygon', 'ethereum', 'avalanche'], + currentValue: ['polygon', 'ethereum', 'avalanche', 'kaia'], + }, + ], + }, + { + date: '2026-05-15', + collectedAt: '2026-08-27', + scopes: ['jpyc-services'], + slug: 'jpyc-ex', + changeType: 'updated', + changeCategory: 'chains_change', + summary: + 'JPYC EX added Kaia support (issuance, redemption, wallet-address registration) and changed the issuance cap from 1M JPY per day to 1M JPY per transaction.', + summaryJa: + 'JPYC EX が Kaia に対応 (発行・償還・アドレス登録)。発行上限を「1日100万円」から「1回100万円」へ変更。', + sourceUrl: 'https://prtimes.jp/main/html/rd/p/000000315.000054018.html', + diffs: [ + { + field: 'chains', + previousValue: ['avalanche', 'ethereum', 'polygon'], + currentValue: ['avalanche', 'ethereum', 'polygon', 'kaia'], + }, + { + field: 'limit', + previousValue: '1,000,000 JPY per day (issuance)', + currentValue: '1,000,000 JPY per transaction (issuance)', + }, + ], + }, + { + // 第 2 回週次更新 (2026-09-04) で未追跡だった商用サービスを backfill。発表日 = PR TIMES。 + date: '2026-07-13', + collectedAt: '2026-09-04', + scopes: ['stablecoin-payments'], + provider: 'NetStars Stablecoin Pay', + changeType: 'added', + changeCategory: 'service_launch', + assets: ['USDC', 'USDT', 'JPYC'], + chains: ['solana', 'polygon'], + summary: + 'NetStars launched Stablecoin Pay for its StarPay merchants: USDC, USDT and JPYC accepted via one app on Solana and Polygon (Aptos planned for summer 2026), merchant fee 0.98% (tax-exempt).', + summaryJa: + 'ネットスターズが StarPay 加盟店向けに「Stablecoin Pay」を本格始動。USDC・USDT・JPYC を 1 つのアプリで受け付け (Solana/Polygon・Aptos は 2026 年夏予定)、加盟店手数料 0.98% (非課税)。', + sourceUrl: 'https://prtimes.jp/main/html/rd/p/000000185.000019526.html', + diffs: [ + { field: 'status', previousValue: null, currentValue: 'commercial' }, + { field: 'fee', previousValue: null, currentValue: '0.98% (tax-exempt)' }, + ], + }, + { + date: '2026-07-15', + scopes: ['stablecoin-payments'], + provider: 'JCB / Circle', + changeType: 'added', + changeCategory: 'partnership', + assets: ['USDC'], + summary: + 'JCB signed an MOU with a Circle affiliate to explore stablecoin-based collaboration, starting with internal USDC transfers and looking at cross-border and merchant payments.', + summaryJa: + 'JCB が Circle 関連会社とステーブルコイン活用の協業検討に関する基本合意書 (MOU) を締結。社内 USDC 資金移動の実証から、クロスボーダー・加盟店決済も検討対象に。', + sourceUrl: 'https://prtimes.jp/main/html/rd/p/000001423.000011361.html', + }, + { + date: '2026-08-10', + scopes: ['stablecoin-payments'], + slug: 'dg-sps', + changeType: 'added', + changeCategory: 'service_launch', + assets: ['USDC'], + chains: ['base'], + summary: + 'Digital Garage started commercial rollout of DG Stablecoin Payment Service (API-based merchant integration; initially USDC on Base, first offered to JCB and DGFT).', + summaryJa: + 'デジタルガレージが DG Stablecoin Payment Service の商用展開を開始 (API 接続の加盟店向け・当初は Base 上の USDC・JCB/DGFT へ先行提供)。', + sourceUrl: 'https://www.garage.co.jp/pr/release/20260810/', + diffs: [{ field: 'status', previousValue: null, currentValue: 'commercial' }], + }, + { + // 発表日は同じ一次ソース (garage.co.jp/pr/release/20260810) の上の決済スコープ行と同一。 + // ディレクトリ追加として記録したのは 2026-08-27 (第 1 回週次更新)。 + date: '2026-08-10', + collectedAt: '2026-08-27', + scopes: ['jpyc-services'], + slug: 'dg-sps', + changeType: 'added', + summary: + 'DG Stablecoin Payment Service (Digital Garage) added: a merchant stablecoin payment platform, initially USDC on Base, first offered to JCB and DGFT.', + summaryJa: + 'デジタルガレージの DG Stablecoin Payment Service を追加。加盟店向けステーブルコイン決済基盤 (当初は Base 上の USDC・JCB/DGFT へ先行提供)。', + sourceUrl: 'https://www.garage.co.jp/pr/release/20260810/', + }, + // ── 2026-08-26 大阪府「先駆的金融市場等形成支援事業補助金」採択 (公式発表・4 事業のうち + // ステーブルコイン決済関連 3 件。一次ソース = 大阪府公式ページ) ── + { + date: '2026-08-26', + scopes: ['stablecoin-payments'], + provider: 'HashPort (Osaka Pref. subsidy)', + changeType: 'added', + changeCategory: 'pilot', + assets: ['JPYC', 'USDC'], + summary: + 'Osaka Prefecture selected HashPort for its financial-pilot subsidy: a stablecoin payment and settlement pilot accepting JPYC/USDC at retail and restaurants with JPY settlement to merchants, plus an escrow-style EC payment API (planned Jan-Mar 2027).', + summaryJa: + '大阪府の先駆的金融市場等形成支援事業補助金に HashPort が採択。JPYC/USDC を受け付け加盟店へ日本円で清算する決済・清算システムと、EC 向けエスクロー型決済 API の実証 (2027 年 1〜3 月予定)。', + sourceUrl: + 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', + }, + { + date: '2026-08-26', + scopes: ['stablecoin-payments'], + provider: 'Mina Wallet / Sumitomo Mitsui Card (Osaka Pref. subsidy)', + changeType: 'added', + changeCategory: 'pilot', + assets: ['JPYC', 'USDC'], + summary: + 'Osaka Prefecture selected Mina Wallet (with Sumitomo Mitsui Card) for a stablecoin payment pilot using My Number Card identity verification (planned Oct 2026 - Feb 2027).', + summaryJa: + '大阪府補助にマイナウォレット (三井住友カードと共同) が採択。マイナンバーカードによる本人確認とステーブルコイン決済の実証 (2026 年 10 月〜2027 年 2 月頃予定)。', + sourceUrl: + 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', + }, + { + date: '2026-08-26', + scopes: ['stablecoin-payments'], + provider: 'Mi&T (Osaka Pref. subsidy)', + changeType: 'added', + changeCategory: 'pilot', + assets: ['JPYC'], + summary: + 'Osaka Prefecture selected Mi&T (Osaka Metropolitan University venture) for an in-store JPYC payment pilot at shops and restaurants around the university campus.', + summaryJa: + '大阪府補助に大阪公立大学発ベンチャーの Mi&T が採択。大学キャンパス周辺の飲食店・小売店での JPYC 実店舗決済の実証。', + sourceUrl: + 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', + }, + // ── 2026-08-27 (第 1 回週次更新)。自社面の観測 = 発表日 == 収集日 ── + { + date: '2026-08-27', + collectedAt: '2026-08-27', + scopes: ['jpyc-services'], + slug: 'aegis-ai', + changeType: 'updated', + changeCategory: 'assets_change', + summary: + 'Aegis now also sells its briefing API in USDC on Base via standard x402, alongside JPYC.', + summaryJa: 'Aegis が JPYC に加えて USDC (Base・標準 x402) での販売を開始。', + sourceUrl: 'https://aegis-ai.xyz/', + diffs: [{ field: 'assets', previousValue: ['JPYC'], currentValue: ['JPYC', 'USDC'] }], + }, + // ── 第 2 回週次更新 (収集 2026-09-04・収集窓 2026-08-27〜09-04) ── + { + // 一次ソース = 金融庁「電子決済手段等取引業者登録一覧」(令和 8 年 8 月 27 日現在)。 + date: '2026-08-27', + collectedAt: '2026-09-04', + scopes: ['jpyc-services'], + slug: 'coincheck', + changeType: 'updated', + changeCategory: 'update', + assets: ['USDC'], + summary: + 'Coincheck was registered as an Electronic Payment Instruments Exchange Service Provider (Kanto Local Finance Bureau No. 00002; handled instrument: USDC), the second such registrant in Japan after SBI VC Trade.', + summaryJa: + 'コインチェックが電子決済手段等取引業者として登録 (関東財務局長第00002号・取扱電子決済手段: USDC)。国内 2 社目 (1 社目は SBI VC トレード)。', + sourceUrl: 'https://www.fsa.go.jp/menkyo/menkyoj/denshikessaisyudan.pdf', + diffs: [ + { + field: 'status', + previousValue: null, + currentValue: 'registered Electronic Payment Instruments Exchange Service Provider (USDC)', + }, + ], + }, + { + // 大阪府採択 (8/26) の後、Mi&T 自身の PR TIMES で手数料・期間・規模が初めて数値開示された。 + date: '2026-08-31', + collectedAt: '2026-09-04', + scopes: ['stablecoin-payments'], + provider: 'Mi&T (Osaka Pref. subsidy)', + changeType: 'updated', + changeCategory: 'fee_change', + assets: ['JPYC'], + summary: + 'Mi&T disclosed details of its Osaka-subsidized in-store JPYC pilot: merchant fee 1.0% of the payment amount, about 5 restaurants and shops around Osaka Metropolitan University, planned mid-November 2026 to mid-March 2027.', + summaryJa: + 'Mi&T が大阪府補助の JPYC 実店舗決済実証の詳細を公表: 店舗側の決済手数料は決済額の 1.0% のみ、大阪公立大学キャンパス周辺の飲食店・小売店 5 店舗程度、2026 年 11 月中旬〜2027 年 3 月中旬 (予定)。', + sourceUrl: 'https://prtimes.jp/main/html/rd/p/000000003.000187870.html', + diffs: [{ field: 'fee', previousValue: null, currentValue: '1.0%' }], + }, + { + date: '2026-09-04', + scopes: ['jpyc-services'], + slug: 'sbi-vc-trade', + changeType: 'verified', + summary: + 'Re-verified against the FSA registry (as of 2026-08-27): SBI VC Trade remains registrant No. 00001 handling USDC, RLUSD and JPYSC.', + summaryJa: + '金融庁の登録一覧 (令和 8 年 8 月 27 日現在) で再確認: SBI VC トレードは第00001号として USDC・RLUSD・JPYSC を取扱い。', + sourceUrl: 'https://www.fsa.go.jp/menkyo/menkyoj/denshikessaisyudan.pdf', + }, + { + date: '2026-09-04', + scopes: ['jpyc-services'], + slug: 'jpyc', + changeType: 'verified', + summary: + 'Re-verified: no new official JPYC announcement found for 2026-08-27 to 2026-09-04; facts unchanged.', + summaryJa: + '再確認: 2026-08-27〜09-04 に JPYC の新規公式発表は見つからず、記載事実に変更なし。', + }, + { + date: '2026-09-04', + scopes: ['jpyc-services'], + slug: 'jpyc-ex', + changeType: 'verified', + summary: + 'Re-verified: no new official JPYC EX announcement found for 2026-08-27 to 2026-09-04; facts unchanged.', + summaryJa: + '再確認: 2026-08-27〜09-04 に JPYC EX の新規公式発表は見つからず、記載事実に変更なし。', + }, + { + date: '2026-09-04', + scopes: ['jpyc-services'], + slug: 'aegis-ai', + changeType: 'verified', + summary: + 'Re-verified: the x402 briefing endpoint still answers 402 (JPYC and USDC sales continue); the landing page was redesigned but the paid API is unchanged.', + summaryJa: + '再確認: x402 ブリーフィング API は引き続き 402 応答 (JPYC/USDC 販売継続)。LP は刷新されたが有料 API は変更なし。', + }, + { + date: '2026-09-04', + scopes: ['stablecoin-payments'], + provider: 'HashPort (Osaka Pref. subsidy)', + changeType: 'verified', + assets: ['JPYC', 'USDC'], + summary: + 'Re-verified on the Osaka Prefecture page (last updated 2026-08-26): 12 applications, 4 grants, JPY 28,890 thousand in total; HashPort pilot schedule unchanged.', + summaryJa: + '大阪府公式ページ (更新 2026-08-26) で再確認: 応募 12 件・交付決定 4 件・総額 28,890 千円。HashPort の実証予定に変更なし。', + sourceUrl: + 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', + }, + { + date: '2026-09-04', + scopes: ['stablecoin-payments'], + provider: 'Mina Wallet / Sumitomo Mitsui Card (Osaka Pref. subsidy)', + changeType: 'verified', + assets: ['JPYC', 'USDC'], + summary: + 'Re-verified on the Osaka Prefecture page (last updated 2026-08-26): Mina Wallet / Sumitomo Mitsui Card pilot schedule unchanged.', + summaryJa: + '大阪府公式ページ (更新 2026-08-26) で再確認: マイナウォレット / 三井住友カードの実証予定に変更なし。', + sourceUrl: + 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', + }, + { + // 週次更新 第 3 回 (2026-09-11)。Kaia × ネットスターズ MOU = 両商品にまたがる 1 事実 (Kaia 側 / StarPay 側)。 + date: '2026-09-11', + collectedAt: '2026-09-11', + scopes: ['jpyc-services'], + slug: 'kaia', + changeType: 'updated', + changeCategory: 'partnership', + assets: ['JPYC'], + summary: + 'KAIA DLT Foundation signed an MOU with NetStars (operator of StarPay) to study integrating Kaia-based stablecoins — JPYC, IDRX and native USDT — into StarPay\'s roughly 700,000 payment locations in Japan, using Kaia\'s FX orchestration layer "Ratio" so merchants receive yen. Launch timing and fees are not disclosed.', + summaryJa: + 'KAIA DLT Foundation がネットスターズ (StarPay 運営) と MOU を締結。Kaia 上の JPYC・IDRX・ネイティブ USDT を StarPay の国内約 70 万決済拠点へ統合する検討を開始し、FX 層「Ratio」で加盟店は円で受け取る構想。稼働時期・手数料は未公表。', + sourceUrl: 'https://prtimes.jp/main/html/rd/p/000000028.000154579.html', + }, + { + date: '2026-09-11', + collectedAt: '2026-09-11', + scopes: ['stablecoin-payments'], + provider: 'NetStars Stablecoin Pay', + changeType: 'updated', + changeCategory: 'partnership', + assets: ['JPYC', 'USDT'], + summary: + 'NetStars signed an MOU with KAIA DLT Foundation to study accepting Kaia-based stablecoins (JPYC, IDRX, native USDT) across StarPay\'s roughly 700,000 payment locations, with yen settlement to merchants via Kaia\'s "Ratio" FX layer. This is a study-phase agreement; no launch date, fee or chain change has been announced for Stablecoin Pay.', + summaryJa: + 'ネットスターズが KAIA DLT Foundation と MOU を締結。StarPay の国内約 70 万決済拠点で Kaia 系ステーブルコイン (JPYC・IDRX・ネイティブ USDT) の受け入れを検討し、Kaia の FX 層「Ratio」で加盟店へ円で精算する構想。検討段階の合意で、Stablecoin Pay の稼働日・手数料・対応チェーンの変更は未発表。', + sourceUrl: 'https://www.netstars.co.jp/news/9737/', + }, + { + date: '2026-09-11', + scopes: ['stablecoin-payments'], + provider: 'HashPort (Osaka Pref. subsidy)', + changeType: 'verified', + assets: ['JPYC', 'USDC'], + summary: + 'Re-verified on the Osaka Prefecture page (still last updated 2026-08-26): 4 grants, JPY 28,890 thousand in total, implementation through 2027-03-31; HashPort pilot schedule unchanged.', + summaryJa: + '大阪府公式ページ (更新日 2026-08-26 のまま) で再確認: 交付決定 4 件・総額 28,890 千円・実施期間 2027-03-31 まで。HashPort の実証予定に変更なし。', + sourceUrl: + 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', + }, + { + date: '2026-09-11', + scopes: ['stablecoin-payments'], + provider: 'Mina Wallet / Sumitomo Mitsui Card (Osaka Pref. subsidy)', + changeType: 'verified', + assets: ['JPYC', 'USDC'], + summary: + 'Re-verified on the Osaka Prefecture page (still last updated 2026-08-26): Mina Wallet / Sumitomo Mitsui Card pilot schedule unchanged.', + summaryJa: + '大阪府公式ページ (更新日 2026-08-26 のまま) で再確認: マイナウォレット / 三井住友カードの実証予定に変更なし。', + sourceUrl: + 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', + }, + { + date: '2026-09-11', + scopes: ['stablecoin-payments'], + provider: 'Mi&T (Osaka Pref. subsidy)', + changeType: 'verified', + assets: ['JPYC'], + summary: + 'Re-verified on the Osaka Prefecture page (still last updated 2026-08-26): Mi&T pilot (merchant fee 1.0%, planned mid-November 2026 to mid-March 2027) unchanged.', + summaryJa: + '大阪府公式ページ (更新日 2026-08-26 のまま) で再確認: Mi&T の実証 (手数料 1.0%・2026 年 11 月中旬〜2027 年 3 月中旬予定) に変更なし。', + sourceUrl: + 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', + }, + { + // 週次更新 第 4 回 (2026-09-18)。Circle の原文は "active or onboarding" — JPYC が Arc 上で稼働中とは + // 書いていないので、稼働を断定せず chains (発行チェーン) も変えない (sourced-facts-only)。 + date: '2026-09-16', + collectedAt: '2026-09-18', + scopes: ['jpyc-services'], + slug: 'jpyc', + changeType: 'updated', + changeCategory: 'partnership', + assets: ['JPYC'], + summary: + 'Circle\'s Arc mainnet launch announcement (2026-09-16) names JPYC among the local stablecoins that are "active or onboarding" to Circle StableFX, its 24/7 FX engine on Arc. The announcement does not say which of the two applies to JPYC, and gives no date for JPYC availability on Arc.', + summaryJa: + 'Circle の Arc メインネット公開発表 (2026-09-16) が、Arc 上の 24 時間 FX エンジン「Circle StableFX」で「稼働中または導入手続き中 (active or onboarding)」の現地通貨ステーブルコインの 1 つとして JPYC を挙げた。JPYC がどちらの段階かと、Arc での提供時期は発表に記載なし。', + sourceUrl: + 'https://www.circle.com/pressroom/circle-launches-arc-mainnet-an-economic-operating-system-for-the-internet', + }, + { + // 週次更新 第 4 回 (2026-09-18)。Upbit の告知は取引所自身の一次ソース。価格・出来高・「初の上場」等の + // 評価は一次ソースに無いので書かない (sourced-facts-only)。JPYC 側の facts (発行チェーン) は不変。 + // 第 5 回 (2026-09-23) で同日 19:23 KST の別告知 (ID 6585・Kaia/Polygon 入金開始) を本イベントに統合。 + // 同日同 slug の updated は dedupe キーが衝突するため別イベントにできない。「置き換え済み」を summary に明記。 + date: '2026-09-17', + collectedAt: '2026-09-23', + scopes: ['jpyc-services'], + slug: 'jpyc', + changeType: 'updated', + changeCategory: 'update', + assets: ['JPYC'], + chains: ['ethereum', 'kaia', 'polygon'], + summary: + 'Upbit (South Korea) announced new trading support for JPYC in its KRW, BTC and USDT markets on 2026-09-17, with deposits and withdrawals on Ethereum only at launch (contract 0xE7C3D8C9a439feDe00D2600032D5dB0Be71C3c29); the trading start was rescheduled twice on the day, to 18:00 KST. Updated 2026-09-23: a second Upbit notice (ID 6585, 2026-09-17 19:23 KST) opened JPYC deposits over Kaia and Polygon (minimum deposit 500 JPYC; 36 / 200 confirmations; withdrawal fee 1 JPYC / 0.2 JPYC), with withdrawals on those networks to follow in a later notice.', + summaryJa: + '韓国の取引所 Upbit が 2026-09-17、JPYC の新規取引支援 (KRW・BTC・USDT マーケット) を告知。開始時点の入出金は Ethereum のみ (コントラクト 0xE7C3D8C9a439feDe00D2600032D5dB0Be71C3c29) で、取引開始時刻は当日 2 回変更され 18:00 KST となった。2026-09-23 更新: 同日 19:23 KST の別告知 (ID 6585) で Kaia・Polygon ネットワーク経由の JPYC 入金を開始 (最小入金 500 JPYC・確認数 36 / 200・出金手数料 1 JPYC / 0.2 JPYC)。両ネットワークの出金は後日の告知で対応予定。', + sourceUrl: 'https://upbit.com/service_center/notice?id=6585', + }, + { + // 週次更新 第 4 回 follow-up (2026-09-18)。発行予約は JPYC EX の機能なので slug は jpyc-ex + // (同日の jpyc/updated = Upbit 告知と dedupe キーが衝突しない)。時刻は JPYC EX 公式お知らせの + // 追記時刻を採用 (報道の時刻とは食い違うため使わない)。原因・Upbit 上場との関係は公式に記載が + // 無いので書かない (sourced-facts-only)。 + date: '2026-09-17', + collectedAt: '2026-09-18', + scopes: ['jpyc-services'], + slug: 'jpyc-ex', + changeType: 'updated', + changeCategory: 'update', + assets: ['JPYC'], + chains: ['ethereum', 'polygon'], + summary: + 'JPYC EX temporarily suspended JPYC issuance reservations: Ethereum from around 19:15 JST on 2026-09-17 (cause under investigation), Polygon added at 21:30 JST. Ethereum reservations were restored at 23:20 JST the same day and Polygon at 00:15 JST on 2026-09-18, after which the service returned to normal. JPYC has not published a cause. Redemption was not mentioned as affected.', + summaryJa: + 'JPYC EX が JPYC の発行予約を一時停止: Ethereum は 2026-09-17 19:15 頃から (原因調査中)、Polygon は同日 21:30 追記で対象に追加。Ethereum は同日 23:20、Polygon は 2026-09-18 0:15 に復旧し、通常どおり利用可能に。原因は公表なし。償還への影響は記載なし。', + sourceUrl: 'https://faq.jpyc.co.jp/s/article/announce-0036', + }, + { + date: '2026-09-18', + scopes: ['stablecoin-payments'], + provider: 'HashPort (Osaka Pref. subsidy)', + changeType: 'verified', + assets: ['JPYC', 'USDC'], + summary: + 'Re-verified on the Osaka Prefecture page (still last updated 2026-08-26): 4 grants, JPY 28,890 thousand in total; HashPort pilot schedule unchanged.', + summaryJa: + '大阪府公式ページ (更新日 2026-08-26 のまま) で再確認: 交付決定 4 件・総額 28,890 千円。HashPort の実証予定に変更なし。', + sourceUrl: + 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', + }, + { + date: '2026-09-18', + scopes: ['stablecoin-payments'], + provider: 'Mina Wallet / Sumitomo Mitsui Card (Osaka Pref. subsidy)', + changeType: 'verified', + assets: ['JPYC', 'USDC'], + summary: + 'Re-verified on the Osaka Prefecture page (still last updated 2026-08-26): Mina Wallet / Sumitomo Mitsui Card pilot schedule unchanged.', + summaryJa: + '大阪府公式ページ (更新日 2026-08-26 のまま) で再確認: マイナウォレット / 三井住友カードの実証予定に変更なし。', + sourceUrl: + 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', + }, + { + date: '2026-09-18', + scopes: ['stablecoin-payments'], + provider: 'Mi&T (Osaka Pref. subsidy)', + changeType: 'verified', + assets: ['JPYC'], + summary: + 'Re-verified on the Osaka Prefecture page (still last updated 2026-08-26): Mi&T pilot (merchant fee 1.0%, planned mid-November 2026 to mid-March 2027) unchanged.', + summaryJa: + '大阪府公式ページ (更新日 2026-08-26 のまま) で再確認: Mi&T の実証 (手数料 1.0%・2026 年 11 月中旬〜2027 年 3 月中旬予定) に変更なし。', + sourceUrl: + 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', + }, + { + date: '2026-09-18', + scopes: ['stablecoin-payments'], + provider: 'NetStars Stablecoin Pay', + changeType: 'verified', + assets: ['JPYC', 'USDT'], + summary: + 'Re-verified on the NetStars news list: no announcement after the 2026-09-11 Kaia MOU; no launch date, fee or chain change published for Stablecoin Pay.', + summaryJa: + 'ネットスターズ公式ニュース一覧で再確認: 2026-09-11 の Kaia MOU 以降の発表なし。Stablecoin Pay の稼働日・手数料・対応チェーンの変更は未公表のまま。', + sourceUrl: 'https://www.netstars.co.jp/news/', + }, + { + // 週次更新 第 5 回 (2026-09-23)。累計発行額は JPYC 社の自社発表 (PR TIMES 一次配信)。 + // 発行チェーン (4) と facts は不変なので diffs は付けない。 + date: '2026-09-18', + collectedAt: '2026-09-23', + scopes: ['jpyc-services'], + slug: 'jpyc', + changeType: 'updated', + changeCategory: 'update', + assets: ['JPYC'], + summary: + 'JPYC Inc. announced on 2026-09-18 that cumulative issuance of the JPYC yen stablecoin has passed JPY 10 billion. Issuance chains are unchanged: Avalanche, Ethereum, Polygon and Kaia (4 chains).', + summaryJa: + 'JPYC 株式会社が 2026-09-18、日本円ステーブルコイン JPYC の累計発行額が 100 億円を突破したと発表。発行チェーンは Avalanche・Ethereum・Polygon・Kaia の 4 チェーンで変更なし。', + sourceUrl: 'https://prtimes.jp/main/html/rd/p/000000331.000054018.html', + }, + { + // 週次更新 第 5 回 (2026-09-23)。金融庁の登録一覧 PDF で再確認。 + date: '2026-09-23', + collectedAt: '2026-09-23', + scopes: ['jpyc-services'], + slug: 'coincheck', + changeType: 'verified', + assets: ['USDC'], + summary: + 'Re-verified against the FSA registry of Electronic Payment Instruments Exchange Service Providers (still as of 2026-08-27): two registrants, SBI VC Trade (No. 00001) and Coincheck (No. 00002, USDC); no new registration.', + summaryJa: + '金融庁の電子決済手段等取引業者登録一覧 (令和 8 年 8 月 27 日現在のまま) で再確認: 登録は SBI VC トレード (第00001号) とコインチェック (第00002号・USDC) の 2 社で、新規登録なし。', + sourceUrl: 'https://www.fsa.go.jp/menkyo/menkyoj/denshikessaisyudan.pdf', + }, + { + date: '2026-09-23', + scopes: ['stablecoin-payments'], + provider: 'HashPort (Osaka Pref. subsidy)', + changeType: 'verified', + assets: ['JPYC', 'USDC'], + summary: + 'Re-verified on the Osaka Prefecture page (still last updated 2026-08-26): HashPort pilot schedule unchanged.', + summaryJa: + '大阪府公式ページ (更新日 2026-08-26 のまま) で再確認: HashPort の実証予定に変更なし。', + sourceUrl: + 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', + }, + { + date: '2026-09-23', + scopes: ['stablecoin-payments'], + provider: 'Mina Wallet / Sumitomo Mitsui Card (Osaka Pref. subsidy)', + changeType: 'verified', + assets: ['JPYC', 'USDC'], + summary: + 'Re-verified on the Osaka Prefecture page (still last updated 2026-08-26): Mina Wallet / Sumitomo Mitsui Card pilot schedule unchanged.', + summaryJa: + '大阪府公式ページ (更新日 2026-08-26 のまま) で再確認: マイナウォレット / 三井住友カードの実証予定に変更なし。', + sourceUrl: + 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', + }, + { + date: '2026-09-23', + scopes: ['stablecoin-payments'], + provider: 'Mi&T (Osaka Pref. subsidy)', + changeType: 'verified', + assets: ['JPYC'], + summary: + 'Re-verified on the Osaka Prefecture page (still last updated 2026-08-26): Mi&T pilot (merchant fee 1.0%, planned mid-November 2026 to mid-March 2027) unchanged.', + summaryJa: + '大阪府公式ページ (更新日 2026-08-26 のまま) で再確認: Mi&T の実証 (手数料 1.0%・2026 年 11 月中旬〜2027 年 3 月中旬予定) に変更なし。', + sourceUrl: + 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', + }, + { + date: '2026-09-23', + scopes: ['stablecoin-payments'], + provider: 'NetStars Stablecoin Pay', + changeType: 'verified', + assets: ['JPYC', 'USDT'], + summary: + 'Re-verified on the NetStars news list: no announcement after the 2026-09-11 Kaia MOU; no launch date, fee or chain change published for Stablecoin Pay.', + summaryJa: + 'ネットスターズ公式ニュース一覧で再確認: 2026-09-11 の Kaia MOU 以降の発表なし。Stablecoin Pay の稼働日・手数料・対応チェーンの変更は未公表のまま。', + sourceUrl: 'https://www.netstars.co.jp/news/', + }, +]; + +// ディレクトリ初期公開日。baseline の 'added' はこの固定日に立てる — entry.updatedAt 由来に +// すると週次更新で updatedAt を進めた瞬間に「追加日」まで動いてしまう (第 1 回運用で発覚)。 +export const BASELINE_DATE = '2026-07-13'; diff --git a/lib/directory/changelogTypes.ts b/lib/directory/changelogTypes.ts new file mode 100644 index 00000000..2c943680 --- /dev/null +++ b/lib/directory/changelogTypes.ts @@ -0,0 +1,74 @@ +// Service Monitor の changelog 語彙と型 (固定語彙の定数 + 型のみ・ロジックなし)。 +// changelogData.ts (データ) と serviceMonitor.ts (契約・ロジック) の両方がここに依存する — +// データ module がロジック module を import しない (循環させない) ための分離 (2026-09-24 R9a)。 +// 公開 export は serviceMonitor.ts から従来どおり再 export する (利用側の import 経路は不変)。 + +export const SERVICE_CHANGE_TYPES = ['added', 'updated', 'removed', 'verified'] as const; +export type ServiceChangeType = (typeof SERVICE_CHANGE_TYPES)[number]; + +// 商品スコープ: 1 つの共通 changelog から用途別ビューを切り出すためのタグ (2026-08-27 裁定 +// 「1 回の週次更新から複数の用途別ビューを生成」)。イベントは複数スコープに属してよい。 +export const SERVICE_CHANGE_SCOPES = ['jpyc-services', 'stablecoin-payments'] as const; +export type ServiceChangeScope = (typeof SERVICE_CHANGE_SCOPES)[number]; + +// 決済監視ビュー用の変更分類 (何が起きたか)。changeType (ディレクトリ操作) と直交する。 +export const SERVICE_CHANGE_CATEGORIES = [ + 'service_launch', + 'pilot', + 'partnership', + 'fee_change', + 'assets_change', + 'chains_change', + 'closure', + 'update', +] as const; +export type ServiceChangeCategory = (typeof SERVICE_CHANGE_CATEGORIES)[number]; + +// 構造化差分 (変更台帳化・2026-09-02 裁定)。散文 summary に加え、一次ソースが「前の値 → 今の値」を +// 明示する場合にだけ値で書く (推測で埋めない = sourced-facts-only)。field は固定語彙 — 同じ分類 +// 基準で継続監視することが商品価値なので自由文字列にしない。 +export const SERVICE_DIFF_FIELDS = [ + 'assets', // 対応ステーブルコイン (例 ['JPYC'] → ['JPYC','USDC']) + 'chains', // 対応チェーン + 'fee', // 手数料・料率 (正規化した文字列・例 '1.0%' / '2 JPYC min') + 'limit', // 上限・下限 (発行上限・送金上限など) + 'status', // 提供状態 (例 null → 'commercial' / 'pilot' / 'closed') + 'feature', // 機能の追加・廃止 (例 'redemption', 'pos-integration') +] as const; +export type ServiceDiffField = (typeof SERVICE_DIFF_FIELDS)[number]; + +export type ServiceChangeDiff = { + field: ServiceDiffField; + /** 変更前の値 (無かった場合は null)。 */ + previousValue: string | readonly string[] | null; + currentValue: string | readonly string[]; + /** 適用日 (YYYY-MM-DD)。発表日 (event.date) と異なる場合のみ。 */ + effectiveAt?: string; +}; + +export type ServiceChangeEvent = { + /** YYYY-MM-DD = **一次ソースの発表日** (2026-09-03 統一。収集日ではない)。 + * changedSince との比較は文字列比較 (同形式ゆえ安全)。 */ + date: string; + /** YYYY-MM-DD = こちらが記録した日 (収集日)。発表日と乖離する場合の監査用・任意。 */ + collectedAt?: string; + /** どの商品ビューに載せるか (必須・明示)。 */ + scopes: readonly ServiceChangeScope[]; + /** ディレクトリエントリに紐づくイベントのみ。業界イベント (実証実験等) は provider を使う。 */ + slug?: string; + /** slug 無しイベントの表示名 (例: 'JCB / Digital Garage / Resona HD')。 */ + provider?: string; + changeType: ServiceChangeType; + /** 決済監視ビュー用の分類 (任意)。 */ + changeCategory?: ServiceChangeCategory; + /** イベント固有の対象資産/チェーン (任意・省略時は entry の facts から導出)。 */ + assets?: readonly string[]; + chains?: readonly string[]; + /** 何が変わったか (英語・1 文・事実のみ)。 */ + summary: string; + summaryJa: string; + /** 変更の根拠 URL。省略時はエントリの sourceUrl。 */ + sourceUrl?: string; + /** 値レベルの差分 (一次ソースが前後の値を明示する場合のみ・任意)。 */ + diffs?: readonly ServiceChangeDiff[]; +}; diff --git a/lib/directory/data.ts b/lib/directory/data.ts index f43780cd..413cdd0a 100644 --- a/lib/directory/data.ts +++ b/lib/directory/data.ts @@ -6,7 +6,7 @@ import type { const VERIFIED_AT = '2026-07-13'; // 週次運用 (JPYC Service Monitor) では、再検証したエントリだけ verifiedAt を、事実を書き換えた -// エントリだけ updatedAt を per-entry で進める。更新時は lib/directory/serviceMonitor.ts の +// エントリだけ updatedAt を per-entry で進める。更新時は lib/directory/changelogData.ts の // MANUAL_CHANGELOG に同一 PR でイベントを追記すること (運用 runbook: plans/jpyc-service-monitor.md)。 function provenance( sourceUrl: string, diff --git a/lib/directory/monitorTeaser.ts b/lib/directory/monitorTeaser.ts new file mode 100644 index 00000000..ffdedb73 --- /dev/null +++ b/lib/directory/monitorTeaser.ts @@ -0,0 +1,38 @@ +// 無料 teaser 2 本 (/api/jpyc/services/teaser・/api/stablecoin-payments/teaser) の共通部分 (2026-09-24 R9b)。 +// 両 teaser は「直近 3 イベント + 最新の記録日 + 総数」を同じ規則で出す。応答の形 (product 名・fullFeed・ +// services 数の有無) は route ごとに違うので、ここでは changes の切り出しだけを共有する。 +// +// stablecoin の teaser も有料 Payment Monitor の provider 投影行 (PaymentChangeRow) ではなく、scope だけを +// 除いた **raw の changelog イベント**を返す (従来どおり)。2 つは形が違うので混ぜない。 + +import { + deltaEffectiveDate, + scopedChangelog, + sortByDeltaEffectiveDate, + type ServiceChangeEventOutput, + type ServiceChangeScope, +} from './serviceMonitor'; + +export const MONITOR_TEASER_EVENTS = 3; + +export type MonitorTeaserChanges = { + latestChanges: ServiceChangeEventOutput[]; + latestRecordedAt: string | null; + totalEvents: number; +}; + +/** + * scope の changelog **全体**から teaser の changes を作る。有料版 snapshot の changes は limit で切られた + * view (date 順の末尾 200 件) なので使わない — 件数が上限を超えると、date は古いが記録は最新の backfill が + * 先に落ちて latestRecordedAt を過小に、totalEvents を過少に出してしまう。 + * 「買う前に確かめる」は記録日で判定する (有料 delta と同じ実効日 max(date, collectedAt))。date だけだと、 + * 後から記録した古い date のイベントが「新しい変更なし」に見えて買い控えが起きる (2026-09-23)。 + */ +export function teaserChangesFor(scope: ServiceChangeScope): MonitorTeaserChanges { + const recorded = sortByDeltaEffectiveDate(scopedChangelog(scope)); + return { + latestChanges: recorded.slice(-MONITOR_TEASER_EVENTS).map(({ scopes: _scopes, ...event }) => event), + latestRecordedAt: recorded.length > 0 ? deltaEffectiveDate(recorded[recorded.length - 1]) : null, + totalEvents: recorded.length, + }; +} diff --git a/lib/directory/paymentMonitor.ts b/lib/directory/paymentMonitor.ts index 4ccb17b7..a0f6dcb1 100644 --- a/lib/directory/paymentMonitor.ts +++ b/lib/directory/paymentMonitor.ts @@ -12,12 +12,10 @@ import { DIRECTORY_ENTRIES } from './data'; import { type ServiceChangeDiff, scopedChangelog, - takeDeltaByDateGroups, + takeDeltaSince, type ServiceChangeCategory, type ServiceChangeType, type ServiceMonitorQuery, - deltaEffectiveDate, - sortByDeltaEffectiveDate, } from './serviceMonitor'; import type { DirectoryEntry } from './types'; import { PAYMENT_PROVIDERS, type PaymentProviderRecord } from './paymentProviders'; @@ -141,10 +139,9 @@ export function createPaymentMonitorEnvelope( hasMore = changelog.length > query.limit; filtered = changelog.slice(-query.limit); } else { - // 実効日 (max(date, collectedAt)) で照合 — 後から記録した古い date のイベントを取りこぼさない (serviceMonitor と同じ)。 - const matched = sortByDeltaEffectiveDate(changelog.filter((event) => deltaEffectiveDate(event) >= (query.changedSince as string))); - // 同一 date のグループは分割しない (serviceMonitor.ts の takeDeltaByDateGroups が単一情報源)。 - const page = takeDeltaByDateGroups(matched, query.limit); + // 実効日 (max(date, collectedAt)) で照合 — 後から記録した古い date のイベントを取りこぼさない。 + // 同一実効日のグループは分割しない (serviceMonitor.ts の takeDeltaSince が単一情報源)。 + const page = takeDeltaSince(changelog, query.changedSince as string, query.limit); filtered = page.taken; hasMore = page.hasMore; if (page.nextChangedSince !== null) nextChangedSince = page.nextChangedSince; diff --git a/lib/directory/serviceMonitor.ts b/lib/directory/serviceMonitor.ts index ee151acd..715fa332 100644 --- a/lib/directory/serviceMonitor.ts +++ b/lib/directory/serviceMonitor.ts @@ -3,7 +3,7 @@ // 商品コンセプト (2026-08-27 裁定・plans/jpyc-service-monitor.md): // 静的一覧を売るのではなく、「JPYC 対応サービスの追加・変更・終了・再確認」を changelog として // 継続提供し、外部エージェントの週次ジョブに組み込んでもらう。マスターデータは directory と共通 -// (data.ts が単一情報源)。changelog は本ファイルの MANUAL_CHANGELOG に週次運用で追記する。 +// (data.ts が単一情報源)。changelog は changelogData.ts の MANUAL_CHANGELOG に週次運用で追記する。 // // 契約 (B1 jpyc live の教訓を踏襲): // - mode: 'snapshot' (changedSince なし・全 published の監視ビュー) | 'delta' (以降の変更のみ) @@ -13,6 +13,8 @@ // (2026-09-23: 後から記録した古い date のイベントを取りこぼさない)。snapshot は date 昇順。 // - date = 一次ソースの発表日 / collectedAt = こちらが記録した日 (2026-09-03 統一)。 +import { BASELINE_DATE, MANUAL_CHANGELOG } from './changelogData'; +import type { ServiceChangeEvent, ServiceChangeScope } from './changelogTypes'; import { DIRECTORY_ENTRIES } from './data'; import { publishedDirectoryEntries } from './query'; import type { @@ -25,644 +27,21 @@ export const SERVICE_MONITOR_MAX_LIMIT = 200; export const SERVICE_MONITOR_LICENSE_NOTICE = 'Facts summarized from official sources; source rights remain with their owners. sourceOk reports source URL reachability only, not whether the information is true.'; -export const SERVICE_CHANGE_TYPES = ['added', 'updated', 'removed', 'verified'] as const; -export type ServiceChangeType = (typeof SERVICE_CHANGE_TYPES)[number]; - -// 商品スコープ: 1 つの共通 changelog から用途別ビューを切り出すためのタグ (2026-08-27 裁定 -// 「1 回の週次更新から複数の用途別ビューを生成」)。イベントは複数スコープに属してよい。 -export const SERVICE_CHANGE_SCOPES = ['jpyc-services', 'stablecoin-payments'] as const; -export type ServiceChangeScope = (typeof SERVICE_CHANGE_SCOPES)[number]; - -// 決済監視ビュー用の変更分類 (何が起きたか)。changeType (ディレクトリ操作) と直交する。 -export const SERVICE_CHANGE_CATEGORIES = [ - 'service_launch', - 'pilot', - 'partnership', - 'fee_change', - 'assets_change', - 'chains_change', - 'closure', - 'update', -] as const; -export type ServiceChangeCategory = (typeof SERVICE_CHANGE_CATEGORIES)[number]; - -// 構造化差分 (変更台帳化・2026-09-02 裁定)。散文 summary に加え、一次ソースが「前の値 → 今の値」を -// 明示する場合にだけ値で書く (推測で埋めない = sourced-facts-only)。field は固定語彙 — 同じ分類 -// 基準で継続監視することが商品価値なので自由文字列にしない。 -export const SERVICE_DIFF_FIELDS = [ - 'assets', // 対応ステーブルコイン (例 ['JPYC'] → ['JPYC','USDC']) - 'chains', // 対応チェーン - 'fee', // 手数料・料率 (正規化した文字列・例 '1.0%' / '2 JPYC min') - 'limit', // 上限・下限 (発行上限・送金上限など) - 'status', // 提供状態 (例 null → 'commercial' / 'pilot' / 'closed') - 'feature', // 機能の追加・廃止 (例 'redemption', 'pos-integration') -] as const; -export type ServiceDiffField = (typeof SERVICE_DIFF_FIELDS)[number]; - -export type ServiceChangeDiff = { - field: ServiceDiffField; - /** 変更前の値 (無かった場合は null)。 */ - previousValue: string | readonly string[] | null; - currentValue: string | readonly string[]; - /** 適用日 (YYYY-MM-DD)。発表日 (event.date) と異なる場合のみ。 */ - effectiveAt?: string; -}; - -export type ServiceChangeEvent = { - /** YYYY-MM-DD = **一次ソースの発表日** (2026-09-03 統一。収集日ではない)。 - * changedSince との比較は文字列比較 (同形式ゆえ安全)。 */ - date: string; - /** YYYY-MM-DD = こちらが記録した日 (収集日)。発表日と乖離する場合の監査用・任意。 */ - collectedAt?: string; - /** どの商品ビューに載せるか (必須・明示)。 */ - scopes: readonly ServiceChangeScope[]; - /** ディレクトリエントリに紐づくイベントのみ。業界イベント (実証実験等) は provider を使う。 */ - slug?: string; - /** slug 無しイベントの表示名 (例: 'JCB / Digital Garage / Resona HD')。 */ - provider?: string; - changeType: ServiceChangeType; - /** 決済監視ビュー用の分類 (任意)。 */ - changeCategory?: ServiceChangeCategory; - /** イベント固有の対象資産/チェーン (任意・省略時は entry の facts から導出)。 */ - assets?: readonly string[]; - chains?: readonly string[]; - /** 何が変わったか (英語・1 文・事実のみ)。 */ - summary: string; - summaryJa: string; - /** 変更の根拠 URL。省略時はエントリの sourceUrl。 */ - sourceUrl?: string; - /** 値レベルの差分 (一次ソースが前後の値を明示する場合のみ・任意)。 */ - diffs?: readonly ServiceChangeDiff[]; -}; - -// 週次運用で追記する手書き changelog (新しいものを**末尾**に追加する — 日付昇順を保つ)。 -// 掟: 事実のみ・一次ソース URL 必須級・エントリ本体 (data.ts) の変更と同一 PR で追記する。 -// removed の場合は data.ts の status を 'archived' にし、ここに removed イベントを足す。 -// 新規エントリは必ず 'added' イベントをここに書く (baseline の自動 added から除外される)。 -// 記入ルール (2026-09-23): 2026-09-24 以降の行は collectedAt 必須 (テストがフェンス)。collectedAt は「本番に -// merge した日 (JST)」以上。merge が遅れたら merge 直前に更新する — delta の cursor (generatedAt の UTC 日付) が -// 実効日を追い越すと、その買い手には永久に届かなくなる。 -const MANUAL_CHANGELOG: readonly ServiceChangeEvent[] = [ - // ── stablecoin-payments backfill (2026-08-27 収集・一次ソース確認済み。初回購入者が - // 空フィードを掴まないよう、決済スコープの過去イベントを遡って積む) ── - { - date: '2025-11-14', - scopes: ['stablecoin-payments'], - provider: 'TIS / JPYC', - changeType: 'added', - changeCategory: 'partnership', - assets: ['JPYC'], - summary: - 'TIS and JPYC signed a basic agreement toward real-world deployment of JPY-stablecoin payments.', - summaryJa: 'TIS と JPYC が、日本円ステーブルコイン決済の社会実装に向けた基本合意書を締結。', - sourceUrl: 'https://www.tis.co.jp/news/2025/tis_news/20251114_1.html', - }, - { - date: '2026-02-19', - scopes: ['stablecoin-payments'], - provider: 'Digital Garage / JCB / Resona HD', - changeType: 'added', - changeCategory: 'pilot', - assets: ['USDC', 'JPYC'], - summary: - 'Digital Garage, JCB and Resona HD announced an in-store stablecoin payment pilot using USDC and JPYC.', - summaryJa: - 'デジタルガレージ・JCB・りそな HD が、USDC と JPYC を用いた実店舗ステーブルコイン決済の実証実験を発表。', - sourceUrl: 'https://www.garage.co.jp/pr/release/20260219/', - }, - // ── JPYC / JPYC EX の Kaia 対応 (PR TIMES 2026-05-15 発表・2026-08-27 に第 1 回週次更新で - // 収集。2026-09-03 の日付訂正で date を収集日から発表日へ直し collectedAt を分離) ── - { - date: '2026-05-15', - collectedAt: '2026-08-27', - scopes: ['jpyc-services'], - slug: 'jpyc', - changeType: 'updated', - changeCategory: 'chains_change', - summary: - 'JPYC now also circulates on Kaia; issuance and circulation cover 4 chains (Polygon, Ethereum, Avalanche, Kaia).', - summaryJa: - 'JPYC が Kaia にも対応し、発行・流通は 4 チェーン (Polygon/Ethereum/Avalanche/Kaia) に。', - sourceUrl: 'https://prtimes.jp/main/html/rd/p/000000315.000054018.html', - diffs: [ - { - field: 'chains', - previousValue: ['polygon', 'ethereum', 'avalanche'], - currentValue: ['polygon', 'ethereum', 'avalanche', 'kaia'], - }, - ], - }, - { - date: '2026-05-15', - collectedAt: '2026-08-27', - scopes: ['jpyc-services'], - slug: 'jpyc-ex', - changeType: 'updated', - changeCategory: 'chains_change', - summary: - 'JPYC EX added Kaia support (issuance, redemption, wallet-address registration) and changed the issuance cap from 1M JPY per day to 1M JPY per transaction.', - summaryJa: - 'JPYC EX が Kaia に対応 (発行・償還・アドレス登録)。発行上限を「1日100万円」から「1回100万円」へ変更。', - sourceUrl: 'https://prtimes.jp/main/html/rd/p/000000315.000054018.html', - diffs: [ - { - field: 'chains', - previousValue: ['avalanche', 'ethereum', 'polygon'], - currentValue: ['avalanche', 'ethereum', 'polygon', 'kaia'], - }, - { - field: 'limit', - previousValue: '1,000,000 JPY per day (issuance)', - currentValue: '1,000,000 JPY per transaction (issuance)', - }, - ], - }, - { - // 第 2 回週次更新 (2026-09-04) で未追跡だった商用サービスを backfill。発表日 = PR TIMES。 - date: '2026-07-13', - collectedAt: '2026-09-04', - scopes: ['stablecoin-payments'], - provider: 'NetStars Stablecoin Pay', - changeType: 'added', - changeCategory: 'service_launch', - assets: ['USDC', 'USDT', 'JPYC'], - chains: ['solana', 'polygon'], - summary: - 'NetStars launched Stablecoin Pay for its StarPay merchants: USDC, USDT and JPYC accepted via one app on Solana and Polygon (Aptos planned for summer 2026), merchant fee 0.98% (tax-exempt).', - summaryJa: - 'ネットスターズが StarPay 加盟店向けに「Stablecoin Pay」を本格始動。USDC・USDT・JPYC を 1 つのアプリで受け付け (Solana/Polygon・Aptos は 2026 年夏予定)、加盟店手数料 0.98% (非課税)。', - sourceUrl: 'https://prtimes.jp/main/html/rd/p/000000185.000019526.html', - diffs: [ - { field: 'status', previousValue: null, currentValue: 'commercial' }, - { field: 'fee', previousValue: null, currentValue: '0.98% (tax-exempt)' }, - ], - }, - { - date: '2026-07-15', - scopes: ['stablecoin-payments'], - provider: 'JCB / Circle', - changeType: 'added', - changeCategory: 'partnership', - assets: ['USDC'], - summary: - 'JCB signed an MOU with a Circle affiliate to explore stablecoin-based collaboration, starting with internal USDC transfers and looking at cross-border and merchant payments.', - summaryJa: - 'JCB が Circle 関連会社とステーブルコイン活用の協業検討に関する基本合意書 (MOU) を締結。社内 USDC 資金移動の実証から、クロスボーダー・加盟店決済も検討対象に。', - sourceUrl: 'https://prtimes.jp/main/html/rd/p/000001423.000011361.html', - }, - { - date: '2026-08-10', - scopes: ['stablecoin-payments'], - slug: 'dg-sps', - changeType: 'added', - changeCategory: 'service_launch', - assets: ['USDC'], - chains: ['base'], - summary: - 'Digital Garage started commercial rollout of DG Stablecoin Payment Service (API-based merchant integration; initially USDC on Base, first offered to JCB and DGFT).', - summaryJa: - 'デジタルガレージが DG Stablecoin Payment Service の商用展開を開始 (API 接続の加盟店向け・当初は Base 上の USDC・JCB/DGFT へ先行提供)。', - sourceUrl: 'https://www.garage.co.jp/pr/release/20260810/', - diffs: [{ field: 'status', previousValue: null, currentValue: 'commercial' }], - }, - { - // 発表日は同じ一次ソース (garage.co.jp/pr/release/20260810) の上の決済スコープ行と同一。 - // ディレクトリ追加として記録したのは 2026-08-27 (第 1 回週次更新)。 - date: '2026-08-10', - collectedAt: '2026-08-27', - scopes: ['jpyc-services'], - slug: 'dg-sps', - changeType: 'added', - summary: - 'DG Stablecoin Payment Service (Digital Garage) added: a merchant stablecoin payment platform, initially USDC on Base, first offered to JCB and DGFT.', - summaryJa: - 'デジタルガレージの DG Stablecoin Payment Service を追加。加盟店向けステーブルコイン決済基盤 (当初は Base 上の USDC・JCB/DGFT へ先行提供)。', - sourceUrl: 'https://www.garage.co.jp/pr/release/20260810/', - }, - // ── 2026-08-26 大阪府「先駆的金融市場等形成支援事業補助金」採択 (公式発表・4 事業のうち - // ステーブルコイン決済関連 3 件。一次ソース = 大阪府公式ページ) ── - { - date: '2026-08-26', - scopes: ['stablecoin-payments'], - provider: 'HashPort (Osaka Pref. subsidy)', - changeType: 'added', - changeCategory: 'pilot', - assets: ['JPYC', 'USDC'], - summary: - 'Osaka Prefecture selected HashPort for its financial-pilot subsidy: a stablecoin payment and settlement pilot accepting JPYC/USDC at retail and restaurants with JPY settlement to merchants, plus an escrow-style EC payment API (planned Jan-Mar 2027).', - summaryJa: - '大阪府の先駆的金融市場等形成支援事業補助金に HashPort が採択。JPYC/USDC を受け付け加盟店へ日本円で清算する決済・清算システムと、EC 向けエスクロー型決済 API の実証 (2027 年 1〜3 月予定)。', - sourceUrl: - 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', - }, - { - date: '2026-08-26', - scopes: ['stablecoin-payments'], - provider: 'Mina Wallet / Sumitomo Mitsui Card (Osaka Pref. subsidy)', - changeType: 'added', - changeCategory: 'pilot', - assets: ['JPYC', 'USDC'], - summary: - 'Osaka Prefecture selected Mina Wallet (with Sumitomo Mitsui Card) for a stablecoin payment pilot using My Number Card identity verification (planned Oct 2026 - Feb 2027).', - summaryJa: - '大阪府補助にマイナウォレット (三井住友カードと共同) が採択。マイナンバーカードによる本人確認とステーブルコイン決済の実証 (2026 年 10 月〜2027 年 2 月頃予定)。', - sourceUrl: - 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', - }, - { - date: '2026-08-26', - scopes: ['stablecoin-payments'], - provider: 'Mi&T (Osaka Pref. subsidy)', - changeType: 'added', - changeCategory: 'pilot', - assets: ['JPYC'], - summary: - 'Osaka Prefecture selected Mi&T (Osaka Metropolitan University venture) for an in-store JPYC payment pilot at shops and restaurants around the university campus.', - summaryJa: - '大阪府補助に大阪公立大学発ベンチャーの Mi&T が採択。大学キャンパス周辺の飲食店・小売店での JPYC 実店舗決済の実証。', - sourceUrl: - 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', - }, - // ── 2026-08-27 (第 1 回週次更新)。自社面の観測 = 発表日 == 収集日 ── - { - date: '2026-08-27', - collectedAt: '2026-08-27', - scopes: ['jpyc-services'], - slug: 'aegis-ai', - changeType: 'updated', - changeCategory: 'assets_change', - summary: - 'Aegis now also sells its briefing API in USDC on Base via standard x402, alongside JPYC.', - summaryJa: 'Aegis が JPYC に加えて USDC (Base・標準 x402) での販売を開始。', - sourceUrl: 'https://aegis-ai.xyz/', - diffs: [{ field: 'assets', previousValue: ['JPYC'], currentValue: ['JPYC', 'USDC'] }], - }, - // ── 第 2 回週次更新 (収集 2026-09-04・収集窓 2026-08-27〜09-04) ── - { - // 一次ソース = 金融庁「電子決済手段等取引業者登録一覧」(令和 8 年 8 月 27 日現在)。 - date: '2026-08-27', - collectedAt: '2026-09-04', - scopes: ['jpyc-services'], - slug: 'coincheck', - changeType: 'updated', - changeCategory: 'update', - assets: ['USDC'], - summary: - 'Coincheck was registered as an Electronic Payment Instruments Exchange Service Provider (Kanto Local Finance Bureau No. 00002; handled instrument: USDC), the second such registrant in Japan after SBI VC Trade.', - summaryJa: - 'コインチェックが電子決済手段等取引業者として登録 (関東財務局長第00002号・取扱電子決済手段: USDC)。国内 2 社目 (1 社目は SBI VC トレード)。', - sourceUrl: 'https://www.fsa.go.jp/menkyo/menkyoj/denshikessaisyudan.pdf', - diffs: [ - { - field: 'status', - previousValue: null, - currentValue: 'registered Electronic Payment Instruments Exchange Service Provider (USDC)', - }, - ], - }, - { - // 大阪府採択 (8/26) の後、Mi&T 自身の PR TIMES で手数料・期間・規模が初めて数値開示された。 - date: '2026-08-31', - collectedAt: '2026-09-04', - scopes: ['stablecoin-payments'], - provider: 'Mi&T (Osaka Pref. subsidy)', - changeType: 'updated', - changeCategory: 'fee_change', - assets: ['JPYC'], - summary: - 'Mi&T disclosed details of its Osaka-subsidized in-store JPYC pilot: merchant fee 1.0% of the payment amount, about 5 restaurants and shops around Osaka Metropolitan University, planned mid-November 2026 to mid-March 2027.', - summaryJa: - 'Mi&T が大阪府補助の JPYC 実店舗決済実証の詳細を公表: 店舗側の決済手数料は決済額の 1.0% のみ、大阪公立大学キャンパス周辺の飲食店・小売店 5 店舗程度、2026 年 11 月中旬〜2027 年 3 月中旬 (予定)。', - sourceUrl: 'https://prtimes.jp/main/html/rd/p/000000003.000187870.html', - diffs: [{ field: 'fee', previousValue: null, currentValue: '1.0%' }], - }, - { - date: '2026-09-04', - scopes: ['jpyc-services'], - slug: 'sbi-vc-trade', - changeType: 'verified', - summary: - 'Re-verified against the FSA registry (as of 2026-08-27): SBI VC Trade remains registrant No. 00001 handling USDC, RLUSD and JPYSC.', - summaryJa: - '金融庁の登録一覧 (令和 8 年 8 月 27 日現在) で再確認: SBI VC トレードは第00001号として USDC・RLUSD・JPYSC を取扱い。', - sourceUrl: 'https://www.fsa.go.jp/menkyo/menkyoj/denshikessaisyudan.pdf', - }, - { - date: '2026-09-04', - scopes: ['jpyc-services'], - slug: 'jpyc', - changeType: 'verified', - summary: - 'Re-verified: no new official JPYC announcement found for 2026-08-27 to 2026-09-04; facts unchanged.', - summaryJa: - '再確認: 2026-08-27〜09-04 に JPYC の新規公式発表は見つからず、記載事実に変更なし。', - }, - { - date: '2026-09-04', - scopes: ['jpyc-services'], - slug: 'jpyc-ex', - changeType: 'verified', - summary: - 'Re-verified: no new official JPYC EX announcement found for 2026-08-27 to 2026-09-04; facts unchanged.', - summaryJa: - '再確認: 2026-08-27〜09-04 に JPYC EX の新規公式発表は見つからず、記載事実に変更なし。', - }, - { - date: '2026-09-04', - scopes: ['jpyc-services'], - slug: 'aegis-ai', - changeType: 'verified', - summary: - 'Re-verified: the x402 briefing endpoint still answers 402 (JPYC and USDC sales continue); the landing page was redesigned but the paid API is unchanged.', - summaryJa: - '再確認: x402 ブリーフィング API は引き続き 402 応答 (JPYC/USDC 販売継続)。LP は刷新されたが有料 API は変更なし。', - }, - { - date: '2026-09-04', - scopes: ['stablecoin-payments'], - provider: 'HashPort (Osaka Pref. subsidy)', - changeType: 'verified', - assets: ['JPYC', 'USDC'], - summary: - 'Re-verified on the Osaka Prefecture page (last updated 2026-08-26): 12 applications, 4 grants, JPY 28,890 thousand in total; HashPort pilot schedule unchanged.', - summaryJa: - '大阪府公式ページ (更新 2026-08-26) で再確認: 応募 12 件・交付決定 4 件・総額 28,890 千円。HashPort の実証予定に変更なし。', - sourceUrl: - 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', - }, - { - date: '2026-09-04', - scopes: ['stablecoin-payments'], - provider: 'Mina Wallet / Sumitomo Mitsui Card (Osaka Pref. subsidy)', - changeType: 'verified', - assets: ['JPYC', 'USDC'], - summary: - 'Re-verified on the Osaka Prefecture page (last updated 2026-08-26): Mina Wallet / Sumitomo Mitsui Card pilot schedule unchanged.', - summaryJa: - '大阪府公式ページ (更新 2026-08-26) で再確認: マイナウォレット / 三井住友カードの実証予定に変更なし。', - sourceUrl: - 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', - }, - { - // 週次更新 第 3 回 (2026-09-11)。Kaia × ネットスターズ MOU = 両商品にまたがる 1 事実 (Kaia 側 / StarPay 側)。 - date: '2026-09-11', - collectedAt: '2026-09-11', - scopes: ['jpyc-services'], - slug: 'kaia', - changeType: 'updated', - changeCategory: 'partnership', - assets: ['JPYC'], - summary: - 'KAIA DLT Foundation signed an MOU with NetStars (operator of StarPay) to study integrating Kaia-based stablecoins — JPYC, IDRX and native USDT — into StarPay\'s roughly 700,000 payment locations in Japan, using Kaia\'s FX orchestration layer "Ratio" so merchants receive yen. Launch timing and fees are not disclosed.', - summaryJa: - 'KAIA DLT Foundation がネットスターズ (StarPay 運営) と MOU を締結。Kaia 上の JPYC・IDRX・ネイティブ USDT を StarPay の国内約 70 万決済拠点へ統合する検討を開始し、FX 層「Ratio」で加盟店は円で受け取る構想。稼働時期・手数料は未公表。', - sourceUrl: 'https://prtimes.jp/main/html/rd/p/000000028.000154579.html', - }, - { - date: '2026-09-11', - collectedAt: '2026-09-11', - scopes: ['stablecoin-payments'], - provider: 'NetStars Stablecoin Pay', - changeType: 'updated', - changeCategory: 'partnership', - assets: ['JPYC', 'USDT'], - summary: - 'NetStars signed an MOU with KAIA DLT Foundation to study accepting Kaia-based stablecoins (JPYC, IDRX, native USDT) across StarPay\'s roughly 700,000 payment locations, with yen settlement to merchants via Kaia\'s "Ratio" FX layer. This is a study-phase agreement; no launch date, fee or chain change has been announced for Stablecoin Pay.', - summaryJa: - 'ネットスターズが KAIA DLT Foundation と MOU を締結。StarPay の国内約 70 万決済拠点で Kaia 系ステーブルコイン (JPYC・IDRX・ネイティブ USDT) の受け入れを検討し、Kaia の FX 層「Ratio」で加盟店へ円で精算する構想。検討段階の合意で、Stablecoin Pay の稼働日・手数料・対応チェーンの変更は未発表。', - sourceUrl: 'https://www.netstars.co.jp/news/9737/', - }, - { - date: '2026-09-11', - scopes: ['stablecoin-payments'], - provider: 'HashPort (Osaka Pref. subsidy)', - changeType: 'verified', - assets: ['JPYC', 'USDC'], - summary: - 'Re-verified on the Osaka Prefecture page (still last updated 2026-08-26): 4 grants, JPY 28,890 thousand in total, implementation through 2027-03-31; HashPort pilot schedule unchanged.', - summaryJa: - '大阪府公式ページ (更新日 2026-08-26 のまま) で再確認: 交付決定 4 件・総額 28,890 千円・実施期間 2027-03-31 まで。HashPort の実証予定に変更なし。', - sourceUrl: - 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', - }, - { - date: '2026-09-11', - scopes: ['stablecoin-payments'], - provider: 'Mina Wallet / Sumitomo Mitsui Card (Osaka Pref. subsidy)', - changeType: 'verified', - assets: ['JPYC', 'USDC'], - summary: - 'Re-verified on the Osaka Prefecture page (still last updated 2026-08-26): Mina Wallet / Sumitomo Mitsui Card pilot schedule unchanged.', - summaryJa: - '大阪府公式ページ (更新日 2026-08-26 のまま) で再確認: マイナウォレット / 三井住友カードの実証予定に変更なし。', - sourceUrl: - 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', - }, - { - date: '2026-09-11', - scopes: ['stablecoin-payments'], - provider: 'Mi&T (Osaka Pref. subsidy)', - changeType: 'verified', - assets: ['JPYC'], - summary: - 'Re-verified on the Osaka Prefecture page (still last updated 2026-08-26): Mi&T pilot (merchant fee 1.0%, planned mid-November 2026 to mid-March 2027) unchanged.', - summaryJa: - '大阪府公式ページ (更新日 2026-08-26 のまま) で再確認: Mi&T の実証 (手数料 1.0%・2026 年 11 月中旬〜2027 年 3 月中旬予定) に変更なし。', - sourceUrl: - 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', - }, - { - // 週次更新 第 4 回 (2026-09-18)。Circle の原文は "active or onboarding" — JPYC が Arc 上で稼働中とは - // 書いていないので、稼働を断定せず chains (発行チェーン) も変えない (sourced-facts-only)。 - date: '2026-09-16', - collectedAt: '2026-09-18', - scopes: ['jpyc-services'], - slug: 'jpyc', - changeType: 'updated', - changeCategory: 'partnership', - assets: ['JPYC'], - summary: - 'Circle\'s Arc mainnet launch announcement (2026-09-16) names JPYC among the local stablecoins that are "active or onboarding" to Circle StableFX, its 24/7 FX engine on Arc. The announcement does not say which of the two applies to JPYC, and gives no date for JPYC availability on Arc.', - summaryJa: - 'Circle の Arc メインネット公開発表 (2026-09-16) が、Arc 上の 24 時間 FX エンジン「Circle StableFX」で「稼働中または導入手続き中 (active or onboarding)」の現地通貨ステーブルコインの 1 つとして JPYC を挙げた。JPYC がどちらの段階かと、Arc での提供時期は発表に記載なし。', - sourceUrl: - 'https://www.circle.com/pressroom/circle-launches-arc-mainnet-an-economic-operating-system-for-the-internet', - }, - { - // 週次更新 第 4 回 (2026-09-18)。Upbit の告知は取引所自身の一次ソース。価格・出来高・「初の上場」等の - // 評価は一次ソースに無いので書かない (sourced-facts-only)。JPYC 側の facts (発行チェーン) は不変。 - // 第 5 回 (2026-09-23) で同日 19:23 KST の別告知 (ID 6585・Kaia/Polygon 入金開始) を本イベントに統合。 - // 同日同 slug の updated は dedupe キーが衝突するため別イベントにできない。「置き換え済み」を summary に明記。 - date: '2026-09-17', - collectedAt: '2026-09-23', - scopes: ['jpyc-services'], - slug: 'jpyc', - changeType: 'updated', - changeCategory: 'update', - assets: ['JPYC'], - chains: ['ethereum', 'kaia', 'polygon'], - summary: - 'Upbit (South Korea) announced new trading support for JPYC in its KRW, BTC and USDT markets on 2026-09-17, with deposits and withdrawals on Ethereum only at launch (contract 0xE7C3D8C9a439feDe00D2600032D5dB0Be71C3c29); the trading start was rescheduled twice on the day, to 18:00 KST. Updated 2026-09-23: a second Upbit notice (ID 6585, 2026-09-17 19:23 KST) opened JPYC deposits over Kaia and Polygon (minimum deposit 500 JPYC; 36 / 200 confirmations; withdrawal fee 1 JPYC / 0.2 JPYC), with withdrawals on those networks to follow in a later notice.', - summaryJa: - '韓国の取引所 Upbit が 2026-09-17、JPYC の新規取引支援 (KRW・BTC・USDT マーケット) を告知。開始時点の入出金は Ethereum のみ (コントラクト 0xE7C3D8C9a439feDe00D2600032D5dB0Be71C3c29) で、取引開始時刻は当日 2 回変更され 18:00 KST となった。2026-09-23 更新: 同日 19:23 KST の別告知 (ID 6585) で Kaia・Polygon ネットワーク経由の JPYC 入金を開始 (最小入金 500 JPYC・確認数 36 / 200・出金手数料 1 JPYC / 0.2 JPYC)。両ネットワークの出金は後日の告知で対応予定。', - sourceUrl: 'https://upbit.com/service_center/notice?id=6585', - }, - { - // 週次更新 第 4 回 follow-up (2026-09-18)。発行予約は JPYC EX の機能なので slug は jpyc-ex - // (同日の jpyc/updated = Upbit 告知と dedupe キーが衝突しない)。時刻は JPYC EX 公式お知らせの - // 追記時刻を採用 (報道の時刻とは食い違うため使わない)。原因・Upbit 上場との関係は公式に記載が - // 無いので書かない (sourced-facts-only)。 - date: '2026-09-17', - collectedAt: '2026-09-18', - scopes: ['jpyc-services'], - slug: 'jpyc-ex', - changeType: 'updated', - changeCategory: 'update', - assets: ['JPYC'], - chains: ['ethereum', 'polygon'], - summary: - 'JPYC EX temporarily suspended JPYC issuance reservations: Ethereum from around 19:15 JST on 2026-09-17 (cause under investigation), Polygon added at 21:30 JST. Ethereum reservations were restored at 23:20 JST the same day and Polygon at 00:15 JST on 2026-09-18, after which the service returned to normal. JPYC has not published a cause. Redemption was not mentioned as affected.', - summaryJa: - 'JPYC EX が JPYC の発行予約を一時停止: Ethereum は 2026-09-17 19:15 頃から (原因調査中)、Polygon は同日 21:30 追記で対象に追加。Ethereum は同日 23:20、Polygon は 2026-09-18 0:15 に復旧し、通常どおり利用可能に。原因は公表なし。償還への影響は記載なし。', - sourceUrl: 'https://faq.jpyc.co.jp/s/article/announce-0036', - }, - { - date: '2026-09-18', - scopes: ['stablecoin-payments'], - provider: 'HashPort (Osaka Pref. subsidy)', - changeType: 'verified', - assets: ['JPYC', 'USDC'], - summary: - 'Re-verified on the Osaka Prefecture page (still last updated 2026-08-26): 4 grants, JPY 28,890 thousand in total; HashPort pilot schedule unchanged.', - summaryJa: - '大阪府公式ページ (更新日 2026-08-26 のまま) で再確認: 交付決定 4 件・総額 28,890 千円。HashPort の実証予定に変更なし。', - sourceUrl: - 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', - }, - { - date: '2026-09-18', - scopes: ['stablecoin-payments'], - provider: 'Mina Wallet / Sumitomo Mitsui Card (Osaka Pref. subsidy)', - changeType: 'verified', - assets: ['JPYC', 'USDC'], - summary: - 'Re-verified on the Osaka Prefecture page (still last updated 2026-08-26): Mina Wallet / Sumitomo Mitsui Card pilot schedule unchanged.', - summaryJa: - '大阪府公式ページ (更新日 2026-08-26 のまま) で再確認: マイナウォレット / 三井住友カードの実証予定に変更なし。', - sourceUrl: - 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', - }, - { - date: '2026-09-18', - scopes: ['stablecoin-payments'], - provider: 'Mi&T (Osaka Pref. subsidy)', - changeType: 'verified', - assets: ['JPYC'], - summary: - 'Re-verified on the Osaka Prefecture page (still last updated 2026-08-26): Mi&T pilot (merchant fee 1.0%, planned mid-November 2026 to mid-March 2027) unchanged.', - summaryJa: - '大阪府公式ページ (更新日 2026-08-26 のまま) で再確認: Mi&T の実証 (手数料 1.0%・2026 年 11 月中旬〜2027 年 3 月中旬予定) に変更なし。', - sourceUrl: - 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', - }, - { - date: '2026-09-18', - scopes: ['stablecoin-payments'], - provider: 'NetStars Stablecoin Pay', - changeType: 'verified', - assets: ['JPYC', 'USDT'], - summary: - 'Re-verified on the NetStars news list: no announcement after the 2026-09-11 Kaia MOU; no launch date, fee or chain change published for Stablecoin Pay.', - summaryJa: - 'ネットスターズ公式ニュース一覧で再確認: 2026-09-11 の Kaia MOU 以降の発表なし。Stablecoin Pay の稼働日・手数料・対応チェーンの変更は未公表のまま。', - sourceUrl: 'https://www.netstars.co.jp/news/', - }, - { - // 週次更新 第 5 回 (2026-09-23)。累計発行額は JPYC 社の自社発表 (PR TIMES 一次配信)。 - // 発行チェーン (4) と facts は不変なので diffs は付けない。 - date: '2026-09-18', - collectedAt: '2026-09-23', - scopes: ['jpyc-services'], - slug: 'jpyc', - changeType: 'updated', - changeCategory: 'update', - assets: ['JPYC'], - summary: - 'JPYC Inc. announced on 2026-09-18 that cumulative issuance of the JPYC yen stablecoin has passed JPY 10 billion. Issuance chains are unchanged: Avalanche, Ethereum, Polygon and Kaia (4 chains).', - summaryJa: - 'JPYC 株式会社が 2026-09-18、日本円ステーブルコイン JPYC の累計発行額が 100 億円を突破したと発表。発行チェーンは Avalanche・Ethereum・Polygon・Kaia の 4 チェーンで変更なし。', - sourceUrl: 'https://prtimes.jp/main/html/rd/p/000000331.000054018.html', - }, - { - // 週次更新 第 5 回 (2026-09-23)。金融庁の登録一覧 PDF で再確認。 - date: '2026-09-23', - collectedAt: '2026-09-23', - scopes: ['jpyc-services'], - slug: 'coincheck', - changeType: 'verified', - assets: ['USDC'], - summary: - 'Re-verified against the FSA registry of Electronic Payment Instruments Exchange Service Providers (still as of 2026-08-27): two registrants, SBI VC Trade (No. 00001) and Coincheck (No. 00002, USDC); no new registration.', - summaryJa: - '金融庁の電子決済手段等取引業者登録一覧 (令和 8 年 8 月 27 日現在のまま) で再確認: 登録は SBI VC トレード (第00001号) とコインチェック (第00002号・USDC) の 2 社で、新規登録なし。', - sourceUrl: 'https://www.fsa.go.jp/menkyo/menkyoj/denshikessaisyudan.pdf', - }, - { - date: '2026-09-23', - scopes: ['stablecoin-payments'], - provider: 'HashPort (Osaka Pref. subsidy)', - changeType: 'verified', - assets: ['JPYC', 'USDC'], - summary: - 'Re-verified on the Osaka Prefecture page (still last updated 2026-08-26): HashPort pilot schedule unchanged.', - summaryJa: - '大阪府公式ページ (更新日 2026-08-26 のまま) で再確認: HashPort の実証予定に変更なし。', - sourceUrl: - 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', - }, - { - date: '2026-09-23', - scopes: ['stablecoin-payments'], - provider: 'Mina Wallet / Sumitomo Mitsui Card (Osaka Pref. subsidy)', - changeType: 'verified', - assets: ['JPYC', 'USDC'], - summary: - 'Re-verified on the Osaka Prefecture page (still last updated 2026-08-26): Mina Wallet / Sumitomo Mitsui Card pilot schedule unchanged.', - summaryJa: - '大阪府公式ページ (更新日 2026-08-26 のまま) で再確認: マイナウォレット / 三井住友カードの実証予定に変更なし。', - sourceUrl: - 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', - }, - { - date: '2026-09-23', - scopes: ['stablecoin-payments'], - provider: 'Mi&T (Osaka Pref. subsidy)', - changeType: 'verified', - assets: ['JPYC'], - summary: - 'Re-verified on the Osaka Prefecture page (still last updated 2026-08-26): Mi&T pilot (merchant fee 1.0%, planned mid-November 2026 to mid-March 2027) unchanged.', - summaryJa: - '大阪府公式ページ (更新日 2026-08-26 のまま) で再確認: Mi&T の実証 (手数料 1.0%・2026 年 11 月中旬〜2027 年 3 月中旬予定) に変更なし。', - sourceUrl: - 'https://www.pref.osaka.lg.jp/o020060/kikaku/osaka-kokusaikinyu/senkuteki_hojokin.html', - }, - { - date: '2026-09-23', - scopes: ['stablecoin-payments'], - provider: 'NetStars Stablecoin Pay', - changeType: 'verified', - assets: ['JPYC', 'USDT'], - summary: - 'Re-verified on the NetStars news list: no announcement after the 2026-09-11 Kaia MOU; no launch date, fee or chain change published for Stablecoin Pay.', - summaryJa: - 'ネットスターズ公式ニュース一覧で再確認: 2026-09-11 の Kaia MOU 以降の発表なし。Stablecoin Pay の稼働日・手数料・対応チェーンの変更は未公表のまま。', - sourceUrl: 'https://www.netstars.co.jp/news/', - }, -]; - -// ディレクトリ初期公開日。baseline の 'added' はこの固定日に立てる — entry.updatedAt 由来に -// すると週次更新で updatedAt を進めた瞬間に「追加日」まで動いてしまう (第 1 回運用で発覚)。 -const BASELINE_DATE = '2026-07-13'; +// changelog の語彙と型は changelogTypes.ts (R9a)。利用側の import 経路を保つため従来どおりここから公開する。 +export { + SERVICE_CHANGE_CATEGORIES, + SERVICE_CHANGE_SCOPES, + SERVICE_CHANGE_TYPES, + SERVICE_DIFF_FIELDS, +} from './changelogTypes'; +export type { + ServiceChangeCategory, + ServiceChangeDiff, + ServiceChangeEvent, + ServiceChangeScope, + ServiceChangeType, + ServiceDiffField, +} from './changelogTypes'; /** * 初期 baseline: MANUAL_CHANGELOG に 'added' を持たないエントリを BASELINE_DATE の @@ -809,6 +188,20 @@ export function takeDeltaByDateGroups( + events: readonly T[], + since: string, + limit: number, +): { taken: T[]; hasMore: boolean; nextChangedSince: string | null } { + const matched = sortByDeltaEffectiveDate(events.filter((event) => deltaEffectiveDate(event) >= since)); + return takeDeltaByDateGroups(matched, limit); +} + /** 監視ビュー 1 行 (editorial の全文は含めない — 詳細は directory 本体商品の領分)。 */ export type ServiceMonitorRow = { slug: string; @@ -910,9 +303,7 @@ export function createServiceMonitorEnvelope( changes = changelog.slice(-query.limit); services = published.map((entry) => toRow(entry, snapshot)); } else { - const since = query.changedSince as string; - const matched = sortByDeltaEffectiveDate(changelog.filter((event) => deltaEffectiveDate(event) >= since)); - const page = takeDeltaByDateGroups(matched, query.limit); + const page = takeDeltaSince(changelog, query.changedSince as string, query.limit); changes = page.taken; hasMore = page.hasMore; if (page.nextChangedSince !== null) nextChangedSince = page.nextChangedSince; diff --git a/tests/app/api/monitor-wire-golden.test.ts b/tests/app/api/monitor-wire-golden.test.ts new file mode 100644 index 00000000..3248f5cd --- /dev/null +++ b/tests/app/api/monitor-wire-golden.test.ts @@ -0,0 +1,471 @@ +// JPYC Service Monitor / Japan Stablecoin Payment Monitor の **応答 byte を凍結する golden** (R9a/R9b・2026-09-24)。 +// +// 対象: 無料 teaser 2 本 (/api/jpyc/services/teaser・/api/stablecoin-payments/teaser) と有料 Monitor 4 本 +// (JPYC 版 /api/paid/jpyc/services・/api/paid/stablecoin-payments = facilitator gate 経由、USDC 版 +// /api/paid/usdc/jpyc/services・/api/paid/usdc/stablecoin-payments = test mode)。 +// +// なぜ意味比較 (toEqual) では足りないか: 有料 Monitor は売っている商品で、キー順・省略と null の区別・ +// 並び順が変わるだけで買い手エージェントの diff/dedupe が壊れ得る。既存の monitor/teaser test は構造と +// 振る舞いを見るもので、応答全体の byte は固定していない。ここでは **未整列の raw text の sha256** を固定する。 +// +// データは固定の fixture (changelogData / data / paymentProviders を mock) — 週次更新で実データが +// 増えても本 golden は動かない。golden が落ちたら「応答を変えた」ことの証明であり、期待値を合わせて +// 通してはいけない (意図して wire を変える PR だけが更新してよい。理由を commit に書く)。 +// 生成: GOLDEN_RECORD=<出力 path> で実行すると期待値を assert せず実測の map を書き出す。 + +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { createHash } from 'node:crypto'; +import { writeFileSync } from 'node:fs'; +import { getAddress } from 'viem'; +import { NextResponse } from 'next/server'; + +type FixtureEvent = Record; +type FixtureEntry = { slug: string; sourceUrl: string } & Record; + +const fixture = vi.hoisted(() => ({ + changelog: [] as FixtureEvent[], + entries: [] as FixtureEntry[], + providers: [] as Record[], + snapshot: {} as Record | null, +})); + +vi.mock('@/lib/directory/changelogData', () => ({ + get MANUAL_CHANGELOG() { + return fixture.changelog; + }, + BASELINE_DATE: '2026-07-13', +})); +vi.mock('@/lib/directory/data', () => ({ + get DIRECTORY_ENTRIES() { + return fixture.entries; + }, +})); +vi.mock('@/lib/directory/paymentProviders', async (original) => ({ + ...(await original()), + get PAYMENT_PROVIDERS() { + return fixture.providers; + }, +})); +vi.mock('@/lib/directory/verification', () => ({ + readDirectoryVerificationSnapshot: async () => fixture.snapshot, +})); + +const paidMocks = vi.hoisted(() => ({ verify: vi.fn(), settle: vi.fn(), lookup: vi.fn(), promote: vi.fn() })); +vi.mock('@/app/api/facilitator/verify/route', () => ({ POST: paidMocks.verify })); +vi.mock('@/app/api/facilitator/settle/route', () => ({ POST: paidMocks.settle })); +vi.mock('@/lib/x402/paymentRedelivery', async (original) => ({ + ...(await original()), + lookupPaymentRedelivery: paidMocks.lookup, + claimPaymentRedelivery: async () => ({ kind: 'unavailable' }), + promotePaymentRedelivery: paidMocks.promote, +})); + +const SELLER = getAddress('0x1234567890123456789012345678901234567890'); +const FORWARDER = getAddress('0x752b7aad0089286eb7b553d84d05233d80c9fcb4'); +const FEE_RECEIVER = getAddress('0x428483d2bd5E9f0e9f8E9f8e9F8E9F8E9f8e9F8e'); +const JPYC_AMOY = getAddress('0x00000000000000000000000000000000000Ca11a'); +const SETTLEMENT = { success: true, transaction: `0x${'ab'.repeat(32)}`, network: 'eip155:80002', payer: SELLER }; +const NOW = '2026-09-24T03:04:05.678Z'; + +function entry(slug: string, status: string, extra: { tokens?: string[]; chains?: string[]; nameJa?: string } = {}): FixtureEntry { + return { + slug, + name: `Name ${slug}`, + nameJa: extra.nameJa ?? '', + status, + sourceUrl: `https://example.com/${slug}`, + sourceType: 'official', + verifiedAt: '2026-07-13', + updatedAt: '2026-07-13', + attribution: `Attr ${slug.length % 2}`, + facts: { + description: 'd', + category: 'payment', + tags: [], + tokens: extra.tokens ?? ['jpyc'], + chains: extra.chains ?? ['polygon'], + languages: ['ja'], + supportsJpyc: true, + supportsUsdc: (extra.tokens ?? []).includes('usdc'), + supportsX402: false, + supportsMcp: false, + }, + editorial: { summaryJa: 's', summaryEn: 's' }, + }; +} + +function provider(name: string, announcedAt: string, slug?: string): Record { + return { + provider: name, + ...(slug ? { slug } : {}), + stage: 'pilot', + assets: ['JPYC'], + chains: [], + settlementCurrency: null, + merchantFee: null, + integrations: [], + posIntegration: null, + region: 'Japan', + announcedAt, + startedAt: null, + plannedPeriod: null, + sourceUrl: `https://example.com/p/${name.length}`, + verifiedAt: '2026-08-01', + }; +} + +// 小さな履歴: backfill (collectedAt > date)・同じ実効日の複数イベント・複数スコープのイベント・ +// slug だけのイベント (有料版は entry から provider/assets/chains を投影するが teaser は投影しない)・ +// archived の removed・別スコープの 'added' が baseline を消す癖、を含む。 +const SMALL_CHANGELOG: FixtureEvent[] = [ + { date: '2026-07-01', scopes: ['stablecoin-payments'], provider: 'Alpha Pay', changeType: 'added', changeCategory: 'partnership', assets: ['JPYC'], summary: 'Alpha added.', summaryJa: 'Alpha 追加。', sourceUrl: 'https://example.com/alpha' }, + { date: '2026-07-13', collectedAt: '2026-08-20', scopes: ['jpyc-services'], slug: 'svc-b', changeType: 'updated', changeCategory: 'chains_change', summary: 'B chains.', summaryJa: 'B チェーン。', diffs: [{ field: 'chains', previousValue: ['polygon'], currentValue: ['polygon', 'kaia'] }] }, + { date: '2026-08-10', scopes: ['stablecoin-payments'], slug: 'svc-a', changeType: 'added', changeCategory: 'service_launch', summary: 'A launched.', summaryJa: 'A 開始。' }, + { date: '2026-08-10', scopes: ['jpyc-services', 'stablecoin-payments'], slug: 'svc-a', changeType: 'updated', summary: 'A updated.', summaryJa: 'A 更新。', sourceUrl: 'https://example.com/a-news' }, + { date: '2026-08-12', collectedAt: '2026-08-20', scopes: ['stablecoin-payments'], provider: 'Beta Wallet', changeType: 'updated', changeCategory: 'fee_change', assets: ['USDC'], chains: ['base'], summary: 'Beta fee.', summaryJa: 'Beta 手数料。', sourceUrl: 'https://example.com/beta', diffs: [{ field: 'fee', previousValue: '1%', currentValue: '0.5%', effectiveAt: '2026-09-01' }] }, + { date: '2026-08-20', scopes: ['jpyc-services'], slug: 'svc-c', changeType: 'verified', summary: 'C verified.', summaryJa: 'C 再確認。' }, + { date: '2026-08-25', scopes: ['stablecoin-payments'], provider: 'Alpha Pay', changeType: 'verified', assets: ['JPYC'], summary: 'Alpha verified.', summaryJa: 'Alpha 再確認。', sourceUrl: 'https://example.com/alpha' }, + { date: '2026-08-26', collectedAt: '2026-08-26', scopes: ['jpyc-services'], slug: 'svc-gone', changeType: 'removed', summary: 'Gone removed.', summaryJa: 'Gone 削除。' }, +]; +const SMALL_ENTRIES: FixtureEntry[] = [ + entry('svc-a', 'published', { tokens: ['usdc', 'jpyc'], chains: ['base', 'polygon'], nameJa: 'A 社' }), + entry('svc-b', 'published'), + entry('svc-c', 'published'), + entry('svc-d', 'published'), + entry('svc-gone', 'archived'), +]; +const SMALL_PROVIDERS = [provider('Alpha Pay', '2026-07-01'), provider('Beta Wallet', '2026-08-12'), provider('Name svc-a', '2026-08-10', 'svc-a'), provider('Gamma', '2026-06-01')]; + +// 有料版の上限 (SERVICE_MONITOR_MAX_LIMIT=200) を超える履歴: baseline 250 件 (全て 2026-07-13) + +// date は古いが記録は最新の backfill。snapshot view (date 順の末尾 200 件) から backfill が落ちても、 +// teaser の latestRecordedAt/totalEvents は全履歴から出ることを固定する。 +const LONG_CHANGELOG: FixtureEvent[] = [ + { date: '2026-01-05', collectedAt: '2026-09-10', scopes: ['jpyc-services', 'stablecoin-payments'], slug: 'syn-001', changeType: 'updated', summary: 'Old news recorded late.', summaryJa: '古い発表を後から記録。' }, + ...SMALL_CHANGELOG, +]; +const LONG_ENTRIES: FixtureEntry[] = [ + ...SMALL_ENTRIES, + ...Array.from({ length: 250 }, (_, i) => entry(`syn-${String(i).padStart(3, '0')}`, 'published')), +]; + +const HISTORIES = { + small: { changelog: SMALL_CHANGELOG, entries: SMALL_ENTRIES, providers: SMALL_PROVIDERS }, + empty: { changelog: [], entries: [], providers: [] }, + long: { changelog: LONG_CHANGELOG, entries: LONG_ENTRIES, providers: SMALL_PROVIDERS }, +} as const; +type HistoryName = keyof typeof HISTORIES; + +const QUERIES = [ + '', + '?limit=2', + '?changedSince=2020-01-01', + '?changedSince=2020-01-01&limit=1', + '?changedSince=2026-08-10', + '?changedSince=2026-08-11&limit=1', + '?changedSince=2026-08-20', + '?changedSince=2026-08-21', + '?changedSince=2026-09-11', +]; + +// 期待値 = 分割前 (origin b13da8c2 のロジック) で記録した raw text の sha256 (GOLDEN_RECORD で生成)。 +const GOLDEN: Record = { + 'empty /api/jpyc/services/teaser': + 'b824d113487f6e33fd43b8adfd1578d3194aaa8e44ef6e652f2cb9c3178e66ad', + 'empty /api/paid/usdc/jpyc/services': + 'c048e3a79d15174fce1fbf989b3ae866b2540bce152c33d55a21e2686fc1b0e0', + 'empty /api/paid/usdc/jpyc/services?changedSince=2020-01-01': + '24037fc08998a5b8b54acb3c29a828d9971b135e916f123ff38505f751668f13', + 'empty /api/paid/usdc/jpyc/services?changedSince=2020-01-01&limit=1': + '40a581abe878607a8156eace54d8edd8f8d001cdb63c21b658df4f674e5aa13d', + 'empty /api/paid/usdc/jpyc/services?changedSince=2026-08-10': + '8f82115bd4bcc79d7b6e4bc636f5e24eee63d7b0e11c4f25501083eee17cba56', + 'empty /api/paid/usdc/jpyc/services?changedSince=2026-08-11&limit=1': + 'a6b69318601254083c1b75876738a77734f9e70772de513feb88ad8267939d0a', + 'empty /api/paid/usdc/jpyc/services?changedSince=2026-08-20': + '878838d555a468a83cc502848f1a1ffd96b4a45f292338cf1d85218243c6dbc4', + 'empty /api/paid/usdc/jpyc/services?changedSince=2026-08-21': + 'f78855c545a77a509efb3e5f80bf8ff07ed6b9339fa0b5d0caa900dcd68b56b9', + 'empty /api/paid/usdc/jpyc/services?changedSince=2026-09-11': + 'f1bd78027182cbce24238b31983f021f6704acbe885249f1c356ff6a5c1a0868', + 'empty /api/paid/usdc/jpyc/services?limit=2': + 'd20cf6121626b616cffefdbc60e1755a169e45e81d2232883624f99400295d2b', + 'empty /api/paid/usdc/stablecoin-payments': + 'e9547ec9c422b1251a1145acded3f2a40af4714f68070a4fb656b77133b8b66f', + 'empty /api/paid/usdc/stablecoin-payments?changedSince=2020-01-01': + '26322abe02d2757a197cbca6fb72de8f05ab5a45f7b07ee3cb0ba77e53ded85a', + 'empty /api/paid/usdc/stablecoin-payments?changedSince=2020-01-01&limit=1': + 'f461365d898153c417dc32c262d6ab8bd1d1a72f91de8bbbf05344a7e3379231', + 'empty /api/paid/usdc/stablecoin-payments?changedSince=2026-08-10': + '7ca50629875035af04530ccfec7c806b82542af21c04376ec74e22f0bd97c520', + 'empty /api/paid/usdc/stablecoin-payments?changedSince=2026-08-11&limit=1': + 'f4a0441f4e0f5c058ce93f589619a83dd1d3a5fe141a0be73541c5fdcecff425', + 'empty /api/paid/usdc/stablecoin-payments?changedSince=2026-08-20': + '06ea844189d1f88ffaa3e20808a4cf3c2c58a6f3f82132f492528f106f5c1d2a', + 'empty /api/paid/usdc/stablecoin-payments?changedSince=2026-08-21': + 'ef400b3dc9af6373cf3112874a3eb7ce79d7518aa00174c35f87b52e67486e66', + 'empty /api/paid/usdc/stablecoin-payments?changedSince=2026-09-11': + 'eeb25f06065cebb52637a619a94dc1743f721ed2e62eeb7e5b4041c79cf821f0', + 'empty /api/paid/usdc/stablecoin-payments?limit=2': + '1311e01f9f865b10d25a8c42cda706e40309b63d59a7d643e56da8e1a6e1f0f3', + 'empty /api/stablecoin-payments/teaser': + 'b8e66b10e1ccb728e2d7af82a0759e7d0642782613529f1b381f0b216a39fb6c', + 'long /api/jpyc/services/teaser': + '49c70329b7dbc3da5a35ce6d327a7cef77a8819886452262c5243def37ca01e9', + 'long /api/paid/usdc/jpyc/services': + '2c5b3cdaca49cc2255e41aca36887c6ea7ac0ff9e46488dbb74c889085303a4d', + 'long /api/paid/usdc/jpyc/services?changedSince=2020-01-01': + 'afafd2958656f1ed62741b30035c84601b84c269a0d800d4b95af471846f58c6', + 'long /api/paid/usdc/jpyc/services?changedSince=2020-01-01&limit=1': + '1a711f5ba8e232155c8887bc211931b1baf920fde0697544776a85e951b98006', + 'long /api/paid/usdc/jpyc/services?changedSince=2026-08-10': + '8566cefb8bc9dd0e33151823021c7199b450a744b95e27422f9683c6af757a92', + 'long /api/paid/usdc/jpyc/services?changedSince=2026-08-11&limit=1': + 'c965c63c2ca17cb045d3f1cb9765c53781c4afc0876155481fc287f01747e7c1', + 'long /api/paid/usdc/jpyc/services?changedSince=2026-08-20': + '4e39c85d42d5999bef937cfb485e429375109ef7f0285a66961532fc451622ca', + 'long /api/paid/usdc/jpyc/services?changedSince=2026-08-21': + 'eb24c8544706d8e30dd63f5557e22117d3a8422fc79a81e52a71a5ba4bdd0a41', + 'long /api/paid/usdc/jpyc/services?changedSince=2026-09-11': + '4a14550685f3b1fe3b30789528a0de3bef432d9e300ea7bf2d11a80eeb738242', + 'long /api/paid/usdc/jpyc/services?limit=2': + '30072bc8583efc1356e1cdee27b3b933da38274eb16191a3d51b08d6e8a13edc', + 'long /api/paid/usdc/stablecoin-payments': + '9aa903c65067f5c972585d5114790fdf4a2e3a486113b6febf6f1f3ee4745934', + 'long /api/paid/usdc/stablecoin-payments?changedSince=2020-01-01': + 'b1c2af66f81073492f1eab46ce71592a09bf70245120b6589527dae000d8e4c1', + 'long /api/paid/usdc/stablecoin-payments?changedSince=2020-01-01&limit=1': + 'c6841f0413e547fcb06cbbf786da3623962b442c658099b35e0c2ff9fc4bcc2f', + 'long /api/paid/usdc/stablecoin-payments?changedSince=2026-08-10': + 'b50fbddd998f3ea8d5c262c82fb564744490e2b579607b52481532461d88c451', + 'long /api/paid/usdc/stablecoin-payments?changedSince=2026-08-11&limit=1': + 'f0d568090b4a01acddec443929fb1082181220db95f3a86b45ed9d7c6ff00140', + 'long /api/paid/usdc/stablecoin-payments?changedSince=2026-08-20': + 'd8a2e4b349104d391319c654d3efe34fa554aa81ed48c02cf1bb62086a76a60a', + 'long /api/paid/usdc/stablecoin-payments?changedSince=2026-08-21': + '7af09af43177d3801a0ad15c5e8d6d92de4e46eb8f23791315e1d3c4c8032854', + 'long /api/paid/usdc/stablecoin-payments?changedSince=2026-09-11': + 'c45663696754d6a90ff51f9243ae85a992e257954f6c325166781480c7a5d661', + 'long /api/paid/usdc/stablecoin-payments?limit=2': + '7bf4363670d9b811ad2b3f17103f1f8fdafbec18fa4133cee1063f342a6c6106', + 'long /api/stablecoin-payments/teaser': + '169c41a21f67fddb9d9a8e6c7f9dbfc8b3dbbc964073bd6bf821841153dbedf9', + 'small /api/jpyc/services/teaser': + '7ac96b885cc88c63a6d2a5b4bfb618559009d7526d5c08e94dc9a210ff0e46eb', + 'small /api/paid/usdc/jpyc/services': + 'ebe12c47e90a4551d146db265c9319c37e60718c6e5cb17a81981fefe7dcfad9', + 'small /api/paid/usdc/jpyc/services?changedSince=2020-01-01': + '29740f3a13e65c0b67394e3cfc17c5a0db4777c079bb9e517b1b15502844758b', + 'small /api/paid/usdc/jpyc/services?changedSince=2020-01-01&limit=1': + 'c33cb3386eab1cfdea85355e901e1accaa2e8f4f014881f6d636f2daaf51e7c6', + 'small /api/paid/usdc/jpyc/services?changedSince=2026-08-10': + '001be34f726a1c2c5033f4d07b91bd72b36ebb6042e83c74a829cb5ee7401212', + 'small /api/paid/usdc/jpyc/services?changedSince=2026-08-11&limit=1': + '798ea1580b6e744082cd2bed7b82952917eaac7735784789c853221b1126c6ce', + 'small /api/paid/usdc/jpyc/services?changedSince=2026-08-20': + '64cafbffd842cbd384c713c79cbba72431e49c9b07f5f07478e0331f90821835', + 'small /api/paid/usdc/jpyc/services?changedSince=2026-08-21': + '56dda43fdcd263dfc91ef485002d735944b627cde5771f251e99be7a6ee9b270', + 'small /api/paid/usdc/jpyc/services?changedSince=2026-09-11': + 'c2c42e31c3016926fd123b9d3a8e403a3ca4d6e1d3a8fe4b24088662f50807dd', + 'small /api/paid/usdc/jpyc/services?limit=2': + 'dcb2a2364806f51eca814ca7b59c68700e58b453430cdb28b999a21f900a0b47', + 'small /api/paid/usdc/stablecoin-payments': + '3a81c6e0b03c43a7c5df23442a30daae905bb71d13e60339a70247282777420e', + 'small /api/paid/usdc/stablecoin-payments?changedSince=2020-01-01': + 'c42bec3bace5c80872703d4e90646c4b41f584bd5d8ce520d201a6b9c6c4d69d', + 'small /api/paid/usdc/stablecoin-payments?changedSince=2020-01-01&limit=1': + '352f380ef116ee906454a33e86108b75b5303f2c4388b6223ff0026cd2419e85', + 'small /api/paid/usdc/stablecoin-payments?changedSince=2026-08-10': + 'f6f5f95ba8c6f416a6c9526e3b7ded874c2bb5695311c881b77b32b53be63b7a', + 'small /api/paid/usdc/stablecoin-payments?changedSince=2026-08-11&limit=1': + '0aa736786af7e27e87e39ca5edda93e1b558189a2aa8e84124fa8ad34c90243b', + 'small /api/paid/usdc/stablecoin-payments?changedSince=2026-08-20': + '48df98b3202d5d76d616d3abcc17e737f39087da3d3e621c1ff663ae0ae1a1b4', + 'small /api/paid/usdc/stablecoin-payments?changedSince=2026-08-21': + '0654539bb913b9943c41e47a2a7792528f1837d4631bf1a4424925ceb216b29b', + 'small /api/paid/usdc/stablecoin-payments?changedSince=2026-09-11': + '8961f0650836b88f4c580149355acd4d2d00fdab89d88d9bd18aa2393c971869', + 'small /api/paid/usdc/stablecoin-payments?limit=2': + '8149806b3135aead3dc9b5080441151327c19dbfb57f1b0c722e6407936a2416', + 'small /api/stablecoin-payments/teaser': + 'f1ad9474e0de4c4218a0d718fd21eb1562aeafd94d6d2f1ecd2084fa499bd998', +}; + +type Route = { GET: (req?: Request) => Promise }; +const ROUTES: Record Promise> = { + '/api/jpyc/services/teaser': () => import('@/app/api/jpyc/services/teaser/route'), + '/api/stablecoin-payments/teaser': () => import('@/app/api/stablecoin-payments/teaser/route'), + '/api/paid/usdc/jpyc/services': () => import('@/app/api/paid/usdc/jpyc/services/route'), + '/api/paid/jpyc/services': () => import('@/app/api/paid/jpyc/services/route'), + '/api/paid/usdc/stablecoin-payments': () => import('@/app/api/paid/usdc/stablecoin-payments/route'), + '/api/paid/stablecoin-payments': () => import('@/app/api/paid/stablecoin-payments/route'), +}; +const actual: Record = {}; + +function sha(text: string): string { + return createHash('sha256').update(text).digest('hex'); +} + +function useHistory(name: HistoryName): void { + vi.useFakeTimers({ toFake: ['Date'] }); + vi.setSystemTime(new Date(NOW)); + const h = HISTORIES[name]; + fixture.changelog = [...h.changelog]; + fixture.entries = [...h.entries]; + fixture.providers = [...h.providers]; + // 検証スナップショット: 一致 (ok:true) / sourceUrl 不一致 (行では null になる) / 判定不能 (ok:null)。 + fixture.snapshot = h.entries.length > 0 + ? { + 'svc-a': { checkedAt: '2026-09-20T00:00:00.000Z', ok: true, sourceUrl: 'https://example.com/svc-a' }, + 'svc-b': { checkedAt: '2026-09-20T00:00:00.000Z', ok: false, sourceUrl: 'https://mismatch.example/' }, + 'svc-c': { checkedAt: '2026-09-21T00:00:00.000Z', ok: null, sourceUrl: 'https://example.com/svc-c' }, + } + : {}; + paidMocks.verify.mockImplementation(async () => NextResponse.json({ isValid: true, payer: SELLER })); + paidMocks.settle.mockImplementation(async () => NextResponse.json(SETTLEMENT)); + paidMocks.lookup.mockResolvedValue({ kind: 'missing' }); + paidMocks.promote.mockResolvedValue({ kind: 'unavailable' }); +} + +async function load(path: string, rail: 'free' | 'usdc' | 'jpyc'): Promise { + vi.stubEnv('NEXT_PUBLIC_ENABLE_WEB3_DIRECTORY', '1'); + vi.stubEnv('X402_PAY_TO_ADDRESS', SELLER); + if (rail === 'usdc') { + vi.stubEnv('X402_NETWORK', 'base'); + vi.stubEnv('X402_TEST_MODE', 'true'); + } + if (rail === 'jpyc') { + vi.stubEnv('NEXT_PUBLIC_ENABLE_X402_FACILITATOR', '1'); + vi.stubEnv('NEXT_PUBLIC_JPYC_FORWARDER_AMOY', FORWARDER); + vi.stubEnv('NEXT_PUBLIC_FEE_RECEIVER_ADDRESS', FEE_RECEIVER); + vi.stubEnv('NEXT_PUBLIC_ENABLE_USAGE_FEE', ''); + vi.stubEnv('NEXT_PUBLIC_JPYC_TESTNET_ADDRESS', JPYC_AMOY); + vi.stubEnv('X402_FEE_BPS', '100'); + vi.stubEnv('X402_FEE_FLOOR_JPYC', '1'); + } + vi.resetModules(); + return (await ROUTES[path]()) as unknown as Route; +} + +/** JPYC 版は facilitator gate を通して content を得る (verify/settle は mock)。 */ +async function paidJpyc(route: Route, url: string): Promise { + const challenge = await route.GET(new Request(url)); + expect(challenge.status).toBe(402); + const required = JSON.parse(Buffer.from(challenge.headers.get('PAYMENT-REQUIRED')!, 'base64').toString()); + const payload = { + signature: `0x${'0'.repeat(63)}1${'0'.repeat(63)}21b`, + authorization: { from: SELLER, validAfter: '0', validBefore: '9999999999', intentSalt: `0x${'22'.repeat(32)}` }, + }; + const payment = { x402Version: 2, resource: required.resource, accepted: required.accepts[0], payload }; + return route.GET(new Request(url, { headers: { 'PAYMENT-SIGNATURE': Buffer.from(JSON.stringify(payment)).toString('base64') } })); +} + +function pin(key: string, text: string): void { + actual[key] = sha(text); + if (process.env.GOLDEN_RECORD) return; + expect(actual[key], `${key}\n${text}`).toBe(GOLDEN[key]); +} + +afterEach(() => { + vi.unstubAllEnvs(); + vi.useRealTimers(); + vi.resetModules(); + if (process.env.GOLDEN_RECORD) { + const sorted = Object.fromEntries(Object.keys(actual).sort().map((k) => [k, actual[k]])); + writeFileSync(process.env.GOLDEN_RECORD, JSON.stringify(sorted, null, 2)); + } +}); + +describe.each(Object.keys(HISTORIES) as HistoryName[])('monitor wire golden (%s history)', (history) => { + it.each([ + ['/api/jpyc/services/teaser'], + ['/api/stablecoin-payments/teaser'], + ])('teaser %s', async (path) => { + useHistory(history); + const route = await load(path, 'free'); + const res = await route.GET(); + expect(res.status).toBe(200); + expect(res.headers.get('cache-control')).toBe('public, s-maxage=300, stale-while-revalidate=600'); + pin(`${history} ${path}`, await res.text()); + }); + + it.each([ + ['/api/paid/usdc/jpyc/services', '/api/paid/jpyc/services'], + ['/api/paid/usdc/stablecoin-payments', '/api/paid/stablecoin-payments'], + ])('paid %s (JPYC 版 %s と byte 一致)', async (usdcPath, jpycPath) => { + useHistory(history); + const usdc = await load(usdcPath, 'usdc'); + const usdcTexts: string[] = []; + for (const qs of QUERIES) { + const res = await usdc.GET(new Request(`https://open-pay.jp${usdcPath}${qs}`)); + expect(res.status).toBe(200); + const text = await res.text(); + usdcTexts.push(text); + pin(`${history} ${usdcPath}${qs}`, text); + } + // JPYC 版は同じ envelope を別の支払い経路で返す = content の byte は USDC 版と同一でなければならない。 + const jpyc = await load(jpycPath, 'jpyc'); + for (const [i, qs] of QUERIES.entries()) { + const res = await paidJpyc(jpyc, `https://open-pay.jp${jpycPath}${qs}`); + expect(res.status).toBe(200); + expect(await res.text()).toBe(usdcTexts[i]); + } + }); +}); + +// 読める形の補助 assert (golden の sha だけでは意図が読めないので、R9b で守る区別を明示する)。 +describe('teaser の意味 (small history)', () => { + it('stablecoin teaser は scope を除いた raw event を返す — 有料版の provider 投影行とは別物', async () => { + useHistory('small'); + const teaser = await (await (await load('/api/stablecoin-payments/teaser', 'free')).GET()).json(); + const paid = await (await (await load('/api/paid/usdc/stablecoin-payments', 'usdc')).GET( + new Request('https://open-pay.jp/api/paid/usdc/stablecoin-payments'), + )).json(); + // 実効日順 (08-10 A updated → 08-20 Beta (backfill) → 08-25 Alpha verified) の末尾 3 件。 + expect(teaser.latestChanges.map((e: { date: string }) => e.date)).toEqual(['2026-08-10', '2026-08-12', '2026-08-25']); + const rawA = teaser.latestChanges[0]; + expect(rawA).toEqual({ date: '2026-08-10', slug: 'svc-a', changeType: 'updated', summary: 'A updated.', summaryJa: 'A 更新。', sourceUrl: 'https://example.com/a-news' }); + const rowA = paid.changes.find((c: { date: string; changeType: string }) => c.date === '2026-08-10' && c.changeType === 'updated'); + expect(rowA).toMatchObject({ provider: 'Name svc-a', assets: ['USDC', 'JPYC'], chains: ['base', 'polygon'] }); + expect(rowA).not.toHaveProperty('slug'); + expect(teaser.latestRecordedAt).toBe('2026-08-25'); + expect(teaser.totalEvents).toBe(paid.totalEvents); + expect(teaser.totalEvents).toBe(5); + }); + + it('jpyc teaser: 同じ実効日のイベントは changelog 順のまま・latestRecordedAt は backfill の記録日', async () => { + useHistory('small'); + const teaser = await (await (await load('/api/jpyc/services/teaser', 'free')).GET()).json(); + expect(teaser.latestChanges.map((e: { slug: string; date: string }) => `${e.slug}|${e.date}`)).toEqual([ + 'svc-b|2026-07-13', + 'svc-c|2026-08-20', + 'svc-gone|2026-08-26', + ]); + expect(teaser.latestRecordedAt).toBe('2026-08-26'); + for (const event of teaser.latestChanges) expect(event).not.toHaveProperty('scopes'); + }); + + it('long history: 有料 snapshot view から落ちる backfill でも teaser は全履歴から最新と数える', async () => { + useHistory('long'); + const teaser = await (await (await load('/api/jpyc/services/teaser', 'free')).GET()).json(); + const paid = await (await (await load('/api/paid/usdc/jpyc/services', 'usdc')).GET( + new Request('https://open-pay.jp/api/paid/usdc/jpyc/services'), + )).json(); + expect(paid.hasMore).toBe(true); + expect(paid.changes).toHaveLength(200); + expect(paid.changes.some((c: { slug: string; date: string }) => c.slug === 'syn-001' && c.date === '2026-01-05')).toBe(false); + expect(teaser.totalEvents).toBeGreaterThan(200); + expect(teaser.latestRecordedAt).toBe('2026-09-10'); + expect(teaser.latestChanges.at(-1)).toMatchObject({ slug: 'syn-001', date: '2026-01-05', collectedAt: '2026-09-10' }); + }); + + it('empty history: latestChanges [] / latestRecordedAt null / totalEvents 0', async () => { + useHistory('empty'); + for (const path of ['/api/jpyc/services/teaser', '/api/stablecoin-payments/teaser']) { + const body = await (await (await load(path, 'free')).GET()).json(); + expect(body.latestChanges).toEqual([]); + expect(body.latestRecordedAt).toBeNull(); + expect(body.totalEvents).toBe(0); + } + }); +}); diff --git a/tests/lib/directory/serviceMonitor.test.ts b/tests/lib/directory/serviceMonitor.test.ts index 372b6db2..fc698165 100644 --- a/tests/lib/directory/serviceMonitor.test.ts +++ b/tests/lib/directory/serviceMonitor.test.ts @@ -403,7 +403,7 @@ describe('ServiceChangeEvent.date / collectedAt (発表日と収集日の分離) // E26: SERVICE_MONITOR_OUTPUT (paidResources.ts の Bazaar/openapi 向け宣言) の // changes.items.required は「実際のイベントに常に存在するキー」の部分集合でなければならない。 // slug はディレクトリエントリに紐づかない業界イベント (provider のみ) では欠落し得るので -// 必須にしてはいけない (serviceMonitor.ts:75-77 の slug?/provider?)。 +// 必須にしてはいけない (changelogTypes.ts の ServiceChangeEvent の slug?/provider?)。 describe('SERVICE_MONITOR_OUTPUT スキーマ (E26): required ⊆ 常に存在するキー', () => { const itemSchema = JPYC_SERVICES_RESOURCE.outputSchema.output as unknown as { properties: {