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

15 KiB
Raw Blame History

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 1 complete — specification drafted. No implementation code exists yet. The approved requirements baseline is Persona-Engine-Developer-Handoff.txt, now converted into specs/001-persona-engine/spec.md. Project governance is ratified in .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.


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
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
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.

# 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
# 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 2 — planning.

  1. Initialize the Spec Kit project structure. Done.
  2. Convert the handoff baseline into specs/001-persona-engine/spec.md. Done.
  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.