Conversation
…nt alias, and source metadata for ContextResolvers Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
…er null entitlement entries Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
blindzero
left a comment
There was a problem hiding this comment.
@copilot from views I miss some parts. we need a "condensed" view for
- per provider, per AuthSession (default)
- per provider, all AuthSessions combined
- all provider combined, but per AuthSession
- all provider, all AuthSessions (which is realized)
…lverViews Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Added the two missing view types in commit
The nested |
…our scopes) Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Fixed in commit
Profile views use last-write-wins with deterministic sort order (provider alias asc, then auth key asc). Profile objects are also now annotated with |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c4766ca356
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
This PR updates IdLE’s ContextResolvers to avoid cross-provider/auth-session collisions by writing resolver outputs to provider/auth-scoped namespaces, then exposing deterministic, engine-built “Views” for common aggregation patterns. It also introduces an execution-time-only Request.Context.Current alias for step precondition evaluation, plus validation of provider/auth path segments.
Changes:
- Write ContextResolver outputs to
Request.Context.Providers.<ProviderAlias>.<AuthSessionKey>.<CapabilitySubPath>and rebuild deterministicRequest.Context.Views.*after each resolver. - Add
SourceProvider/SourceAuthSessionNamemetadata to entitlement entries and identity profiles. - Add fixture workflows + expanded Pester coverage; update example workflow and docs to reflect new paths and Views.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
src/IdLE.Core/Private/Invoke-IdleContextResolvers.ps1 |
Implements provider/auth-scoped outputs, source metadata, path validation, and deterministic Views rebuilding. |
src/IdLE.Core/Public/Invoke-IdlePlanObject.ps1 |
Adds execution-time Request.Context.Current alias during precondition evaluation. |
tests/Core/New-IdlePlan.ContextResolvers.Tests.ps1 |
Updates existing resolver tests and adds new coverage for multi-provider/session scoping, Views, validation, and Current. |
tests/fixtures/workflows/*.psd1 |
Adds/updates workflow fixtures to exercise multi-provider, multi-session, invalid segments, and Current precondition usage. |
examples/workflows/mock/joiner-with-context-resolvers.psd1 |
Updates example to reference Views/scoped paths instead of legacy flat paths. |
docs/use/workflows/context-resolver.md |
Documents scoped namespaces, Views, and Current alias; updates examples and troubleshooting guidance. |
docs/reference/capabilities.md |
Updates capability reference docs to describe scoped outputs and Views for both capabilities. |
Comments suppressed due to low confidence (3)
docs/use/workflows/context-resolver.md:276
- This troubleshooting note says “Views are only available for
IdLE.Entitlement.List”, but the same document (anddocs/reference/capabilities.md) describes deterministic Views forIdLE.Identity.Readas well. Please update/remove this line to avoid contradicting the rest of the docs.
- Ensure template placeholders resolve correctly.
- Remember: scoped path uses `Providers.<Alias>.<AuthKey>.<SubPath>`.
Views are only available for `IdLE.Entitlement.List`.
tests/Core/New-IdlePlan.ContextResolvers.Tests.ps1:541
- This test name/docstring claims the global view is “sorted by provider alias then auth session key”, but the assertions only check that both IDs are present and do not validate order. Either assert the expected ordering explicitly or adjust the test description so it matches what’s being verified.
It 'global view merges entitlements from all providers sorted by provider alias then auth session key' {
$wfPath = Join-Path $script:FixturesPath 'resolver-two-providers.psd1'
$req = New-IdleTestRequest -LifecycleEvent 'Joiner'
$makeProvider = {
param([string]$GroupId)
$p = [pscustomobject]@{ FixtureGroupId = $GroupId }
$p | Add-Member -MemberType ScriptMethod -Name GetCapabilities -Value { return @('IdLE.Entitlement.List') }
$p | Add-Member -MemberType ScriptMethod -Name ListEntitlements -Value {
param([string]$IdentityKey)
return @(@{ Kind = 'Group'; Id = $this.FixtureGroupId })
}
return $p
}
$providers = @{
Entra = & $makeProvider -GroupId 'entra-grp'
AD = & $makeProvider -GroupId 'ad-grp'
StepRegistry = @{ 'IdLE.Step.EmitEvent' = 'Invoke-IdleContextResolverTestNoopStep' }
}
$plan = New-IdlePlan -WorkflowPath $wfPath -Request $req -Providers $providers
# Global view contains both providers' entitlements (sorted: AD before Entra alphabetically)
$globalView = @($plan.Request.Context.Views.Identity.Entitlements)
$globalView.Count | Should -Be 2
$ids = $globalView | ForEach-Object { $_.Id }
$ids | Should -Contain 'entra-grp'
$ids | Should -Contain 'ad-grp'
}
src/IdLE.Core/Public/Invoke-IdlePlanObject.ps1:469
- The
Request.Context.Currentalias is not guaranteed to be cleaned up on all control-flow paths. In particular, when a precondition fails withOnPreconditionFalse=FailorBlocked, the codebreaks out of the loop without removingCurrent, and exceptions thrown duringAssert-IdleConditionPathsResolvable/Test-IdleConditionwould also bypass cleanup. Use atry/finally(or explicit cleanup beforebreak) to ensureCurrentis always removed/restored.
if ($null -ne $stepPrecondition) {
$preconditionPassed = $true
if ($stepPrecondition -isnot [System.Collections.IDictionary]) {
# Fail closed: a malformed or unexpected node type is treated as a failed precondition.
$preconditionPassed = $false
}
else {
# Validate that all non-Exists paths exist at execution time.
# Exists operator paths are excluded because Exists semantics intentionally allow missing paths.
Assert-IdleConditionPathsResolvable -Condition ([hashtable]$stepPrecondition) -Context $preconditionContext -StepName $stepName -Source 'Precondition' -ExcludeExistsOperatorPaths
if (-not (Test-IdleCondition -Condition ([hashtable]$stepPrecondition) -Context $preconditionContext)) {
$preconditionPassed = $false
}
}
if (-not $preconditionPassed) {
$onPreconditionFalse = [string](Get-IdlePropertyValue -Object $step -Name 'OnPreconditionFalse')
if ([string]::IsNullOrWhiteSpace($onPreconditionFalse)) { $onPreconditionFalse = 'Blocked' }
# Always emit StepPreconditionFailed for engine observability.
$context.EventSink.WriteEvent(
'StepPreconditionFailed',
"Step '$stepName' precondition check failed.",
$stepName,
@{
StepType = $stepType
Index = $i
OnPreconditionFalse = $onPreconditionFalse
}
)
# Emit the caller-configured PreconditionEvent if present.
$pcEvt = Get-IdlePropertyValue -Object $step -Name 'PreconditionEvent'
if ($null -ne $pcEvt) {
$pcEvtType = [string](Get-IdlePropertyValue -Object $pcEvt -Name 'Type')
$pcEvtMsg = [string](Get-IdlePropertyValue -Object $pcEvt -Name 'Message')
$pcEvtData = Get-IdlePropertyValue -Object $pcEvt -Name 'Data'
# PreconditionEvent.Data is validated as a hashtable at planning time and
# stored via Copy-IdleDataObject, so it will be a hashtable (IDictionary) here.
$pcEvtDataHt = if ($pcEvtData -is [System.Collections.IDictionary]) { [hashtable]$pcEvtData } else { $null }
$context.EventSink.WriteEvent($pcEvtType, $pcEvtMsg, $stepName, $pcEvtDataHt)
}
if ($onPreconditionFalse -eq 'Fail') {
$failed = $true
$stepResults += [pscustomobject]@{
PSTypeName = 'IdLE.StepResult'
Name = $stepName
Type = $stepType
Status = 'Failed'
Error = 'Precondition check failed.'
Attempts = 0
}
$context.EventSink.WriteEvent(
'StepFailed',
"Step '$stepName' failed (precondition check failed).",
$stepName,
@{
StepType = $stepType
Index = $i
Error = 'Precondition check failed.'
}
)
}
elseif ($onPreconditionFalse -eq 'Continue') {
# Emit events and skip the step; continue to subsequent steps.
$stepResults += [pscustomobject]@{
PSTypeName = 'IdLE.StepResult'
Name = $stepName
Type = $stepType
Status = 'PreconditionSkipped'
Attempts = 0
}
$i++
# Clean up the Current alias before continuing to the next step.
if ($currentContextSet -and $null -ne $request -and $null -ne $request.Context -and $request.Context -is [System.Collections.IDictionary]) {
$null = $request.Context.Remove('Current')
}
continue
}
else {
# Default: Blocked. Does not trigger OnFailureSteps.
$blocked = $true
$stepResults += [pscustomobject]@{
PSTypeName = 'IdLE.StepResult'
Name = $stepName
Type = $stepType
Status = 'Blocked'
Attempts = 0
}
$context.EventSink.WriteEvent(
'StepBlocked',
"Step '$stepName' blocked (precondition check failed).",
$stepName,
@{
StepType = $stepType
Index = $i
}
)
}
break
}
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Code Coverage Report
|
… cleanup on Fail/Blocked Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
blindzero
left a comment
There was a problem hiding this comment.
i get this error:
PS C:\Users\adm-mafl-server\Downloads\idle> $plan = New-IdlePlan -WorkflowPath .\context-resolver.psd1 -Request $request -Providers $providers
MethodInvocationException: C:\Users\adm-mafl-server\Downloads\idle\IdentityLifecycleEngine\src\IdLE.Core\Private\Invoke-IdleContextResolvers.ps1:714:24
Line |
714 | … return @($provider.ListEntitlements($identityKey, $AuthSession)) …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Exception calling "ListEntitlements" with "2" argument(s): "Exception calling "ListUserGroups" with "2" argument(s): "Exception calling "GetAllPages" with "2"
| argument(s): "The property '@odata.nextLink' cannot be found on this object. Verify that the property exists."""
MethodInvocationException: C:\Users\adm-mafl-server\Downloads\idle\IdentityLifecycleEngine\src\IdLE.Core\Private\Invoke-IdleContextResolvers.ps1:716:20
Line |
716 | return @($provider.ListEntitlements($identityKey))
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Exception calling "ListEntitlements" with "1" argument(s): "Exception calling "ResolveIdentity" with "2" argument(s): "Exception calling "GetUserByUpn" with "2"
| argument(s): "Exception calling "InvokeGraphRequest" with "4" argument(s): "Unable to index into an object of type "System.Net.Http.Headers.HttpResponseHeaders".""""
OperationStopped: C:\Users\adm-mafl-server\Downloads\idle\IdentityLifecycleEngine\src\IdLE.Core\Private\Resolve-IdleTemplateString.ps1:115:13
Line |
115 | throw [System.ArgumentException]::new(
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Template resolution error in step 'Profile SoT - Entra.Default': Path 'Request.Context.Providers.Entra.Default.Identity.Profile' resolved to null or does not exist.
| Ensure the request contains all required values. (Parameter 'Workflow')
for a request only using emit event and context resolver for testing the different scnarios.
I used this session and provider setting:
$authSessions = New-IdleAuthSession -SessionMap @{
@{ AuthSessionName = 'AD' } = @{ AuthSessionType = 'Credential'; Credential = $adCred }
@{ AuthSessionName = 'Entra' } = @{ AuthSessionType = 'OAuth'; Credential = $entraAccessToken }
@{ AuthSessionName = 'EXO' } = @{ AuthSessionType = 'OAuth'; Credential = $exoAccessToken }
}
# Creating Providers
$providers = @{
AD = New-IdleADIdentityProvider
Entra = New-IdleEntraIDIdentityProvider
EXO = New-IdleExchangeOnlineProvider
AuthSessionBroker = $authSessions
}
Default doesnt mean "the one if only one", but "if not named". Hence it seems, that Entra.Default and AD.Default do not exist.
In such a case the error should be not a 3 point stacktrace but rather a clear error message (the last of the three messages).
When removing the .Default ones I got the next error:
Line |
714 | … return @($provider.ListEntitlements($identityKey, $AuthSession)) …
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Exception calling "ListEntitlements" with "2" argument(s): "Exception calling "ListUserGroups" with "2" argument(s): "Exception calling "GetAllPages" with "2"
| argument(s): "The property '@odata.nextLink' cannot be found on this object. Verify that the property exists."""
MethodInvocationException: C:\Users\adm-mafl-server\Downloads\idle\IdentityLifecycleEngine\src\IdLE.Core\Private\Invoke-IdleContextResolvers.ps1:716:20
Line |
716 | return @($provider.ListEntitlements($identityKey))
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Exception calling "ListEntitlements" with "1" argument(s): "Exception calling "ResolveIdentity" with "2" argument(s): "Exception calling "GetUserByUpn" with "2"
| argument(s): "Exception calling "InvokeGraphRequest" with "4" argument(s): "Unable to index into an object of type "System.Net.Http.Headers.HttpResponseHeaders".""""
OperationStopped: C:\Users\adm-mafl-server\Downloads\idle\IdentityLifecycleEngine\src\IdLE.Core\Private\Resolve-IdleTemplateString.ps1:115:13
Line |
115 | throw [System.ArgumentException]::new(
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| Template resolution error in step 'Entitlement SoT - Entra.Entra': Path 'Request.Context.Providers.Entra.Entra.Identity.Entitlements' resolved to null or does not
| exist. Ensure the request contains all required values. (Parameter 'Workflow')
Here I am wondering why Request.Context.Providers.Entra.Entra.Identity.Entitlements does not exist now, because it is a Source of Truth.
…ccess, reset nextLink to null at loop top Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Fixed in
Please make sure you pull the latest commit on this branch before testing again. |
|
The remaining error is not a ContextResolver design problem anymore, but a provider robustness issue exposed by the new issue #253 behavior. PR #259 correctly makes ContextResolvers resolve provider/auth-scoped entitlement data during plan build. That means Entra entitlement listing is now exercised deterministically and the latent paging bug becomes visible. Observed failure: Assessment:
Requested change:
|
blindzero
left a comment
There was a problem hiding this comment.
@copilot Please fix this as a provider-layer regression in the Entra adapter, not in ContextResolvers.
Problem:
PR #259 correctly makes ContextResolvers resolve provider/auth-scoped entitlement data during plan build for issue #253. This now reliably executes the Entra entitlement path:
ListEntitlements -> Adapter.ListUserGroups -> Adapter.GetAllPages
The current failure
"The property '@odata.nextLink' cannot be found on this object"
shows that the Entra paging code is still not fully resilient under Set-StrictMode -Version Latest when the final Graph page does not contain @odata.nextLink.
Required fix:
-
Keep Invoke-IdleContextResolvers fail-fast behavior unchanged.
- Do NOT swallow provider errors in the core.
- The throw at Invoke-IdleContextResolvers is correct and should remain.
-
Fix the Entra adapter paging logic in New-IdleEntraIDAdapter.ps1.
- Introduce a small internal helper for safe response property access, e.g.:
Get-IdleEntraGraphResponseProperty -InputObject -Name - It must:
- return $null when the property/key is absent
- support both IDictionary/hashtable and PSObject-based responses
- never throw for missing properties under StrictMode
- Use this helper for BOTH:
- 'value'
- '@odata.nextLink'
- Introduce a small internal helper for safe response property access, e.g.:
-
Update GetAllPages to be explicitly termination-safe.
- Set $nextLink = $null at the start of every loop iteration after receiving the response
- Read the next link only via the safe helper
- Missing '@odata.nextLink' must mean "last page", not failure
- Preserve current behavior for normal multi-page responses
-
Add regression tests that reproduce the real bug.
At minimum add provider tests covering:- single-page response with .value and without '@odata.nextLink'
- multi-page response where first page has '@odata.nextLink' and final page does not
- response represented as PSCustomObject
- response represented as hashtable / IDictionary
- StrictMode-safe behavior (missing property must not throw)
-
Add one integration-style regression test on the plan/context-resolver path.
- Use New-IdlePlan with a workflow that triggers IdLE.Entitlement.List via ContextResolvers
- Back the Entra provider with a fake adapter that returns a final page without '@odata.nextLink'
- Assert that plan build succeeds and Request.Context.Providers / Views are populated as expected
- This verifies the issue #253 scenario end-to-end and prevents future regressions
-
Keep the PR focused.
- No unrelated refactors
- No fallback behavior changes in the core
- Only provider resilience + regression coverage
…sion tests - Add private helper Get-IdleEntraIDGraphResponseProperty that safely reads response properties from PSCustomObject and IDictionary/hashtable without throwing under Set-StrictMode -Version Latest - Rewrite GetAllPages in New-IdleEntraIDAdapter to use the helper instead of inline try/catch blocks; reset $nextLink = $null at top of each iteration - Add provider-level regression tests: single-page no-nextLink, multi-page, hashtable responses, non-collection endpoints, direct helper unit tests Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Fixed in Changes:
|
blindzero
left a comment
There was a problem hiding this comment.
@copilot Request changes
The implementation looks close, but the current docs/examples still leave important authoring and troubleshooting gaps around the new scoped ContextResolver outputs from #253.
Please update the documentation and examples before merge.
Required documentation changes
-
Update the ContextResolver workflow docs to document the recommended debugging workflow explicitly.
- Add a troubleshooting section that shows host-side inspection with
ConvertTo-Json, for example:$plan.Request.Context | ConvertTo-Json -Depth 8$plan.Request.Context.Providers | ConvertTo-Json -Depth 8$plan.Request.Context.Views | ConvertTo-Json -Depth 8
- Make it clear that this is the current recommended way to inspect complex resolver outputs during authoring/debugging.
- Also state explicitly that normal template substitution remains scalar-only and is not the right mechanism for logging whole objects/lists.
- Add a troubleshooting section that shows host-side inspection with
-
Clarify the actual profile shape returned by
IdLE.Identity.Read.- The docs/examples currently encourage or at least make it too easy to assume paths like:
...Identity.Profile.DisplayName
- Based on the current data model, profile attributes are under:
...Identity.Profile.Attributes.<Name>
- Please fix all affected examples accordingly, especially
DisplayName.
- The docs/examples currently encourage or at least make it too easy to assume paths like:
-
Document View behavior more explicitly in troubleshooting.
- Please explain that profile Views are deterministic convenience views, not raw source-of-truth mirrors.
- Document the practical consequence of the ordering/selection rule:
- global/profile Views are built deterministically
- when multiple profile contributions exist, selection is effectively last-writer-wins based on the defined stable ordering
- Add a short troubleshooting note explaining why:
Request.Context.Views.Identity.Profile
may legitimately differ from a specific scoped source-of-truth path such asRequest.Context.Providers.<Provider>.<AuthSession>.Identity.Profile
-
Document the correct consumption model for entitlements.
- Make it explicit that entitlement outputs are lists of objects.
- Show that they are intended to be consumed through path traversal / conditions such as:
...Entitlements.Id...Entitlements.DisplayName...Entitlements.SourceProvider...Entitlements.SourceAuthSessionName
- Also state that whole-list template interpolation is intentionally not supported.
Required example changes
- Update the context-related example workflow(s).
- Please add or extend an example that demonstrates:
IdLE.Identity.ReadIdLE.Entitlement.List- a scoped source-of-truth path check
- a View path check
- a
Containscondition over...Entitlements.Id(or another scalar entitlement property)
- Add an inline comment that complex resolver outputs should be inspected host-side via
ConvertTo-Json, not rendered directly with{{...}}.
- Please add or extend an example that demonstrates:
Why this is important
The current implementation now exposes the correct scoped paths and Views from #253, but the documentation is not yet precise enough to prevent predictable authoring mistakes:
- assuming
Profile.DisplayNameinstead ofProfile.Attributes.DisplayName - expecting templates to stringify complex resolver outputs
- not understanding why a View can differ from a specific scoped source-of-truth path
This is now a docs/examples completeness issue, not primarily an implementation issue.
…s path, View determinism note Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Done in
|
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 22 out of 22 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
src/IdLE.Core/Public/Invoke-IdlePlanObject.ps1:386
Request.Context.Currentis cleaned up in the normal precondition pass/fail flow, but ifAssert-IdleConditionPathsResolvableorTest-IdleConditionthrows, the function will exit without removingCurrentfrom the request context. This can leak a step-scoped alias into the caller’s request/plan object and potentially affect subsequent logic if the exception is caught upstream. Consider wrapping the precondition evaluation block in atry/finallysoCurrentis always removed when$currentContextSetis true.
# Set Request.Context.Current alias for step-relative path resolution in preconditions.
# Resolved from Step.With.Provider + Step.With.AuthSessionName (or 'Default').
# Scoped to the precondition evaluation; cleaned up immediately after.
$currentContextSet = $false
if ($null -ne $stepPrecondition -and $null -ne $request -and $null -ne $request.Context -and $request.Context -is [System.Collections.IDictionary]) {
# If the caller has already provided a Context['Current'], do not overwrite it.
$currentAlreadyPresent = $request.Context.Contains('Current')
if (-not $currentAlreadyPresent) {
$currentProviderAlias = $null
$currentAuthKey = 'Default'
if ($null -ne $stepWith) {
if ($stepWith -is [System.Collections.IDictionary]) {
if ($stepWith.Contains('Provider') -and -not [string]::IsNullOrWhiteSpace([string]$stepWith['Provider'])) {
$currentProviderAlias = [string]$stepWith['Provider']
}
if ($stepWith.Contains('AuthSessionName') -and -not [string]::IsNullOrWhiteSpace([string]$stepWith['AuthSessionName'])) {
$currentAuthKey = [string]$stepWith['AuthSessionName']
}
}
elseif ($stepWith.PSObject.Properties.Name -contains 'Provider') {
$pVal = $stepWith.Provider
if (-not [string]::IsNullOrWhiteSpace([string]$pVal)) { $currentProviderAlias = [string]$pVal }
$aVal = if ($stepWith.PSObject.Properties.Name -contains 'AuthSessionName') { $stepWith.AuthSessionName } else { $null }
if (-not [string]::IsNullOrWhiteSpace([string]$aVal)) { $currentAuthKey = [string]$aVal }
}
}
$currentContextValue = $null
if (-not [string]::IsNullOrWhiteSpace($currentProviderAlias)) {
$providersNode = if ($request.Context.Contains('Providers')) { $request.Context['Providers'] } else { $null }
if ($null -ne $providersNode -and $providersNode -is [System.Collections.IDictionary] -and $providersNode.Contains($currentProviderAlias)) {
$providerNode = $providersNode[$currentProviderAlias]
if ($null -ne $providerNode -and $providerNode -is [System.Collections.IDictionary] -and $providerNode.Contains($currentAuthKey)) {
$currentContextValue = $providerNode[$currentAuthKey]
}
}
}
$request.Context['Current'] = $currentContextValue
$currentContextSet = $true
}
}
if ($null -ne $stepPrecondition) {
$preconditionPassed = $true
if ($stepPrecondition -isnot [System.Collections.IDictionary]) {
# Fail closed: a malformed or unexpected node type is treated as a failed precondition.
$preconditionPassed = $false
}
else {
# Validate that all non-Exists paths exist at execution time.
# Exists operator paths are excluded because Exists semantics intentionally allow missing paths.
Assert-IdleConditionPathsResolvable -Condition ([hashtable]$stepPrecondition) -Context $preconditionContext -StepName $stepName -Source 'Precondition' -ExcludeExistsOperatorPaths
if (-not (Test-IdleCondition -Condition ([hashtable]$stepPrecondition) -Context $preconditionContext)) {
$preconditionPassed = $false
}
}
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
…r-paths Post-#259 cleanup: correct ContextResolver paths, Views, and attribute structure across docs and tests
GetAllPagesinNew-IdleEntraIDAdapter.ps1: guard@odata.nextLinkaccess so it doesn't throw underSet-StrictMode -Version Latestwhen the last page has no nextLink propertyGet-IdleEntraIDGraphResponsePropertyin the Entra provider that safely reads named properties from bothPSCustomObjectandIDictionary/hashtable Graph API responsesGetAllPagesto use the helper for both.valueand@odata.nextLinkaccessdocs/use/workflows/context-resolver.md:-Depth 8, add targetedProviders/Viewssub-tree inspection commands, add note that template substitution is scalar-only and cannot log whole objects/listsProfile.DisplayName→Profile.Attributes.DisplayName(canonical attribute path):::infoadmonition explaining Profile Views are deterministic convenience aggregations (last-writer-wins, sorted alphabetically), not mirrors of a specific provider result, with guidance on when to use Views vs scoped source-of-truth pathsOriginal prompt
This section details on the original issue you should resolve
<issue_title>ContextResolvers: Provider/Auth-scoped Context Namespaces + Deterministic Views without wildcards</issue_title>
<issue_description>## Problem Statement
ContextResolvers write capability results into a single, capability-defined
Request.Contexttarget path. This makes it impossible (or unsafe) to use the same capability multiple times for different Identity Providers and/or different AuthSession contexts (e.g., Entra + AD, or multiple Entra sessions), because results collide/overwrite each other.As a result:
IdLE.Entitlement.Listfrom Entra and from AD).Proposed Solution
This issue implements a breaking but consistent design with the following guardrails:
Get-IdleValuePath/ condition path resolution (e.g. noProviders.*.*).Current), derived from the step/resolver Provider/Auth settings.1) Provider/Auth-scoped Context Namespace (Source of Truth)
Introduce (or standardize) a provider/auth-scoped structure under
Request.Context:Request.Context.Providers.<ProviderAlias>.<AuthSessionKey>.<CapabilityRoot>.<...>Where:
<ProviderAlias>is the provider name used in IdLE (e.g.,Entra,AD) and must be a valid path segment.<AuthSessionKey>is:Defaultwhen noWith.AuthSessionNameis specifiedWith.AuthSessionNameExamples:
Request.Context.Providers.Entra.Default.Identity.ProfileRequest.Context.Providers.Entra.CorpAdmin.Identity.EntitlementsRequest.Context.Providers.AD.Default.Identity.EntitlementsImplementation rule: ContextResolvers MUST write their results into the scoped namespace above (in addition to any engine-defined Views, see below).
2) Deterministic Views (Wildcard-equivalent Without Wildcards)
Add engine-defined “Views” for common “wildcard” use cases.
Global view (all providers, all auth sessions):
Request.Context.Views.<CapabilityRoot>.<...>Provider view (one provider, all auth sessions):
Request.Context.Views.Providers.<ProviderAlias>.<CapabilityRoot>.<...>Examples (Entitlements):
Request.Context.Views.Identity.Entitlements(all providers, all auth)Request.Context.Views.Providers.Entra.Identity.Entitlements(Entra only, all auth)View semantics are capability-specific and must be explicitly defined and tested.
No view implies no merge.
Entitlements View Semantics (required)
For
IdLE.Entitlement.Listresults:Request.Context.Providers.<ProviderAlias>.<AuthSessionKey>.Identity.EntitlementsRequest.Context.Views.Identity.Entitlements= merge/flatten of all scoped entitlement listsRequest.Context.Views.Providers.<ProviderAlias>.Identity.Entitlements= merge/flatten of all scoped entitlement lists for that providerAdditionally, each entitlement entry MUST include source metadata:
SourceProvider(string)SourceAuthSessionName(string,Defaultif not specified)This enables:
3) Step-relative Context Alias (Execution-time)
Introduce a step-relative alias path:
Request.Context.Current.<CapabilityRoot>.<...>Resolution rule:
Currentis resolved using the Provider/Auth context of the executing unit:Step.With.Provider+Step.With.AuthSessionName(orDefault)Scope restriction:
Request.Context.Current.*MUST NOT be used in plan-time conditions (where there is no single execution context).4) Reference Behavior (Conditions, Preconditions, Template Substitution)
Request.Context.Providers...)Request.Context.Views...)Request.Context.Current...(execution-time only)**No wildcard...
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.