コンテンツにスキップ

アーキテクチャ概要

Miseru は Cloudflare 上の複数の Worker で構成されています。役割を分離することで、配信(高頻度・低レイテンシ)とアップロード・管理(認証あり)を独立に運用しています。

コンポーネントドメイン役割
miseru-apiapi.miseru.devアップロード・失効・コメント生成・GitHub OAuth・ダッシュボード API
miseru-imgimg.miseru.dev配信専用。署名検証と失効チェックのみを行う読み取り経路
miseru-webapp.miseru.devダッシュボード SPA の配信。/v1/*/auth/* は service binding で miseru-api に同一オリジン中継
miseru-cleanup(Cron)毎時の失効スイープ

ストレージは次の3つを使い分けています。

  • R2 — 画像本体。ここからの削除が「失効の確定」
  • D1 — 画像・プロジェクト・トークン・組織のメタデータ
  • KV — レート制限カウンタ・OAuth state・ログインセッション

ダッシュボード(miseru-web)が API を service binding で中継するため、ブラウザから見ると常に同一オリジンです。CORS 設定は存在しません。

AIエージェント / CLI
│ ① POST /v1/upload (Upload Token)
miseru-api ──── R2 に画像保存 + D1 に記録 ──▶ 署名付き URL + markdown を返却
│ ② GitHub Action が GET /v1/comment で集約 markdown を取得し
│ PR に 1 コメントを投稿/更新
GitHub PR ──── ③ 閲覧時: GitHub Camo が img.miseru.dev を代理取得
│ └▶ miseru-img: HMAC 検証 → 失効チェック → R2 から配信
│ ④ PR クローズ: Action が POST /v1/expire
miseru-api ──── 失効ポリシー適用(pr_close なら R2 から即削除)
miseru-cleanup(毎時)── retain_days 期限切れ・失効失敗分をスイープ
  1. アップロード — miseru-api がバリデーション(サイズ・形式・レート制限)後、R2 へ保存し D1 に記録します。R2 保存後に D1 への記録が失敗した場合は R2 側を補償削除し、参照されない孤児オブジェクトを残しません
  2. PR コメント — コメントの markdown はサーバー側で PR 単位に集約生成されます。Action はマーカー(<!-- miseru:pr-<n> -->)で既存コメントを見つけて更新するため、スクリーンショットが何枚増えても PR には常に1コメントです
  3. 配信 — 配信経路(miseru-img)は書き込みを一切持たず、「署名が正しいか」「失効していないか」だけを判定して R2 の内容を返します。キャッシュ制御を含む配信の詳細は配信とセキュリティの仕組みを参照してください
  4. 失効 — 失効は必ず「R2 削除(強整合)の確定」を先に行います。オリジンが生きたままだとキャッシュ層の再取得で 200 が再キャッシュされ、失効が延命されてしまうためです。キャッシュ済みの配信は Cache-Control の期限による自然失効に委ねます
  5. スイープ — 失効処理が一時的に失敗した画像は失効予約としてマークされ、毎時の Cron が完了させます。一度の失敗で画像が「消せないまま残る」ことはありません
  • 配信と管理の分離 — img は D1/R2 の読み取りのみ。api 側の障害や再デプロイが配信に波及しにくい構成です
  • fail-open なレート制限 — レート制限カウンタ(KV)の障害時はアップロードを止めず、エラーログのみ残します。可用性をレート制限の厳密さより優先しています
  • 鍵ローテーション前提の署名 — 配信 URL の署名は key version(kid)付きで、secret をローテーションしても発行済み URL が壊れません