v4 Decision ADR — 要件から逆算した architecture 確定

2026-04-17 | Claude Code | 好み/経験ではなく要件・Invariants から導出した決定文書

📌 本書の位置づけ

Codex 6回目(16)で叩き台が reject-and-rewrite 判定。 ユーザー指示「好み・経験は置いておき、要件から決定せよ」に基づき、 本書は discussion draft ではなく Decision 文書として書く。

構成: ①実現したいこと → ②非機能要件(Invariants) → ③具体的な候補案 → ④ 要件×候補のスコアリング → ⑤Decision → ⑥rejected alternatives / assumptions / consequences / open risks → ⑦子安氏への依頼

1. このアプリで実現したいこと(要件整理)

1.1 ビジネス要件

項目内容
事業モデル飲食店向け Instagram 自動投稿 SaaS
料金体系通常 ¥20,000/月 / 上位 ¥35,000/月
短期目標10→30 店舗(安定運用)
中期目標100 店舗(商用規模)
想定売上100店舗 × ¥35,000 = ¥3.5M/月
契約上の約束予約投稿の確実な実行、投稿履歴の保全、承認フロー付き運用

1.2 機能要件(F-xx:v2 設計書から継承)

ID機能v4 優先度
F-A画像アップロード + AIキャプション生成(Claude)MVP
F-B即時投稿(IG Graph API)MVP
F-01予約投稿(日時指定 + キャンセル)MVP
F-02投稿履歴画面(予約済/承認待/すべて タブ)MVP
F-03店舗設定 GUI(メソッドモード/承認フロー ON/OFF)MVP
F-06IG トークン自動更新 CronMVP
F-07投稿承認フロー(承認 URL 発行 + 承認/却下)MVP
F-08投稿メソッドモード(3軸ハッシュタグ / コト訴求ルール)MVP
F-03b店舗設定 GUI 拡充(ブランドボイス/禁止表現)Phase 2
F-09LINE 通知連携(承認 URL)Phase 2
F-04フィードバック学習(engagement 分析→prompt 自動改善)Phase 2
F-05助っ人 API 連携(カプセル社)Phase 3

1.3 運用要件

項目内容
運用チーム子安氏1名 + CC 支援
24/7 監視人間監視不可。自動アラート + 翌営業日対応が前提
SLO(提案)予約投稿成功率 99.5% / 投稿 API p95 < 60秒 / 月間 downtime < 2時間
RTO1時間
RPO5分
インシデント対応メール通知 + Slack 通知、営業時間外は自動収束を重視(人手介入最小化)

2. 非機能要件(Invariants・確定版)

Codex 6回目(16章)指摘で INV-8/9/10 を MUST 昇格、追加 invariant を導入。全 15項目すべて MUST(launch-blocking)。

IDInvariantWhy this matters
INV-1Authenticated Actor Model匿名 bearer URL で Instagram 公開させない。actor identity を audit に残す
INV-2Tenant Isolation(行レベル)100店舗間でデータ・rate limit・event が絶対に混ざらない
INV-3Idempotent Instagram PublishWorker クラッシュ・タイムアウトでも絶対に重複公開しない
INV-4Bounded Media Path画像処理で Worker メモリ上限を超えない / 画像を DB に入れない
INV-5Durable Audit Trail承認・公開・設定変更すべて tamper-evident、並行 fork しない
INV-6Single Writer Where Requiredrate limit / audit chain / publish state は atomic single writer
INV-7Observable Operationsリアルタイム検知・90日ログ保全・postmortem 可能
INV-8Recovery from Partial Failurepartial failure を検出し reconcile 可能、RTO 1h / RPO 5min
INV-9Abuse ControlsCredential stuffing / URL 漏洩 / upload DoS / retry storm 多層防御
INV-10Migration Rollbackv2→v4 段階移行、store 単位で rollback 可
INV-11Schema EvolutionDB スキーマを zero-downtime で進化、旧スキーマとの互換性保持(Codex指摘)
INV-12Backup / Restore DrillPITR(point-in-time recovery)+ 月次 restore 演習(Codex指摘)
INV-13Secrets LifecycleJWT/暗号鍵/IG Token の rotation・revocation・audit を vault 経由で(Codex指摘)
INV-14Retention / Deletion(GDPR 的)店舗解約時のデータ削除・監査除外・削除証明書(Codex指摘)
INV-15Deployment SafetyFeature flag + カナリアリリース + 即時 rollback(Codex指摘)

