audit-ppmplugin について
audit-ppmpluginは、完成した.ppmpluginをアップロード前に検証する読み取り専用スキルです。パッケージ形式、マニフェスト、バイナリ、ランタイムの読み込みに関する問題をローカルで確認します。
主な機能
- 01パッケージの読み取り専用検査
- 02ZIP構成とマニフェストの検証
- 03マニフェストとバイナリの整合性確認
- 04Android DEXの整合性と漏出スキャン
- 05iOSフレームワーク構造の確認
活用シーン
- 01アップロード前のパッケージ検証
- 02マニフェストとプラットフォームバイナリの不一致特定
- 03Android DEXやiOSフレームワークの読み込みリスク検出
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ microsoft/ power-platform-skills --skill audit-ppmpluginインストール後、Claude Code で/audit-ppmpluginを入力して読み込まれたか確認します。
詳細
/audit-ppmplugin
検証ゲートです。/assemble-ppmplugin はバンドルが正しい形式であることを証明します。このスキルは、wrap ランタイムで実際に読み込まれ、ディスパッチされることを証明します。ほとんどの .ppmplugin の失敗は表面化しません。バンドルのアップロードは問題なく完了した後、メソッドが native module 'X' not loaded を返したり、ランタイムがパッケージクラスをインスタンス化できなかったり、0x80040265(正規プレフィックス違反)でアップロードが拒否されたりします。こうした問題の発見には、wrap のビルドを一往復する手間がかかります。このスキルを使えば、ディスク上で数秒のうちに問題を見つけられます。
バンドルは読み取り専用です。検査のために一時ディレクトリへ展開しますが、.ppmplugin は変更しません。修正は上流に戻して行います(マニフェストの問題は /generate-ppmplugin-manifest、バイナリの問題はビルドスキル)。その後、再度アセンブルして監査します。
shared/ppmplugin-format.md の §1(レイアウト)、§3(正規プレフィックス)、§4(バリデーターのルール)、§5(Android DEX の要件)を読んでください。このスキルは、ビルド済みの成果物に対してこれらすべてを検証します。
このスキルで行わないこと
- ビルド、zip 化、作成は行いません。完成した
.ppmpluginを検査します。修正は上流で行い、再度アセンブルしてください。 - zip をその場で修正しません。バンドルは変更不可の成果物です。変更すると、ステージング済みのソースとの整合性が失われます。代わりに、上流のスキルを案内します。
- Dataverse へのアップロードやキャンバスアプリへの組み込みは行いません(ステージ 3 — 保留中)。「アップロード準備完了」とはローカル検証に合格したという意味であり、アップロード済みという意味ではありません。
ステップ 1 — 共通ドキュメントの確認、成果物の特定、前提条件の確認
shared/shared-instructions.mdとshared/ppmplugin-format.mdを読んでください。- 監査するバンドルを特定します。
- ユーザーがパスを指定した場合は、そのパスを使います。ディレクトリの場合は、
<dir>/*.ppmpluginを探します(複数ある場合はユーザーに確認します)。 - 指定がなければ、アセンブルの出力を既定値として使います。リポジトリのルートにある
ppmplugin/<name>.ppmpluginのうち、単一のもの(複数ある場合は最新ビルド)です。 - 見つからない場合は、
NEEDS_CONTEXT: no .ppmplugin to audit — run /assemble-ppmplugin first, or pass a pathで停止します。
- ユーザーがパスを指定した場合は、そのパスを使います。ディレクトリの場合は、
- 前提条件の確認(shared-instructions §9.2)。方針:すぐに諦めず、解決方法を探す(§1.5)。失敗とする前に、パスからツールを探してください。
| 確認項目 | 検証方法 | 見つからない場合 |
|---|---|---|
jar(JDK) | jar --version — バンドルの一覧表示と展開に使用 | BLOCKED: jar not found — install a JDK (JDK 17) で停止 |
| DEX スキャナー | dexdump(推奨。/build-android-binary が $D8 を解決するのと同様に、/build-android-binary のように Android SDK の build-tools/*/dexdump 内を探します)。それ以外は strings(mac/Linux には付属) | どちらもない場合、DEX の文字列チェックは警告に切り替わります(「DEX scan skipped — no dexdump/strings」)。構造チェック(マジック値、サイズ)は引き続き実行されます。Windows でどちらもない場合は、その不足を記録します。 |
- 展開:元のファイルは読み取り専用のまま、新しい一時ディレクトリにバンドルを展開して検査します。
work=$(mktemp -d); ( cd "$work" && jar xf "<abs path to .ppmplugin>" )$work = New-Item -ItemType Directory -Force (Join-Path $env:TEMP "ppm-audit"); Push-Location $work; jar xf "<abs path>"; Pop-Location
以降の各ステップでは、検出事項を [SEVERITY] <check-id>: <result> の形式で一覧に追加します。最初の CRITICAL で止めず、ユーザーが一度に修正できるよう、すべての項目を収集してください。重要度は次のとおりです。CRITICAL(アップロード時またはデバイス上で失敗するため、ブロックします)、WARNING(誤りの可能性が高いものの、動作する場合もあります)、INFO(スタイルまたは参考情報)。
ステップ 2 — カテゴリ A:ZIP の構造
jar tf "<.ppmplugin>" の出力を使用します。バンドルはネイティブのみです(形式 §1。ルートに manifest.json があり、宣言された android/ / ios/ スライスのみを含みます)。
| チェック | 検証内容 | 重大度 |
|---|---|---|
zip-manifest-at-root | manifest.json がアーカイブのルートにある | CRITICAL |
zip-only-native-slices | トップレベルのエントリ ⊆ { manifest.json、android/、ios/ } | CRITICAL |
zip-no-ts-js-layer | src/、*.ts、*.tsx、*.js、extension.js、extension.hbc がどこにもない — バンドルに TS/JS レイヤーは含まれません | CRITICAL |
zip-no-build-dirs | android-build/、ios-build/、node_modules/、dist/、build/ が混入していない | CRITICAL |
zip-no-stray-files | META-INF/、.DS_Store、ドットファイル、入れ子になった *.zip / *.ppmplugin がない | WARNING |
zip-reasonable-size | バンドルが 50 MB 未満(これを超える場合、通常は React または node_modules が誤って含まれています) | WARNING |
src/ ツリー、extension.js / .hbc、または任意の *.ts がある場合は、SDK 時代/JS レイヤーの混入です — CRITICAL としてフラグを立て、形式 §6 を参照します(バンドルに含まれるのはネイティブバイナリのみです)。
ステップ 3 — カテゴリ B:マニフェストのスキーマ、バリデーターのルール、フィールドの混入
抽出した manifest.json を読み込みます。構築済みのアーティファクトに対して、ppmplugin-format §4 のすべてのルールを再実行します(二重の防御 — /generate-ppmplugin-manifest の後にマニフェストが手作業で編集されている可能性があります)。
| チェック | 検証内容 | 重大度 |
|---|---|---|
mf-name-shape | name が ^[a-z0-9][a-z0-9-]{0,63}$ に一致する | CRITICAL |
mf-canonical-prefix | 各 receivers[].nativeModule が name の正規プレフィックスで始まる(- / _ で分割し、各セグメントを PascalCase にして結合 — §3)(Ordinal、大文字と小文字を区別) | CRITICAL |
mf-reserved-prefix | nativeModule が予約済みプレフィックスで始まらない(§4 の大文字と小文字を区別しないリスト:Microsoft、MS、Intune、Wrap、Pcf、PowerApps、…) | CRITICAL |
mf-reserved-exact-known | nativeModule が、ローカルで確認済みの互換性のない名前のサブセット(§4:DeviceInfo、AuthenticationHelper、NetworkClient、DataverseOfflineProvider、IntuneMAM)に一致しない。すべてを網羅しているわけではありません。修正するには、getName() を予約されていない形式に変更し(Module サフィックスまたはベンダープレフィックスを追加)、再導出します | CRITICAL |
mf-nativeModule-generic-noun | nativeModule が汎用的なプラットフォーム名詞のヒューリスティック ^(Device|Network|File|Audio|Camera|Sensor|Location|Storage|Notification|Bluetooth|Wifi|Media|Photo|Contact|Calendar|Battery) に一致しない — こうした裸の名前は拒否リストに載りやすく、共有 NativeModules 名前空間で衝突しやすいものです。(ヒューリスティックであり、実際のリストではありません — DeviceInfo はローカルでは READY と判定されましたが、サーバーに拒否されました。これがあれば警告が出ていたでしょう。) | WARNING |
mf-methods | すべての receivers[].methods が存在し、空でなく、32 件以下で、それぞれが ^[a-zA-Z_$][a-zA-Z0-9_$]{0,127}$ に一致する | CRITICAL |
mf-receiver-name | 各 receivers[].name が JS 識別子の正規表現に一致する | CRITICAL |
mf-version-present | version が空でない文字列である | WARNING |
mf-no-sdk-fields | マニフェストに entrypoints.js、entrypoints.ts、extension.js、extension.hbc、extensionClassName、jsLayer フィールドがない。これは当方のネイティブのみのポリシーであり、サーバー側のゲートではありません — wrap injector は entrypoints.js を任意として扱い、受け入れます。ネイティブのみのバンドルであることを明確に保つため、これらにフラグを立てます。ブロックはせず、WARNING とします。 | WARNING |
レポートには、互換性のない完全一致名の既知のサブセットのみがローカルでチェックされる
(mf-reserved-exact-known)ため、ローカルで問題がなくても、アップロードサービスがその名前を
受け入れるとは限らないことを記載してください。
ステップ 4 — カテゴリー C:マニフェストとバンドルの整合性
宣言された entrypoints は、zip 内の実際のバイナリと完全に一致している必要があります。(/assemble-ppmplugin はビルド時にこの整合性を取りますが、完成したバンドルを監査することで、手編集されたマニフェストや手作業で zip 化されたバンドルを検出できます。)
| チェック | 検証内容 | 重大度 |
|---|---|---|
consistency-has-native | entrypoints.android / entrypoints.ios の少なくとも一方が宣言されている(マニフェストのみのバンドルには読み込む対象がない) | CRITICAL |
consistency-android-dex | entrypoints.android が宣言されている場合 → zip 内に android/<entrypoints.android.dex> が存在する | CRITICAL |
consistency-ios-framework | entrypoints.ios が宣言されている場合 → zip 内に ios/<entrypoints.ios.framework>.framework/ が存在する(フラットなフレームワーク。カテゴリー E の ios-flat-framework-not-xcframework を参照) | CRITICAL |
consistency-no-orphan | zip 内のすべての android/ または ios/ スライスに対応する宣言済みエントリーポイントがある(ランタイムがルーティングしない未宣言のバイナリがない) | WARNING |
consistency-packageClass-shape | entrypoints.android.packageClass が完全修飾 Java クラス名である(^([a-z][a-z0-9_]*\.)+[A-Z][A-Za-z0-9_]*$) | CRITICAL |
dispatch-composite-key | 複合ルーティングキー <name>/<receivers[].name> の形式が正しい(両方のセグメントが存在し、サフィックスが JS 識別子として有効)こと。このキーをディスパッチャー PCF が ReceiverKey としてバインドし、NativeModules.<nativeModule>.<method> に到達します(形式 §2 — ランタイムディスパッチの契約) | WARNING |
ステップ 5 — カテゴリー D:DEX の整合性と SDK 漏洩のバイトスキャン(Android)
entrypoints.android が宣言されている場合のみ実行します。DEX は DexClassLoader 経由で読み込まれます(形式 §5)。以下のチェックでは、読み込み可能であり、SDK 時代の漏洩がないことを確認します。利用可能な場合は dexdump -l plain <dex>、それ以外の場合は strings <dex> を使用します。どちらもチェック対象の文字列/型プールを出力します。
| チェック | 検証内容 | 重大度 |
|---|---|---|
dex-magic | 先頭のバイトが dex\n03[5-9] である(有効な DEX) | CRITICAL |
dex-has-packageClass | entrypoints.android.packageClass の型記述子(Lcom/.../<Pascal>Package;)が DEX 内にある。それ以外の場合、ランタイムのクラス読み込みに失敗します | CRITICAL |
dex-has-module-class | モジュールクラスの記述子が DEX 内にある | CRITICAL |
dex-no-INativeExtension | INativeExtension、INativeOperation、INativeExtensionContext の文字列がない。SDK 時代のシンボルがネイティブ専用バンドルに含まれています。クリーンさに関するポリシーであり、サーバーのゲートではありません。プレーヤーはこれらを無視します(古いパターンのプラグインでも、receivers[] が実在するネイティブモジュールに対応していればディスパッチされます)。そのため検出結果は示しますが、ブロックはしません。 | WARNING |
dex-no-jslayer-loader | HermesBytecodeLoader、WrapPluginJsLayerLoader の文字列がない。JS レイヤーローダーはネイティブ専用 DEX に含まれるべきではありません(同様に、警告のみでブロックしません)。 | WARNING |
dex-no-sendAsync | sendAsync シンボルがない。SDK トランスポートが漏洩している可能性があります | WARNING |
これらの SDK 漏洩チェックは、ネイティブ専用の契約の要です。コンパイルが正常に完了しても、これらのシンボルが推移的に取り込まれる可能性があります。ビルド済み DEX をスキャンすることだけが、それらを検出できる手段です。
ステップ 6 — カテゴリ E:iOS フレームワークの構造(wrap-CI ゲート)
entrypoints.ios が宣言されている場合のみ実行します。CRITICAL セットは、ランタイムの dlopen による読み込みと wrap 署名パイプラインで必要となる項目に限定されています。アンブレラヘッダーとモジュールマップはビルド時の衛生管理にすぎません(ローダーはバイナリを dlopen しますが、モジュールを import することはありません)。そのため、これらは INFO とし、存在しないことを理由に有効なバンドルをブロックしないでください。
| チェック | 検証内容 | 重大度 |
|---|---|---|
ios-flat-framework-not-xcframework | バンドルに ios/<framework>.framework/ が含まれており、ios/<framework>.xcframework/ ではないこと。.xcframework が存在する場合は、wrap-CI のエラーをそのまま出力して FAIL にします("Framework '.framework' not found in plugin")。修正方法は /build-ios-binary で再ビルドすることです(デバイス用スライス、フラットなフレームワーク。-create-xcframework を削除)。 | CRITICAL |
ios-binary-present | ios/<framework>.framework/<framework>(Mach-O バイナリ。名前は正確に <framework>)が存在すること。ローダーは Frameworks/<framework>.framework/<framework> を dlopen するため、バイナリがない場合や名前が異なる場合は読み込めません | CRITICAL |
ios-framework-has-infoplist | ios/<framework>.framework/Info.plist が存在し、CFBundlePackageType=FMWK、CFBundleExecutable=<framework>、CFBundleIdentifier、CFBundleShortVersionString、MinimumOSVersion が設定されていること。Info.plist のないフレームワークバンドルは無効で、wrap の codesign ステップで拒否されます | CRITICAL |
ios-framework-has-umbrella | ios/<framework>.framework/Headers/<framework>.h のアンブレラヘッダーが存在すること。ビルド時の衛生管理のみであり、dlopen ローダーはモジュールを import しないため、存在しなくても読み込みには影響しません | INFO |
ios-framework-has-modulemap | ios/<framework>.framework/Modules/module.modulemap が存在すること。ビルド時の衛生管理のみ(理由は同じ)であり、ランタイムでは必須ではありません | INFO |
ios-moduleClass-match | entrypoints.ios.moduleClass が文書化された Obj-C クラス(RCT<Pascal>Module)であり、ソースにアクセスできる場合は .h/.m のクラス名も一致すること | WARNING |
ios-moduleName-matches | ソースにアクセスできる場合、.m が receivers[].nativeModule を返す + (NSString *)moduleName を宣言し、RCT_EXPORT_MODULE(...) を使用していないこと(wrap プラグインは +load ではなくランタイムに登録されます) | CRITICAL |
(Dynamic と Static の違い、デッドコードストリッピング、弱い React リンクなどの詳細な Mach-O イントロスペクションは、ビルド時に /build-ios-binary が担当します。ここでは、同梱されたフレームワークがローダーと wrap CI に取り込まれる形式であることを検証します。)
ステップ 7 — カテゴリ F:ソースとレシーバーの契約(リポジトリにアクセスできる場合)
これらのチェックには拡張機能のソースが必要です。監査が制御リポジトリで実行され(バンドルのソースツリーが存在する場合)は実行してください。それ以外の場合は、各項目を SKIPPED — source not reachable とし、ビルドスキルがビルド時にすでに検証済みであることを記載してください。
| チェック | 検証内容 | 重大度 |
|---|---|---|
src-getName-matches | Kotlin モジュールの getName() リテラル == receivers[].nativeModule — 不一致の場合、デバイス上で native module 'X' not loaded が発生します(コンパイルは正常に成功するため、検出が最も困難です) | CRITICAL |
src-methods-annotated | receivers[].methods[] の各エントリーに、対応する @ReactMethod(Android)/RCT_EXPORT_METHOD(iOS)があること | CRITICAL |
src-package-fqn-matches | ReactPackage の FQN == entrypoints.android.packageClass | CRITICAL |
src-package-no-arg-ctor | <Pascal>Package(Android)に public な引数なしコンストラクターがあること — ランタイムは getDeclaredConstructor().newInstance() を使用します。プライマリーコンストラクターにパラメーターがある場合(class <Pascal>Package(...))、プラグインの読み込みはエラーなしで失敗します(Loaded 0 plugin package(s))。iOS での同等条件:モジュールクラスが [cls new] でインスタンス化されること(カスタム引数付きイニシャライザーがないこと)。 | CRITICAL |
src-ios-module-no-arg-init | (iOS)モジュールクラスに引数付きのイニシャライザーしかない状態でないこと — プレーヤーは NSClassFromString(moduleClass) → [cls new] を実行するため、引数を取るイニシャライザーしかないクラスはインスタンス化されず、読み込み時にスキップされます(§5b)。 | CRITICAL |
src-ios-requires-main-queue | (iOS)+ (BOOL)requiresMainQueueSetup が宣言されている場合、戻り値が NO であること(YES を返すと起動時にメインスレッドでセットアップが実行され、重い処理や例外を発生させる init があると、起動が停止またはクラッシュします — §5b)。 | WARNING |
src-method-single-map-param | 各 @ReactMethod/RCT_EXPORT_METHOD が、リクエストパラメーターとして ReadableMap/NSDictionary を 1 つ(続いて Promise/リゾルバー)受け取ること — ラッププロキシは PCF の args:[request] を位置引数として展開するため、位置引数を複数取るメソッドにはペイロードが届きません(Promise のみを取る引数なしの操作は問題ありません)。 | WARNING |
pcf-composite-key-matches-receiver | 隣接する PCF(pcf/<…>/index.ts)が COMPOSITE_KEY = "<name>/<receiver>" を宣言している(または ReceiverKey をバインドしている)場合、その値が <manifest.name>/<receivers[].name> と一致すること。一致しない場合、PCF はマニフェストに登録されていないレシーバーへディスパッチし、最初の呼び出しで失敗します(実際のバグ:PCF→Snapshot、マニフェスト→DeviceInfoExtension)。 | WARNING |
pcf-uses-sendasync | 隣接する PCF(pcf/<…>/index.ts)が存在する場合、必ず window.PowerApps.NativeExtension.sendAsync(...) 経由でディスパッチし、cordova.exec(または任意の cordova.*)を直接呼び出してはいけません。 cordova.exec の直接呼び出しは CRITICAL な欠陥です。生の cordova グローバルは PCF サンドボックスに公開されていないため、デバイス上では呼び出しが何もせずに終了します(特に Android で顕著です)。zip/マニフェスト/DEX/キーのチェックには合格し、デバイス上でのみ失敗します。修正方法:ホストから注入される sendAsync グローバル経由で呼び出します(形式 §2)。 | CRITICAL |
pcf-envelope-is-raw-object | 隣接する PCF が存在する場合、sendAsync ペイロードは 生の { method, args: [...] } オブジェクトでなければなりません。PCF 側で事前に JSON.stringify してはいけません(sendAsync 内部で文字列化されます)。sendAsync(key, JSON.stringify({...})) のように二重エンコードすると、プロキシの JSON.parse の結果が {method,args} ではなく文字列になり、BRIDGE_FAILED が発生します。修正方法:文字列化した値ではなく、オブジェクトリテラルを渡します。 | CRITICAL |
pcf-args-is-array | 隣接する PCF が存在する場合、sendAsync ペイロード内の args は単独のオブジェクトではなく、JSON 配列(args: [request])であること — パース後、ラッププロキシは Array.isArray(parsed.args) ? parsed.args : [] を実行し、配列以外の値を破棄するため、ネイティブメソッドにペイロードが届きません(形式 §2)。 | WARNING |
pcf-has-ambient-dts | 隣接する PCF が存在する場合、ホスト SDK パッケージをインポートする代わりに、window.PowerApps.NativeExtension.sendAsync の型を定義するローカルの PowerAppsNativeExtension.d.ts アンビエント宣言を同梱していること — これにより PCF のホスト非依存性を維持できます。 | INFO |
src-no-ReactModule-annotation | モジュールに @ReactModule アノテーションがないこと(DexClassLoader と互換性がありません — 形式 §5) | CRITICAL |
src-ctor-no-throwable-sideeffects | スキャン範囲はファイル全体ではなく、モジュールの構築クロージャに限定します — プライマリー/セカンダリーコンストラクター、Kotlin の init{} ブロック、構築時に実行されるプロパティ初期化子(private val x = …)、およびそれらが呼び出すすべてのプライベート関数(foo()/this.foo() を 1 段階たどる)です。この範囲内で次を検出します:(a)register*Callback(…, null)、または明示的な Looper を指定しない Handler()/Handler(...)。(b)try { } catch の中にない副作用を伴う呼び出し(register*/add*Listener/observe/getSystemService とその後の使用/ファイルまたはネットワーク I/O/runBlocking)。@ReactMethod の本体は範囲外です(構築時ではなく、呼び出しごとに実行されます)。モジュールは、Looper がない可能性のあるスレッド上でブリッジの起動時に即時に構築されます — 例外を捕捉しないと、UI が表示される前にホストの起動時クラッシュを引き起こします(形式 §5)。修正方法:初回呼び出しまで初期化を遅延する、Handler(Looper.getMainLooper()) を渡す、避けられない初期化を try/catch で囲む。**iOS での同等条件:**例外を発生させる/重い -init に同じスキャンを適用します(§5b に従い、[cls new] で即時にインスタンス化されます)。これはヒューリスティックです。決定的な検出は、/test-native-extension の Layer 1 による読み込み/初期化準備状況のアサーション(静的)と、Layer 5 によるランタイム起動クラッシュスキャンです。 | WARNING |
pcf-response-unwraps-message | 隣接する PCF が存在する場合、sendAsync の成功パスでラップのレスポンスコンテナー({isUpdate, message:"<json>"})をアンラップすること — つまり、単一の JSON.parse だけで処理するのではなく、extractResponse のようなヘルパー(result.data をパースし、message を調べる)を呼び出します。単一のパースだけを行うハンドラーはコンテナーを受け取り、ネイティブ処理が成功しているにもかかわらず、すべての呼び出しで UNEXPECTED_PAYLOAD が発生します(形式 §2)。 | WARNING |
src-promise-always-settled | Promise/resolver+rejecter を取る各 @ReactMethod/RCT_EXPORT_METHOD に、到達可能な経路上で少なくとも 1 つの promise.resolve/promise.reject(または resolve(...)/reject(...))があること。Promise を取るメソッドが解決も拒否もせずに戻る可能性があると、呼び出し元で制御がハングし、コードやメッセージも返されません。 | WARNING |
src-listener-released | モジュール内の各 register*/add*Listener/observe/取得したマネージャーに対し、invalidate()/onCatalystInstanceDestroy()/ティアダウン内で対応する解放処理(unregister*/remove*Listener/.close()/.release())があること。解放処理のないコールバック登録はリークを引き起こし、破棄済みのモジュールに対して呼び出される可能性があります。 | WARNING |
ステップ 8 — 判定を報告し、修正先へ案内する
判定を、見えるフェンス付きブロックとして出力します(shared-instructions §9.1)。各重要度には shared-instructions.md §9.3 に従って接頭辞を付けます。絵文字が表示されないターミナルでも意味が伝わるよう、文字列は残してください。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
audit-ppmplugin — pen-input.ppmplugin
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Path: ppmplugin/pen-input.ppmplugin (v0.1.4, Android-only, 3.4 KB)
🔴 CRITICAL: 0 🟡 WARNING: 1 🔵 INFO: 0 SKIPPED: 4 (source not reachable)
🟡 [WARNING] zip-no-stray-files: .DS_Store present in android/
Fix: re-run /assemble-ppmplugin (it excludes dotfiles).
ⓘ The upload service may apply additional name checks not represented locally.
🟢 ✓ READY TO UPLOAD
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
CRITICAL がある場合、判定行は 🔴 で始まり、最終行は 🔴 ✗ NOT READY — fix the CRITICAL findings above とします。
クリーンでない各指摘には、具体的な次のアクションを示す Fix: を付け、担当スキルへ案内します(zip パッチは禁止です)。
- マニフェストのルール/漏えいフィールド → 「
/generate-ppmplugin-manifestを編集して再実行し、その後/assemble-ppmpluginを実行します。」 - DEX の漏えい/クラスの不足 → 「Android ソースに SDK シンボルが取り込まれています。インポートを削除し、
/build-android-binaryを再実行してから/assemble-ppmpluginを実行します。」 - Zip 内の余分なファイル/構造 → 「
/assemble-ppmpluginを再実行します。」
次に、AskUserQuestion を使って次のステップを提示します(shared-instructions §9.1)。
- クリーン(CRITICAL 0 件): Re-run /generate-ppmplugin-manifest(該当先を示す WARNING がある場合のみ)/Stay — 手動でアップロードします(ステージ 3 はまだスキルではありません)を提示します。ユーザーが
Run /…オプションを選択した場合は、同じターンで Skill ツールを使って実行します(§8 および §9.1「説明するのではなく実行する」)。 - 問題あり(CRITICAL 1 件以上): 最初の CRITICAL の担当スキルに移るオプション、Run /generate-ppmplugin-manifest または Run /build-android-binary を提示し、その後、再アセンブルして再監査します。選択されたら実行します。
.extension-state.md の ## ppmplugin (third-party controls) ブロックを更新します。Audit: <name>.ppmplugin <ISO timestamp> — <CRITICAL>C/<WARNING>W (READY | BLOCKED)。
終了コード:
DONE— CRITICAL 0 件(クリーン、または WARNING のみの場合はその旨を記載)。バンドルはローカル検証に合格しています。BLOCKED: audit found <N> CRITICAL — see findings— READY 判定を拒否します。アップロード可能であると主張しないでください。