Skip to content

Replace IdLE.Step.EnsureAttribute with IdLE.Step.EnsureAttributes - multi-attribute-capability #169

@blindzero

Description

@blindzero

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:

  1. 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).
  2. 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

  1. 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.

  2. Add With.Names/With.Values arrays
    → rejected: risk of mismatched lengths and unclear semantics; a hashtable is clearer.

  3. 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.

Metadata

Metadata

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions