diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt
index 3666150db0..4559c9503f 100644
--- a/.github/actions/spelling/allow.txt
+++ b/.github/actions/spelling/allow.txt
@@ -180,6 +180,7 @@ Minimatch
Moq
motw
mrm
+msb
msdata
MSHCTX
MSHLFLAGS
@@ -197,6 +198,7 @@ ncacn
Nelon
netstandard
newid
+NNN
NOCASE
NOCLOSEPROCESS
nodiscard
diff --git a/.github/instructions/manifest-schema-versioning.instructions.md b/.github/instructions/manifest-schema-versioning.instructions.md
new file mode 100644
index 0000000000..e89802c57c
--- /dev/null
+++ b/.github/instructions/manifest-schema-versioning.instructions.md
@@ -0,0 +1,79 @@
+---
+applyTo: "schemas/JSON/manifests/**,src/ManifestSchema/**"
+---
+
+# Manifest Schema Versioning
+
+The `schemas/JSON/manifests/` directory holds JSON schemas for WinGet package manifests.
+`latest/` is the in-development schema; each `v{X.Y.0}/` directory is a frozen snapshot.
+The binary version is defined in `src/binver/binver/version.h` as `{VERSION_MAJOR}.{VERSION_MINOR}.0`.
+See `schemas/JSON/manifests/README.md` for full documentation.
+
+## Script
+
+`schemas/JSON/manifests/Checkpoint-LatestManifestSchema.ps1` automates most steps in both workflows.
+
+```powershell
+# Freeze only (no version bump, C++ changes are manual).
+.\schemas\JSON\manifests\Checkpoint-LatestManifestSchema.ps1
+
+# Freeze current latest/, then advance schema to match the binary version (automates C++ source edits).
+.\schemas\JSON\manifests\Checkpoint-LatestManifestSchema.ps1 -BumpVersion
+
+# Dry run.
+.\schemas\JSON\manifests\Checkpoint-LatestManifestSchema.ps1 -BumpVersion -WhatIf
+```
+
+## Workflow A: Starting a new schema version (`-BumpVersion`)
+
+Run when `version.h` has been advanced to a new minor version and the `latest/` schema version
+does not yet match. The script automates steps 1–7 below. Step 8 (test manifests) is always manual.
+
+| Step | File(s) | Change |
+|------|---------|--------|
+| 1 | `schemas/JSON/manifests/v{OLD}/` | **Create** frozen copy of current `latest/` (only if the folder doesn't exist). |
+| 2 | `schemas/JSON/manifests/latest/*.json` | **Replace** every occurrence of the old version string with the new one (`$id` and `description` fields). |
+| 3 | `src/AppInstallerCommonCore/Public/winget/ManifestCommon.h` | **Add** `constexpr std::string_view s_ManifestVersionV{MAJOR}_{MINOR} = "{VERSION}"sv;` before the "Any new manifest version" comment. |
+| 4 | `src/ManifestSchema/ManifestSchema.h` | **Add** five `IDX_MANIFEST_SCHEMA_V{MAJOR}_{MINOR}_*` constants (Singleton/Version/Installer/DefaultLocale/Locale) at the next available IDs (must stay below 300). |
+| 5 | `src/ManifestSchema/ManifestSchema.rc` | **Append** five new resource entries pointing to `latest/`. |
+| 6 | `src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp` | **Prepend** a new `if (manifestVersion >= ManifestVer{ s_ManifestVersionV{MAJOR}_{MINOR} })` block; old top block becomes `else if`. |
+| 7 | `src/WinGetUtilInterop/Manifest/ManifestVersion.cs` | **Add** `public const string ManifestVersionV{MAJOR}_{MINOR} = "{VERSION}";` |
+
+## Workflow B: Freezing `latest/` at a release
+
+Run when a WinGet release approaches and the in-flight schema must be locked in.
+The script (without `-BumpVersion`) automates steps 1–4 below. Steps 5–8 are only needed if
+the C++ infrastructure was not set up in a prior PR.
+
+| Step | File(s) | Change |
+|------|---------|--------|
+| 1 | `schemas/JSON/manifests/v{VERSION}/` | **Create** — copy each `latest/manifest.*.latest.json` to `manifest.*.{VERSION}.json`. |
+| 2 | `src/ManifestSchema/ManifestSchema.rc` | **Update** five resource entries from `latest/` paths to `v{VERSION}/` paths. |
+| 3 | `src/ManifestSchema/ManifestSchema.vcxitems` | **Add** five `` entries. |
+| 4 | `src/ManifestSchema/ManifestSchema.vcxitems.filters` | **Add** `` with a new GUID, and five `` items with `schema\v{VERSION}`. |
+| 5 | `src/AppInstallerCommonCore/Public/winget/ManifestCommon.h` | **Add** version constant (if not already present). |
+| 6 | `src/ManifestSchema/ManifestSchema.h` | **Add** five IDX constants (if not already present). |
+| 7 | `src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp` | **Prepend** new if-block (if not already present). |
+| 8 | `src/WinGetUtilInterop/Manifest/ManifestVersion.cs` | **Add** version constant (if not already present). |
+
+## ManifestSchemaValidation.cpp pattern
+
+```cpp
+if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_N })
+{
+ resourceMap = {
+ { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_N_SINGLETON },
+ { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_N_VERSION },
+ { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_N_INSTALLER },
+ { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_N_DEFAULTLOCALE },
+ { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_N_LOCALE },
+ };
+}
+else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_ })
+```
+
+## Key invariants
+
+- `latest/` `` entries in `ManifestSchema.vcxitems` are **never removed**; versioned entries are added alongside them.
+- `ManifestSchema.rc` entries for the **current** version always point to `latest/`; entries for all **prior** versions point to their `v{VERSION}/` directories.
+- Resource IDs in `ManifestSchema.h` are sequential groups of 5, must stay below 300.
diff --git a/schemas/JSON/manifests/Checkpoint-LatestManifestSchema.ps1 b/schemas/JSON/manifests/Checkpoint-LatestManifestSchema.ps1
new file mode 100644
index 0000000000..8f630f02ed
--- /dev/null
+++ b/schemas/JSON/manifests/Checkpoint-LatestManifestSchema.ps1
@@ -0,0 +1,498 @@
+<#
+.SYNOPSIS
+ Branches the 'latest' manifest schemas and/or bumps the schema version to match the binary.
+
+.DESCRIPTION
+ Without -BumpVersion:
+ Freezes the current 'latest/' schemas into a versioned folder (e.g. v1.28.0/), and
+ updates ManifestSchema.rc, ManifestSchema.vcxitems, and ManifestSchema.vcxitems.filters
+ to reference the now-versioned files. Use this when a WinGet release is approaching and
+ the in-flight schema needs to be locked in.
+
+ With -BumpVersion:
+ Reads the target version from src/binver/binver/version.h (MAJOR.MINOR.0) and compares
+ it against the version embedded in the 'latest/' schema $id fields. When they differ:
+ 1. Branches the current 'latest/' schema if a versioned folder does not yet exist.
+ 2. Updates every $id and description string in the 'latest/' JSON files to the new version.
+ 3. Adds the new version constant to ManifestCommon.h.
+ 4. Adds five new IDX_MANIFEST_SCHEMA_* constants to ManifestSchema.h.
+ 5. Appends new resource entries (pointing to 'latest/') to ManifestSchema.rc.
+ 6. Prepends a new version block to the if-chain in ManifestSchemaValidation.cpp.
+ 7. Adds the new version constant to ManifestVersion.cs.
+ When they already match, reports that no bump is needed and exits.
+
+ C++ test manifests (ManifestV*-Singleton.yaml etc.), the test project files, and
+ YamlManifest.cpp always require manual updates -- see README.md.
+
+.PARAMETER BumpVersion
+ Also advance the 'latest/' schema version to match src/binver/binver/version.h and
+ update all associated source files.
+
+.PARAMETER RepoRoot
+ Path to the repository root. Defaults to three directories above this script, which is
+ correct when the script lives at schemas/JSON/manifests/Checkpoint-LatestManifestSchema.ps1.
+
+.EXAMPLE
+ # Freeze the in-flight schema at its current version number.
+ .\schemas\JSON\manifests\Checkpoint-LatestManifestSchema.ps1
+
+.EXAMPLE
+ # Freeze then advance the schema version to match the binary.
+ .\schemas\JSON\manifests\Checkpoint-LatestManifestSchema.ps1 -BumpVersion
+
+.EXAMPLE
+ .\schemas\JSON\manifests\Checkpoint-LatestManifestSchema.ps1 -BumpVersion -WhatIf
+#>
+[CmdletBinding(SupportsShouldProcess)]
+param(
+ [switch] $BumpVersion,
+ [string] $RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..\..') -ErrorAction Stop).Path
+)
+
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+
+# ---------------------------------------------------------------------------
+# Helpers
+# ---------------------------------------------------------------------------
+
+function Save-XmlFile {
+ param([xml]$Xml, [string]$Path)
+ $settings = [System.Xml.XmlWriterSettings]@{
+ Indent = $true
+ IndentChars = ' '
+ Encoding = New-Object System.Text.UTF8Encoding $true
+ }
+ $writer = [System.Xml.XmlWriter]::Create($Path, $settings)
+ try { $Xml.Save($writer) }
+ finally { $writer.Close() }
+}
+
+function Save-TextFile {
+ param([string]$Content, [string]$Path)
+ $Content | Out-File -FilePath $Path -Encoding utf8 -NoNewline
+}
+
+# Derive the C++ identifier suffix from a version string: "1.29.0" -> "V1_29"
+function Get-CppSuffix {
+ param([string]$Version)
+ $parts = $Version -split '\.'
+ return "V$($parts[0])_$($parts[1])"
+}
+
+# ---------------------------------------------------------------------------
+# Resolve paths
+# ---------------------------------------------------------------------------
+
+$manifestsDir = Join-Path $RepoRoot 'schemas\JSON\manifests'
+$latestDir = Join-Path $manifestsDir 'latest'
+$manifestSchemaDir = Join-Path $RepoRoot 'src\ManifestSchema'
+$rcFile = Join-Path $manifestSchemaDir 'ManifestSchema.rc'
+$vcxitemsFile = Join-Path $manifestSchemaDir 'ManifestSchema.vcxitems'
+$filtersFile = Join-Path $manifestSchemaDir 'ManifestSchema.vcxitems.filters'
+$manifestCommonH = Join-Path $RepoRoot 'src\AppInstallerCommonCore\Public\winget\ManifestCommon.h'
+$schemaValidationCpp = Join-Path $RepoRoot 'src\AppInstallerCommonCore\Manifest\ManifestSchemaValidation.cpp'
+$manifestSchemaH = Join-Path $RepoRoot 'src\ManifestSchema\ManifestSchema.h'
+$manifestVersionCs = Join-Path $RepoRoot 'src\WinGetUtilInterop\Manifest\ManifestVersion.cs'
+$binverH = Join-Path $RepoRoot 'src\binver\binver\version.h'
+
+foreach ($path in $latestDir, $rcFile, $vcxitemsFile, $filtersFile) {
+ if (-not (Test-Path $path)) {
+ throw "Required path not found: '$path'. Verify -RepoRoot is correct."
+ }
+}
+
+# ---------------------------------------------------------------------------
+# Read schema version from latest/
+# ---------------------------------------------------------------------------
+
+$installerSchema = Join-Path $latestDir 'manifest.installer.latest.json'
+$schemaJson = Get-Content $installerSchema -Raw | ConvertFrom-Json
+$idField = $schemaJson.'$id'
+
+if ($idField -notmatch '(\d+\.\d+\.\d+)\.schema\.json$') {
+ throw "Could not parse version from `$id field: '$idField'"
+}
+$schemaVersion = $Matches[1]
+$versionedDir = Join-Path $manifestsDir "v$schemaVersion"
+
+Write-Host "Current schema version: $schemaVersion"
+
+# ---------------------------------------------------------------------------
+# If -BumpVersion: read binary version and decide what to do
+# ---------------------------------------------------------------------------
+
+$newVersion = $null
+
+if ($BumpVersion) {
+ foreach ($path in $binverH, $manifestCommonH, $schemaValidationCpp, $manifestSchemaH, $manifestVersionCs) {
+ if (-not (Test-Path $path)) {
+ throw "Required path not found for -BumpVersion: '$path'. Verify -RepoRoot is correct."
+ }
+ }
+
+ $binverContent = Get-Content $binverH -Raw
+ if ($binverContent -notmatch '#define VERSION_MAJOR\s+(\d+)') { throw "Could not parse VERSION_MAJOR from version.h" }
+ $binMajor = $Matches[1]
+ if ($binverContent -notmatch '#define VERSION_MINOR\s+(\d+)') { throw "Could not parse VERSION_MINOR from version.h" }
+ $binMinor = $Matches[1]
+ $newVersion = "$binMajor.$binMinor.0"
+
+ Write-Host "Binary version: $newVersion"
+
+ if ($schemaVersion -eq $newVersion) {sXml
+ Write-Host ''
+ Write-Host "Schema version already matches the binary version ($newVersion). No bump needed."
+ exit 0
+ }
+
+ Write-Host "Schema will be bumped: $schemaVersion -> $newVersion"
+}
+
+Write-Host ''
+
+# ---------------------------------------------------------------------------
+# Branch function: freeze current latest/ into v{schemaVersion}/
+# ---------------------------------------------------------------------------
+
+$schemaTypes = @('singleton', 'version', 'installer', 'defaultLocale', 'locale')
+$rcRelBase = '..\..\schemas\JSON\manifests'
+$vcxitemsRelBase = '$(MSBuildThisFileDirectory)..\..\schemas\JSON\manifests'
+$ns = 'http://schemas.microsoft.com/developer/msbuild/2003'
+
+function Invoke-CheckpointLatest {
+ param([string]$Version, [string]$VersionedDir)
+
+ $alreadyBranched = (Test-Path $VersionedDir) -and
+ (@(Get-ChildItem $VersionedDir -Filter "*.json" -ErrorAction SilentlyContinue).Count -ge $schemaTypes.Count)
+
+ if ($alreadyBranched) {
+ Write-Host "=== Branch: v$Version already exists -- skipping schema file copy ==="
+ } else {
+ # Step 1: Schema files
+ Write-Host '=== Checkpoint Step 1: Schema files ==='
+ if ($PSCmdlet.ShouldProcess($VersionedDir, 'Create directory')) {
+ New-Item -ItemType Directory -Path $VersionedDir -Force | Out-Null
+ }
+ foreach ($type in $schemaTypes) {
+ $src = Join-Path $latestDir "manifest.$type.latest.json"
+ $dest = Join-Path $VersionedDir "manifest.$type.$Version.json"
+ if ($PSCmdlet.ShouldProcess("manifest.$type.latest.json -> manifest.$type.$Version.json", 'Copy')) {
+ Copy-Item -Path $src -Destination $dest -Force
+ Write-Host " Copied manifest.$type.$Version.json"
+ }
+ }
+ }
+
+ # Step 2: ManifestSchema.rc -- redirect latest/ entries to versioned paths
+ Write-Host ''
+ Write-Host '=== Checkpoint Step 2: ManifestSchema.rc ==='
+ $rcContent = Get-Content $rcFile -Raw
+ $rcModified = $false
+
+ foreach ($type in $schemaTypes) {
+ $latestPath = ("$rcRelBase\latest\manifest.$type.latest.json").Replace('\', '\\')
+ $versionedPath = ("$rcRelBase\v$Version\manifest.$type.$Version.json").Replace('\', '\\')
+
+ if ($rcContent.Contains($latestPath)) {
+ if ($PSCmdlet.ShouldProcess($rcFile, "Redirect manifest.$type from latest/ to v$Version/")) {
+ $rcContent = $rcContent.Replace($latestPath, $versionedPath)
+ $rcModified = $true
+ Write-Host " Updated path for manifest.$type"
+ }
+ } elseif ($rcContent.Contains($versionedPath)) {
+ Write-Host " manifest.$type already points to v$Version/ - no change needed."
+ } else {
+ Write-Warning " manifest.${type}: neither latest/ nor v${Version}/ path found in RC file. Manual edit required."
+ }
+ }
+
+ if ($rcModified -and $PSCmdlet.ShouldProcess($rcFile, 'Save')) {
+ Save-TextFile $rcContent $rcFile
+ Write-Host " Saved $rcFile"
+ }
+
+ # Step 3: ManifestSchema.vcxitems -- add versioned entries
+ Write-Host ''
+ Write-Host '=== Checkpoint Step 3: ManifestSchema.vcxitems ==='
+
+ [xml]$vcxitemsXml = Get-Content $vcxitemsFile -Raw
+ $namespaceManager = New-Object System.Xml.XmlNamespaceManager($vcxitemsXml.NameTable)
+ $namespaceManager.AddNamespace('msb', $ns)
+
+ $noneGroup = $vcxitemsXml.SelectSingleNode('//msb:ItemGroup[msb:None]', $namespaceManager)
+ if (-not $noneGroup) { throw 'Could not locate containing in ManifestSchema.vcxitems.' }
+
+ $vcxModified = $false
+ foreach ($type in $schemaTypes) {
+ $versionedInclude = "$vcxitemsRelBase\v$Version\manifest.$type.$Version.json"
+ if ($vcxitemsXml.SelectSingleNode("//msb:None[@Include='$versionedInclude']", $namespaceManager)) {
+ Write-Host " manifest.$type v$Version already present - no change needed."
+ continue
+ }
+ if ($PSCmdlet.ShouldProcess($vcxitemsFile, "Add for manifest.$type v$Version")) {
+ $el = $vcxitemsXml.CreateElement('None', $ns)
+ $el.SetAttribute('Include', $versionedInclude)
+ $noneGroup.AppendChild($el) | Out-Null
+ $vcxModified = $true
+ Write-Host " Added for manifest.$type.$Version.json"
+ }
+ }
+
+ if ($vcxModified -and $PSCmdlet.ShouldProcess($vcxitemsFile, 'Save')) {
+ Save-XmlFile $vcxitemsXml $vcxitemsFile
+ Write-Host " Saved $vcxitemsFile"
+ }
+
+ # Step 4: ManifestSchema.vcxitems.filters -- add filter + entries
+ Write-Host ''
+ Write-Host '=== Checkpoint Step 4: ManifestSchema.vcxitems.filters ==='
+
+ [xml]$filtersXml = Get-Content $filtersFile -Raw
+ $filtersNamespaceManager = New-Object System.Xml.XmlNamespaceManager($filtersXml.NameTable)
+ $filtersNamespaceManager.AddNamespace('msb', $ns)
+
+ $filterName = "schema\v$Version"
+ $filterGroup = $filtersXml.SelectSingleNode('//msb:ItemGroup[msb:Filter]', $filtersNamespaceManager)
+ $existingFilter = $filtersXml.SelectSingleNode("//msb:Filter[@Include='$filterName']", $filtersNamespaceManager)
+ $filtersModified = $false
+
+ if (-not $existingFilter) {
+ if ($PSCmdlet.ShouldProcess($filtersFile, "Add for $filterName")) {
+ $newGuid = [System.Guid]::NewGuid().ToString().ToLower()
+ $filterEl = $filtersXml.CreateElement('Filter', $ns)
+ $filterEl.SetAttribute('Include', $filterName)
+ $guidEl = $filtersXml.CreateElement('UniqueIdentifier', $ns)
+ $guidEl.InnerText = "{$newGuid}"
+ $filterEl.AppendChild($guidEl) | Out-Null
+ $filterGroup.AppendChild($filterEl) | Out-Null
+ $filtersModified = $true
+ Write-Host " Added for $filterName (GUID: {$newGuid})"
+ }
+ } else {
+ Write-Host " for $filterName already present."
+ }
+
+ $noneFilterGroup = $filtersXml.SelectSingleNode('//msb:ItemGroup[msb:None]', $filtersNamespaceManager)
+ if (-not $noneFilterGroup) {
+ $noneFilterGroup = $filtersXml.CreateElement('ItemGroup', $ns)
+ $filtersXml.DocumentElement.AppendChild($noneFilterGroup) | Out-Null
+ }
+
+ foreach ($type in $schemaTypes) {
+ $versionedInclude = "$vcxitemsRelBase\v$Version\manifest.$type.$Version.json"
+ if ($filtersXml.SelectSingleNode("//msb:None[@Include='$versionedInclude']", $filtersNamespaceManager)) {
+ Write-Host " manifest.$type v$Version filter entry already present."
+ continue
+ }
+ if ($PSCmdlet.ShouldProcess($filtersFile, "Add filter entry for manifest.$type v$Version")) {
+ $noneEl = $filtersXml.CreateElement('None', $ns)
+ $noneEl.SetAttribute('Include', $versionedInclude)
+ $filterChild = $filtersXml.CreateElement('Filter', $ns)
+ $filterChild.InnerText = $filterName
+ $noneEl.AppendChild($filterChild) | Out-Null
+ $noneFilterGroup.AppendChild($noneEl) | Out-Null
+ $filtersModified = $true
+ Write-Host " Added filter entry for manifest.$type.$Version.json"
+ }
+ }
+
+ if ($filtersModified -and $PSCmdlet.ShouldProcess($filtersFile, 'Save')) {
+ Save-XmlFile $filtersXml $filtersFile
+ Write-Host " Saved $filtersFile"
+ }
+}
+
+# ---------------------------------------------------------------------------
+# Run checkpoint step
+# ---------------------------------------------------------------------------
+
+Invoke-CheckpointLatest -Version $schemaVersion -VersionedDir $versionedDir
+
+# ---------------------------------------------------------------------------
+# If not bumping, print manual-step reminder and exit
+# ---------------------------------------------------------------------------
+
+if (-not $BumpVersion) {
+ Write-Host ''
+ Write-Host '=== Done ==='
+ exit 0
+}
+
+# ---------------------------------------------------------------------------
+# BumpVersion: update latest/ JSON files and all source files
+# ---------------------------------------------------------------------------
+
+$oldVersion = $schemaVersion
+$oldSuffix = Get-CppSuffix $oldVersion
+$newSuffix = Get-CppSuffix $newVersion
+$parts = $newVersion -split '\.'
+$newMajor = $parts[0]
+$newMinor = $parts[1]
+
+# Bump Step 1: Update $id and description in all latest/ JSON files
+Write-Host ''
+Write-Host '=== Bump Step 1: Update latest/ schema files ==='
+foreach ($type in $schemaTypes) {
+ $jsonFile = Join-Path $latestDir "manifest.$type.latest.json"
+ $content = Get-Content $jsonFile -Raw
+ if ($content.Contains($oldVersion)) {
+ if ($PSCmdlet.ShouldProcess($jsonFile, "Replace $oldVersion with $newVersion")) {
+ $updated = $content.Replace($oldVersion, $newVersion)
+ Save-TextFile $updated $jsonFile
+ Write-Host " Updated manifest.$type.latest.json ($oldVersion -> $newVersion)"
+ }
+ } else {
+ Write-Warning " manifest.$type.latest.json: '$oldVersion' not found. Inspect the file manually."
+ }
+}
+
+# Bump Step 2: ManifestCommon.h -- add new version constant
+Write-Host ''
+Write-Host '=== Bump Step 2: ManifestCommon.h ==='
+$mcContent = Get-Content $manifestCommonH -Raw
+
+$newConstant = " // V$newMajor.$newMinor manifest version`n constexpr std::string_view s_ManifestVersion${newSuffix} = `"$newVersion`"sv;`n"
+$insertBefore = ' // Any new manifest version must also be added to'
+
+if ($mcContent.Contains("s_ManifestVersion${newSuffix}")) {
+ Write-Host " s_ManifestVersion${newSuffix} already present - no change needed."
+} elseif (-not $mcContent.Contains($insertBefore)) {
+ Write-Warning " Could not locate insertion point in ManifestCommon.h. Manual edit required."
+} else {
+ if ($PSCmdlet.ShouldProcess($manifestCommonH, "Add s_ManifestVersion${newSuffix}")) {
+ $mcContent = $mcContent.Replace($insertBefore, "$newConstant`n$insertBefore")
+ Save-TextFile $mcContent $manifestCommonH
+ Write-Host " Added s_ManifestVersion${newSuffix} = `"$newVersion`"sv"
+ }
+}
+
+# Bump Step 3: ManifestSchema.h -- add five new IDX constants
+Write-Host ''
+Write-Host '=== Bump Step 3: ManifestSchema.h ==='
+$manifestSchemaHeaderContent = Get-Content $manifestSchemaH -Raw
+
+if ($manifestSchemaHeaderContent.Contains("IDX_MANIFEST_SCHEMA_${newSuffix}_LOCALE")) {
+ Write-Host " IDX_MANIFEST_SCHEMA_${newSuffix}_* already present - no change needed."
+} else {
+ # Find the last defined IDX constant ID number to determine the next available IDs.
+ $allIds = [System.Text.RegularExpressions.Regex]::Matches($manifestSchemaHeaderContent, '#define IDX_MANIFEST_SCHEMA_\w+\s+(\d+)') |
+ ForEach-Object { [int]$_.Groups[1].Value }
+ if (-not $allIds) { throw 'Could not parse existing IDX constants from ManifestSchema.h.' }
+ $nextId = ($allIds | Measure-Object -Maximum).Maximum + 1
+
+ if ($nextId + 4 -ge 300) {
+ Write-Warning " Next available ID would be $nextId; approaching the 300 limit. Manual review required."
+ }
+
+ $newBlock = @"
+#define IDX_MANIFEST_SCHEMA_${newSuffix}_SINGLETON $nextId
+#define IDX_MANIFEST_SCHEMA_${newSuffix}_VERSION $($nextId + 1)
+#define IDX_MANIFEST_SCHEMA_${newSuffix}_INSTALLER $($nextId + 2)
+#define IDX_MANIFEST_SCHEMA_${newSuffix}_DEFAULTLOCALE $($nextId + 3)
+#define IDX_MANIFEST_SCHEMA_${newSuffix}_LOCALE $($nextId + 4)
+
+
+"@
+ # Insert before the "Packages schema starts at 300" comment
+ $insertBefore = '// Packages schema starts at 300'
+ if (-not $manifestSchemaHeaderContent.Contains($insertBefore)) {
+ Write-Warning " Could not locate insertion point in ManifestSchema.h. Manual edit required."
+ } elseif ($PSCmdlet.ShouldProcess($manifestSchemaH, "Add IDX_MANIFEST_SCHEMA_${newSuffix}_* constants")) {
+ $manifestSchemaHeaderContent = $manifestSchemaHeaderContent.Replace($insertBefore, "$newBlock$insertBefore")
+ Save-TextFile $manifestSchemaHeaderContent $manifestSchemaH
+ Write-Host " Added IDX_MANIFEST_SCHEMA_${newSuffix}_* (IDs $nextId-$($nextId + 4))"
+ }
+}
+
+# Bump Step 4: ManifestSchema.rc -- append new resource entries pointing to latest/
+Write-Host ''
+Write-Host '=== Bump Step 4: ManifestSchema.rc ==='
+$rcContent = Get-Content $rcFile -Raw
+
+if ($rcContent.Contains("IDX_MANIFEST_SCHEMA_${newSuffix}_SINGLETON")) {
+ Write-Host " IDX_MANIFEST_SCHEMA_${newSuffix}_* already present in RC file - no change needed."
+} else {
+ $rcAppend = @"
+
+
+IDX_MANIFEST_SCHEMA_${newSuffix}_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.singleton.latest.json"
+IDX_MANIFEST_SCHEMA_${newSuffix}_VERSION MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.version.latest.json"
+IDX_MANIFEST_SCHEMA_${newSuffix}_INSTALLER MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.installer.latest.json"
+IDX_MANIFEST_SCHEMA_${newSuffix}_DEFAULTLOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.defaultLocale.latest.json"
+IDX_MANIFEST_SCHEMA_${newSuffix}_LOCALE MANIFESTSCHEMA_RESOURCE_TYPE "..\\..\\schemas\\JSON\\manifests\\latest\\manifest.locale.latest.json"
+"@
+ if ($PSCmdlet.ShouldProcess($rcFile, "Append IDX_MANIFEST_SCHEMA_${newSuffix}_* entries")) {
+ $rcContent = $rcContent.TrimEnd() + $rcAppend + "`n"
+ Save-TextFile $rcContent $rcFile
+ Write-Host " Appended IDX_MANIFEST_SCHEMA_${newSuffix}_* entries (pointing to latest/)"
+ }
+}
+
+# Bump Step 5: ManifestSchemaValidation.cpp -- prepend new version block at top of if-chain
+Write-Host ''
+Write-Host '=== Bump Step 5: ManifestSchemaValidation.cpp ==='
+$cppContent = Get-Content $schemaValidationCpp -Raw
+
+if ($cppContent.Contains("s_ManifestVersion${newSuffix}")) {
+ Write-Host " s_ManifestVersion${newSuffix} already present - no change needed."
+} else {
+ # The current top of the if-chain begins with "if (manifestVersion >= ManifestVer{ s_ManifestVersionOLD })"
+ $oldIfLine = " if (manifestVersion >= ManifestVer{ s_ManifestVersion${oldSuffix} })"
+ if (-not $cppContent.Contains($oldIfLine)) {
+ Write-Warning " Could not locate '$oldIfLine' in ManifestSchemaValidation.cpp. Manual edit required."
+ } else {
+ $newBlock = @"
+ if (manifestVersion >= ManifestVer{ s_ManifestVersion${newSuffix} })
+ {
+ resourceMap = {
+ { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_${newSuffix}_SINGLETON },
+ { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_${newSuffix}_VERSION },
+ { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_${newSuffix}_INSTALLER },
+ { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_${newSuffix}_DEFAULTLOCALE },
+ { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_${newSuffix}_LOCALE },
+ };
+ }
+ else if (manifestVersion >= ManifestVer{ s_ManifestVersion${oldSuffix} })
+"@
+ if ($PSCmdlet.ShouldProcess($schemaValidationCpp, "Prepend s_ManifestVersion${newSuffix} block")) {
+ $cppContent = $cppContent.Replace($oldIfLine, $newBlock)
+ Save-TextFile $cppContent $schemaValidationCpp
+ Write-Host " Prepended s_ManifestVersion${newSuffix} block; old block demoted to 'else if'"
+ }
+ }
+}
+
+# Bump Step 6: ManifestVersion.cs -- add new version constant
+Write-Host ''
+Write-Host '=== Bump Step 6: ManifestVersion.cs ==='
+$csContent = Get-Content $manifestVersionCs -Raw
+
+if ($csContent.Contains("ManifestVersion${newSuffix}")) {
+ Write-Host " ManifestVersion${newSuffix} already present - no change needed."
+} else {
+ # Insert before the closing #pragma restore line
+ $insertBefore = '#pragma warning restore SA1310'
+ $newConstant = @"
+ ///
+ /// V$newMajor.$newMinor manifest version.
+ ///
+ public const string ManifestVersion${newSuffix} = "$newVersion";
+
+
+"@
+ if (-not $csContent.Contains($insertBefore)) {
+ Write-Warning " Could not locate insertion point in ManifestVersion.cs. Manual edit required."
+ } elseif ($PSCmdlet.ShouldProcess($manifestVersionCs, "Add ManifestVersion${newSuffix}")) {
+ $csContent = $csContent.Replace($insertBefore, "$newConstant$insertBefore")
+ Save-TextFile $csContent $manifestVersionCs
+ Write-Host " Added ManifestVersion${newSuffix} = `"$newVersion`""
+ }
+}
+
+# ---------------------------------------------------------------------------
+# Summary
+# ---------------------------------------------------------------------------
+
+Write-Host ''
+Write-Host '=== Done ==='
diff --git a/schemas/JSON/manifests/README.md b/schemas/JSON/manifests/README.md
new file mode 100644
index 0000000000..2e9a4832be
--- /dev/null
+++ b/schemas/JSON/manifests/README.md
@@ -0,0 +1,214 @@
+# WinGet Manifest Schemas
+
+This directory contains JSON schemas for WinGet package manifests.
+
+## Directory Structure
+
+| Directory | Contents |
+|-----------|----------|
+| `latest/` | The in-development schema. File names use `latest` as the version token. The `$id` field in each file contains the **current version string** (e.g., `1.29.0`). |
+| `v{X.Y.0}/` | Frozen snapshots. File names use the numeric version. Content is identical to `latest/` at the moment of branching. |
+| `preview/` | Legacy v0.1.0 preview schema. |
+
+Each version directory contains five schema files:
+
+| File (using `latest/` as an example) | Manifest type |
+|---------------------------------------|---------------|
+| `manifest.singleton.latest.json` | Single-file manifest |
+| `manifest.version.latest.json` | Multi-file: version manifest |
+| `manifest.installer.latest.json` | Multi-file: installer manifest |
+| `manifest.defaultLocale.latest.json` | Multi-file: default locale manifest |
+| `manifest.locale.latest.json` | Multi-file: locale manifest |
+
+The version string embedded in the `$id` of each `latest/` file is the authoritative schema
+version. The binary version is defined in `src/binver/binver/version.h` as
+`{VERSION_MAJOR}.{VERSION_MINOR}.0`.
+
+---
+
+## Helper script
+
+`Checkpoint-LatestManifestSchema.ps1` (in this directory) automates most of both workflows below.
+Run from any directory; it locates the repo root automatically.
+
+```powershell
+# Freeze the current latest/ schema at its version (no source-file changes).
+.\schemas\JSON\manifests\Checkpoint-LatestManifestSchema.ps1
+
+# Freeze latest/, then advance to match the binary version and update all source files.
+.\schemas\JSON\manifests\Checkpoint-LatestManifestSchema.ps1 -BumpVersion
+
+# Preview changes without writing anything.
+.\schemas\JSON\manifests\Checkpoint-LatestManifestSchema.ps1 -BumpVersion -WhatIf
+```
+
+---
+
+## Workflow A: Advancing to a new schema version
+
+Use this when starting work on a new set of schema changes (e.g. after `version.h` has been
+bumped to a new minor version). The goal is to ensure `latest/` carries the new version number
+before any schema edits are made.
+
+**Run the script:**
+
+```powershell
+.\schemas\JSON\manifests\Checkpoint-LatestManifestSchema.ps1 -BumpVersion
+```
+
+The script performs the following automatically:
+
+1. Reads the target version from `src/binver/binver/version.h` (`MAJOR.MINOR.0`).
+2. Reads the current schema version from `latest/manifest.installer.latest.json` (`$id` field).
+3. If the versions already match, exits with no changes.
+4. If they differ, checkpoints the current schema (see Workflow B steps 1–4 below).
+5. Replaces every occurrence of the old version string in all five `latest/` JSON files (`$id`
+ and `description` fields).
+6. Adds a new version constant to `ManifestCommon.h`.
+7. Adds five new `IDX_MANIFEST_SCHEMA_*` resource-ID constants to `ManifestSchema.h`.
+8. Appends new resource entries (pointing to `latest/`) to `ManifestSchema.rc`.
+9. Prepends a new `if (manifestVersion >= ...)` block at the top of the version-check chain in
+ `ManifestSchemaValidation.cpp`, converting the old top block to an `else if`.
+10. Adds a new version constant to `ManifestVersion.cs`.
+
+---
+
+## Workflow B: Freezing `latest/` into a numbered version
+
+Use this when a WinGet release is approaching and the in-flight schema needs to be locked in —
+i.e., `latest/` is frozen at its current version number and subsequent changes will target the
+next version.
+
+The version to freeze is embedded in the `$id` field of each `latest/` file — for example:
+
+```
+"$id": "https://aka.ms/winget-manifest.installer.1.28.0.schema.json"
+ ^^^^^^
+ This is the version.
+```
+
+**Run the script (automates steps 1–4):**
+
+```powershell
+.\schemas\JSON\manifests\Checkpoint-LatestManifestSchema.ps1
+```
+
+### Step 1 — Create the versioned schema directory
+
+Create `schemas/JSON/manifests/v{VERSION}/` and copy each `latest/` file with the version
+token substituted for `latest`:
+
+| Source (`latest/`) | Destination (`v1.28.0/` example) |
+|---------------------|----------------------------------|
+| `manifest.singleton.latest.json` | `manifest.singleton.1.28.0.json` |
+| `manifest.version.latest.json` | `manifest.version.1.28.0.json` |
+| `manifest.installer.latest.json` | `manifest.installer.1.28.0.json` |
+| `manifest.defaultLocale.latest.json` | `manifest.defaultLocale.1.28.0.json` |
+| `manifest.locale.latest.json` | `manifest.locale.1.28.0.json` |
+
+The JSON content does **not** need editing; the `$id` already contains the version string.
+
+### Step 2 — Update `src/ManifestSchema/ManifestSchema.rc`
+
+Redirect the five resource entries for this version from `latest/` to the versioned paths:
+
+```
+# Before
+IDX_MANIFEST_SCHEMA_V1_28_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\..\schemas\JSON\manifests\latest\manifest.singleton.latest.json"
+
+# After
+IDX_MANIFEST_SCHEMA_V1_28_SINGLETON MANIFESTSCHEMA_RESOURCE_TYPE "..\..\schemas\JSON\manifests\v1.28.0\manifest.singleton.1.28.0.json"
+```
+
+Repeat for all five manifest types.
+
+### Step 3 — Update `src/ManifestSchema/ManifestSchema.vcxitems`
+
+Add five `` entries for the new versioned files inside the existing `` that
+contains the `` elements. The `latest/` entries should remain unchanged.
+
+```xml
+
+
+
+
+
+```
+
+### Step 4 — Update `src/ManifestSchema/ManifestSchema.vcxitems.filters`
+
+Add a `` entry for the new version directory, then add five `` items that
+reference the versioned files and assign them to that filter:
+
+```xml
+
+
+ {new-guid-here}
+
+
+
+
+ schema\v1.28.0
+
+
+```
+
+---
+
+## C++ source changes
+
+These files are updated automatically by `-BumpVersion`. They are listed here for reference
+when doing the work manually or reviewing what the script changes.
+
+### `src/AppInstallerCommonCore/Public/winget/ManifestCommon.h`
+
+Add a version constant before the "Any new manifest version" comment:
+
+```cpp
+// V1.N manifest version
+constexpr std::string_view s_ManifestVersionV1_N = "1.N.0"sv;
+```
+
+### `src/AppInstallerCommonCore/Manifest/ManifestSchemaValidation.cpp`
+
+Prepend a new `if` block at the top of the version-check chain so that manifests at this
+version or higher use the new schema resources. The previous top-level `if` becomes `else if`:
+
+```cpp
+if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_N })
+{
+ resourceMap = {
+ { ManifestTypeEnum::Singleton, IDX_MANIFEST_SCHEMA_V1_N_SINGLETON },
+ { ManifestTypeEnum::Version, IDX_MANIFEST_SCHEMA_V1_N_VERSION },
+ { ManifestTypeEnum::Installer, IDX_MANIFEST_SCHEMA_V1_N_INSTALLER },
+ { ManifestTypeEnum::DefaultLocale, IDX_MANIFEST_SCHEMA_V1_N_DEFAULTLOCALE },
+ { ManifestTypeEnum::Locale, IDX_MANIFEST_SCHEMA_V1_N_LOCALE },
+ };
+}
+else if (manifestVersion >= ManifestVer{ s_ManifestVersionV1_ })
+// ...
+```
+
+### `src/ManifestSchema/ManifestSchema.h`
+
+Add five resource-ID constants, continuing the numeric sequence. IDs must stay below 300
+(per the comment in that file):
+
+```c
+#define IDX_MANIFEST_SCHEMA_V1_N_SINGLETON NNN
+#define IDX_MANIFEST_SCHEMA_V1_N_VERSION NNN+1
+#define IDX_MANIFEST_SCHEMA_V1_N_INSTALLER NNN+2
+#define IDX_MANIFEST_SCHEMA_V1_N_DEFAULTLOCALE NNN+3
+#define IDX_MANIFEST_SCHEMA_V1_N_LOCALE NNN+4
+```
+
+### `src/WinGetUtilInterop/Manifest/ManifestVersion.cs`
+
+Add a version constant:
+
+```csharp
+///
+/// V1.N manifest version.
+///
+public const string ManifestVersionV1_N = "1.N.0";
+```