Skip to content

Custom step modules cannot self-register their handler without using Providers.StepRegistry workaround #292

@blindzero

Description

@blindzero

Problem Statement

Custom step module authors currently have no clean way to register the mapping from their step type name to their handler function. The step registry (Step.Type → handler function name) is hardcoded in IdLE.Core for built-in steps. For external/custom steps, the only available path is passing a Providers.StepRegistry hashtable at every Invoke-IdlePlan call:

Invoke-IdlePlan $plan -Providers @{
    StepRegistry = @{
        'MeineOrg.Step.SAP.CreateUser' = 'MeineOrg.IdLE.Steps.SAP\Invoke-SAPCreateUser'
    }
}

This is unintuitive and leaks an implementation detail (the handler function name) into caller code. Every consumer of the plan must know and repeat this mapping.

The root cause is two separate data structures that belong together:

Structure Location Contains
StepMetadataCatalog.psd1 Per step module Step contract (capabilities, With schema)
Step Registry Get-IdleStepRegistry.ps1 in Core Step.Type → handler function name

The catalog is already loaded automatically during module discovery (IdLE.Steps.* + Get-IdleStepMetadataCatalog). The registry is not.

Proposed Solution

Add an optional Handler key to the StepMetadataCatalog.psd1 format:

# MeineOrg.IdLE.Steps.SAP/StepMetadataCatalog.psd1
@{
    'MeineOrg.Step.SAP.CreateUser' = @{
        Handler = 'MeineOrg.IdLE.Steps.SAP\Invoke-SAPCreateUser'   # <-- new
        RequiredCapabilities = @('sap:identity:create')
        WithSchema = @{
            RequiredKeys = @('SapSystemId')
            OptionalKeys = @('TargetOU')
        }
    }
}

During Resolve-IdleStepMetadataCatalog, when a step pack catalog is loaded, any Handler entries are automatically fed into the step registry — no extra wiring by the caller required.

Module-qualified names (ModuleName\FunctionName) should be encouraged so the handler function does not need to be globally exported, avoiding session-scope pollution. Resolve-IdleStepHandler already supports this lookup path via Get-Module -All.

Alternatives Considered

  • Keep Providers.StepRegistry as the only mechanism: Works but requires callers to repeat implementation details. Not suitable as the primary extensibility path.
  • Separate StepRegistry.psd1 file per module: Cleaner than the parameter workaround, but unnecessary indirection when the catalog already exists and is already loaded.

Impact

  • Handler is optional — built-in steps whose handlers are hardcoded in the registry do not need it. Fully backwards compatible.
  • Custom step module authors declare everything in one place (StepMetadataCatalog.psd1).
  • Callers never need to touch Providers.StepRegistry for well-behaved step modules.
  • Handler functions can remain private/nested — no session-scope pollution.
  • Assert-IdleNoScriptBlock guard must be extended to cover Handler values (must be a plain string, not a ScriptBlock).
  • Conflict handling needs a decision: if a step type appears in both the hardcoded registry and a catalog Handler entry, which wins?

Additional Context

Relevant files:

  • src/IdLE.Core/Private/Resolve-IdleStepMetadataCatalog.ps1 — catalog loading, lines 265–358
  • src/IdLE.Core/Private/Get-IdleStepRegistry.ps1 — hardcoded registry, lines 110–221
  • src/IdLE.Core/Private/Resolve-IdleStepHandler.ps1 — two-tier handler resolution
  • src/IdLE.Steps.Common/StepMetadataCatalog.psd1 — reference catalog format

Metadata

Metadata

Assignees

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions