<# .SYNOPSIS Fails if the rule engine has acquired a dependency it must not have. .DESCRIPTION Constitution Principle IV: the pure rule engine must not depend on Microsoft Graph, authentication, Azure Automation, or console rendering. Directory structure alone does not enforce that — one convenient call is all it takes to make the engine untestable offline, and the failure is silent until someone tries to run the tests without a tenant. This check is the enforcement. It runs in CI (pipelines/validate.yml) and is cheap enough to run locally on every change. .EXAMPLE ./tests/Test-EnginePurity.ps1 #> [CmdletBinding()] param( [string] $EnginePath = (Join-Path (Split-Path $PSScriptRoot -Parent) 'src/RuleEngine'), [switch] $PassThru ) $ErrorActionPreference = 'Stop' $forbidden = @( @{ Name = 'Microsoft Graph call'; Regex = 'Invoke-MgGraphRequest|Connect-MgGraph|graph\.microsoft\.com|Invoke-PersonaGraphRequest' } @{ Name = 'Authentication layer'; Regex = 'Connect-Persona\w+' } @{ Name = 'Data provider layer'; Regex = 'Get-Persona(Users|GroupMembership|DirectoryRoles)' } @{ Name = 'Persistence layer'; Regex = 'Set-UserPersonaAttribute|New-PersonaWriteBody|Compare-PersonaValue' } @{ Name = 'Console rendering'; Regex = 'Write-Host|Write-UserPersonaResult|Write-PersonaSummary' } @{ Name = 'Direct HTTP'; Regex = 'Invoke-RestMethod|Invoke-WebRequest|System\.Net\.Http' } @{ Name = 'Filesystem access'; Regex = 'Get-Content|Set-Content|Out-File|Export-Csv' } @{ Name = 'Non-deterministic input'; Regex = 'Get-Random|Get-Date|\[datetime\]::(Now|UtcNow|Today)|New-Guid' } ) if (-not (Test-Path $EnginePath)) { Write-Host "Engine path '$EnginePath' does not exist yet - nothing to check." -ForegroundColor Yellow if ($PassThru) { return @() } exit 0 } $findings = [System.Collections.Generic.List[object]]::new() foreach ($file in Get-ChildItem -Path $EnginePath -Filter '*.ps1' -File -Recurse) { # Tokenize rather than scan raw text. Comments in this codebase legitimately # name the forbidden functions when explaining why the engine does not call # them, and a text scan cannot tell a trailing comment from code. The parser # can, exactly. $tokens = $null $parseErrors = $null $null = [System.Management.Automation.Language.Parser]::ParseFile( $file.FullName, [ref]$tokens, [ref]$parseErrors) if ($parseErrors.Count -gt 0) { $findings.Add([pscustomobject]@{ File = $file.Name Line = $parseErrors[0].Extent.StartLineNumber Dependency = 'Parse error' Text = $parseErrors[0].Message }) continue } # Rebuild each line from its non-comment tokens. $codeByLine = @{} foreach ($token in $tokens) { if ($token.Kind -eq 'Comment') { continue } $line = $token.Extent.StartLineNumber if (-not $codeByLine.ContainsKey($line)) { $codeByLine[$line] = [System.Text.StringBuilder]::new() } $null = $codeByLine[$line].Append($token.Text).Append(' ') } foreach ($line in ($codeByLine.Keys | Sort-Object)) { $code = $codeByLine[$line].ToString() foreach ($rule in $forbidden) { if ($code -match $rule.Regex) { $findings.Add([pscustomobject]@{ File = $file.Name Line = $line Dependency = $rule.Name Text = $code.Trim() }) } } } } if ($findings.Count -gt 0) { Write-Host "Engine purity check FAILED - $($findings.Count) violation(s) of Principle IV:" -ForegroundColor Red $findings | Format-Table -AutoSize | Out-String | Write-Host Write-Host 'The rule engine must remain testable offline with synthetic data.' -ForegroundColor Red if ($PassThru) { return $findings } exit 1 } Write-Host 'Engine purity check passed: the rule engine has no forbidden dependencies.' -ForegroundColor Green if ($PassThru) { return @() } exit 0