mcpField
M
migrate-placements

migrate-placements

github.com/adaptyteamマーケティング・グロース688 回インストール1 日前に更新
リポジトリ

migrate-placements について

migrate-placements は、Adapty のペイウォール配置を Flow 配置へ移行するスキルです。既存の配置と Flow を調べ、新しい配置を作成し、既存データを変更せずにコード変更を引き継ぎます。

主な機能

  1. 01配置と既存 Flow の棚卸し
  2. 02Flow とペイウォール配置の照合
  3. 03新しい Flow 配置の作成
  4. 04旧 ID と新 ID の対応表作成
  5. 05既存配置の維持

活用シーン

  1. 01ペイウォール配置から Flow への移行
  2. 02途中まで進んだ移行の再開
  3. 03アプリリリース向けの配置変更準備

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/adaptyteam/adapty-skills --skill migrate-placements

インストール後、Claude Code で/migrate-placementsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
api-surface.md
migrate.py
stub-flow.json

詳細

migrate-placements

話し方

固定ブロックを含め、ユーザーが読むすべての行で:

  • 私がすることには「私」、相手がすることには「あなた」を使います。 結果から始め、次のステップを1つ示して終えます。前置きも、依頼内容の要約もしません。
  • 1つのブロックで太字にするのは最大1か所です。 固定ブロックの見出しがその1か所です。App、Flows、Permanent などのラベルは通常の書式にし、選択肢のリストにあるルート名(Reuse、Convert、Build、Stub)も通常の書式にします。太字の導入句が4つあれば、太字は4か所です。
  • ユーザーが入力する値、または別の場所で目にする値だけを生の値で示します。 プレースメントの開発者 ID はコード書式にします。ユーザーがアプリに入力するためです。アプリはタイトルで、フローは名前で示します。同じ名前のフローが複数ある場合は最終編集日も付け、フローページへのリンクを付けます。アプリやフローの UUID は使いません。件数とステータスは通常の言葉で示します(「1件、現在公開済み」など)。コード書式にはしません。
  • 計画を変える質問だけをします。 質問への答えがどれであっても結果が同じなら、質問せずに1行で伝えます。
  • 同じことを繰り返しません。 承認ゲートで伝えた内容を、最終レポートで繰り返しません。
  • クリックできる完全なリンクを使います。 固定ブロックも含め、ユーザーの言語で返信します。

このスキルでできること、できないこと

Adapty のドキュメントページをこのスキルからリンクできない場合、つまりダッシュボードでのプレースメント、オーディエンス、フローの動作について調べる場合は、adapty-docs スキルを使ってください。トピック名からドキュメント URL を組み立ててはいけません。

このスキルの全体像を決める事実は2つあり、どちらも実測されています (api-surface.md)。

プレースメントのコンテンツタイプは、作成後に変更できません。 オーディエンスがデプロイされた環境では、ペイウォールプレースメントにフローのオーディエンスを指定した placements update は Placement type can not be changed. と拒否されます。これはバックエンドのルールであり、CLI の機能不足ではありません。変換はできません。 すべての移行は placements create で行い、既存のペイウォールプレースメントはそのまま維持されます。

プレースメント ID は恒久的です。 ドキュメントには、次のように記載されています。「プレースメント ID は、タイプを問わずアプリ内のすべてのプレースメントで一意です。そのため、同じ ID をある場所ではフローに、別の場所ではペイウォールに使うことはできません。」(placements.md)プレースメントの削除は対象外のため、新しいプレースメントで古い ID を再利用することはできず、誤って作成されたプレースメントはアプリに永久に残ります。作成を始める前に、すべての ID を完全な形で提示し、承認を得ます。

そのため、成果物はプレースメントとコード変更の引き継ぎ情報であり、ダッシュボード上で完了するものではありません。 このスキルは、新しいフロープレースメントと old paywall placement ID → new flow placement ID の対応表を作成します。アプリで呼び出し箇所を変更してリリースするまでは、ユーザーに何も届きません。 これは明確に伝えるべき安全性の特性でもあります。リリースまでは古いプレースメントが引き続き機能するため、何もリリースしないことがロールバックになります。

作業の一部がすでに済んでいる可能性もあるため、作成する前に確認します。 移行の途中まで進めたユーザーは、一部のペイウォールに対応するフローをすでに用意していることがあります。確認せずに実行すると、内容が少ないフローをもう1つ作成することになります。flows delete がないため、そのフローは永久に残ります。フェーズ2で既存のフローを読み込み、フェーズ4でそれぞれの対応を決めます。

対象範囲。このスキルはフローコンテンツを設計または作成しません。それは flow-generator の役割であり、flows config update、プレビューの反復、公開を担当します。ペイウォールをフローに変換することもしません。それは上記のダッシュボードボタンをユーザーがクリックして行う操作で、CLI に相当する機能はありません。このスキルの役割は、作成されたフローを見つけて、そこにプレースメントを設定することです。アプリ側の呼び出し箇所も実装しません。それは adapty-integration の役割です。また、既存のプレースメント、ペイウォール、フローを削除または変更することもありません。

フェーズ 1 — 解決と確認

この移行には flows publish が必要です。ほかのスキルと同じ方法で $ADAPTY を一度だけ解決します。 latest を一度だけインストールして使い続け、グローバルプレフィックスに書き込めない場合に限り npx にフォールバックします。その後、コマンドが使えることを確認します。

npm i -g adapty@latest >/dev/null 2>&1 \
  && ADAPTY="adapty" \
  || ADAPTY="npx --yes adapty@latest"              # fallback: prefix not writable
$ADAPTY --version
$ADAPTY auth whoami

