# 修正計画書（content-verification 調査） — 2026-06-29

**作成ブランチ**: `claude/content-verification-undx76`
**取り込み方法**: 本ブランチを別エンジニアが Cursor で確認し、`main` へ反映 → サーバへデプロイ（GitHub Actions からのデプロイは現状不可。後述 B）。
**位置づけ**: 全6ドメインの「正本ドキュメント ↔ 実コード」突合調査（`docs/funnel-spec.md` 他）の結果から、**明らかに修正した方がよい4項目（A〜D）** の修正方針・影響範囲・リスク・検証計画をまとめた提案書。実コードはまだ変更していない（合意後に着手）。

---

## サマリ（優先度つき）

| 項目 | 内容 | 種別 | 優先 | 規模感 |
|---|---|---|---|---|
| **A** | ドキュメント陳腐化の是正（購入後付与は「実装済み」） | ドキュメント | 🔴最優先 | 小（数行） |
| **B** | CI `deploy.yml` の整理（SFTP非対応で全赤字） | CI/運用 | 🟠高 | 小（要方針判断） |
| **C** | 会員の初回ログイン導線（ウェルカムメール＋PW設定） | 機能追加 | 🔴高 | 中 |
| **D** | 契約書の締結実行（署名画面/電子署名/PDF/メール） | 機能追加 | 🟠中 | 大（要設計） |

**共通の運用前提（再掲）**: 本番は `php artisan queue:work` と `schedule:run`（cron）の常時稼働が必須（停止＝配信・リマインダ・購入後付与が止まる）。

---

## A. ドキュメント陳腐化の是正（最優先・最小リスク）

### 背景・根拠（実コードで確認済み）
調査の結果、「商品購入後の自動付与は未接続／ログのみ」という記述は**古く、実態と異なる**。**テレコムクレジット経路の自動付与は完全に実装・配線済み**：

- `app/Services/TelecomCredit/TelecomCreditWebhookService.php:92`（入金確定）
  → `app/Services/OrderPaymentFulfillmentService.php::markPaid()`（`fulfillPaidOrder()` を実呼び出し）
  → `app/Services/OrderPurchaseFulfillmentService.php::fulfillPaidOrder()`
    - `grantMembershipAccess()`: `SiteMember` を `firstOrCreate` ＋ `MemberBundleGrant(source='purchase')` 付与
    - `registerScenarioSubscriber()`: `Subscriber` 登録（`registration_route='purchase:order:{uid}'`）
- テスト: `tests/Unit/ProductPlanPurchaseGrantServiceTest.php`

### 陳腐化している記述 / 正しい記述
| 箇所 | 状態 |
|---|---|
| `CLAUDE.md`（2026-05-15 ログ）「`OrderPaymentFulfillmentService` は会員サイト/シナリオ等はログのみ」 | ❌ 陳腐化。後続の 2026-05-29 ログ「入金確定時 `OrderPurchaseFulfillmentService` で会員付与・シナリオ登録」で実装済み |
| `docs/funnel-spec.md` §10.1「Stripe / UnivaPay / 銀行振込の購入完了 Webhook から会員作成・自動付与する処理は未接続」 | ⭕ 狭義には正しい（これらは未実装）。ただし「未接続」だけ読むとテレコムも含むと誤読しやすい |
| `docs/funnel-spec.md` §11/§9.1「テレコム入金確定後の自動付与は `OrderPurchaseFulfillmentService`」 | ⭕ 正しい（コードと一致） |

### 修正方針
1. **`CLAUDE.md`**: 追記式ログの方針に従い、**末尾ではなく最新日付で新エントリを追記**（過去ログは消さない）。内容例:
   > **購入後付与の実装状況（明確化）**: テレコム入金確定 → `OrderPaymentFulfillmentService::markPaid` → `OrderPurchaseFulfillmentService::fulfillPaidOrder` で会員作成＋`member_bundle_grants(source=purchase)`＋シナリオ登録まで**実装済み**。2026-05-15 ログの「ログのみ」は当時の状態で、現在は該当しない。**未実装は Stripe / UnivaPay / 銀行振込（決済経路そのものが未実装）と、購入者の初回ログイン案内メールのみ**（本計画 C）。
