Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 159 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# AGENTS.md

This repository welcomes contributions from both humans and automated agents.

Use this document as the **default operating manual** for any agent (AI assistant, code generator, refactoring bot, CI helper)
working in the repo.

> If this file conflicts with any other repo document, follow the more specific rule (e.g., STYLEGUIDE.md for code style).

---

## 1. Project intent (read first)

**IdentityLifecycleEngine (IdLE)** is a **generic, headless, configuration-driven** identity lifecycle orchestration engine
(Joiner / Mover / Leaver) built for **PowerShell 7+**.

Core principles:

- Portable, modular, testable, highly configurable
- **Plan → Execute** separation (deterministic planning, repeatable execution)
- Workflow configuration is **data-only** (no script blocks / no dynamic expressions)
- Engine stays **host-agnostic** (no UI / no service-host coupling)

Authoritative docs:

- `README.md` (high-level)
- `docs/index.md` (documentation entry point)
- `docs/advanced/architecture.md` (architecture decisions)
- `docs/advanced/security.md` (trust boundaries)

---

## 2. How to behave as an agent

### 2.1 No assumptions
- If something is unclear, **ask targeted questions**.
- Prefer a sensible default proposal, but **explicitly label it** as a default.

### 2.2 One change-set at a time
- Keep PRs focused: one issue / one theme.
- Avoid drive-by refactors unless the issue is specifically about refactoring.

### 2.3 Determinism over cleverness
- Prefer explicit validation and deterministic behavior.
- Avoid “magic” behavior, hidden fallbacks, or implicit global state.

---

## 3. Coding standards (PowerShell)

Follow `STYLEGUIDE.md` for the full rule set. In short:

- PowerShell **Core 7+**
- Use **approved PowerShell verbs** (Verb-Noun)
- 4 spaces indentation, UTF-8, LF
- Public cmdlets require **comment-based help** (`.SYNOPSIS`, `.DESCRIPTION`, `.PARAMETER`, `.EXAMPLE`, `.OUTPUTS`)
- Inline comments should explain **why**, not what

### 3.1 Public vs. Private
- Keep a clean separation between Public and Private functions.
- Treat exported commands as stable contracts.

### 3.2 Configuration is data-only (no code in config)
- Workflow definitions (PSD1) must be **data-only**:
- No `ScriptBlock`
- No dynamic PowerShell expressions
- Validate early and fail with actionable errors.

---

## 4. Architectural constraints

### 4.1 Headless core
The engine (`IdLE.Core`) must **not** depend on:

- UI frameworks
- interactive prompts
- service hosts / web servers

### 4.2 Steps vs. Providers
- **Steps**: convergence logic, idempotent intent, no authentication
- **Providers**: system adapters, handle authentication and external calls
- Steps should only write to declared `State.*` outputs.

### 4.3 Eventing
Use the single event contract:

- `Context.EventSink.WriteEvent(Type, Message, StepName, Data)`

Do not introduce alternative eventing APIs unless explicitly planned and documented.

---

## 5. Testing expectations

Follow `docs/advanced/testing.md` and `CONTRIBUTING.md`.

- Use **Pester** for tests.
- Unit tests must not call live systems.
- Provider implementations require **provider contract tests**.

**PR rule:** New behavior should include tests. Bug fixes must include a regression test.

---

## 6. Documentation responsibilities

- Keep docs short and linkable.
- Update docs when changing contracts, configuration schema, public cmdlets, step behavior, or provider contracts.

### 6.1 Generated references
The cmdlet and step references under `docs/reference/` are generated.
Do **not** edit generated files by hand—regenerate via the repository tools as documented in `CONTRIBUTING.md`.

---

## 7. Security and trust boundaries

Follow `docs/advanced/security.md`.

- Treat workflow definitions and lifecycle requests as **untrusted inputs**
- Reject executable objects in untrusted inputs (e.g., ScriptBlocks)
- Treat step registry, providers, and external event sinks as **trusted extension points**, but validate their shapes

---

## 8. PR checklist (Definition of Done)

Before proposing or finalizing a PR, ensure:

- [ ] Changes are scoped to a single issue/theme
- [ ] All tests pass (`Invoke-Pester -Path ./tests`)
- [ ] Public APIs have comment-based help
- [ ] Docs updated where needed (`README.md`, `docs/`, `examples/`)
- [ ] Generated docs regenerated if required (`docs/reference/*`)
- [ ] No architecture rules violated (`docs/advanced/architecture.md`)
- [ ] No security boundary regressions (`docs/advanced/security.md`)

---

## 9. Where to put new guidance for agents

- General, cross-cutting agent rules → `AGENTS.md` (repo root)
- Code style details → `STYLEGUIDE.md`
- Contributor workflow and DoD → `CONTRIBUTING.md`
- Architecture decisions → `docs/advanced/architecture.md`
- Security boundaries → `docs/advanced/security.md`

---

## 10. When in doubt

Prefer:

- clarity over cleverness
- explicit validation over implicit behavior
- small PRs over large rewrites
- documentation + tests as part of the same change

10 changes: 9 additions & 1 deletion docs/advanced/provider-capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,15 @@ Naming convention:
- dot-separated segments
- no whitespace
- starts with a letter
- examples: `Identity.Read`, `Identity.Disable`, `Entitlement.Write`
- examples: `Identity.Read`, `Identity.Disable`, `IdLE.Entitlement.List`