どこかで読んだバージョン番号を条件にしないでください。コマンドを確認してください。 $ADAPTY flows publish --help がコマンドの説明を表示するかどうかが、古くならない唯一の判定方法です。数値による下限は、すでに一度誤りになっています。flows publish は 0.8.3-beta.0 と 0.8.3-beta.1 で公開され、0.8.3 には含まれず、その後再び含まれました。latest のインストール後もコマンドが見つからない場合は、npx --yes adapty@beta を一度試してください。そこでも見つからなければ、その方法はまだリリースされていません。その旨を伝え、ユーザーには追いかけるべきバージョン番号ではなく、ダッシュボードでの手順を案内してください(フローへの移行)。

インストールは一度だけにし、呼び出しのたびに npx で囲まないでください。 ラッパーはインストール済みの場合の 0.07 秒に対し、呼び出しごとに約 1 秒かかります。このスキルではプレースメントごとに 1 回呼び出します。npx にフォールバックする場合は --yes が不可欠です。これがないと npx はインストールの許可を求めて停止し、ヘッドレス実行では応答できる人がいません。macOS のデフォルトである zsh では、複数の単語を含む $ADAPTY は単語分割されません。同じシェルで一度 setopt shwordsplit を実行してください。command not found: npx --yes adapty@latest はシェルの問題であり、CLI が見つからないという意味ではありません。auth whoami に失敗した場合は、$ADAPTY auth login でブラウザーが開きます。そこでの操作はユーザーが行います。その後、アプリ UUID を取得するために $ADAPTY apps list --json を実行します。

次に、対応できないインベントリ調査に 152 回の読み取りを費やす前に、機能を確認します。 読み取り専用の判定項目は content_type です。audience union がデプロイされている環境では、すべての audience にこの項目が返り、デプロイされていない環境では項目自体が省略されます。したがって確認には、audience を持つプレースメントが必要です。audiences 配列が空だと、デプロイとは無関係な理由で「なし」と判定されるため、任意のプレースメントを使っても判断できません。

$ADAPTY placements list --app "$APP" --page-size 20 --json    # summaries only — no audiences here
$ADAPTY placements get --app "$APP" <ID> --json               # repeat until one has a non-empty audiences[]

get は数回試したら止めてください。目的は網羅的な調査ではなく、存在を示す 1 つの事例を見つけることです。結果は 3 通りで、3 つ目は 2 つ目とは異なります。

  • content_type がある ⇒ union がデプロイされています。続行します。
  • 存在する audience に content_type がない ⇒ ないことが確認できました。停止してください。書き込みが audiences.0.paywall_id: Field required を返すこと、このデプロイ環境では移行がダッシュボードでの操作になることを伝えてください:https://app.adapty.io/placements。**これを左右するのはアカウントではなく 2 つの要素です。上記の CLI バージョンとAPI のデプロイ状況**であり、そのデプロイ環境ではどのアカウントでも同じです。アカウントを切り替えても解決しません。現在、これは想定される結果ではありません。 本番環境では audience union がモデル化されているため、ここに到達した場合は機能が存在しないのではなく、接続先のサーバーが古いことを意味します。
  • audience を持つプレースメントがない — 新しいアプリ、またはすべて空 ⇒ 判定できません。 停止しないでください。フェーズ 2 は読み取り専用で、常に安全に実行できます。実行したうえで、機能が未確認であることを伝え、最初の書き込みで確認します。(アプリにプレースメントがない場合は、移行対象がないため、この理由で作業を終了します。)

このシグナルは相関関係であって証明ではないため、同じ結果が後から判明することもあります。後の段階で停止しても保護されるのはプレースメントであり、フローではありません。 フェーズ 5 で flows publish が http_404 を返した場合、そのルートは未確認なので、どちらとも決めつけず実行してください。その時点で、paywall ごとに flows create はすでに 1 回実行されており、flows delete はありません。そのため、作成されたフローレコードは残り、ダッシュボードから削除する必要があります。さらに後のフェーズ 7 で audiences.0.paywall_id: Field required が発生すると、そのフローは作成済みで公開済みです。どちらの停止条件でも守られるのは、取り消しが一切できない事柄です。プレースメントは作成されず、paywall のプレースメントも変更されません。

フェーズ 2 — インベントリ

読み取り専用で、網羅的に実行します。--page-size のデフォルトは 20 で、レスポンスの meta.pagination {count, page, pages} だけがページ数を示すため、ページネーションのループは references/migrate.py が担当します。

python3 <skill>/references/migrate.py inventory --app "$APP" --adapty "$ADAPTY" \
  --scope active --out inventory.json          # or --scope all; see below

1 ページ目だけを読んではいけません。 --page-size のデフォルトは 20 です。1 回の呼び出しで得た行数を数えるだけでは、150 件のプレースメントがあるアプリを 130 件少なく見積もってしまいます。その出力は、小規模なアプリの場合とまったく同じに見えます。

ここ、および後続のすべてのフェーズにおける、migrate.py の終了コードの読み方です。2 は処理が成功せず、原因が内部の問題だと特定されていないことを意味します。メッセージには判明している内容が示されるので、よく読み、入力を確認してください。3 はスクリプト内部の不変条件が破れたことを意味し、入力はほぼ間違いなく問題ありません。ファイルを編集せず、処理を止めて報告してください。終了コード 2 でメッセージが曖昧なのは、スクリプトが推測を避けているためであり、バグではありません。

placements list が返すのは {developer_id, id, title} で、オーディエンスは含まれません。オーディエンスを返すのは placements get だけなので、N 件の分類には N 回の GET が必要です。一括読み取りはありません(api-surface.md)。

