|
|
|
|
@@ -1,31 +1,23 @@
|
|
|
|
|
<#
|
|
|
|
|
.SYNOPSIS
|
|
|
|
|
Phase 6.2 exit-gate compliance check — stub. Each `Assert-*` either passes
|
|
|
|
|
(Write-Host green) or throws. Non-zero exit = fail.
|
|
|
|
|
Phase 6.2 exit-gate compliance check. Each check either passes or records a
|
|
|
|
|
failure; non-zero exit = fail.
|
|
|
|
|
|
|
|
|
|
.DESCRIPTION
|
|
|
|
|
Validates Phase 6.2 (Authorization runtime) completion. Checks enumerated
|
|
|
|
|
in `docs/v2/implementation/phase-6-2-authorization-runtime.md`
|
|
|
|
|
§"Compliance Checks (run at exit gate)".
|
|
|
|
|
|
|
|
|
|
Current status: SCAFFOLD. Every check writes a TODO line and does NOT throw.
|
|
|
|
|
Each implementation task in Phase 6.2 is responsible for replacing its TODO
|
|
|
|
|
with a real check before closing that task.
|
|
|
|
|
|
|
|
|
|
.NOTES
|
|
|
|
|
Usage: pwsh ./scripts/compliance/phase-6-2-compliance.ps1
|
|
|
|
|
Exit: 0 = all checks passed (or are still TODO); non-zero = explicit fail
|
|
|
|
|
Exit: 0 = all checks passed; non-zero = one or more FAILs
|
|
|
|
|
#>
|
|
|
|
|
[CmdletBinding()]
|
|
|
|
|
param()
|
|
|
|
|
|
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
|
$script:failures = 0
|
|
|
|
|
|
|
|
|
|
function Assert-Todo {
|
|
|
|
|
param([string]$Check, [string]$ImplementationTask)
|
|
|
|
|
Write-Host " [TODO] $Check (implement during $ImplementationTask)" -ForegroundColor Yellow
|
|
|
|
|
}
|
|
|
|
|
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..')).Path
|
|
|
|
|
|
|
|
|
|
function Assert-Pass {
|
|
|
|
|
param([string]$Check)
|
|
|
|
|
@@ -34,47 +26,121 @@ function Assert-Pass {
|
|
|
|
|
|
|
|
|
|
function Assert-Fail {
|
|
|
|
|
param([string]$Check, [string]$Reason)
|
|
|
|
|
Write-Host " [FAIL] $Check — $Reason" -ForegroundColor Red
|
|
|
|
|
Write-Host " [FAIL] $Check - $Reason" -ForegroundColor Red
|
|
|
|
|
$script:failures++
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Write-Host ""
|
|
|
|
|
Write-Host "=== Phase 6.2 compliance — Authorization runtime ===" -ForegroundColor Cyan
|
|
|
|
|
Write-Host ""
|
|
|
|
|
function Assert-Deferred {
|
|
|
|
|
param([string]$Check, [string]$FollowupPr)
|
|
|
|
|
Write-Host " [DEFERRED] $Check (follow-up: $FollowupPr)" -ForegroundColor Yellow
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Write-Host "Stream A — LdapGroupRoleMapping (control plane)"
|
|
|
|
|
Assert-Todo "Control/data-plane separation — Core.Authorization has zero refs to LdapGroupRoleMapping" "Stream A.2"
|
|
|
|
|
Assert-Todo "Authoring validation — AclsTab rejects duplicate (LdapGroup, Scope) pre-save" "Stream A.3"
|
|
|
|
|
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 ', ')"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function Assert-TextAbsent {
|
|
|
|
|
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-Fail $Check "pattern '$Pattern' unexpectedly found in $p"
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Assert-Pass "$Check (pattern '$Pattern' absent from: $($RelPaths -join ', '))"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Write-Host ""
|
|
|
|
|
Write-Host "Stream B — Evaluator + trie + cache"
|
|
|
|
|
Assert-Todo "Trie invariants — PermissionTrieBuilder idempotent (build twice == equal)" "Stream B.1"
|
|
|
|
|
Assert-Todo "Additive grants + cluster isolation — cross-cluster leakage impossible" "Stream B.1"
|
|
|
|
|
Assert-Todo "Galaxy FolderSegment coverage — folder-subtree grant cascades; siblings unaffected" "Stream B.2"
|
|
|
|
|
Assert-Todo "Redundancy-safe invalidation — generation-mismatch forces trie re-load on peer" "Stream B.4"
|
|
|
|
|
Assert-Todo "Membership freshness — 15 min interval elapsed + LDAP down = fail-closed" "Stream B.5"
|
|
|
|
|
Assert-Todo "Auth cache fail-closed — 5 min AuthCacheMaxStaleness exceeded = NotGranted" "Stream B.5"
|
|
|
|
|
Assert-Todo "AuthorizationDecision shape — Allow + NotGranted only; Denied variant exists unused" "Stream B.6"
|
|
|
|
|
Write-Host "=== Phase 6.2 compliance - Authorization runtime ===" -ForegroundColor Cyan
|
|
|
|
|
Write-Host ""
|
|
|
|
|
|
|
|
|
|
Write-Host "Stream A - LdapGroupRoleMapping (control plane)"
|
|
|
|
|
Assert-FileExists "LdapGroupRoleMapping entity present" "src/ZB.MOM.WW.OtOpcUa.Configuration/Entities/LdapGroupRoleMapping.cs"
|
|
|
|
|
Assert-FileExists "AdminRole enum present" "src/ZB.MOM.WW.OtOpcUa.Configuration/Enums/AdminRole.cs"
|
|
|
|
|
Assert-FileExists "ILdapGroupRoleMappingService present" "src/ZB.MOM.WW.OtOpcUa.Configuration/Services/ILdapGroupRoleMappingService.cs"
|
|
|
|
|
Assert-FileExists "LdapGroupRoleMappingService impl present" "src/ZB.MOM.WW.OtOpcUa.Configuration/Services/LdapGroupRoleMappingService.cs"
|
|
|
|
|
Assert-TextFound "Write-time invariant: IsSystemWide XOR ClusterId" "IsSystemWide=true requires ClusterId" @("src/ZB.MOM.WW.OtOpcUa.Configuration/Services/LdapGroupRoleMappingService.cs")
|
|
|
|
|
Assert-FileExists "EF migration for LdapGroupRoleMapping" "src/ZB.MOM.WW.OtOpcUa.Configuration/Migrations/20260419131444_AddLdapGroupRoleMapping.cs"
|
|
|
|
|
|
|
|
|
|
Write-Host ""
|
|
|
|
|
Write-Host "Stream C — OPC UA operation wiring"
|
|
|
|
|
Assert-Todo "Every operation wired — Browse/Read/Write/HistoryRead/HistoryUpdate/CreateMonitoredItems/TransferSubscriptions/Call/Ack/Confirm/Shelve" "Stream C.1-C.7"
|
|
|
|
|
Assert-Todo "HistoryRead uses its own flag — Read+no-HistoryRead denies HistoryRead" "Stream C.3"
|
|
|
|
|
Assert-Todo "Mixed-batch semantics — 3 allowed + 2 denied returns per-item status, no coarse failure" "Stream C.6"
|
|
|
|
|
Assert-Todo "Browse ancestor visibility — deep grant implies ancestor browse; denied ancestors filter" "Stream C.7"
|
|
|
|
|
Assert-Todo "Subscription re-authorization — revoked grant surfaces BadUserAccessDenied in one publish" "Stream C.5"
|
|
|
|
|
Write-Host "Stream B - Permission-trie evaluator (Core.Authorization)"
|
|
|
|
|
Assert-FileExists "OpcUaOperation enum present" "src/ZB.MOM.WW.OtOpcUa.Core.Abstractions/OpcUaOperation.cs"
|
|
|
|
|
Assert-FileExists "NodeScope record present" "src/ZB.MOM.WW.OtOpcUa.Core/Authorization/NodeScope.cs"
|
|
|
|
|
Assert-FileExists "AuthorizationDecision tri-state" "src/ZB.MOM.WW.OtOpcUa.Core/Authorization/AuthorizationDecision.cs"
|
|
|
|
|
Assert-TextFound "Verdict has Denied member (reserved for v2.1)" "Denied" @("src/ZB.MOM.WW.OtOpcUa.Core/Authorization/AuthorizationDecision.cs")
|
|
|
|
|
Assert-FileExists "IPermissionEvaluator present" "src/ZB.MOM.WW.OtOpcUa.Core/Authorization/IPermissionEvaluator.cs"
|
|
|
|
|
Assert-FileExists "PermissionTrie present" "src/ZB.MOM.WW.OtOpcUa.Core/Authorization/PermissionTrie.cs"
|
|
|
|
|
Assert-FileExists "PermissionTrieBuilder present" "src/ZB.MOM.WW.OtOpcUa.Core/Authorization/PermissionTrieBuilder.cs"
|
|
|
|
|
Assert-FileExists "PermissionTrieCache present" "src/ZB.MOM.WW.OtOpcUa.Core/Authorization/PermissionTrieCache.cs"
|
|
|
|
|
Assert-TextFound "Cache keyed on GenerationId" "GenerationId" @("src/ZB.MOM.WW.OtOpcUa.Core/Authorization/PermissionTrieCache.cs")
|
|
|
|
|
Assert-FileExists "UserAuthorizationState present" "src/ZB.MOM.WW.OtOpcUa.Core/Authorization/UserAuthorizationState.cs"
|
|
|
|
|
Assert-TextFound "MembershipFreshnessInterval default 15 min" "FromMinutes\(15\)" @("src/ZB.MOM.WW.OtOpcUa.Core/Authorization/UserAuthorizationState.cs")
|
|
|
|
|
Assert-TextFound "AuthCacheMaxStaleness default 5 min" "FromMinutes\(5\)" @("src/ZB.MOM.WW.OtOpcUa.Core/Authorization/UserAuthorizationState.cs")
|
|
|
|
|
Assert-FileExists "TriePermissionEvaluator impl present" "src/ZB.MOM.WW.OtOpcUa.Core/Authorization/TriePermissionEvaluator.cs"
|
|
|
|
|
Assert-TextFound "HistoryRead maps to NodePermissions.HistoryRead" "HistoryRead.+NodePermissions\.HistoryRead" @("src/ZB.MOM.WW.OtOpcUa.Core/Authorization/TriePermissionEvaluator.cs")
|
|
|
|
|
|
|
|
|
|
Write-Host ""
|
|
|
|
|
Write-Host "Stream D — Admin UI + SignalR invalidation"
|
|
|
|
|
Assert-Todo "SignalR invalidation — sp_PublishGeneration pushes PermissionTrieCache invalidate < 2 s" "Stream D.4"
|
|
|
|
|
Write-Host "Control/data-plane separation (decision #150)"
|
|
|
|
|
Assert-TextAbsent "Evaluator has zero references to LdapGroupRoleMapping" "LdapGroupRoleMapping" @(
|
|
|
|
|
"src/ZB.MOM.WW.OtOpcUa.Core/Authorization/TriePermissionEvaluator.cs",
|
|
|
|
|
"src/ZB.MOM.WW.OtOpcUa.Core/Authorization/PermissionTrie.cs",
|
|
|
|
|
"src/ZB.MOM.WW.OtOpcUa.Core/Authorization/PermissionTrieBuilder.cs",
|
|
|
|
|
"src/ZB.MOM.WW.OtOpcUa.Core/Authorization/PermissionTrieCache.cs",
|
|
|
|
|
"src/ZB.MOM.WW.OtOpcUa.Core/Authorization/IPermissionEvaluator.cs")
|
|
|
|
|
|
|
|
|
|
Write-Host ""
|
|
|
|
|
Write-Host "Stream C foundation (dispatch-wiring gate)"
|
|
|
|
|
Assert-FileExists "ILdapGroupsBearer present" "src/ZB.MOM.WW.OtOpcUa.Server/Security/ILdapGroupsBearer.cs"
|
|
|
|
|
Assert-FileExists "AuthorizationGate present" "src/ZB.MOM.WW.OtOpcUa.Server/Security/AuthorizationGate.cs"
|
|
|
|
|
Assert-TextFound "Gate has StrictMode knob" "StrictMode" @("src/ZB.MOM.WW.OtOpcUa.Server/Security/AuthorizationGate.cs")
|
|
|
|
|
Assert-Deferred "DriverNodeManager dispatch-path wiring (11 surfaces)" "Phase 6.2 Stream C follow-up task #143"
|
|
|
|
|
|
|
|
|
|
Write-Host ""
|
|
|
|
|
Write-Host "Stream D data layer (ValidatedNodeAclAuthoringService)"
|
|
|
|
|
Assert-FileExists "ValidatedNodeAclAuthoringService present" "src/ZB.MOM.WW.OtOpcUa.Admin/Services/ValidatedNodeAclAuthoringService.cs"
|
|
|
|
|
Assert-TextFound "InvalidNodeAclGrantException present" "class InvalidNodeAclGrantException" @("src/ZB.MOM.WW.OtOpcUa.Admin/Services/ValidatedNodeAclAuthoringService.cs")
|
|
|
|
|
Assert-TextFound "Rejects None permissions" "Permission set cannot be None" @("src/ZB.MOM.WW.OtOpcUa.Admin/Services/ValidatedNodeAclAuthoringService.cs")
|
|
|
|
|
Assert-Deferred "RoleGrantsTab + AclsTab Probe-this-permission + SignalR invalidation + draft diff section" "Phase 6.2 Stream D follow-up task #144"
|
|
|
|
|
|
|
|
|
|
Write-Host ""
|
|
|
|
|
Write-Host "Cross-cutting"
|
|
|
|
|
Assert-Todo "No test-count regression — dotnet test ZB.MOM.WW.OtOpcUa.slnx count ≥ pre-Phase-6.2 baseline" "Final exit-gate"
|
|
|
|
|
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 = 1042
|
|
|
|
|
if ($passCount -ge $baseline) { Assert-Pass "No test-count regression ($passCount >= $baseline pre-Phase-6.2 baseline)" }
|
|
|
|
|
else { Assert-Fail "Test-count regression" "passed $passCount < baseline $baseline" }
|
|
|
|
|
|
|
|
|
|
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.2 compliance: scaffold-mode PASS (all checks TODO)" -ForegroundColor Green
|
|
|
|
|
Write-Host "Phase 6.2 compliance: PASS" -ForegroundColor Green
|
|
|
|
|
exit 0
|
|
|
|
|
}
|
|
|
|
|
Write-Host "Phase 6.2 compliance: $script:failures FAIL(s)" -ForegroundColor Red
|
|
|
|
|
|