tests/maproom/scripts/New-S2DFixtures.ps1

<#
.SYNOPSIS
    Generates all 8 S2DCartographer unit-test fixture JSON files in tests/maproom/Fixtures/.
 
.DESCRIPTION
    Creates scenario-specific fixtures covering: healthy 2-node all-NVMe, 3-node mixed-tier,
    4-node three-way mirror with thin volumes, 4-node mixed resiliency, 16-node enterprise,
    2-node thin-overcommit warning, 3-node insufficient-reserve critical, and single-node.
 
    All company data uses the IIC (Infinite Improbability Corp) canonical standard.
 
.EXAMPLE
    .\tests\maproom\scripts\New-S2DFixtures.ps1
 
.NOTES
    Run from repo root or any location — paths are resolved relative to this script.
#>

[CmdletBinding()]
param()

Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'

$fixturesDir = Resolve-Path (Join-Path $PSScriptRoot '..\Fixtures')
Write-Host "Generating fixtures to: $fixturesDir" -ForegroundColor Cyan

# ─── Constants ────────────────────────────────────────────────────────────────
$TiB_DIVISOR     = [int64]1099511627776
$TB_DIVISOR      = [int64]1000000000000
$GiB_DIVISOR     = [int64]1073741824
$INFRA_VOL_BYTES = [int64]268435456000   # 250 GiB — Azure Local infrastructure volume
$POOL_OVERHEAD   = 0.005                 # 0.5% pool metadata

# IIC domain constants
$IIC_DOMAIN   = 'iic.local'
$IIC_COMPANY  = 'Infinite Improbability Corp'
$IIC_TENANT   = '00000000-0000-0000-0000-000000000000'
$IIC_SUB      = '33333333-3333-3333-3333-333333333333'
$IIC_RG       = 'rg-iic-compute-01'

# ─── Helpers ──────────────────────────────────────────────────────────────────

function script:BytesToTiB([int64]$b) { [Math]::Round($b / $TiB_DIVISOR, 3) }
function script:BytesToTB([int64]$b)  { [Math]::Round($b / $TB_DIVISOR, 3) }

function script:MakeDisk([string]$node, [int]$num, [string]$role, [string]$media,
                          [int64]$sizeBytes, [string]$model, [string]$fw,
                          [string]$mfr, [int]$wear = 0) {
    $sizeTiB = [Math]::Round($sizeBytes / $TiB_DIVISOR, 3)
    $sizeTB  = [Math]::Round($sizeBytes / $TB_DIVISOR, 3)
    [ordered]@{
        NodeName          = $node
        FriendlyName      = $model
        SerialNumber      = 'IIC-{0}-D{1:D2}' -f $node.ToUpper(), $num
        Model             = $model
        MediaType         = $media
        BusType           = if ($media -eq 'NVMe') { 'NVMe' } else { 'SATA' }
        Role              = $role
        Usage             = if ($role -eq 'Cache') { 'Journal' } else { 'Auto-Select' }
        SizeBytes         = $sizeBytes
        SizeTiB           = $sizeTiB
        SizeTB            = $sizeTB
        SizeDisplay       = "$sizeTiB TiB ($sizeTB TB)"
        FirmwareVersion   = $fw
        Manufacturer      = $mfr
        HealthStatus      = 'Healthy'
        OperationalStatus = 'OK'
        CanPool           = $false
        WearPercentage    = $wear
        Temperature       = 35
        PowerOnHours      = 14400
        ReadErrors        = 0
        WriteErrors       = 0
    }
}

function script:MakeVolume([string]$name, [string]$resiliency, [int]$copies,
                            [int64]$footprintBytes, [string]$provisioning = 'Fixed',
                            [bool]$isInfra = $false, [int64]$allocatedBytes = 0) {
    $effPct  = if ($copies -gt 0) { [Math]::Round(100.0 / $copies, 1) } else { 50.0 }
    $usable  = [long]($footprintBytes / $copies)
    $alloc   = if ($allocatedBytes -gt 0) { $allocatedBytes } else { $usable }
    [ordered]@{
        FriendlyName          = $name
        ResiliencySettingName = $resiliency
        NumberOfDataCopies    = $copies
        ProvisioningType      = $provisioning
        IsInfrastructure      = $isInfra
        FootprintBytes        = $footprintBytes
        FootprintTiB          = [Math]::Round($footprintBytes / $TiB_DIVISOR, 3)
        SizeBytes             = $usable
        SizeTiB               = [Math]::Round($usable / $TiB_DIVISOR, 3)
        AllocatedBytes        = $alloc
        AllocatedTiB          = [Math]::Round($alloc / $TiB_DIVISOR, 3)
        EfficiencyPercent     = $effPct
        HealthStatus          = 'Healthy'
        OperationalStatus     = 'OK'
    }
}