3. 候補案(具体的な Bill of Materials)

⚠ Codex 6回目指摘 #5 対応

「A/B は option family でしかない」と指摘されたため、本書では各案を1つの具体的製品名まで確定させ、代替を rejected alternatives に回す。

3.1 候補1: 案 α — Cloudflare all-in

レイヤー製品/技術
フロントエンドCloudflare Pages + React 19 + Vite
API GatewayCloudflare Workers + Hono
AuthCloudflare Access(店舗アカウント) + 独自承認者セッション(HttpOnly cookie)
一貫性境界Durable Objects: PublishOrchestratorDO(per-post) / RateLimitDO(per-ip) / AuditAppenderDO(per-store)
非同期ジョブCloudflare Queues(予約投稿 / retry / reconciliation)
CronWorkers Cron Trigger
MediaR2 + client direct upload(presigned URL)
DBD1(SQLite エッジ分散)
AbuseWAF Rate Limiting + Turnstile
観測Logpush → R2 + Sentry (Cloudflare SDK)
SecretsWorkers Secrets(wrangler secret put)
BackupD1 は PITR 非対応、wrangler d1 export で日次バックアップ

3.2 候補2: 案 β — Supabase + Cloudflare Workers hybrid

レイヤー製品/技術
フロントエンドCloudflare Pages + React 19 + Vite
API GatewayCloudflare Workers + Hono(軽量ルート)
業務ロジックSupabase Edge Functions(Deno, 重いビジネスロジック)
AuthSupabase Auth(email/password + JWT + MFA 可)
一貫性境界Postgres advisory lock + トランザクション
非同期ジョブpg-boss(Postgres 上の job queue、成熟度高)
Cronpg_cron(Postgres 拡張)
MediaCloudflare R2(既存資産活用) + client presigned URL
DBSupabase Postgres(RLS + PITR 標準装備)
AbuseCloudflare WAF + Turnstile + pg_net rate limit
観測Supabase Logs + Sentry
SecretsSupabase Vault(Postgres pgsodium)
BackupPITR 標準装備(7日)+ 日次フル + 月次 restore 演習

3.3 候補3: 案 γ — Neon + Cloudflare Workers + Clerk(serverless Postgres)

レイヤー製品/技術
フロントエンドCloudflare Pages + React 19 + Vite
API GatewayCloudflare Workers + Hono(Neon HTTP driver)
AuthClerk(managed auth, JWT + MFA + org + role)
一貫性境界Postgres(Neon serverless) + advisory lock
非同期ジョブTrigger.dev v3(TypeScript-first, 分散 job engine)
CronTrigger.dev scheduled tasks
MediaCloudflare R2 + client presigned URL
DBNeon(serverless Postgres)(PITR + branching)
AbuseCloudflare WAF + Turnstile + Clerk rate limit
観測Sentry + Better Stack(構造化ログ)
SecretsCloudflare Secrets + Clerk 内蔵 key
BackupNeon PITR(7日)+ Branch による安全な restore 演習

4. スコアリング(Invariants × 各案)

各 invariant に対する各案の適合度を 1〜5 で評価(5=自然に満たせる、1=実装が困難・race 残存リスク大)。

Invariant案α CF案β Supabase案γ Neon注記
INV-1 Auth actor255α は独自 session 実装が必要、β/γ は managed auth
INV-2 Tenant isolation355β/γ は Postgres RLS で機械的 enforce
INV-3 Idempotent publish355DO でも実現可だが tx 境界が曖昧。Postgres tx + outbox が教科書的
INV-4 Bounded media444全案とも R2 direct upload で同等
INV-5 Durable audit355α は per-tenant AuditDO(cold start懸念)、β/γ は Postgres tx で自然
INV-6 Single writer455advisory lock は成熟、DO は要設計
INV-7 Observable455Supabase Logs / Better Stack は SQL でクエリ可
INV-8 Recovery255D1 は PITR 非対応、β/γ は標準装備
INV-9 Abuse controls444全案とも CF WAF + Turnstile 利用可能
INV-10 Migration rollback245γ の Neon branching は rollback テスト最強
INV-11 Schema evolution255D1 は zero-downtime migration 弱い、Postgres は成熟
INV-12 Backup/restore255D1 PITR 非対応が致命的
INV-13 Secrets lifecycle345Clerk は key rotation が UI 操作
INV-14 Retention/deletion355Postgres は DELETE + tombstone + audit が素直
INV-15 Deployment safety445Neon branch で本番とほぼ同一環境でテスト可
合計(最大 75)45/7568/7573/75

