Files
personaEngine2/README.md
T

280 lines
15 KiB
Markdown
Raw Normal View History

2026-08-20 16:44:04 -04:00
# Persona Engine
A modular, configuration-driven **PowerShell 7** identity-classification service for **Microsoft Entra ID**.
The engine enumerates Entra user accounts, evaluates each one against an ordered, JSON-defined rule set, deterministically assigns **exactly one persona**, and updates a single approved persona attribute — and only when the calculated value differs from the current value.
2026-08-20 16:53:23 -04:00
> **Project status: Phase 1 complete — specification drafted.**
> No implementation code exists yet. The approved requirements baseline is
> [`Persona-Engine-Developer-Handoff.txt`](Persona-Engine-Developer-Handoff.txt), now converted
> into [`specs/001-persona-engine/spec.md`](specs/001-persona-engine/spec.md). Project governance
> is ratified in [`.specify/memory/constitution.md`](.specify/memory/constitution.md) (v1.0.0).
> The next step is Phase 2: produce `plan.md` and `research.md`, closing OTD-001 through OTD-005.
2026-08-20 16:44:04 -04:00
---
## Spec-driven development
This project follows the **GitHub Spec Kit** workflow. Nothing is implemented before it is specified, planned, and decomposed into tasks.
```
Specify -> Plan -> Tasks -> Implement
```
| Stage | Artifact | Phase | State |
| --- | --- | --- | --- |
| Baseline | `Persona-Engine-Developer-Handoff.txt` | 0 | Approved |
2026-08-20 16:53:23 -04:00
| Constitution | `.specify/memory/constitution.md` | 0 | Ratified v1.0.0 |
| Specify | `specs/001-persona-engine/spec.md` | 1 | Draft complete |
| Plan | `specs/001-persona-engine/plan.md`, `research.md` | 2 | In progress |
2026-08-20 16:44:04 -04:00
| Contracts | `data-model.md`, `contracts/`, `persona-engine.schema.json` | 3 | Not started |
| Tasks | `specs/001-persona-engine/tasks.md` | 4 | Not started |
| Implement | `src/`, `tests/`, `pipelines/` | 512 | Not started |
Any item that is unresolved must be captured as an explicit **assumption, risk, or architecture decision**. It must never be silently implemented.
---
## Core design principles
| Principle | Meaning |
| --- | --- |
| **Deterministic** | The same input and configuration always produce the same persona. |
| **Exactly one result** | Every evaluated account receives one persona — never zero, never several. |
| **First match wins** | Rules are ordered; evaluation stops at the first match. |
| **Idempotent** | Re-running changes nothing unless the calculated value actually changed. |
| **Configuration-driven** | Business rules live in JSON, never in PowerShell source. |
| **Fail safe** | If evaluation cannot complete reliably, the existing persona is preserved. |
| **Explainable** | Every result identifies the matched rule, run ID, UPN, and Account Object ID. |
| **Modular** | The pure rule engine has no dependency on Graph, Azure Automation, or the console. |
---
## Scope (version 1)
**In scope**
- Microsoft Entra **user objects only**
- Ordered, first-match business rules defined in **JSON** (the only supported configuration format)
- Property, group-membership, and role-based conditions with nested `All` / `Any` composition
- Native PowerShell `-WhatIf` as the approved no-write control
- Structured, audit-friendly logging plus immediate per-user output and periodic summaries
- Local PowerShell 7 execution and Azure Automation PowerShell 7 runbook execution
**Out of scope for v1** — the architecture must not assume these share user-object properties:
- Service principals, managed identities, workload identities, agentic identities
### Candidate persona catalogue
These are candidate business classifications, **not** hard-coded engine behaviour:
`Guest` · `BreakGlass-Admin` · `Tier0-Admin` · `Tier1-Admin` · `Tier2-Admin` · `Restricted-User` · `Test-Account` · `Service-Account` · `Shared-Functional-Account` · `Meeting-Room-Device` · `Employee` · `Contractor` · `Student`
Two values are processing results rather than rules:
- **`Unclassified`** — evaluation succeeded, but no rule matched.
- **`EvaluationError`** — evaluation could not complete; the existing persona is preserved.
---
## Components
### 1. `Invoke-PersonaEngine.ps1`
Retrieval, evaluation, reporting, and controlled persistence.
| Parameter | Notes |
| --- | --- |
| `-ConfigPath <string>` | Required |
| `-WhatIf` | Native risk-mitigation parameter; the approved no-write control |
| `-Verbose` / `-Debug` | Native common parameters; `-Debug` must **not** mean read-only |
| `-UserObjectId <GUID>` | Optional single-user test execution |
| `-OutputPath <string>` | Optional override, if permitted |
| `-CorrelationId <GUID>` | Optional supplied run identifier |
The script uses `CmdletBinding` with `SupportsShouldProcess`.
```powershell
# Read-only evaluation
./Invoke-PersonaEngine.ps1 -ConfigPath ./config/persona-engine.json -WhatIf
# Read-only with operational detail
./Invoke-PersonaEngine.ps1 -ConfigPath ./config/persona-engine.json -WhatIf -Verbose
# Single-user validation
./Invoke-PersonaEngine.ps1 -ConfigPath ./config/persona-engine.json -UserObjectId <ACCOUNT-OBJECT-ID> -WhatIf
# Production, changed-values-only processing
./Invoke-PersonaEngine.ps1 -ConfigPath ./config/persona-engine.json
```
**Exit codes**
| Code | Meaning |
| --- | --- |
| `0` | Successful run; no fatal processing errors |
| `1` | Configuration validation failure |
| `2` | Authentication / authorization failure |
| `3` | User enumeration failure |
| `4` | Fatal required data-provider failure |
| `5` | Reconciliation failure |
| `6` | Unexpected fatal engine error |
A per-user `EvaluationError` does not necessarily terminate the run, but the final status must report the number of affected accounts and may apply a configurable warning/failure threshold.
### 2. `Edit-PersonaEngineConfig.ps1`
Configuration validation, interactive editing, synthetic rule testing, and pipeline enforcement.
| Parameter | Notes |
| --- | --- |
| `-ConfigPath <string>` | Required |
| `-ValidateOnly` | Validate without entering the editor |
| `-NonInteractive` | Pipeline mode; returns codes instead of prompting |
| `-SchemaPath <string>` | Optional schema override |
| `-OutputPath <string>` | Optional Save-As target |
| `-TreatWarningsAsErrors` | Escalate warnings |
| `-TestDataPath <string>` | Optional synthetic sample input |
```powershell
# Validate only
./Edit-PersonaEngineConfig.ps1 -ConfigPath ./config/persona-engine.json -ValidateOnly
# Pipeline validation
./Edit-PersonaEngineConfig.ps1 -ConfigPath ./config/persona-engine.json -ValidateOnly -NonInteractive
# Interactive editor
./Edit-PersonaEngineConfig.ps1 -ConfigPath ./config/persona-engine.json
```
Validation runs in four layers: **JSON syntax → JSON Schema → semantic → safety**.
---
## Architecture
```
Invoke-PersonaEngine.ps1
|
+-- Configuration Import-PersonaConfiguration, Test-PersonaConfiguration, Resolve-TargetAttribute
+-- Authentication Connect-PersonaGraphInteractive, Connect-PersonaGraphManagedIdentity
+-- Data Providers Get-PersonaUsers, Get-PersonaGroupMembership, Get-PersonaDirectoryRoles
+-- Normalization ConvertTo-PersonaUserRecord, ConvertTo-PersonaMembershipRecord
+-- Rule Engine Test-PersonaCondition, Test-PersonaConditionGroup, Test-PersonaRule, Resolve-UserPersona
+-- Persistence Compare-PersonaValue, Set-UserPersonaAttribute
+-- Presentation Write-UserPersonaResult, Write-PersonaSummary
+-- Audit New-PersonaAuditRecord, Export-PersonaRunReport
```
**Critical flow**
```
Graph acquisition -> normalized identity record -> pure rule engine
-> persona decision result -> comparison -> optional persistence adapter
-> console and structured audit output
```
The **pure rule engine must not depend** on Graph authentication, Azure Automation, or console rendering. It has to be testable offline with synthetic data.
---
## Planned repository structure
```
PersonaEngine/
|-- README.md
|-- Invoke-PersonaEngine.ps1
|-- Edit-PersonaEngineConfig.ps1
|-- PersonaEngine.psd1
|-- PersonaEngine.psm1
|
|-- config/ persona-engine.example.json, persona-engine.schema.json
|-- src/ Authentication/ Configuration/ DataProviders/ Normalization/
| RuleEngine/ Persistence/ Presentation/ Audit/
|-- tests/ Unit/ Integration/ Configuration/ Safety/ TestData/
|-- docs/ Architecture.md BusinessRules.md ConfigurationReference.md
| Logging.md SecurityModel.md OperationsRunbook.md
|-- pipelines/ validate.yml test.yml release.yml
|-- specs/
|-- 001-persona-engine/
|-- spec.md plan.md tasks.md research.md data-model.md quickstart.md
|-- contracts/ checklists/
```
---
## Security model
- **Authentication** — Azure Automation uses a **managed identity**. Local development uses an approved interactive or read-only application identity. **No client secret in source control.**
- **Least privilege** — the execution identity gets only what the enabled rules require: in-scope user properties, configured group membership, configured role data, and the existing persona value.
- **Write permissions** — the production identity is granted the minimum permission needed to update the configured target attribute. Whether Entra can enforce write scope at the **individual attribute level must be verified, never assumed** (see OTD-003).
- **Compensating controls**, if the Graph permission proves broader than the single attribute: the persistence module accepts only the approved target attribute; that attribute must appear in `approvedWritableAttributes`; validation rejects all others; a dedicated function builds a request body containing only that attribute; unit and integration tests inspect the request body; code owners and branch policies gate persistence changes; directory audit logs are monitored for unexpected property writes.
- **Data handling** — UPN and Account Object ID are approved for logs. Never log access tokens, authorization headers, secrets, or full Graph responses. Detailed condition values are diagnostic-only, behind `-Debug`.
- **Kill switch** — disable the Automation schedule, run with `-WhatIf`, revoke production write permission, or disable write deployment stages.
---
## Sanitization requirements
**Every** artifact in this repository — docs, examples, tests, configuration samples — must be free of organization names, real domains, tenant or subscription IDs, automation account names, real UPNs or Object IDs, real group/role identifiers, environment-specific attribute names, Log Analytics details, and any secret, token, certificate, or credential.
Use placeholders only:
`<ORGANIZATION-NAME>` · `<PRIMARY-DOMAIN>` · `<TENANT-ID>` · `<ACCOUNT-OBJECT-ID>` · `<GROUP-OBJECT-ID>` · `<APPROVED-PERSONA-ATTRIBUTE-NAME>` · `<AUTOMATION-ACCOUNT-NAME>` · `<LOG-OUTPUT-PATH>`
Synthetic test data must be obviously fictional and must never reproduce real employee records. Tenant-specific configuration belongs in a protected repository or configuration store, not here.
---
## Open technical decisions
Research items to be resolved in `research.md` or an ADR. **OTD-001 through OTD-005 must be closed before persistence implementation.**
| ID | Decision |
| --- | --- |
| OTD-001 | Select the exact Entra persona attribute mechanism — data type, Graph read/update method, discoverability, Conditional Access compatibility |
| OTD-002 | Confirm exact least-privilege Microsoft Graph permissions |
| OTD-003 | Confirm whether write authorization can be restricted to the individual target attribute |
| OTD-004 | Select the Graph access approach — SDK cmdlets, direct REST, or a controlled combination |
| OTD-005 | Select a JSON Schema validation approach compatible with PS7 locally and in Azure Automation |
| OTD-006 | Select structured-log destination and transport |
| OTD-007 | Define retry policy — retryable status codes, max attempts, backoff, jitter, logging |
| OTD-008 | Define full versus incremental processing roadmap |
| OTD-009 | Define production schedule and concurrency lock |
| OTD-010 | Define rollback implementation |
---
## Getting started
2026-08-20 16:53:23 -04:00
Implementation has not begun. The current work item is Phase 2 — planning.
2026-08-20 16:44:04 -04:00
2026-08-20 16:53:23 -04:00
1. ~~Initialize the Spec Kit project structure.~~ Done.
2. ~~Convert the handoff baseline into `specs/001-persona-engine/spec.md`.~~ Done.
2026-08-20 16:44:04 -04:00
3. Build a requirements traceability list using FR/NFR identifiers.
4. Close OTD-001 through OTD-005 before any persistence work.
5. Create `persona-engine.schema.json` and a placeholder-only `persona-engine.example.json`.
6. Define normalized PowerShell object contracts.
7. **Build the pure rule engine first**, with offline Pester tests, before any Graph integration.
8. Implement configuration validation and non-interactive pipeline mode.
9. Implement Graph read adapters, then console and structured logging.
10. Implement the persistence adapter **last**, with `ShouldProcess` and tests proving zero writes under `-WhatIf`.
Requirements: **PowerShell 7**. Offline unit testing must not require tenant connectivity.
---
## Delivery
- Hosted in **Azure DevOps Git** — feature branches, pull requests, protected release branch, code owners on persistence, security configuration, and production rules.
- **Validation pipeline** — repository hygiene checks, PowerShell static analysis, JSON Schema validation, semantic/safety configuration validation, Pester unit tests, Pester safety tests, test result publication, artifact packaging.
- **Release pipeline** — validate approved branch/tag, repeat validation and tests, package, deploy to Azure Automation, import modules, publish runbook, **keep the schedule disabled**, execute `-WhatIf` validation, approval gate, then enable enforcement.
- Changes to the target attribute, `approvedWritableAttributes`, rule priority, rule enablement, rule conditions, persona outputs, authentication permissions, persistence functions, logging destination, or `WhatIf`/`ShouldProcess` behaviour **all require review**.
---
## Definition of done (v1)
Version 1 is complete when both PowerShell scripts are implemented; the JSON Schema exists and is documented; validation covers syntax, schema, semantic, and safety layers; ordered first-match evaluation and nested `All`/`Any` work within the configured depth; the initial property and membership operators are tested; null behaviour matches the approved decision; required group-lookup failures produce `EvaluationError` and preserve the existing persona; `Unclassified` users are reported distinctly; each user result displays immediately; interim and final summaries work — including interval `0` — and reconciliation checks pass; logs include UPN and Account Object ID; **`-WhatIf` produces zero Graph writes**; only changed valid values are written in enforcement mode; the write payload contains only the approved target attribute; local offline Pester, read-only production-tenant, Azure Automation PowerShell 7, and Azure DevOps pipeline runs all pass; security review confirms permissions and compensating controls; operational documentation, kill switch, and rollback procedure are complete; and `-WhatIf` impact evidence is reviewed before enforcement is enabled.