Skip to content

Research & Design Decisions — voting


Purpose: voting spec の design 書き直し(data-model の 3 系統契約と Stripe Webhook 駆動アーキテクチャへの適合)で行った調査・統合判断・トレードオフを記録する。

Summary

  • Feature: voting
  • Discovery Scope: Extension(既存の有料投票フロー設計を 3 系統データアーキテクチャと最新の data-model 契約に合わせて再構築)
  • Key Findings:
    • CreditPackage は microCMS 所有(完全イミュータブル + isActive のみ可変)。voting spec 内に重複の creditPackageService を持たず、features/cms/services/creditPackageService を call する。
    • 投票期間は lib/voting-period.getVotingPeriod() から取得し、Server Action 側でガードする。Vercel ENV の値変更は再デプロイで反映。
    • 合計票数 / 合計金額は クライアント値を信用せず Server で再計算(createPaymentIntent)、かつ Webhook 受信時にも再取得して再計算する。CreditPackage が完全イミュータブルなので両時点で同一値が得られる。
    • Vote 一括生成は Webhook ハンドラの prisma.$transaction に集約。Purchase.status='PROCESSING' 時のみ遷移する FOR UPDATE 行ロックで冪等化。

Research Log

Topic 1: data-model 契約の変化と voting への波及

  • Context: 旧 voting design は Prisma 単独前提で features/voting/services/{creditPackageService,votingPeriodService,nicknameService}.ts を抱え、Purchase に total*、PurchaseItem に *Snapshot、Vote に status 列があった。
  • Sources Consulted: voting/design.md(旧)、data-model/design.md(新)、steering/tech.md
  • Findings:
    • CreditPackage は microCMS 所有 → features/cms/services/creditPackageService を call。PrismaCreditPackage 型は存在しない。
    • VotingPeriod は ENV → lib/voting-period.getVotingPeriod()。null フォールバックではなく throw。
    • Vote イミュータブル → status 列なし、有効性は Purchase.status との JOIN。
    • PurchaseItem snapshot 列なし → 価格は microCMS から都度取得。
  • Implications:
    • voting spec の File Structure Plan から services/ 配下のサービスファイルを削除(features/voting/services/ 自体は nicknameService のために残るが、所有は data-model)。
    • 合計金額の改竄防止は「Server で都度再計算」のみ。Snapshot 列に頼らない。

Topic 2: Webhook 駆動の単一権威化と冪等化

  • Context: 「決済 = 投票」のワンショットを保証するには、Vote 生成のトリガーを 1 箇所に絞る必要がある。
  • Sources Consulted: Stripe Webhook 公式ドキュメント、data-modelPaymentStatus enum 設計
  • Findings:
    • クライアントから Vote 生成を呼ばない方針が要件 1.1(単一フロー)と非機能-冪等性に整合。
    • Webhook 重複配信(Stripe 公式仕様で複数回送られる可能性あり)に対し、paymentIntentId UNIQUE + status='PROCESSING' 限定で UPDATE することで冪等。
    • Stripe Webhook は 200 を返さなければ自動再試行されるため、microCMS 一時障害時もリカバリ可能。
  • Implications:
    • クライアントは getPurchaseStatus polling で SUCCEEDED 観測後に UI 反映するだけ。
    • Webhook ハンドラに prisma.$transaction + FOR UPDATE を明示。

Topic 3: クライアント polling の打ち切り時間

  • Context: Webhook 配送遅延(まれに数秒〜数十秒)時の UX を決める必要があった。
  • Sources Consulted: 既存 voting design、Stripe SLA
  • Findings:
    • 30 秒以内に SUCCEEDED 観測されない場合、pending_settlement 表示で「後ほど反映」を案内するのが典型的な UX。
    • 5 分以内に未観測なら error にしてサポート連絡導線を出す(ui-design.md 委譲)。
  • Implications:
    • usePurchaseFlow の状態機械に pending_settlement を含める。
    • 1 秒間隔で polling、visibilitychange で resume。
  • Context: 要件 7.x で「ワンクリック決済を優先、未登録ユーザーはカード入力」を要求。
  • Sources Consulted: Stripe Payment Element / Link 公式ドキュメント
  • Findings:
    • automatic_payment_methods: { enabled: true } で PaymentIntent を作成すると、Stripe が利用環境に応じて最適な決済手段(Link / カード / Apple Pay / Google Pay 等)を自動表示する。
    • Link は Stripe Dashboard で有効化すれば追加実装不要。
  • Implications:
    • 個別の決済手段判定は不要。<PaymentElement> をそのまま使う。
    • 設計を簡潔に保てる。

