Skip to content

Extend Condition DSL with Contains and Like operators (with member-access enumeration)#252

Merged
ntt-matthias-fleschuetz merged 8 commits intomainfrom
copilot/extend-condition-dsl-with-operators
Mar 1, 2026
Merged

Extend Condition DSL with Contains and Like operators (with member-access enumeration)#252
ntt-matthias-fleschuetz merged 8 commits intomainfrom
copilot/extend-condition-dsl-with-operators

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 1, 2026

Adds list membership and wildcard pattern matching to the Condition/Precondition DSL. Common enterprise JML scenarios (group membership guards, contractor checks, pattern-based exclusions) previously required host-side workarounds.

Operators

Contains / NotContains

  • List membership evaluation (case-insensitive)
  • Path must resolve to array/list (throws on scalar or hashtable)
Condition = @{
  NotContains = @{
    Path  = 'Request.Context.Identity.Entitlements.Id'
    Value = 'CN=BreakGlass-Users,OU=Groups,DC=example,DC=com'
  }
}

Like / NotLike

  • Wildcard pattern matching (case-insensitive)
  • Scalar: direct match
  • List: true if ANY element matches (Like) or NO element matches (NotLike)
  • Throws on hashtable/dictionary
# Guard destructive leaver step
Condition = @{
  All = @(
    @{ Equals = @{ Path = 'Plan.LifecycleEvent'; Value = 'Leaver' } }
    @{
      NotLike = @{
        Path    = 'Request.Context.Identity.Entitlements.Id'
        Pattern = 'CN=HR-*'
      }
    }
  )
}

Implementation

  • Schema validation: Test-IdleConditionSchema.ps1 - Added operator shape validation (Path+Value / Path+Pattern)
  • Evaluation logic: Test-IdleCondition.ps1 - Implements case-insensitive list/scalar semantics with explicit hashtable rejection
  • Member-access enumeration: Get-IdlePropertyValue.ps1 - Enhanced to support property extraction from array items (e.g., Entitlements.Id returns array of all Id values)
  • Tests: 58 new tests covering schema validation, evaluation, edge cases, member-access enumeration, and hashtable rejection
  • Documentation: conditions.md, context-resolver.md, and capabilities.md updated with operator reference, member-access enumeration explanation, semantics table, corrected path syntax, and troubleshooting guidance for inspecting resolved context data

Member-Access Enumeration

A critical enhancement to support real-world provider data: entitlements are objects with Kind, Id, and DisplayName properties, not simple strings.

The path resolver now supports PowerShell-style member-access enumeration:

  • Request.Context.Identity.Entitlements → array of entitlement objects
  • Request.Context.Identity.Entitlements.Id → array of all Id values
  • Request.Context.Identity.Entitlements.DisplayName → array of all DisplayName values

This enables the documented use cases to work with realistic provider output.

Discoverability and Troubleshooting

Added comprehensive guidance for workflow authors to inspect resolved context data:

  • New troubleshooting section in context-resolver.md showing three methods to inspect context structure:
    • ConvertTo-Json -Depth 5 for full structure inspection
    • Format-Table -AutoSize for quick tabular view
    • Get-Member for property discovery
  • Enhanced capabilities reference documenting entitlement object structure with examples
  • Cross-references between documentation for complete workflow authoring guidance

This addresses the challenge of discovering what data structure is written by context resolvers, enabling admins to determine the correct path syntax for Conditions.

Additional Changes

Fixed PSSA warnings per CI mode requirements:

  • PSUseApprovedVerbs: Ensure-IdleBlankLineBeforeMarkdownListsAdd-IdleBlankLineBeforeMarkdownLists
  • PSUseConsistentIndentation: Generate-IdleStepReference.ps1, New-IdleEntraIDAdapter.ps1

Backward Compatibility

Pure additive extension. Existing workflows unchanged. Member-access enumeration is transparent for paths that don't use it.

Original prompt

This section details on the original issue you should resolve

<issue_title>Extend Condition DSL with Contains and Like operators (list-aware evaluation)</issue_title>
<issue_description>## Problem Statement

The current Condition / Precondition DSL does not provide native support
for evaluating list membership or wildcard matching on resolved context data.

Typical real-world use cases include:

  • Execute a step only if the identity is not member of a specific group
  • Execute a step only if the identity is not member of any group matching a pattern
  • Execute a step only if a string attribute matches a wildcard pattern

Today, these scenarios require host-side boolean flags or workaround logic,
which reduces expressiveness and breaks provider-agnostic design principles.

Proposed Solution

Extend the Condition / Precondition DSL with the following operators:

  • Contains
  • NotContains
  • Like
  • NotLike

Semantics

Case-insensitive by default.

Contains / NotContains

  • Intended for list membership evaluation
  • Path must resolve to a list
  • Contains returns true if the list contains the specified value
  • NotContains returns true if the list does not contain the specified value

Example:

Condition = @{
  NotContains = @{
    Path  = 'Request.Context.Identity.Entitlements'
    Value = 'CN=BreakGlass-Users,OU=Groups,DC=example,DC=com'
  }
}

