add-table-to-offline-profile について
add-table-to-offline-profile は、既存の Mobile Offline Profile にテーブルを 1 つ追加するスキルです。`/add-dataverse` でアプリに追加したテーブルのオフライン設定などに使い、前提条件、同期範囲、リレーションと列を設定して変更を公開します。
主な機能
- 01既存のオフラインプロファイルにテーブルを追加
- 02承認範囲内でオフラインフラグを確認・有効化
- 03レコード範囲、リレーション、選択列を設定
- 04プロファイル変更を公開
活用シーン
- 01アプリに追加した Dataverse テーブルをオフライン対応
- 02テーブルのオフライン対象範囲と同期列を設定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ microsoft/ power-platform-skills --skill add-table-to-offline-profileインストール後、Claude Code で/add-table-to-offline-profileを入力して読み込まれたか確認します。
詳細
共有手順:shared-instructions.md — 最初に読んでください。
参考資料:
- dataverse-offline-api.md §4 + §7 — POST item + PATCH selectedcolumns
- offline-profile-reconciliation.md — Step 7 で記述する
schemaColumnsのベースライン
オフラインプロファイルにテーブルを追加する
既存のモバイルオフラインプロファイルに、テーブルを1つ追加します。最も一般的なケースは、ユーザーが /add-dataverse を実行してアプリに新しいテーブルを追加し、そのテーブルもオフラインで利用できるようにしたい場合です。
/setup-offline-profile を実行して既存のプロファイルを確認する場合(拡張モード)と同等ですが、すでにプロファイルに含まれているテーブルへのテーブルごとの質問は省略し、新しいテーブルのみを設定します。
呼び出しコンテキストとルートのバインド
この内部ヘルパーは常に子呼び出しです。プロジェクトの読み取りやコマンドの実行に先立ち、app-working-directory.md を実行してください。
所有者の絶対パス working_dir を必須とし、--working-dir が指定されている場合は、同じネイティブな正規ルートを示している必要があります。ルートコンテキストが存在しない、相対パスである、または矛盾している場合は、プロジェクトにアクセスする前に NEEDS_CONTEXT を返します。ディレクトリにアクセスできない場合は BLOCKED を返します。起動時の cwd を使用したり、別のルート解決手段を導入したりしないでください。
所有者から MOBILE_APP_ORCHESTRATING=1、orchestrator、working_dir、phase、approved_scope、および指定された回答を受け取ります。選択された環境 ID/URL/テナント、プロファイル ID、テーブルの正確な許可リスト、承認済み操作を変更せずに維持してください。スコープが存在しない、または矛盾している場合は NEEDS_CONTEXT を返し、別の環境、プロファイル、テーブルで代用しないでください。このマーカーは承認を意味しません。スキーマの承認だけでは、オフラインの前提条件の変更、プロファイル項目、リレーションシップ、列、公開、削除、メンバーシップの編集は許可されません。同じ現在のオフライン承認だけを再利用し、それ以外の場合は Step 3–4 の承認ゲートを維持してください。
--plan-only または計画フェーズからの引き継ぎでは、許可された読み取り専用の調査後、前提条件の更新、プロファイルの変更、公開、成果物の書き込みを行う前に、提案を返してください。ゲート応答によって提案のみのモードを解除することはできません。
バージョン/認証の確認、行数の取得、画面の grep、参考資料からコピーしたコマンド、検証、復旧、再試行を含むすべてのシェル呼び出しとファイルツールに、正規の契約を適用してください。各呼び出しで絶対パスの PLUGIN_ROOT と必須コンテキストを再指定してください。以前の cd、export、シェル変数は引き継がれません。以下の正規 Bash ガードまたはその PowerShell 相当を使用してください。プレースホルダーは検証済みでシェルクォートされた呼び出し値に置き換えてください。値が不足している場合は、未解決のプレースホルダーを実行せず、NEEDS_CONTEXT を返してください。
ファイルツールでは絶対パスの <working_dir>/... パスを使用し、プラグインの読み取り/スクリプトでは ${PLUGIN_ROOT}/... を使用してください。マニフェストの場所はこのルート内で一度だけ解決し、その絶対パスを維持してください。ファイルが見つからない場合に、別のアプリを検索してはいけません。
ワークフロー
- プロジェクトを確認してプロファイルを特定 → 2. 対象テーブルを解決 → 3. 前提条件を確認(必要に応じて自動で有効化) → 4. スコープ選択(質問は1つ) → 5. POST item + PATCH selectedcolumns → 6. 公開 → 7. 成果物を更新 → 8. 要約
Step 1 — プロジェクトを確認し、プロファイルを特定する
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
if [ ! -f power.config.json ] || [ ! -f app.config.js ]; then
echo "BLOCKED: working_dir is not an initialized app" >&2
exit 1
fi
<working_dir>/power.config.json を読み込み、空でない environmentId がオーナーの選択した環境と一致することを必須とします。一致する完全な環境コンテキストを再利用するか、バインドされたルートから永続化を行わない次の検索を実行します。
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
environment_id=$(node -p "require('./power.config.json').environmentId || ''") || {
echo "BLOCKED: unreadable power.config.json" >&2; exit 1;
}
if [ -z "$environment_id" ] || [ "$environment_id" != "<selected-environment-id>" ]; then
echo "NEEDS_CONTEXT: selected environment does not match working_dir" >&2
exit 1
fi
node "${PLUGIN_ROOT}/scripts/resolve-environment.js" "$environment_id" --no-cache --require-tenant
返された環境 ID、HTTPS URL、テナントがオーナーの情報と一致することを必須とします。コンテキストが不完全または矛盾している場合は、NEEDS_CONTEXT を返します。--no-cache --require-tenant を削除したり、設定を変更したり、失敗から復旧するために認証または環境を切り替えたりしてはいけません。すべてのリクエストで、同一の <envUrl> と <tenantId> を使用してください。
マニフェストのパス(2 か所にあり、どちらもバインドされたルート内にあります):
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
if [ -f .datamodel-manifest.json ]; then
printf '%s\n' '<working_dir>/.datamodel-manifest.json'
elif [ -f docs/plan-artifacts/.datamodel-manifest.json ]; then
printf '%s\n' '<working_dir>/docs/plan-artifacts/.datamodel-manifest.json'
else
echo "NEEDS_CONTEXT: no manifest in working_dir" >&2
exit 1
fi
<working_dir>/offline-profile.json、またはオーナーが明示した --profile-id からプロファイルを読み込み、/edit-offline-profile の解決ルールをここで適用します。オーナーのプロファイル、および選択した環境にあるライブプロファイルと一致することを確認してください。スナップショットと引数の不一致、404、または権限エラーを理由に、別のプロファイルを選択してはいけません。不一致の場合は NEEDS_CONTEXT、アクセスエラーの場合は BLOCKED を返します。プロファイルを特定するためだけに、編集ワークフローを実行してはいけません。
プロファイルが存在しない場合は、STOP してください。次を推奨します:Run /setup-offline-profile first to create the profile.
Step 2 — 対象テーブルを解決する
$ARGUMENTS の解析:
| フラグ | 効果 |
|---|---|
--table <logical-name> | 対象を明示的に指定 |
--all-new | オーナーが許可したテーブルの正確な許可リスト内で、マニフェストにある未登録のテーブルのみを追加(一括モード)。無関係な保留中のテーブルまで対象を広げてはいけません |
| (フラグなし) | 対話形式:選択したプロファイルにまだない、マニフェスト内の許可テーブルを AskUserQuestion で一覧表示します(最大 4 件)。候補が 4 件を超える場合は、次のメッセージで名前を指定するようユーザーに求めます |
明示的な --table はオーナーの許可リストに含まれている必要があります。選択が矛盾している場合は NEEDS_CONTEXT を返します。一括 --all-new モードでは、承認された未登録のテーブルごとに Step 3~7 を繰り返します。Dataverse のプロファイル項目への POST は直列化されるため、各テーブルは順番に処理します。すべての反復で、同じルート、環境、プロファイルのコンテキストを維持します。
Step 3 — 前提条件を確認する(必要に応じて自動で有効化)
対象テーブルの EntityMetadata を照会します。
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" "<envUrl>" GET \
"EntityDefinitions(LogicalName='<table>')?\$select=IsAvailableOffline,ChangeTrackingEnabled,OwnershipType,IsCustomizable" \
--tenant-id "<tenantId>"
| フラグの状態 | アクション |
|---|---|
両方がすでに true | Step 4 に進みます |
いずれかが false かつ IsCustomizable | 現在のオフライン承認が、これらの正確な前提条件の変更と公開を対象としている場合に限り、update-entity-offline-flags.js を使用します。それ以外の場合は、変更内容を提示し、先にオーナーまたはユーザーの承認を得ます |
IsCustomizable.Value = false | BLOCKED: <table> is system-managed and cannot be flagged for offline. Use a different table or accept this row is read-only-offline. を返して STOP します。 |
承認後、解決済みのテナントを指定して、このテーブルに限りヘルパーを実行します。
--tenant-id は最初の認証と 401 時の再認証の両方を固定します。この前提条件の変更に、暗黙的に設定されたテナント変数を使用してはいけません。
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node "${PLUGIN_ROOT}/scripts/update-entity-offline-flags.js" "<envUrl>" \
--table "<table>" --offline true --tracking true --tenant-id "<tenantId>"
プロセスの終了コードだけでなく、返された HTTP ステータスを確認してください。403 または PrivilegeCheckFailed は BLOCKED とします。権限を回避するために、ID またはプロファイルを変更してはいけません。
変更に成功したら、承認済みの前提条件の公開を実行します。
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" "<envUrl>" POST \
"PublishXml" --tenant-id "<tenantId>" --body '{
"ParameterXml": "<importexportxml><entities><entity><table></entity></entities></importexportxml>"
}'
このヘルパーが前提条件を変更したテーブルのみを公開します。オーナーが環境内のすべての保留中のカスタマイズを公開することを別途承認しない限り、PublishAllXml を使用してはいけません。テーブルのみの承認では、その代替手段は許可されていません。
提案のみのモードでは、前提条件の変更も公開も許可されません。
Step 4 — スコープ選択(質問は1つ)
バインドされた絶対パスのマニフェストから、対象テーブルの lookups[] を取得し、デフォルト値の判断に利用します。選択した環境で、このテーブルのみを対象としたルートバインドの行数プローブを実行し、参照データの分類に利用します。
アーキテクトの優先順位カスケードを反映した4つの選択肢で AskUserQuestion を実行します。
| 選択肢ラベル | 対応する値 |
|---|---|
Organization rows — User's rows only (Recommended for most tables) | recorddistributioncriteria: 2、recordsownedbyme: true |
All records (for small reference data) | recorddistributioncriteria: 1 |
Related rows only (for pure child tables) | recorddistributioncriteria: 0 |
Custom — specify exact flags in next message | テキストで指定するようユーザーに促します |
質問本文の推奨内容は、追加対象テーブルに対するアーキテクトのヒューリスティックから導きます(offline-profile-architect.md の Step 4 にあるインライン記述と同じ優先順位カスケードを使用します)。ユーザーは変更できます。
提案するリレーションシップ、選択列、同期間隔をスコープの選択肢とともに表示します。提示された回答を再利用できるのは、このオフライン変更を明示的に承認している場合のみです。それ以外の場合は、Step 5 に進む前に選択を待ちます。キャンセルされた場合は、プロファイル項目を作成せずに停止します。呼び出しもオーケストレーションマーカーも、この確認への回答にはなりません。
Step 5 — 項目と関連付けを POST し、selectedcolumns を PATCH する
Step 5a — プロファイル項目を POST する
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" "<envUrl>" POST \
"mobileofflineprofileitems" --tenant-id "<tenantId>" \
--body '{
"name": "<DisplayName>",
"[email protected]": "/mobileofflineprofiles(<profileId>)",
"selectedentitytypecode": "<table>",
"recorddistributioncriteria": <0|1|2>,
"recordsownedbyme": <bool>,
"recordsownedbymyteam": false,
"recordsownedbymybusinessunit": false,
"getrelatedentityrecords": true,
"syncintervalinminutes": 10
}' \
--include-headers
レスポンスヘッダー OData-EntityId から itemId を取得します。
Step 5b — 新しいテーブルのリレーションシップに対する関連付けを POST する
新しい項目の作成後、新しいテーブルのリレーションシップ(マニフェストの lookups[] と EntityDefinitions(LogicalName='<table>')/ManyToOneRelationships クエリから取得)をたどり、ターゲットがすでにプロファイルに含まれている各リレーションシップについて、mobileofflineprofileitemassociation を1件ずつ POST します。
shared/references/dataverse-offline-api.md §5–§6 の手順に従います。
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" "<envUrl>" POST \
"mobileofflineprofileitemassociations" --tenant-id "<tenantId>" \
--body '{
"name": "<relationshipSchemaName>",
"relationshipdisplayname": "<relationshipSchemaName>",
"relationshipid": "<MetadataId-from-EntityDefinitions>",
"[email protected]": "/mobileofflineprofileitems(<newItemId>)"
}' \
--include-headers
重要:リクエスト本文に selectedrelationshipsschema を含めないでください。サーバーが設定します(2026-05-24 の実測結果)。
recorddistributioncriteria: 0(関連行のみ)の新しいテーブルには、少なくとも1つのインバウンドリレーションシップを含める必要があります。含まれていない場合、テーブルは行を1件も同期しません。このスキルはこれを検証し、「関連行のみ」スコープのテーブルに対して関連付けが作成されない場合は警告する必要があります。
Step 5c — selectedcolumns を PATCH する
offline-profile-architect.md の Step 6 にある決定的な和集合(常に含める列 ∪ ルックアップ ∪ 画面の grep で検出した列。重複を除去し、ソート)を使って selectedcolumns を構築します。ファイルツールの grep は、絶対パスの <working_dir>/app/ と <working_dir>/src/、および選択したテーブルに限定します。Step 4 で承認された提案列を使用します。
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" "<envUrl>" PATCH \
"mobileofflineprofileitems(<itemId>)" --tenant-id "<tenantId>" \
--body '{"selectedcolumns":"{\"Columns\":[...]}"}'
Step 6 — 公開(対象を絞った PublishXml)
cd -- '<working_dir>' || { echo "BLOCKED: cannot enter working_dir" >&2; exit 1; }
node "${PLUGIN_ROOT}/scripts/dataverse-request.js" "<envUrl>" POST \
"PublishXml" --tenant-id "<tenantId>" --body '{
"ParameterXml": "<publish><mobileofflineprofiles><mobileofflineprofile><profileId></mobileofflineprofile></mobileofflineprofiles></publish>"
}'
組織全体のカスタマイズではなく、このプロファイルのみを公開します。0x80071141 の循環リレーションシップへの対処と PublishAllXml へのフォールバックについては、shared/references/dataverse-offline-api.md §9 を参照してください。
関連するすべての復旧コマンド、再 GET による検証、再試行では、同じルートにバインドし、同じ環境/テナント/プロファイル/テーブル/項目のコンテキストを渡す必要があります。循環関連付けを削除するには、その手順においてユーザーによる明示的な判断が必要です。組織全体に対する PublishAllXml へのフォールバックには、その広範な公開についての承認が必要です。プロファイルに限定した承認を、黙ってより広い承認に拡大してはなりません。別のプロファイルで再試行したり、権限エラーを回避したりしてはなりません。Step 7 に進む前に、公開を確認してください。
関連する復旧手順で別のオフラインヘルパーが必要な場合は、まず変更案をオーナーに返して承認を得てから、絶対パス ${PLUGIN_ROOT}/skills/<helper>/SKILL.md にあるヘルパーを読み込みます。次の内容を転送します。
Arguments: --working-dir '<working_dir>' --profile-id "<profileId>" --table "<table>" <exact-approved-change-flags>
Context: MOBILE_APP_ORCHESTRATING=1, orchestrator, working_dir, phase, approved_scope,
selected environment ID/URL/tenant, profile ID, table allowlist, item ID,
supplied answers, --plan-only (if present)
受信側は、同じ正規ルートの契約を適用する必要があります。コンテキストが不足している場合は NEEDS_CONTEXT を返し、直接呼び出しや再探索に切り替えてはなりません。
ステップ 7 — アーティファクトの更新
新しいテーブルのエントリを <working_dir>/offline-profile.json の tables[] に追記します。
そのエントリには、この時点でバインドされている絶対マニフェストから取得した、
追加するテーブルのスキーマ列の論理名一式を含む schemaColumns 配列を必ず含める必要があります。
これは、${PLUGIN_ROOT}/scripts/offline-profile-delta.js が将来のマニフェスト変更を
差分比較する際のスキーマ照合のベースラインです。これを省略すると、再照合されるまで
columnBaselineMissing が報告されます。これは selectedColumns(キュレーションされた同期セット)
とは別物です — 詳細は
offline-profile-reconciliation.md を参照してください。
そのワークフローを実行せずに、
/setup-offline-profile ステップ 9a
の正規のエントリ形状に合わせてください。差分チェックは、
--project-root '<working_dir>' を独自のガード付き呼び出しで使用します。
<working_dir>/memory-bank.md の ## Offline profile ブロックに追記します:
addedTables:
- { at: 2026-05-19T..., table: <name>, itemId: <guid>, scope: <criterion> }
ステップ 8 — サマリー
✓ Added <table> to profile.
Profile : <name>
New item ID : <guid>
Scope : <human-readable>
Sync : <minutes> min
Columns : <N> selected
Published : <ISO timestamp>
Total tables in profile now: <N>
Users who already have this profile assigned will receive the new table on their
next sign-in sync. To force-refresh, sign out and back in on the device.
ステータスコード(最終行)
DONE— テーブルを追加し、プロファイルを公開し、アーティファクトを更新しましたDONE_WITH_CONCERNS: <list>— 注意事項付きで追加しました(前提条件の自動有効化、公開タイムアウト後の成功など)NEEDS_CONTEXT: <missing>— 追加先のプロファイルがない、または対象テーブルがありませんBLOCKED: <reason>— 対象の IsCustomizable=false、認証失敗、POST/PATCH の失敗
このスキルが行わないこと
- プロファイル項目への N:N(多対多)リレーションシップ の追加 — 一般的ではなく、v0.1 のレシピ(
regardingobjectidルックアップによる N:1 / 1:N を前提)では対応していません。ユーザーに M:N のユースケースがある場合は、メーカーポータルを案内してください。 - プロファイル内の既存テーブルの再設計 — それは
/edit-offline-profileの役割です。 - プロファイルからのテーブルの削除 — 手動での
DELETE /mobileofflineprofileitems(<itemId>)が必要です(現在のスキルにはありません)。