本文へ移動
cccskills
無料GitHub で公開

audit-ppmplugin

Statically audit a built `.ppmplugin` before wrap testing. Checks archive layout, manifest compatibility, bundle consistency, Android DEX integrity and SDK leakage, iOS framework structure, native source-to-receiver alignment, and the PCF composite-key/sendAsync transport contract. Reports CRITICAL, WARNING, and INFO findings with fixes routed to the owning stage; never modifies the archive. Requires `jar`; `dexdump` or `strings` improves DEX inspection. Run after /assemble-ppmplugin or standalone on an existing bundle.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md22.6 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

/audit-ppmplugin

The verification gate. /assemble-ppmplugin proves the bundle is well-formed; this skill proves it will actually load and dispatch on the wrap runtime. Most .ppmplugin failures are silent — the bundle uploads fine, then a method returns native module 'X' not loaded, the runtime cannot instantiate the package class, or the upload is rejected with 0x80040265 (canonical-prefix violation). Those cost a full wrap-build round-trip to discover. This skill surfaces them in seconds, on disk.

It is read-only on the bundle — it unzips to a temp dir for inspection and never mutates the .ppmplugin. Fixes route back upstream (/generate-ppmplugin-manifest for manifest issues, the build skills for binary issues), then re-assemble + re-audit.

Read shared/ppmplugin-format.md §1 (layout), §3 (canonical-prefix), §4 (validator rules), §5 (Android DEX requirements) — this skill enforces all four against the built artifact.

What this skill does NOT do

  • Does not build, zip, or author anything — it inspects a finished .ppmplugin. Fixes are made upstream and re-assembled.
  • Does not patch the zip in place — a bundle is an immutable deliverable; mutating it would desync it from the staged sources. It points at the upstream skill instead.
  • Does not upload to Dataverse / wire into a canvas app (Stage 3 — deferred). "READY TO UPLOAD" means passes local verification, not uploaded.

Step 1 — Read shared docs + resolve the artifact + prereqs

  1. Read shared/shared-instructions.md and shared/ppmplugin-format.md.
  2. Resolve the bundle to audit:
    • If the user passed a path, use it. If it's a directory, look for <dir>/*.ppmplugin (prompt if multiple).
    • Else default to the assemble output: the single ppmplugin/<name>.ppmplugin at the repo root (the most-recently-built if several).
    • If none found, STOP with NEEDS_CONTEXT: no .ppmplugin to audit — run /assemble-ppmplugin first, or pass a path.
  3. Prereq block (shared-instructions §9.2). Policy: resolve, don't punt (§1.5) — locate a tool by path before failing:
CheckVerifyIf missing
jar (JDK)jar --version — used to list + extract the bundleSTOP BLOCKED: jar not found — install a JDK (JDK 17)
DEX scannerdexdump (preferred — locate in Android SDK build-tools/*/dexdump like /build-android-binary resolves $D8); else strings (mac/linux ships it)If neither: the DEX string checks degrade to WARNING ("DEX scan skipped — no dexdump/strings"); structural checks (magic, size) still run. On Windows without either, note the gap.
  1. Extract the bundle to a fresh temp dir for inspection (read-only on the original):
    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
    

Each subsequent step appends findings to a running list as [SEVERITY] <check-id>: <result>. Don't stop at the first CRITICAL — collect everything so the user fixes in one pass. Severities: CRITICAL (will fail at upload or on device — blocks), WARNING (likely-wrong, may still work), INFO (style / advisory).


Step 2 — Category A: Zip structure

Listing comes from jar tf "<.ppmplugin>". The bundle is native-only (format §1): manifest.json at root + only the declared android/ / ios/ slices.

CheckAssertsSeverity
zip-manifest-at-rootmanifest.json is at the archive rootCRITICAL
zip-only-native-slicestop-level entries ⊆ { manifest.json, android/, ios/ }CRITICAL
zip-no-ts-js-layerNO src/, *.ts, *.tsx, *.js, extension.js, extension.hbc anywhere — the bundle ships no TS/JS layerCRITICAL
zip-no-build-dirsNO android-build/, ios-build/, node_modules/, dist/, build/ leaked inCRITICAL
zip-no-stray-filesNO META-INF/, .DS_Store, dotfiles, nested *.zip/*.ppmpluginWARNING
zip-reasonable-sizebundle < 50 MB (a larger one usually means React or node_modules got swept in)WARNING

A src/ tree, an extension.js/.hbc, or any *.ts is SDK-era / JS-layer leakage — flag CRITICAL and point at format §6 (the bundle is native binaries only).


Step 3 — Category B: Manifest schema, validator rules + field leakage

Read the extracted manifest.json. Re-run every ppmplugin-format §4 rule against the built artifact (defense in depth — the manifest may have been hand-edited after /generate-ppmplugin-manifest):

CheckAssertsSeverity
mf-name-shapename matches ^[a-z0-9][a-z0-9-]{0,63}$CRITICAL
mf-canonical-prefixeach receivers[].nativeModule starts with the canonical prefix of name (split on -/_, PascalCase each segment, join — §3) (Ordinal, case-sensitive)CRITICAL
mf-reserved-prefixno nativeModule starts with a reserved prefix (case-insensitive list in §4: Microsoft, MS, Intune, Wrap, Pcf, PowerApps, …)CRITICAL
mf-reserved-exact-knownno nativeModule matches the locally checked incompatible-name subset (§4: DeviceInfo, AuthenticationHelper, NetworkClient, DataverseOfflineProvider, IntuneMAM). Non-exhaustive; fix = rename getName() to a non-reserved form (add Module suffix or a vendor prefix) and re-deriveCRITICAL
mf-nativeModule-generic-nounnativeModule does NOT match the generic-platform-noun heuristic ^(Device|Network|File|Audio|Camera|Sensor|Location|Storage|Notification|Bluetooth|Wifi|Media|Photo|Contact|Calendar|Battery) — these bare names are both denylist-prone and collision-prone in the shared NativeModules namespace. (Heuristic, not the real list — DeviceInfo returned READY locally yet was server-rejected; this would have warned.)WARNING
mf-methodsevery receivers[].methods is present, non-empty, ≤32, each matches ^[a-zA-Z_$][a-zA-Z0-9_$]{0,127}$CRITICAL
mf-receiver-nameeach receivers[].name matches the JS-identifier regexCRITICAL
mf-version-presentversion is a non-empty stringWARNING
mf-no-sdk-fieldsNO entrypoints.js, entrypoints.ts, extension.js, extension.hbc, extensionClassName, jsLayer fields in the manifest. Our native-only policy, NOT a server gate — the wrap injector treats entrypoints.js as optional and accepts it; we flag it so the native-only bundle stays unambiguous. WARNING, not a block.WARNING

Note in the report that only a known subset of incompatible exact names is checked locally (mf-reserved-exact-known), so a clean local result does not guarantee the upload service will accept the name.


Step 4 — Category C: Manifest ↔ bundle consistency

The declared entrypoints must match the binaries actually in the zip — exactly. (/assemble-ppmplugin reconciles this at build time; auditing the finished bundle catches a hand-edited manifest or a hand-zipped bundle.)

CheckAssertsSeverity
consistency-has-nativeat least one of entrypoints.android / entrypoints.ios is declared (a manifest-only bundle has nothing to load)CRITICAL
consistency-android-dexif entrypoints.android declared → android/<entrypoints.android.dex> exists in the zipCRITICAL
consistency-ios-frameworkif entrypoints.ios declared → ios/<entrypoints.ios.framework>.framework/ exists in the zip (flat framework — see ios-flat-framework-not-xcframework in Category E)CRITICAL
consistency-no-orphanevery android/ or ios/ slice in the zip is backed by a matching declared entrypoint (no undeclared binary the runtime won't route)WARNING
consistency-packageClass-shapeentrypoints.android.packageClass is a fully-qualified Java class name (^([a-z][a-z0-9_]*\.)+[A-Z][A-Za-z0-9_]*$)CRITICAL
dispatch-composite-keythe composite routing key <name>/<receivers[].name> is well-formed (both segments present, JS-identifier-safe suffix) — this is what a dispatcher PCF binds as ReceiverKey to reach NativeModules.<nativeModule>.<method> (format §2 — Runtime dispatch contract)WARNING

Step 5 — Category D: DEX integrity + SDK-leakage byte-scan (Android)

Only if entrypoints.android is declared. The DEX is loaded via DexClassLoader (format §5); these confirm it's loadable and free of SDK-era leakage. Use dexdump -l plain <dex> if available, else strings <dex> — both expose the string/type pool the checks scan.

CheckAssertsSeverity
dex-magicfirst bytes are dex\n03[5-9] (valid DEX)CRITICAL
dex-has-packageClassthe entrypoints.android.packageClass type descriptor (Lcom/.../<Pascal>Package;) is in the DEX — otherwise runtime class loading failsCRITICAL
dex-has-module-classthe module class descriptor is in the DEXCRITICAL
dex-no-INativeExtensionNO INativeExtension, INativeOperation, INativeExtensionContext strings — SDK-era symbols in a native-only bundle. Cleanliness policy, not a server gate: the player ignores them (an old-pattern plugin still dispatches if its receivers[] map to real native modules), so surface it but don't block.WARNING
dex-no-jslayer-loaderNO HermesBytecodeLoader, WrapPluginJsLayerLoader strings — a JS-layer loader has no place in a native-only DEX (same: flagged, not blocking).WARNING
dex-no-sendAsyncNO sendAsync symbol — possible SDK transport leakWARNING

These SDK-leakage checks are the heart of the native-only contract: a clean compile can still pull these symbols in transitively. Scanning the built DEX is the only place they're catchable.


Step 6 — Category E: iOS framework structure (the wrap-CI gates)

Only if entrypoints.ios is declared. The CRITICAL set is exactly what the runtime's dlopen loading and wrap signing pipeline require. The umbrella header + module map are build hygiene only (the loader dlopens the binary; it never imports the module), so they're INFO — do not block a valid bundle on their absence.

CheckAssertsSeverity
ios-flat-framework-not-xcframeworkthe bundle ships ios/<framework>.framework/ — NOT ios/<framework>.xcframework/. If an .xcframework is present, FAIL with the wrap-CI error verbatim ("Framework '<Name>.framework' not found in plugin") and the fix: rebuild with /build-ios-binary (device slice, flat framework — drop -create-xcframework).CRITICAL
ios-binary-presentios/<framework>.framework/<framework> (the Mach-O binary, named exactly <framework>) exists — the loader dlopens Frameworks/<framework>.framework/<framework>, so a missing or differently-named binary won't loadCRITICAL
ios-framework-has-infoplistios/<framework>.framework/Info.plist exists with CFBundlePackageType=FMWK, CFBundleExecutable=<framework>, CFBundleIdentifier, CFBundleShortVersionString, MinimumOSVersion — a framework bundle with no Info.plist is invalid and the wrap codesign step rejects itCRITICAL
ios-framework-has-umbrellaios/<framework>.framework/Headers/<framework>.h umbrella header present — build hygiene only; the dlopen loader does not import the module, so its absence does NOT break loadingINFO
ios-framework-has-modulemapios/<framework>.framework/Modules/module.modulemap present — build hygiene only (same reason); not runtime-requiredINFO
ios-moduleClass-matchentrypoints.ios.moduleClass is the documented Obj-C class (RCT<Pascal>Module) and, when source is reachable, the .h/.m class name matches itWARNING
ios-moduleName-matcheswhen source is reachable, the .m declares + (NSString *)moduleName returning receivers[].nativeModule and does not use RCT_EXPORT_MODULE(...) (wrap plugins are registered by the runtime, not +load)CRITICAL

(Deep Mach-O introspection — Dynamic-vs-Static, dead-code-stripping, weak React link — is owned by /build-ios-binary at build time; here we verify the shipped framework is what the loader + wrap CI ingest.)


Step 7 — Category F: Source-to-receiver contract (when the repo is reachable)

These need the extension source. If the audit runs in the control repo (the bundle's source tree is present), run them; else mark each SKIPPED — source not reachable and note that the build skills already asserted them at build time.

CheckAssertsSeverity
src-getName-matchesthe Kotlin module's getName() literal == receivers[].nativeModule — mismatch = native module 'X' not loaded on device (compiles fine; hardest to catch)CRITICAL
src-methods-annotatedevery receivers[].methods[] entry has a matching @ReactMethod (Android) / RCT_EXPORT_METHOD (iOS)CRITICAL
src-package-fqn-matchesthe ReactPackage FQN == entrypoints.android.packageClassCRITICAL
src-package-no-arg-ctorthe <Pascal>Package (Android) has a public no-arg constructor — the runtime uses getDeclaredConstructor().newInstance(); a primary-constructor parameter list (class <Pascal>Package(...)) makes the plugin silently fail to load (Loaded 0 plugin package(s)). iOS analogue: the module class instantiates via [cls new] (no custom arg-ed initializer).CRITICAL
src-ios-module-no-arg-init(iOS) the module class has no arg-ed-only initializer — the player does NSClassFromString(moduleClass) → [cls new], so a class whose only initializer takes arguments won't instantiate and is skipped at load (§5b).CRITICAL
src-ios-requires-main-queue(iOS) if + (BOOL)requiresMainQueueSetup is declared, it returns NO (returning YES forces main-thread setup at launch and, with any heavy/throwing init, stalls or crashes startup — §5b).WARNING
src-method-single-map-parameach @ReactMethod / RCT_EXPORT_METHOD takes one ReadableMap/NSDictionary request param (then the Promise/resolver) — the wrap proxy spreads the PCF's args:[request] positionally, so a method with multiple positional params won't receive its payload (a no-arg op taking only the Promise is fine).WARNING
pcf-composite-key-matches-receiverif a sibling PCF (pcf/<…>/index.ts) declares COMPOSITE_KEY = "<name>/<receiver>" (or binds a ReceiverKey), it MUST equal <manifest.name>/<receivers[].name> — a mismatch means the PCF dispatches to a receiver the manifest never registers, failing on first call (real bug: PCF→Snapshot, manifest→DeviceInfoExtension).WARNING
pcf-uses-sendasyncif a sibling PCF (pcf/<…>/index.ts) is present, it MUST dispatch via window.PowerApps.NativeExtension.sendAsync(...) and MUST NOT call cordova.exec (or any cordova.*) directly. A direct cordova.exec call is the CRITICAL defect: the raw cordova global is not exposed to the PCF sandbox, so the call is a silent no-op on device (worst on Android) — it passes zip/manifest/DEX/key checks and only fails on-device. Fix: route through the host-injected sendAsync global (format §2).CRITICAL
pcf-envelope-is-raw-objectif a sibling PCF is present, the sendAsync payload MUST be a raw { method, args: [...] } object — the PCF must NOT pre-JSON.stringify it (sendAsync stringifies internally). A sendAsync(key, JSON.stringify({...})) double-encodes → the proxy JSON.parse yields a string, not {method,args} → BRIDGE_FAILED. Fix: pass the object literal, not a stringified one.CRITICAL
pcf-args-is-arrayif a sibling PCF is present, the inner args inside the sendAsync payload is a JSON array (args: [request]), not a bare object — after parsing, the wrap proxy does Array.isArray(parsed.args) ? parsed.args : [] and drops a non-array, so the native method gets no payload (format §2).WARNING
pcf-has-ambient-dtsif a sibling PCF is present, it ships a local PowerAppsNativeExtension.d.ts ambient declaration (typing window.PowerApps.NativeExtension.sendAsync) rather than importing a host SDK package — keeps the PCF host-agnostic.INFO
src-no-ReactModule-annotationthe module has no @ReactModule annotation (incompatible with DexClassLoader — format §5)CRITICAL
src-ctor-no-throwable-sideeffectsScope the scan to the module's construction closure, not the whole file — the primary/secondary constructor(s), the Kotlin init{} block(s), property initializers that run at construction (private val x = …), PLUS any private function they call (follow one level of foo() / this.foo()). Within that closure, flag: (a) register*Callback(…, null) or a bare Handler() / Handler(...) with no explicit Looper; (b) any side-effecting call (register*/add*Listener/observe/getSystemService+use/file or network I/O/runBlocking) not inside a try { } catch. @ReactMethod bodies are OUT of scope (they run per-call, not at construction). The module is constructed eagerly at bridge startup on a possibly Looper-less thread — an uncaught throw crashes the host at launch, before any UI (format §5). Fix: defer to lazy first-call init, pass Handler(Looper.getMainLooper()), wrap unavoidable init in try/catch. iOS analogue: apply the same scan to a throwing/heavy -init (instantiated eagerly via [cls new], §5b). Heuristic — the definitive catch is /test-native-extension Layer 1's load/init readiness asserts (static) + Layer 5's runtime launch crash-scan.WARNING
pcf-response-unwraps-messageif a sibling PCF is present, its sendAsync success path unwraps the wrap response container ({isUpdate, message:"<json>"}) — i.e. calls an extractResponse-style helper (parse result.data + probe message), not a bare single JSON.parse. A single-parse-only handler lands on the container and fails every call with UNEXPECTED_PAYLOAD though native succeeded (format §2).WARNING
src-promise-always-settledeach @ReactMethod / RCT_EXPORT_METHOD that takes a Promise / resolver+rejecter contains at least one promise.resolve / promise.reject (or resolve(...) / reject(...)) on a reachable path. A Promise-taking method that can return without settling leaves the maker with a hung control and no code/message.WARNING
src-listener-releasedeach register* / add*Listener / observe / acquired manager in the module has a matching release (unregister* / remove*Listener / .close() / .release()) in invalidate() / onCatalystInstanceDestroy() / teardown. A registered callback with no release leaks and can fire into a dead module.WARNING