同じコマンドで flows list と paywalls list も読み取ります。アカウントがすでに持っているフローと、それらとの照合に使うペイウォールのタイトルです。オーディエンスが持つのは paywall_id であり、名前は持たないためです(api-surface.md)。アカウントの規模に関係なく、ページネーション付きの読み取りが 2 回必要ですが、すでに実行する N 回の GET に加わるだけです。--no-existing を指定するとこれらの読み取りを省略できます。読み取りに失敗した場合に限って指定し、その場合はゼロ件だったと報告するのではなく、質問への回答が得られなかったと伝えてください。

--scope active を指定し、予測ではなく返された内容を読み取ってください。 本番環境では placements list と placements get の両方に is_active が含まれるため(api-surface.md)、このフィルターは実際に機能します。通常は次のようになります。

30 placement(s) read -> inventory.json
scope=active: 30 kept, 120 filtered out (30 active, 118 inactive, 2 unknown) -- 2 placement(s)
carry no readable is_active and were withheld as unknown, not as inactive; offer --scope all

デプロイが遅れている場合もフォールバックします。フォールバックは結果であり、障害ではありません。

150 placement(s) read -> inventory.json
scope=active: 150 kept, 0 filtered out (0 active, 0 inactive, 150 unknown) -- fell back to all:
no placement carries is_active, so activity is unknown rather than inactive; --scope active was
ignored and every placement kept

is_active がないことは、false であることを意味しません。 その場合は、アカウントをフィルタリングできないと伝え、以下の規模のしきい値を使ってください。その場合、スコープを判断する唯一の手段がそれです。移行対象が空だと報告してはいけません。ツールは空の移行対象を返しませんし、あなたもそう報告すべきではありません。コマンドを実行せずに、どちらの結果も報告してはいけません。

その結果の両方の側面をユーザーに報告してください。 作業を隠すフィルターは、フィルターがないよりも問題です。is_active が文書化されているプレースメントのステータスより狭い範囲に絞り込まれると、除外された行は移行が必要なプレースメントであり、誰にも見えなくなります。plan は除外件数を summary.scope に再掲します。メッセージから省かないでください。

混在するケースには専用のフラグがあるため、見つけ出す必要はありません。 読み取り可能な is_active を持たない行は、active の対象から除外されますが、非アクティブな行とは別に数えられます。「無効にした」と「判別できなかった」は、ユーザーに伝えるべき内容が異なるためです。該当する行がある場合、スコープブロックには理由とともに unknown_withheld: true が設定されます。このフラグがある場合に、フェーズ 4 で対象範囲を広げる選択肢を提示します。

出力ではなく、--scope でフィルタリングしてください。 is_active は list の概要に含まれますが、オーディエンスはそれぞれ 1 回の get が必要です。そのため、GET のループより前にフィルタリングすることで、呼び出し回数を 2 + 150 から 2 + 30 に減らせます。後からフィルタリングしても、何も節約できません。 規模のゲート。 このフィールドが存在する場合は冗長ですが、存在しないデプロイでは唯一の選択肢です。これは配置を優先する列挙の範囲を指定します。より手軽に絞り込める方法としてpaywalls placementsを使わないでください。これは有効かつペイウォール型の行に絞り込まれるため、無効な配置はまったく確認できません(api-surface.md)。まずmeta.pagination.countを確認してください。配置数が25を超える場合は、配置ごとにgetを1回実行するコストを伝え、実行前に範囲を絞る方法を提案してください。たとえば、developer_idの部分一致、または配置の明示的なリストです。そうすれば「すべて」は、うっかり全件を処理するのではなく、コストを理解したうえでの選択になります。

まずは絞り込んでください。 あとから範囲を広げる場合は、最初からすべて読み直す必要があります。再開はできず、--outは上書きします。ただし、読み取りは低コストで冪等です。本当に重要なコストは、範囲を広げることで増えるフローの数です。ペイウォールが1つ増えるごとにフローが1つ追加され、そのフローは誰かが手作業で入力します。ユーザーにかかるコストはフェーズ4で説明します。

フェーズ 3 — 分類とグループ化

python3 <skill>/references/migrate.py plan --inventory inventory.json

オーディエンスごとに、paywall_id があれば移行可能、flow_id があれば対応済み、A/B テストのエントリがあればスキップ(下記)、どちらもなければスキップします。移行可能なオーディエンスは paywall_id ごとにグループ化します。このグループ化がそのままフロー計画になります。つまり、異なるペイウォールごとに再利用可能なフローを 1 つ作成し、それを使用するすべてのプレースメントとオーディエンスで共有します。これにより、ユーザーは後から複数のフローではなく 1 つのフローを調整できます。

プレースメントごとに 1 行ずつではなく、件数とグループ化を報告します。移行可能なプレースメント数、移行可能なオーディエンス数、異なるペイウォール数、フロー対応済み数、オーディエンスなしの数、スキップした A/B テスト数です。150 行のダンプはレポートではありません。 異なるペイウォール数は、次のフェーズで作成するフロー数として示します。

summary.unwritable_multi_segment が 0 でない場合は、ここでその数を報告し、該当するプレースメントをブロック対象として扱います。 segment_id を複数持つオーディエンスは、読み取り可能ですが書き込みできません。API は書き込み時にエントリごとに 1 つまでに制限しますが、読み取りパスでは意図的にその制限を回避し、レガシー行が往復処理で維持されるようにしています(api-surface.md)。そのようなプレースメントに対して plan は command を出力せず、その理由を示します。セグメントを分割または削除する提案はしないでください。それによって誰に何が表示されるかが変わるため、判断するのはユーザーです。まずダッシュボードで解決する必要があることと、それ以外の移行はそのまま進められることを伝えます。

A/B テストはスキップし、plan は各テストを明記します(skipped: A/B test <title>、stderr)。デフォルトのオーディエンスにテストがある場合、プレースメント全体をスキップします(skipped_placements:ID なし、command なし、フロー作成なし)。ソースのプレースメントは変更されません。スキップはそれぞれ 1 回報告します(api-surface.md)。A/B テストの読み取りに対応する前のデプロイでは、テストが通知なしに省略されます。その場合は、API が返した内容に基づく件数であることを伝え、テストを実施中だとわかっているプレースメントをダッシュボードで確認します。

summary.scope がある場合は、同じ行で報告します。インベントリから除外されたプレースメントの数と、その理由です。上記の件数が読み取った内容を示すのに対し、このブロックだけが読み取られなかった内容を示します。

summary.exposure は 3 つ目のブロックで、どちらとも重複しません。この計画で作成するプレースメント間のアクティビティの内訳です。ここでは出力しません。使用するのはフェーズ 6 であり、その数値が意味を持つのはそこだけです。

summary.existing は 4 つ目で、フェーズ 4 の質問内容を変えるため、こちらは報告します。 flows_needed の各行には、ペイウォールの paywall_title と、アカウント内に名前が一致するフローがある場合は existing_flow_candidates が含まれます。各候補には status と next_step があり、attach(すでに published)または publish_then_attach(変換済みのフローが draft のまま)を示します。異なるペイウォールのうち、すでに候補があるものの数を伝え、それらの名前を挙げます。

候補はすべて提案であり、ユーザーがそれぞれを確認します。 照合は名前だけで行われます。API には、どのペイウォールからフローが作成されたかを記録する仕組みがないため、一致は偶然の可能性があります。また、名前が変更されたフローは照合対象が残らないため、一致しないからといって、変換済みのものがない証拠にはなりません。フロー一覧を手元に用意して、確認を依頼します — https://app.adapty.io/flows.

フェーズ 4 — 2 つの質問

両方を 1 つのメッセージで尋ねてから、停止します。2 つ目はペイウォールごとの質問なので、行ごとに推奨ルートを示した 1 つの表にします。ペイウォールごとにメッセージを分けたり、アカウント全体で 1 つのルートにしたりしないでください。

1. 対象範囲。 is_active がある場合は、列挙して選択解除する方式ではなく、実質的に 3 つの選択肢があります。

選択肢フィルター適切なケース
アクティブのみis_active: trueデフォルトの提案。実際に配信中のものを移行します
すべてフィルターなし無効なプレースメントも含め、アカウント全体を移行します
指定したリストユーザーが指定する ID段階的な移行、または特定のアプリ領域
選択肢が異なる場合にのみ尋ねてください。 アカウント内のすべての配置が有効な場合(または
--scope active の対象がすべての場合と同じ場合)、回答によって何も変わりません。「 件
すべて有効なので、すべてを使います」と一行で伝え、質問 2 だけを尋ねてください。

有効なものだけを提案し、その理由を伝えてください: 無効な配置は誰にも利用されないため、 そのためのフローを作っても、結果を目にするユーザーはいません。また、対象範囲に異なるペイウォールが 1 つ増えるたびに、誰かが手作業で入力するフローも 1 つ増えます。範囲を広げると何が必要になるかを 伝え、無料とは言わないでください: --scope all で再実行すると、アカウント全体を再度読み取ります。 再開機能はなく、--out は上書きするためです。

範囲のブロックに unknown_withheld: true がある場合は、件数を添えて、範囲を広げる選択肢を 明示的にユーザーに提示してください。 これらの行が除外されたのは、ステータスを読み取れなかったためであり、 誰かが無効にしたためではありません。ユーザーが最も除外を取り消したいと考えそうなのは、このケースです。

フェーズ 2 ではすでに --scope active が適用されていますが、それはユーザーに代わって 決定したということではありません — フィールドが存在するか、無効な配置がいくつあるかは、読み取りで 確認して初めてわかるため、その前に選択を提示しても意味がありません。まだ何も書き込まれていません。 ここでユーザーに範囲を広げてもらいます。

フィールドが存在せず、インベントリの取得がフォールバックした場合は、アカウントを絞り込めないと伝え、 代わりに最初の質問をしてください。表示された件数のすべてにするか、指定した一部だけにするかを尋ねます。 これはフェーズ 2 の規模に関する確認への回答であり、同じことを二度尋ねるものではありません。