4.1 スコアリングからの一次結論

案γ(Neon + Clerk)が 73/75 で最高スコア。 案β(Supabase)が 68/75 で次点。 案α(Cloudflare all-in)は 45/75 で要件に対して構造的に不足(特に INV-8/10/11/12 で致命的に弱い:D1 が PITR 非対応)。

5. コスト比較(100店舗 3年 TCO モデル)

Codex 6回目指摘 #10 対応で月額 infra ではなく 3年 TCO で比較。

項目案α CF案β Supabase案γ Neon
本番 infra(月額)$100$175$180
staging/dev 環境$20$50(Supabase 別プロジェクト)$15(Neon Branch 追加費用少)
Sentry / 観測(月額)$26$26$26
Claude API(月額)$40$40$40
月額計$186$291$261
3年 infra 合計$6,696$10,476$9,396
Auth ライセンス(Clerk Pro)$0(独自実装)$0(Supabase Auth 込み)$25/月 × 36 = $900
実装工数(子安氏 ¥8,000/h 前提)230h × ¥8,000 = ¥1.84M180h × ¥8,000 = ¥1.44M170h × ¥8,000 = ¥1.36M
運用工数(3年, 10h/月)360h × ¥8,000 = ¥2.88M240h × ¥8,000 = ¥1.92M240h × ¥8,000 = ¥1.92M
インシデント対応(推定)¥1.5M(D1 制約起因)¥0.5M¥0.3M(Neon branch で事前検証容易)
3年 TCO 合計約 ¥7.2M約 ¥5.4M約 ¥5.1M

売上 3年 = ¥3.5M × 36 = ¥126M。TCO 比率はどの案も 4〜6% の範囲。コストは決定要因にならない(最安 α は invariants 不足で reject 対象)。

6. Decision

✅ 採用アーキテクチャ

案γ — Neon + Cloudflare Workers + Clerk + R2 + Trigger.dev

Invariants スコア 73/75 / 3年 TCO 約 ¥5.1M / 最低実装工数 170h

6.1 採用理由(好み・経験を排除した根拠)

  1. INV-11/12(Schema evolution + Backup/restore): Neon は PITR + Branch を標準装備。案α(D1)は PITR 非対応で構造的に不可能
  2. INV-1/13(Auth + Secrets lifecycle): Clerk は MFA / SSO / rotation が UI 操作で完結、独自実装不要 → v3 whack-a-mole の最大因子(auth 周り)を回避
  3. INV-3/5/6(idempotent / audit / single writer): Postgres トランザクション + advisory lock が業界標準、prior art 豊富
  4. INV-10/15(Migration rollback + Deployment safety): Neon Branch で本番同等環境での rollback rehearsal が可能
  5. TCO 3年で最安: Clerk 有料分を足しても案β/γ が安い(インシデント対応コスト削減分)
  6. プラットフォーム非依存性: Postgres / Clerk は別 cloud に移植容易

6.2 残す Cloudflare 資産

つまり「Cloudflare を捨てる」のではなく「一貫性境界だけ Postgres に移す hybrid」

7. Rejected Alternatives(明示的に不採用とした案)

7.1 案α(Cloudflare all-in)

不採用理由: D1 が PITR 非対応で INV-12 を満たせない。zero-downtime schema migration(INV-11)も弱い。auth を独自実装するコストが INV-1 の v3 再発リスクを引き起こす。

7.2 案β(Supabase)

次点として保留。採用可能性あり。Clerk 不採用で自前 auth を Supabase Auth に寄せる場合、β が選ばれる可能性。子安氏が「Clerk ライセンス $25/月 × 100店舗 = $2,500/月」を高いと判断する場合のみ β に切替。ただし 3年 TCO では差は小さい。

7.3 案δ(Fly.io + Postgres + BullMQ monolith)

不採用理由: container 常時起動で月額コスト高、Workers 既存資産の廃棄コスト、単一リージョン依存。

7.4 案ε(AWS Lambda + DynamoDB + SQS)

不採用理由: DynamoDB は single-table design 前提でチームの学習コスト高、transactional semantics 弱、audit chain 実装が複雑化。

7.5 案ζ(Vercel + Vercel Postgres)

不採用理由: Vercel Postgres = Neon OEM なので DB は同等だが、Vercel Functions より Cloudflare Workers 方が既存資産との親和性高。

