Problem Statement
IdLE.Step.EnsureAttribute currently supports exactly one attribute (With.Name + With.Value) per step.
In real-world workflows we often need to converge many attributes for the same identity. This forces authors to add multiple EnsureAttribute steps, which creates:
- unnecessary workflow verbosity and maintenance overhead,
- larger plans (harder to review / export / troubleshoot),
- repeated provider/session resolution overhead in the engine.
The current limitation is visible in the implementation: Invoke-IdleStepEnsureAttribute enforces With.IdentityKey, With.Name, With.Value and calls provider method EnsureAttribute(IdentityKey, Name, Value). The built-in step registry only maps IdLE.Step.EnsureAttribute to Invoke-IdleStepEnsureAttribute.
Proposed Solution
Introduce a plural step type that can ensure multiple attributes in one step:
- New step type:
IdLE.Step.EnsureAttributes
- New handler:
Invoke-IdleStepEnsureAttributes
- New schema (example):
@{
Name = 'Ensure core user attributes'
Type = 'IdLE.Step.EnsureAttributes'
With = @{
Provider = 'AD' # optional, default: Identity
IdentityKey = '{{Request.IdentityKeys.SamAccountName}}'
Attributes = @{
GivenName = 'Max'
Surname = 'Power'
UserPrincipalName = 'max.power@contoso.com'
}
# AuthSessionName / AuthSessionOptions like other steps (optional)
}
}
Provider interaction / performance
To keep providers stable while still allowing optimizations:
-
Preferred (optional) fast path: If the provider implements a batch method EnsureAttributes, call it.
- Proposed signature:
EnsureAttributes(IdentityKey, AttributesHashtable)
- Optional
AuthSession parameter support (same discovery behavior as Invoke-IdleProviderMethod already uses).
-
Fallback (no provider change required): If EnsureAttributes is not available, iterate keys and call the existing EnsureAttribute(IdentityKey, Name, Value) for each attribute.
Result shape
StepResult.Changed should be true if any attribute was changed.
- Additionally, return per-attribute details in a structured way (e.g.
StepResult.Data.Attributes), without leaking secrets:
- AttributeName
- Changed (bool)
- Error (null/string)
- (Optional) Before/After only if engine already allows it safely (default should not emit values).
Backward compatibility / migration
We should switch the primary documentation and examples to EnsureAttributes.
For compatibility we have two viable options (choose one during implementation; default recommendation: A):
-
Option A (recommended, non-breaking for existing workflows):
- Keep
IdLE.Step.EnsureAttribute working.
- Implement it as a thin wrapper that internally calls the plural handler with a single entry.
- Mark
IdLE.Step.EnsureAttribute as deprecated in docs and changelog; schedule removal for a future major (1.0+) if desired.
-
Option B (breaking, pre-1.0 acceptable):
- Remove
IdLE.Step.EnsureAttribute from the built-in registry and docs.
- Workflows using it must be updated to
IdLE.Step.EnsureAttributes.
Given the user goal is mainly workflow overhead, Option A achieves that immediately (new workflows use plural), while existing workflows keep running.
Alternatives Considered
-
Keep step singular but enhance workflow syntax with loops/macros
→ rejected: IdLE workflows are intentionally data-only; adding looping constructs increases complexity and undermines simplicity/security.
-
Add With.Names/With.Values arrays
→ rejected: risk of mismatched lengths and unclear semantics; a hashtable is clearer.
-
Only implement plural by calling provider EnsureAttribute in a loop
→ acceptable baseline; however the optional provider EnsureAttributes fast-path provides a clean performance upgrade path.
Impact
Engine / Core / Steps
- Step registry must register the new type:
IdLE.Core/Private/Get-IdleStepRegistry.ps1: add mapping for IdLE.Step.EnsureAttributes.
- Keep (Option A) or remove (Option B) mapping for
IdLE.Step.EnsureAttribute.
Step metadata catalog
IdLE.Steps.Common/Public/Get-IdleStepMetadataCatalog.ps1: add IdLE.Step.EnsureAttributes with the same capability:
RequiredCapabilities = @('IdLE.Identity.Attribute.Ensure')
Provider contracts
- No breaking provider change is required if we implement the fallback loop.
- Providers may implement
EnsureAttributes for efficiency.
Workflows / docs / examples
- Update docs and examples to use
IdLE.Step.EnsureAttributes to reduce workflow verbosity.
Additional Context
Code references (current state)
- Step implementation:
src/IdLE.Steps.Common/Public/Invoke-IdleStepEnsureAttribute.ps1
- Built-in step registry:
src/IdLE.Core/Private/Get-IdleStepRegistry.ps1 (registers only IdLE.Step.EnsureAttribute)
- Step metadata:
src/IdLE.Steps.Common/Public/Get-IdleStepMetadataCatalog.ps1 (contains only IdLE.Step.EnsureAttribute)
Implementation Plan (Step-0 / Agent-Safe)
1) Add plural step handler in IdLE.Steps.Common
- Create:
src/IdLE.Steps.Common/Public/Invoke-IdleStepEnsureAttributes.ps1
- Validate
With is a hashtable
- Require:
With.IdentityKey + With.Attributes (hashtable, non-empty)
- Optional:
With.Provider (default Identity)
- Acquire auth session like other steps (
With.AuthSessionName, With.AuthSessionOptions)
- Call provider:
- Try
EnsureAttributes(IdentityKey, AttributesHashtable) first
- Fallback: foreach attribute -> call
EnsureAttribute(IdentityKey, Name, Value)
- Aggregate and return
IdLE.StepResult
Changed = $true if any attribute changed
- Add
Data.Attributes array with per-attribute status (Changed/Error)
2) Keep / deprecate singular step (Option A default)
- Update
Invoke-IdleStepEnsureAttribute.ps1 to become a wrapper:
- Convert
With.Name + With.Value into With.Attributes = @{ <Name> = <Value> }
- Delegate to
Invoke-IdleStepEnsureAttributes
- Keep error messages compatible where reasonable
3) Register the new step type in Core registry
- Update:
src/IdLE.Core/Private/Get-IdleStepRegistry.ps1
- Add built-in registration:
IdLE.Step.EnsureAttributes -> Invoke-IdleStepEnsureAttributes (module-qualified lookup supported)
- Keep existing
IdLE.Step.EnsureAttribute entry (Option A)
4) Update step metadata catalog
- Update:
src/IdLE.Steps.Common/Public/Get-IdleStepMetadataCatalog.ps1
- Add:
IdLE.Step.EnsureAttributes with IdLE.Identity.Attribute.Ensure
5) Documentation updates (required)
-
Add a new step reference page for EnsureAttributes (plural)
- Clearly document schema (
With.Attributes), auth options, provider selection, result shape
- Include at least one realistic example (AD / Entra / EXO as appropriate)
-
Update existing EnsureAttribute docs to:
- mark deprecated (Option A)
- point to plural step
-
Update any step index pages and any workflow docs that show EnsureAttribute usage.
6) Examples updates (required)
- Update existing example workflows that set multiple attributes:
- Replace multiple
EnsureAttribute steps with a single EnsureAttributes
7) Tests (required)
Add Pester unit tests (using mock providers) covering:
- Validation:
- Missing
With.Attributes fails
With.Attributes not a hashtable fails
- Provider invocation:
- When provider implements
EnsureAttributes, it is called exactly once
- When provider lacks
EnsureAttributes, fallback calls EnsureAttribute once per key
- Changed aggregation:
Changed true if any attribute changed, false otherwise
- Compatibility wrapper (Option A):
EnsureAttribute wrapper delegates and preserves behavior
Acceptance Criteria
- A workflow can ensure N attributes for one identity using one step:
IdLE.Step.EnsureAttributes.
- Built-in registry resolves the step handler without requiring global exports.
Plan capability derivation includes the new step type.
- Docs and examples prefer plural step; singular is deprecated (Option A) or removed (Option B).
- Pester tests cover all critical paths above.
Problem Statement
IdLE.Step.EnsureAttributecurrently supports exactly one attribute (With.Name+With.Value) per step.In real-world workflows we often need to converge many attributes for the same identity. This forces authors to add multiple
EnsureAttributesteps, which creates:The current limitation is visible in the implementation:
Invoke-IdleStepEnsureAttributeenforcesWith.IdentityKey,With.Name,With.Valueand calls provider methodEnsureAttribute(IdentityKey, Name, Value). The built-in step registry only mapsIdLE.Step.EnsureAttributetoInvoke-IdleStepEnsureAttribute.Proposed Solution
Introduce a plural step type that can ensure multiple attributes in one step:
IdLE.Step.EnsureAttributesInvoke-IdleStepEnsureAttributesProvider interaction / performance
To keep providers stable while still allowing optimizations:
Preferred (optional) fast path: If the provider implements a batch method
EnsureAttributes, call it.EnsureAttributes(IdentityKey, AttributesHashtable)AuthSessionparameter support (same discovery behavior asInvoke-IdleProviderMethodalready uses).Fallback (no provider change required): If
EnsureAttributesis not available, iterate keys and call the existingEnsureAttribute(IdentityKey, Name, Value)for each attribute.Result shape
StepResult.Changedshould betrueif any attribute was changed.StepResult.Data.Attributes), without leaking secrets:Backward compatibility / migration
We should switch the primary documentation and examples to
EnsureAttributes.For compatibility we have two viable options (choose one during implementation; default recommendation: A):
Option A (recommended, non-breaking for existing workflows):
IdLE.Step.EnsureAttributeworking.IdLE.Step.EnsureAttributeas deprecated in docs and changelog; schedule removal for a future major (1.0+) if desired.Option B (breaking, pre-1.0 acceptable):
IdLE.Step.EnsureAttributefrom the built-in registry and docs.IdLE.Step.EnsureAttributes.Given the user goal is mainly workflow overhead, Option A achieves that immediately (new workflows use plural), while existing workflows keep running.
Alternatives Considered
Keep step singular but enhance workflow syntax with loops/macros
→ rejected: IdLE workflows are intentionally data-only; adding looping constructs increases complexity and undermines simplicity/security.
Add
With.Names/With.Valuesarrays→ rejected: risk of mismatched lengths and unclear semantics; a hashtable is clearer.
Only implement plural by calling provider
EnsureAttributein a loop→ acceptable baseline; however the optional provider
EnsureAttributesfast-path provides a clean performance upgrade path.Impact
Engine / Core / Steps
IdLE.Core/Private/Get-IdleStepRegistry.ps1: add mapping forIdLE.Step.EnsureAttributes.IdLE.Step.EnsureAttribute.Step metadata catalog
IdLE.Steps.Common/Public/Get-IdleStepMetadataCatalog.ps1: addIdLE.Step.EnsureAttributeswith the same capability:RequiredCapabilities = @('IdLE.Identity.Attribute.Ensure')Provider contracts
EnsureAttributesfor efficiency.Workflows / docs / examples
IdLE.Step.EnsureAttributesto reduce workflow verbosity.Additional Context
Code references (current state)
src/IdLE.Steps.Common/Public/Invoke-IdleStepEnsureAttribute.ps1src/IdLE.Core/Private/Get-IdleStepRegistry.ps1(registers onlyIdLE.Step.EnsureAttribute)src/IdLE.Steps.Common/Public/Get-IdleStepMetadataCatalog.ps1(contains onlyIdLE.Step.EnsureAttribute)Implementation Plan (Step-0 / Agent-Safe)
1) Add plural step handler in IdLE.Steps.Common
src/IdLE.Steps.Common/Public/Invoke-IdleStepEnsureAttributes.ps1Withis a hashtableWith.IdentityKey+With.Attributes(hashtable, non-empty)With.Provider(defaultIdentity)With.AuthSessionName,With.AuthSessionOptions)EnsureAttributes(IdentityKey, AttributesHashtable)firstEnsureAttribute(IdentityKey, Name, Value)IdLE.StepResultChanged = $trueif any attribute changedData.Attributesarray with per-attribute status (Changed/Error)2) Keep / deprecate singular step (Option A default)
Invoke-IdleStepEnsureAttribute.ps1to become a wrapper:With.Name+With.ValueintoWith.Attributes = @{ <Name> = <Value> }Invoke-IdleStepEnsureAttributes3) Register the new step type in Core registry
src/IdLE.Core/Private/Get-IdleStepRegistry.ps1IdLE.Step.EnsureAttributes->Invoke-IdleStepEnsureAttributes(module-qualified lookup supported)IdLE.Step.EnsureAttributeentry (Option A)4) Update step metadata catalog
src/IdLE.Steps.Common/Public/Get-IdleStepMetadataCatalog.ps1IdLE.Step.EnsureAttributeswithIdLE.Identity.Attribute.Ensure5) Documentation updates (required)
Add a new step reference page for
EnsureAttributes(plural)With.Attributes), auth options, provider selection, result shapeUpdate existing
EnsureAttributedocs to:Update any step index pages and any workflow docs that show
EnsureAttributeusage.6) Examples updates (required)
EnsureAttributesteps with a singleEnsureAttributes7) Tests (required)
Add Pester unit tests (using mock providers) covering:
With.AttributesfailsWith.Attributesnot a hashtable failsEnsureAttributes, it is called exactly onceEnsureAttributes, fallback callsEnsureAttributeonce per keyChangedtrue if any attribute changed, false otherwiseEnsureAttributewrapper delegates and preserves behaviorAcceptance Criteria
IdLE.Step.EnsureAttributes.Plancapability derivation includes the new step type.