2. フローのコンテンツの入手元 — 異なるペイウォールごとに尋ねます。フェーズ 3 で、 フローがすでにあるものとないものがあるとわかったため、アカウント全体に対して一度ではなく、 ペイウォールごとに尋ねてください。 提示する順序は次の 4 つです。

  • 再利用 — existing_flow_candidates にある、またはユーザーが名前を挙げた、アカウント内の 既存フローです。候補がある場合は、まずこれを提示してください。 費用がかからず、 再利用するフローがユーザー自身の成果物である唯一の方法だからです。ユーザーにフローを確認し、 flows get を再実行して、そのステータスに応じて対応します。published ならそのまま紐付けます。 それ以外は先に公開します(フェーズ 5 の公開部分のみを行い、flows create 部分は行いません)。
  • 変換 — ペイウォール自身の概要ページにあるダッシュボードの Move to new builder を使うと、 ワンクリックで下書きフローとして再作成されます。レイアウト、すべてのロケールのコピー、 価格を変数として扱う商品が引き継がれます (ドキュメント)。従来の Paywall Builder で作成されたペイウォールには、これを既定の方法として使ってください — Adapty 自身も再構築ではなく 変換を推奨しています。操作するのはユーザーであり、あなたではありません。この操作を行う CLI コマンドは ありません。ユーザーに案内し、完了を待ってから inventory を再実行し、表示されたフローに対して 再利用の方法を取ります。ビルダーには、引き継がれなかった項目が一覧表示された Import review パネルが開きます。そのパネルの項目を確認するのもユーザー自身です。
  • 作成 — ペイウォールを flow-generator に渡し、読み取らせてフローを設計します。変換では対応できない ペイウォールや、既存の画面を再現するのではなく作り直したい場合に使います。
  • スタブ — references/stub-flow.json から、公開可能な最小限のフローを 1 つ作成します。 最終手段であり、近道ではありません。 ユーザーにはプレースホルダーが表示されるため、フェーズ 6 で 同意が必要な説明が含まれます。上記 3 つの方法が使えないと確認できた場合、または配置から誰も利用して いない場合にのみ提示してください。

変換が利用可能かどうかは、CLI では確認できません。 paywalls list/get が返すのは {id, title, product_ids} のみで、どのビルダーで作成されたかはわかりません。また、 Move to new builder が表示されるのは、従来の Paywall Builder で作成されたペイウォールに限られます。 そのため、ボタンがあるとユーザーに伝えてはいけません。ボタンの機能と場所を案内し、ユーザーからの報告を 待ってください。 選択をユーザーの代わりにしてはいけません。この4つではユーザーに表示される内容が異なり、どれを選ぶかはエージェントが決めることではありません。**してはいけないのは、すでにフローがあるペイウォールに対して、ユーザーが再利用を拒否していないのにフローを作成することです。**これはアカウントに重複して残ります。flows delete がないためです。

is_active がある場合のリハーサル順序

これはフェーズではなく、大規模な移行または初回の移行時に一度だけ提案する方法です。まず非アクティブなプレースメントから実施します。これなら create → publish → attach の一連の処理を実際に最後まで実行しながら、公開状態でユーザーに表示することはありません。その結果として残るのはシミュレーションではなく、実際のアカウント状態です。このスキルが依存する唯一の呼び出しである、公開済みフローを使った placements create は未検証であるため、初めて実行するなら、ミスをしても購入に影響しない場所で試すのが望ましいです。その後、動作が問題ないと確認できた手順でアクティブなものを繰り返します。

正直に伝えるべきコストがあり、納得できるものです。2回に分けて実施することになり、作成したプレースメントはどちらの場合もすべて削除できません。提案はしますが、押しつけてはいけません。このフィールドがなければ、順序を決める材料がないため、どのプレースメントが安全か推測して進めるのではなく、この手順は完全に省略してください。

**前提となるのは、is_active: false は誰にも配信されていないことを意味し、このフィールドが表すのはAPI のソースに記載されたアクティベーション状態だけだということです。**つまり、Live と Inactive、ダッシュボード上のトグルそのものであり、それ以上の細かな情報は示しません。このフィールドからは、リリース済みのアプリが今もそのプレースメントを呼び出しているかどうかはわかりません。非アクティブなプレースメントではペイウォールが配信されないため、そこにスタブを置いてもプレースホルダーがユーザーに表示されることはありません。ただし、ユーザーがあるプレースメントはライブトラフィックだと言う場合は、フラグよりもユーザーの話を優先し、アクティブとして扱ってください。

フェーズ 5 — フローを作成する

ユーザーが再利用を選んだペイウォールでは、このフェーズの大半をスキップします。 作成するものも書き込むものもありません。flows get を再実行し、ステータスが published なら台帳に記録して先に進みます。そうでなければ、以下の公開とポーリングだけを実行します。それ以外は何もしません。flows create と config update は、まだフローがないペイウォール用です。再利用したフローに対して実行すると、この手順全体で避けようとしている重複が発生します。また、それを取り消す flows delete はありません。ユーザーが 新しいビルダーに移動 をクリックするのを待っているペイウォールは、このフェーズに進める状態ではありません。台帳には含めず、次回の inventory で処理します。

新しいフローが必要な、重複を除いた各ペイウォールについて、次の順序で実行します。この順序は計測によって決まっています。ドラフトフローを指定したプレースメントは拒否されます。Cannot attach a draft flow to a placement — publish it first.、終了コード 2 になります。古い CLI ではバックエンド独自の Flow must be published before placing in a placement. が表示されます。また、公開は非同期です。

$ADAPTY flows create --app "$APP" --name "<paywall title> (flow)" --json      # row only; draft
$ADAPTY flows config validate <FLOW> --app "$APP" \
  --config-file <skill>/references/stub-flow.json --json                      # expect valid: true
$ADAPTY flows config update <FLOW> --app "$APP" \
  --config-file <skill>/references/stub-flow.json --json                      # or flow-generator's config
$ADAPTY flows publish --app "$APP" <FLOW> --yes
$ADAPTY flows get <FLOW> --app "$APP" --json                                  # poll until published

検証は書き込む前にローカルファイルに対して実行し、書き込んだ後には決して実行しません。書き込み後では、すでに保存されたバイト列を検証することになります。create が先なのは、検証でフロー ID を解決する必要があるためです。

flows publish が報告するのは status: publishing で、published になることはありません。そのため、ステータスが published になるまで flows get をポーリングし、公開の応答をもとにフローが有効になったと報告してはいけません。このコマンドはポーリング自体を出力し、その診断呼び出しも併せて出力します。publication_failed の場合、flows config get には publication_status、transform_error、publication_error が含まれます。transform_error は変換サービスからの異議をそのまま示すものです。原因を作り上げず、表示されたとおりに引用してください。--yes をここで渡すのは、フェーズ 4 でユーザーがフローの内容を選択し、プレースメントはフェーズ 6 まで引き続き保留されるためです。これを指定しないと、非 TTY 実行では終了コード 2 で拒否され、処理が停止します。公開の契約、400 の処理、失敗の診断は flow-generator が担当します。再導出せず、そちらに委ねてください。

flows publish で http_404 が返された場合、ここで実行を停止します。これは正常な停止ではありません。 このデプロイ環境ではそのルートが有効になっていないため、アカウントを切り替えても解決しません。しかし、このペイウォールに対して flows create はすでに実行済みであり、flows delete もありません。そのため、フローの行がいくつ存在するか、またダッシュボードから削除する必要があることを伝えてください — https://app.adapty.io/flows。公開で 404 が返された後は、残りのペイウォールに対するフロー作成を続けないでください。

配布されている references/stub-flow.json をそのまま使用し、より小さいものを手書きしないでください。 実際の変換サービスから valid: true が返ることが確認されています。それより小さい場合、失敗時にはフィールド名が示されない、場所情報のない Generated JSON failed schema validation になります(api-surface.md)。

フローファイル — flows.json

作業を進めながら書き込む唯一のファイルで、フェーズ 5 が使う台帳はこれだけです。 paywall_id → flow_id の対応を格納した JSON オブジェクトで、各フローが published に達するたびに全体を書き直します。最後に一度だけ書き込むのではありません。

{
  "9f3c1a20-...": "6b41e0d7-...",
  "c07e4b19-...": "a2d55f81-..."
}

逐次記録することが重要です。 flows create は重複を排除せず、flows delete もありません。そのため、フロー 3 とフロー 4 の間で実行が止まった場合、3 件を記録しておく必要があります。そうしないと再実行時にペイウォールごとに 2 つ目のフローが作成され、重複した行はすべて誰かが手動で削除することになります。published を確認した flows get の後に書き込んでください。ファイルの各行は、実際にアタッチできるフローを表します。

再利用または変換したフローも、ほかのエントリと同じです。 台帳に記録するのはフェーズ 7 でアタッチする内容であり、このフェーズで作成した内容ではありません。そのため、ユーザーがすでに所有しているフローも、flows get で published と確認できたら同じファイルに記録します。そうすれば、フェーズ 7 では両者を見分けられません。それが目的です。

再開時は、まず台帳を読み込みます。 すでに記録されている各 paywall_id について、flows get <FLOW> --app "$APP" で記録済みのフローが引き続き published と表示されることを確認し、そのペイウォールをスキップします。記録された ID のステータスが published 以外なら、スキップしてはいけません。公開を完了し、2 つ目のフローは作成しないでください。

作業ディレクトリ内の inventory.json と同じ場所に flows.json を置き、進捗を表示するときにパスを明記してください。これを読み込むのはフェーズ 7 だけです。それ以外では使いません。

フェーズ 6 — 承認ゲート

次のブロックを値で埋めて表示し、明示的な承認を待ちます。承認が得られるまでは、どのコマンドにも --yes を付けません。解決済みの値をそのまま提示してください。ID が永続的であるという説明文で済ませてはいけません。この承認で許可されるのは placements create のみです。それ以外は許可されません。 フェーズ 5 はすでに実行済みなので、このブロックでは、何も実行されていないと主張するのではなく、まだ実行されていないことを示します。

プレースメントはまだ作成されていません。この承認によって作成が許可されます。

アプリ: 作成するプレースメント: — 取り消せない操作 フロー:<flow line — 以下のパスに応じて選択>

既存のペイウォールプレースメント新しいフロープレースメントフロー
<developer_id><proposed developer_id>

<テストが plan によってスキップされた場合のみ、テストに関する行>

永続的です。プレースメントは削除できません。ここにある ID が間違っていた場合、 アプリに永久に残ります。承認する前に中央の列を確認してください。

ペイウォールプレースメントには手を加えません。アプリが以下の変更をリリースするまで、 そのまま配信されます。これがロールバックの方法でもあります。何もリリースしません。

<取り消しに関する行 — 以下のスタブとビルドのパスのみ>

まだユーザーには配信されません。アプリで、現在古いプレースメントを呼び出している箇所を getFlow("<new developer_id>") に変更する必要があります。

テストに関する行は、文言の引用ではなく内容を示すものです(テスト全体をスキップした場合も記載してください)。 「除外:A/B テスト <title>(<developer_id> 上)。古いプレースメントで引き続き実行されます。」

この 2 つの欄を切り替える基準は 1 つです。この実行でフローを作成したかどうかです。 スタブとビルドでは作成しましたが、既存では作成していません。ユーザーから渡されたのは、すでに公開済みのフローです。そのため、この実行では何も作成しておらず、「現在アカウントにあり、ダッシュボードでのみ削除可能」という記載は、この実行が行ったことについて誤った主張になります。

  • スタブまたはビルド。 フローの行と、取り消しに関する行は次のとおりです。文言をそのまま使用してください。

    件。すでに作成・公開済みで、ペイウォールごとに 1 件です。現在アカウントにあり、削除できるのはダッシュボードからのみです

    承認しなくても、フローは取り消されません。この 件はいずれの場合も残ります。フローを削除する CLI コマンドはありません。

  • 既存。 フローの行は次のとおりです。取り消しに関する欄は完全に省略します。承認しない場合、アカウントの状態はこの実行の開始時とまったく同じであり、警告することはありません。

    件。選択したフローが公開済みです。この実行では何も作成していません