Like / NotLike

  • Intended for wildcard pattern matching
  • If Path resolves to:
    • Scalar → evaluate normally
    • List → evaluate true if any element matches the pattern

Example (list evaluation):

Condition = @{
  NotLike = @{
    Path    = 'Request.Context.Identity.Entitlements'
    Pattern = 'CN=HR-*'
  }
}

Example (scalar evaluation):

Condition = @{
  Like = @{
    Path    = 'Request.Context.Identity.Profile.DisplayName'
    Pattern = '* (Contractor)'
  }
}

Design Principles

  • All, Any, and None remain logical combinators (AND / OR / NOT)
  • No new logical operators introduced
  • No provider changes required
  • Fail-fast if:
    • Contains used on scalar
    • Invalid Path
    • Missing Value/Pattern

Applies to

  • Condition
  • Preconditions

Alternatives Considered

Introduce dedicated operators such as AnyLike

Rejected because:

  • Any is a logical combinator (OR), not a comparison operator
  • Overloading it would break DSL consistency
  • Adds unnecessary surface area

Host-side boolean flags

Rejected because:

  • Breaks provider-agnostic workflow design
  • Pushes logic outside DSL
  • Reduces readability and maintainability

Impact

  • Does not affect existing workflows
  • No breaking changes
  • Pure additive DSL extension

Backward compatibility: fully preserved.

Additional Context

This enhancement improves common enterprise scenarios:

  • Joiner / Mover / Leaver group-guard logic
  • Conditional entitlement pruning
  • Pattern-based exclusion rules
  • Safer destructive step gating

Documentation updates required:

  • Conditions page (new operators section)
  • Preconditions page (reference section)
  • Common Patterns examples
  • Explicit note: Contains only valid for lists, Like supports scalar and list paths
    </issue_description>

<agent_instructions>just extend, we accept breaking changes as long as all documentation, examples, references, are fully updated. no backward compatibility mapping or similar is required.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 3 commits March 1, 2026 15:41
…ition DSL

Co-authored-by: ntt-matthias-fleschuetz <55826276+ntt-matthias-fleschuetz@users.noreply.github.com>
Co-authored-by: ntt-matthias-fleschuetz <55826276+ntt-matthias-fleschuetz@users.noreply.github.com>
… operators

Co-authored-by: ntt-matthias-fleschuetz <55826276+ntt-matthias-fleschuetz@users.noreply.github.com>
Copilot AI changed the title [WIP] Extend Condition DSL with Contains and Like operators Extend Condition DSL with Contains and Like operators Mar 1, 2026
@ntt-matthias-fleschuetz ntt-matthias-fleschuetz marked this pull request as ready for review March 1, 2026 16:01
Copilot AI review requested due to automatic review settings March 1, 2026 16:01
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

Extends IdLE’s Condition/Precondition DSL to cover common JML gating scenarios by adding list membership (Contains/NotContains) and wildcard matching (Like/NotLike) operators, with accompanying schema validation, tests, and documentation updates.

Changes:

  • Added schema validation for Contains, NotContains, Like, and NotLike operator shapes.
  • Implemented evaluator semantics for list/scalar handling and case-insensitive comparisons/pattern matching.
  • Updated documentation and adjusted tooling/provider scripts to satisfy PSSA/CI requirements.

Reviewed changes

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

Show a summary per file
File Description
tools/Generate-IdleStepReference.ps1 Renames helper to an approved verb and adjusts formatting/indentation.
tests/Core/Test-IdleCondition.Tests.ps1 Adds schema + evaluator tests for the new operators and edge cases.
src/IdLE.Provider.EntraID/Private/New-IdleEntraIDAdapter.ps1 Indentation-only change to satisfy consistent indentation checks.
src/IdLE.Core/Private/Test-IdleConditionSchema.ps1 Extends condition schema validation to recognize and validate new operators.
src/IdLE.Core/Private/Test-IdleCondition.ps1 Adds runtime evaluation logic for Contains/NotContains/Like/NotLike.
docs/use/workflows/conditions.md Documents new operators, semantics table, troubleshooting, and examples.

Comment thread src/IdLE.Core/Private/Test-IdleCondition.ps1
Comment thread src/IdLE.Core/Private/Test-IdleCondition.ps1
Comment thread src/IdLE.Core/Private/Test-IdleCondition.ps1
Comment thread src/IdLE.Core/Private/Test-IdleCondition.ps1
Comment thread src/IdLE.Core/Private/Test-IdleCondition.ps1
Comment thread src/IdLE.Core/Private/Test-IdleCondition.ps1
Comment thread src/IdLE.Core/Private/Test-IdleCondition.ps1
Comment thread src/IdLE.Core/Private/Test-IdleCondition.ps1
Co-authored-by: ntt-matthias-fleschuetz <55826276+ntt-matthias-fleschuetz@users.noreply.github.com>
Copilot AI force-pushed the copilot/extend-condition-dsl-with-operators branch from 891e854 to b80ff9c Compare March 1, 2026 16:18
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 1, 2026