function script:MakeWaterfall([int64]$rawBytes, [int]$nodeCount, [int64]$largestDiskBytes,
                               [int64]$allocatedBytes, [double]$blendedEfficiency = 33.3) {
    $overhead     = [long]($rawBytes * $POOL_OVERHEAD)
    $poolTotal    = $rawBytes - $overhead
    $reserveRec   = [Math]::Min($nodeCount, 4) * $largestDiskBytes
    $free         = $poolTotal - $allocatedBytes
    $isReserveOK  = $free -ge $reserveRec
    $reserveStat  = if ($free -ge $reserveRec) { 'Adequate' } elseif ($free -ge ($reserveRec*0.5)) { 'Warning' } else { 'Critical' }
    $available    = $poolTotal - $reserveRec - $INFRA_VOL_BYTES
    $usable       = [long]($available * ($blendedEfficiency / 100.0))
    [ordered]@{
        Raw = [ordered]@{
            Bytes   = $rawBytes
            TiB     = (BytesToTiB $rawBytes)
            TB      = (BytesToTB $rawBytes)
        }
        PoolOverhead = [ordered]@{
            Bytes = $overhead
            TiB   = (BytesToTiB $overhead)
        }
        PoolTotal = [ordered]@{
            Bytes = $poolTotal
            TiB   = (BytesToTiB $poolTotal)
        }
        Reserve = [ordered]@{
            RecommendedBytes = $reserveRec
            RecommendedTiB   = (BytesToTiB $reserveRec)
            ActualFreeBytes  = $free
            ActualFreeTiB    = (BytesToTiB $free)
            Status           = $reserveStat
        }
        InfrastructureVolume = [ordered]@{
            Bytes   = $INFRA_VOL_BYTES
            TiB     = (BytesToTiB $INFRA_VOL_BYTES)
            Present = $true
        }
        Available = [ordered]@{
            Bytes = $available
            TiB   = (BytesToTiB $available)
        }
        ResiliencyEfficiency = [ordered]@{
            BlendedPercent = $blendedEfficiency
        }
        Usable = [ordered]@{
            Bytes = $usable
            TiB   = (BytesToTiB $usable)
            TB    = (BytesToTB $usable)
        }
    }
}

function script:MakeHealthChecks([bool]$reserveOK = $true, [bool]$overcommit = $false,
                                  [bool]$diskHealth = $true, [bool]$volumeHealth = $true,
                                  [bool]$nvmeWear = $true) {
    @(
        [ordered]@{ CheckName='ReserveAdequacy';     Severity='Critical'; Status=if($reserveOK){'Pass'}else{'Fail'};   Details=if($reserveOK){'Reserve meets recommendation'}else{'CRITICAL: Reserve below 50% of recommendation'} }
        [ordered]@{ CheckName='DiskSymmetry';        Severity='Warning';  Status='Pass'; Details='All nodes have identical disk count and type' }
        [ordered]@{ CheckName='VolumeHealth';        Severity='Critical'; Status=if($volumeHealth){'Pass'}else{'Fail'}; Details=if($volumeHealth){'All volumes Healthy'}else{'One or more volumes in Degraded state'} }
        [ordered]@{ CheckName='DiskHealth';          Severity='Critical'; Status=if($diskHealth){'Pass'}else{'Fail'};   Details=if($diskHealth){'All disks Healthy'}else{'One or more disks non-Healthy'} }
        [ordered]@{ CheckName='NvmeWear';            Severity='Warning';  Status=if($nvmeWear){'Pass'}else{'Warn'};     Details=if($nvmeWear){'All NVMe disks below 80% wear'}else{'Warning: NVMe disk approaching end of wear'} }
        [ordered]@{ CheckName='ThinOvercommit';      Severity='Warning';  Status=if($overcommit){'Warn'}else{'Pass'};   Details=if($overcommit){'WARNING: Thin-provisioned volumes exceed available pool capacity'}else{'No thin overcommit detected'} }
        [ordered]@{ CheckName='FirmwareConsistency'; Severity='Info';     Status='Pass'; Details='All disks of the same model are on the same firmware version' }
        [ordered]@{ CheckName='RebuildCapacity';     Severity='Critical'; Status='Pass'; Details='Cluster can survive a single node failure and fully rebuild' }
        [ordered]@{ CheckName='InfraVolume';         Severity='Info';     Status='Pass'; Details='Infrastructure volume detected (250 GiB)' }
        [ordered]@{ CheckName='CacheTierHealth';     Severity='Warning';  Status='Pass'; Details='Cache tier healthy' }
    )
}

