<# .SYNOPSIS Phase 6.1 exit-gate compliance check. Each check either passes or records a failure; non-zero exit = fail. .DESCRIPTION Validates Phase 6.1 (Resilience & Observability runtime) completion. Checks enumerated in `docs/v2/implementation/phase-6-1-resilience-and-observability.md` §"Compliance Checks (run at exit gate)". Runs a mix of file-presence checks, text-pattern sweeps over the committed codebase, and a full `dotnet test` pass to exercise the invariants each class encodes. Meant to be invoked from repo root. .NOTES Usage: pwsh ./scripts/compliance/phase-6-1-compliance.ps1 Exit: 0 = all checks passed; non-zero = one or more FAILs #> [CmdletBinding()] param() $ErrorActionPreference = 'Stop' $script:failures = 0 $repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path function Assert-Pass { param([string]$Check) Write-Host " [PASS] $Check" -ForegroundColor Green } function Assert-Fail { param([string]$Check, [string]$Reason) Write-Host " [FAIL] $Check - $Reason" -ForegroundColor Red $script:failures++ } function Assert-Deferred { param([string]$Check, [string]$FollowupPr) Write-Host " [DEFERRED] $Check (follow-up: $FollowupPr)" -ForegroundColor Yellow } function Assert-FileExists { param([string]$Check, [string]$RelPath) $full = Join-Path $repoRoot $RelPath if (Test-Path $full) { Assert-Pass "$Check ($RelPath)" } else { Assert-Fail $Check "missing file: $RelPath" } } function Assert-TextFound { param([string]$Check, [string]$Pattern, [string[]]$RelPaths) foreach ($p in $RelPaths) { $full = Join-Path $repoRoot $p if (-not (Test-Path $full)) { continue } if (Select-String -Path $full -Pattern $Pattern -Quiet) { Assert-Pass "$Check (matched in $p)" return } } Assert-Fail $Check "pattern '$Pattern' not found in any of: $($RelPaths -join ', ')" } Write-Host "" Write-Host "=== Phase 6.1 compliance - Resilience & Observability runtime ===" -ForegroundColor Cyan Write-Host "" Write-Host "Stream A - Resilience layer" Assert-FileExists "Pipeline builder present" "src/ZB.MOM.WW.OtOpcUa.Core/Resilience/DriverResiliencePipelineBuilder.cs" Assert-FileExists "CapabilityInvoker present" "src/ZB.MOM.WW.OtOpcUa.Core/Resilience/CapabilityInvoker.cs" Assert-FileExists "WriteIdempotentAttribute present" "src/ZB.MOM.WW.OtOpcUa.Core.Abstractions/WriteIdempotentAttribute.cs" Assert-TextFound "Pipeline key includes HostName (per-device isolation)" "PipelineKey\(.+HostName" @("src/ZB.MOM.WW.OtOpcUa.Core/Resilience/DriverResiliencePipelineBuilder.cs") Assert-TextFound "OnReadValue routes through invoker" "DriverCapability\.Read," @("src/ZB.MOM.WW.OtOpcUa.Server/OpcUa/DriverNodeManager.cs") Assert-TextFound "OnWriteValue routes through invoker" "ExecuteWriteAsync" @("src/ZB.MOM.WW.OtOpcUa.Server/OpcUa/DriverNodeManager.cs") Assert-TextFound "HistoryRead routes through invoker" "DriverCapability\.HistoryRead" @("src/ZB.MOM.WW.OtOpcUa.Server/OpcUa/DriverNodeManager.cs") Assert-FileExists "Galaxy supervisor CircuitBreaker preserved" "src/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Proxy/Supervisor/CircuitBreaker.cs" Assert-FileExists "Galaxy supervisor Backoff preserved" "src/ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Proxy/Supervisor/Backoff.cs" Write-Host "" Write-Host "Stream B - Tier A/B/C runtime" Assert-FileExists "DriverTier enum present" "src/ZB.MOM.WW.OtOpcUa.Core.Abstractions/DriverTier.cs" Assert-TextFound "DriverTypeMetadata requires Tier" "DriverTier Tier" @("src/ZB.MOM.WW.OtOpcUa.Core.Abstractions/DriverTypeRegistry.cs") Assert-FileExists "MemoryTracking present" "src/ZB.MOM.WW.OtOpcUa.Core/Stability/MemoryTracking.cs" Assert-FileExists "MemoryRecycle present" "src/ZB.MOM.WW.OtOpcUa.Core/Stability/MemoryRecycle.cs" Assert-TextFound "MemoryRecycle is Tier C gated" "_tier == DriverTier\.C" @("src/ZB.MOM.WW.OtOpcUa.Core/Stability/MemoryRecycle.cs") Assert-FileExists "ScheduledRecycleScheduler present" "src/ZB.MOM.WW.OtOpcUa.Core/Stability/ScheduledRecycleScheduler.cs" Assert-TextFound "Scheduler ctor rejects Tier A/B" "tier != DriverTier\.C" @("src/ZB.MOM.WW.OtOpcUa.Core/Stability/ScheduledRecycleScheduler.cs") Assert-FileExists "WedgeDetector present" "src/ZB.MOM.WW.OtOpcUa.Core/Stability/WedgeDetector.cs" Assert-TextFound "WedgeDetector is demand-aware" "HasPendingWork" @("src/ZB.MOM.WW.OtOpcUa.Core/Stability/WedgeDetector.cs") Write-Host "" Write-Host "Stream C - Health + logging" Assert-FileExists "DriverHealthReport present" "src/ZB.MOM.WW.OtOpcUa.Core/Observability/DriverHealthReport.cs" Assert-FileExists "HealthEndpointsHost present" "src/ZB.MOM.WW.OtOpcUa.Server/Observability/HealthEndpointsHost.cs" Assert-TextFound "State matrix: Healthy = 200" "ReadinessVerdict\.Healthy => 200" @("src/ZB.MOM.WW.OtOpcUa.Core/Observability/DriverHealthReport.cs") Assert-TextFound "State matrix: Faulted = 503" "ReadinessVerdict\.Faulted => 503" @("src/ZB.MOM.WW.OtOpcUa.Core/Observability/DriverHealthReport.cs") Assert-FileExists "LogContextEnricher present" "src/ZB.MOM.WW.OtOpcUa.Core/Observability/LogContextEnricher.cs" Assert-TextFound "Enricher pushes DriverInstanceId property" "DriverInstanceId" @("src/ZB.MOM.WW.OtOpcUa.Core/Observability/LogContextEnricher.cs") Assert-TextFound "JSON sink opt-in via Serilog:WriteJson" "Serilog:WriteJson" @("src/ZB.MOM.WW.OtOpcUa.Server/Program.cs") Write-Host "" Write-Host "Stream D - LiteDB generation-sealed cache" Assert-FileExists "GenerationSealedCache present" "src/ZB.MOM.WW.OtOpcUa.Configuration/LocalCache/GenerationSealedCache.cs" Assert-TextFound "Sealed files marked ReadOnly" "FileAttributes\.ReadOnly" @("src/ZB.MOM.WW.OtOpcUa.Configuration/LocalCache/GenerationSealedCache.cs") Assert-TextFound "Corruption fails closed with GenerationCacheUnavailableException" "GenerationCacheUnavailableException" @("src/ZB.MOM.WW.OtOpcUa.Configuration/LocalCache/GenerationSealedCache.cs") Assert-FileExists "ResilientConfigReader present" "src/ZB.MOM.WW.OtOpcUa.Configuration/LocalCache/ResilientConfigReader.cs" Assert-FileExists "StaleConfigFlag present" "src/ZB.MOM.WW.OtOpcUa.Configuration/LocalCache/StaleConfigFlag.cs" Write-Host "" Write-Host "Stream E - Admin /hosts (data layer)" Assert-FileExists "DriverInstanceResilienceStatus entity" "src/ZB.MOM.WW.OtOpcUa.Configuration/Entities/DriverInstanceResilienceStatus.cs" Assert-FileExists "DriverResilienceStatusTracker present" "src/ZB.MOM.WW.OtOpcUa.Core/Resilience/DriverResilienceStatusTracker.cs" Assert-Deferred "FleetStatusHub SignalR push + Blazor /hosts column refresh" "Phase 6.1 Stream E.2/E.3 visual-compliance follow-up" Write-Host "" Write-Host "Cross-cutting" Write-Host " Running full solution test suite..." -ForegroundColor DarkGray $prevPref = $ErrorActionPreference $ErrorActionPreference = 'Continue' $testOutput = & dotnet test (Join-Path $repoRoot 'ZB.MOM.WW.OtOpcUa.slnx') --nologo 2>&1 $ErrorActionPreference = $prevPref $passLine = $testOutput | Select-String 'Passed:\s+(\d+)' -AllMatches $failLine = $testOutput | Select-String 'Failed:\s+(\d+)' -AllMatches $passCount = 0; foreach ($m in $passLine.Matches) { $passCount += [int]$m.Groups[1].Value } $failCount = 0; foreach ($m in $failLine.Matches) { $failCount += [int]$m.Groups[1].Value } $baseline = 906 if ($passCount -ge $baseline) { Assert-Pass "No test-count regression ($passCount >= $baseline baseline)" } else { Assert-Fail "Test-count regression" "passed $passCount < baseline $baseline" } # Pre-existing Client.CLI Subscribe flake tracked separately; exit gate tolerates a single # known flake but flags any NEW failures. if ($failCount -le 1) { Assert-Pass "No new failing tests (pre-existing CLI flake tolerated)" } else { Assert-Fail "New failing tests" "$failCount failures > 1 tolerated" } Write-Host "" if ($script:failures -eq 0) { Write-Host "Phase 6.1 compliance: PASS" -ForegroundColor Green exit 0 } Write-Host "Phase 6.1 compliance: $script:failures FAIL(s)" -ForegroundColor Red exit 1