{"id":11550,"plugin_id":"plugin_asdk_app_6a4d8a8bafb48191a99b4a581d74b190","kind":"skill","collection_source":null,"comparison_source":null,"observed_at":"2026-09-30T22:59:44.528Z","digest":"f9f87f6d2e5c1840feb69d1915f7ee764f1670b222ee5db55433ca96572db40f","against":null,"payload":{"name":"azure-integration-verify","description":"Verify Azure Marketplace integration end to end by reading expected values from the Suger Console settings page and then walking Azure Portal and Microsoft Partner Center step by step with browser tools.","included_files":[],"skill_md_contents":"---\nname: azure-integration-verify\ndescription: \"Verify Azure Marketplace integration end to end by reading expected values from the Suger Console settings page and then walking Azure Portal and Microsoft Partner Center step by step with browser tools.\"\n---\n\n# Verify Azure Marketplace Integration\n\nUse browser frontend tools only. Follow the exact step order below. Read and record every value as you go.\n\n## Execution Contract\n\nThis flow has exactly **three** allowed reasons to pause, and exactly **one** allowed reason to abort. Everything else continues without stopping.\n\n- **Pause only for sign-in, and only if the page actually shows a sign-in form.** Steps 4 and 13 may land on a Microsoft sign-in screen. If the page is already the Azure Portal home or Partner Center dashboard, the user is already signed in — do not pause, do not ask, proceed. The only reason to pause is an active login form on screen.\n- **Pause for a slow-loading page**, only after at least five `extract_page` retries on the same step (Steps 13 and 14). Never pause on the first retry.\n- **Pause in Step 6 only to ask for the AD application name**, and only when the default-name search returned no row. This is the single branch in the flow where the agent asks the user a content question. It is bounded: one question, one answer, then resume.\n- **Abort only in Step 6**, and only when the Azure AD application still cannot be found after the user-provided name has also been paginated through the full `All applications` list. That is the only reason to abort.\n- **Every other outcome is a finding, not a stop.** Missing data, empty list, `accountEnrollments` absent, `Tenant` entry absent, wrong value, unchecked box, missing role, failed check, tool error — record it and move to the next step.\n\nAll browser tool calls used by this flow are pre-approved:\n\n- `navigate` to any URL listed in a step is pre-approved. Call it directly. Do not ask the user before calling `navigate`. Do not stop on `navigate`.\n- `extract_page`, `click`, `fill`, `list_tabs`, `get_ui_context` are all pre-approved.\n- Do not invent new reasons to stop (\"to be safe\", \"to confirm with the user\", \"just in case\", \"since the data is missing\"). The only valid stops are the three pauses and the one abort listed above.\n\n## Pausing With Choices\n\nWhenever you must pause (active sign-in screen, or a page still not loaded after retries), emit a message with **explicit short choices**, not a free-form question. The user should be able to continue by picking one option, not by typing a sentence.\n\nUse this template, but **omit any choice the step does not support**. Emit only the choices that the current step explicitly permits — do not include `Skip` as a no-op, and do not invent new choices. Include a bullet in the emitted prompt only when the per-step availability list below permits it; do not include the per-step commentary itself in the user-facing message.\n\n> `<what is blocking>`. Choose one:\n> - `Continue` — `<condition that lets the flow resume>`\n> - `Skip` — skip this check and move on\n> - `Abort` — stop the verification now\n\nPer-step choice availability:\n- Step 4 (sign-in): emit only `Continue` / `Abort`. No `Skip` (rationale in Step 4).\n- Step 13 (sign-in): emit only `Continue` / `Abort`. No `Skip` (same rationale).\n- Step 14 (slow-load): emit `Continue` / `Skip`. No `Abort` (the step is optional by design).\n- Step 6 (ask-for-app-name): this is a content question, not a blocking prompt. Emit a picklist of visible app names plus a free-text option. Do not use the `Continue` / `Skip` / `Abort` template here.\n\nWhen the user replies:\n- `Continue` — call `extract_page` again and resume the step.\n- `Skip` — record the current check as `skipped` and move to the next step.\n- `Abort` — jump straight to the Reporting Format with results gathered so far.\n\nNever say \"please sign in and let me know when you are done\" or ask the user to type a status update. Always offer structured choices scoped to what the step allows.\n\n## Core Rules\n\n- Start each major step with `get_ui_context` or `list_tabs` so you know which page and tab you are operating on.\n- Use `extract_page` before direct page actions such as `click`, `fill`, or `select`.\n- Call `extract_page` again after every important transition (navigation, popup open, tab switch, pagination).\n- Azure Portal and Microsoft Partner Center pages often load slowly. If `extract_page` returns a skeleton, a loading spinner, or a list that is clearly shorter than expected, wait a few seconds and call `extract_page` again. Retry at least five times. **Do not flag a check as failed while the page still looks like it is loading.** If after five retries the page is still not loaded, emit a `Continue` / `Skip` choice prompt (see \"Pausing With Choices\") instead of flagging the check.\n- The only destinations this flow uses are Suger Console, `portal.azure.com`, and `partner.microsoft.com`. Inside that set, `navigate` needs no confirmation.\n- If Azure Portal or Microsoft Partner Center redirects to a Microsoft sign-in screen, wait for the user to complete sign-in. Do not fill credentials yourself. After the user signs in, call `extract_page` again before continuing.\n- Record every value you read (IDs, URIs, checkbox states, role names, expiry timestamps). You will reuse them in the final report.\n\n## Step 1: Open The Suger Integrations Settings Page\n\n1. Use `get_ui_context` to determine whether the current environment is dev or prod from the hostname.\n2. Use `navigate` to open the integrations settings page for that environment:\n   - Dev: `https://console.dev.suger.io/settings?tab=integrations`\n   - Prod: `https://console.suger.io/settings?tab=integrations`\n3. Call `extract_page`.\n\n## Step 2: Open The Azure Marketplace Integration Details\n\n1. Locate the Azure Marketplace integration card on the settings page.\n2. `click` the card's `Details` button to open the details popup.\n3. In the popup, `click` the `Expand All` button.\n4. Call `extract_page` to read the collapsed, structured details.\n\n## Step 3: Record The Expected Tenant ID From accountEnrollments (Optional, Never Blocks)\n\nThis step is optional. It only affects Step 14. It never blocks the flow. Whether you find a value or not, continue to Step 4 immediately — without asking the user, without warning, without extra commentary.\n\n1. In the details popup you just read, look for an `accountEnrollments` list.\n2. If an entry with `typeName = Tenant` exists, record its `id` field as `expectedTenantID`.\n3. In every other case (no `accountEnrollments`, empty `accountEnrollments`, no entry with `typeName = Tenant`, or the field cannot be read at all), set `expectedTenantID = none`.\n\n`none` is a normal, expected outcome. It is **not** a failure. Do not stop. Do not ask the user. Proceed to Step 4.\n\n## Step 4: Open Azure Portal\n\n1. Use `navigate` to open `https://portal.azure.com/#home`.\n2. Call `extract_page`.\n3. Decide which of these two states the page is in:\n   - **Already signed in** — the page shows Azure Portal home (dashboard, services grid, search bar at top). Proceed directly to Step 5. Do not ask the user anything.\n   - **Sign-in form** — the page is a Microsoft login screen. Emit a `Continue` / `Abort` choice prompt (see \"Pausing With Choices\"). On `Continue`, call `extract_page` again and proceed.\n4. Do not ask the user to \"please log in\" when the page is already Azure Portal home. The signed-in case is the common case.\n5. `Skip` is intentionally not offered at this pause — without Microsoft sign-in, every remaining step would fail, so skipping would only produce a misleading all-skipped report. The user either signs in (`Continue`) or aborts the verification (`Abort`).\n\n## Step 5: Open App Registrations\n\n1. In the Azure Portal top search bar, `fill` the value `App registrations`.\n2. `click` the `App registrations` entry in the search results. The expected destination URL is:\n   - `https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade`\n3. Call `extract_page`.\n\n## Step 6: Find The Suger Azure AD Application\n\n1. On the App registrations blade, `click` the `All applications` tab.\n2. In the search box below the tab bar, search for `Suger Marketplace Connection` (this is the common default name; the user may have registered their Suger Azure AD application under a different name).\n3. Call `extract_page`.\n4. If a row named `Suger Marketplace Connection` appears, record its `Application (client) ID` as `clientID` (a GUID like `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`) and continue to Step 7. Always record the GUID you actually read from the page — never substitute any example value that appears in this document.\n5. If the search returns no row, use the Step 6 pause (see \"Execution Contract\" — this is the single allowed content-question pause):\n   - Pause and ask the user: \"I can't find an Azure AD application named `Suger Marketplace Connection`. What name did you register the Suger integration under?\" Show the visible application names from the current page as picklist choices plus a free-text option.\n   - Re-run the search with the user's answer. If found, record `clientID` and continue to Step 7.\n   - If the search still returns nothing, paginate through the full `All applications` list starting from page 1, calling `extract_page` after each page change, scanning for the user's chosen name.\n6. If the application still cannot be found after scanning the full list:\n   - Tell the user: \"No Azure AD Application named `<user-supplied-name>` was found in this tenant. If you registered the app under a different name, re-run the verification and provide the correct name when prompted.\"\n   - Abort the remaining verification steps and jump to the Reporting Format with the results gathered so far. This flow accepts exactly **one** user name attempt per run; re-prompting indefinitely risks an interaction loop, so on abort we let the user restart the skill cleanly.\n\n## Step 7: Open The Application Overview\n\n1. Confirm `clientID` is set from Step 6. If for any reason the value is unset or lost (long pause, retry, session drop), restart Step 6 before proceeding — do not carry on with an unknown `<clientID>` since Steps 7, 9, and 11 all depend on it for URL substring matching.\n2. `click` the matching application row (expected name `Suger Marketplace Connection`, or the user-chosen name from Step 6).\n3. The destination URL should contain the substring `ApplicationMenuBlade/~/Overview/appId/<clientID>` (with `<clientID>` replaced by the GUID recorded in Step 6). Do not require exact URL equality — Azure Portal appends query parameters (`isMSAApp~/false`, tenant hints, session state) that vary. A substring match on the blade path and `appId/<clientID>` segment is authoritative.\n4. Call `extract_page`.\n\n## Step 8: Verify Supported Account Types\n\n1. On the Overview page, read the `Supported account types` field.\n2. Record the value. The expected value is any multi-tenant configuration. Azure Portal has renamed this field across UI revisions, so accept any of the following labels as a pass:\n   - `Multiple organizations`\n   - `Accounts in any organizational directory`\n   - `Accounts in any organizational directory (Any Microsoft Entra ID tenant - Multitenant)`\n   - `Multitenant`\n   - `Accounts in any organizational directory and personal Microsoft accounts`\n   - `Accounts in any organizational directory (Any Microsoft Entra ID tenant - Multitenant) and personal Microsoft accounts (e.g. Skype, Xbox)`\n3. Only flag as failure when the value indicates a **single tenant** configuration, or a **personal-account-only** configuration. In practice:\n   - Fail on: `Single organization`, `Accounts in this organizational directory only`, `Single tenant`, `Accounts in this organizational directory only (Suger Inc only - Single tenant)`.\n   - Fail on: `Personal Microsoft accounts only` (personal MSA without any organizational directory).\n   - Pass on any label that includes the substring \"any organizational directory\" or \"Multiple organizations\" or \"Multitenant\", regardless of whether it also includes \"personal Microsoft accounts\".\n   Continue to Step 9 either way.\n\n## Step 9: Verify The Redirect URI\n\n1. On the Overview page, locate the `Redirect URIs` field and `click` its value link.\n2. The destination URL should contain the substring `ApplicationMenuBlade/~/Authentication/appId/<clientID>` (with `<clientID>` replaced by the GUID recorded in Step 6). Use substring match, not equality.\n3. Call `extract_page`.\n4. Verify that a Web platform redirect URI is configured with the value:\n   - `https://api.suger.cloud/public/integration/azure/authCode`\n5. Record **every** redirect URI shown (all platforms, all entries — Web, SPA, Mobile), as a comma-separated list for the report. Then mark whether the expected Suger URI above is present among them.\n\n## Step 10: Verify Implicit Grant And Hybrid Flow Settings\n\nRemain on the Authentication blade from Step 9.\n\n1. Scroll to (or select) the `Implicit grant and hybrid flows` section. In the Azure Portal this section lives on the same Authentication blade; it does not have its own URL.\n2. Record the checked state of each of the following:\n   - `Access tokens (used for implicit flows)`\n   - `ID tokens (used for implicit and hybrid flows)`\n3. Both should be checked. Flag any unchecked option as a failure but continue.\n\n## Step 11: Open API Permissions\n\n1. In the left navigation of this app registration, `click` `API permissions`.\n2. The destination URL should contain the substring `ApplicationMenuBlade/~/CallAnAPI/appId/<clientID>` (with `<clientID>` replaced by the GUID recorded in Step 6). Use substring match, not equality.\n3. Call `extract_page`.\n\n## Step 12: Verify API Permissions\n\n**Match by App ID, not by display name.** Display names drift between Azure Portal revisions (e.g. the `Add a permission` dialog shows `MicrosoftPartner` as one word while `Configured permissions` shows `Microsoft Partner` with a space). App IDs are stable and are shown in the \"API / Permissions name\" column when you expand each row.\n\n1. Verify that the configured permissions list contains all three of these API rows, identified by their App IDs:\n   - **Row A**: App ID `4990cffe-04e8-4e8b-808a-1175604b879f` — display name `MicrosoftPartner` or `Microsoft Partner` (both acceptable)\n   - **Row B**: App ID `fa3d9a0c-3fb0-42cc-9193-47c7ecd2edbd` — display name `Microsoft Partner Center`\n   - **Row C**: App ID `fabfbdc4-5751-471c-ac43-3826fa1afc31` — display name `Microsoft Partner Center` (yes, same display name as Row B — the App ID is the only way to tell them apart)\n2. For each of Rows A, B, C record and verify:\n   - the permission (scope) is `user_impersonation`\n   - the status starts with `Granted` — accept both `Granted` alone (some tenants without a display name render it this way) and `Granted for <directory-name>` (e.g. `Granted for Suger Inc`, `Granted for Contoso Corp`). Flag as failure only when the status is `Not granted`, empty, `(No consent)`, or contains an error indicator.\n3. Flag any missing API (by App ID), wrong scope, or non-granted status as a failure but continue.\n4. If the App ID column is not visible by default on this blade, `click` the row to expand it — the App ID appears in the expanded detail pane. If expanding fails AND the two rows share the display name `Microsoft Partner Center`, **display-name fallback cannot disambiguate them**: record both report rows (the ones keyed by App IDs `fa3d9a0c-...` and `fabfbdc4-...`) as `fail` with observed value `App ID column unavailable; cannot disambiguate the two same-named Microsoft Partner Center entries`. Display-name fallback is acceptable only for the `MicrosoftPartner` / `Microsoft Partner` row (App ID `4990cffe-...`), which has a unique display name.\n\n## Step 13: Verify Partner Center User Management Roles\n\n1. Use `navigate` to open:\n   - `https://partner.microsoft.com/en-us/dashboard/account/v3/usermanagement#users`\n2. Call `extract_page`. If the page is already the Partner Center dashboard, proceed directly. Only if the page is an active Microsoft sign-in form, emit a `Continue` / `Abort` choice prompt (see \"Pausing With Choices\"). On `Continue`, call `extract_page` again and proceed.\n3. `click` the `Microsoft Entra applications` tab. After the click, call `extract_page` and confirm the URL fragment has switched to `#azureapps` (or another Entra-apps-specific fragment) and the table now lists applications rather than users. If the tab click silently failed (URL still `#users`, or the list still shows human users), retry the click up to 3 times. If the tab never switches, record Step 13 as a failure in report row 9 with observed value `tab switch failed; roles unreadable` and continue to Step 14 — do not attempt role matching against a user list.\n4. Locate `Suger Marketplace Connection` (or the Step-6 user-chosen name) in the list. Do not use the search box. Paginate from page 1 forward, calling `extract_page` after each page change, until the entry is found or the list ends. If two rows share the name, focus on the row whose type is `Microsoft Entra Apps`.\n5. Read the roles assigned to that entry and record them as a raw list.\n6. Verify that all of the following roles are present. Role matching rules (applied to every required role):\n   - Normalize the observed role string before matching: trim surrounding whitespace, replace non-breaking spaces (`U+00A0`) with regular spaces (`U+0020`), and collapse runs of internal whitespace (`\\s+`) down to a single space so `Manager ( Commercial Marketplace )` normalizes to `Manager (Commercial Marketplace)`.\n   - Compare case-insensitively.\n   - Reject any program suffix unless explicitly whitelisted below.\n   For the `Manager` role, accept only Commercial-Marketplace-compatible labels. After the whitespace normalization above, match the regex `/^Manager(\\s*\\((Commercial Marketplace|Windows|Microsoft 365|Azure)\\))?$/i`. Do **not** accept `Manager (Referrals)`, `Manager (CSP)`, or `Manager (<other>)` — those belong to unrelated Partner Center programs and do not grant Azure Marketplace management permissions.\n   For the other four roles, match the regex `/^<Role>$/i` — no program suffix is permitted. `Developer (Referrals)`, `Finance Contributor (CSP)`, etc. are rejected; these four roles do not receive program suffixes in the Partner Center UI.\n   Required roles:\n   - `Manager` (match by the Manager regex above)\n   - `Developer` (exact, case-insensitive)\n   - `Business Contributor` (exact, case-insensitive)\n   - `Finance Contributor` (exact, case-insensitive)\n   - `Marketer` (exact, case-insensitive)\n7. Flag any missing role as a failure but continue. If `Manager` exists but only with a rejected suffix (e.g. `Manager (Referrals)`), record the observed suffix in the report so the user can see why it failed.\n8. If the entry cannot be found after a full pagination pass, record this as a failure and continue.\n\n## Step 14: Verify Tenant Association\n\nOnly run this step if `expectedTenantID` recorded in Step 3 is not `none`. Otherwise, record this step as `skipped` because `accountEnrollments` had no `Tenant` entry to compare against.\n\n1. Use `navigate` to open:\n   - `https://partner.microsoft.com/en-us/dashboard/account/v3/tenantmanagement#commercial`\n2. **Wait for the Commercial tenants list to finish loading before doing anything else.** This list is slow. Do not search and do not draw conclusions until the list is confirmed loaded.\n   a. Call `extract_page`.\n   b. If the page shows a loading spinner, `Loading...`, an empty list, a skeleton, or clearly fewer rows than a commercial account would have, wait a few seconds and call `extract_page` again.\n   c. Repeat at least five times before drawing any conclusion.\n   d. If after five retries the list is still not visibly loaded, emit a `Continue` / `Skip` choice prompt (see \"Pausing With Choices\"). On `Continue`, call `extract_page` again and treat the list as loaded. On `Skip`, record this step as `skipped` with reason `load timeout`.\n3. Only once the list is confirmed loaded, search for `expectedTenantID` (a GUID). The Commercial tenants list shows each tenant by **name** and **domain** by default, with the tenant's Directory/Tenant ID typically visible only after expanding the row or opening a detail pane.\n   a. If the page has a search input that accepts GUIDs, use it with `expectedTenantID`.\n   b. Otherwise, paginate the list. For each row, expand it (or open its detail pane) and look for a `Directory ID` / `Tenant ID` field. Match against that GUID field only — do not match on tenant name or domain (those can collide or drift).\n4. Record whether `expectedTenantID` is found, and the tenant-name/domain of the matching row if found.\n5. Flag as failed **only** when the list is loaded and the ID is not present in any row's Directory ID / Tenant ID field. Never flag as failed while the list is still loading.\n\n## Step 15: Report\n\nProduce a concise verification summary using the Reporting Format below. The primary output is a Markdown table. Keep prose minimal.\n\n## Reporting Format\n\nOutput in this exact shape.\n\n**Header**\n\n- Environment: dev or prod\n- Azure AD application: name and `clientID`\n- Expected tenant ID: value or `none`\n\n**Results table**\n\n| # | Check | Status | Observed |\n|---|---|---|---|\n| 1 | Azure AD application (Step 6) | pass / fail | name + clientID |\n| 2 | Supported account types (Step 8) | pass / fail | observed value |\n| 3 | Redirect URIs (Step 9) | pass / fail | all redirect URIs, each in its own backticks, separated by `,` with no padding (e.g. `` `uri1`,`uri2` ``) to avoid Markdown table collisions with `\\|` inside the URIs; explicit note whether the expected `https://api.suger.cloud/public/integration/azure/authCode` is present |\n| 4 | Access tokens, implicit flow (Step 10) | pass / fail | checked / unchecked |\n| 5 | ID tokens, hybrid flow (Step 10) | pass / fail | checked / unchecked |\n| 6 | API: `MicrosoftPartner` (App ID `4990cffe-04e8-4e8b-808a-1175604b879f`, Step 12) | pass / fail | scope + grant status |\n| 7 | API: `Microsoft Partner Center` (App ID `fa3d9a0c-3fb0-42cc-9193-47c7ecd2edbd`, Step 12) | pass / fail | scope + grant status |\n| 8 | API: `Microsoft Partner Center` (App ID `fabfbdc4-5751-471c-ac43-3826fa1afc31`, Step 12) | pass / fail | scope + grant status |\n| 9 | Partner Center roles (Step 13) | pass / fail | present roles, comma-separated (for a rejected `Manager (<suffix>)`, include the suffix in the observed value); or the literal `tab switch failed; roles unreadable` when Step 13 could not switch to the `Microsoft Entra applications` tab |\n| 10 | Tenant in Commercial tenants (Step 14) | pass / fail / skipped | matching tenant name + domain when found, `not found` when not, or skip reason |\n\n(The Step 3 `expectedTenantID` value is included in the Header section above this table, not as a pass/fail row, because it is purely informational.)\n\nStatus vocabulary:\n- `pass` — check passed.\n- `fail` — check failed. Add one bullet in the Notes section below.\n- `skipped` — check was not run (for example Step 14 when `expectedTenantID` was `none`, or a load-timeout skip).\n\n**Notes** (include only if the table has any `fail` or `skipped` row)\n\nOne bullet per non-`pass` row, formatted as:\n\n- `<check>: expected <X>, got <Y>. <one-line why-it-matters>. Fix: <one-line action>.`\n\nOr for skipped rows:\n\n- `<check>: skipped. Reason: <why>.`\n\nException: when Step 14 (Tenant in Commercial tenants) is skipped because `expectedTenantID = none`, omit the Notes bullet for this row. The Header line `Expected tenant ID: none` already conveys the skip reason, and repeating it as a Notes bullet is noise in the common case where the integration simply does not expose `accountEnrollments`.\n\n**Conclusion** (one line + caveat)\n\n- If every row is `pass`: `Azure Marketplace integration setup appears correctly configured.`\n- Otherwise: `<N> failures, <M> skipped. See notes above.`\n\n**Completeness caveat** (always include as the final line of the report)\n\nThis skill verifies integration **setup**: the Azure AD application, Partner Center role assignments, and tenant association. It does **not** verify product **Technical Configuration**. Per the Azure integration docs, entitlement retrieval additionally requires that at least one listed product in Microsoft Partner Center has its Technical Configuration (Azure AD Tenant ID, Azure AD Application Client ID, Landing Page URL, Connection Webhook) matching the Suger integration values. A `pass` on every row above is **necessary but not sufficient** — if the user has products listed and entitlements are still not flowing, direct them to check Technical Configuration on each product manually.\n\nKeep the report compact. Operational details belong in the per-failure Notes bullet, not in separate paragraphs.\n"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}