Skip to content

feat: Planning-time ContextResolvers (read-only) to populate Request.Context before condition evaluation#222

Merged
blindzero merged 5 commits intomainfrom
copilot/implement-context-resolvers
Feb 22, 2026
Merged

feat: Planning-time ContextResolvers (read-only) to populate Request.Context before condition evaluation#222
blindzero merged 5 commits intomainfrom
copilot/implement-context-resolvers

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 22, 2026

Workflows requiring dynamic context (current entitlements, identity attributes) for condition evaluation had to rely on host-side enrichment. This adds a first-class ContextResolvers workflow section that invokes read-only provider capabilities at planning time, writing results into Request.Context.* before step conditions are evaluated.

Workflow Schema

New optional top-level ContextResolvers array in PSD1 workflow definitions. Each capability writes to a predefined, fixed path under Request.Context — the output path is not user-configurable, which prevents accidental overwrites and ensures consistent context shape across workflows:

@{
  Name           = 'Joiner - With Context'
  LifecycleEvent = 'Joiner'
  ContextResolvers = @(
    @{
      Capability = 'IdLE.Entitlement.List'          # must be in read-only allow-list
      Provider   = 'Identity'                        # optional; auto-selected if omitted
      With       = @{ IdentityKey = '{{Request.IdentityKeys.EmployeeId}}' }
      # Writes to Request.Context.Identity.Entitlements (predefined, not configurable)
    }
    @{
      Capability = 'IdLE.Identity.Read'
      With       = @{ IdentityKey = '{{Request.IdentityKeys.EmployeeId}}' }
      # Writes to Request.Context.Identity.Profile (predefined, not configurable)
    }
  )
  Steps = @(
    @{
      Name      = 'GrantAdditionalAccess'
      Type      = 'IdLE.Step.EnsureEntitlement'
      Condition = @{ Exists = 'Request.Context.Identity.Entitlements' }
      With      = @{ ... }
    }
  )
}

Auth sessions are supported in resolvers via With.AuthSessionName / With.AuthSessionOptions, using the same AuthSessionBroker pattern as step execution:

@{
  Capability = 'IdLE.Entitlement.List'
  Provider   = 'Identity'
  With       = @{
    IdentityKey     = '{{Request.IdentityKeys.EmployeeId}}'
    AuthSessionName = 'EntraID'
  }
}

Predefined Context paths per capability

Capability Request.Context path Required With keys
IdLE.Entitlement.List Request.Context.Identity.Entitlements IdentityKey
IdLE.Identity.Read Request.Context.Identity.Profile IdentityKey

Engine Changes

  • Test-IdleWorkflowSchema — validates ContextResolvers entries: required Capability key, allowed keys (Capability, Provider, With); To is not a valid key (output path is predefined per capability); single hashtable (instead of array) is now explicitly rejected with a clear error
  • Test-IdleWorkflowDefinitionObject — normalized workflow output now includes ContextResolvers
  • New-IdlePlanObject — executes resolvers immediately after workflow load, before step normalization; ensures Request.Context is a writable hashtable before resolvers run (creates it when missing or $null); request snapshot captured after resolvers using a live property check so dynamically added Context is captured

New Private Functions

  • Get-IdleReadOnlyCapabilities — allow-list enforced at runtime (IdLE.Entitlement.List, IdLE.Identity.Read); non-listed capabilities are rejected with a clear error
  • Get-IdleCapabilityContextPath — maps each read-only capability to its fixed predefined output path under Request.Context
  • Invoke-IdleContextResolvers — orchestrates resolver execution in declared order; acquires auth sessions from Providers.AuthSessionBroker (same pattern as steps) and threads them to provider methods that declare an AuthSession parameter
  • Get-IdleAuthSessionBroker — extracts AuthSessionBroker from the Providers map safely
  • Select-IdleResolverProviderAlias — explicit alias lookup or capability-based auto-select; iterates aliases in sorted order for determinism; throws an explicit ambiguity error when multiple providers match and Provider is not specified
  • Invoke-IdleResolverCapabilityDispatch — capability→method mapping for IdLE.Entitlement.List and IdLE.Identity.Read; uses Test-IdleProviderMethodParameter for backwards-compatible AuthSession passing
  • Set-IdleContextValue — dotted-path writer that creates missing intermediate hashtables; throws InvalidOperationException when an existing intermediate node is not a dictionary (prevents silent data loss when host has pre-populated context)

Provider Change

  • New-IdleMockIdentityProviderListEntitlements now returns @() for unknown identities instead of throwing, consistent with real API semantics and needed for planning-time calls on identities not yet created

Documentation

  • docs/reference/capabilities.md — new section documents the read-only capability allow-list, predefined Request.Context output paths, and required With keys for each capability

Tests

15 Pester tests (using fixture files under tests/fixtures/workflows/) covering: resolver influences condition applicability, IdLE.Identity.Read populates Context.Identity.Profile, To key rejected as unknown, non-allow-listed capability rejection, plan snapshot capture, provider auto-selection, provider ambiguity detection, auth session threading, context type conflict detection, Request.Context guard when property is absent, schema validation, and template resolution in With.

Original prompt

This section details on the original issue you should resolve

<issue_title>Planning-time Context Resolvers (read-only) using Provider Capabilities to populate Request.Context</issue_title>
<issue_description>## Problem Statement

IdLE evaluates step Condition during planning. Many workflows require up-to-date, read-only information to decide whether a step applies, for example:

  • group membership / entitlements
  • identity properties
  • device inventory hints

Today this requires host-side enrichment, which reduces portability and standardization.

We want a first-class, portable mechanism to resolve such read-only information during planning, similar to Terraform “data sources”, while keeping IdLE guardrails:

  • read-only only (no side effects)
  • deterministic plan artifacts (resolved data is captured in plan snapshot/export)
  • provider extensibility (providers can contribute additional read operations)
  • clear execution order (resolve once per plan build before conditions)

Proposed Solution

1) Workflow-level ContextResolvers section (planning-time)

Add an optional workflow section:

  • ContextResolvers: list of resolvers
    • Capability: provider capability to call (read-only only), e.g.:
      • IdLE.Entitlement.List
      • IdLE.Identity.Get (or similar, if/when defined)
      • IdLE.Device.List (if/when Intune provider exists)
    • Provider: optional selector (if multiple providers support the capability)
    • With: resolver inputs (identity keys, filters, etc.)
    • To: path relative to Request.Context (e.g., Identity.Entitlements, Identity.Attributes, Devices.Intune.ManagedDevices)

Rules:

  • To MUST write under Request.Context.* only.
  • If To points outside Request.Context, planning MUST fail fast with a clear validation error.
  • Only allow a defined allow-list of read-only capabilities in ContextResolvers.

2) Planning pipeline behavior

During plan creation:

  1. Validate the ContextResolvers schema.
  2. Execute resolvers in declared order (ordered execution is sufficient for v1).
  3. Enforce read-only by design:
    • only allow calling capabilities from the read-only allow-list
    • do not allow Ensure* / modifying capabilities
  4. Write resolver results into Request.Context.* at To.
  5. Evaluate step Condition using the now-populated Request.Context.*.
  6. Export the plan including Request.Context.* in RequestSnapshot (per Issue 2).

3) Provider extensibility model

  • Providers already declare supported capabilities; ContextResolvers reuses this system.
  • Providers may implement additional read capabilities; if a capability is added to the allow-list, it can be used by workflows.
  • Resolver outputs must be serializable and stable.

4) Minimum viable resolver (required for v1)

Implement at least one end-to-end resolver based on an existing capability:

  • Capability: IdLE.Entitlement.List
    • returns normalized entitlements for identity keys in the request
    • writes to Request.Context.Identity.Entitlements (or the configured To)

Alternatives Considered

  1. Separate “resolver types” registry (new type system)
    • Adds another classification dimension besides step types and capabilities.
  2. Host-only enrichment
    • Works but reduces portability and standardization.
  3. Runtime-only evaluation
    • Conflicts with deterministic planning and makes workflows less declarative.

Impact

  • Planning will call provider read capabilities when resolvers are configured.
  • Requires strong validation and snapshot safety rules to avoid secret leakage.
  • Improves portability and declarative workflow authoring.

Dependencies

  • Requires Issue 1 (schema: Request.Intent and Request.Context).
  • Requires Issue 2 (plan snapshot/export includes Request.Context safely).

Definition of Done (Step-0-Ready / Agent-Safe)

Design / Contracts

  • Define workflow schema for ContextResolvers and validate it.
  • Define and document the allow-list of read-only capabilities usable in ContextResolvers.
  • Define provider selection rules when multiple providers support a capability.
  • Define normalized output shape for IdLE.Entitlement.List.

Implementation

  • Execute ContextResolvers before condition evaluation during plan build.
  • Invoke provider capability calls for resolvers via existing capability dispatch mechanisms.
  • Write results under Request.Context.* only.
  • Fail fast if To is outside Request.Context.
  • Fail fast if a resolver references a capability not in the read-only allow-list.

Tests (Pester)

  • Resolver runs before conditions and influences step applicability.
  • To outside Request.Context is rejected.
  • Resolver output is captured in plan snapshot/export.
  • Non-allow-listed capability in resolver -> validation error.