Architecture Pattern Evaluation

OptionDescriptionStrengthsRisks / LimitationsNotes
クライアント主導: Stripe 成功 callback で Vote 生成Server Action 経由で Vote を作成レスポンスが早い冪等化が難しい、ユーザーが画面を閉じると失敗不採用
採用: Webhook 駆動の単一権威Stripe Webhook で Vote 生成冪等・サーバー権威クライアントは polling 必要要件 1.1 に整合
Server Action + Webhook 二重生成防止Both で生成し UNIQUE で抑止速度と信頼性両立実装複雑度高、冪等化が二重に必要不採用
Snapshot 列を使う旧設計価格を PurchaseItem に保存過去明細が自己完結data-model の Out-of-scope不採用

Design Decisions

Decision: 合計票数を Webhook 受信時に microCMS から再取得して再計算する

  • Context: PurchaseItem に *Snapshot 列を持たない方針(data-model)で、決済成功時の合計票数をどう確定するか。
  • Alternatives Considered:
    1. Stripe PaymentIntent の metadata に totalCredits を保存し、Webhook 時に使う。
    2. Webhook ハンドラが microCMS から都度取得して再計算する。
  • Selected Approach: 2。CreditPackage が完全イミュータブル(data-model Req 3.3)であることを根拠に、再取得しても値が変わらない前提で動作する。
  • Rationale: metadata は 500 文字制限がありアイテム数が多い場合に収まらない。microCMS 取得は数 ms オーダーで Webhook タイムアウト内に収まる。
  • Trade-offs: microCMS が一時的にダウンすると Webhook も失敗するが、Stripe が自動再配信するため最終的には成功する。
  • Follow-up: Webhook ハンドラに「microCMS 取得失敗時は 500 を返して再配信を促す」分岐を実装。

Decision: クライアントは Vote を生成しない(楽観更新のみ)

  • Context: 旧設計の一部に「クライアントから Vote 作成 Server Action を呼ぶ」案があったが、Webhook と二重で生成する複雑さがあった。
  • Alternatives Considered:
    1. クライアントが Server Action で Vote を作成し、Webhook は status 更新のみ。
    2. Webhook のみが Vote を作成し、クライアントは status polling と楽観更新。
  • Selected Approach: 2。Webhook 駆動の単一権威。
  • Rationale: 冪等化が圧倒的に簡単。Stripe Webhook は外部からの破壊不可な権威として扱える。
  • Trade-offs: ユーザー観測は polling 経由で多少遅延するが、UX 上問題ない範囲。
  • Follow-up: クライアント側で楽観的得票数加算を行うフックを home / candidate-detail で活用。

Decision: useNickname の正規化はクライアント・サーバー両方で行う

  • Context: 要件 6.3 は「未入力・空白のみは無効」とし、UX として即時フィードバックが必要。
  • Alternatives Considered:
    1. サーバー側のみで検証し、クライアントはサブミット時に結果を受け取る。
    2. クライアント側でも normalizeNickname を実行し、リアルタイムバリデーション。
  • Selected Approach: 2。ただし「真の検証」はサーバー側で再実施。
  • Rationale: UX 上、入力エラーを即時表示するのが望ましい。クライアント検証は spoof 可能だがサーバーで再検証するため害なし。
  • Trade-offs: クライアントとサーバーで同じ正規化関数を保持(data-model 所有を両方から import)。
  • Follow-up: 正規化関数のテストは data-model 側で行い、voting spec は使用箇所のテストのみ。