8. Assumptions(前提条件)

  1. 子安氏が Postgres + Node.js 環境での SaaS 実装経験を持つ(もしくは learning 時間 20h 以内で戦力化)
  2. 店舗は通常業務時間内(9-23時)に操作する。深夜帯の高負荷は想定外
  3. Instagram Graph API の仕様が v4 リリース後 1年間は大きく変わらない
  4. ¥35,000/月 上位プランの契約率が 100店舗中 80% 以上(売上前提)
  5. 既存 v2 稼働店舗(10店舗)は v4 リリース後 6ヶ月以内に全店舗 v4 移行に同意する

9. Consequences(採用後に起きること)

9.1 Positive

9.2 Negative

9.3 Migration Consequences

10. Open Risks(未解決リスクと閉じ方)

RiskMitigation閉じる evidence
Neon cold start が UX を損なうConnection pooling(pgbouncer)設定、Warmup cronprototype で p95 < 3s 計測
Clerk 無料枠超過(10,000 MAU)有料プラン $25/月を予算化予算承認
Trigger.dev が期待通り動くか1週間 prototype で予約投稿の retry storm を検証G6 負荷試験パス
子安氏の習熟時間Neon / Clerk / Trigger.dev 公式 tutorial を事前共有Week 1 の習熟レポート
IG Graph API idempotency の実現outbox pattern + creation_id 保存 + Graph API での照合prototype で Worker crash テスト

11. Evidence Required(prototype phase で検証する)

Codex 6回目指摘 #6 対応。Week 1-2 の prototype phase で以下の pass/fail 基準 を満たすことを確認。

#検証項目Pass 基準
E1Authz: 店舗管理者 + 承認者 + Admin の 3 actor で publish 動作各 actor が自分の store のみ操作可、他は 403
E2Publish idempotency: Worker crash 後の retryInstagram 側に 1投稿のみ(outbox + Graph API 照合)
E3Audit append: 100並行書込で chain 整合advisory lock + tx で fork ゼロ
E4Retry storm: IG 1h outage 後の復旧outage 解除後 10分以内に全件再送完了
E5Migration dry-run: v2 10店舗データを v4 に変換1店舗あたり 5分以内、diff ゼロ
E6Observability: publish 失敗 → Slack 通知60秒以内に通知到達
E7Operator debug: 任意の post の audit trail を SQL でクエリ1分以内で全履歴取得

12. Delivery Plan(MVP vs Phase 2)

12.1 Phase 1 MVP(Week 3〜Week 14 = 12週間)

12.2 Phase 2(MVP 運用 2ヶ月後開始)

12.3 Phase 3(Phase 2 完了後)

12.4 MVP のスコープカット候補(3ヶ月厳守する場合)

13. Operating Model(Codex 6回目指摘 #9 対応)

13.1 SLO

指標目標測定方法
予約投稿成功率99.5%posts.status='published' / posts.status IN ('published','failed') per 月
投稿 API p95< 60秒Sentry performance
月間 downtime< 2時間Better Stack uptime monitor
audit chain integrity100%日次バッチで chain verify

13.2 Alert Threshold

イベント閾値通知先
予約投稿失敗(3回リトライ後)1件Slack #ops
Cron 遅延5分超過Slack #ops
login 失敗スパイク同一IP 10回/5分Slack #security
IG token 失効 48h 前自動Slack #ops + メール
audit chain fork 検知即時Slack #security(緊急)

13.3 On-call / 運用フロー

14. 子安氏への依頼事項

✅ この段階で子安氏に確認したい「閉じた質問」のみ

ADR は CC が Decision まで提出する。子安氏は以下の4点にのみ yes/no/modify で回答すれば良い

#質問期待される回答形式
Q1Decision(6章)の 案γ(Neon + Clerk + Trigger.dev + R2 + CF Workers)で進めて良いかYes / No(No なら案β or 代替案を指定)
Q2Invariants(2章)15項目に追加 / 削除 / 優先度変更はあるか追加リスト or 「なし」
Q3Evidence required(11章)E1〜E7 の pass 基準に修正はあるか修正リスト or 「なし」
Q4Delivery Plan(12章)MVP を 12週間で実装可能か(子安氏の稼働率想定)Yes / No(No なら現実的な工期)

14.1 進め方

14.2 MTG は Q1 = No の場合のみ開く

Codex 6回目指摘 #8(ADR が hard decision を丸投げ)対応。CC が Decision を提示し、子安氏は「承認 or 却下」だけでも進める設計。

15. 付録:関連ドキュメント