2. **`docs/funnel-spec.md` §10.1（562 行付近）**: 「未接続」の主語を明確化（例: 「**テレコム以外の** Stripe / UnivaPay / 銀行振込は…未接続。テレコムは実装済み（§9.1・§11）」）。

### リスク / 検証
- リスク: ほぼ無し（文書のみ）。
- 検証: `git diff` で文書差分のみ。`ProductPlanPurchaseGrantServiceTest` が引き続き green であることを確認（コードは触らない）。

---

## B. CI `deploy.yml` の整理（全赤字の解消）

### 背景・根拠
- ワークフロー `Deploy to Production`（`.github/workflows/deploy.yml`）が **6 回連続で失敗**。
- 失敗箇所はデプロイ転送ステップのみ。ビルド（composer / npm / vite）は成功。
- エラー: `protocol: invalid parameter - you provided "sftp". Try "ftp", "ftps", or "ftps-legacy"`。
  - 使用アクション `SamKirkland/FTP-Deploy-Action@v4.3.5` は **SFTP 非対応**（FTP/FTPS のみ）。`protocol: sftp` / `port: 22` を渡しているため必ず失敗。
- セキュリティ上の懸念（平文コミット）: `server: 133.130.90.66`、`username: root`、配置先 `/var/www/html/putage.maspis.com/`、**root＋パスワード認証**でファイル丸ごと転送。`php artisan migrate` / `view:cache` 等のデプロイ後処理なし。
- 構成上の注意: この `deploy.yml` と一連の Jun18-19 コミットは **ブランチ `wip-version-badge-and-import-20260619`（`bc253a6`）にのみ存在**。**現在の `main`（`55101ad`）には `.github` 自体が無い**（＝今の main に CI/デプロイ自動化は入っていない）。

### 方針の選択肢（要・別エンジニア判断）
実デプロイは Cursor から手動で行っており、GitHub からサーバへ届かない前提なので、**自動デプロイを復活させるか否か**を先に決める。

1. **【推奨・手動デプロイ継続なら】デプロイ責務を外す**: ワークフローを「**ビルド/テストのCIチェック専用**」に変更（`composer install` → `npm ci && npm run build` → `php artisan test`）。`SamKirkland/FTP-Deploy-Action` ステップを削除。赤字は解消し、PR/Push 時の壊れ検知だけ残る。
2. **【自動デプロイを正式採用なら】SFTP 対応アクションへ置換 ＋ 安全化**:
   - アクションを `wlixcc/SFTP-Deploy-Action`（または `Dylan700/ftp-deploy-action` の sftp／`appleboy/scp-action`／rsync over SSH）に置換。
   - **SSH 鍵認証**（パスワード廃止）、**非 root の deploy ユーザー**、`server`/`username`/`path` も **secrets 化**。
   - 転送後に SSH で `php artisan migrate --force` / `view:cache` / `storage:link` を実行するステップを追加。
3. **【不要なら】`deploy.yml` を削除**（手動デプロイに一本化。CI を持たない）。

> なお現状 main に `.github` が無いため、いずれの案も **「main にどのブランチの作業を正とするか」**（後述・履歴整理）と併せて決める必要がある。

### 履歴整理の論点（B に付随・重要）
- `main`(55101ad) に Jun18-19 の作業（バージョンバッジ・取り込み改善・deploy.yml）が**入っていない**。これらは `wip-version-badge-and-import-20260619` にのみ存在。
- **何を正として main に集約するか**（wip を main へ取り込むのか、main が正で wip を破棄するのか）を別エンジニアと確認するのが先決。CI 修正はその後。

### リスク / 検証
- 案1/3: リスク低（デプロイ挙動を変えない／CIのみ）。
- 案2: 本番デプロイ経路を変えるため**ステージングで要検証**。鍵・secrets の設定が必要。
- 検証: ワークフローは push 時に走るため、まず wip 系ブランチで dry-run。

---

## C. 会員の初回ログイン導線（ウェルカムメール＋パスワード設定）

