# Launch プラン製品マッピングの並行処理修正

認可レビューの AUTH-03 を修正しました。Launch サーバー側と対応する回帰テストのみを変更し、デプロイや SDK 公開は行っていません。Licentivo と SDK の認可判断の境界は変更されていません。

## 変更

- マッピング保存時、トランザクション内でまず共通の Launch 製品レコードをロックし、その後プランバージョンをロックします。異なるプランやプロセスの保存リクエストも同じ製品ロックに従い、競合リクエストは HTTP 409 `PRODUCT_MAPPING_CONFLICT` を返します。
- `products.licentivo_product_id` は安定した公開製品ルートを保存します。初めてマッピングが正常に保存された後は、同じ Launch 製品の他のプランはこの Licentivo 製品のみを選択できます。異なるポリシーの選択は可能です。このフィールドは機能権限、デバイス枠、顧客の認可状態を保存しません。
- 顧客 ID は安定したルートを読み取り、すべてのプランの最大バージョンから製品を選択しなくなります。プラン公開時と SDK 連携設定生成時にも同じ紐付けを確認します。
- 旧データベースには null 許容フィールドが自動追加されます。安定フィールドがまだないデータは、支払い済みなどの購入履歴、過去の公開記録、残りのマッピングの順に参照します。同じ優先順位に複数の製品がある場合は競合を明示し、推測しません。一意の購入済み製品は無関係なドラフトより優先され、その後互換性のあるマッピングを保存することで紐付けを固定できます。製品内容の編集では紐付けを保持します。

複数の Licentivo 製品に販売された過去の誤ったデータは引き続き手動照合が必要で、今回は購入記録やリモートライセンスを書き換えません。別の Licentivo 製品を使用する必要がある場合は新しい Launch 製品を作成し、新しいプランで元の製品ルートを書き換えてはいけません。

実装は[マッピング保存](https://github.com/spf86/licentivo-launch/blob/main/internal/app/connections.go)、[製品ルート](https://github.com/spf86/licentivo-launch/blob/main/internal/app/product_mapping.go)、[ID 発行](https://github.com/spf86/licentivo-launch/blob/main/internal/app/identity.go)、[公開検証](https://github.com/spf86/licentivo-launch/blob/main/internal/app/catalog.go)、[連携チェック](https://github.com/spf86/licentivo-launch/blob/main/internal/app/integration.go)、[データモデル](https://github.com/spf86/licentivo-launch/blob/main/internal/storage/models.go)を参照してください。

## ローカル検証

| チェック | 結果 |
| --- | --- |
| 修正前の実装との比較 | 同じ回帰テストが競合する 2 件の保存が同時に成功する状態を安定して捕捉し、期待どおり失敗 |
| 修正後のマッピング専用回帰 | 競合リクエストは一方が成功、もう一方が 409。互換性のある 2 件の保存は両方成功。決済同期後の ID は誤ったドラフトの影響を受けず、SDK 連携層は正しい製品コンテキストを受け入れる |
| 過去データの保護 | 購入済みルートの優先、曖昧な場合のエラー、誤ったドラフトの公開禁止、誤った過去の公開から SDK 設定を生成できないこと、内容編集時の紐付け保持がすべて成功 |
| Go の全ソースパッケージと MySQL 回帰 | サブケースを含む 192 件のテスト合格記録。失敗なし。`TestProductUploadRoundTrip` はスキップされ、合格には数えていません |
| 旧データベース構造のアップグレード | ルート列がない構造は初期化後に null 許容フィールドが追加され、繰り返し初期化も成功 |
| 実際の Launch、Licentivo、Go SDK の連携テスト | 13 の業務段階が成功。購入、実際の署名済み機能実行、通常終了からの復元、ログアウト、リモート解除、旧世代、更新返金、アカウント失効、返金／チャージバック後の実行拒否を含む |
| 最終追加テスト | 新しい空のデータベースでマッピング専用回帰に成功。過去の公開に関する連携設定チェックも追加し、`go vet` が成功 |

完全回帰と実サービス連携テストの完了後、最後に SDK 連携チェックの整合性検証を追加し、このシナリオを含む専用回帰と `go vet` を個別に再実行しました。旧実装の期待された失敗は、テストが元の競合を捕捉できることを証明するものであり、未解決の回帰失敗ではありません。

連携テストはローカルの独立した MySQL 8.0.32、現在の Licentivo サービス、実際の Go SDK を使用しました。決済チャネルと DeepL は隔離された模擬サービスを使用し、実際の請求や有料翻訳はありません。並行処理と過去の誤ったマッピングの専用テストは実際の Launch 業務、MySQL、プロトコルシミュレーターを使用しました。今回 9 種類の SDK すべてで実サービス連携テストを完了したとは主張しません。

今回は `192.168.2.177` にアクセスしていません。今回起動した MySQL と Licentivo の一時プロセスは停止し、既存のサービスは保持しました。元資料は無視対象ディレクトリ `.local/mapping-fix-20261024/` にあり、共有可能な要約は[検証証拠](https://github.com/spf86/licentivo-launch/blob/main/docs/product-mapping-fix-evidence-2026-10-24.json)を参照してください。