Risks & Mitigations

  • Webhook 配送遅延: 1 秒間隔 polling + pending_settlement 表示。5 分以内未観測でサポート連絡導線。
  • Stripe Dashboard 設定漏れ(Webhook URL / Link 有効化): 環境構築チェックリスト(steering/structure.md の運用ノートに後続追加)。
  • microCMS のレート制限: unstable_cachegetActiveCreditPackages を 60-180 秒キャッシュ。Webhook 時は getCreditPackage(id) を直接(キャッシュ不要)。
  • クライアント時計の改竄による期間判定回避: Server Action 側で必ず getVotingPeriod() で再判定。
  • 大量 Vote 生成のトランザクション長期化: 合計票数を運用上の上限内(数千)に制限。CreditPackage の最大票数 × 最大数量で実質上限を設計時に確認。

References


Gap Analysis: voting × 既存コードベース(2026-05-17 時点)

目的: 既に approved 済みの voting spec(requirements / design / tasks)と、2026-05 の大規模リセット直後の現コードベースとの間に残るギャップを洗い出し、/kiro-impl voting 起動前に押さえるべき前提・選択肢・リスクを整理する。本ファイルは決定ではなく情報提供。

0. Discovery Scope

  • Discovery Scope: Greenfield 寄りの Extension(spec は完成済み・コードはリセット後の最小骨格のみ)
  • 対象 spec: voting(spec.json.phase = "tasks-generated"、approvals: requirements/design/tasks すべて approved)
  • 前提: data-model spec の tasks 完了を前提に置く設計(本 spec の冒頭注記による)

1. Current State Investigation

1.1 既存実装(投票 / 決済 / 関連基盤)

調査結果: 投票・決済に該当する実装は一切存在しない。リセット後のスケルトンのみ。