### 背景・根拠（実コードで確認済み）
- 購入後付与（A）で会員は作られるが、**パスワードが `Hash::make(Str::random(24))`**（`OrderPurchaseFulfillmentService::grantMembershipAccess`）＝**本人が知らない値**。
- **ウェルカム/初回ログインメールは存在しない**（`app/Mail/` はイベント系3つのみ。`OrderPurchaseFulfillmentService` にメール送信なし）。
- **会員のパスワード再設定（セルフ）も無い**（`membership_site_members` に reset/verify トークン列なし。会員PW更新は管理者操作 `site.members.password` のみ）。
- 結果: **購入者は自分のアカウントにログインできない**。＝購入フローが実運用上で完結しない最大の機能欠落。

### 修正方針（最小構成）
1. **パスワード設定トークンの保管**（いずれか）:
   - 案a（推奨・明示的）: 新テーブル `member_password_setups`（`site_member_id`・`token`(unique)・`expires_at`・`used_at`）。
   - 案b（軽量）: `membership_site_members` に `password_setup_token` / `password_setup_expires_at` を追加。
2. **メール送信**: 新 Mailable `MemberWelcomeMail`（件名「会員登録が完了しました／ログイン設定のご案内」）。
   - 送信タイミング: `OrderPurchaseFulfillmentService::grantMembershipAccess()` で **新規 `SiteMember` 作成時のみ**（`firstOrCreate` の created 判定）。既存会員・Webhook 再送では送らない（冪等）。
   - 送信元: 当面はプラットフォーム既定メーラー＋サイト名。将来はサイト/商品オーナーの `delivery_accounts` 送信元に寄せる（イベントの `EventOutboundMailService` と同型）。
   - 本文にパスワード設定リンク（トークン付き）と会員ログインURL（`/member/{slug}/login`）。
3. **公開ルート**（`web-public.php` の `member` グループ内・非認証）:
   - `GET  /member/{slug}/password-setup/{token}` … トークン検証→PW設定フォーム表示。
   - `POST /member/{slug}/password-setup/{token}` … PW更新→`used_at` 記録→`member.login.show` へ。
   - スロットル付与（`throttle`）、トークンは**単回使用＋有効期限**（例 72h）。
4. **（任意・推奨）セルフのパスワード再設定**: 「パスワードをお忘れの方」→ 同じトークン基盤でメール再送。スコープが広がるため**フェーズ2**として分離可。

### 影響ファイル（想定）
- 追加: `app/Mail/MemberWelcomeMail.php`、ビュー `resources/views/emails/member-welcome.blade.php`、`app/Http/Controllers/MemberPasswordSetupController.php`、マイグレーション（案a のテーブル or 案b の列）、`resources/views/member/password-setup.blade.php`。
- 変更: `app/Services/OrderPurchaseFulfillmentService.php`（新規作成時にトークン発行＋メール送信）、`routes/web-public.php`（ルート追加）、`app/Models/SiteMember.php`（必要に応じリレーション）。
- ドキュメント: `docs/funnel-spec.md` §10.1（「初回ログイン案内メールは未実装」を実装済みに更新）、§11 にチェック項目追加、`docs/manual.md`。

### リスク / デグレ注意 / 検証
- リスク: メール未達（送信元未設定環境）→ 送信失敗時もログ＋画面は壊さない（既存の `DeliveryAccountMailService` 方針に合わせる）。トークン漏洩→単回・期限・スロットルで緩和。
- デグレ注意: `firstOrCreate` の created 判定を誤ると**重複送信**。Webhook 再送・手動付与（`source=manual`）では送らない設計にする。
- 検証: `Mail::fake()` で「購入Webhook→新規会員→`MemberWelcomeMail` 送信」を確認するFeatureテスト。トークンルートでPW設定→ログイン成功までのテスト。`tests/Unit/ProductPlanPurchaseGrantServiceTest.php` の非回帰。

---

## D. 契約書の締結実行（署名画面 / 電子署名 / PDF / メール）

