From af95986465e4cf461070f3510c140c7c0b6213f2 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <55826276+ntt-matthias-fleschuetz@users.noreply.github.com> Date: Wed, 25 Feb 2026 15:21:59 +0100 Subject: [PATCH 01/28] docs: updated docs for workflows, separated with conditions, preconditions and template substitution --- docs/use/workflows.md | 205 +++++++++++------------ docs/use/workflows/conditions.md | 193 +++++++++++++++++++++ docs/use/workflows/preconditions.md | 250 +++++++++------------------- docs/use/workflows/templates.md | 161 ++++++++++++++++++ website/sidebars.js | 2 + 5 files changed, 528 insertions(+), 283 deletions(-) create mode 100644 docs/use/workflows/conditions.md create mode 100644 docs/use/workflows/templates.md diff --git a/docs/use/workflows.md b/docs/use/workflows.md index 1031ff5e..4979a58a 100644 --- a/docs/use/workflows.md +++ b/docs/use/workflows.md @@ -3,16 +3,48 @@ title: Workflows & Steps sidebar_label: Workflows / Steps --- -Workflows define **what** IdLE should do for a lifecycle event (Joiner/Mover/Leaver). +Workflows are **data-only** PowerShell hashtables (`.psd1`) that describe **which steps** should be planned and executed for a specific lifecycle event. Workflows define **what** IdLE should do for a lifecycle event (Joiner/Mover/Leaver). A workflow is a **data-only** PowerShell hashtable stored in a `.psd1` file. It describes the ordered steps to execute, plus optional conditions and error handling. +Workflows are designed for **admins and workflow authors**: + +- You define *what should happen* (steps and their configuration). +- IdLE builds a **plan** and then **executes** it. +- Providers implement the system-specific operations. + +## How workflows are used in the lifecycle + +1. You write the workflow definition (`.psd1`). +2. You create a request (intent + inputs). +3. You build a plan (IdLE validates and resolves templates). +4. You invoke the plan. + :::info -For specification-level details (schema, templates, conditions, and validation rules), use the [Reference](../reference/intro-reference.md) section. +For specification-level details on step types, use the [Step Reference](../reference/steps.md) section. \ +Otherwise, start with [Quick Start](quickstart.md). ::: --- +## Plan vs Execute + +When you run IdLE, it happens in two distinct phases: + +1. **Planning (Plan Build)** + IdLE reads the workflow definition and builds a plan of steps. + + - `Condition` is evaluated here. + - If a condition is false, the step is marked as `NotApplicable`. + +2. **Execution (Plan Run)** + IdLE executes the planned steps and records results. + + - `Preconditions` are evaluated here. + - If a precondition is false, `OnPreconditionFalse` decides what happens (for example `Skip` or `Fail`). + +--- + ## What a workflow contains At a high level, a workflow contains: @@ -20,146 +52,107 @@ At a high level, a workflow contains: - metadata (name, lifecycle event) - a list of steps (ordered) - per-step configuration (`With`) -- optional execution logic (conditions, `OnFailureSteps`, etc.) +- per-step optional execution logic (`Condition`, `Preconditions`, `OnFailureSteps`, etc.) The Big Picture is described in [Concepts](../about/concepts.md). +A step is a self-contained unit of work. Most steps follow this pattern: + +- `Name` (string) – a human-readable identifier +- `Type` (string) – the step type (for example `IdLE.Step.EnsureAttribute`) +- `With` (hashtable) – step-specific configuration +- `Condition` (hashtable, optional) – optional planning-time applicability +- `Preconditions` (hashtable, optional) – optional execution-time guard +- `OnPreconditionFalse` (string, optional) – behavior when the precondition is false + +> Step types define which keys are supported inside `With`. See the step reference for details. + ### Step execution controls Each step supports several optional execution control properties: | Property | Evaluated at | Purpose | |---|---|---| -| `Condition` | Plan time | Include or skip the step based on request/intent data. | -| `Preconditions` | Execution time (runtime) | Guard the step against stale or unsafe state immediately before it runs. See [Runtime Preconditions](workflows/preconditions.md). | +| `Condition` | Plan time | Include or skip the step based on request/intent data during planning. See [Conditions](workflows/conditions.md) | +| `Preconditions` | Execution time (runtime) | Guard the step against stale or unsafe state immediately before execution. See Runtime [Preconditions](workflows/preconditions.md). | | `OnFailureSteps` | After failure (workflow-level) | Cleanup/rollback steps run after a primary step fails. | ---- - -## Minimal workflow example - -```powershell -@{ - Name = 'Joiner - Minimal' - LifecycleEvent = 'Joiner' - - Steps = @( - @{ - Name = 'Emit start' - Type = 'IdLE.Step.EmitEvent' - With = @{ - Message = 'Starting Joiner workflow' - } - } - ) -} -``` +:::warning Do not confuse Conditions and Preconditions +**Conditions** decide step applicability during **planning** (a step becomes `NotApplicable`). +**Preconditions** guard step behavior during **execution** (`Skip` / `Fail` / `Continue`). --- -## How workflows are used in the lifecycle +## Template Substitution -1. You write the workflow definition (`.psd1`). -2. You create a request (intent + inputs). -3. You build a plan (IdLE validates and resolves templates). -4. You invoke the plan. - -Start with [Quick Start](quickstart.md). - ---- - -## Common pitfalls - -- **Not data-only:** embedding ScriptBlocks or secrets in workflow files (not allowed). -- **Wrong StepType name:** the step module is not imported or the type name is wrong. -- **Missing provider alias:** `With.Provider = 'Identity'` but the host did not supply that alias. -- **Template paths resolve to null:** the referenced request/identity data is missing. - ---- - -## Template substitution - -Step configuration values (`With.*`) support `{{path}}` placeholders that are resolved against the +Many step configurations use **template substitution** to insert values from `Plan`, `Request`, and `Workflow` into strings (for example to build a UPN or display name). \ +These `{{path}}` placeholders that are resolved against the request during plan build (`New-IdlePlan`). Multiple placeholders may appear in a single value. ```powershell IdentityKey = '{{Request.IdentityKeys.sAMAccountName}}' -DisplayName = '{{Request.Intent.GivenName}}' +DisplayName = '{{Request.Intent.GivenName}} {{Request.Intent.SurnameName}}' Message = 'User {{Request.Intent.DisplayName}} is joining.' ``` -### Allowed roots +See: [Template Substitution](./workflows/templates) -For security, only these path roots are permitted: +--- -| Root | Description | -| ---- | ----------- | -| `Request.Intent.*` | Caller-provided action inputs | -| `Request.Context.*` | Read-only associated context (host/resolver-provided) | -| `Request.IdentityKeys.*` | Identifiers of the target identity | -| `Request.LifecycleEvent` | Lifecycle event type (e.g. `Joiner`) | -| `Request.CorrelationId` | Stable correlation identifier | -| `Request.Actor` | Originator of the request | +## Minimal workflow example -### Pure vs. mixed placeholders +This example shows a small workflow with: -A value containing **only** a single placeholder preserves the resolved type (bool, int, datetime, guid, string): +- a value containing a [template substition](./workflows/templates.md) +- a step that is only applicable for `Joiner` ([Condition](./workflows/conditions.md)) +- a step that is guarded at runtime ([Preconditions](./workflows/preconditions.md)) -```powershell -# Resolves to the actual [bool] value, not the string "True" -Enabled = '{{Request.Intent.IsEnabled}}' -``` - -A value with surrounding text always produces a **string**: ```powershell -Message = 'Account for {{Request.Intent.DisplayName}} created.' -``` +@{ + Name = 'Joiner - Standard' + LifecycleEvent = 'Joiner' -### Backslash and special characters + Steps = @( + @{ + Name = 'Emit start' + Type = 'IdLE.Step.EmitEvent' + With = @{ Message = 'Starting Joiner for {{Request.Intent.FullName}}' } + } -Backslash (`\`) is a **literal character** in template strings and requires no escaping. -Windows-style paths and domain-qualified names work as-is: + @{ + Name = 'Provision only for Joiner' + Type = 'IdLE.Step.EmitEvent' -```powershell -# \ is kept as-is; only the placeholder is substituted -IdentityKey = 'DOMAIN\{{Request.IdentityKeys.sAMAccountName}}' -# → e.g. 'DOMAIN\jdoe' -``` + Condition = @{ + Equals = @{ Path = 'Plan.LifecycleEvent'; Value = 'Joiner' } + } -### Escaping a literal `{{` + With = @{ Message = 'Provisioning for Joiner' } + } -To include a literal `{{` in the output, prefix it with `\`. The escape is applied whenever -`\{{` is **not** immediately followed by a valid allowed-root template path and `}}`: + @{ + Name = 'Disable identity only if it exists' + Type = 'IdLE.Step.DisableIdentity' -```powershell -# \{{ not followed by a valid path+}} → literal {{ in output -Value = 'Literal \{{ braces here' -# → 'Literal {{ braces here' + Preconditions = @{ + Equals = @{ Path = 'Request.Context.IdentityExists'; Value = 'True' } + } -# \{{ followed by an invalid/disallowed path → also escaped (literal {{ in output) -Value = '\{{Request.InvalidRoot}}' -# → '{{Request.InvalidRoot}}' + OnPreconditionFalse = 'Skip' + } + ) +} ``` -Summary of backslash behaviour: - -| Input | Result | -| ----- | ------ | -| `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.Intent.Name}}` | `Literal {{ and TestName` — escape + template | - -### Validation +--- -During plan build, IdLE validates every template value: +## Common pitfalls -- **Unbalanced braces** — mismatched `{{`/`}}` pairs throw a syntax error. -- **Invalid path** — paths must use dot-separated identifiers (letters, numbers, underscores). -- **Disallowed root** — paths outside the allowlist throw a security error. -- **Null or missing value** — if the resolved path does not exist, an error is thrown. -- **Non-scalar value** — resolving to a hashtable or array is not allowed. +- **Not data-only:** embedding ScriptBlocks or secrets in workflow files (not allowed). +- **Wrong StepType name:** the step module is not imported or the type name is wrong. +- **Missing provider alias:** `With.Provider = 'Identity'` but the host did not supply that alias. +- **Template paths resolve to null:** the referenced request/identity data is missing. --- @@ -170,11 +163,3 @@ For full definitions and reference, see: - [Reference](../reference/intro-reference.md) - [Reference: Step Types](../reference/steps.md) - [Reference: Providers](../reference/providers.md) - ---- - -## Next steps - -- Add runtime safety guards: [Runtime Preconditions](workflows/preconditions.md) -- Map external systems: [Providers](providers.md) -- Review and export plans: [Plan Export](plan-export.md) (e.g. for CI systems) diff --git a/docs/use/workflows/conditions.md b/docs/use/workflows/conditions.md new file mode 100644 index 00000000..6cea1f36 --- /dev/null +++ b/docs/use/workflows/conditions.md @@ -0,0 +1,193 @@ +--- +title: Conditions +sidebar_label: Conditions +--- + +# Conditions + +## What are Conditions? + +Conditions control **step applicability during planning**. + +- Evaluated while the plan is being built +- If the condition evaluates to `false`, the step becomes `NotApplicable` +- Conditions shape the plan, not execution + +Think of **Conditions** as a *planning-time filter*. \ +They decide whether a step becomes part of the executable plan. + +--- + +## ⚠️ Conditions vs Preconditions + +:::warning Do not confuse Conditions and Preconditions +Conditions decide step applicability during **planning**. +Preconditions guard step behavior during **execution**. + +See: [Preconditions](./preconditions) +::: + +| Conditions | Preconditions | +|------------|--------------| +| Planning time | Execution time | +| Marks step `NotApplicable` | Controls runtime behavior | +| Affects plan shape | Affects execution flow | + +--- + +## Full Example + +```powershell +@{ + Name = 'Provision EU Joiner' + Type = 'IdLE.Step.EmitEvent' + + Condition = @{ + All = @( + @{ Equals = @{ Path = 'Plan.LifecycleEvent'; Value = 'Joiner' } } + @{ In = @{ Path = 'Request.Context.Region'; Values = @('EU','DE') } } + @{ Exists = 'Request.IdentityKeys.EmployeeId' } + ) + } + + With = @{ + Message = 'Provisioning for EU Joiner' + } +} +``` + +### Explanation + +The step is applicable only if: + +1. The lifecycle event is `Joiner` +2. The region is `EU` or `DE` +3. An `EmployeeId` exists + +If any condition evaluates to false, the step is marked as `NotApplicable` during planning. + +--- + +## Condition DSL + +Preconditions use the **same DSL** as Conditions. +This section is the authoritative DSL reference. + +### Groups + +- `All` — all child conditions must be true (AND) +- `Any` — at least one child condition must be true (OR) +- `None` — none of the child conditions must be true (NOR) + +### Operators + +#### Equals + +```powershell +@{ Equals = @{ Path = 'Plan.LifecycleEvent'; Value = 'Joiner' } } +``` + +#### NotEquals + +```powershell +@{ NotEquals = @{ Path = 'Request.Context.Tenant'; Value = 'DEV' } } +``` + +#### Exists + +```powershell +@{ Exists = 'Request.Context.ManagerUpn' } +``` + +#### In + +```powershell +@{ + In = @{ + Path = 'Plan.LifecycleEvent' + Values = @('Joiner','Mover') + } +} +``` + +--- + +## Comparison Semantics + +- Comparisons are string-based +- Deterministic evaluation +- Values are converted to string before comparison + +--- + +## Validation Rules + +- Each node may contain exactly one operator or group +- Unknown keys cause planning-time errors +- Missing or empty `Path` causes validation errors + +--- + +## Common Patterns + +### Only for a lifecycle event + +```powershell +Condition = @{ Equals = @{ Path = 'Plan.LifecycleEvent'; Value = 'Leaver' } } +``` + +### Only if a request field exists + +```powershell +Condition = @{ Exists = 'Request.Context.ManagerUpn' } +``` + +### Allowlist values (In) + +```powershell +Condition = @{ In = @{ Path = 'Request.Context.Region'; Values = @('EU','US') } } +``` + +### Negation (NOT via None) + +```powershell +Condition = @{ None = @( @{ Equals = @{ Path = 'Request.Context.Tenant'; Value = 'DEV' } } ) } +``` + +### Combine multiple checks (All / AND) + +```powershell +Condition = @{ + All = @( + @{ Equals = @{ Path = 'Plan.LifecycleEvent'; Value = 'Joiner' } } + @{ Exists = 'Request.IdentityKeys.EmployeeId' } + ) +} +``` + +--- + +## Troubleshooting + +### Step is `NotApplicable` but you expected it to run + +- Verify the used `Path` values are correct and exist at planning time (for example `Request.Context.*` vs `Request.IdentityKeys.*`). +- Remember comparisons are string-based. Normalize boolean-like values in the request (for example use `'True'` / `'False'` consistently). + +### Planning fails with “Unknown key … in condition node” + +Each node may contain exactly one of: + +- a group: `All`, `Any`, `None` +- an operator: `Equals`, `NotEquals`, `Exists`, `In` + +Any additional keys cause a planning-time validation error. + +### Planning fails with “Missing or empty Path” + +Operators like `Equals`, `NotEquals`, and `In` require a non-empty `Path`. +For `Exists`, prefer the short form `Exists = '…'` to avoid shape errors. + +### Confusion about “Skipped” + +Conditions do not “skip” execution. They decide applicability during planning and mark the step as `NotApplicable`. diff --git a/docs/use/workflows/preconditions.md b/docs/use/workflows/preconditions.md index 6f19dfa3..aafeebb9 100644 --- a/docs/use/workflows/preconditions.md +++ b/docs/use/workflows/preconditions.md @@ -1,225 +1,129 @@ --- -title: Runtime Preconditions -sidebar_label: Runtime Preconditions +title: Preconditions +sidebar_label: Preconditions --- -Runtime Preconditions are **read-only execution guards** evaluated immediately before a step runs. -They protect against stale plans: when time passes between plan creation and execution, external -state may have changed. Preconditions check live (or request-context) data at execution time and -stop the run before an unsafe action is taken. +# Preconditions -:::info Planning-time vs. runtime -`Condition` is evaluated at **planning time** and controls whether a step is included in the plan -(`Status = Planned | NotApplicable`). Preconditions are evaluated at **execution time**, after the -plan is built, immediately before each step runs. This keeps planning deterministic while enabling -safety guards. -::: - ---- - -## When to use preconditions - -Use preconditions when: +## What are Preconditions? -- The validity of a step depends on **current state** that may change after plan creation. -- A policy or compliance rule must be checked **live** before an action is allowed to proceed. -- You want to surface a structured, human-readable message to an operator when a gate fails. +Preconditions guard **step execution**. -**Example — BYOD policy:** +- Evaluated during execution +- Do not change plan shape +- Controlled via `OnPreconditionFalse` -Before disabling an identity, the system should verify that company data has been wiped from any -BYOD (Bring Your Own Device) device. If the wipe confirmation is missing, execution must stop with -a `Blocked` outcome and a message instructing the operator to perform the wipe manually. +Think of **Preconditions** as runtime safety checks. \ +They protect execution but do not affect planning. --- -## Schema +## ⚠️ Preconditions vs Conditions -Add these optional properties to a workflow step definition: +:::warning Do not confuse Preconditions and Conditions +Preconditions are evaluated during **execution**. +Conditions are evaluated during **planning**. -| Property | Type | Required | Description | -|---|---|---|---| -| `Preconditions` | `Array[Condition]` | No | One or more condition nodes (same DSL as `Condition`). All must pass for the step to execute. | -| `OnPreconditionFalse` | `String` | No | Behavior when a precondition fails. `Blocked` (default), `Fail`, or `Continue`. | -| `PreconditionEvent` | `Hashtable` | No | Structured event emitted when a precondition fails. | - -### PreconditionEvent schema +See: [Conditions](./conditions) +::: -| Key | Type | Required | Description | -|---|---|---|---| -| `Type` | `String` | **Yes** | Event type string (for example: `ManualActionRequired`). | -| `Message` | `String` | **Yes** | Human-readable description of the required action. | -| `Data` | `Hashtable` | No | Optional key-value payload. Must not contain secrets. | +| Preconditions | Conditions | +|--------------|------------| +| Execution time | Planning time | +| Controls runtime behavior | Marks step `NotApplicable` | +| Affects execution result | Affects plan shape | --- -## Example +## Full Example ```powershell @{ - Name = 'Leaver' - LifecycleEvent = 'Leaver' - - Steps = @( - @{ - Name = 'DisableIdentity' - Type = 'IdLE.Step.DisableIdentity' - With = @{ - Provider = 'Identity' - IdentityKey = '{{Request.IdentityKeys.sAMAccountName}}' - } - - # Runtime guard: only execute if BYOD wipe is confirmed. - # Note: the condition DSL compares values as strings. - # Request.Context.Byod.WipeConfirmed must be the string 'true' (e.g. set by a ContextResolver). - Preconditions = @( - @{ - Equals = @{ - Path = 'Request.Context.Byod.WipeConfirmed' - Value = 'true' - } - } - ) - OnPreconditionFalse = 'Blocked' - PreconditionEvent = @{ - Type = 'ManualActionRequired' - Message = 'Perform Intune retire / wipe company data for BYOD device before disabling the identity.' - Data = @{ - Reason = 'BYOD wipe not confirmed' - } - } - } - ) + Name = 'Disable existing identity' + Type = 'IdLE.Step.DisableIdentity' + + Preconditions = @{ + Equals = @{ Path = 'Request.Context.IdentityExists'; Value = 'True' } + } + + OnPreconditionFalse = 'Skip' } ``` ---- +### Explanation -## Condition DSL +The step executes only if: -Each entry in `Preconditions` uses the same **declarative condition DSL** as the `Condition` -property. Supported operators: +- `IdentityExists` equals `True` -| Operator | Shape | Description | -|---|---|---| -| `Equals` | `@{ Path = '...'; Value = '...' }` | True when the resolved path equals the value (string comparison). | -| `NotEquals` | `@{ Path = '...'; Value = '...' }` | True when the resolved path does not equal the value. | -| `Exists` | `'path'` or `@{ Path = '...' }` | True when the resolved path is non-null. | -| `In` | `@{ Path = '...'; Values = @(...) }` | True when the resolved path value is in the list. | -| `All` | `@{ All = @( ... ) }` | True when all child conditions are true (AND). | -| `Any` | `@{ Any = @( ... ) }` | True when at least one child condition is true (OR). | -| `None` | `@{ None = @( ... ) }` | True when no child conditions are true (NOR). | +If the precondition evaluates to false: -### Path resolution +- `Skip` → step is skipped +- `Fail` → execution fails +- `Continue` → execution continues -Paths are resolved against the **execution-time context**, which includes: +--- -| Root | Description | -|---|---| -| `Plan.*` | The plan object (e.g. `Plan.LifecycleEvent`). | -| `Request.*` | The lifecycle request, including `Request.Intent.*`, `Request.Context.*`, `Request.IdentityKeys.*`. | +## Condition DSL -A leading `context.` prefix is ignored for readability (e.g. `context.Request.Intent.Department` -resolves identically to `Request.Intent.Department`). +:::tip Preconditions use the same **Condition DSL** as Conditions. +For the complete DSL reference, see: [Conditions → Condition DSL](./conditions) +::: --- -## Blocked vs. Failed vs. Continue outcomes - -| Outcome | `OnPreconditionFalse` | Meaning | Stops execution? | OnFailureSteps triggered? | -|---|---|---|---|---| -| `Blocked` | `Blocked` (default) | A policy or precondition gate stopped execution. Not a technical failure. | **Yes** | **No** | -| `Failed` | `Fail` | Treated as a genuine failure (same semantics as a step error). | **Yes** | **Yes** | -| `PreconditionSkipped` | `Continue` | Emits observability events and skips the step; subsequent steps run normally. | **No** | **No** | +## Common Patterns -### Execution result — Blocked +### Guard destructive operations (Skip if not safe) -When a step is `Blocked`: - -- `result.Status` is `'Blocked'`. -- `result.Steps[n].Status` is `'Blocked'` for the blocking step. -- `result.OnFailure.Status` is `'NotRun'` (OnFailureSteps do not execute). -- A `StepPreconditionFailed` engine event is always emitted. -- A `StepBlocked` engine event is emitted for the blocked step. -- If `PreconditionEvent` is configured, an additional event of the declared `Type` is also emitted. +```powershell +Preconditions = @{ Equals = @{ Path = 'Request.Context.IdentityExists'; Value = 'True' } } +OnPreconditionFalse = 'Skip' +``` -### Execution result — Fail +### Fail fast if a mandatory prerequisite is missing -When `OnPreconditionFalse = 'Fail'`: +```powershell +Preconditions = @{ Exists = 'Request.IdentityKeys.EmployeeId' } +OnPreconditionFalse = 'Fail' +``` -- `result.Status` is `'Failed'`. -- `result.Steps[n].Status` is `'Failed'` with `Error = 'Precondition check failed.'`. -- `OnFailureSteps` run (same behavior as any other step failure). -- A `StepPreconditionFailed` engine event is always emitted. -- A `StepFailed` engine event is emitted (matching the format of regular step failure events). -- If `PreconditionEvent` is configured, an additional event of the declared `Type` is also emitted. +### Continue but record the outcome -### Execution result — Continue +Use this for optional operations where you prefer to continue execution but still want the step result to show the precondition outcome. -When `OnPreconditionFalse = 'Continue'`: +```powershell +Preconditions = @{ Exists = 'Request.Context.OptionalValue' } +OnPreconditionFalse = 'Continue' +``` -- `result.Status` is `'Completed'` (unless a subsequent step fails for another reason). -- `result.Steps[n].Status` is `'PreconditionSkipped'` for the skipped step. -- Subsequent steps execute as normal. -- A `StepPreconditionFailed` engine event is always emitted for observability. -- If `PreconditionEvent` is configured, an additional event of the declared `Type` is also emitted. +### Only execute for specific lifecycle events -Use `Continue` when a precondition failure is advisory rather than blocking — for example, to emit -an audit event noting that an optional step was skipped due to a policy condition, while allowing -the rest of the workflow to complete. +```powershell +Preconditions = @{ In = @{ Path = 'Plan.LifecycleEvent'; Values = @('Joiner','Mover') } } +OnPreconditionFalse = 'Skip' +``` --- -## Events emitted on precondition failure +## Troubleshooting -| Event type | `OnPreconditionFalse` modes | Description | -|---|---|---| -| `StepPreconditionFailed` | All (`Blocked`, `Fail`, `Continue`) | Always emitted. Contains `StepType`, `Index`, `OnPreconditionFalse`. | -| `StepBlocked` | `Blocked` | Emitted when the step outcome is `Blocked`. Contains `StepType`, `Index`. | -| `StepFailed` | `Fail` | Emitted when the step outcome is `Failed`. Contains `StepType`, `Index`, `Error`. | -| Configured `PreconditionEvent.Type` | All (if `PreconditionEvent` configured) | Caller-defined event. | +### Step is skipped unexpectedly -### StepPreconditionFailed event +- Check `OnPreconditionFalse`. If it is set to `Skip`, a false precondition will skip execution by design. +- Validate that the precondition `Path` resolves to the expected runtime value. -| Field | Value | -|---|---| -| `Type` | `StepPreconditionFailed` | -| `StepName` | The name of the affected step. | -| `Data.StepType` | The step type identifier. | -| `Data.Index` | The step index in the plan. | -| `Data.OnPreconditionFalse` | `Blocked`, `Fail`, or `Continue`. | +### Step fails due to precondition -### PreconditionEvent (caller-configured) +- If `OnPreconditionFalse = 'Fail'`, the step will fail intentionally when the precondition is false. +- Ensure required request values are prepared before execution (often host-side request preparation). -If `PreconditionEvent` is configured, an additional event is emitted with: +### Precondition seems correct but still evaluates false -| Field | Value | -|---|---| -| `Type` | The configured `PreconditionEvent.Type`. | -| `Message` | The configured `PreconditionEvent.Message`. | -| `StepName` | The name of the affected step. | -| `Data` | The configured `PreconditionEvent.Data` (if provided). | - -:::warning Log safety -`PreconditionEvent.Data` is surfaced as a structured event and may be forwarded to audit sinks. -Do **not** include secrets, credentials, or personal data in `Data`. -::: - -:::note String comparison -The condition DSL always compares values as **strings** (for example, boolean `$true` becomes `'True'`). -Ensure context values are stored as strings when using `Equals` or `In` operators. -::: - ---- - -## Backward compatibility - -Steps without `Preconditions` behave exactly as before. Adding preconditions to a step does not -affect any other steps. - ---- +- Remember comparisons are string-based. Normalize values (especially booleans) consistently (for example `'True'` / `'False'`). +- Confirm you are using the correct path (`Plan.*`, `Request.*`). -## Reference +### Where is the DSL documented? -- [Steps reference](../../reference/steps.md) -- [Concepts: Plan → Execute separation](../../about/concepts.md) +Preconditions use the same Condition DSL as Conditions. For the complete DSL reference, see: +[Conditions → Condition DSL](./conditions) diff --git a/docs/use/workflows/templates.md b/docs/use/workflows/templates.md new file mode 100644 index 00000000..2e36a3d4 --- /dev/null +++ b/docs/use/workflows/templates.md @@ -0,0 +1,161 @@ +--- +title: Template Substitution +sidebar_label: Template Substitution +--- + +# Template Substitution + +Template substitution allows you to reference values from the **planning context** inside step configuration (`With`), Conditions, and Preconditions. + +Templates are **data-only** and safe. +No ScriptBlocks or dynamic PowerShell expressions are supported. + +--- + +## What is Template Substitution? + +Template substitution resolves values from: + +- `Plan` +- `Request` +- `Workflow` + +It replaces template placeholders with actual values before execution. + +Think of template substitution as **value resolution**, not logic execution. \ +It simply reads values from the context and inserts them into configuration fields. + +--- + +## Resolution Context + +Templates can reference: + +| Root | Description | +| ---- | ----------- | +| `Request.Intent.*` | Caller-provided action inputs | +| `Request.Context.*` | Read-only associated context (host/resolver-provided) | +| `Request.IdentityKeys.*` | Identifiers of the target identity | +| `Request.LifecycleEvent` | Lifecycle event type (e.g. `Joiner`) | +| `Request.CorrelationId` | Stable correlation identifier | +| `Request.Actor` | Originator of the request | + +--- + +## Example + +```powershell +@{ + Name = 'Create UPN' + Type = 'IdLE.Step.EnsureAttribute' + + With = @{ + UserPrincipalName = '{{Request.IdentityKeys.FirstName}}.{{Request.IdentityKeys.LastName}}@example.com' + } +} +``` + +If: + +- FirstName = John +- LastName = Doe + +The resolved value becomes: + +``` +john.doe@example.com +``` + +--- + +## Common Patterns + +### Pure placeholder resolution + +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.Intent.IsEnabled}}' +``` + +### Build composite attributes + +```powershell +DisplayName = '{{Request.IdentityKeys.FirstName}} {{Request.IdentityKeys.LastName}}' +``` + +### Include lifecycle event + +A value with surrounding text always produces a **string**: + +```powershell +Description = 'Provisioned during {{Plan.LifecycleEvent}}' +``` + +### Backslash and special characters + +Backslash (`\`) is a **literal character** in template strings and requires no escaping. +Windows-style paths and domain-qualified names work as-is: + +```powershell +# \ is kept as-is; only the placeholder is substituted +IdentityKey = 'DOMAIN\{{Request.IdentityKeys.sAMAccountName}}' +# → e.g. 'DOMAIN\jdoe' +``` + +### Escaping a literal `{{` + +To include a literal `{{` in the output, prefix it with `\`. The escape is applied whenever +`\{{` is **not** immediately followed by a valid allowed-root template path and `}}`: + +```powershell +# \{{ not followed by a valid path+}} → literal {{ in output +Value = 'Literal \{{ braces here' +# → 'Literal {{ braces here' + +# \{{ followed by an invalid/disallowed path → also escaped (literal {{ in output) +Value = '\{{Request.InvalidRoot}}' +# → '{{Request.InvalidRoot}}' +``` + +Summary of backslash behaviour: + +| Input | Result | +| ----- | ------ | +| `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.Intent.Name}}` | `Literal {{ and TestName` — escape + template | + +### Template Validation + +During plan build, IdLE validates every template value: + +- **Unbalanced braces** — mismatched `{{`/`}}` pairs throw a syntax error. +- **Invalid path** — paths must use dot-separated identifiers (letters, numbers, underscores). +- **Disallowed root** — paths outside the allowlist throw a security error. +- **Null or missing value** — if the resolved path does not exist, an error is thrown. +- **Non-scalar value** — resolving to a hashtable or array is not allowed. + +--- + +## Troubleshooting + +### Placeholder not resolved + +- Verify the path exists in the request or plan context. +- Ensure correct casing and full path (e.g. `Request.Context.*`). + +### Empty value after substitution + +- The referenced path may be `$null`. +- Validate the request preparation logic before execution. + +--- + +## Related Topics + +- [Workflows](../workflows) +- [Conditions](./conditions) +- [Preconditions](./preconditions) diff --git a/website/sidebars.js b/website/sidebars.js index 1fd0815f..8525d8db 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -52,7 +52,9 @@ const sidebars = { collapsed: true, items: [ 'use/workflows', + 'use/workflows/conditions', 'use/workflows/preconditions', + 'use/workflows/templates', ], }, 'use/providers', From 72fbc14f5932d93e71b5baed8111c66c7bdd32a6 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <55826276+ntt-matthias-fleschuetz@users.noreply.github.com> Date: Wed, 25 Feb 2026 17:18:27 +0100 Subject: [PATCH 02/28] docs: combining multiple condition checks in preconditions --- docs/use/workflows/preconditions.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/use/workflows/preconditions.md b/docs/use/workflows/preconditions.md index aafeebb9..b69d483a 100644 --- a/docs/use/workflows/preconditions.md +++ b/docs/use/workflows/preconditions.md @@ -104,6 +104,18 @@ Preconditions = @{ In = @{ Path = 'Plan.LifecycleEvent'; Values = @('Joiner','Mo OnPreconditionFalse = 'Skip' ``` +### Combine multiple checks (All / AND) + +```powershell +Preconditions = @{ + All = @( + @{ In = @{ Path = 'Request.Intent.Region'; Value = 'EU' }} + @{ Equals = @{ Path = 'Plan.LifecycleEvent'; Value = 'Joiner' } } + @{ Exists = 'Request.IdentityKeys.EmployeeId' } + ) +} +``` + --- ## Troubleshooting From d0d48892288a2eae04c3b243928c174076dff734 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <55826276+ntt-matthias-fleschuetz@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:32:52 +0100 Subject: [PATCH 03/28] docs: adding context resolver and cross link in workflow-sub-pages --- docs/use/workflows/conditions.md | 12 +++--- docs/use/workflows/context-resolver.md | 58 ++++++++++++++++++++++++++ docs/use/workflows/preconditions.md | 12 +++--- docs/use/workflows/templates.md | 19 +++++---- website/sidebars.js | 1 + 5 files changed, 82 insertions(+), 20 deletions(-) create mode 100644 docs/use/workflows/context-resolver.md diff --git a/docs/use/workflows/conditions.md b/docs/use/workflows/conditions.md index 6cea1f36..7c9ab142 100644 --- a/docs/use/workflows/conditions.md +++ b/docs/use/workflows/conditions.md @@ -18,13 +18,13 @@ They decide whether a step becomes part of the executable plan. --- -## ⚠️ Conditions vs Preconditions +## ⚠️ Context Resolvers vs Templates vs Conditions vs Preconditions -:::warning Do not confuse Conditions and Preconditions -Conditions decide step applicability during **planning**. -Preconditions guard step behavior during **execution**. - -See: [Preconditions](./preconditions) +:::warning Do not confuse these concepts +**[Context Resolvers](./context-resolver.md)** populate `Request.Context.*` during **planning**. +**[Template Substitution](./templates.md)** consumes `Plan` / `Request` / `Workflow` values to build strings. +**Conditions** decide step applicability during **planning** (`NotApplicable`). +**[Preconditions](./preconditions.md)** guard step behavior during **execution** (`Skip` / `Fail` / `Continue`). ::: | Conditions | Preconditions | diff --git a/docs/use/workflows/context-resolver.md b/docs/use/workflows/context-resolver.md new file mode 100644 index 00000000..cec38b3e --- /dev/null +++ b/docs/use/workflows/context-resolver.md @@ -0,0 +1,58 @@ +--- +title: Context Resolvers +sidebar_label: Context Resolvers +--- + +## What are Context Resolvers? + +Context Resolvers populate **`Request.Context.*` during planning** using **read-only provider capabilities**. + +- Resolvers run during **plan build** +- They enrich the request with stable, pre-resolved data (for example an entitlement snapshot) +- They are **data-only** and validated strictly (fail-fast) + +This allows **Conditions**, **Preconditions**, and **Template Substitution** to reference values that were resolved once at planning time. + +--- + +## ⚠️ Context Resolvers vs Templates vs Conditions vs Preconditions + +:::warning Do not confuse these concepts +**Context Resolvers** populate `Request.Context.*` during **planning**. +**[Template Substitution](./templates.md)** consumes `Plan` / `Request` / `Workflow` values to build strings. +**[Conditions](conditions.md)** decide step applicability during **planning** (`NotApplicable`). +**[Preconditions](./preconditions.md)** guard step behavior during **execution** (`Skip` / `Fail` / `Continue`). +::: + +--- + +## Common Patterns + +### Entitlement snapshot (without pattern matching) + +Resolve entitlements once during planning: + +```powershell +ContextResolvers = @( + @{ + Capability = 'IdLE.Entitlement.List' + With = @{ IdentityKey = '{{Request.IdentityKeys.EmployeeId}}' } + } +) +``` + +Then guard on availability: + +```powershell +Condition = @{ Exists = 'Request.Context.Identity.Entitlements' } +``` + +> The current Condition DSL does not support list-membership or pattern operators. +> Membership evaluation requires either host-prepared boolean flags or a future DSL enhancement. + +--- + +## Troubleshooting + +- Ensure `ContextResolvers` is defined at workflow root. +- Ensure a provider advertises the requested capability. diff --git a/docs/use/workflows/preconditions.md b/docs/use/workflows/preconditions.md index b69d483a..e472747a 100644 --- a/docs/use/workflows/preconditions.md +++ b/docs/use/workflows/preconditions.md @@ -18,13 +18,13 @@ They protect execution but do not affect planning. --- -## ⚠️ Preconditions vs Conditions +## ⚠️ Context Resolvers vs Templates vs Conditions vs Preconditions -:::warning Do not confuse Preconditions and Conditions -Preconditions are evaluated during **execution**. -Conditions are evaluated during **planning**. - -See: [Conditions](./conditions) +:::warning Do not confuse these concepts +**[Context Resolvers](./context-resolver.md)** populate `Request.Context.*` during **planning**. +**[Template Substitution](./templates.md)** consumes `Plan` / `Request` / `Workflow` values to build strings. +**[Conditions](./conditions.md)** decide step applicability during **planning** (`NotApplicable`). +**Preconditions** guard step behavior during **execution** (`Skip` / `Fail` / `Continue`). ::: | Preconditions | Conditions | diff --git a/docs/use/workflows/templates.md b/docs/use/workflows/templates.md index 2e36a3d4..c0ece94c 100644 --- a/docs/use/workflows/templates.md +++ b/docs/use/workflows/templates.md @@ -27,6 +27,17 @@ It simply reads values from the context and inserts them into configuration fiel --- +## ⚠️ Context Resolvers vs Templates vs Conditions vs Preconditions + +:::warning Do not confuse these concepts +**[Context Resolvers](./context-resolver.md)** populate `Request.Context.*` during **planning**. +**Template Substitution** consumes `Plan` / `Request` / `Workflow` values to build strings. +**[Conditions](./conditions.md)** decide step applicability during **planning** (`NotApplicable`). +**[Preconditions](./preconditions.md)** guard step behavior during **execution** (`Skip` / `Fail` / `Continue`). +::: + +--- + ## Resolution Context Templates can reference: @@ -151,11 +162,3 @@ During plan build, IdLE validates every template value: - The referenced path may be `$null`. - Validate the request preparation logic before execution. - ---- - -## Related Topics - -- [Workflows](../workflows) -- [Conditions](./conditions) -- [Preconditions](./preconditions) diff --git a/website/sidebars.js b/website/sidebars.js index 8525d8db..157ffc35 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -55,6 +55,7 @@ const sidebars = { 'use/workflows/conditions', 'use/workflows/preconditions', 'use/workflows/templates', + 'use/workflows/context-resolver', ], }, 'use/providers', From cfca91ca3050d5cfea8e232a4359fa5dcf16f907 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <55826276+ntt-matthias-fleschuetz@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:32:52 +0100 Subject: [PATCH 04/28] docs: adding context resolver and cross link in workflow-sub-pages --- docs/use/workflows.md | 49 ++++++++++++++-------- docs/use/workflows/conditions.md | 12 +++--- docs/use/workflows/context-resolver.md | 58 ++++++++++++++++++++++++++ docs/use/workflows/preconditions.md | 12 +++--- docs/use/workflows/templates.md | 19 +++++---- website/sidebars.js | 1 + 6 files changed, 113 insertions(+), 38 deletions(-) create mode 100644 docs/use/workflows/context-resolver.md diff --git a/docs/use/workflows.md b/docs/use/workflows.md index 4979a58a..a6bfef66 100644 --- a/docs/use/workflows.md +++ b/docs/use/workflows.md @@ -50,12 +50,41 @@ When you run IdLE, it happens in two distinct phases: At a high level, a workflow contains: - metadata (name, lifecycle event) -- a list of steps (ordered) +- a list of context resolver instructions (`ContextResolvers`) +- a list of steps (ordered) (`Steps`) - per-step configuration (`With`) - per-step optional execution logic (`Condition`, `Preconditions`, `OnFailureSteps`, etc.) The Big Picture is described in [Concepts](../about/concepts.md). +### Context Resolvers + +Workflows may define a `ContextResolvers` section at workflow root level. + +Resolvers run during **plan build** and populate `Request.Context.*` +using read-only provider capabilities. + +This allows Conditions, Preconditions and Templates to rely on +stable pre-resolved associated data. + +See: [Context Resolvers](./context-resolvers) + +### Template Substitution + +Many step configurations use **template substitution** to insert values from `Plan`, `Request`, and `Workflow` into strings (for example to build a UPN or display name). \ +These `{{path}}` placeholders that are resolved against the +request during plan build (`New-IdlePlan`). Multiple placeholders may appear in a single value. + +```powershell +IdentityKey = '{{Request.IdentityKeys.sAMAccountName}}' +DisplayName = '{{Request.Intent.GivenName}} {{Request.Intent.SurnameName}}' +Message = 'User {{Request.Intent.DisplayName}} is joining.' +``` + +See: [Template Substitution](./workflows/templates) + +### What a step contains + A step is a self-contained unit of work. Most steps follow this pattern: - `Name` (string) – a human-readable identifier @@ -67,7 +96,7 @@ A step is a self-contained unit of work. Most steps follow this pattern: > Step types define which keys are supported inside `With`. See the step reference for details. -### Step execution controls +#### Step execution controls Each step supports several optional execution control properties: @@ -83,22 +112,6 @@ Each step supports several optional execution control properties: --- -## Template Substitution - -Many step configurations use **template substitution** to insert values from `Plan`, `Request`, and `Workflow` into strings (for example to build a UPN or display name). \ -These `{{path}}` placeholders that are resolved against the -request during plan build (`New-IdlePlan`). Multiple placeholders may appear in a single value. - -```powershell -IdentityKey = '{{Request.IdentityKeys.sAMAccountName}}' -DisplayName = '{{Request.Intent.GivenName}} {{Request.Intent.SurnameName}}' -Message = 'User {{Request.Intent.DisplayName}} is joining.' -``` - -See: [Template Substitution](./workflows/templates) - ---- - ## Minimal workflow example This example shows a small workflow with: diff --git a/docs/use/workflows/conditions.md b/docs/use/workflows/conditions.md index 6cea1f36..7c9ab142 100644 --- a/docs/use/workflows/conditions.md +++ b/docs/use/workflows/conditions.md @@ -18,13 +18,13 @@ They decide whether a step becomes part of the executable plan. --- -## ⚠️ Conditions vs Preconditions +## ⚠️ Context Resolvers vs Templates vs Conditions vs Preconditions -:::warning Do not confuse Conditions and Preconditions -Conditions decide step applicability during **planning**. -Preconditions guard step behavior during **execution**. - -See: [Preconditions](./preconditions) +:::warning Do not confuse these concepts +**[Context Resolvers](./context-resolver.md)** populate `Request.Context.*` during **planning**. +**[Template Substitution](./templates.md)** consumes `Plan` / `Request` / `Workflow` values to build strings. +**Conditions** decide step applicability during **planning** (`NotApplicable`). +**[Preconditions](./preconditions.md)** guard step behavior during **execution** (`Skip` / `Fail` / `Continue`). ::: | Conditions | Preconditions | diff --git a/docs/use/workflows/context-resolver.md b/docs/use/workflows/context-resolver.md new file mode 100644 index 00000000..cec38b3e --- /dev/null +++ b/docs/use/workflows/context-resolver.md @@ -0,0 +1,58 @@ +--- +title: Context Resolvers +sidebar_label: Context Resolvers +--- + +## What are Context Resolvers? + +Context Resolvers populate **`Request.Context.*` during planning** using **read-only provider capabilities**. + +- Resolvers run during **plan build** +- They enrich the request with stable, pre-resolved data (for example an entitlement snapshot) +- They are **data-only** and validated strictly (fail-fast) + +This allows **Conditions**, **Preconditions**, and **Template Substitution** to reference values that were resolved once at planning time. + +--- + +## ⚠️ Context Resolvers vs Templates vs Conditions vs Preconditions + +:::warning Do not confuse these concepts +**Context Resolvers** populate `Request.Context.*` during **planning**. +**[Template Substitution](./templates.md)** consumes `Plan` / `Request` / `Workflow` values to build strings. +**[Conditions](conditions.md)** decide step applicability during **planning** (`NotApplicable`). +**[Preconditions](./preconditions.md)** guard step behavior during **execution** (`Skip` / `Fail` / `Continue`). +::: + +--- + +## Common Patterns + +### Entitlement snapshot (without pattern matching) + +Resolve entitlements once during planning: + +```powershell +ContextResolvers = @( + @{ + Capability = 'IdLE.Entitlement.List' + With = @{ IdentityKey = '{{Request.IdentityKeys.EmployeeId}}' } + } +) +``` + +Then guard on availability: + +```powershell +Condition = @{ Exists = 'Request.Context.Identity.Entitlements' } +``` + +> The current Condition DSL does not support list-membership or pattern operators. +> Membership evaluation requires either host-prepared boolean flags or a future DSL enhancement. + +--- + +## Troubleshooting + +- Ensure `ContextResolvers` is defined at workflow root. +- Ensure a provider advertises the requested capability. diff --git a/docs/use/workflows/preconditions.md b/docs/use/workflows/preconditions.md index b69d483a..e472747a 100644 --- a/docs/use/workflows/preconditions.md +++ b/docs/use/workflows/preconditions.md @@ -18,13 +18,13 @@ They protect execution but do not affect planning. --- -## ⚠️ Preconditions vs Conditions +## ⚠️ Context Resolvers vs Templates vs Conditions vs Preconditions -:::warning Do not confuse Preconditions and Conditions -Preconditions are evaluated during **execution**. -Conditions are evaluated during **planning**. - -See: [Conditions](./conditions) +:::warning Do not confuse these concepts +**[Context Resolvers](./context-resolver.md)** populate `Request.Context.*` during **planning**. +**[Template Substitution](./templates.md)** consumes `Plan` / `Request` / `Workflow` values to build strings. +**[Conditions](./conditions.md)** decide step applicability during **planning** (`NotApplicable`). +**Preconditions** guard step behavior during **execution** (`Skip` / `Fail` / `Continue`). ::: | Preconditions | Conditions | diff --git a/docs/use/workflows/templates.md b/docs/use/workflows/templates.md index 2e36a3d4..c0ece94c 100644 --- a/docs/use/workflows/templates.md +++ b/docs/use/workflows/templates.md @@ -27,6 +27,17 @@ It simply reads values from the context and inserts them into configuration fiel --- +## ⚠️ Context Resolvers vs Templates vs Conditions vs Preconditions + +:::warning Do not confuse these concepts +**[Context Resolvers](./context-resolver.md)** populate `Request.Context.*` during **planning**. +**Template Substitution** consumes `Plan` / `Request` / `Workflow` values to build strings. +**[Conditions](./conditions.md)** decide step applicability during **planning** (`NotApplicable`). +**[Preconditions](./preconditions.md)** guard step behavior during **execution** (`Skip` / `Fail` / `Continue`). +::: + +--- + ## Resolution Context Templates can reference: @@ -151,11 +162,3 @@ During plan build, IdLE validates every template value: - The referenced path may be `$null`. - Validate the request preparation logic before execution. - ---- - -## Related Topics - -- [Workflows](../workflows) -- [Conditions](./conditions) -- [Preconditions](./preconditions) diff --git a/website/sidebars.js b/website/sidebars.js index 8525d8db..157ffc35 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -55,6 +55,7 @@ const sidebars = { 'use/workflows/conditions', 'use/workflows/preconditions', 'use/workflows/templates', + 'use/workflows/context-resolver', ], }, 'use/providers', From 9e9802465fb5a84a4e5d841638727dc8ba967648 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <55826276+ntt-matthias-fleschuetz@users.noreply.github.com> Date: Thu, 26 Feb 2026 15:51:19 +0100 Subject: [PATCH 05/28] docs: updated context resolver doku --- docs/use/workflows/context-resolver.md | 130 +++++++++++++++++++++++-- 1 file changed, 120 insertions(+), 10 deletions(-) diff --git a/docs/use/workflows/context-resolver.md b/docs/use/workflows/context-resolver.md index cec38b3e..e8b5733e 100644 --- a/docs/use/workflows/context-resolver.md +++ b/docs/use/workflows/context-resolver.md @@ -7,11 +7,13 @@ sidebar_label: Context Resolvers Context Resolvers populate **`Request.Context.*` during planning** using **read-only provider capabilities**. -- Resolvers run during **plan build** -- They enrich the request with stable, pre-resolved data (for example an entitlement snapshot) -- They are **data-only** and validated strictly (fail-fast) +- They run during **plan build** +- They execute before step `Condition` evaluation +- They enrich the request with stable, pre-resolved associated data +- They are strictly validated and fail fast on invalid configuration -This allows **Conditions**, **Preconditions**, and **Template Substitution** to reference values that were resolved once at planning time. +Context Resolvers allow **Conditions**, **Preconditions**, and **Template Substitution** +to rely on data that was resolved once during planning. --- @@ -26,11 +28,102 @@ This allows **Conditions**, **Preconditions**, and **Template Substitution** to --- +## Full Example + +A resolver entry is defined at workflow root level: + + +```powershell +@{ + Name = 'Joiner - Context Resolver Demo' + LifecycleEvent = 'Joiner' + + ContextResolvers = @( + @{ + Capability = 'IdLE.Identity.Read' + Provider = 'Identity' # optional + With = @{ + IdentityKey = '{{Request.IdentityKeys.EmployeeId}}' + } + } + + @{ + Capability = 'IdLE.Entitlement.List' + With = @{ + IdentityKey = '{{Request.IdentityKeys.EmployeeId}}' + } + } + ) + + Steps = @( + + @{ + Name = 'Disable only if identity exists' + Type = 'IdLE.Step.DisableIdentity' + + Condition = @{ + Exists = 'Request.Context.Identity.Profile' + } + } + + @{ + Name = 'Emit audit event' + Type = 'IdLE.Step.EmitEvent' + + With = @{ + Message = 'Disabled identity {{Request.Context.Identity.Profile.DisplayName}}' + } + } + ) +} +``` + +### Keys + +- `Capability` (required) + A permitted read-only capability. + +- `Provider` (optional) + Provider alias. If omitted, IdLE selects a provider advertising the capability. + +- `With` (required) + Inputs required by the capability. Template substitution is supported. + +Output paths are predefined and cannot be changed. + +--- + ## Common Patterns -### Entitlement snapshot (without pattern matching) +### Resolve once, use everywhere + +Resolve identity or entitlements once and reuse the result in: + +- Conditions +- Preconditions +- Templates -Resolve entitlements once during planning: +Example: + +```powershell +Condition = @{ Exists = 'Request.Context.Identity.Profile' } + +DisplayName = '{{Request.Context.Identity.Profile.DisplayName}}' +``` + +### Guard destructive steps + +Only perform destructive actions if identity exists: + +```powershell +Condition = @{ + Exists = 'Request.Context.Identity.Profile' +} +``` + +### Entitlement snapshot usage + +Resolve entitlements once: ```powershell ContextResolvers = @( @@ -47,12 +140,29 @@ Then guard on availability: Condition = @{ Exists = 'Request.Context.Identity.Entitlements' } ``` -> The current Condition DSL does not support list-membership or pattern operators. -> Membership evaluation requires either host-prepared boolean flags or a future DSL enhancement. - --- ## Troubleshooting +### Resolver not executed + - Ensure `ContextResolvers` is defined at workflow root. -- Ensure a provider advertises the requested capability. +- Verify correct property name (`ContextResolvers`). + +### Capability not permitted + +- Only allowlisted read-only capabilities can be used. +- Validation happens during plan build. + +### Ambiguous provider + +- If multiple providers advertise a capability, specify `Provider` explicitly. + +### Context value missing + +- Verify required `With` parameters. +- Ensure template placeholders resolve correctly. + +### Type conflict in context path + +- A resolver cannot overwrite an existing path with incompatible type. From 5e49684298c26c65e3f1123e2791031792e11987 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <55826276+ntt-matthias-fleschuetz@users.noreply.github.com> Date: Fri, 27 Feb 2026 19:13:51 +0100 Subject: [PATCH 06/28] docs: updated provider docs and template with contextprovider section --- .../providers/_provider-name_template.md | 31 +++++++++++ docs/reference/providers/provider-ad.md | 51 +++++++++++++++++++ .../provider-directorysync-entraconnect.md | 7 +++ docs/reference/providers/provider-entraID.md | 51 +++++++++++++++++++ .../providers/provider-exchangeonline.md | 7 +++ docs/reference/providers/provider-mock.md | 40 +++++++++++++++ 6 files changed, 187 insertions(+) diff --git a/docs/reference/providers/_provider-name_template.md b/docs/reference/providers/_provider-name_template.md index b6620357..a2780118 100644 --- a/docs/reference/providers/_provider-name_template.md +++ b/docs/reference/providers/_provider-name_template.md @@ -117,6 +117,37 @@ $result = Invoke-IdlePlan -Plan $plan -Providers $providers --- + +## Context Resolvers + +> Context Resolvers populate **`Request.Context.*` during planning** using **read-only** provider capabilities. +> Workflow authors can then reference the resolved values in **Conditions**, **Preconditions**, and **Templates**. + +### Supported Context Resolver capabilities + +> Document, **per supported read-only capability**, what your provider writes into `Request.Context.*`. +> - If your provider supports none of the allowlisted capabilities, state that explicitly. +> - Keep this section **reference-style**: focus on *paths*, *shapes*, and *types* (including nested properties). +> - Output paths are **predefined** by the engine and **cannot be changed** by workflow authors. + +#### Capability: `IdLE.Capability.Path` + +Writes to: `Request.Context.Target` +Type: `OutputType` (`PSTypeName = 'IdLE.Identity'`) + +Top-level properties: + +| Property | Type | Notes | +| --- | --- | --- | +| `PSTypeName` | `string` | Always `IdLE.Identity`. | +| `Property1` | `property1-type` | property1 description | +| `PropertyN` | `propertyN-type` | propertyN description | +| `PropertyX` | `hashtable` | optional in case of Key/value bag; keys are strings; values are provider-defined (commonly `string`). | + +`PropertyX` contents: +- List the attributes you populate and their types. +- Only document what workflow authors can *rely on* (stable contract, not incidental Graph/AD fields). + ## Configuration ### Provider creation diff --git a/docs/reference/providers/provider-ad.md b/docs/reference/providers/provider-ad.md index 7e42da22..0fdfef4f 100644 --- a/docs/reference/providers/provider-ad.md +++ b/docs/reference/providers/provider-ad.md @@ -86,6 +86,57 @@ The AD provider supports the common identity lifecycle and entitlement operation | `IdLE.Step.RemoveEntitlement` | Remove managed groups | Prefer explicit allow-lists / managed lists | | `IdLE.Step.DeleteIdentity` | Delete user | **Opt-in** via `-AllowDelete` (see Configuration) | +## Context Resolvers + +This provider supports Context Resolvers for the allowlisted, read-only capabilities below. + +### Capability: `IdLE.Identity.Read` + +Writes to: `Request.Context.Identity.Profile` +Type: `PSCustomObject` (`PSTypeName = 'IdLE.Identity'`) + +Top-level properties: + +| Property | Type | Notes | +| --- | --- | --- | +| `PSTypeName` | `string` | Always `IdLE.Identity`. | +| `IdentityKey` | `string` | The identity key used by the workflow (GUID/UPN/sAMAccountName). | +| `Enabled` | `bool` | Derived from AD user `Enabled`. | +| `Attributes` | `hashtable` | Key/value bag; keys are strings; values are typically `string`. | + +`Attributes` keys populated by this provider (when present on the AD user object): + +| Attribute key | Type | +| --- | --- | +| `GivenName` | `string` | +| `Surname` | `string` | +| `DisplayName` | `string` | +| `Description` | `string` | +| `Department` | `string` | +| `Title` | `string` | +| `EmailAddress` | `string` | +| `UserPrincipalName` | `string` | +| `sAMAccountName` | `string` | +| `DistinguishedName` | `string` | + +### Capability: `IdLE.Entitlement.List` + +Writes to: `Request.Context.Identity.Entitlements` +Type: `object[]` (array of `PSCustomObject`, `PSTypeName = 'IdLE.Entitlement'`) + +Each element represents one AD group membership: + +| Property | Type | Notes | +| --- | --- | --- | +| `PSTypeName` | `string` | Always `IdLE.Entitlement`. | +| `Kind` | `string` | Always `Group`. | +| `Id` | `string` | AD group `DistinguishedName`. | +| `DisplayName` | `string` | AD group `Name`. | + +Notes: +- The output paths are fixed by the engine and cannot be changed. +- Use these values in **Conditions**, **Preconditions**, and **Templates** (resolved during planning). + ## Configuration ### Provider factory diff --git a/docs/reference/providers/provider-directorysync-entraconnect.md b/docs/reference/providers/provider-directorysync-entraconnect.md index e3ed42d3..f4a4ecb5 100644 --- a/docs/reference/providers/provider-directorysync-entraconnect.md +++ b/docs/reference/providers/provider-directorysync-entraconnect.md @@ -92,6 +92,13 @@ Those are typically used by step types like: - `IdLE.Step.TriggerDirectorySync` (trigger + optional wait/poll) +## Context Resolvers + +This provider does **not** support any of the allowlisted Context Resolver capabilities. + +Context Resolvers can only use read-only capabilities like `IdLE.Identity.Read` and `IdLE.Entitlement.List`. +This provider does not advertise these capabilities, so it cannot be used in the workflow `ContextResolvers` section. + ## Configuration This provider has no admin-facing option bag. Configuration is done through: diff --git a/docs/reference/providers/provider-entraID.md b/docs/reference/providers/provider-entraID.md index 1667fef5..da8a984e 100644 --- a/docs/reference/providers/provider-entraID.md +++ b/docs/reference/providers/provider-entraID.md @@ -86,6 +86,57 @@ Recommended wiring in examples: - `AuthSessionOptions = @{ Role = 'Admin' }` for routing (optional) - Use a more privileged role only for privileged actions (e.g. delete) +## Context Resolvers + +This provider supports Context Resolvers for the allowlisted, read-only capabilities below. + +### Capability: `IdLE.Identity.Read` + +Writes to: `Request.Context.Identity.Profile` +Type: `PSCustomObject` (`PSTypeName = 'IdLE.Identity'`) + +Top-level properties: + +| Property | Type | Notes | +| --- | --- | --- | +| `PSTypeName` | `string` | Always `IdLE.Identity`. | +| `IdentityKey` | `string` | The identity key used by the workflow (typically the Entra user `id`). | +| `Enabled` | `bool` | Derived from Entra user `accountEnabled`. | +| `Attributes` | `hashtable` | Key/value bag; keys are strings; values are typically `string`. | + +`Attributes` keys populated by this provider (when present on the user object): + +| Attribute key | Type | Source (Graph field) | +| --- | --- | --- | +| `GivenName` | `string` | `givenName` | +| `Surname` | `string` | `surname` | +| `DisplayName` | `string` | `displayName` | +| `UserPrincipalName` | `string` | `userPrincipalName` | +| `Mail` | `string` | `mail` | +| `Department` | `string` | `department` | +| `JobTitle` | `string` | `jobTitle` | +| `OfficeLocation` | `string` | `officeLocation` | +| `CompanyName` | `string` | `companyName` | + +### Capability: `IdLE.Entitlement.List` + +Writes to: `Request.Context.Identity.Entitlements` +Type: `object[]` (array of `PSCustomObject`, `PSTypeName = 'IdLE.Entitlement'`) + +Each element represents one Entra ID group membership: + +| Property | Type | Notes | +| --- | --- | --- | +| `PSTypeName` | `string` | Always `IdLE.Entitlement`. | +| `Kind` | `string` | Always `Group`. | +| `Id` | `string` | Entra group `id`. | +| `DisplayName` | `string` or `$null` | Group `displayName` (if returned by the adapter). | +| `Mail` | `string` or `$null` | Group `mail` (if returned by the adapter). | + +Notes: +- The output paths are fixed by the engine and cannot be changed. +- Use these values in **Conditions**, **Preconditions**, and **Templates** (resolved during planning). + ## Configuration ### Provider constructor / factory diff --git a/docs/reference/providers/provider-exchangeonline.md b/docs/reference/providers/provider-exchangeonline.md index 89c09ede..064d4790 100644 --- a/docs/reference/providers/provider-exchangeonline.md +++ b/docs/reference/providers/provider-exchangeonline.md @@ -156,6 +156,13 @@ For **app-only** flows, the token's `roles` claim must include: --- +## Context Resolvers + +This provider does **not** support any of the allowlisted Context Resolver capabilities. + +Context Resolvers can only use read-only capabilities like `IdLE.Identity.Read` and `IdLE.Entitlement.List`. +This provider does not advertise these capabilities, so it cannot be used in the workflow `ContextResolvers` section. + ## Configuration ### Provider creation diff --git a/docs/reference/providers/provider-mock.md b/docs/reference/providers/provider-mock.md index 149e5b01..92df852c 100644 --- a/docs/reference/providers/provider-mock.md +++ b/docs/reference/providers/provider-mock.md @@ -61,6 +61,46 @@ No authentication is required. The Mock provider ignores `AuthSessionName`. - Identity: create/update attributes (in-memory) - Entitlements: ensure/remove group memberships (in-memory) +## Context Resolvers + +This provider supports Context Resolvers for the allowlisted, read-only capabilities below. + +### Capability: `IdLE.Identity.Read` + +Writes to: `Request.Context.Identity.Profile` +Type: `PSCustomObject` (`PSTypeName = 'IdLE.Identity'`) + +Top-level properties: + +| Property | Type | Notes | +| --- | --- | --- | +| `PSTypeName` | `string` | Always `IdLE.Identity`. | +| `IdentityKey` | `string` | The identity key used by the workflow. | +| `Enabled` | `bool` | Stored boolean value (defaults to `$true` when created on demand). | +| `Attributes` | `hashtable` | Free-form key/value bag stored in the mock provider store. | + +Mock-specific behavior: +- Missing identities are created **on-demand** on first `GetIdentity` call (planning-time resolvers may therefore “create” a record in the in-memory store). +- `Attributes` is whatever your tests/demos put into the store (commonly `string` values). + +### Capability: `IdLE.Entitlement.List` + +Writes to: `Request.Context.Identity.Entitlements` +Type: `object[]` (array of `PSCustomObject`, `PSTypeName = 'IdLE.Entitlement'`) + +Each element is normalized via `ConvertToEntitlement`: + +| Property | Type | Notes | +| --- | --- | --- | +| `PSTypeName` | `string` | Always `IdLE.Entitlement`. | +| `Kind` | `string` | Required; non-empty. | +| `Id` | `string` | Required; non-empty. | +| `DisplayName` | `string` or `$null` | Optional. | + +Notes: +- The output paths are fixed by the engine and cannot be changed. +- Use these values in **Conditions**, **Preconditions**, and **Templates** (resolved during planning). + ## Configuration This provider has no admin-facing options. From dace1c27c03a4392f75aeeb9852445a529bbe462 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <55826276+ntt-matthias-fleschuetz@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:07:35 +0100 Subject: [PATCH 07/28] docs: fixed broken link --- docs/use/workflows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use/workflows.md b/docs/use/workflows.md index bb57e07a..a7cd473f 100644 --- a/docs/use/workflows.md +++ b/docs/use/workflows.md @@ -113,7 +113,7 @@ using read-only provider capabilities. This allows Conditions, Preconditions and Templates to rely on stable pre-resolved associated data. -See: [Context Resolvers](./context-resolvers) +See: [Context Resolvers](./workflows/context-resolver.md) ### Template Substitution From b56411fe0f771ba7b17aee52bda13fdca3a271d0 Mon Sep 17 00:00:00 2001 From: Matthias <13959569+blindzero@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:10:49 +0100 Subject: [PATCH 08/28] Update docs/use/workflows.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/use/workflows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use/workflows.md b/docs/use/workflows.md index a7cd473f..4dfcfd0a 100644 --- a/docs/use/workflows.md +++ b/docs/use/workflows.md @@ -123,7 +123,7 @@ request during plan build (`New-IdlePlan`). Multiple placeholders may appear in ```powershell IdentityKey = '{{Request.IdentityKeys.sAMAccountName}}' -DisplayName = '{{Request.Intent.GivenName}} {{Request.Intent.SurnameName}}' +DisplayName = '{{Request.Intent.GivenName}} {{Request.Intent.Surname}}' Message = 'User {{Request.Intent.DisplayName}} is joining.' ``` From 7ba692519816188116c1a7305570d7fd55cb4c17 Mon Sep 17 00:00:00 2001 From: Matthias <13959569+blindzero@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:12:17 +0100 Subject: [PATCH 09/28] Update docs/use/workflows.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/use/workflows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use/workflows.md b/docs/use/workflows.md index 4dfcfd0a..ff09851c 100644 --- a/docs/use/workflows.md +++ b/docs/use/workflows.md @@ -162,7 +162,7 @@ Each step supports several optional execution control properties: This example shows a small workflow with: -- a value containing a [template substition](./workflows/templates.md) +- a value containing a [template substitution](./workflows/templates.md) - a step that is only applicable for `Joiner` ([Condition](./workflows/conditions.md)) - a step that is guarded at runtime ([Preconditions](./workflows/preconditions.md)) From 25f0fb1b7105b523cb72ad19f3b522d33f11dfa5 Mon Sep 17 00:00:00 2001 From: Matthias <13959569+blindzero@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:12:32 +0100 Subject: [PATCH 10/28] Update docs/use/workflows/templates.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/use/workflows/templates.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use/workflows/templates.md b/docs/use/workflows/templates.md index c0ece94c..d12b76e8 100644 --- a/docs/use/workflows/templates.md +++ b/docs/use/workflows/templates.md @@ -58,7 +58,7 @@ Templates can reference: ```powershell @{ Name = 'Create UPN' - Type = 'IdLE.Step.EnsureAttribute' + Type = 'IdLE.Step.EnsureAttributes' With = @{ UserPrincipalName = '{{Request.IdentityKeys.FirstName}}.{{Request.IdentityKeys.LastName}}@example.com' From 9dac3589577a84a327bb2d7953f0782c65639cd6 Mon Sep 17 00:00:00 2001 From: Matthias <13959569+blindzero@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:12:41 +0100 Subject: [PATCH 11/28] Update docs/use/workflows.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/use/workflows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use/workflows.md b/docs/use/workflows.md index ff09851c..4245738c 100644 --- a/docs/use/workflows.md +++ b/docs/use/workflows.md @@ -134,7 +134,7 @@ See: [Template Substitution](./workflows/templates) A step is a self-contained unit of work. Most steps follow this pattern: - `Name` (string) – a human-readable identifier -- `Type` (string) – the step type (for example `IdLE.Step.EnsureAttribute`) +- `Type` (string) – the step type (for example `IdLE.Step.EnsureAttributes`) - `With` (hashtable) – step-specific configuration - `Condition` (hashtable, optional) – optional planning-time applicability - `Preconditions` (hashtable, optional) – optional execution-time guard From 5401cd38e985fab57ad06b22cb7c90dcbaa06560 Mon Sep 17 00:00:00 2001 From: Matthias <13959569+blindzero@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:28:52 +0100 Subject: [PATCH 12/28] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/use/workflows/conditions.md | 4 ++-- docs/use/workflows/context-resolver.md | 4 ++-- docs/use/workflows/preconditions.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/use/workflows/conditions.md b/docs/use/workflows/conditions.md index 7c9ab142..26d3b74f 100644 --- a/docs/use/workflows/conditions.md +++ b/docs/use/workflows/conditions.md @@ -22,9 +22,9 @@ They decide whether a step becomes part of the executable plan. :::warning Do not confuse these concepts **[Context Resolvers](./context-resolver.md)** populate `Request.Context.*` during **planning**. -**[Template Substitution](./templates.md)** consumes `Plan` / `Request` / `Workflow` values to build strings. +**[Template Substitution](./templates.md)** consumes `Request.*` values to build strings. **Conditions** decide step applicability during **planning** (`NotApplicable`). -**[Preconditions](./preconditions.md)** guard step behavior during **execution** (`Skip` / `Fail` / `Continue`). +**[Preconditions](./preconditions.md)** guard step behavior during **execution** (`Blocked` / `Fail` / `Continue`). ::: | Conditions | Preconditions | diff --git a/docs/use/workflows/context-resolver.md b/docs/use/workflows/context-resolver.md index e8b5733e..d02281a8 100644 --- a/docs/use/workflows/context-resolver.md +++ b/docs/use/workflows/context-resolver.md @@ -21,9 +21,9 @@ to rely on data that was resolved once during planning. :::warning Do not confuse these concepts **Context Resolvers** populate `Request.Context.*` during **planning**. -**[Template Substitution](./templates.md)** consumes `Plan` / `Request` / `Workflow` values to build strings. +**[Template Substitution](./templates.md)** consumes `Request.*` values to build strings. **[Conditions](conditions.md)** decide step applicability during **planning** (`NotApplicable`). -**[Preconditions](./preconditions.md)** guard step behavior during **execution** (`Skip` / `Fail` / `Continue`). +**[Preconditions](./preconditions.md)** guard step behavior during **execution** (`Blocked` / `Fail` / `Continue`). ::: --- diff --git a/docs/use/workflows/preconditions.md b/docs/use/workflows/preconditions.md index 6ddf23cf..db4b853e 100644 --- a/docs/use/workflows/preconditions.md +++ b/docs/use/workflows/preconditions.md @@ -22,7 +22,7 @@ They protect execution but do not affect planning. :::warning Do not confuse these concepts **[Context Resolvers](./context-resolver.md)** populate `Request.Context.*` during **planning**. -**[Template Substitution](./templates.md)** consumes `Plan` / `Request` / `Workflow` values to build strings. +**[Template Substitution](./templates.md)** uses allowlisted `Request.*` values (such as `Request.Context.*`) to build strings. **[Conditions](./conditions.md)** decide step applicability during **planning** (`NotApplicable`). **Preconditions** guard step behavior during **execution** (`Skip` / `Fail` / `Continue`). ::: @@ -67,7 +67,7 @@ If the precondition evaluates to false: ## Condition DSL :::tip Preconditions use the same **Condition DSL** as Conditions. -For the complete DSL reference, see: [Conditions → Condition DSL](./conditions) +For the complete DSL reference, see: [Conditions → Condition DSL](./conditions.md) ::: --- From f7d9f0b31a80f300ad27c01b60ef9c8bdfd959cf Mon Sep 17 00:00:00 2001 From: Matthias <13959569+blindzero@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:29:54 +0100 Subject: [PATCH 13/28] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/use/workflows.md | 6 +++--- docs/use/workflows/templates.md | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/use/workflows.md b/docs/use/workflows.md index 4245738c..95152f34 100644 --- a/docs/use/workflows.md +++ b/docs/use/workflows.md @@ -117,9 +117,9 @@ See: [Context Resolvers](./workflows/context-resolver.md) ### Template Substitution -Many step configurations use **template substitution** to insert values from `Plan`, `Request`, and `Workflow` into strings (for example to build a UPN or display name). \ -These `{{path}}` placeholders that are resolved against the -request during plan build (`New-IdlePlan`). Multiple placeholders may appear in a single value. +Many step configurations use **template substitution** to insert values from the incoming request into strings (for example to build a UPN or display name). \ +These `{{Request.*}}` placeholders are resolved against the +request during plan build (`New-IdlePlan`). Only `Request.*` roots are allowed (for example `Request.Intent`, `Request.Context`, `Request.IdentityKeys`, `Request.LifecycleEvent`). Multiple placeholders may appear in a single value. ```powershell IdentityKey = '{{Request.IdentityKeys.sAMAccountName}}' diff --git a/docs/use/workflows/templates.md b/docs/use/workflows/templates.md index d12b76e8..1615f3a0 100644 --- a/docs/use/workflows/templates.md +++ b/docs/use/workflows/templates.md @@ -5,7 +5,7 @@ sidebar_label: Template Substitution # Template Substitution -Template substitution allows you to reference values from the **planning context** inside step configuration (`With`), Conditions, and Preconditions. +Template substitution allows you to reference values from the **planning context** inside step configuration (`With`) values during planning. Conditions and Preconditions use the condition DSL and path resolution and do **not** support `{{...}}` templates. Templates are **data-only** and safe. No ScriptBlocks or dynamic PowerShell expressions are supported. @@ -33,7 +33,7 @@ It simply reads values from the context and inserts them into configuration fiel **[Context Resolvers](./context-resolver.md)** populate `Request.Context.*` during **planning**. **Template Substitution** consumes `Plan` / `Request` / `Workflow` values to build strings. **[Conditions](./conditions.md)** decide step applicability during **planning** (`NotApplicable`). -**[Preconditions](./preconditions.md)** guard step behavior during **execution** (`Skip` / `Fail` / `Continue`). +**[Preconditions](./preconditions.md)** guard step behavior during **execution** (`Blocked` / `Fail` / `Continue`). ::: --- @@ -74,7 +74,7 @@ If: The resolved value becomes: ``` -john.doe@example.com +John.Doe@example.com ``` --- @@ -101,7 +101,7 @@ DisplayName = '{{Request.IdentityKeys.FirstName}} {{Request.IdentityKeys.LastNam A value with surrounding text always produces a **string**: ```powershell -Description = 'Provisioned during {{Plan.LifecycleEvent}}' +Description = 'Provisioned during {{Request.LifecycleEvent}}' ``` ### Backslash and special characters From 3976d65f9dbb6771ed67e101fb42c2e2ed555311 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <55826276+ntt-matthias-fleschuetz@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:31:45 +0100 Subject: [PATCH 14/28] docs: fix Precondition plural --- docs/use/workflows.md | 2 +- docs/use/workflows/conditions.md | 2 +- docs/use/workflows/preconditions.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/use/workflows.md b/docs/use/workflows.md index a7cd473f..b1c11075 100644 --- a/docs/use/workflows.md +++ b/docs/use/workflows.md @@ -194,7 +194,7 @@ This example shows a small workflow with: Name = 'Disable identity only if it exists' Type = 'IdLE.Step.DisableIdentity' - Preconditions = @{ + Precondition = @{ Equals = @{ Path = 'Request.Context.IdentityExists'; Value = 'True' } } diff --git a/docs/use/workflows/conditions.md b/docs/use/workflows/conditions.md index 7c9ab142..74f97b6f 100644 --- a/docs/use/workflows/conditions.md +++ b/docs/use/workflows/conditions.md @@ -27,7 +27,7 @@ They decide whether a step becomes part of the executable plan. **[Preconditions](./preconditions.md)** guard step behavior during **execution** (`Skip` / `Fail` / `Continue`). ::: -| Conditions | Preconditions | +| Condition | Precondition | |------------|--------------| | Planning time | Execution time | | Marks step `NotApplicable` | Controls runtime behavior | diff --git a/docs/use/workflows/preconditions.md b/docs/use/workflows/preconditions.md index 6ddf23cf..8238b72f 100644 --- a/docs/use/workflows/preconditions.md +++ b/docs/use/workflows/preconditions.md @@ -27,7 +27,7 @@ They protect execution but do not affect planning. **Preconditions** guard step behavior during **execution** (`Skip` / `Fail` / `Continue`). ::: -| Preconditions | Conditions | +| Precondition | Condition | |--------------|------------| | Execution time | Planning time | | Controls runtime behavior | Marks step `NotApplicable` | From 4ed27513c05d1ea830a0503b222075463848617e Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <55826276+ntt-matthias-fleschuetz@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:34:37 +0100 Subject: [PATCH 15/28] docs: fix wrong allowed paths for substitution --- docs/use/workflows/templates.md | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/docs/use/workflows/templates.md b/docs/use/workflows/templates.md index 1615f3a0..d96868e7 100644 --- a/docs/use/workflows/templates.md +++ b/docs/use/workflows/templates.md @@ -14,11 +14,7 @@ No ScriptBlocks or dynamic PowerShell expressions are supported. ## What is Template Substitution? -Template substitution resolves values from: - -- `Plan` -- `Request` -- `Workflow` +Template substitution resolves values from: `Request.*` It replaces template placeholders with actual values before execution. From 5b81cb0c52a2b98d479074714dc2c84f985034b1 Mon Sep 17 00:00:00 2001 From: Matthias <13959569+blindzero@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:35:45 +0100 Subject: [PATCH 16/28] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/use/workflows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use/workflows.md b/docs/use/workflows.md index 8da76822..65ff58c2 100644 --- a/docs/use/workflows.md +++ b/docs/use/workflows.md @@ -198,7 +198,7 @@ This example shows a small workflow with: Equals = @{ Path = 'Request.Context.IdentityExists'; Value = 'True' } } - OnPreconditionFalse = 'Skip' + OnPreconditionFalse = 'Continue' } ) } From 224e1df44b0549f3ffdafd0ae0cde8ec6e49139b Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <55826276+ntt-matthias-fleschuetz@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:39:06 +0100 Subject: [PATCH 17/28] docs: fix OnPreconditionFalse to Continue not Skip --- docs/use/workflows/preconditions.md | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/docs/use/workflows/preconditions.md b/docs/use/workflows/preconditions.md index cf4993ab..ecdec50b 100644 --- a/docs/use/workflows/preconditions.md +++ b/docs/use/workflows/preconditions.md @@ -228,7 +228,7 @@ a `Blocked` outcome and a message instructing the operator to perform the wipe m `Precondition` uses the same **declarative condition DSL** as the `Condition` property. Supported operators: -- Check `OnPreconditionFalse`. If it is set to `Skip`, a false precondition will skip execution by design. +- Check `OnPreconditionFalse`. If it is set to `Continue`, a false precondition will skip execution by design. - Validate that the precondition `Path` resolves to the expected runtime value. ### Step fails due to precondition @@ -245,12 +245,3 @@ property. Supported operators: Preconditions use the same Condition DSL as Conditions. For the complete DSL reference, see: [Conditions → Condition DSL](./conditions) -At planning time, IdLE validates `Path` references to fail fast on typos and wrong roots. For `Precondition`, unresolved paths under `Request.Context.*` are treated as soft (non-fatal) to support context enrichment that may arrive later at runtime (for example via host/runtime context resolver behavior). Other unresolved roots still fail fast. -When this soft-check path is used, IdLE records a planning warning (`PreconditionContextPathUnresolvedAtPlan`) in `Plan.Warnings`, and the warning is included in `Export-IdlePlan` output for CI policy checks. - ---- - -## Reference - -- [Steps reference](../../reference/steps.md) -- [Concepts: Plan → Execute separation](../../about/concepts.md) From ee15db8c4ad83e91eb6bdd2ade7cd04cc70b4a8f Mon Sep 17 00:00:00 2001 From: Matthias <13959569+blindzero@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:39:46 +0100 Subject: [PATCH 18/28] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/use/workflows/preconditions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/use/workflows/preconditions.md b/docs/use/workflows/preconditions.md index ecdec50b..0b9c3ec0 100644 --- a/docs/use/workflows/preconditions.md +++ b/docs/use/workflows/preconditions.md @@ -42,11 +42,11 @@ They protect execution but do not affect planning. Name = 'Disable existing identity' Type = 'IdLE.Step.DisableIdentity' - Preconditions = @{ + Precondition = @{ Equals = @{ Path = 'Request.Context.IdentityExists'; Value = 'True' } } - OnPreconditionFalse = 'Skip' + OnPreconditionFalse = 'Continue' } ``` From db0f89af85d33785d2593fac8646314cf784eb93 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <55826276+ntt-matthias-fleschuetz@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:39:06 +0100 Subject: [PATCH 19/28] docs: fix OnPreconditionFalse to Continue not Skip --- docs/use/workflows/preconditions.md | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/docs/use/workflows/preconditions.md b/docs/use/workflows/preconditions.md index cf4993ab..258342f7 100644 --- a/docs/use/workflows/preconditions.md +++ b/docs/use/workflows/preconditions.md @@ -46,7 +46,7 @@ They protect execution but do not affect planning. Equals = @{ Path = 'Request.Context.IdentityExists'; Value = 'True' } } - OnPreconditionFalse = 'Skip' + OnPreconditionFalse = 'Continue' } ``` @@ -58,7 +58,7 @@ The step executes only if: If the precondition evaluates to false: -- `Skip` → step is skipped +- `Continue` → step is skipped - `Fail` → execution fails - `Continue` → execution continues @@ -228,7 +228,7 @@ a `Blocked` outcome and a message instructing the operator to perform the wipe m `Precondition` uses the same **declarative condition DSL** as the `Condition` property. Supported operators: -- Check `OnPreconditionFalse`. If it is set to `Skip`, a false precondition will skip execution by design. +- Check `OnPreconditionFalse`. If it is set to `Continue`, a false precondition will skip execution by design. - Validate that the precondition `Path` resolves to the expected runtime value. ### Step fails due to precondition @@ -245,12 +245,3 @@ property. Supported operators: Preconditions use the same Condition DSL as Conditions. For the complete DSL reference, see: [Conditions → Condition DSL](./conditions) -At planning time, IdLE validates `Path` references to fail fast on typos and wrong roots. For `Precondition`, unresolved paths under `Request.Context.*` are treated as soft (non-fatal) to support context enrichment that may arrive later at runtime (for example via host/runtime context resolver behavior). Other unresolved roots still fail fast. -When this soft-check path is used, IdLE records a planning warning (`PreconditionContextPathUnresolvedAtPlan`) in `Plan.Warnings`, and the warning is included in `Export-IdlePlan` output for CI policy checks. - ---- - -## Reference - -- [Steps reference](../../reference/steps.md) -- [Concepts: Plan → Execute separation](../../about/concepts.md) From 2c9562117a521a6fb7fe4cb1f239c10a02d5b002 Mon Sep 17 00:00:00 2001 From: Matthias <13959569+blindzero@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:41:36 +0100 Subject: [PATCH 20/28] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/use/workflows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use/workflows.md b/docs/use/workflows.md index 65ff58c2..72829935 100644 --- a/docs/use/workflows.md +++ b/docs/use/workflows.md @@ -154,7 +154,7 @@ Each step supports several optional execution control properties: :::warning Do not confuse Conditions and Preconditions **Conditions** decide step applicability during **planning** (a step becomes `NotApplicable`). -**Preconditions** guard step behavior during **execution** (`Skip` / `Fail` / `Continue`). +**Preconditions** guard step behavior during **execution** (`OnPreconditionFalse` can mark the step `Blocked`, `Failed`, or allow it to `Continue`). --- From 7b52f07d90068887f143bf1dcc5a707f277cdb31 Mon Sep 17 00:00:00 2001 From: Matthias <13959569+blindzero@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:41:55 +0100 Subject: [PATCH 21/28] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/use/workflows/templates.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/use/workflows/templates.md b/docs/use/workflows/templates.md index d96868e7..b4c8c8ba 100644 --- a/docs/use/workflows/templates.md +++ b/docs/use/workflows/templates.md @@ -27,7 +27,7 @@ It simply reads values from the context and inserts them into configuration fiel :::warning Do not confuse these concepts **[Context Resolvers](./context-resolver.md)** populate `Request.Context.*` during **planning**. -**Template Substitution** consumes `Plan` / `Request` / `Workflow` values to build strings. +**Template Substitution** consumes allowlisted `Request.*` values to build strings. **[Conditions](./conditions.md)** decide step applicability during **planning** (`NotApplicable`). **[Preconditions](./preconditions.md)** guard step behavior during **execution** (`Blocked` / `Fail` / `Continue`). ::: @@ -151,8 +151,8 @@ During plan build, IdLE validates every template value: ### Placeholder not resolved -- Verify the path exists in the request or plan context. -- Ensure correct casing and full path (e.g. `Request.Context.*`). +- Verify the path exists on the request object (allowed `Request.*` roots only). +- Ensure correct casing and full path (for example, `Request.Context.*`). ### Empty value after substitution From 03bb4da89dce213aae6a7f2daa22e5a8bea89175 Mon Sep 17 00:00:00 2001 From: Matthias <13959569+blindzero@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:48:51 +0100 Subject: [PATCH 22/28] Update docs/use/workflows/preconditions.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/use/workflows/preconditions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/use/workflows/preconditions.md b/docs/use/workflows/preconditions.md index 4eafa2ce..093f765c 100644 --- a/docs/use/workflows/preconditions.md +++ b/docs/use/workflows/preconditions.md @@ -58,9 +58,9 @@ The step executes only if: If the precondition evaluates to false: -- `Continue` → step is skipped -- `Fail` → execution fails -- `Continue` → execution continues +- `Blocked` → step is marked `Blocked`; workflow stops at this step +- `Fail` → execution fails immediately +- `Continue` → step is skipped; workflow continues --- From a6ab2863c4c8de9cf9bd2b2e8eb1be034919c49b Mon Sep 17 00:00:00 2001 From: Matthias <13959569+blindzero@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:49:07 +0100 Subject: [PATCH 23/28] Update docs/use/workflows.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/use/workflows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use/workflows.md b/docs/use/workflows.md index 72829935..cb22b4b9 100644 --- a/docs/use/workflows.md +++ b/docs/use/workflows.md @@ -86,7 +86,7 @@ When you run IdLE, it happens in two distinct phases: 2. **Execution (Plan Run)** IdLE executes the planned steps and records results. - - `Precondition` are evaluated here. + - `Precondition` is evaluated here. - If a precondition is false, `OnPreconditionFalse` decides what happens (for example `Skip` or `Fail`). --- From 075f6e0c95a38265a866aeca3d96defdf9fe5310 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <55826276+ntt-matthias-fleschuetz@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:51:57 +0100 Subject: [PATCH 24/28] docs: remove duplicate minimal example --- docs/use/workflows.md | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/docs/use/workflows.md b/docs/use/workflows.md index cb22b4b9..084b2a98 100644 --- a/docs/use/workflows.md +++ b/docs/use/workflows.md @@ -38,27 +38,6 @@ Each step supports several optional execution control properties: --- -## Minimal workflow example - -```powershell -@{ - Name = 'Joiner - Minimal' - LifecycleEvent = 'Joiner' - - Steps = @( - @{ - Name = 'Emit start' - Type = 'IdLE.Step.EmitEvent' - With = @{ - Message = 'Starting Joiner workflow' - } - } - ) -} -``` - ---- - ## How workflows are used in the lifecycle 1. You write the workflow definition (`.psd1`). @@ -127,7 +106,7 @@ DisplayName = '{{Request.Intent.GivenName}} {{Request.Intent.Surname}}' Message = 'User {{Request.Intent.DisplayName}} is joining.' ``` -See: [Template Substitution](./workflows/templates) +See: [Template Substitution](./workflows/templates.md) ### What a step contains @@ -158,7 +137,7 @@ Each step supports several optional execution control properties: --- -## Minimal workflow example +## Workflow example This example shows a small workflow with: From 321903d1720294df22800d8baed4b1c293a24bb3 Mon Sep 17 00:00:00 2001 From: Matthias <13959569+blindzero@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:52:59 +0100 Subject: [PATCH 25/28] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/use/workflows/preconditions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use/workflows/preconditions.md b/docs/use/workflows/preconditions.md index 093f765c..c6ec8775 100644 --- a/docs/use/workflows/preconditions.md +++ b/docs/use/workflows/preconditions.md @@ -244,4 +244,4 @@ property. Supported operators: ### Where is the DSL documented? Preconditions use the same Condition DSL as Conditions. For the complete DSL reference, see: -[Conditions → Condition DSL](./conditions) +[Conditions → Condition DSL](./conditions.md) From 57bd427d99e87766968f0096a62522f71df6a4d5 Mon Sep 17 00:00:00 2001 From: Matthias <13959569+blindzero@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:52:59 +0100 Subject: [PATCH 26/28] Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/use/workflows/preconditions.md | 2 +- docs/use/workflows/templates.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/use/workflows/preconditions.md b/docs/use/workflows/preconditions.md index 093f765c..c6ec8775 100644 --- a/docs/use/workflows/preconditions.md +++ b/docs/use/workflows/preconditions.md @@ -244,4 +244,4 @@ property. Supported operators: ### Where is the DSL documented? Preconditions use the same Condition DSL as Conditions. For the complete DSL reference, see: -[Conditions → Condition DSL](./conditions) +[Conditions → Condition DSL](./conditions.md) diff --git a/docs/use/workflows/templates.md b/docs/use/workflows/templates.md index b4c8c8ba..02a8daa0 100644 --- a/docs/use/workflows/templates.md +++ b/docs/use/workflows/templates.md @@ -14,7 +14,7 @@ No ScriptBlocks or dynamic PowerShell expressions are supported. ## What is Template Substitution? -Template substitution resolves values from: `Request.*` +Template substitution resolves values from: `Request.*`g It replaces template placeholders with actual values before execution. From e9b35394c0605ee0db7b4f76a93d60e2cf05f804 Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <55826276+ntt-matthias-fleschuetz@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:57:09 +0100 Subject: [PATCH 27/28] docs: remove duplicate sections --- docs/use/workflows.md | 119 +++++++++++++++++------------------------- 1 file changed, 48 insertions(+), 71 deletions(-) diff --git a/docs/use/workflows.md b/docs/use/workflows.md index 084b2a98..fc64524e 100644 --- a/docs/use/workflows.md +++ b/docs/use/workflows.md @@ -15,29 +15,6 @@ Workflows are designed for **admins and workflow authors**: --- -## What a workflow contains - -At a high level, a workflow contains: - -- metadata (name, lifecycle event) -- a list of steps (ordered) -- per-step configuration (`With`) -- optional execution logic (conditions, `OnFailureSteps`, etc.) - -The Big Picture is described in [Concepts](../about/concepts.md). - -### Step execution controls - -Each step supports several optional execution control properties: - -| Property | Evaluated at | Purpose | -|---|---|---| -| `Condition` | Plan time | Include or skip the step based on request/intent data. | -| `Precondition` | Execution time (runtime) | Guard the step against stale or unsafe state immediately before it runs. See [Runtime Preconditions](workflows/preconditions.md). | -| `OnFailureSteps` | After failure (workflow-level) | Cleanup/rollback steps run after a primary step fails. | - ---- - ## How workflows are used in the lifecycle 1. You write the workflow definition (`.psd1`). @@ -70,6 +47,54 @@ When you run IdLE, it happens in two distinct phases: --- +## Workflow example + +This example shows a small workflow with: + +- a value containing a [template substitution](./workflows/templates.md) +- a step that is only applicable for `Joiner` ([Condition](./workflows/conditions.md)) +- a step that is guarded at runtime ([Preconditions](./workflows/preconditions.md)) + + +```powershell +@{ + Name = 'Joiner - Standard' + LifecycleEvent = 'Joiner' + + Steps = @( + @{ + Name = 'Emit start' + Type = 'IdLE.Step.EmitEvent' + With = @{ Message = 'Starting Joiner for {{Request.Intent.FullName}}' } + } + + @{ + Name = 'Provision only for Joiner' + Type = 'IdLE.Step.EmitEvent' + + Condition = @{ + Equals = @{ Path = 'Plan.LifecycleEvent'; Value = 'Joiner' } + } + + With = @{ Message = 'Provisioning for Joiner' } + } + + @{ + Name = 'Disable identity only if it exists' + Type = 'IdLE.Step.DisableIdentity' + + Precondition = @{ + Equals = @{ Path = 'Request.Context.IdentityExists'; Value = 'True' } + } + + OnPreconditionFalse = 'Continue' + } + ) +} +``` + +--- + ## What a workflow contains At a high level, a workflow contains: @@ -137,54 +162,6 @@ Each step supports several optional execution control properties: --- -## Workflow example - -This example shows a small workflow with: - -- a value containing a [template substitution](./workflows/templates.md) -- a step that is only applicable for `Joiner` ([Condition](./workflows/conditions.md)) -- a step that is guarded at runtime ([Preconditions](./workflows/preconditions.md)) - - -```powershell -@{ - Name = 'Joiner - Standard' - LifecycleEvent = 'Joiner' - - Steps = @( - @{ - Name = 'Emit start' - Type = 'IdLE.Step.EmitEvent' - With = @{ Message = 'Starting Joiner for {{Request.Intent.FullName}}' } - } - - @{ - Name = 'Provision only for Joiner' - Type = 'IdLE.Step.EmitEvent' - - Condition = @{ - Equals = @{ Path = 'Plan.LifecycleEvent'; Value = 'Joiner' } - } - - With = @{ Message = 'Provisioning for Joiner' } - } - - @{ - Name = 'Disable identity only if it exists' - Type = 'IdLE.Step.DisableIdentity' - - Precondition = @{ - Equals = @{ Path = 'Request.Context.IdentityExists'; Value = 'True' } - } - - OnPreconditionFalse = 'Continue' - } - ) -} -``` - ---- - ## Common pitfalls - **Not data-only:** embedding ScriptBlocks or secrets in workflow files (not allowed). From 35858815eab784f583918a7baac570d84bb5b32c Mon Sep 17 00:00:00 2001 From: Matthias Fleschuetz <55826276+ntt-matthias-fleschuetz@users.noreply.github.com> Date: Fri, 27 Feb 2026 20:58:35 +0100 Subject: [PATCH 28/28] docs: added clarity on what happens next for Precondition failure --- docs/use/workflows.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/use/workflows.md b/docs/use/workflows.md index fc64524e..c1f16518 100644 --- a/docs/use/workflows.md +++ b/docs/use/workflows.md @@ -43,7 +43,7 @@ When you run IdLE, it happens in two distinct phases: IdLE executes the planned steps and records results. - `Precondition` is evaluated here. - - If a precondition is false, `OnPreconditionFalse` decides what happens (for example `Skip` or `Fail`). + - If a precondition is false, `OnPreconditionFalse` decides what happens (`Blocked`, `Fail`, or `Continue`). `Continue` causes the step to be recorded with status `PreconditionSkipped`. ---