観点期待する実体現コード状態
Server Action: createPaymentIntentapp/actions/purchase.tsapp/actions/ ディレクトリ自体が存在しないMissing
Server Action: getPurchaseStatus同上同上Missing
Stripe Webhook ハンドラapp/api/webhooks/stripe/route.tsapp/api/ ディレクトリが存在しないMissing
features/voting/hooks/usePurchaseFlowfeatures/voting/hooks/*features/ ディレクトリ自体が存在しないMissing
features/voting/components/*PurchaseFlowContainer / PurchaseCreditsModal / StripePaymentModal / NicknameInput / CreditPackageRow同上Missing
features/cms/services/creditPackageServicedata-model spec で提供される予定未実装(data-model の tasks 未着手)Upstream Missing
features/cms/services/candidateService.getCandidatedata-model spec未実装Upstream Missing
features/voting/services/nicknameService.normalizeNicknamedata-model spec(features/voting/services/ の所有は data-model)未実装Upstream Missing
lib/voting-period.getVotingPerioddata-model spec未実装(lib/ には utils.ts のみ)Upstream Missing
lib/prisma.tsdata-model spec未実装Upstream Missing
prisma/schema.prisma(Purchase / PurchaseItem / Vote / PaymentStatus enum)data-model specprisma/ ディレクトリ自体が存在しないUpstream Missing
Stripe SDKstripe / @stripe/stripe-js / @stripe/react-stripe-jsstripe@^22@stripe/stripe-js@^9package.json に既導入。@stripe/react-stripe-js未導入Partial
ZodServer Action 入力検証zod@^4 導入済みPresent
React Hook Formフォーム制御react-hook-form@^7 / @hookform/resolvers@^5 導入済みPresent
Sonner(トースト)エラー通知の選択肢sonner@^2 導入済みPresent(任意)
app/page.tsx候補者一覧 → 投票導線プレースホルダのみ(再構築中 メッセージ)Stub
app/providers.tsxProvider 集約空の pass-throughStub
auth.ts / app/admin/**保有しない(steering 方針)存在しないCorrectly Absent

1.2 関連 spec の状況

specphaseapproval 状況本 spec への影響
data-modeltasks-generated / approved完了本 spec は data-model の services を call する前提。data-model の /kiro-impl が先行している必要がある
hometasks-generated / approved完了<PurchaseFlowContainer> を import するため、本 spec のコンポーネントが home の実装より先に存在する必要がある(または並行)
candidate-detailtasks-generated / approved完了同上。useVoteFlow / useVotingPeriodStatus を import
candidate-ranking(未確認だが存在)直接の依存なし(本 spec は ranking の純関数を借りない)

1.3 環境・外部統合の準備度

外部必要なもの現状
Stripe DashboardAPI キー(test/live)、Webhook エンドポイント設定、Link 有効化、対応決済手段の有効化未設定(運営オペ作業)。STRIPE_SECRET_KEY / STRIPE_WEBHOOK_SECRET / NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY の投入が必要
microCMScreditPackages / candidates コレクション + 読み取り専用 API キーdata-model spec のタスクで運営オペが必要(本 spec 外)
Neon / PostgresDATABASE_URL、Purchase/PurchaseItem/Vote テーブルローカルは docker-compose.yml が既にある(postgres:17 想定)。Prisma マイグレーションは data-model spec タスク
Vercel ENVVOTING_PERIOD_START / VOTING_PERIOD_END未投入(運営オペ)
@stripe/react-stripe-jsクライアント Payment Elementpackage.json に未追加。本 spec の tasks 1.1 で追加予定
Stripe CLIWebhook ローカル受信(stripe listen)ローカル動作確認に必須(docs にメモを残すべき)

1.4 保護パスと境界

/kiro-impl voting で新規生成すべきファイルはすべて CLAUDE.md の保護パス配下(features/**, app/api/**, app/actions/** 相当, app/_components/** は対象外だが本 spec は直接使わない)。直接 Edit / Write を回避し、必ず /kiro-impl voting 経由で生成する

例外的に編集可能なのは:

  • package.json(Stripe / React Stripe.js の依存追加)
  • next.config.ts(CSP やリモートパターン追加が必要なら)
  • ローカル .env.local テンプレ整備

2. Requirements Feasibility Analysis(Requirement-to-Asset Map)

Req(代表)必要アセット現状Gap タグ
F1.1 単一フロー(候補者+パッケージ+ニックネーム+決済)<PurchaseFlowContainer> + usePurchaseFlowMissingMissing(本 spec で新規作成)
F1.2 決済成功 = 合計票数加算Webhook handleSucceeded + vote.createManyMissingMissing(本 spec)
F1.3 失敗時 Vote 非生成Webhook handleFailedMissingMissing(本 spec)
F1.5 連続購入抑止usePurchaseFlowstep !== 'select' ガードMissingMissing(本 spec)
F1.6 期間外サーバーガードcreatePaymentIntentgetVotingPeriod()Missing(lib/voting-period 自体が upstream missing)Blocked by data-model
F2.x モーダル開閉・選択リセット・処理中無効化useVoteFlow + <PurchaseFlowContainer>MissingMissing(本 spec)
F3.x モーダル内コンテンツ<PurchaseCreditsModal>MissingMissing(本 spec)
F4.x 有効パッケージを displayOrder 順で取得creditPackageService.getActiveCreditPackagesMissingBlocked by data-model
F5.x 数量入力 + 合計即時更新 + 0 で disableusePurchaseCart + <CreditPackageRow>MissingMissing(本 spec)
F5.5 改竄抑止のサーバー再計算createPaymentIntent 内で getCreditPackage(id) 並列取得MissingMissing(本 spec、ただし upstream getCreditPackage 必須)
F6.x ニックネーム入力 + localStorage 永続化 + 1〜32 + 補助テキストuseNickname + <NicknameInput> + normalizeNickname(data-model)MissingMissing(本 spec) + normalizeNicknameBlocked by data-model
F7.x Stripe Payment Element(Link 優先 + カード)<StripePaymentModal> + @stripe/react-stripe-jsMissing(SDK も未導入)Missing(本 spec)
F8.x 決済金額・対象表示・第三者プロバイダ告知<StripePaymentModal> UIMissingMissing(本 spec)
F9.x 決済処理中状態 + 成功フィードバックusePurchaseFlow step 機械MissingMissing(本 spec)
F10.x 成功後シーケンス(加算 + 永続化 + Confetti + クローズ + リセット)usePurchaseFlow.onSuccess + useVoteFlow.handlePaidVoteConfirmed + useNickname.persistMissingMissing(本 spec)
F11.x 閉じ時リセット + 処理中閉じ不可<PurchaseFlowContainer>MissingMissing(本 spec)
非機能-トランザクションWebhook 内 prisma.$transactionMissingMissing(本 spec、要 Prisma スキーマ)
非機能-冪等性paymentIntentId UNIQUE + status='PROCESSING' FOR UPDATEMissingBlocked by data-model(UNIQUE 制約) + 本 spec(Webhook ロジック)

Unknown / Constraint / Complexity

  • Unknown V1: data-model spec の /kiro-impl が完了しているか不明。本 spec の /kiro-impl voting 起動前に kiro-spec-status data-model で確認すべき。
  • Unknown V2: Stripe Dashboard の Webhook URL は Vercel デプロイ後に確定する。ローカルでは stripe listen --forward-to localhost:3000/api/webhooks/stripe を必須として開発ドキュメントに記述する必要がある
  • Unknown V3: @stripe/react-stripe-js のバージョンは未確定。@stripe/stripe-js@^9 と互換のメジャーを選ぶ必要がある(現状 v3.x 系が stripe-js@^9 と同期、要確認)。
  • Unknown V4: CSP ヘッダーで Stripe.js を許可する設定が next.config.ts に未投入。本番運用に向けて script-src への https://js.stripe.com 追加検討が必要(本番デプロイ前に追加)。
  • Constraint V1: 保護パス(app/api/**, features/**, app/actions/ 相当)はすべて /kiro-impl voting 経由で生成する。直接 Edit / Write 禁止。
  • Constraint V2: useNicknameクライアント側 から normalizeNickname を import する設計。data-model spec が「Server / Client 両用の純関数として export」することを保証しているため、'use client' を含まない純関数ファイル(features/voting/services/nicknameService.ts)に置く必要がある(本 spec design の Boundary Commitments 通り)。
  • Complexity Signal: アルゴリズム的に難所はない。難所は (i) Webhook 冪等性の二段ガード(FOR UPDATE + 条件付き updateMany)(ii) クライアント polling + visibilitychange + 30s/5min タイムアウトの 3 段階遷移(iii) Stripe Payment Element の clientSecret ライフサイクル(モーダル閉じ時の Elements インスタンス破棄)、(iv) useNickname の SSR 安全な初期化

3. Implementation Approach Options

Option A — design / tasks をそのまま実装(Stay-the-course)

  • 概要: approved 済みの design.md / tasks.md(セクション 1〜7、サブタスク 1.1 / 1.2 / 2.1 / 3.1 / 3.2 / 4.1 / 4.2 / 4.3 / 5.1〜5.5 / 6.1〜6.5 / 7.1〜7.3)を /kiro-impl voting で順番に実装する。前提として /kiro-impl data-model を先行完了させる
  • トレードオフ:
    • 良: spec / steering / Boundary Commitments と完全整合。再設計コストゼロ。Webhook 駆動の単一権威 + 冪等化が design に明文化済み。
    • 良: 依存追加は @stripe/react-stripe-js のみで済む(他は既導入)。
    • 良: タスクの粒度が細かく、reviewer subagent が境界違反を検出しやすい。
    • 悪: data-model/kiro-impl 未完了時に着手すると upstream missing で半数以上のタスクが blocking になる。
    • 悪: テスト基盤(Vitest / Playwright)が未導入のため、tasks 7.1〜7.3 は「テストファイルを書くだけで CI 実行できない」状態になる可能性がある(data-model research の R2 と同じ懸念)。
  • 適合度: 最も高い。Discovery Scope(Extension)と Boundary Commitments の整合性は完璧。

Option B — data-model と並行で進め、契約モックで stub する(Aggressive Parallelization)

  • 概要: data-model の services を 未実装のまま モック関数(/* TODO: data-model 実装後に差し替え */ コメント付き)で stub し、voting の UI / Hook / Server Action 骨格を先に走らせる。data-model 完了後にモックを実 SDK 呼び出しに置換する。
  • トレードオフ:
    • 良: 開発時間が圧縮され、フロントエンド作業者とデータ層作業者が並行できる。
    • 良: UI レイアウト(<PurchaseCreditsModal> / <StripePaymentModal>)を早期に視覚化できる。
    • 悪: モックと実 SDK のシグネチャ齟齬で 再修正コストが発生しやすい(特に Prisma の Purchase レコード形状や microCMS の getCreditPackage 戻り値型)。
    • 悪: /kiro-impl voting のレビューア subagent は「実 import が存在しない」状態を完了として認めない可能性が高い。
    • 悪: Boundary Commitments の "Allowed Dependencies" に data-model services が含まれており、stub 化は境界違反に近い。
  • 適合度: 低。data-model の所要工数が小さい(M)ので、並列化のメリットがコストを上回りにくい。