Step 8 — Report + route fixes

Print the verdict as a visible fenced block (shared-instructions §9.1). Prefix each severity per shared-instructions.md §9.3 — 🔴 CRITICAL, 🟡 WARNING, 🔵 INFO, 🟢 READY — keeping the word so meaning survives a no-emoji terminal:

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 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
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

(When there are CRITICALs, the verdict line reads 🔴 and the last line is 🔴 ✗ NOT READY — fix the CRITICAL findings above.)

Each non-clean finding carries Fix: with the concrete next action, routed to the owning skill (never a zip patch):

  • Manifest rule / leakage field → "edit + re-run /generate-ppmplugin-manifest, then /assemble-ppmplugin."
  • DEX leakage / missing class → "the Android source pulled in SDK symbols — strip the import; re-run /build-android-binary then /assemble-ppmplugin."
  • Zip stray-file / structure → "re-run /assemble-ppmplugin."

Then surface next steps via AskUserQuestion (shared-instructions §9.1):

  • Clean (0 CRITICAL): offer Re-run /generate-ppmplugin-manifest (only if a WARNING points there) / Stay — I'll upload manually (Stage 3 is not yet a skill). When the user picks a Run /… option, invoke it via the Skill tool in the same turn (§8 + §9.1 "execute, don't describe").
  • Dirty (≥1 CRITICAL): offer to jump to the owning skill of the first CRITICAL — Run /generate-ppmplugin-manifest or Run /build-android-binary — then re-assemble + re-audit. Invoke on selection.