Code Coverage Report

Overall Project 73.25% 🍏

There is no coverage information present for the Files changed

Copy link
Copy Markdown
Collaborator

@ntt-matthias-fleschuetz ntt-matthias-fleschuetz left a comment

Choose a reason for hiding this comment

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

@copilot I am not sure if the list handling is really supporting how contextresolver for e.g. for entitlements are writing to Request.Context.Identity.Entitlements
As I understand, Entitlements is a list and each entry has different properties (e.g. Id, DisplayName), depending on the provider used. So Request.Context.Identity.Entitlements never contains something that can be evaluated by some Contains / NotContains
Value = 'CN=BreakGlass-Users,OU=Groups,DC=example,DC=com' or am I mistaken? I assume same for Like / NotLike as the pattern may apply to one field we do not determine? Technically, the path to the fields that may contain this is Request.Context.Identity.Entitlements[].DisplayName` or similar, while [] is hard to resolve.
How is it handling this at the moment and can we achieve the original use case for groups at all with current implementation?

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 1, 2026

@copilot I am not sure if the list handling is really supporting how contextresolver for e.g. for entitlements are writing to Request.Context.Identity.Entitlements
As I understand, Entitlements is a...

You're absolutely correct. The current implementation has a critical flaw:

The Problem:

  • Request.Context.Identity.Entitlements resolves to a list of objects (with Kind, Id, DisplayName properties)
  • Contains/Like operators cast these objects to strings, producing @{Kind=Group; Id=...} which never matches the DN string
  • The path resolver doesn't support member-access enumeration (Entitlements.Id returns null)

Current Behavior:
When an entitlement object like @{ Kind='Group'; Id='CN=Users,...'; DisplayName='Users' } is cast to string, it becomes @{Kind=Group; Id=CN=Users,...; DisplayName=Users}, not CN=Users,....

Required Fix:
The path resolver (Get-IdlePropertyValue) needs to support PowerShell's member-access enumeration so that Request.Context.Identity.Entitlements.Id returns @('g1', 'g2', ...) instead of null.

Next Steps:
I'll enhance Get-IdlePropertyValue to support array member access, update tests with realistic entitlement objects, and fix documentation examples to show the correct path syntax (Entitlements.Id or Entitlements.DisplayName).

Copilot AI and others added 2 commits March 1, 2026 16:37
Co-authored-by: ntt-matthias-fleschuetz <55826276+ntt-matthias-fleschuetz@users.noreply.github.com>
Co-authored-by: ntt-matthias-fleschuetz <55826276+ntt-matthias-fleschuetz@users.noreply.github.com>
Copilot AI changed the title Extend Condition DSL with Contains and Like operators Extend Condition DSL with Contains and Like operators (with member-access enumeration) Mar 1, 2026
Copy link
Copy Markdown
Collaborator

@ntt-matthias-fleschuetz ntt-matthias-fleschuetz left a comment

Choose a reason for hiding this comment

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

@copilot an additional problem is that we cannot check what is exactly written by context resolve when its a list. Template Substitution for EmitEvents do not support any non-scalar values. We need a way that an admin in workflow definition can find out what is exactly written to the context path elements.

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Mar 1, 2026

@copilot an additional problem is that we cannot check what is exactly written by context resolve when its a list. Template Substitution for EmitEvents do not support any non-scalar values. We need a ...

You're right - inspecting resolved context data is crucial. Here's how admins can discover the structure:

Option 1: Use the plan object in tests/scripts

$plan = New-IdlePlan -WorkflowPath ./workflow.psd1 -Request $req -Providers $providers
$plan.Request.Context.Identity.Entitlements | ConvertTo-Json -Depth 3

Option 2: Add a debug EmitEvent step
Use IdLE.Step.EmitEvent with the Data parameter to emit structured context:

@{
  Name = 'Debug: Show Entitlements'
  Type = 'IdLE.Step.EmitEvent'
  With = @{
    Message = 'Entitlement count: {{Request.Context.Identity.Entitlements.Count}}'
  }
}

(Note: .Count works because arrays have this property)

Recommended Documentation Enhancement:
Add a troubleshooting section to context-resolver.md showing how to inspect resolved data. This is outside the scope of the current PR but would be valuable.

The member-access enumeration fix (commits 521fdfe, d5c66a7) enables the original use cases once admins know to use Entitlements.Id or Entitlements.DisplayName paths.

Co-authored-by: ntt-matthias-fleschuetz <55826276+ntt-matthias-fleschuetz@users.noreply.github.com>
@ntt-matthias-fleschuetz ntt-matthias-fleschuetz merged commit d5cd722 into main Mar 1, 2026
8 checks passed
@blindzero blindzero deleted the copilot/extend-condition-dsl-with-operators branch March 22, 2026 16:22
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.

Extend Condition DSL with Contains and Like operators (list-aware evaluation)

3 participants