Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,11 @@ pids
*.seed
*.pid.lock

# Temp directories
# Temp files
tmp/
temp/
*.tmp
*.temp

# End of file
2 changes: 1 addition & 1 deletion docs/extend/extensibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,4 @@ The following are **not contracts** and may change in minor/patch versions:

**Lifecycle request contract**:
- Required fields: `LifecycleEvent`, `CorrelationId`
- Optional fields: `Actor`, `IdentityKeys`, `DesiredState`, `Changes`
- Optional fields: `Actor`, `IdentityKeys`, `Intent`, `Context`, `Changes`
34 changes: 30 additions & 4 deletions docs/reference/cmdlets/New-IdleRequest.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Creates a lifecycle request object.

```
New-IdleRequest [-LifecycleEvent] <String> [[-CorrelationId] <String>] [[-Actor] <String>]
[[-IdentityKeys] <Hashtable>] [[-DesiredState] <Hashtable>] [[-Changes] <Hashtable>]
[[-IdentityKeys] <Hashtable>] [[-Intent] <Hashtable>] [[-Context] <Hashtable>] [[-Changes] <Hashtable>]
[-ProgressAction <ActionPreference>] [<CommonParameters>]
```

Expand All @@ -30,9 +30,16 @@ Changes is optional and stays $null when omitted.

### EXAMPLE 1
```
# Minimal Joiner request - CorrelationId is auto-generated, Intent/Context default to empty
New-IdleRequest -LifecycleEvent Joiner -CorrelationId (New-Guid) -IdentityKeys @{ EmployeeId = '12345' }
```

### EXAMPLE 2
```
# Joiner request with caller-provided action inputs (Intent) and read-only associated context (Context)
New-IdleRequest -LifecycleEvent Joiner -CorrelationId (New-Guid) -IdentityKeys @{ EmployeeId = '12345' } -Intent @{ Department = 'Engineering'; Title = 'Engineer' } -Context @{ Identity = @{ ObjectId = 'abc-123' } }
```

## PARAMETERS

### -LifecycleEvent
Expand Down Expand Up @@ -99,8 +106,9 @@ Accept pipeline input: False
Accept wildcard characters: False
```

### -DesiredState
A hashtable describing the desired state (attributes, entitlements, etc.).
### -Intent
A hashtable containing the caller-provided action inputs for the workflow (attributes,
entitlements, operator flags, etc.).

```yaml
Type: Hashtable
Expand All @@ -114,6 +122,24 @@ Accept pipeline input: False
Accept wildcard characters: False
```

### -Context
A hashtable containing read-only associated context provided by the host or resolvers
(e.g.
identity snapshots, device hints).
Must not be treated as mutable state within IdLE.

```yaml
Type: Hashtable
Parameter Sets: (All)
Aliases:

Required: False
Position: 6
Default value: @{}
Accept pipeline input: False
Accept wildcard characters: False
```

### -Changes
Optional hashtable describing changes (typically used for Mover lifecycle events).

Expand All @@ -123,7 +149,7 @@ Parameter Sets: (All)
Aliases:

Required: False
Position: 6
Position: 7
Default value: None
Accept pipeline input: False
Accept wildcard characters: False
Expand Down
2 changes: 1 addition & 1 deletion docs/reference/providers/provider-mock.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import CodeBlock from '@theme/CodeBlock';
Use the Mock provider when you want to:

- validate **workflow logic**, conditions, and error handling
- validate **template placeholders** (e.g. `{{Request.Input...}}`) without external dependencies
- validate **template placeholders** (e.g. `{{Request.Intent...}}`) without external dependencies
- build demos or CI checks that should never modify production systems

Non-goals:
Expand Down
8 changes: 4 additions & 4 deletions docs/use/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ $workflowContent = @'
Provider = 'Identity'
IdentityKey = '{{Request.IdentityKeys.EmployeeId}}'
Attributes = @{
GivenName = '{{Request.DesiredState.GivenName}}'
Surname = '{{Request.DesiredState.Surname}}'
GivenName = '{{Request.Intent.GivenName}}'
Surname = '{{Request.Intent.Surname}}'
}
}
}
Expand All @@ -105,7 +105,7 @@ A request represents business intent (Joiner/Mover/Leaver) plus input data.
```powershell
$request = New-IdleRequest -LifecycleEvent 'Joiner' -IdentityKeys @{
EmployeeId = '12345'
} -DesiredState @{
} -Intent @{
GivenName = 'Max'
Surname = 'Power'
}
Expand All @@ -128,7 +128,7 @@ $providers = @{
## 5) Build the plan (validation + template resolution)

Plan building is a **fail-fast** step. IdLE validates the workflow and resolves templates like
`{{Request.DesiredState.GivenName}}`.
`{{Request.Intent.GivenName}}`.

```powershell
$plan = New-IdlePlan -WorkflowPath $workflowPath -Request $request -Providers $providers
Expand Down
6 changes: 3 additions & 3 deletions docs/use/walkthrough/01-workflow-definition.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ Create a file `joiner.psd1` with this content:
Provider = 'Identity'
IdentityKey = '{{Request.IdentityKeys.EmployeeId}}'
Attributes = @{
GivenName = '{{Request.DesiredState.GivenName}}'
Surname = '{{Request.DesiredState.Surname}}'
GivenName = '{{Request.Intent.GivenName}}'
Surname = '{{Request.Intent.Surname}}'
}
}
}
Expand All @@ -74,7 +74,7 @@ Workflows are treated as **untrusted input** and must remain **data-only**.
- `Steps` is an ordered list.
- Each step references a **StepType** by name (`Type`).
- Step configuration lives under `With`.
- Template expressions like `{{Request.DesiredState.GivenName}}` are resolved when building the plan.
- Template expressions like `{{Request.Intent.GivenName}}` are resolved when building the plan.

---

Expand Down
12 changes: 6 additions & 6 deletions docs/use/walkthrough/02-request-creation.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Create a minimal request that matches the workflow from [Walkthrough 1](01-workf
- A request object that contains:
- `LifecycleEvent`
- `IdentityKeys.EmployeeId`
- `DesiredState.GivenName` and `DesiredState.Surname`
- `Intent.GivenName` and `Intent.Surname`

---

Expand All @@ -31,7 +31,7 @@ In PowerShell, create the request like this:
```powershell
$request = New-IdleRequest -LifecycleEvent 'Joiner' -IdentityKeys @{
EmployeeId = '12345'
} -DesiredState @{
} -Intent @{
GivenName = 'Max'
Surname = 'Power'
}
Expand All @@ -40,8 +40,8 @@ $request = New-IdleRequest -LifecycleEvent 'Joiner' -IdentityKeys @{
This request provides the values referenced in the workflow templates:

- `{{Request.IdentityKeys.EmployeeId}}`
- `{{Request.DesiredState.GivenName}}`
- `{{Request.DesiredState.Surname}}`
- `{{Request.Intent.GivenName}}`
- `{{Request.Intent.Surname}}`

---

Expand All @@ -56,8 +56,8 @@ Identity keys are typically:
- unique
- provided by the upstream system (HR, IAM, ticket)

### DesiredState
Desired state contains the data you want IdLE to enforce (attributes, entitlements, mailbox settings, …).
### Intent
Intent contains the caller-provided action inputs (attributes, entitlements, mailbox settings, …) that the workflow should act on.

For this walkthrough we keep it minimal and only set two attributes.

Expand Down
4 changes: 2 additions & 2 deletions docs/use/walkthrough/03-plan-creation.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Build a plan from your workflow and request, while supplying providers (recommen
## You will have

- A plan object that is safe to review and execute
- Templates resolved (for example `{{Request.DesiredState.GivenName}}`)
- Templates resolved (for example `{{Request.Intent.GivenName}}`)
- Validation errors surfaced early (before execution)

---
Expand Down Expand Up @@ -59,7 +59,7 @@ During plan build IdLE typically:
- validates the workflow structure and step types
- validates that referenced providers exist (when supplied)
- checks required capabilities (provider/step contracts)
- resolves template expressions (for example `{{Request.DesiredState.GivenName}}`)
- resolves template expressions (for example `{{Request.Intent.GivenName}}`)
- produces a deterministic execution plan

:::info
Expand Down
6 changes: 3 additions & 3 deletions docs/use/walkthrough/05-providers-authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ Example workflow usage:
Provider = 'Identity'
IdentityKey = '{{Request.IdentityKeys.EmployeeId}}'
Attributes = @{
GivenName = '{{Request.DesiredState.GivenName}}'
Surname = '{{Request.DesiredState.Surname}}'
GivenName = '{{Request.Intent.GivenName}}'
Surname = '{{Request.Intent.Surname}}'
}
}
}
Expand Down Expand Up @@ -108,7 +108,7 @@ Example (step requests a named session):

IdentityKey = '{{Request.IdentityKeys.EmployeeId}}'
Attributes = @{
Department = '{{Request.DesiredState.Department}}'
Department = '{{Request.Intent.Department}}'
}
}
}
Expand Down
14 changes: 7 additions & 7 deletions docs/use/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ request during plan build (`New-IdlePlan`). Multiple placeholders may appear in

```powershell
IdentityKey = '{{Request.IdentityKeys.sAMAccountName}}'
DisplayName = '{{Request.DesiredState.GivenName}}'
Message = 'User {{Request.DesiredState.DisplayName}} is joining.'
DisplayName = '{{Request.Intent.GivenName}}'
Message = 'User {{Request.Intent.DisplayName}} is joining.'
```

### Allowed roots
Expand All @@ -84,27 +84,27 @@ For security, only these path roots are permitted:

| Root | Description |
| ---- | ----------- |
| `Request.DesiredState.*` | Intended target state of the identity |
| `Request.Intent.*` | Caller-provided action inputs |
| `Request.Context.*` | Read-only associated context (host/resolver-provided) |
| `Request.IdentityKeys.*` | Identifiers of the target identity |
| `Request.Changes.*` | Explicit deltas (Mover events) |
| `Request.LifecycleEvent` | Lifecycle event type (e.g. `Joiner`) |
| `Request.CorrelationId` | Stable correlation identifier |
| `Request.Actor` | Originator of the request |
| `Request.Input.*` | Alias for `Request.DesiredState.*` when no `Input` property exists |

### Pure vs. mixed placeholders

A value containing **only** a single placeholder preserves the resolved type (bool, int, datetime, guid, string):

```powershell
# Resolves to the actual [bool] value, not the string "True"
Enabled = '{{Request.DesiredState.IsEnabled}}'
Enabled = '{{Request.Intent.IsEnabled}}'
```

A value with surrounding text always produces a **string**:

```powershell
Message = 'Account for {{Request.DesiredState.DisplayName}} created.'
Message = 'Account for {{Request.Intent.DisplayName}} created.'
```

### Backslash and special characters
Expand Down Expand Up @@ -140,7 +140,7 @@ Summary of backslash behaviour:
| `DOMAIN\{{Request.IdentityKeys.sAMAccountName}}` | `DOMAIN\jdoe` — `\` literal, valid template resolved |
| `Literal \{{ braces here` | `Literal {{ braces here` — escape applied |
| `\{{Request.InvalidRoot}}` | `{{Request.InvalidRoot}}` — invalid root, escape applied |
| `Literal \{{ and {{Request.Input.Name}}` | `Literal {{ and TestName` — escape + template |
| `Literal \{{ and {{Request.Intent.Name}}` | `Literal {{ and TestName` — escape + template |

### Validation

Expand Down
17 changes: 8 additions & 9 deletions examples/Invoke-LeaverWithManagerOOF.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ template variables in Out of Office messages.
Key concepts:
- Manager lookup is performed HOST-SIDE, not inside workflow steps
- Request enrichment happens before calling New-IdlePlan
- Templates like {{Request.DesiredState.Manager.DisplayName}} are resolved during planning
- Templates like {{Request.Intent.Manager.DisplayName}} are resolved during planning

.NOTES
This is an example only. Adapt authentication, provider setup, and directory queries
Expand Down Expand Up @@ -112,18 +112,20 @@ switch ($DirectorySource) {
}
}

# 2. Build lifecycle request with enriched DesiredState
# 2. Build lifecycle request with caller-provided Intent
Write-Host "==> Building lifecycle request..." -ForegroundColor Cyan

$desiredState = @{}
$enrichedIntent = @{
UserPrincipalName = $UserPrincipalName
}

if ($managerInfo) {
$desiredState['Manager'] = $managerInfo
$enrichedIntent['Manager'] = $managerInfo
}
else {
# Fallback: use generic support contact
Write-Warning "No manager found; using generic support contact in OOF message."
$desiredState['Manager'] = @{
$enrichedIntent['Manager'] = @{
DisplayName = 'IT Support'
Mail = 'support@contoso.com'
}
Expand All @@ -132,10 +134,7 @@ else {
$request = New-IdleRequest `
-LifecycleEvent 'Leaver' `
-Actor $env:USERNAME `
-Input @{
UserPrincipalName = $UserPrincipalName
} `
-DesiredState $desiredState
-Intent $enrichedIntent

Write-Host " Request CorrelationId: $($request.CorrelationId)" -ForegroundColor Gray

Expand Down
16 changes: 8 additions & 8 deletions examples/workflows/templates/ad-joiner-entraconnect-entraid.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@
Type = 'IdLE.Step.CreateIdentity'
With = @{
Provider = 'Directory'
AuthSessionName = '{{Request.Input.Auth.Directory}}'
AuthSessionName = '{{Request.Intent.Auth.Directory}}'

IdentityKey = '{{Request.Input.SamAccountName}}'
IdentityKey = '{{Request.Intent.SamAccountName}}'

Attributes = @{
GivenName = '{{Request.Input.GivenName}}'
Surname = '{{Request.Input.Surname}}'
Department = '{{Request.Input.Department}}'
GivenName = '{{Request.Intent.GivenName}}'
Surname = '{{Request.Intent.Surname}}'
Department = '{{Request.Intent.Department}}'
}
}
}
Expand Down Expand Up @@ -48,12 +48,12 @@
Role = 'Admin'
}

IdentityKey = '{{Request.Input.UserPrincipalName}}'
IdentityKey = '{{Request.Intent.UserPrincipalName}}'

Entitlement = @{
Kind = 'Group'
Id = '{{Request.Input.AllEmployeesGroupId}}'
DisplayName = '{{Request.Input.AllEmployeesGroupName}}'
Id = '{{Request.Intent.AllEmployeesGroupId}}'
DisplayName = '{{Request.Intent.AllEmployeesGroupName}}'
}
State = 'Present'
}
Expand Down
Loading