Update the ## ppmplugin (third-party controls) block in .extension-state.md: Audit: <name>.ppmplugin <ISO timestamp> — <CRITICAL>C/<WARNING>W (READY | BLOCKED).

Return code:

  • DONE — 0 CRITICAL (clean, or warnings only ⇒ note them). The bundle passes local verification.
  • BLOCKED: audit found <N> CRITICAL — see findings — refuse the READY verdict; do not claim it's uploadable.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

Activates and provisions a Power Pages website in a Power Platform environment via the Power Platform REST API. Use when the user wants to activate, provision, turn on, or enable a Power Pages website or portal.

日本語の概要は準備中です。原文の説明を表示しています。

microsoft/power-platform-skills9892026年10月11日 更新

Integrates Power Pages generative-AI summarization APIs (PREVIEW) into a Single Page Application (SPA) site — the Search Summary API and the Data Summarization API — on any record-detail or list page. Generates per-target service code (CSRF-handled) and AI site settings; delegates Web API settings, table permissions, and web roles to `/integrate-webapi` and `/create-webroles`. Use whenever a user wants AI/Copilot output that condenses Dataverse content on a Power Pages site — an AI summary, AI-generated overview or "key insights" across a record or list, a search-results summary, a case/incident summary, or recommendation-chip refinement — even when phrased as "AI-generated paragraph", "insights", or "overview". Do NOT use for: generative pages in model-driven apps (use the model-apps `genpage` skill), Copilot Studio agents/chatbots, summarizing documents or PDFs, Power BI dashboards, plain keyword search with no AI summary, or plain Dataverse CRUD (use `/integrate-webapi`).