### Entitlement capability set

Providers that support entitlement assignments should advertise the minimal trio:

- `IdLE.Entitlement.List` — list entitlements assigned to a specific identity
- `IdLE.Entitlement.Grant` — assign an entitlement to an identity
- `IdLE.Entitlement.Revoke` — remove an entitlement from an identity

## High-level flow

Expand Down
7 changes: 7 additions & 0 deletions docs/usage/steps.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ IdLE uses a fail-fast execution model in V1:
- a failing step stops plan execution
- results and events capture what happened

## Built-in steps (starter pack)

IdLE ships with a small set of built-in steps to keep demos and tests frictionless:

- **IdLE.Step.EnsureAttribute**: converges an identity attribute to the desired value using `With.IdentityKey`, `With.Name`, and `With.Value`. Requires a provider with `EnsureAttribute` and usually the `Identity.Attribute.Ensure` capability.
- **IdLE.Step.EnsureEntitlement**: converges an entitlement assignment to `Present` or `Absent` using `With.IdentityKey`, `With.Entitlement` (Kind + Id + optional DisplayName), `With.State`, and optional `With.Provider` (default `Identity`). Requires provider methods `ListEntitlements` plus `GrantEntitlement` or `RevokeEntitlement` and typically the capabilities `IdLE.Entitlement.List` plus `IdLE.Entitlement.Grant|Revoke`.

## Related

- [Workflows](workflows.md)
Expand Down
4 changes: 4 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ Workflow samples are located in:

- `examples/workflows/`

Highlighted samples:

- `joiner-ensureentitlement.psd1` — ensures a demo group assignment via the built-in EnsureEntitlement step

Workflows are **data-only** PSD1 files. A minimal workflow looks like:

```powershell
Expand Down
18 changes: 18 additions & 0 deletions examples/workflows/joiner-ensureentitlement.psd1
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
@{
Name = 'Joiner - Ensure Entitlement'
LifecycleEvent = 'Joiner'
Steps = @(
@{
Name = 'Ensure Department'
Type = 'IdLE.Step.EnsureAttribute'
With = @{ IdentityKey = 'user1'; Name = 'Department'; Value = 'IT'; Provider = 'Identity' }
RequiresCapabilities = 'Identity.Attribute.Ensure'
},
@{
Name = 'Assign demo group'
Type = 'IdLE.Step.EnsureEntitlement'
With = @{ IdentityKey = 'user1'; Entitlement = @{ Kind = 'Group'; Id = 'demo-group'; DisplayName = 'Demo Group' }; State = 'Present'; Provider = 'Identity' }
RequiresCapabilities = @('IdLE.Entitlement.List', 'IdLE.Entitlement.Grant')
}
)
}
22 changes: 17 additions & 5 deletions src/IdLE.Core/Private/Get-IdleProviderCapabilities.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -49,19 +49,29 @@ function Get-IdleProviderCapabilities {
# We keep this conservative to avoid accidentally overstating capabilities.
$methodNames = @($Provider.PSObject.Methods.Name)

if ($methodNames -contains 'GetIdentity') {
$capabilities += 'Identity.Read'
if ($methodNames -contains 'ListEntitlements') {
$capabilities += 'IdLE.Entitlement.List'
}
if ($methodNames -contains 'GrantEntitlement') {
$capabilities += 'IdLE.Entitlement.Grant'
}
if ($methodNames -contains 'RevokeEntitlement') {
$capabilities += 'IdLE.Entitlement.Revoke'
}
if ($methodNames -contains 'EnsureAttribute') {
$capabilities += 'Identity.Attribute.Ensure'
}
if ($methodNames -contains 'DisableIdentity') {
$capabilities += 'Identity.Disable'
}
if ($methodNames -contains 'GetIdentity') {
$capabilities += 'Identity.Read'
}
}

# Normalize, validate, and return a stable list.
$normalized = @()
$normalized = New-Object System.Collections.Generic.List[string]
$seen = New-Object System.Collections.Generic.HashSet[string]
foreach ($c in @($capabilities)) {
if ($null -eq $c) {
continue
Expand All @@ -81,8 +91,10 @@ function Get-IdleProviderCapabilities {
throw "Provider capability '$s' is invalid. Expected dot-separated segments like 'Identity.Read' or 'Entitlement.Write'."
}

$normalized += $s
if ($seen.Add($s)) {
$null = $normalized.Add($s)
}
}

return @($normalized | Sort-Object -Unique)
return @($normalized)
}
7 changes: 7 additions & 0 deletions src/IdLE.Core/Private/Get-IdleStepRegistry.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -121,5 +121,12 @@ function Get-IdleStepRegistry {
}
}

if (-not $registry.ContainsKey('IdLE.Step.EnsureEntitlement')) {
$handler = Resolve-IdleStepHandlerName -CommandName 'Invoke-IdleStepEnsureEntitlement' -ModuleName 'IdLE.Steps.Common'
if (-not [string]::IsNullOrWhiteSpace($handler)) {
$registry['IdLE.Step.EnsureEntitlement'] = $handler
}
}

return $registry
}
Loading
Loading