Skip to content
20 changes: 18 additions & 2 deletions docs/reference/cmdlets/Invoke-IdlePlan.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,37 @@ Invoke-IdlePlan [-Plan] <Object> [[-Providers] <Hashtable>] [[-Event
Executes a plan deterministically and emits structured events.
Delegates execution to IdLE.Core.

Provider resolution:
- If -Providers is supplied, it is used for execution.
- If -Providers is not supplied, Plan.Providers is used if available.
- If neither is present, execution fails early with a clear error message.

## EXAMPLES

### EXAMPLE 1
```
Invoke-IdlePlan -Plan $plan -Providers $providers
# Default: plan built with providers, execution uses Plan.Providers
$providers = @{ Identity = $provider; AuthSessionBroker = $broker }
$plan = New-IdlePlan -WorkflowPath ./joiner.psd1 -Request $req -Providers $providers
Invoke-IdlePlan -Plan $plan
```

### EXAMPLE 2
```
# Override: explicit -Providers at invoke time
Invoke-IdlePlan -Plan $plan -Providers $otherProviders
```

### EXAMPLE 3
```
$execOptions = @{
RetryProfiles = @{
Default = @{ MaxAttempts = 3; InitialDelayMilliseconds = 200 }
ExchangeOnline = @{ MaxAttempts = 6; InitialDelayMilliseconds = 500 }
}
DefaultRetryProfile = 'Default'
}
Invoke-IdlePlan -Plan $plan -Providers $providers -ExecutionOptions $execOptions
Invoke-IdlePlan -Plan $plan -ExecutionOptions $execOptions
```

## PARAMETERS
Expand All @@ -60,6 +74,8 @@ Accept wildcard characters: False

### -Providers
Provider registry/collection passed through to execution.
If omitted and Plan.Providers exists, Plan.Providers will be used.
If supplied, overrides Plan.Providers.

```yaml
Type: Hashtable
Expand Down
10 changes: 9 additions & 1 deletion docs/use/plan-export.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,18 @@ For the exact format and normative rules, see [Plan Export Specification](../ref
```powershell
# Example only. Adjust parameters to your environment.
$request = New-IdleLifecycleRequest -LifecycleEvent 'Joiner' -IdentityKeys @{ EmployeeId = 'jdoe' }
$plan = New-IdlePlan -WorkflowPath './workflows/joiner.psd1' -Request $request
$providers = @{ Identity = New-IdleMockIdentityProvider }
$plan = New-IdlePlan -WorkflowPath './workflows/joiner.psd1' -Request $request -Providers $providers
Export-IdlePlan -Plan $plan -Path './artifacts/joiner.plan.json'
```

:::note
Comment thread
blindzero marked this conversation as resolved.

Exported plans typically do not include provider objects. When executing an exported plan,
you must supply providers at execution time.

:::

### Review tips

- Verify provider selection matches your intent (especially in multi-provider environments).
Expand Down
39 changes: 39 additions & 0 deletions docs/use/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,52 @@ As providers may require additional tools and configuration, they are not import

:::

### Provider Resolution

When executing a plan, providers can be supplied in two ways:

1. **During planning** (recommended for most scenarios):

```powershell
Import-Module -Name IdLE.Provider.Mock

$providers = @{
Identity = New-IdleMockIdentityProvider
}

# Build plan with providers
$plan = New-IdlePlan -WorkflowPath ./joiner.psd1 -Request $request -Providers $providers

# Execute without re-supplying providers (uses Plan.Providers)
$result = Invoke-IdlePlan -Plan $plan
```

2. **At execution time** (for provider override or exported plans):

```powershell
# Override providers at execution time
$otherProviders = @{
Identity = New-IdleMockIdentityProvider -Config $differentConfig
}

$result = Invoke-IdlePlan -Plan $plan -Providers $otherProviders
```

#### Resolution Rules

- If `-Providers` is supplied to `Invoke-IdlePlan`, it **takes precedence** over `Plan.Providers`.
- If `-Providers` is **not** supplied, `Invoke-IdlePlan` uses `Plan.Providers` (if available).
- If neither is present, execution fails early with: `Providers are required. Provide -Providers to Invoke-IdlePlan or build the plan with Providers.`

#### Exported Plans
Comment thread
blindzero marked this conversation as resolved.

When a plan is exported without provider objects (for review or audit), providers must be supplied at execution time:

```powershell
# Export plan (without providers)
Export-IdlePlan -Plan $plan -Path ./plan.json

# Later: execute with providers (plan import functionality is planned for future release)
$result = Invoke-IdlePlan -Plan $plan -Providers $providers
```

Expand Down
20 changes: 11 additions & 9 deletions docs/use/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,28 +124,29 @@ With the following command we create a simple 'Joiner' request.
$request = New-IdleLifecycleRequest -LifecycleEvent 'Joiner'
```

### 4. Build the plan (deterministic, data-only)
### 4. Select providers

The plan evaluates validity of the request in combination with the workflow definition.
For first run, we just use our internal mock provider.

```powershell
$plan = New-IdlePlan -WorkflowPath $workflow -Request $request
$providers = @{
Identity = New-IdleMockIdentityProvider
}
```

### 5. Select providers
### 5. Build the plan with providers

For first run, we just use our internal mock provider.
The plan evaluates validity of the request in combination with the workflow definition.

```powershell
$providers = @{
Identity = New-IdleMockIdentityProvider
}
$plan = New-IdlePlan -WorkflowPath $workflow -Request $request -Providers $providers
```

### 6. Execute the plan

```powershell
$result = Invoke-IdlePlan -Plan $plan -Providers $providers
# Execute without re-supplying providers (uses Plan.Providers automatically)
$result = Invoke-IdlePlan -Plan $plan
```

### 7. Inspect result + events
Expand All @@ -161,5 +162,6 @@ $result.Events | Select-Object Type, StepName, Message
- If your workflow contains steps that require additional provider roles (e.g. `Messaging`, `Entitlement`),
you must add them to `$providers`.
- Many steps default to the provider alias `'Identity'` unless a step explicitly sets `With.Provider`.
- You can override providers at execution time by passing `-Providers` to `Invoke-IdlePlan`.

:::
3 changes: 2 additions & 1 deletion examples/Invoke-IdleDemo.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,8 @@ foreach ($wf in $selected) {

Write-Host ""
Write-DemoHeader "Execute"
$result = Invoke-IdlePlan -Plan $plan -Providers $providers
# Execute plan using Plan.Providers (no need to re-supply -Providers)
$result = Invoke-IdlePlan -Plan $plan
$allResults += $result

Write-Host ""
Expand Down
52 changes: 42 additions & 10 deletions src/IdLE.Core/Public/Invoke-IdlePlanObject.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ function Invoke-IdlePlanObject {
.DESCRIPTION
Executes steps in order, emits structured events, and returns a stable execution result.

Provider resolution:
- If -Providers is supplied, it is used for execution.
- If -Providers is not supplied (null), Plan.Providers is used if available.
- If neither is present, execution fails early with a clear error message.

Security:
- ScriptBlocks are rejected in plan and providers.
- The returned execution result is an output boundary: Providers are redacted.
Expand All @@ -15,6 +20,8 @@ function Invoke-IdlePlanObject {

.PARAMETER Providers
Provider registry/collection passed through to execution.
If omitted and Plan.Providers exists, Plan.Providers will be used.
If supplied, overrides Plan.Providers.

.PARAMETER EventSink
Optional external event sink object. Must provide a WriteEvent(event) method.
Expand Down Expand Up @@ -96,18 +103,43 @@ function Invoke-IdlePlanObject {
}

Assert-IdleNoScriptBlock -InputObject $Plan -Path 'Plan'
Assert-IdleNoScriptBlock -InputObject $Providers -Path 'Providers'

# Resolve effective providers: explicit -Providers parameter takes precedence, otherwise use Plan.Providers.
# This allows the common workflow: build plan with providers once, execute without re-supplying them.
$effectiveProviders = $Providers
if ($null -eq $effectiveProviders) {
if ($planPropNames -contains 'Providers') {
$planProviders = $Plan.Providers
# Accept both IDictionary (hashtables) and PSCustomObject-shaped provider registries
if ($null -ne $planProviders) {
$isValidProvider = ($planProviders -is [System.Collections.IDictionary]) -or
($planProviders.PSObject -and $planProviders.PSObject.Properties)
if ($isValidProvider) {
$effectiveProviders = $planProviders
}
}
}
}

# Early validation: fail with a clear message if no providers are available.
if ($null -eq $effectiveProviders) {
throw [System.InvalidOperationException]::new(
'Providers are required. Provide -Providers to Invoke-IdlePlan or build the plan with Providers.'
Comment thread
blindzero marked this conversation as resolved.
)
}

Assert-IdleNoScriptBlock -InputObject $effectiveProviders -Path 'Providers'

# Validate ExecutionOptions
Assert-IdleExecutionOptions -ExecutionOptions $ExecutionOptions

# StepRegistry is constructed via helper to ensure built-in steps and host-provided steps can co-exist.
$stepRegistry = Get-IdleStepRegistry -Providers $Providers
$stepRegistry = Get-IdleStepRegistry -Providers $effectiveProviders

$context = [pscustomobject]@{
PSTypeName = 'IdLE.ExecutionContext'
Plan = $Plan
Providers = $Providers
Providers = $effectiveProviders
EventSink = $engineEventSink
}

Expand Down Expand Up @@ -202,14 +234,14 @@ function Invoke-IdlePlanObject {

if ($requiresAuthBroker) {
$broker = $null
if ($Providers -is [System.Collections.IDictionary]) {
if ($Providers.Contains('AuthSessionBroker')) {
$broker = $Providers['AuthSessionBroker']
if ($effectiveProviders -is [System.Collections.IDictionary]) {
if ($effectiveProviders.Contains('AuthSessionBroker')) {
$broker = $effectiveProviders['AuthSessionBroker']
}
}
else {
if ($null -ne $Providers -and $Providers.PSObject.Properties.Name -contains 'AuthSessionBroker') {
$broker = $Providers.AuthSessionBroker
if ($null -ne $effectiveProviders -and $effectiveProviders.PSObject.Properties.Name -contains 'AuthSessionBroker') {
$broker = $effectiveProviders.AuthSessionBroker
}
}

Expand Down Expand Up @@ -620,8 +652,8 @@ function Invoke-IdlePlanObject {

# Issue #48:
# Redact provider configuration/state at the output boundary (execution result).
$redactedProviders = if ($null -ne $Providers) {
Copy-IdleRedactedObject -Value $Providers
$redactedProviders = if ($null -ne $effectiveProviders) {
Copy-IdleRedactedObject -Value $effectiveProviders
}
else {
$null
Expand Down
18 changes: 16 additions & 2 deletions src/IdLE/Public/Invoke-IdlePlan.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,18 @@ function Invoke-IdlePlan {
Executes a plan deterministically and emits structured events.
Delegates execution to IdLE.Core.

Provider resolution:
- If -Providers is supplied, it is used for execution.
- If -Providers is not supplied, Plan.Providers is used if available.
- If neither is present, execution fails early with a clear error message.

.PARAMETER Plan
The plan object created by New-IdlePlan.

.PARAMETER Providers
Provider registry/collection passed through to execution.
If omitted and Plan.Providers exists, Plan.Providers will be used.
If supplied, overrides Plan.Providers.

.PARAMETER EventSink
Optional external event sink for streaming. Must be an object with a WriteEvent(event) method.
Expand All @@ -21,7 +28,14 @@ function Invoke-IdlePlan {
Must be a hashtable with optional keys: RetryProfiles, DefaultRetryProfile.

.EXAMPLE
Invoke-IdlePlan -Plan $plan -Providers $providers
# Default: plan built with providers, execution uses Plan.Providers
$providers = @{ Identity = $provider; AuthSessionBroker = $broker }
$plan = New-IdlePlan -WorkflowPath ./joiner.psd1 -Request $req -Providers $providers
Invoke-IdlePlan -Plan $plan

.EXAMPLE
# Override: explicit -Providers at invoke time
Invoke-IdlePlan -Plan $plan -Providers $otherProviders

.EXAMPLE
$execOptions = @{
Expand All @@ -31,7 +45,7 @@ function Invoke-IdlePlan {
}
DefaultRetryProfile = 'Default'
}
Invoke-IdlePlan -Plan $plan -Providers $providers -ExecutionOptions $execOptions
Invoke-IdlePlan -Plan $plan -ExecutionOptions $execOptions

.OUTPUTS
PSCustomObject (PSTypeName: IdLE.ExecutionResult)
Expand Down
12 changes: 8 additions & 4 deletions tests/Core/Invoke-IdlePlan.ExecutionOptions.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,8 @@ Describe 'Invoke-IdlePlan - ExecutionOptions validation' {
'@

$req = New-IdleLifecycleRequest -LifecycleEvent 'Joiner'
$plan = New-IdlePlan -WorkflowPath $wfPath -Request $req
$providers = @{ StepRegistry = @{} }
$plan = New-IdlePlan -WorkflowPath $wfPath -Request $req -Providers $providers

$opts = @{
SomeKey = { Write-Host 'test' }
Expand All @@ -159,7 +160,8 @@ Describe 'Invoke-IdlePlan - ExecutionOptions validation' {
'@

$req = New-IdleLifecycleRequest -LifecycleEvent 'Joiner'
$plan = New-IdlePlan -WorkflowPath $wfPath -Request $req
$providers = @{ StepRegistry = @{} }
$plan = New-IdlePlan -WorkflowPath $wfPath -Request $req -Providers $providers

$opts = @{
RetryProfiles = @{
Expand All @@ -181,7 +183,8 @@ Describe 'Invoke-IdlePlan - ExecutionOptions validation' {
'@

$req = New-IdleLifecycleRequest -LifecycleEvent 'Joiner'
$plan = New-IdlePlan -WorkflowPath $wfPath -Request $req
$providers = @{ StepRegistry = @{} }
$plan = New-IdlePlan -WorkflowPath $wfPath -Request $req -Providers $providers

$opts = @{
RetryProfiles = @{
Expand All @@ -204,7 +207,8 @@ Describe 'Invoke-IdlePlan - ExecutionOptions validation' {
'@

$req = New-IdleLifecycleRequest -LifecycleEvent 'Joiner'
$plan = New-IdlePlan -WorkflowPath $wfPath -Request $req
$providers = @{ StepRegistry = @{} }
$plan = New-IdlePlan -WorkflowPath $wfPath -Request $req -Providers $providers

$opts = @{
RetryProfiles = @{
Expand Down
Loading
Loading