function script:WritFixture([string]$name, [hashtable]$data) {
    $path = Join-Path $fixturesDir "$name.json"
    $data | ConvertTo-Json -Depth 20 | Set-Content -Path $path -Encoding UTF8
    Write-Host " ✅ $name.json" -ForegroundColor Green
}

# ─── FIXTURE 1: 2node-allnvme ─────────────────────────────────────────────────
# 2 nodes · 0 cache · 4× 3.84 TB NVMe capacity per node · two-way mirror · healthy
Write-Host "`n[1/8] 2node-allnvme" -ForegroundColor DarkGray
$diskBytes = [int64]3840000000000
$nodes2    = @('azl-iic-2na-n01', 'azl-iic-2na-n02')
$disks2na  = foreach ($n in $nodes2) {
    foreach ($d in 1..4) { MakeDisk $n $d 'Capacity' 'NVMe' $diskBytes 'INTEL SSDPE2KX040T8' 'VCV10162' 'Intel' 12 }
}
$raw2na    = [int64]($diskBytes * 8)
$vol2na_fp = [int64]($diskBytes * 8 * 0.8)  # volume footprint occupies ~80% raw (2-way = 40% usable of raw)
$pool2na   = [long]($raw2na * (1 - $POOL_OVERHEAD)) - $INFRA_VOL_BYTES - [int64]($diskBytes * 2 * 0.5)  # reasonable alloc
$alloc2na  = [int64]($raw2na * 0.38)          # leaves room for reserve
$wf2na     = MakeWaterfall $raw2na 2 $diskBytes $alloc2na 50.0

