Files
personaEngine2/README.md
T
2026-08-20 16:44:04 -04:00

277 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.
> **Project status: Phase 0 — approved requirements baseline.**
> No implementation code exists yet. The authoritative baseline is
> [`Persona-Engine-Developer-Handoff.txt`](Persona-Engine-Developer-Handoff.txt).
> The next step is Phase 1: convert that baseline into `specs/001-persona-engine/spec.md`.
---
## 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 |
| Specify | `specs/001-persona-engine/spec.md` | 1 | Not started |
| Plan | `specs/001-persona-engine/plan.md`, `research.md` | 2 | Not started |
| 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
Implementation has not begun. The current work item is Phase 1.
1. Initialize the Spec Kit project structure.
2. Convert the handoff baseline into `specs/001-persona-engine/spec.md`.
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.