2026-04-17 | Claude Code | 17 の改訂版(6項目 fix)+ Codex 8回目レビュー予定
17(Decision ADR v1)は Codex 7回目レビュー(18)で needs-major-revision 判定。子安氏送付前にさらに 1ラウンド改訂した本書 v2 が最終候補。 Codex 7回目指摘 6件(critical 1 / high 4 / medium 1)を全て織り込み済み。
変更点サマリ: ①γ専用 Secrets 設計追加(pgsodium 参照削除) ②TCO を 30/100/300店舗 scenario 化 ③Operating Model 二択明示(24/7 採用) ④スコアリング rubric 導入 + unknown マーク ⑤Evidence を failure matrix 化 ⑥Invariant に INV-16〜20 追加(multi-vendor SLA / compliance)
| 項目 | 内容 |
|---|---|
| 事業モデル | 飲食店向け Instagram 自動投稿 SaaS |
| 料金 | 通常 ¥20,000/月 / 上位 ¥35,000/月 |
| 短期 | 10→30 店舗 |
| 中期 | 100 店舗 |
| 長期 | 300 店舗(水平拡大上限想定) |
| 100店舗時売上 | ¥3.5M/月(想定 80% が上位プラン) |
| 商用約束 | 予約投稿の確実実行・投稿履歴保全・承認フロー・監査証跡 |
| ID | 機能 | v4 優先度 |
|---|---|---|
| F-A | 画像アップロード + AIキャプション生成(Claude) | MVP |
| F-B | 即時投稿(IG Graph API) | MVP |
| F-01 | 予約投稿 | MVP |
| F-02 | 投稿履歴画面 | MVP |
| F-03 | 店舗設定 GUI | MVP |
| F-06 | IG トークン自動更新 | MVP |
| F-07 | 投稿承認フロー | MVP |
| F-08 | 投稿メソッドモード | MVP |
| F-03b | 店舗設定 GUI 拡充 | Phase 2 |
| F-09 | LINE 通知 | Phase 2 |
| F-04 | フィードバック学習 | Phase 2 |
| F-05 | 助っ人 API 連携 | Phase 3 |
| 項目 | 内容 |
|---|---|
| 運用チーム | 子安氏1名 + CC 支援 + 高木氏(ビジネス窓口) |
| SLO | 予約投稿成功率 99.5% / 投稿API p95 < 60秒 / 月間 downtime < 2時間 |
| RTO / RPO | RTO 1時間 / RPO 5分 |
| On-call | 24/7 escalation(§12 で後述) |
| ID | Invariant | Why |
|---|---|---|
| INV-1 | Authenticated Actor Model | 匿名 bearer URL で IG 公開不可 |
| INV-2 | Tenant Isolation(行レベル) | 100店舗間のデータ混在禁止 |
| INV-3 | Idempotent Instagram Publish | Worker crash 等で重複公開しない |
| INV-4 | Bounded Media Path | Worker メモリ上限超過しない |
| INV-5 | Durable Audit Trail | tamper-evident / 並行 fork しない |
| INV-6 | Single Writer Where Required | rate limit / audit / publish state は atomic |
| INV-7 | Observable Operations | リアルタイム検知 / 90日ログ保全 |
| INV-8 | Recovery from Partial Failure | RTO 1h / RPO 5min |
| INV-9 | Abuse Controls | Credential stuffing / URL 漏洩対策 |
| INV-10 | Migration Rollback | store 単位 rollback 可 |
| INV-11 | Schema Evolution | zero-downtime DB migration |
| INV-12 | Backup / Restore Drill | PITR + 月次 restore 演習 |
| INV-13 | Secrets Lifecycle | JWT/暗号鍵/IG token の rotation / revocation / audit |
| INV-14 | Retention / Deletion(GDPR) | 解約時のデータ削除・削除証明 |
| INV-15 | Deployment Safety | Feature flag + カナリア + 即時 rollback |
| ID | Invariant | Why this was missed in v1 |
|---|---|---|
| INV-16 | Composite Vendor Availability | 複数ベンダ依存時の稼働率積算。例: Clerk 99.9% × Neon 99.95% × CF 99.99% = 99.84% → 月間 downtime 69分。単一 99.95% ベンダより悪化する可能性 |
| INV-17 | Vendor Outage Fallback Behavior | Clerk 障害時のログイン不可・Neon 障害時のデータ参照不可・Trigger.dev 障害時の予約投稿停止 に対する明示的な degraded mode 設計 |
| INV-18 | Data Residency / Export Portability | 日本の個人情報保護法 + GDPR 相当:データ居住地の開示・顧客からのデータ export リクエスト対応 |
| INV-19 | AI Audit Logging | Claude API への prompt / response をテナント単位で保存・redaction・retention(顧客苦情時の証跡) |
| INV-20 | API Compatibility / Versioning | 顧客が将来 API 連携する可能性への備え。破壊的変更時の deprecation policy |
| 案α Cloudflare all-in | 案β Supabase hybrid | 案γ Neon + Clerk | |
|---|---|---|---|
| Frontend | CF Pages | CF Pages | CF Pages |
| API Gateway | CF Workers + Hono | CF Workers + Hono | CF Workers + Hono |
| Auth | CF Access + 独自 session | Supabase Auth | Clerk |
| 一貫性境界 | Durable Objects | Postgres advisory lock | Postgres advisory lock |
| 非同期ジョブ | CF Queues | pg-boss | Trigger.dev v3 |
| Cron | Workers Cron | pg_cron | Trigger.dev scheduled |
| Media | R2 | R2 | R2 |
| DB | D1 (SQLite) | Supabase Postgres | Neon (serverless PG) |
| Backup | wrangler d1 export 日次 | PITR 7日 標準 | PITR 7日 + Branch |
| Observability | Logpush + Sentry | Supabase Logs + Sentry | Sentry + Better Stack |
| Secrets | Workers Secrets | Supabase Vault (pgsodium) | §9 で詳述 |
| Abuse | CF WAF + Turnstile | CF WAF + Turnstile | CF WAF + Turnstile |
| Score | 意味 | 判定基準 |
|---|---|---|
| 5 | Native | プラットフォーム標準機能が invariant をそのまま実装。prior art 多数・failure mode が既知 |
| 4 | Sound | 標準パターンで実装可能、ただしプラットフォーム固有の運用知識が必要 |
| 3 | Achievable | 実装可能だがカスタム設計が必要、failure mode に新規リスクあり |
| 2 | Difficult | 実装可能だが大量のカスタム実装が必要、v3 で whack-a-mole が起きた領域 |
| 1 | Blocked | プラットフォームが構造的に invariant を満たせない(launch-blocking) |
| ? | Unknown | 評価に必要な evidence 不足、prototype で確認が必要 |
INV のうち Hard Gate(1つでも Score=1 なら launch 不可):
残り 16 項目は weighted total で総合評価。
| Invariant | α | β | γ | Evidence / Rationale |
|---|---|---|---|---|
| INV-1 Auth actor | 3 | 5 | 5 | α: CF Access は店舗 auth に使えるが承認者サブアカウント + CSRF + MFA は自作。β/γ: Supabase Auth / Clerk は native 実装 |
| INV-2 Tenant isolation | 3 | 5 | 5 | α: D1 で自前実装。β/γ: Postgres RLS が native |
| INV-3 Idempotent publish (Hard Gate) | 3 | 5 | 5 | α: DO は single-writer を提供するが、outbox pattern + IG Graph API reconciliation は自作。β/γ: Postgres tx + outbox table が教科書的パターン |
| INV-4 Bounded media | 4 | 4 | 4 | 全案とも R2 direct upload で同等 |
| INV-5 Durable audit | 3 | 5 | 5 | α: per-tenant AuditDO は実装可だが cold-start / backpressure が新規設計。β/γ: Postgres tx + append-only + hash chain が標準 |
| INV-6 Single writer | 4 | 5 | 5 | α: DO は single writer 提供、β/γ: advisory lock 成熟 |
| INV-7 Observable | 4 | 5 | 5 | β/γ: SQL で直接クエリ可能 |
| INV-8 Recovery (Hard Gate) | 1 | 5 | 5 | D1 は PITR 非対応で RPO 5min 不達成。日次 export では RPO 24h が上限。β/γ: PITR 標準 |
| INV-9 Abuse | 4 | 4 | 4 | 全案とも CF WAF + Turnstile |
| INV-10 Migration rollback | 2 | 4 | 5 | γ: Neon Branch で本番同等環境での rollback rehearsal 可能 |
| INV-11 Schema evolution | 2 | 5 | 5 | D1 は ALTER TABLE 制約多、Postgres は zero-downtime migration ツール(pg_trgm, pg_repack 等)成熟 |
| INV-12 Backup/restore (Hard Gate) | 1 | 5 | 5 | D1 PITR 非対応で Hard Gate 失敗 |
| INV-13 Secrets lifecycle (Hard Gate) | 3 | 4 | 4 | §9 で γ 専用設計を提示。β は pgsodium、γ は CF Workers Secrets + 独自 envelope encryption |
| INV-14 Retention/deletion | 3 | 5 | 5 | Postgres は DELETE + tombstone + audit が素直 |
| INV-15 Deployment safety | 4 | 4 | 5 | Neon Branch で prod-like env |
| INV-16 Composite availability | 5 | 3 | 2 | α が最良: 単一ベンダなので composite 劣化なし。γ: Clerk 99.9% × Neon 99.95% × Trigger.dev 99.9% × CF 99.99% = 99.74%(月 113分 downtime 許容) |
| INV-17 Outage fallback | 3 | 3 | 3 | 全案で degraded mode を自前設計する必要あり(read-only mode, publish queue freeze 等) |
| INV-18 Data residency | ? | ? | ? | prototype で確認: CF は JP region 指定可、Supabase Tokyo region あり、Neon は AWS ap-northeast-1 ある |
| INV-19 AI audit | 3 | 5 | 5 | Postgres に jsonb で prompt/response 保存が素直、redaction は app 層で |
| INV-20 API versioning | 4 | 4 | 4 | 全案とも Hono で /v1/ prefix ルーティング可能、同等 |
| 案 | INV-3 | INV-8 | INV-12 | INV-13 | Hard Gate |
|---|---|---|---|---|---|
| α | 3 | 1 | 1 | 3 | ❌ Failed(INV-8, INV-12) |
| β | 5 | 5 | 5 | 4 | ✅ Pass |
| γ | 5 | 5 | 5 | 4 | ✅ Pass |
INV-18(?)を除外した 15項目で合計:
α は Hard Gate 失敗(D1 の PITR 非対応)で invariants を構造的に満たせない。 β と γ は weighted score で同点(67/75)。差別化は INV-10(migration rollback)と INV-16(composite availability)のトレードオフ:
Clerk Pro tier は 10,000 MAU まで $25/月、超過は $0.02/MAU。 店舗あたり staff 2-5 名想定(店長 + 現場スタッフ 1-4名)。
| 店舗数 | staff/店 | 総 MAU | Clerk 月額 |
|---|---|---|---|
| 30 | 2 | 60 | $25 |
| 30 | 5 | 150 | $25 |
| 100 | 2 | 200 | $25 |
| 100 | 5 | 500 | $25 |
| 300 | 5 | 1,500 | $25 |
結論: 想定範囲では Clerk は全て $25/月。v1 の「$25×100店舗 = $2,500/月」は MAU と店舗数を混同した誤り。本書は $25/月前提で統一。
| 項目 | 30 店舗 | 100 店舗 | 300 店舗 |
|---|---|---|---|
| CF Workers Paid | $5 | $5 | $10(超過分) |
| CF Pages | $0 | $0 | $0 |
| CF R2 | $2 | $5 | $15 |
| CF WAF | $20 | $20 | $20 |
| CF Turnstile | $0 | $0 | $0 |
| Neon Launch | $19 | $19 | $69(Scale plan) |
| Clerk Pro | $25 | $25 | $25(10k MAU 以内) |
| Trigger.dev | $20 | $50 | $100 |
| Sentry Team | $26 | $26 | $26 |
| Better Stack | $24 | $24 | $24 |
| Claude API | $15 | $40 | $120 |
| Staging/Dev 環境 | $15 | $20 | $30 |
| 月額計 | $171 | $234 | $439 |
| 店舗あたり単価 | $5.7 | $2.3 | $1.5 |
| 項目 | α(参考・Hard Gate失敗) | β Supabase | γ Neon+Clerk | 備考 |
|---|---|---|---|---|
| Infra 3年合計(100店舗) | $100×36=$3,600 | $209×36=$7,524 | $234×36=$8,424 | 通常運用コスト |
| 実装工数(¥8,000/h) | N/A | 180h=¥1.44M | 180h=¥1.44M | 実装量は同等(Postgres パターンで近似) |
| 運用工数(3年 10h/月 ¥8,000/h) | N/A | 360h=¥2.88M | 360h=¥2.88M | 24/7 escalation 前提 |
| Incident cost 推定(3年) | N/A | ¥0.6M | ¥0.6M | 根拠: Codex 過去レビューで両案とも PITR 標準・outbox 設計済み。重大事故を年1回・復旧8h×¥8,000×2名=¥128k/回と想定 |
| 3年 TCO 合計 | N/A | 約 ¥6.0M | 約 ¥6.1M | 差は ¥100k(1.6%) |
| 店舗数 | 売上/月 | Infra/月(γ) | Infra 比率 |
|---|---|---|---|
| 30 | ¥600,000 | $171 ≈ ¥25,650 | 4.3% |
| 100 | ¥2,000,000 | $234 ≈ ¥35,100 | 1.8% |
| 300 | ¥6,000,000 | $439 ≈ ¥65,850 | 1.1% |
スケールするほど比率が下がる(スケール利益が効く)。
β と γ の TCO 差は 1.6%(¥100k/3年)。統計的に意味のある差ではない。コストは決定要因にならない。 決定要因は次章(§6)の非コスト要因に移る。
Hard Gate 通過 / Weighted Score 67 / TCO ¥6.1M/3年 / Migration rollback 優位
| 観点 | β | γ | 勝者 |
|---|---|---|---|
| Migration rollback(INV-10) | 4 | 5 | γ |
| Deployment safety(INV-15) | 4 | 5 | γ |
| Composite availability(INV-16) | 3 | 2 | β |
| Auth 機能(MFA / SSO / org 管理) | Supabase Auth(basic) | Clerk(pro-grade) | γ |
| Postgres プロバイダの成熟度 | Supabase(自社運用) | Neon(自社運用 + serverless) | 同等 |
| Tokyo region 対応 | ◎ | ◎ (AWS ap-northeast-1) | 同等 |
Composite availability 99.74% ≒ 月間 113分 downtime 許容。これは SLO「月間 downtime < 2時間(120分)」の範囲内。SLO を満たせる境界内なので受容可能。
Reject 理由: INV-8 と INV-12 で Hard Gate 失敗(D1 PITR 非対応)。wrangler d1 export 日次では RPO 5min 不達成。
次点、非採用。TCO 差は実質ない(¥100k/3年)。INV-10 Migration rollback で γ に劣る。子安氏が Neon/Clerk/Trigger.dev 不採用を強く推奨する場合のみ β に切替。
Reject 理由: container 常時起動コスト、単一リージョン依存、Workers 既存資産の廃棄コスト。
Reject 理由: DynamoDB single-table design の学習コスト、audit chain 実装の複雑化。
| Risk | Mitigation | 閉じる evidence |
|---|---|---|
| Neon cold start が UX 悪化 | pgbouncer / Connection pooling / Warmup cron | E8: p95 DB query < 300ms |
| Trigger.dev 本番 SLA 未検証 | 1週間 prototype で retry storm 試験 | E4: IG 1h outage 後の復旧 10分以内 |
| 子安氏の習熟時間 | 事前 tutorial 共有 + 1週目は CC 支援厚め | Week 1 の習熟レポート |
| IG Graph API idempotency | outbox + creation_id + reconciliation | E2: Worker crash test |
| INV-18 Data residency 未確認 | 各ベンダの JP region 契約確認 | E9: 全 PII が JP region に留まる証明 |
| Class | 対象 | 保管場所 | Rotation 周期 |
|---|---|---|---|
| Platform secrets | JWT signing key(Clerk 内蔵) | Clerk managed | Clerk UI で 30日ごと(推奨) |
| Infrastructure secrets | Neon DB URL / R2 access key / Trigger.dev API key / Sentry DSN / Anthropic API key | Cloudflare Workers Secrets(wrangler secret put) | 90日ごと(手動) |
| Master Encryption Key(MEK) | v4_MEK_V1, V2, ...(ローテーション用) | Cloudflare Workers Secrets | 180日ごと(手動) |
| Per-tenant secrets | 各店舗の Instagram access_token / refresh_token | Neon Postgres(MEKで envelope暗号化) | IG 側 60日(F-06 Cron で自動) |
Instagram access_token を Neon に保存する時: 1. アプリ側で DEK (Data Encryption Key) を生成 DEK = crypto.getRandomValues(32 bytes) // AES-256 2. DEK で IG token を暗号化 ciphertext = AES-GCM(DEK, IG_token, IV=12 random bytes) 3. MEK (Master Encryption Key, Cloudflare Workers Secrets から取得) で DEK を暗号化 wrapped_DEK = AES-GCM(MEK, DEK, IV=12 random bytes) 4. Neon に保存 INSERT INTO stores (id, ig_token_ciphertext, ig_token_iv, ig_token_wrapped_dek, ig_token_wrapped_dek_iv, ig_token_mek_version) VALUES (store_id, ciphertext, iv, wrapped_DEK, wrapped_DEK_iv, 'v1'); 復号時: 1. mek_version 列から使用 MEK version を特定('v1' なら env.V4_MEK_V1) 2. MEK で wrapped_DEK を復号 → DEK 3. DEK で ciphertext を復号 → IG_token
DELETE FROM stores WHERE id = ? で ciphertext ごと削除(crypto-shredding)ig_token_ciphertext を NULL に、F-06 cron で refresh token から再取得以下の操作を audit_logs に記録:
Neon PITR で DB を過去時点に restore した場合:
v2 の IG token は AES-GCM で暗号化されているが、envelope pattern ではない。v4 移行時:
services/crypto.ts)で plaintext 化| Actor | Action | Expected |
|---|---|---|
| Store manager (store A) | Publish to store A | 200 OK |
| Store manager (store A) | Publish to store B | 403 Forbidden |
| Approver (store A) | Approve store A request | 200 OK |
| Approver (store A) | Create post for store A | 403 Forbidden |
| Admin | Any action on any store | 200 OK + audit log |
| Unauthenticated | Any authenticated endpoint | 401 Unauthorized |
| Crash timing | Expected recovery |
|---|---|
| Before IG media_create API call | Next cron retry, publish once |
| After IG media_create success but before creation_id saved | Reconciliation: query IG for orphan media_id, resume |
| After creation_id saved but before IG media_publish | Next cron picks up creation_id, publishes |
| After IG media_publish success but before DB commit | Reconciliation: query IG, confirm published, update DB |
| After DB commit but before Trigger.dev ack | Trigger.dev retries, second attempt sees status='published', skips |
| During reconciliation query | Next reconciliation cron completes |
Pass 基準: 全 6ケースで Instagram 投稿は最終的に ちょうど1回、DB posts テーブルに ちょうど1行 status='published'。
Scenario: 100 並行リクエストが同時に audit_logs 書込をトリガー
Pass 基準: advisory lock + tx でchain fork ゼロ、全 100行が prev_hash で連結、seq が 1〜100 の連番。
| Backlog size | Outage type | Expected recovery |
|---|---|---|
| 30 posts (30店舗想定) | Mock IG API 500 | Outage 解除後 5分で全完了 |
| 100 posts (100店舗想定) | Mock IG API 500 | Outage 解除後 10分で全完了 |
| 300 posts (300店舗想定) | Mock IG API 500 | Outage 解除後 15分で全完了 |
| 100 posts | Mock IG rate limit (429) | exponential backoff で 20分で全完了 |
| 100 posts | Trigger.dev 自体の障害(mock) | Trigger.dev 復旧後 5分で re-enqueue |
| Dataset | Test | Pass 基準 |
|---|---|---|
| v2 現本番 10店舗 snapshot | v4 へ変換 + dual-read 検証 | 1店舗 5分以内、posts 件数 diff ゼロ |
| 100店舗合成データ(10倍) | 一括移行スクリプト | 全件 30分以内、ランダム 100件の内容一致 |
| 移行中に v2 へ新規投稿 | Dual-write が v4 にも反映されるか | 最大 10秒遅延で同期 |
| Rollback: v4 → v2 へ戻す | Neon Branch を活用 | 1店舗 10分以内、データ喪失ゼロ |
Publish 失敗時に Slack 通知が 60秒以内に到達。Sentry に exception 記録。Better Stack で downtime 計測。
任意の post の audit trail を SQL 1クエリで 1分以内に取得。
CF Workers から Neon への p95 query latency < 300ms(cold start 除外時)。cold start 込みで p95 < 3秒。
Neon、Clerk、Trigger.dev、R2 の全ベンダで設定した region が日本(ap-northeast-1 or Tokyo)であり、PII が region を出ないことを管理画面で証明。
| 時間帯 | Primary | Secondary | 応答時間 |
|---|---|---|---|
| 平日 9-19時 | 子安氏 | 高木氏 | 15分以内 |
| 平日 19時-翌9時 | 子安氏(on-call 手当付き) | 高木氏 | 30分以内 |
| 土日祝 | 当番制(子安氏・高木氏交替) | もう一方 | 30分以内 |
| Severity | イベント例 | 通知先 | 対応 |
|---|---|---|---|
| P1(即時) | Audit chain fork / DB breach / 全店舗 publish 不能 | Slack #incident + SMS(Better Stack) | 30分以内に primary 応答 |
| P2(1時間以内) | 予約投稿失敗 >10件 / Cron 遅延 >15分 / Clerk or Neon or Trigger.dev 障害 | Slack #ops + Email | 1時間以内に primary 応答 |
| P3(翌営業日) | 個別投稿失敗 / login スパイク / IG token 期限 48h 前 | Slack #ops | 翌営業日対応 |
Better Stack で以下を 1分間隔で監視:
Codex 7回目指摘 #8 対応で、closed 4問 + open 欄を用意。Q1 = Yes でも prototype に入る前に Q5(blocking concerns)を読み合わせる。
| # | 質問 | 回答形式 |
|---|---|---|
| Q1 | Decision(6章)の案γ で進めて良いか | Yes / No(No なら β or 代替案) |
| Q2 | Invariants 20項目に追加・削除・優先度変更はあるか | 追加リスト or「なし」 |
| Q3 | Evidence(10章)E1〜E9 の pass 基準に修正はあるか | 修正リスト or「なし」 |
| Q4 | Delivery Plan(11章)MVP を 12週間で実装可能か | Yes / No(No なら現実工期) |
| Q5 | Blocking concerns 自由記述(Q1-Q4 を Yes と答えても気になる点があれば) | 自由記述 |