WritFixture '2node-allnvme' ([ordered]@{
    scenario      = '2node-allnvme'
    description   = '2-node, all-NVMe capacity (no cache tier), two-way mirror, healthy baseline'
    company       = $IIC_COMPANY
    clusterName   = 'azlocal-iic-2na-01'
    nodeCount     = 2
    nodes         = $nodes2
    diskConfig    = [ordered]@{ cacheDisksPerNode=0; capacityDisksPerNode=4; diskModel='INTEL SSDPE2KX040T8'; diskSizeTB=3.84; cacheTierMode='None -- all-NVMe software storage bus write cache' }
    physicalDisks = @($disks2na)
    storagePool   = [ordered]@{ name="S2D on azlocal-iic-2na-01"; totalSizeBytes=[long]($raw2na*(1-$POOL_OVERHEAD)); allocatedSizeBytes=$alloc2na; freeSizeBytes=([long]($raw2na*(1-$POOL_OVERHEAD))-$alloc2na) }
    volumes       = @(
        (MakeVolume 'VMs'  'Mirror' 2 ([int64]($diskBytes*5)) 'Fixed')
        (MakeVolume 'ClusterStorage_InfraVolume'  'Mirror' 2 ([int64]($INFRA_VOL_BYTES*2)) 'Fixed' $true)
    )
    capacityWaterfall = $wf2na
    healthChecks  = (MakeHealthChecks -reserveOK $true)
    metadata      = [ordered]@{ tenantId=$IIC_TENANT; subscriptionId=$IIC_SUB; resourceGroup=$IIC_RG }
    generatedAt   = (Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')
    generatedBy   = 'New-S2DFixtures.ps1'
})

# ─── FIXTURE 2: 3node-mixed-tier ──────────────────────────────────────────────
# 3 nodes · 2× 1.6 TB NVMe cache + 4× 1.92 TB SSD capacity per node · three-way mirror · healthy
Write-Host "[2/8] 3node-mixed-tier" -ForegroundColor DarkGray
$cacheDiskBytes = [int64]1600000000000   # 1.6 TB NVMe (cache)
$capDiskBytes   = [int64]1920000000000   # 1.92 TB SSD (capacity)
$nodes3m        = @('azl-iic-3mt-n01', 'azl-iic-3mt-n02', 'azl-iic-3mt-n03')
$disks3mt       = foreach ($n in $nodes3m) {
    foreach ($d in 1..2) { MakeDisk $n $d 'Cache'    'NVMe' $cacheDiskBytes 'INTEL SSDPED1K750GA' 'E2010420' 'Intel' 8 }
    foreach ($d in 3..6) { MakeDisk $n $d 'Capacity' 'SSD'  $capDiskBytes   'SAMSUNG MZ7LH1T9HMLT' 'HXT7904Q' 'Samsung' 5 }
}
$raw3mt   = [int64]($capDiskBytes * 12)  # 12 capacity disks
$alloc3mt = [int64]($raw3mt * 0.35)
$wf3mt    = MakeWaterfall $raw3mt 3 $capDiskBytes $alloc3mt 33.3

WritFixture '3node-mixed-tier' ([ordered]@{
    scenario      = '3node-mixed-tier'
    description   = '3-node, NVMe cache tier + SSD capacity tier, three-way mirror, healthy'
    company       = $IIC_COMPANY
    clusterName   = 'azlocal-iic-3mt-01'
    nodeCount     = 3
    nodes         = $nodes3m
    diskConfig    = [ordered]@{ cacheDisksPerNode=2; capacityDisksPerNode=4; cacheDiskModel='INTEL SSDPED1K750GA'; cacheDiskSizeTB=1.6; capDiskModel='SAMSUNG MZ7LH1T9HMLT'; capDiskSizeTB=1.92; cacheTierMode='ReadWrite' }
    physicalDisks = @($disks3mt)
    storagePool   = [ordered]@{ name="S2D on azlocal-iic-3mt-01"; totalSizeBytes=[long]($raw3mt*(1-$POOL_OVERHEAD)); allocatedSizeBytes=$alloc3mt; freeSizeBytes=([long]($raw3mt*(1-$POOL_OVERHEAD))-$alloc3mt) }
    volumes       = @(
        (MakeVolume 'VMs'  'Mirror' 3 ([int64]($capDiskBytes*4)) 'Fixed')
        (MakeVolume 'ClusterStorage_InfraVolume' 'Mirror' 3 ([int64]($INFRA_VOL_BYTES*3)) 'Fixed' $true)
    )
    capacityWaterfall = $wf3mt
    healthChecks  = (MakeHealthChecks -reserveOK $true)
    metadata      = [ordered]@{ tenantId=$IIC_TENANT; subscriptionId=$IIC_SUB; resourceGroup=$IIC_RG }
    generatedAt   = (Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')
    generatedBy   = 'New-S2DFixtures.ps1'
})

# ─── FIXTURE 3: 4node-3way-mirror ─────────────────────────────────────────────
# 4 nodes · 4× 7.68 TB NVMe per node · three-way mirror · one thin-provisioned volume
Write-Host "[3/8] 4node-3way-mirror" -ForegroundColor DarkGray
$disk4n  = [int64]7680000000000   # 7.68 TB NVMe
$nodes4n = @('azl-iic-4n3-n01', 'azl-iic-4n3-n02', 'azl-iic-4n3-n03', 'azl-iic-4n3-n04')
$disks4n = foreach ($n in $nodes4n) {
    foreach ($d in 1..4) { MakeDisk $n $d 'Capacity' 'NVMe' $disk4n 'SAMSUNG PM9A3 7.68TB' 'GXA7602Q' 'Samsung' 15 }
}
$raw4n3   = [int64]($disk4n * 16)
$thin4n_provBytes = [int64]($disk4n * 6)  # thin vol: 6 drive-sizes provisioned...
$alloc4n3 = [int64]($disk4n * 9)          # pool allocated (thin writes = 3 TB equivalent)
$wf4n3    = MakeWaterfall $raw4n3 4 $disk4n $alloc4n3 33.3
$thinAllocBytes = [int64]($disk4n * 2)   # only 2 drives worth actually written

WritFixture '4node-3way-mirror' ([ordered]@{
    scenario      = '4node-3way-mirror'
    description   = '4-node, 4x 7.68 TB NVMe, three-way mirror, one thin-provisioned volume with moderate writes'
    company       = $IIC_COMPANY
    clusterName   = 'azlocal-iic-4n3-01'
    nodeCount     = 4
    nodes         = $nodes4n
    diskConfig    = [ordered]@{ cacheDisksPerNode=0; capacityDisksPerNode=4; diskModel='SAMSUNG PM9A3 7.68TB'; diskSizeTB=7.68; cacheTierMode='None -- all-NVMe software storage bus' }
    physicalDisks = @($disks4n)
    storagePool   = [ordered]@{ name="S2D on azlocal-iic-4n3-01"; totalSizeBytes=[long]($raw4n3*(1-$POOL_OVERHEAD)); allocatedSizeBytes=$alloc4n3; freeSizeBytes=([long]($raw4n3*(1-$POOL_OVERHEAD))-$alloc4n3) }
    volumes       = @(
        (MakeVolume 'VMs-Fixed'  'Mirror' 3 ([int64]($disk4n*6)) 'Fixed')
        (MakeVolume 'Archive-Thin' 'Mirror' 3 ([int64]($disk4n*3)) 'Thin' $false $thinAllocBytes)
        (MakeVolume 'ClusterStorage_InfraVolume' 'Mirror' 3 ([int64]($INFRA_VOL_BYTES*3)) 'Fixed' $true)
    )
    capacityWaterfall = $wf4n3
    healthChecks  = (MakeHealthChecks -reserveOK $true)
    metadata      = [ordered]@{ tenantId=$IIC_TENANT; subscriptionId=$IIC_SUB; resourceGroup=$IIC_RG }
    generatedAt   = (Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')
    generatedBy   = 'New-S2DFixtures.ps1'
})

# ─── FIXTURE 4: 4node-mixed-resiliency ────────────────────────────────────────
# 4 nodes · 4× 7.68 TB NVMe · VMs=three-way mirror · Archive=dual parity · blended efficiency
Write-Host "[4/8] 4node-mixed-resiliency" -ForegroundColor DarkGray
$disk4mr  = [int64]7680000000000
$nodes4mr = @('azl-iic-4mr-n01', 'azl-iic-4mr-n02', 'azl-iic-4mr-n03', 'azl-iic-4mr-n04')
$disks4mr = foreach ($n in $nodes4mr) {
    foreach ($d in 1..4) { MakeDisk $n $d 'Capacity' 'NVMe' $disk4mr 'SAMSUNG PM9A3 7.68TB' 'GXA7602Q' 'Samsung' 18 }
}
$raw4mr    = [int64]($disk4mr * 16)
$volVMsFP  = [int64]($disk4mr * 6)   # 3-way: 2 TiB usable per drive × 6 drives
$volArcFP  = [int64]($disk4mr * 4)   # dual parity (4-node): 50% eff → 2 drives usable
$alloc4mr  = $volVMsFP + $volArcFP + [int64]($INFRA_VOL_BYTES * 3)
# Blended: (33.3% × 6 + 50% × 4) / 10 ≈ 39.98% ≈ 40%
$blend4mr  = [Math]::Round((33.3 * 6 + 50.0 * 4) / 10, 1)
$wf4mr     = MakeWaterfall $raw4mr 4 $disk4mr $alloc4mr $blend4mr

WritFixture '4node-mixed-resiliency' ([ordered]@{
    scenario      = '4node-mixed-resiliency'
    description   = '4-node, VMs volume uses three-way mirror, Archive uses dual parity; used for blended waterfall testing'
    company       = $IIC_COMPANY
    clusterName   = 'azlocal-iic-4mr-01'
    nodeCount     = 4
    nodes         = $nodes4mr
    diskConfig    = [ordered]@{ cacheDisksPerNode=0; capacityDisksPerNode=4; diskModel='SAMSUNG PM9A3 7.68TB'; diskSizeTB=7.68 }
    physicalDisks = @($disks4mr)
    storagePool   = [ordered]@{ name="S2D on azlocal-iic-4mr-01"; totalSizeBytes=[long]($raw4mr*(1-$POOL_OVERHEAD)); allocatedSizeBytes=$alloc4mr; freeSizeBytes=([long]($raw4mr*(1-$POOL_OVERHEAD))-$alloc4mr) }
    volumes       = @(
        (MakeVolume 'VMs'     'Mirror'  3 $volVMsFP 'Fixed')
        (MakeVolume 'Archive' 'Parity'  2 $volArcFP 'Fixed')
        (MakeVolume 'ClusterStorage_InfraVolume' 'Mirror' 3 ([int64]($INFRA_VOL_BYTES*3)) 'Fixed' $true)
    )
    blendedResiliencyEfficiency = $blend4mr
    capacityWaterfall           = $wf4mr
    healthChecks                = (MakeHealthChecks -reserveOK $true)
    metadata      = [ordered]@{ tenantId=$IIC_TENANT; subscriptionId=$IIC_SUB; resourceGroup=$IIC_RG }
    generatedAt   = (Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')
    generatedBy   = 'New-S2DFixtures.ps1'
})

# ─── FIXTURE 5: 16node-enterprise ─────────────────────────────────────────────
# 16 nodes · 4× 15.36 TB NVMe per node · three-way mirror · reserve capped at 4 drives
Write-Host "[5/8] 16node-enterprise" -ForegroundColor DarkGray
$disk16n  = [int64]15360000000000  # 15.36 TB NVMe
$nodes16n = 1..16 | ForEach-Object { 'azl-iic-16e-n{0:D2}' -f $_ }
$disks16n = foreach ($n in $nodes16n) {
    foreach ($d in 1..4) { MakeDisk $n $d 'Capacity' 'NVMe' $disk16n 'MICRON 9300 MAX 15.36TB' '5005' 'Micron' 10 }
}
$raw16n   = [int64]($disk16n * 64)
$alloc16n = [int64]($raw16n * 0.42)   # moderate allocation
$wf16n    = MakeWaterfall $raw16n 16 $disk16n $alloc16n 33.3

WritFixture '16node-enterprise' ([ordered]@{
    scenario      = '16node-enterprise'
    description   = '16-node enterprise cluster, 4x 15.36 TB NVMe per node, three-way mirror, reserve capped at min(16,4)=4 drives'
    company       = $IIC_COMPANY
    clusterName   = 'azlocal-iic-16e-01'
    nodeCount     = 16
    nodes         = @($nodes16n)
    diskConfig    = [ordered]@{ cacheDisksPerNode=0; capacityDisksPerNode=4; diskModel='MICRON 9300 MAX 15.36TB'; diskSizeTB=15.36 }
    physicalDisks = @($disks16n)
    storagePool   = [ordered]@{ name="S2D on azlocal-iic-16e-01"; totalSizeBytes=[long]($raw16n*(1-$POOL_OVERHEAD)); allocatedSizeBytes=$alloc16n; freeSizeBytes=([long]($raw16n*(1-$POOL_OVERHEAD))-$alloc16n) }
    capacityWaterfall = $wf16n
    healthChecks  = (MakeHealthChecks -reserveOK $true)
    metadata      = [ordered]@{ tenantId=$IIC_TENANT; subscriptionId=$IIC_SUB; resourceGroup=$IIC_RG }
    generatedAt   = (Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')
    generatedBy   = 'New-S2DFixtures.ps1'
})

# ─── FIXTURE 6: 2node-overcommitted ───────────────────────────────────────────
# 2 nodes · 4× 960 GB SSD per node · thin volumes summing more than usable → WARN
Write-Host "[6/8] 2node-overcommitted" -ForegroundColor DarkGray
$disk2oc  = [int64]960000000000   # 960 GB SSD
$nodes2oc = @('azl-iic-2oc-n01', 'azl-iic-2oc-n02')
$disks2oc = foreach ($n in $nodes2oc) {
    foreach ($d in 1..4) { MakeDisk $n $d 'Capacity' 'SSD' $disk2oc 'SAMSUNG SM863A 960GB' 'MAV21K3Q' 'Samsung' 45 }
}
$raw2oc      = [int64]($disk2oc * 8)
$poolTotal2oc = [long]($raw2oc * (1 - $POOL_OVERHEAD))
$reserve2oc  = [int64]($disk2oc * 2)       # 2-node reserve = 2 drives
# Pool has only ~5% free (critical reserve), and thin provisioned > available
$alloc2oc    = [int64]($poolTotal2oc * 0.95)
$free2oc     = $poolTotal2oc - $alloc2oc
# Thin volumes: provisioned sum exceeds available after reserve
$thinVol1FP  = [int64]($raw2oc * 0.4)
$thinVol2FP  = [int64]($raw2oc * 0.5)   # combined provisions > available
$wf2oc       = [ordered]@{
    Raw         = [ordered]@{ Bytes=$raw2oc; TiB=(BytesToTiB $raw2oc); TB=(BytesToTB $raw2oc) }
    PoolTotal   = [ordered]@{ Bytes=$poolTotal2oc; TiB=(BytesToTiB $poolTotal2oc) }
    Reserve     = [ordered]@{ RecommendedBytes=$reserve2oc; RecommendedTiB=(BytesToTiB $reserve2oc); ActualFreeBytes=$free2oc; ActualFreeTiB=(BytesToTiB $free2oc); Status='Critical' }
    Usable      = [ordered]@{ Bytes=[long](($poolTotal2oc - $reserve2oc - $INFRA_VOL_BYTES) / 2); TiB=(BytesToTiB ([long](($poolTotal2oc - $reserve2oc - $INFRA_VOL_BYTES) / 2))) }
    Overcommit  = [ordered]@{
        TotalProvisionedBytes = ($thinVol1FP / 2 + $thinVol2FP / 2)
        TotalProvisionedTiB   = (BytesToTiB ([int64]($thinVol1FP / 2 + $thinVol2FP / 2)))
        OvercommitRatio       = [Math]::Round(($thinVol1FP/2 + $thinVol2FP/2) / [long](($poolTotal2oc - $reserve2oc - $INFRA_VOL_BYTES) / 2), 2)
    }
}

WritFixture '2node-overcommitted' ([ordered]@{
    scenario      = '2node-overcommitted'
    description   = '2-node, thin-provisioned volumes sum exceeds available capacity; triggers ThinOvercommit Warning and ReserveAdequacy Critical'
    company       = $IIC_COMPANY
    clusterName   = 'azlocal-iic-2oc-01'
    nodeCount     = 2
    nodes         = $nodes2oc
    diskConfig    = [ordered]@{ cacheDisksPerNode=0; capacityDisksPerNode=4; diskModel='SAMSUNG SM863A 960GB'; diskSizeTB=0.96 }
    physicalDisks = @($disks2oc)
    storagePool   = [ordered]@{ name="S2D on azlocal-iic-2oc-01"; totalSizeBytes=$poolTotal2oc; allocatedSizeBytes=$alloc2oc; freeSizeBytes=$free2oc }
    volumes       = @(
        (MakeVolume 'VMs'     'Mirror' 2 $thinVol1FP  'Thin' $false ([int64]($thinVol1FP * 0.6)))
        (MakeVolume 'Archive' 'Mirror' 2 $thinVol2FP  'Thin' $false ([int64]($thinVol2FP * 0.4)))
        (MakeVolume 'ClusterStorage_InfraVolume' 'Mirror' 2 ([int64]($INFRA_VOL_BYTES*2)) 'Fixed' $true)
    )
    capacityWaterfall = $wf2oc
    healthChecks  = (MakeHealthChecks -reserveOK $false -overcommit $true)
    metadata      = [ordered]@{ tenantId=$IIC_TENANT; subscriptionId=$IIC_SUB; resourceGroup=$IIC_RG }
    generatedAt   = (Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')
    generatedBy   = 'New-S2DFixtures.ps1'
})

# ─── FIXTURE 7: 3node-insufficient-reserve ────────────────────────────────────
# 3 nodes · 4× 3.84 TB NVMe per node · pool nearly fully allocated → reserve CRITICAL
Write-Host "[7/8] 3node-insufficient-reserve" -ForegroundColor DarkGray
$disk3ir  = [int64]3840000000000
$nodes3ir = @('azl-iic-3ir-n01', 'azl-iic-3ir-n02', 'azl-iic-3ir-n03')
$disks3ir = foreach ($n in $nodes3ir) {
    foreach ($d in 1..4) { MakeDisk $n $d 'Capacity' 'NVMe' $disk3ir 'INTEL SSDPE2KX040T8' 'VCV10162' 'Intel' 22 }
}
$raw3ir      = [int64]($disk3ir * 12)
$poolTotal3ir = [long]($raw3ir * (1 - $POOL_OVERHEAD))
$reserveRec3ir = [int64]($disk3ir * 3)   # 3 nodes × largest disk
# Pool is 97% allocated — almost nothing free
$alloc3ir    = [long]($poolTotal3ir * 0.97)
$free3ir     = $poolTotal3ir - $alloc3ir  # only ~3% free — far below 50% of reserve rec
$wf3ir       = [ordered]@{
    Raw         = [ordered]@{ Bytes=$raw3ir; TiB=(BytesToTiB $raw3ir); TB=(BytesToTB $raw3ir) }
    PoolTotal   = [ordered]@{ Bytes=$poolTotal3ir; TiB=(BytesToTiB $poolTotal3ir) }
    Reserve     = [ordered]@{
        RecommendedBytes = $reserveRec3ir
        RecommendedTiB   = (BytesToTiB $reserveRec3ir)
        ActualFreeBytes  = $free3ir
        ActualFreeTiB    = (BytesToTiB $free3ir)
        DeficitBytes     = $reserveRec3ir - $free3ir
        DeficitTiB       = (BytesToTiB ($reserveRec3ir - $free3ir))
        Status           = 'Critical'
    }
    Usable = [ordered]@{ Bytes=[long](($poolTotal3ir - $reserveRec3ir - $INFRA_VOL_BYTES)/3); TiB=(BytesToTiB ([long](($poolTotal3ir - $reserveRec3ir - $INFRA_VOL_BYTES)/3))) }
}

WritFixture '3node-insufficient-reserve' ([ordered]@{
    scenario      = '3node-insufficient-reserve'
    description   = '3-node, 4x 3.84 TB NVMe per node, pool 97% allocated, unallocated free space far below reserve recommendation; triggers ReserveAdequacy Critical'
    company       = $IIC_COMPANY
    clusterName   = 'azlocal-iic-3ir-01'
    nodeCount     = 3
    nodes         = $nodes3ir
    diskConfig    = [ordered]@{ cacheDisksPerNode=0; capacityDisksPerNode=4; diskModel='INTEL SSDPE2KX040T8'; diskSizeTB=3.84 }
    physicalDisks = @($disks3ir)
    storagePool   = [ordered]@{ name="S2D on azlocal-iic-3ir-01"; totalSizeBytes=$poolTotal3ir; allocatedSizeBytes=$alloc3ir; freeSizeBytes=$free3ir }
    reserveRecommended = [ordered]@{ bytes=$reserveRec3ir; TiB=(BytesToTiB $reserveRec3ir) }
    reserveActual      = [ordered]@{ bytes=$free3ir;       TiB=(BytesToTiB $free3ir) }
    capacityWaterfall  = $wf3ir
    healthChecks  = (MakeHealthChecks -reserveOK $false)
    metadata      = [ordered]@{ tenantId=$IIC_TENANT; subscriptionId=$IIC_SUB; resourceGroup=$IIC_RG }
    generatedAt   = (Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')
    generatedBy   = 'New-S2DFixtures.ps1'
})

# ─── FIXTURE 8: single-node ───────────────────────────────────────────────────
# 1 node · 4× 1.92 TB NVMe · nested two-way mirror (~25% efficiency)
Write-Host "[8/8] single-node" -ForegroundColor DarkGray
$disk1n  = [int64]1920000000000   # 1.92 TB NVMe
$nodes1n = @('azl-iic-1n-n01')
$disks1n = foreach ($d in 1..4) { MakeDisk 'azl-iic-1n-n01' $d 'Capacity' 'NVMe' $disk1n 'INTEL SSDPE2KX020T8' 'VCV10162' 'Intel' 8 }
$raw1n   = [int64]($disk1n * 4)
$alloc1n = [int64]($raw1n * 0.50)
$wf1n    = [ordered]@{
    Raw       = [ordered]@{ Bytes=$raw1n; TiB=(BytesToTiB $raw1n); TB=(BytesToTB $raw1n) }
    PoolTotal = [ordered]@{ Bytes=[long]($raw1n*(1-$POOL_OVERHEAD)); TiB=(BytesToTiB ([long]($raw1n*(1-$POOL_OVERHEAD)))) }
    Reserve   = [ordered]@{ RecommendedBytes=$disk1n; RecommendedTiB=(BytesToTiB $disk1n); ActualFreeBytes=([long]($raw1n*(1-$POOL_OVERHEAD))-$alloc1n); Status='Adequate' }
    Resiliency = [ordered]@{ Type='Nested Two-Way Mirror'; EfficiencyPercent=25.0; Notes='Single-node: local mirror only, ~25% usable efficiency' }
    Usable    = [ordered]@{ Bytes=[long](($raw1n*(1-$POOL_OVERHEAD)-$disk1n-$INFRA_VOL_BYTES)*0.25); TiB=(BytesToTiB ([long](($raw1n*(1-$POOL_OVERHEAD)-$disk1n-$INFRA_VOL_BYTES)*0.25))) }
}

WritFixture 'single-node' ([ordered]@{
    scenario      = 'single-node'
    description   = '1-node cluster, 4x 1.92 TB NVMe, nested two-way mirror (~25% efficiency)'
    company       = $IIC_COMPANY
    clusterName   = 'azlocal-iic-1n-01'
    nodeCount     = 1
    nodes         = $nodes1n
    diskConfig    = [ordered]@{ cacheDisksPerNode=0; capacityDisksPerNode=4; diskModel='INTEL SSDPE2KX020T8'; diskSizeTB=1.92; resiliency='Nested Two-Way Mirror (local)'; efficiencyPct=25 }
    physicalDisks = @($disks1n)
    storagePool   = [ordered]@{ name="S2D on azlocal-iic-1n-01"; totalSizeBytes=[long]($raw1n*(1-$POOL_OVERHEAD)); allocatedSizeBytes=$alloc1n; freeSizeBytes=([long]($raw1n*(1-$POOL_OVERHEAD))-$alloc1n) }
    capacityWaterfall = $wf1n
    healthChecks  = (MakeHealthChecks -reserveOK $true)
    metadata      = [ordered]@{ tenantId=$IIC_TENANT; subscriptionId=$IIC_SUB; resourceGroup=$IIC_RG }
    generatedAt   = (Get-Date -Format 'yyyy-MM-ddTHH:mm:ssZ')
    generatedBy   = 'New-S2DFixtures.ps1'
})

Write-Host "`n✅ All 8 fixtures generated in $fixturesDir" -ForegroundColor Green