Docs / Examples

  • Add example workflow using ContextResolvers and referencing `Request.Context.Identity.Entitlemen...

🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.

…pulation

Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Copilot AI changed the title [WIP] Add planning-time context resolvers for read-only data feat: Planning-time ContextResolvers (read-only) to populate Request.Context before condition evaluation Feb 22, 2026
Copilot AI requested a review from blindzero February 22, 2026 17:59
@blindzero blindzero changed the title feat: Planning-time ContextResolvers (read-only) to populate Request.Context before condition evaluation Planning-time ContextResolvers (read-only) to populate Request.Context before condition evaluation Feb 22, 2026
…s per capability, IdLE.Identity.Read support, fixture files

Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
Copilot AI changed the title Planning-time ContextResolvers (read-only) to populate Request.Context before condition evaluation feat: Planning-time ContextResolvers (read-only) to populate Request.Context before condition evaluation Feb 22, 2026
Copilot AI requested a review from blindzero February 22, 2026 18:25
@github-actions
Copy link
Copy Markdown

github-actions bot commented Feb 22, 2026

Code Coverage Report

Overall Project 72.88% 🍏

There is no coverage information present for the Files changed

@blindzero blindzero marked this pull request as ready for review February 22, 2026 18:47
Copilot AI review requested due to automatic review settings February 22, 2026 18:47
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 08ac130bea

ℹ️ 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".

Repository owner deleted a comment from chatgpt-codex-connector bot Feb 22, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-class, planning-time ContextResolvers to IdLE workflows so read-only provider capabilities can populate Request.Context.* before step condition evaluation, enabling portable condition logic without host-side enrichment.

Changes:

  • Extends workflow schema/normalization to support ContextResolvers and rejects unsupported resolver keys (e.g., To).
  • Executes resolver capability calls during plan creation and writes results to predefined Request.Context paths.
  • Adds Pester coverage + fixtures, updates mock provider behavior, and documents the read-only allow-list and context paths.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/IdLE.Core/Public/New-IdlePlanObject.ps1 Runs ContextResolvers during planning (before condition evaluation) and snapshots request after resolution.
src/IdLE.Core/Public/Test-IdleWorkflowDefinitionObject.ps1 Normalizes workflow output to include ContextResolvers.
src/IdLE.Core/Private/Test-IdleWorkflowSchema.ps1 Adds schema validation for root ContextResolvers and per-resolver allowed keys.
src/IdLE.Core/Private/Invoke-IdleContextResolvers.ps1 Implements resolver orchestration: provider selection, capability dispatch, and writing to Request.Context.
src/IdLE.Core/Private/Get-IdleReadOnlyCapabilities.ps1 Defines the read-only capability allow-list and fixed Request.Context output-path mapping.
src/IdLE.Provider.Mock/Public/New-IdleMockIdentityProvider.ps1 Changes ListEntitlements to return empty for unknown identities (planning-time friendly).
tests/Core/New-IdlePlan.ContextResolvers.Tests.ps1 Adds end-to-end tests for resolver ordering, schema failures, allow-list enforcement, snapshots, auto-selection, and templating.
tests/fixtures/workflows/resolver-condition.psd1 Fixture: entitlement resolver enables an Exists condition.
tests/fixtures/workflows/resolver-empty-entitlements.psd1 Fixture: entitlement resolver returns empty list to drive NotApplicable outcome.
tests/fixtures/workflows/resolver-identity-read.psd1 Fixture: identity read resolver populates Request.Context.Identity.Profile.
tests/fixtures/workflows/resolver-snapshot.psd1 Fixture: resolved context is captured in plan request snapshot.
tests/fixtures/workflows/resolver-template.psd1 Fixture: templates inside With are resolved (e.g., {{Request.IdentityKeys.Id}}).
tests/fixtures/workflows/resolver-autoselect.psd1 Fixture: provider auto-selection when Provider is omitted.
tests/fixtures/workflows/resolver-no-provider.psd1 Fixture: failure when no provider advertises required capability.
tests/fixtures/workflows/resolver-non-allowlisted-cap.psd1 Fixture: rejects non-allow-listed capability usage in a resolver.
tests/fixtures/workflows/resolver-missing-capability.psd1 Fixture: rejects resolver missing required Capability.
tests/fixtures/workflows/resolver-unknown-key.psd1 Fixture: rejects unknown resolver key.
tests/fixtures/workflows/resolver-with-to-key.psd1 Fixture: rejects To key (output path is predefined).
examples/workflows/mock/joiner-with-context-resolvers.psd1 Example workflow demonstrating resolver-driven context for conditions.
docs/reference/capabilities.md Documents ContextResolvers allow-list, fixed output paths, and required With keys.

Repository owner deleted a comment from chatgpt-codex-connector bot Feb 22, 2026
…ct guard, missing-context guard

Co-authored-by: blindzero <13959569+blindzero@users.noreply.github.com>
@blindzero blindzero merged commit aabdfe1 into main Feb 22, 2026
8 checks passed
@blindzero blindzero deleted the copilot/implement-context-resolvers branch February 27, 2026 20:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Planning-time Context Resolvers (read-only) using Provider Capabilities to populate Request.Context

3 participants