TASK 48 slice 2: the app packs as a Microsoft Store MSIX
The manifest the Store route needs, and a real package built from it. Packaging/Package.appxmanifest is a full-trust packagedClassicApp: EntryPoint Windows.FullTrustApplication, uap10:TrustLevel=mediumIL, uap10:RuntimeBehavior=packagedClassicApp, the rescap runFullTrust capability, and webcam + microphone DeviceCapabilities. That combination is what lets the packaged app launch the bundled ffmpeg as a child process and write to the user's profile; an appContainer package would virtualise the recordings folder instead. x64 only, en-US only (policy 10.7). Packaging/Assets/ carries the four tiles the packer requires, generated from the app's own Assets/llamacasty-logo.jpg by scripts/make-store-logos.py - listing art and app art cannot drift. scripts/package-msix.sh publishes Release win-x64 self-contained into artifacts/msix-stage (a RID-less publish would copy every RID's ONNX native), stages the manifest as AppxManifest.xml + Assets, and runs the Windows SDK's makeappx. MSIX_PFX/MSIX_PFX_PASSWORD sign a package for a local test install; the Store signs submissions. artifacts/ is gitignored. First real package: artifacts/LlamaCasty.msix, 159 MB, 509 files, 'Package creation succeeded'. Schema traps found by packing (makeappx reports one error at a time): Square310x310Logo and Wide310x150Logo are NOT uap:VisualElements attributes - they live on uap:DefaultTile. Guard: StorePackagingTests.The_package_manifest_declares_full_trust_camera_and_ microphone - identity/architecture, the full-trust application element, the three capabilities, no appContainer VALUE (not the file text: the manifest's own comment says the word), en-US only, and every referenced logo present. Full suite 438/438, 0 warnings. Cold-start smoke test after the Velopack removal: MainWindow loaded, no exceptions.
This commit is contained in:
@@ -63,4 +63,78 @@ public class StorePackagingTests
|
||||
.Where(path => !path.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}"))
|
||||
.Where(path => !path.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}"));
|
||||
}
|
||||
|
||||
private static readonly XNamespace Foundation =
|
||||
"http://schemas.microsoft.com/appx/manifest/foundation/windows10";
|
||||
private static readonly XNamespace Uap =
|
||||
"http://schemas.microsoft.com/appx/manifest/uap/windows10";
|
||||
private static readonly XNamespace Uap10 =
|
||||
"http://schemas.microsoft.com/appx/manifest/uap/windows10/10";
|
||||
private static readonly XNamespace Rescap =
|
||||
"http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities";
|
||||
|
||||
/// <summary>TASK 48, slice 2 — the MSIX manifest. This is the file that decides whether
|
||||
/// recording, capturing and the bundled ffmpeg still work once the app is packaged:
|
||||
/// a full-trust packagedClassicApp at mediumIL can launch ffmpeg as a child process and
|
||||
/// write to the user's profile; an AppContainer cannot, and recording would silently
|
||||
/// land in a virtualised folder. So the guard pins every property that makes that true,
|
||||
/// plus the x64/en-US decisions and the logo files the packer requires.</summary>
|
||||
[Fact]
|
||||
public void The_package_manifest_declares_full_trust_camera_and_microphone()
|
||||
{
|
||||
var manifestPath = SourceAssert.RepoPath(Path.Combine("Packaging", "Package.appxmanifest"));
|
||||
Assert.True(File.Exists(manifestPath), "Packaging/Package.appxmanifest is missing.");
|
||||
var manifest = XDocument.Load(manifestPath);
|
||||
var package = manifest.Root!;
|
||||
Assert.Equal(Foundation + "Package", package.Name);
|
||||
|
||||
// 1. Identity: x64 only (creator ruling 2026-10-09), and the title the Store expects.
|
||||
var identity = package.Element(Foundation + "Identity")!;
|
||||
Assert.Equal("LlamaCasty", (string?)identity.Attribute("Name"));
|
||||
Assert.Equal("x64", (string?)identity.Attribute("ProcessorArchitecture"));
|
||||
|
||||
// 2. Full trust: packagedClassicApp + mediumIL + the restricted runFullTrust capability.
|
||||
var application = package.Element(Foundation + "Applications")!
|
||||
.Element(Foundation + "Application")!;
|
||||
Assert.Equal("Windows.FullTrustApplication", (string?)application.Attribute("EntryPoint"));
|
||||
Assert.Equal("ytLive.exe", (string?)application.Attribute("Executable"));
|
||||
Assert.Equal("mediumIL", (string?)application.Attribute(Uap10 + "TrustLevel"));
|
||||
Assert.Equal("packagedClassicApp", (string?)application.Attribute(Uap10 + "RuntimeBehavior"));
|
||||
|
||||
var capabilities = package.Element(Foundation + "Capabilities")!;
|
||||
Assert.Contains(capabilities.Elements(Rescap + "Capability"),
|
||||
element => (string?)element.Attribute("Name") == "runFullTrust");
|
||||
var devices = capabilities.Elements(Foundation + "DeviceCapability")
|
||||
.Select(element => (string?)element.Attribute("Name"))
|
||||
.ToArray();
|
||||
Assert.Contains("webcam", devices);
|
||||
Assert.Contains("microphone", devices);
|
||||
|
||||
// 3. ⛔ Never appContainer — that is the change that breaks recording (item 6).
|
||||
// Matched on ATTRIBUTE VALUES, not the file text: the manifest's own comment
|
||||
// explains why it must not be appContainer, and prose is allowed to say so.
|
||||
Assert.DoesNotContain(
|
||||
manifest.Descendants()
|
||||
.SelectMany(element => element.Attributes())
|
||||
.Select(attribute => attribute.Value),
|
||||
value => string.Equals(value, "appContainer", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// 4. English only (policy 10.7): a second language forces the listing to be localised.
|
||||
var languages = package.Element(Foundation + "Resources")!
|
||||
.Elements(Foundation + "Resource")
|
||||
.Select(element => (string?)element.Attribute("Language"))
|
||||
.ToArray();
|
||||
Assert.Equal(new[] { "en-US" }, languages);
|
||||
|
||||
// 5. Every image the manifest references is actually in the package source tree —
|
||||
// makeappx refuses to pack otherwise.
|
||||
var packaging = Path.GetDirectoryName(manifestPath)!;
|
||||
foreach (var logo in manifest.Descendants()
|
||||
.SelectMany(element => element.Attributes())
|
||||
.Where(attribute => attribute.Name.LocalName.EndsWith("Logo", StringComparison.Ordinal))
|
||||
.Select(attribute => attribute.Value.Replace('\\', Path.DirectorySeparatorChar)))
|
||||
{
|
||||
Assert.True(File.Exists(Path.Combine(packaging, logo)), $"manifest references a missing logo: {logo}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user