ブロック内のその他の項目はすべて、どのパスでも表示します。永続性に関する行、プレースメントに手を加えないことを示す行、呼び出し先の変更を示す行は、3 つのパスすべてに当てはまります。 提案される ID は migrate.py の propose_developer_id から取得します(<original-id>-flow)。衝突は恒久的なため、インベントリ内のすべての developer_id と事前に照合済みです。完全な一覧を表示してください。件数に省略しないでください。

フローの内容がスタブの場合は、これも表示して、別途確認の同意を得てください。 スタブを使った配置は、移行の縮小版ではなく、ユーザーにとって別の結果になります。

is_active が判明している場合は、無条件の承認を求めるのではなく、対象となる件数を明示してください。 これは注意書きより機械的なガードを優先する、このリポジトリの方針です。非アクティブな配置には、そうしたコストは一切ありません。

件数は summary.scope ではなく、必ず summary.exposure から読み取ってください。 scope は list が返したアカウント全体を区分します。一方、exposure は、すでにフローになっているものと空の配置をすべて除外したうえで、この計画によって実際に作成される配置を区分します。ここで scope を引用すると、現在の実際の影響範囲を過大に見積もることになります。それこそ、このブロックが防ごうとしていることです。

このブロックには 2 つの形式があり、表示するのは必ずどちらか一方だけです。 どちらを使うかは exposure.status_readable だけで決まり、ほかの項目は関係ありません。

exposure.status_readable表示する内容スロット
true件数ありの形式(直下のブロック)<n> = exposure.placements、<a> = exposure.active
false件数なしの形式(この後にある 2 つ目のブロック)なし。文面はそのまま使用します

件数ありの形式:

スタブは 1 行のプレースホルダー画面です。 アプリからスタブを参照するようになると、そこにあるものがそのままコンテンツに なります。ユーザーにはプレースホルダーが表示され、購入できなくなります。

移行する 件の配置のうち、 件はアクティブです。これらにスタブを紐付けると、 アプリをリリースして呼び出しを変更した時点で、その箇所で購入できなくなり、 フローを完成させるまでこの状態が続きます。残りの <n-a> 件は非アクティブで 誰にも提供されないため、スタブを使っても影響はありません。

呼び出しの変更をリリースする前にフローを完成させる場合に限り、同意してください。 そうでなければ、いいえと答えてください。フローはすでに公開済みのため、ここで 停止し、配置は作成しません。フローを完成させて再度公開した後、準備ができたら いつでも同意してください。配置を紐付けられるのは、公開済みのフローだけです。

件数なしの形式。 exposure.status_readable が false の場合、件数を提示できず、確認の同意だけを得ます。推測した数値に置き換えず、次の文面をそのまま使用してください。

スタブは 1 行のプレースホルダー画面です。 アプリからスタブを参照するようになると、そこにあるものがそのままコンテンツに なります。ユーザーにはプレースホルダーが表示され、購入できなくなります。まだ何も呼び出さない配置なら問題ありませんが、 これからリリースする配置では売上が止まります。 呼び出しの変更をリリースする前にフローを完成させる場合に限り、同意してください。 そうでなければ、いいえと答えてください。フローはすでに公開済みのため、ここで 停止し、配置は作成しません。フローを完成させて再度公開した後、準備ができたら いつでも同意してください。配置を紐付けられるのは、公開済みのフローだけです。

どちらの形式でも「再度公開する」という文言を残してください。 スタブを完成させるとフローは dirty になります。dirty なフローを紐付けられるかどうかは未確認のため (api-surface.md)、紐付け可能として扱うステータスは published のみです。戻ってきた際は、編集によって状態が変わっていないと決めつけず、フェーズ 5 と同じ方法で flows get を再実行してください。

フェーズ 7 — 作成

placements create を手入力で実行しないでください。台帳を指定して plan を再実行し、出力されたコマンドを実行してください。

python3 <skill>/references/migrate.py plan --inventory inventory.json --flows flows.json > plan.json

--flows を指定すると、各配置行に command が追加されます。これは、to_flow_audience で作成したオーディエンスを使って build_create_command が組み立てた、正確な argv です。1 行ずつ出力し、変更せずに実行してください。

python3 - plan.json <<'PY'
import json, shlex, sys
for row in json.load(open(sys.argv[1]))['placements']:
    cmd = row.get('command')
    print(shlex.join(cmd) if cmd else '# NO COMMAND: ' + row['command_unavailable'])
PY
# then, one at a time, prefixed with $ADAPTY:
$ADAPTY placements create --app ... --title ... --developer-id ... --audiences '[...]'

**argv を生成することで、ガードを単に利用可能なものではなく、必ず適用されるものにできます。**このスキルで唯一の取り消せないコマンドであるため、入力し直すことを信頼できる最後の箇所です。

  • 新しい配置のすべてのオーディエンスは flow です。 paywall と flow が混在するオーディエンスはバックエンドで 400 エラーになり、build_create_command はコマンドを出力せず、例外を発生させます。これは paywall のみの配列にも適用される、同じ不変条件のもう一方からの確認です。手書きの argv には、このような拒否機能はありません。
  • **すべてのエントリーで content_type が必須です。**これが欠けていると、CLI はリクエストを送信せずに終了コード 2 で終了します。union 前の環境から読み込むと、この項目は省略されます。この経路では毎回、normalize_audience が挿入します。
  • **segment_ids と priority は to_flow_audience によってそのまま引き継がれます。**これらはターゲティングを指定するもので、変更すると、何が誰に表示されるかが気付かないうちに変わります。入力し直すと、数字が抜け落ちることがあります。
  • **paywall が flows.json にない行には、command がなく、missing_flows と理由だけが含まれます。**手作業で補わないでください。その paywall についてフェーズ 5 を完了し、plan を再実行してください。
  • **multi_segment_audiences を含む行にも command はありません。この問題は再実行では解決できません。**そのオーディエンスには複数の segment_id が含まれています。API は読み取り時にこれらを返しますが、書き込み時には拒否します。その配置名を明記して報告し、ユーザーに委ねてください。無理に書き込もうとしても失敗し、セグメントを編集するとターゲティングが変わります。
  • placements create にはプロンプトもプレビューもありません。flows publish とは異なり、送信した argv はそのまま送信されます。ここでの唯一のゲートはフェーズ 6 です(api-surface.md)。