Option C — 最小実装で先に E2E 通し、Webhook 周辺は後追い(Vertical Slice First)

  • 概要: tasks のうち F1.1 / F1.5 / F5.x / F6.x / F7.x / F9.x の単純な happy path のみ(3.1, 4.1, 4.2, 5.1〜5.5, 6.1〜6.5)を最小実装して 1 候補者 1 パッケージで決済完了するまで通し、F1.3(失敗) / F1.6(期間外) / 非機能-冪等性 / Webhook 重複 / 払戻 / polling タイムアウトは後続イテレーションに回す。
  • トレードオフ:
    • 良: 最短で「投票が成立する」状態を見せられる。Stripe 設定の生死を早期に検証可能。
    • 悪: 非機能-冪等性 は本来 Webhook 実装と同時に必要(Stripe は実環境で当然のように重複配信する)。後追いだと初回本番投入時の事故リスクが高い。
    • 悪: tasks.md の section/サブタスクのつながり(_Depends: 関係)を崩すため、レビューア subagent の検出がノイジーになる。
  • 適合度: 中。デモ用なら有効だが、本番投入を見据えるなら Option A 推奨。

4. Effort & Risk

観点判定1 行根拠
Effort(本 spec 単独、data-model 完了後)L(5〜10 日)新規ファイル数が多い(Server Action 1、Webhook 1、Hook 5、Component 5、型 1)。Stripe ローカルテスト + Webhook 冪等性検証 + クライアント polling の 3 セットで統合確認のラウンドが膨らむ。
Effort(data-model 含む合計)XL(8〜17 日)data-model M + voting L。並列化は限定的なため線形に近い。
RiskMedium-High(1) Stripe Webhook 冪等性の検証は本番相当の負荷でしか露見しないバグが残りやすい、(2) useNickname の SSR / Hydration 安全性は React 19 + Next.js 16 で挙動を確認する必要、(3) @stripe/react-stripe-js のバージョン互換、(4) vote.createMany で合計票数が数千を超えるシナリオの実測未経験。アーキテクチャ的な未知数は小さいが、統合点が多い。