日本語の概要は準備中です。原文の説明を表示しています。

microsoft/power-platform-skills9892026年10月11日 更新

Adds Azure DevOps connector to a Power Apps code app. Use when querying work items, creating bugs, managing pipelines, or making ADO API calls.

日本語の概要は準備中です。原文の説明を表示しています。

microsoft/power-platform-skills9892026年10月11日 更新

Internal implementation skill invoked by /add-native for camera, image picker, barcode scanner, QR scanner, and camera/gallery Dataverse artifact workflows.

日本語の概要は準備中です。原文の説明を表示しています。

microsoft/power-platform-skills9892026年10月11日 更新

Integrates Power Automate cloud flows into a Power Pages site. Lists available flows, suggests relevant ones based on intent, identifies scenarios and web roles, creates metadata files, and generates client-side code to call flows. Handles both new flow registration and adding already-registered flows to additional pages without re-creating metadata. Use when the user wants to add, connect, register, or link a Power Automate cloud flow to their site.

日本語の概要は準備中です。原文の説明を表示しています。

microsoft/power-platform-skills9892026年10月11日 更新

Adds any Power Platform connector to a Power Apps code app. Generic fallback for connectors not covered by a specific skill.

日本語の概要は準備中です。原文の説明を表示しています。

microsoft/power-platform-skills9892026年10月11日 更新

microsoft のスキルをすべて見る

このスキルの問題を報告する