### 背景・根拠（実コードで確認済み）
- `app/Http/Controllers/ContractInstanceController.php::send()`（813-837 行）は **ステータス遷移＋`sent_at` 更新のみ**。最後に flash 文言 `'送付しました（実際のメール送信・署名画面は未接続です）'` を返し、**コード自身が未接続を明言**。
- メール送信（`Mail::`/Mailable）・**公開署名ルート**・PDF生成は**いずれも無し**（`contract.*` は全て管理者用ルート）。
- 一方、土台は揃っている: 署名者に `access_code` / `party_type` / `sign_order` / `email`、配置（`contract_instance_placements`）は %座標で JSON 永続化済み、メール文面マージ `ContractEmailTemplateMergeService` あり。
- **PDFライブラリ未導入**（composer に dompdf/tcpdf/mpdf/snappy なし）。締結PDF生成には依存追加が必要。

### 修正方針（フェーズ分割を推奨）
> 本項目は大型で、電子署名の**法的妥当性（タイムスタンプ・改ざん検知）**は製品判断を要する。まずは別途**設計メモ（`docs/contract-signing-design.md`）**を起票し、段階導入する。

- **フェーズ1: 署名画面の公開URL＋アクセス制御**
  - 公開ルート `GET /contract/sign/{token}`（`web-public.php`・署名付きURL or `access_code` ゲート）。署名者ごとにトークン発行。
  - PDF.js で文書を表示し、当該署名者の配置パーツ（テキスト/日付/署名枠）だけ操作可能に。
- **フェーズ2: 署名キャプチャ＋ステータス進行**
  - テキスト/日付入力、署名/印影は canvas（signature pad）でキャプチャ。`POST` で配置値＋署名画像を保存（新テーブル `contract_instance_signatures` 等）。
  - `sign_order` に従い次の署名者へ。全員完了で `status=completed`・`completed_at`。承認ワークフロー（`pending_approval`→…）の自動遷移もここで接続。
- **フェーズ3: 締結PDF生成**
  - 既存PDFに配置値を**スタンプ**するため `setasign/FPDI`（既存PDFへのオーバーレイに適）＋必要なら `tecnickcom/tcpdf`。`composer require` で依存追加。
  - 完成PDFを `storage` 保存し、当事者へ配布。
- **フェーズ4: メール送信**
  - `send()`／承認遷移／リマインダ／期限切れで Mailable 送信。文面は既存 `ContractEmailTemplateMergeService` の置き換え（`%契約書名%` 等）を利用。リマインダ/期限切れはバッチ（`schedule:run`）。
  - `send()` の flash 文言「未接続です」を実挙動に合わせて更新。

### 影響ファイル（想定・フェーズ1-2 中心）
- 追加: 公開署名コントローラ、署名ビュー（PDF.js＋signature pad）、`contract_instance_signatures` マイグレーション、Mailable 群（送付/リマインド/期限切れ）。
- 変更: `ContractInstanceController::send()`（メール送信接続・flash更新）、`routes/web-public.php`、`config`（PDFライブラリ）、`composer.json`（FPDI 等）。
- ドキュメント: `docs/contract-spec.md`・`docs/gmo-sign-functional-spec.md`、`docs/funnel-spec.md`（横断チェック）。

### リスク / 検証
- リスク（高）: 法的妥当性・改ざん検知は本実装の範囲外であることを明示（製品判断）。公開署名URLの**認可**（access_code/署名付きURL・有効期限・単回）を厳格に。PDF依存追加でビルド/サーバ環境（GD/フォント）要件が増える。
- 検証: フェーズごとに Feature テスト（トークン認可、署名保存→ステータス遷移、PDF生成のスモーク、`Mail::fake()` 送信）。

---

## 推奨の進め方（取り込み順）

1. **A（ドキュメント是正）** … 即時・最小リスク。まず誤読の再発を止める。
2. **B の方針決定** … 「自動デプロイを復活させるか」「main/wip のどちらを正にするか」を別エンジニアと確認 → 案1〜3を選択。
3. **C（会員初回ログイン導線）** … 購入フローを実運用可能にする。中規模・独立して実装可能。
4. **D（契約締結）** … 設計メモ起票 → フェーズ1から段階導入。

> 本書は提案であり、実コードは未変更。合意した項目から、本ブランチ上で diff を作成して提案する。
