Support/Package/Schema/Eigenverft.Manifested.Sandbox.Package.DefinitionSchema.ReleaseMerge.ps1

<#
    Eigenverft.Manifested.Sandbox.Package.DefinitionSchema.ReleaseMerge
    Merges shared lifecycle defaults from baseline wire definitions (schemaVersion 1.1) into one effective release (runtime model).
 
    Wire vs effective (read this before changing validators, JSON schema, or lifecycle code):
 
    - On disk and in Assert-PackageDefinitionSchema* / eigenverft-module-package-definition-1.1.schema.json,
      the definition uses legacy shared.* or packageOperations.shared.* plus assigned/removed (optional release overrides):
        shared.discovery / packageOperations.shared.discovery -> effective release: existingInstallDiscovery
        shared.ownershipPolicy / packageOperations.shared.ownershipPolicy -> effective release: existingInstallPolicy
      Release rows may also carry discovery / ownershipPolicy before merge; those are renamed the same way.
 
    - After Resolve-PackageEffectiveRelease, Package subsystem code (Selection, lifecycle, Validation, etc.)
      MUST use assigned / removed (not legacy install / remove on the effective release), and
      existingInstallDiscovery / existingInstallPolicy only. Do not read .discovery or .ownershipPolicy on the merged release object.
 
    - JSON schema "releaseNarrow" lists only wire-shaped release fields; the effective names above are
      runtime-only. If you extend discovery/policy, update wire schema + Wire1_1 validators + this merge in lockstep.
 
    - Definitions may use legacy top-level 'shared' (sharedLifecycle) or 'packageOperations'
      (packageOperations.shared + assigned + removed). Get-PackageDefinitionNormalizedSharedView
      maps the new shape into a normalized view with wire keys install/remove for merge consumption only.
#>


function Get-PackageDefinitionNormalizedSharedView {
    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$Definition
    )

    $hasShared = $Definition.PSObject.Properties['shared'] -and $null -ne $Definition.shared
    $hasPackageOperations = $Definition.PSObject.Properties['packageOperations'] -and $null -ne $Definition.packageOperations

    if ($hasShared -and $hasPackageOperations) {
        throw "Package definition '$($Definition.id)' defines both 'shared' and 'packageOperations'; use only one wire shape."
    }
    if (-not $hasShared -and -not $hasPackageOperations) {
        throw "Package definition '$($Definition.id)' is missing required lifecycle block: provide either 'shared' or 'packageOperations'."
    }

    if ($hasPackageOperations) {
        $po = $Definition.packageOperations
        foreach ($req in @('shared', 'assigned', 'removed')) {
            if (-not $po.PSObject.Properties[$req] -or $null -eq $po.$req) {
                throw "Package definition '$($Definition.id)' packageOperations is missing required property '$req'."
            }
        }
        $innerShared = $po.shared
        return [pscustomobject]@{
            compatibility   = $innerShared.compatibility
            discovery       = $innerShared.discovery
            ownershipPolicy = $innerShared.ownershipPolicy
            validation      = $innerShared.validation
            install         = $po.assigned
            remove          = $po.removed
        }
    }

    return $Definition.shared
}

function Resolve-PackageEffectiveRelease {
<#
.SYNOPSIS
Builds the effective Package release by applying definition shared defaults.
 
.DESCRIPTION
Applies whole-block fallback from the definition shared block to a single release
entry. When a release defines one of the known lifecycle blocks, that block fully
replaces the default block for that key. Wire properties discovery and ownershipPolicy
(v4 JSON names on disk) are copied or renamed to existingInstallDiscovery and
existingInstallPolicy on the effective release for all downstream Package code.
 
.PARAMETER Definition
The Package definition object.
 
.PARAMETER Release
The raw release object from the definition.
 
.EXAMPLE
Resolve-PackageEffectiveRelease -Definition $definition -Release $release
#>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory = $true)]
        [psobject]$Definition,

        [Parameter(Mandatory = $true)]
        [psobject]$Release
    )

    $effectiveRelease = ConvertTo-PackageObject -InputObject $Release

    if ($effectiveRelease.PSObject.Properties['discovery'] -and
        -not $effectiveRelease.PSObject.Properties['existingInstallDiscovery']) {
        $effectiveRelease | Add-Member -MemberType NoteProperty -Name 'existingInstallDiscovery' -Value (ConvertTo-PackageObject -InputObject $effectiveRelease.discovery)
        $null = $effectiveRelease.PSObject.Properties.Remove('discovery')
    }
    if ($effectiveRelease.PSObject.Properties['ownershipPolicy'] -and
        -not $effectiveRelease.PSObject.Properties['existingInstallPolicy']) {
        $effectiveRelease | Add-Member -MemberType NoteProperty -Name 'existingInstallPolicy' -Value (ConvertTo-PackageObject -InputObject $effectiveRelease.ownershipPolicy)
        $null = $effectiveRelease.PSObject.Properties.Remove('ownershipPolicy')
    }

    if ($effectiveRelease.PSObject.Properties['install'] -and
        -not $effectiveRelease.PSObject.Properties['assigned']) {
        $effectiveRelease | Add-Member -MemberType NoteProperty -Name 'assigned' -Value (ConvertTo-PackageObject -InputObject $effectiveRelease.install)
        $null = $effectiveRelease.PSObject.Properties.Remove('install')
    }
    if ($effectiveRelease.PSObject.Properties['remove'] -and
        -not $effectiveRelease.PSObject.Properties['removed']) {
        $effectiveRelease | Add-Member -MemberType NoteProperty -Name 'removed' -Value (ConvertTo-PackageObject -InputObject $effectiveRelease.remove)
        $null = $effectiveRelease.PSObject.Properties.Remove('remove')
    }

    $shared = Get-PackageDefinitionNormalizedSharedView -Definition $Definition
    foreach ($propertyName in @('compatibility', 'assigned', 'validation', 'existingInstallDiscovery', 'existingInstallPolicy')) {
        $sharedValue = $null
        switch ($propertyName) {
            'existingInstallDiscovery' {
                if ($shared.PSObject.Properties['discovery']) {
                    $sharedValue = $shared.discovery
                }
            }
            'existingInstallPolicy' {
                if ($shared.PSObject.Properties['ownershipPolicy']) {
                    $sharedValue = $shared.ownershipPolicy
                }
            }
            'assigned' {
                if ($shared.PSObject.Properties['install'] -and $null -ne $shared.install) {
                    $sharedValue = $shared.install
                }
            }
            default {
                if ($shared.PSObject.Properties[$propertyName]) {
                    $sharedValue = $shared.$propertyName
                }
            }
        }

        if (-not $effectiveRelease.PSObject.Properties[$propertyName] -and $null -ne $sharedValue) {
            $effectiveRelease | Add-Member -MemberType NoteProperty -Name $propertyName -Value (ConvertTo-PackageObject -InputObject $sharedValue)
        }
    }

    if ($shared.PSObject.Properties['remove'] -and $null -ne $shared.remove) {
        if ($effectiveRelease.PSObject.Properties['removed']) {
            $null = $effectiveRelease.PSObject.Properties.Remove('removed')
        }
        $effectiveRelease | Add-Member -MemberType NoteProperty -Name 'removed' -Value (ConvertTo-PackageObject -InputObject $shared.remove)
    }

    return $effectiveRelease
}