# Ensure Pipelines Host

> >-

- **Type:** Skill
- **Install:** `agentstack add skill-microsoft-power-platform-skills-ensure-pipelines-host`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [microsoft](https://agentstack.voostack.com/s/microsoft)
- **Installs:** 0
- **Category:** [Developer Tools](https://agentstack.voostack.com/c/developer-tools)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [microsoft](https://github.com/microsoft)
- **Source:** https://github.com/microsoft/power-platform-skills/tree/main/plugins/power-pages/skills/ensure-pipelines-host
- **Website:** https://aka.ms/ppskills

## Install

```sh
agentstack add skill-microsoft-power-platform-skills-ensure-pipelines-host
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

> **Plugin check**: Run `node "${PLUGIN_ROOT}/scripts/check-version.js"` — if it outputs a message, show it to the user before proceeding.

# ensure-pipelines-host

> **Scope:** When no host is bound to the source env, this skill detects any existing host (Custom or PE) for reuse, or — in `NoHost` state — offers three provisioning paths: a new **Platform Host** (recommended; idempotent, ~3–5 min); a new **Custom Host** (admin-only, ~5–10 min); or PPAC manual provisioning (fallback). Implementation details — endpoint names, template names, BAP audience — live in Phase 4.0 / 4.A / 4.C below; user-facing prose stays focused on outcomes.

Power Platform Pipelines need a **host environment** — a Dataverse environment with the *Power Platform Pipelines* managed solution installed, where pipelines, stages, run history, and artifacts live. The existing `setup-pipeline` and `deploy-pipeline` skills assume a host is already configured. This skill closes that gap.

## What we know (sources of truth)

This plan is grounded in three primary sources, in priority order:

1. **`useGetOrCreatePlatformEnvironment.v4.ts`** (Microsoft-internal client source — `power-platform-ux/packages/powerapps-appdeployment-ux/src/hooks/v4/`). Defines the exact HTTP contract for Platform Environment provisioning: endpoint, body, headers, polling.
2. **`ProjectHostProvider.tsx`** (same repo, `src/components/ProjectHostProvider/`). Defines the exact resolution order the Power Apps UI uses to determine which environment is the project host for a source environment. We mirror that order so this skill agrees with the UI.
3. **eng.ms `createcustompipelineshost`** (Microsoft-internal). Documents the Custom Host fast-path: a `D365_ProjectHost` org template that ships the Pipelines app pre-installed, callable through the standard environment-creation API.

Public Microsoft Learn (`learn.microsoft.com/power-platform/alm/{platform-host-pipelines, custom-host-pipelines, set-a-default-pipelines-host}`) is the user-facing description of the same flows; we cite it for behaviors users will recognize. HARs in `PipelinesDeployScenario.har` and `Pipelines.har` confirm the read-side calls.

## Three host shapes the tenant can be in

| Shape | How it got there | Where it lives | Org template |
|---|---|---|---|
| **Platform Host (PE)** | Auto-provisioned by `getOrCreate` BAP call (or as a side-effect of first navigation to the Pipelines page in `make.powerapps.com`). Hidden from the env picker. One per tenant. | Microsoft-managed Dataverse env in tenant's home geo | `D365_1stPartyAdminApps` |
| **Custom Host** | Created by an admin via PPAC `Deployments → New custom host`, or via the standard env-create API with the `D365_ProjectHost` template, or by installing the Power Platform Pipelines app on an existing Dataverse env. | A regular Dataverse env in the tenant | `D365_ProjectHost` (or app-installed-onto-existing-env) |
| **No host bound to source env** | Tenant has not used Pipelines from this env. | — | — |

The current `discover-pipelines-host.js` only checks the tenant-level `DefaultCustomPipelinesHostEnvForTenant` setting. That's one signal of many. This skill implements the full resolution order.

## Resolution order (mirrors `ProjectHostProvider.tsx`)

This is the load-bearing decision tree. It is what the Power Apps UI does. We replicate it so the skill agrees with the UI.

```
┌─────────────────────────────────────────────────────────────────────┐
│ 1. GetOrgDbOrgSetting('ProjectHostEnvironmentId') on source env     │
└──────────────────────────┬──────────────────────────────────────────┘
                           │
            ┌──────────────┴───────────────┐
            │ value present                │ value empty
            ▼                              ▼
┌───────────────────────┐         ┌────────────────────────────┐
│ 2. Resolve env via    │         │ 5a. Tenant-wide search:    │
│    BAP GET            │         │   list envs + per-env      │
│    /environments/{id} │         │   /deploymentpipelines     │
└───────┬───────────────┘         │   probe.                   │
        │                         │                            │
   environmentSku?                │   - 1 Custom Host found →  │
        │                         │     AvailableUnboundCustom │
   ┌────┴────────────┐            │     (3.C-pre)              │
   │ Platform        │            │   - >1 Custom Hosts →      │
   │                 │            │     MultipleUnboundCustom  │
   │                 │            │     (3.C-pre')             │
   │                 │            │   - PE only →              │
   │                 │            │     PlatformHostExists-    │
   │                 │            │     Unbound (3.C-pre'')    │
   │                 │            │   - none → NoHost (3.C)    │
   │                 │            │                            │
   │                 │            │ 5b. Decision tree paths    │
   │                 │            │   for create-new (3.C):    │
   │                 │            │   - Platform getOrCreate   │
   │                 │            │     (fast-path, no admin)  │
   │                 │            │   - Custom D365_ProjectHost│
   │                 │            │     (fast-path, admin)     │
   │                 │            │   - Manual app install     │
   │                 │            │   - Manual PPAC create     │
   │                 │            └────────────────────────────┘
   ▼                 │
┌──────────────┐     │
│ 3. Check     │     │ environmentSku ≠ Platform (Custom Host)
│  Default-    │     ▼
│  Custom-     │   ┌──────────────────────────────┐
│  Pipelines-  │   │ 4. Use the Custom Host       │
│  HostEnv-    │   │    directly. Skip default-   │
│  ForTenant   │   │    custom check.             │
└──────┬───────┘   └──────────────────────────────┘
       │
   ┌───┴────────────────────────┐
   │ admin set a custom default │
   │                            │
   ▼                            ▼
┌─────────────────┐  ┌─────────────────────────┐
│ default ==      │  │ default !=              │
│ org setting?    │  │ org setting             │
│                 │  │                         │
│ → use default   │  │ → CannotRedirect ERROR  │
│   custom        │  │   (user locked to PE    │
└─────────────────┘  │   but admin overrode    │
                     │   at tenant scope)      │
                     └─────────────────────────┘

   if no admin default → use PE
```

Source: `ProjectHostProvider.tsx` lines 100–213 (orgSetting fetch → defaultCustomPipelinesHost fetch → finalProjectHostEnvironmentId resolution).

## What this skill does NOT do

These are deliberate non-goals (each based on a hard constraint or a destructive blast-radius — see *Design Constraints* below):

- **Does not silently provision anything.** Any action that creates an env or binds the source env to a host requires explicit user confirmation, with the tenant name + tenant ID echoed back. PE is tenant-singleton and admin-non-deletable, so the Phase 4.0 pre-call confirmation gate is the principal mitigation against wrong-tenant provisioning. The `getOrCreate` endpoint is idempotent — calling it on a tenant that already has a PE returns the existing one rather than creating a duplicate.
- **Does not call `Force Link`** to rebind an environment to a different host. Force Link is destructive (makers lose access to existing pipelines in the previous host) and is hidden behind a separate confirmation gate, only reachable when the user explicitly says "rebind".
- **Does not change the tenant-level `DefaultCustomPipelinesHostEnvForTenant` setting.** That setting is irreversible-adjacent (existing pipelines in the previous default become inaccessible — see `learn.microsoft.com/power-platform/alm/set-a-default-pipelines-host`). Out of scope.
- **Does not delete environments.**
- **Does not write `ProjectHostEnvironmentId` directly.** Binding is established through the documented Pipelines flow (creating a `deploymentenvironment` record in the host); writing the org setting directly bypasses validation.

## Auth strategy: PAC-first with BAP fallback (`--source auto`)

Read-side detection (Phase 2 resolution order, env list, env-by-id) defaults to `--source auto`:
1. If a BAP token is provided, **try BAP env-list / env-GET first** (richer data including `lastModifiedTime`, `permissions`, `tenantId`).
2. **On HTTP 401 or 403, fall back to `pac admin list --json`** via `pac-bap-shim.js`. PAC has its own first-party client-ID grants on BAP that Az CLI doesn't always inherit (verified 2026-04-28: `D365DemoTSCE53051106` demo tenant rejects Az tokens for BAP even with correct audience claims).
3. If no BAP token is provided at all, go straight to PAC.

The PAC shim returns BAP-shaped data; downstream code (sku filter, ranking, classification) is unchanged. Fields not provided by PAC (`tenantId`, `lastModifiedTime`, `permissions`, `isManaged`) come back as `null` — none are critical for host detection. PAC also doesn't surface Platform Hosts (PE) since `pac admin list` doesn't include Platform-sku envs; PE detection requires `--source bap` with a working BAP token.

**Write-side actions** (env-create POST in `provision-custom-host.js`, lifecycle op polling) still require BAP. Az CLI tokens with the right audience usually work for these even when env-list calls fail, because the BAP RP enforces different policy on actions than reads. If `provision-custom-host.js` returns 401, the user must register a service principal in the target tenant (or use the PPAC UI fallback path 4.C).

## Design Constraints

1. **JIT provisioning is required when a PE is selected — existing or freshly provisioned.** From `ProjectHostProvider.tsx` (line 232–240 comment): *"In the Platform Environment case, the user may not already be provisioned there, so BAP cannot discover it. So we'll use the org URL we retrieve from the getOrCreate call to make this first request so that user JIT can be triggered."* When Phase 2 detects an existing PE and the user accepts it (Phase 3.A) — or when Phase 4.0 provisions a new PE via `getOrCreate` — Phase 5's `WhoAmI` call against `instanceApiUrl` triggers JIT before any subsequent host op. (For Custom Host paths the caller has access by construction.)
2. **`CannotRedirect` is a real terminal state**, not a theoretical edge case. It happens when `ProjectHostEnvironmentId` (org setting on source env) points at PE but `DefaultCustomPipelinesHostEnvForTenant` (admin tenant setting) points elsewhere. The skill must detect this and surface it as a specific error — falling through silently would route pipeline ops at the wrong host.
3. **Admin-only Custom Host fast-path.** PPAC's `New custom host` flow is gated by `DeploymentHubCreatePipelinesHostForAdminsOnly` and shows only for Global / Power Platform / Dynamics admins (eng.ms doc). The BAP env-create API also needs the equivalent privilege. Non-admins get 403; the skill preflight-attestation-prompts and gracefully falls back to manual paths.
4. **404 from BAP env GET is ambiguous.** Returns 404 for *deleted*, *disabled*, *no-PE*, and *no-access* without distinguishing (`PowerPipelines_PE_Knowledge.md` §6.A). We never treat a single 404 as "no host exists" — we corroborate via list-environments and the org setting before acting.
5. **Each environment is bound to only one host at a time.** Rebinding requires Force Link, which is destructive in the previous host. Out of scope (see non-goals).
6. **The skill runs in user OAuth context** — same scope and audience the Power Apps UI uses. BAP calls use `https://service.powerapps.com/` audience.

> **Idempotency of `getOrCreate`** — the BAP `getOrCreate` endpoint is idempotent (existing PE returns 200 + `provisioningState === 'Succeeded'`; new PE returns 202 + lifecycle op). Phase 4.0 leverages this — calling getOrCreate on a tenant that already has a PE is safe and just returns the existing one. The `provision-platform-host.js` helper surfaces the distinction via an `alreadyExisted: true | false` flag in its return value (recorded in the `docs/alm/last-host-check.json` telemetry block as `platformHostAlreadyExisted`).

## Prerequisites

- PAC CLI logged in (`pac env who` succeeds)
- Azure CLI logged in (`az account show` succeeds)
- A source Dataverse environment URL (read from `powerpages.config.json` if invoked from a Power Pages project; passed as arg otherwise)
- For Phase 4 admin-only paths: caller has Global / Power Platform / Dynamics admin (skill detects and surfaces 403 cleanly if missing)

## Phases

### Phase 1 — Detect prerequisites and gather tenant context

**Create all tasks upfront at the start of this phase.**

Tasks to create:

1. "Check local cache and detect prerequisites"
2. "Run resolution order to find host"
3. "Confirm action with user"
4. "Execute chosen path"
5. "JIT-provision and verify host"
6. "Write host-check artifact"

Steps:

0. **Local cache fast-path.** If `docs/alm/last-host-check.json` exists AND `Date.now() - Date.parse(checkedAt)  🚦 **Gate (consent · ensure-pipelines-host:1.4.tenant-identity):** Echo tenant display name + tenant GUID + dev env URL before any host detection. First of the wrong-tenant guards. Cancel exits cleanly before any BAP/Dataverse call.

4. **Tenant identity confirmation gate.** Echo back via `AskUserQuestion`:

   > "About to inspect Pipelines host configuration for tenant **{TENANT_DISPLAY_NAME}** (`{tenantId}`), org `{organizationId}`, dev env `{devEnvUrl}`. Continue? 1. Yes / 2. Cancel"

   (When `TENANT_DISPLAY_NAME` is null, drop the bold tenant-name segment and lead with the tenant GUID.)

   First of the consent gates that guard against wrong-tenant operations.

### Phase 1.5 — Ground in current Pipelines host documentation

> Reference: `${PLUGIN_ROOT}/references/alm-docs-grounding.md`

Cap this step at ~30 seconds. If MCP search / fetch errors out, log a one-line note and continue — this skill must remain runnable offline.

1. Run `microsoft_docs_search` with the query: `Power Platform Pipelines host environment Platform Host Custom Host`.
2. Fetch `https://learn.microsoft.com/en-us/power-platform/alm/pipelines` (and at most one sister page on host setup, default-custom-host configuration, or admin role requirements) in parallel via `microsoft_docs_fetch`.
3. Extract a one-paragraph summary of what Microsoft Learn currently says about Platform vs Custom Host trade-offs, the resolution order (org-db setting → BAP env metadata → tenant default), and admin role requirements. Compare against this skill's own *Resolution order* section and `${PLUGIN_ROOT}/references/cicd-pipeline-patterns.md`; flag any divergence (e.g. new Platform-Host SKU, changed default-custom-host setting name, new tenant policy controls).
4. Use the summary to inform Phase 2+ decisions. Do not silently change skill behavior — surface any divergence to the user as a soft warning before Phase 3 (Confirm action with user).

### Phase 2 — Run resolution order to find host

This phase is read-only. It produces a `RESOLUTION` object the user-confirm phase branches on.

The phase mirrors `ProjectHostProvider.tsx` exactly. The `useState` variables in that hook map to fields in our `RESOLUTION`:

| TS variable | Our field |
|---|---|
| `orgSetting.orgDbOrgSettingValue` | `orgSettingHostEnvId` |
| `initialProjectHostEnvironmentId` | (same) |
| `isInitialHostPlatformEnvironment` | `isPlatform` |
| `defaultCustomPipelinesHost` | `tenantDefaultCustomHostEnvId` |
| `finalProjectHostEnvironmentId` | `finalHostEnvId` |
| `projectHostStatus` | `status` |

Steps:

1. **Org-setting probe** (mirrors `useGetOrgDbOrgSetting('ProjectHostEnvironmentId')` line 103 in tsx). New helper `check-env-host-binding.js`:

   ```
   POST {devEnvUrl}/api/data/v9.0/GetOrgDbOrgSetting
   Authorization: Bearer {DEV_TOKEN}
   Body: { "SettingName": "ProjectHostEnvironmentId" }
   ```
   - Empty `SettingValue` → no current binding. Skip to S

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [microsoft](https://github.com/microsoft)
- **Source:** [microsoft/power-platform-skills](https://github.com/microsoft/power-platform-skills)
- **License:** MIT
- **Homepage:** https://aka.ms/ppskills

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-microsoft-power-platform-skills-ensure-pipelines-host
- Seller: https://agentstack.voostack.com/s/microsoft
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