5. design phase / 実装直前への推奨事項

design.md / tasks.md は approved 済み。/kiro-impl voting を起動する前にチェックしておくべき項目を列挙する。

  1. 推奨アプローチ: Option A(approved 通りに実装)。並列化や最小実装で時間を節約するメリットより、境界遵守 + 冪等性確保のメリットが大きい。
  2. 着手順序の明確化: kiro-spec-status data-model を実行し、data-model の tasks が completed であることを確認してから /kiro-impl voting を起動する。完了していない場合は先に /kiro-impl data-model を起動
  3. 依存追加のレビュー:
    • package.json@stripe/react-stripe-js を追加(tasks 1.1)。互換バージョンは @stripe/stripe-js@^9 と整合するメジャーを採用(本 spec design / tasks の中ではバージョン固定なし、/kiro-impl 中に最新の互換版を確認)。
    • stripe@^22@stripe/stripe-js@^9 は既に package.json に存在するので追加不要。
  4. 環境変数テンプレ: .env.example に以下を追加する手順を /kiro-impl 実行時のチェックリストに含める(/kiro-impl 中に作成可、.env.example は spec 外パスのため直接編集可)。
    • STRIPE_SECRET_KEY
    • NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY
    • STRIPE_WEBHOOK_SECRET
    • UPSTASH_REDIS_REST_URL / UPSTASH_REDIS_REST_TOKEN(steering で予定されているが本 spec の tasks には未含。レート制限は steering 方針のため後続 spec で対応する想定)
  5. 本 spec の所有境界(再確認):
    • useNickname / usePurchaseCart / usePurchaseFlow / useVoteFlow / useVotingPeriodStatus本 spec 単独所有(features/voting/hooks/)。home / candidate-detail は import のみ。
    • normalizeNickname再定義禁止(features/voting/services/nicknameService.ts は data-model 所有)。
    • lib/voting-period.ts複製禁止(data-model 所有)。
  6. Research Needed(次フェーズに持ち越し):
    • R1: @stripe/react-stripe-js の最新互換版を確認(stripe-js@^9 系との Peer Dependency)。
    • R2: Next.js 16 の Server Action における CSRF 既定動作の現行仕様確認(design の Security Considerations 前提の有効性)。
    • R3: Vitest / Playwright 導入は本 spec の tasks 7.1〜7.3 で必要だが、テスト基盤 spec の有無を kiro-spec-status で確認。なければ testing-foundation 新 spec を立てるか、tasks 7.x を「テストファイル作成のみ・CI 連携は別 spec」と注記して進める判断が必要。
    • R4: 本番運用での Stripe Webhook URL(Vercel 環境変数経由ではなく Stripe Dashboard 側設定)の運用フローを steering/structure.mddocs-site/** に追記。
    • R5: vote.createMany の最大件数想定(CreditPackage の最大票数 × 最大数量 × カートアイテム数)を運用上限として明示し、design の Performance に追記。例: 最大票数 150 × 数量 20 × アイテム 20 = 60,000 件は要再評価(Postgres INSERT 一発の安全上限を超える可能性)。
  7. Fallback シナリオ:
    • Stripe Link が日本市場で利用不可だった場合: automatic_payment_methods 設定は維持しつつ Link を Dashboard で無効化。requirements の「ワンクリック決済優先」表現は維持(他のウォレットが代替する)。
    • Webhook が安定動作しない場合(極めて稀): クライアント polling 経由でも Server Action から stripe.paymentIntents.retrieve(id) を呼び、Webhook 未着でも SUCCEEDED が観測できる二重経路を追加(design に未記載の補強策)。

6. Output Checklist

  • [x] Requirement-to-Asset Map(セクション 2 表)
  • [x] Implementation Options A / B / C(セクション 3)
  • [x] Effort(L / XL)& Risk(Medium-High)判定(セクション 4)
  • [x] 推奨事項(Option A)と Key Decisions の再確認(セクション 5)
  • [x] Research Needed(R1〜R5)
  • [x] Fallback シナリオ(セクション 5.7)

7. 次の一手(design phase に進むうえで)

本 spec は既に design approved だが、本 gap analysis を踏まえた次の一手を整理する。

  1. kiro-spec-status data-model を実行し、data-model の tasks 進捗を確認する。未完なら 先に /kiro-impl data-model を起動(本 spec の前提条件)。
  2. data-model 完了後に /kiro-impl voting を起動。サブタスク順は tasks.md の依存関係(_Depends:)に従う(1.1 → 2.1 → 3.1 → 3.2 → 4.1 → 4.2 → 4.3 → 5.x → 6.x → 7.x)。
  3. Storybook / Vitest / Playwright の導入有無を別途確認し、なければ /kiro-spec-init testing-foundation で新 spec を立てる(本 spec の tasks 7.x の CI 検証が成立する前提を整える)。
  4. Stripe Dashboard の Webhook エンドポイント設定 + Link 有効化 を運営オペとして実施(/kiro-impl の前にチェックリストとして残す)。
  5. @stripe/react-stripe-js のバージョン/kiro-impl voting 内の tasks 1.1 で確定して package.json に追加する。
  6. 想定外の挙動(冪等性で Vote が二重生成、polling で SUCCEEDED 観測できないなど)が発生した場合、本 research.md の Risks & Mitigations セクションを参照してデバッグ方針を決める。