配置台帳 — placements.json

**これが 2 つ目で最後の台帳です。flows.json と同じ規律で扱います。**新しい developer_id から、作成された配置の id への対応を記録する JSON オブジェクトです。作成に成功するたびに書き換えてください。

{ "main-flow": "1f8e...", "onboarding-flow": "77b2..." }

**成功したものだけを記録します。**キーがある配置はすでに存在するため、二度と作成してはいけません。すでに使用されている ID で再度作成しようとすると、拒否されるか、最悪の場合は永久に残る 2 つ目の行が作成されます。したがって再開時には placements.json を読み込み、developer_id がすでにキーになっている承認済みエントリーはすべてスキップしてください。**失敗は意図的に記録しません。**失敗したエントリーは再試行が必要であり、台帳に記録してスキップすると、気付かれないまま取り残されてしまいます。

**1 件の失敗で実行全体を中断しないでください。**自分の作業メモに記録して次のエントリーに進み、フェーズ 8 ですべての失敗とその理由を報告してください。

**ただし、audiences.0.paywall_id: Field required エラーだけは例外で、すべてを中断します。**これはこのエントリーの問題ではありません。フェーズ 1 のプローブの回答が遅れて届いたということであり、残りのすべてのエントリーも同じように失敗します。停止し、状況を正確に報告してください。配置は作成されておらず、フェーズ 5 で作成したフローは存在していて公開済みですが、CLI から削除することはできません。

フェーズ 8 — レポートと引き継ぎ

作成したもの、スキップしたもの(A/B テストを含む)、理由とともに失敗したものを示し、続けてユーザーが対応する 2 つの項目を記載します。

以前のペイウォール配置新しいフロー配置
<old developer_id><new developer_id>

呼び出しの変更についても記載します。アプリは現在、古い配置を取得している箇所で getFlow("<new developer_id>") を使い、新しい配置を取得します。フローのレンダリングには Adapty SDK v4.0 以降が必要です。そのため、古い SDK を使っている場合は、まずバージョンをアップグレードしてから呼び出しを変更します。getFlow はフローとペイウォールの配置の両方を読み取るため、どちらの場合もアプリが呼び出すメソッドは 1 つです(フローへの移行)。実装は、プラットフォームごとの呼び出し箇所とレンダリングを担当する adapty-integration に引き継ぎます。ここではアプリコードを書かないでください。

古い配置は引き続き有効であることと、その理由を伝えます。 すでにリリース済みのバージョンを使うユーザーには、古い配置 ID がコンパイル済みで含まれており、アプリを更新するまでフローにアクセスできません。そのため、ペイウォール配置を早期に廃止すると、更新していないユーザー全員がペイウォールを利用できなくなります。両方の配置を並行して稼働させ、それぞれの指標で個別に測定し、v4 の採用率が十分に高くなるまで継続します。

ロールバック用ファイルは不要です。その理由も伝えてください。 既存のものは何も変更していないため、手を加えていないペイウォール配置がロールバック手段になります。 アプリで呼び出しの変更がリリースされるまでは、ユーザーは古いペイウォールを利用します。新しいフローに問題があれば、何もリリースせず、ビルダーでフローを修正してください — https://adapty.io/docs/adapty-flow-builder.md。**ロールバックの対象は配置であり、フローではありません。** 実行で作成したフローはすべてCLI から削除できないため、その数と削除する場所(https://app.adapty.io/flows)を明記します。

最後に、未解決の項目を一度だけ挙げます。スタブが残っているフロー、失敗した配置、クリーンアップが必要なフローです。これらはアプリがリリースされるまで誰にも表示されないことは、このレポート全体で一度だけ、上記の呼び出し変更と合わせて伝えます。ここでは繰り返しません。

出力内容

ユーザーが読むのはあなたのメッセージであり、このファイルではありません。短くまとめてください。

固定するのは 2 つのブロックだけです。それ以外は固定しません。 フェーズ 6 では、承認ゲートと、内容がスタブの場合のスタブ確認の両方を出力します。各ブロックの所定の箇所を埋め、それ以上の文言を加えないでください。フェーズ 8 の旧→新テーブルは、ゲートの中央列と同じデータです。そのため、テーブルを出力し、同じ内容を文章で繰り返さないでください。

それ以外はすべて 1 行にするか、省略します。 フェーズ 3 の件数は 1 行で示します。フェーズ 4 の質問は 1 つのメッセージにまとめ、一度だけ尋ねます。フェーズ 5 のフローごとの進捗は、逐次報告せず件数で示します。機能上の制約による停止は、1 行とダッシュボードへのリンクだけにします。各項目は一度だけ伝えてください。 ゲートですでに永続性について説明している場合、フェーズ 8 で繰り返しません。

コメント