2026-04-17 | Claude Code | 好み/経験ではなく要件・Invariants から導出した決定文書
Codex 6回目(16)で叩き台が reject-and-rewrite 判定。 ユーザー指示「好み・経験は置いておき、要件から決定せよ」に基づき、 本書は discussion draft ではなく Decision 文書として書く。
構成: ①実現したいこと → ②非機能要件(Invariants) → ③具体的な候補案 → ④ 要件×候補のスコアリング → ⑤Decision → ⑥rejected alternatives / assumptions / consequences / open risks → ⑦子安氏への依頼
| 項目 | 内容 |
|---|---|
| 事業モデル | 飲食店向け Instagram 自動投稿 SaaS |
| 料金体系 | 通常 ¥20,000/月 / 上位 ¥35,000/月 |
| 短期目標 | 10→30 店舗(安定運用) |
| 中期目標 | 100 店舗(商用規模) |
| 想定売上 | 100店舗 × ¥35,000 = ¥3.5M/月 |
| 契約上の約束 | 予約投稿の確実な実行、投稿履歴の保全、承認フロー付き運用 |
| 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-06 | IG トークン自動更新 Cron | MVP |
| F-07 | 投稿承認フロー(承認 URL 発行 + 承認/却下) | MVP |
| F-08 | 投稿メソッドモード(3軸ハッシュタグ / コト訴求ルール) | MVP |
| F-03b | 店舗設定 GUI 拡充(ブランドボイス/禁止表現) | Phase 2 |
| F-09 | LINE 通知連携(承認 URL) | Phase 2 |
| F-04 | フィードバック学習(engagement 分析→prompt 自動改善) | Phase 2 |
| F-05 | 助っ人 API 連携(カプセル社) | Phase 3 |
| 項目 | 内容 |
|---|---|
| 運用チーム | 子安氏1名 + CC 支援 |
| 24/7 監視 | 人間監視不可。自動アラート + 翌営業日対応が前提 |
| SLO(提案) | 予約投稿成功率 99.5% / 投稿 API p95 < 60秒 / 月間 downtime < 2時間 |
| RTO | 1時間 |
| RPO | 5分 |
| インシデント対応 | メール通知 + Slack 通知、営業時間外は自動収束を重視(人手介入最小化) |
Codex 6回目(16章)指摘で INV-8/9/10 を MUST 昇格、追加 invariant を導入。全 15項目すべて MUST(launch-blocking)。
| ID | Invariant | Why this matters |
|---|---|---|
| INV-1 | Authenticated Actor Model | 匿名 bearer URL で Instagram 公開させない。actor identity を audit に残す |
| INV-2 | Tenant Isolation(行レベル) | 100店舗間でデータ・rate limit・event が絶対に混ざらない |
| INV-3 | Idempotent Instagram Publish | Worker クラッシュ・タイムアウトでも絶対に重複公開しない |
| INV-4 | Bounded Media Path | 画像処理で Worker メモリ上限を超えない / 画像を DB に入れない |
| INV-5 | Durable Audit Trail | 承認・公開・設定変更すべて tamper-evident、並行 fork しない |
| INV-6 | Single Writer Where Required | rate limit / audit chain / publish state は atomic single writer |
| INV-7 | Observable Operations | リアルタイム検知・90日ログ保全・postmortem 可能 |
| INV-8 | Recovery from Partial Failure | partial failure を検出し reconcile 可能、RTO 1h / RPO 5min |
| INV-9 | Abuse Controls | Credential stuffing / URL 漏洩 / upload DoS / retry storm 多層防御 |
| INV-10 | Migration Rollback | v2→v4 段階移行、store 単位で rollback 可 |
| INV-11 | Schema Evolution | DB スキーマを zero-downtime で進化、旧スキーマとの互換性保持(Codex指摘) |
| INV-12 | Backup / Restore Drill | PITR(point-in-time recovery)+ 月次 restore 演習(Codex指摘) |
| INV-13 | Secrets Lifecycle | JWT/暗号鍵/IG Token の rotation・revocation・audit を vault 経由で(Codex指摘) |
| INV-14 | Retention / Deletion(GDPR 的) | 店舗解約時のデータ削除・監査除外・削除証明書(Codex指摘) |
| INV-15 | Deployment Safety | Feature flag + カナリアリリース + 即時 rollback(Codex指摘) |
「A/B は option family でしかない」と指摘されたため、本書では各案を1つの具体的製品名まで確定させ、代替を rejected alternatives に回す。
| レイヤー | 製品/技術 |
|---|---|
| フロントエンド | Cloudflare Pages + React 19 + Vite |
| API Gateway | Cloudflare Workers + Hono |
| Auth | Cloudflare Access(店舗アカウント) + 独自承認者セッション(HttpOnly cookie) |
| 一貫性境界 | Durable Objects: PublishOrchestratorDO(per-post) / RateLimitDO(per-ip) / AuditAppenderDO(per-store) |
| 非同期ジョブ | Cloudflare Queues(予約投稿 / retry / reconciliation) |
| Cron | Workers Cron Trigger |
| Media | R2 + client direct upload(presigned URL) |
| DB | D1(SQLite エッジ分散) |
| Abuse | WAF Rate Limiting + Turnstile |
| 観測 | Logpush → R2 + Sentry (Cloudflare SDK) |
| Secrets | Workers Secrets(wrangler secret put) |
| Backup | D1 は PITR 非対応、wrangler d1 export で日次バックアップ |
| レイヤー | 製品/技術 |
|---|---|
| フロントエンド | Cloudflare Pages + React 19 + Vite |
| API Gateway | Cloudflare Workers + Hono(軽量ルート) |
| 業務ロジック | Supabase Edge Functions(Deno, 重いビジネスロジック) |
| Auth | Supabase Auth(email/password + JWT + MFA 可) |
| 一貫性境界 | Postgres advisory lock + トランザクション |
| 非同期ジョブ | pg-boss(Postgres 上の job queue、成熟度高) |
| Cron | pg_cron(Postgres 拡張) |
| Media | Cloudflare R2(既存資産活用) + client presigned URL |
| DB | Supabase Postgres(RLS + PITR 標準装備) |
| Abuse | Cloudflare WAF + Turnstile + pg_net rate limit |
| 観測 | Supabase Logs + Sentry |
| Secrets | Supabase Vault(Postgres pgsodium) |
| Backup | PITR 標準装備(7日)+ 日次フル + 月次 restore 演習 |
| レイヤー | 製品/技術 |
|---|---|
| フロントエンド | Cloudflare Pages + React 19 + Vite |
| API Gateway | Cloudflare Workers + Hono(Neon HTTP driver) |
| Auth | Clerk(managed auth, JWT + MFA + org + role) |
| 一貫性境界 | Postgres(Neon serverless) + advisory lock |
| 非同期ジョブ | Trigger.dev v3(TypeScript-first, 分散 job engine) |
| Cron | Trigger.dev scheduled tasks |
| Media | Cloudflare R2 + client presigned URL |
| DB | Neon(serverless Postgres)(PITR + branching) |
| Abuse | Cloudflare WAF + Turnstile + Clerk rate limit |
| 観測 | Sentry + Better Stack(構造化ログ) |
| Secrets | Cloudflare Secrets + Clerk 内蔵 key |
| Backup | Neon PITR(7日)+ Branch による安全な restore 演習 |
各 invariant に対する各案の適合度を 1〜5 で評価(5=自然に満たせる、1=実装が困難・race 残存リスク大)。
| Invariant | 案α CF | 案β Supabase | 案γ Neon | 注記 |
|---|---|---|---|---|
| INV-1 Auth actor | 2 | 5 | 5 | α は独自 session 実装が必要、β/γ は managed auth |
| INV-2 Tenant isolation | 3 | 5 | 5 | β/γ は Postgres RLS で機械的 enforce |
| INV-3 Idempotent publish | 3 | 5 | 5 | DO でも実現可だが tx 境界が曖昧。Postgres tx + outbox が教科書的 |
| INV-4 Bounded media | 4 | 4 | 4 | 全案とも R2 direct upload で同等 |
| INV-5 Durable audit | 3 | 5 | 5 | α は per-tenant AuditDO(cold start懸念)、β/γ は Postgres tx で自然 |
| INV-6 Single writer | 4 | 5 | 5 | advisory lock は成熟、DO は要設計 |
| INV-7 Observable | 4 | 5 | 5 | Supabase Logs / Better Stack は SQL でクエリ可 |
| INV-8 Recovery | 2 | 5 | 5 | D1 は PITR 非対応、β/γ は標準装備 |
| INV-9 Abuse controls | 4 | 4 | 4 | 全案とも CF WAF + Turnstile 利用可能 |
| INV-10 Migration rollback | 2 | 4 | 5 | γ の Neon branching は rollback テスト最強 |
| INV-11 Schema evolution | 2 | 5 | 5 | D1 は zero-downtime migration 弱い、Postgres は成熟 |
| INV-12 Backup/restore | 2 | 5 | 5 | D1 PITR 非対応が致命的 |
| INV-13 Secrets lifecycle | 3 | 4 | 5 | Clerk は key rotation が UI 操作 |
| INV-14 Retention/deletion | 3 | 5 | 5 | Postgres は DELETE + tombstone + audit が素直 |
| INV-15 Deployment safety | 4 | 4 | 5 | Neon branch で本番とほぼ同一環境でテスト可 |
| 合計(最大 75) | 45/75 | 68/75 | 73/75 |
案γ(Neon + Clerk)が 73/75 で最高スコア。 案β(Supabase)が 68/75 で次点。 案α(Cloudflare all-in)は 45/75 で要件に対して構造的に不足(特に INV-8/10/11/12 で致命的に弱い:D1 が PITR 非対応)。
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.84M | 180h × ¥8,000 = ¥1.44M | 170h × ¥8,000 = ¥1.36M |
| 運用工数(3年, 10h/月) | 360h × ¥8,000 = ¥2.88M | 240h × ¥8,000 = ¥1.92M | 240h × ¥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 対象)。
Invariants スコア 73/75 / 3年 TCO 約 ¥5.1M / 最低実装工数 170h
つまり「Cloudflare を捨てる」のではなく「一貫性境界だけ Postgres に移す hybrid」。
不採用理由: D1 が PITR 非対応で INV-12 を満たせない。zero-downtime schema migration(INV-11)も弱い。auth を独自実装するコストが INV-1 の v3 再発リスクを引き起こす。
次点として保留。採用可能性あり。Clerk 不採用で自前 auth を Supabase Auth に寄せる場合、β が選ばれる可能性。子安氏が「Clerk ライセンス $25/月 × 100店舗 = $2,500/月」を高いと判断する場合のみ β に切替。ただし 3年 TCO では差は小さい。
不採用理由: container 常時起動で月額コスト高、Workers 既存資産の廃棄コスト、単一リージョン依存。
不採用理由: DynamoDB は single-table design 前提でチームの学習コスト高、transactional semantics 弱、audit chain 実装が複雑化。
不採用理由: Vercel Postgres = Neon OEM なので DB は同等だが、Vercel Functions より Cloudflare Workers 方が既存資産との親和性高。
| Risk | Mitigation | 閉じる evidence |
|---|---|---|
| Neon cold start が UX を損なう | Connection pooling(pgbouncer)設定、Warmup cron | prototype で 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 テスト |
Codex 6回目指摘 #6 対応。Week 1-2 の prototype phase で以下の pass/fail 基準 を満たすことを確認。
| # | 検証項目 | Pass 基準 |
|---|---|---|
| E1 | Authz: 店舗管理者 + 承認者 + Admin の 3 actor で publish 動作 | 各 actor が自分の store のみ操作可、他は 403 |
| E2 | Publish idempotency: Worker crash 後の retry | Instagram 側に 1投稿のみ(outbox + Graph API 照合) |
| E3 | Audit append: 100並行書込で chain 整合 | advisory lock + tx で fork ゼロ |
| E4 | Retry storm: IG 1h outage 後の復旧 | outage 解除後 10分以内に全件再送完了 |
| E5 | Migration dry-run: v2 10店舗データを v4 に変換 | 1店舗あたり 5分以内、diff ゼロ |
| E6 | Observability: publish 失敗 → Slack 通知 | 60秒以内に通知到達 |
| E7 | Operator debug: 任意の post の audit trail を SQL でクエリ | 1分以内で全履歴取得 |
| 指標 | 目標 | 測定方法 |
|---|---|---|
| 予約投稿成功率 | 99.5% | posts.status='published' / posts.status IN ('published','failed') per 月 |
| 投稿 API p95 | < 60秒 | Sentry performance |
| 月間 downtime | < 2時間 | Better Stack uptime monitor |
| audit chain integrity | 100% | 日次バッチで chain verify |
| イベント | 閾値 | 通知先 |
|---|---|---|
| 予約投稿失敗(3回リトライ後) | 1件 | Slack #ops |
| Cron 遅延 | 5分超過 | Slack #ops |
| login 失敗スパイク | 同一IP 10回/5分 | Slack #security |
| IG token 失効 48h 前 | 自動 | Slack #ops + メール |
| audit chain fork 検知 | 即時 | Slack #security(緊急) |
ADR は CC が Decision まで提出する。子安氏は以下の4点にのみ yes/no/modify で回答すれば良い。
| # | 質問 | 期待される回答形式 |
|---|---|---|
| Q1 | Decision(6章)の 案γ(Neon + Clerk + Trigger.dev + R2 + CF Workers)で進めて良いか | Yes / No(No なら案β or 代替案を指定) |
| Q2 | Invariants(2章)15項目に追加 / 削除 / 優先度変更はあるか | 追加リスト or 「なし」 |
| Q3 | Evidence required(11章)E1〜E7 の pass 基準に修正はあるか | 修正リスト or 「なし」 |
| Q4 | Delivery Plan(12章)MVP を 12週間で実装可能か(子安氏の稼働率想定) | Yes / No(No なら現実的な工期) |
Codex 6回目指摘 #8(ADR が hard decision を丸投げ)対応。CC が Decision を提示し、子安氏は「承認 or 却下」だけでも進める設計。