Public/Invoke-GovernanceAudit.ps1
|
function Invoke-GovernanceAudit { <# .SYNOPSIS Builds the resolved model then runs the compliance loop in Audit mode: every governed resource is checked and every deviation is reported. No changes are made to the live Azure DevOps organisation. Returns a non-terminating error and exits non-zero when findings exist, so CI pipelines can detect non-compliance. #> [CmdletBinding()] param( [Parameter(Mandatory)][string]$ProgramPath, [Parameter(Mandatory)][string]$ResolvedPath, [string]$Org ) # Always build first — audit must reflect the latest authored config. Invoke-GovernanceBuild -ProgramPath $ProgramPath -OutputPath $ResolvedPath | Out-Null $source = Import-GovernanceSource -ProgramPath $ProgramPath $manifest = $source.Manifest $teamIds = $source.TeamIds # read-only in audit; not written back $targetOrg = if ($Org) { $Org } else { $manifest.org } $orgUrl = ConvertTo-AdoOrgUrl -Org $targetOrg Initialize-AdoAuth -Manifest $manifest -OrgUrl $orgUrl | Out-Null $resolved = ConvertFrom-Yaml (Get-Content $ResolvedPath -Raw) $reportPath = Join-Path (Split-Path $ResolvedPath -Parent) 'audit-report.txt' Write-Host "Auditing '$($resolved.program)' in $orgUrl" -ForegroundColor Cyan # The target project is itself a governed resource: if it is missing, every # downstream check would fail with noise, so report the one real finding. $projectDecl = $resolved.project $projectName = if ($projectDecl -and $projectDecl.name) { $projectDecl.name } else { $resolved.program } if (-not (Test-AdoProject -OrgUrl $orgUrl -Project $projectName)) { Write-Host " [MISSING] project: $projectName" -ForegroundColor Red Write-Error "Governance compliance: project '$projectName' does not exist in $orgUrl — every governed resource is missing." return } Invoke-GovernanceReconcile -Resolved $resolved -OrgUrl $orgUrl -Mode 'Audit' -ReportPath $reportPath -TeamIds $teamIds | Out-Null } |