# TASK 48 โ€” Distribution & packaging: route decision, MSIX, signing > Catalog: [`TASKS.md`](../TASKS.md) โ€” status and requirements live here. > **Research: [`TASKS/research-store-certification.md`](research-store-certification.md)** โ€” the "why". > Carved out of TASK 36 item 6 (release engineering) on 2026-09-27 so distribution has one > owner instead of three scattered mentions. **Status:** ๐Ÿ”ถ In progress โ€” **โœ… ROUTE DECIDED 2026-09-27: Microsoft Store MSIX + Store IAP.** Polar is deleted; every licensing subsystem goes with it. **Goal:** get LlamaCasty in front of a user without a SmartScreen scarewall, ideally without an annual certificate bill, and without breaking recording, capture, or audio. --- ## 0. โœ… THE DECISION โ€” Microsoft Store, MSIX, Store IAP (creator ruling 2026-09-27) **Creator's stated criteria, verbatim: "zero headaches, minimal maintenance (for me) while still providing accountability and a reasonably easy upgrade flow."** **Ruling: distribute as an MSIX package through the Microsoft Store, and take payment through Store IAP.** All four criteria are satisfied by the same mechanism: | Criterion | How MSIX + Store IAP satisfies it | |---|---| | Zero headaches | **$0/yr** โ€” Microsoft holds the signing certificate, the reputation, and the installer. No cert, no HSM, no annual renewal, no SmartScreen ramp | | Minimal maintenance | Microsoft handles **updates**, **payments**, **entitlements**, **refunds**, and **customer support**. Nothing to run | | Accountability | Microsoft reviews every submission โ€” that *is* the accountability layer, and it is a real one | | Easy upgrade flow | Store auto-update. **Velopack, the update URL, and the DO droplet all go** | ### The consequence that makes this cheap: the entire licensing subsystem is deleted Choosing Store IAP over Store+Polar is what makes "minimal maintenance" real. Keeping Polar would have left the creator maintaining a licensing backend, a customer portal, activation limits, and an offline-grace machine โ€” the exact burden the ruling was made to avoid. **Dead code / deleted concepts (TASK 10 is now a teardown, not a build):** - `Services/PolarLicenseService.cs` โ€” HTTP validation, the swallowed network failures, the ignored `expires_at` - `Helpers/PolarLicense.cs` โ€” the record type - `ViewModels/MainViewModel.License.cs` โ€” `PremiumUrl`, the customer portal, the `OfflineGracePeriod = 14 days` subscription-era artifact, renewal/lapse copy - `Controls/OverlayHost.xaml` โ€” the incorrect "Polar unlocks alerts" copy resolves itself - `ytLive.csproj:92` Velopack `PackageReference` + `App.xaml.cs:3,38-46` โ€” see item 3 - The `MONETIZATION.md` provider sections (gitignored โ€” local file) **What replaces it:** `IsPremium` is derived from the **Store entitlement** instead of a remote HTTP call. One bit, locally cached, refreshed by the OS. The offline-grace machine disappears because the OS supplies license state โ€” and with it the whole class of "network flaky โ†’ app thinks I'm expired" bugs. โš ๏ธ **The watermarks posture is unchanged:** free gets everything; the branding flash stays the ONLY paid delta (TASK 36 item 2). Store IAP changes how the bit is *obtained*, never what it *gates*. ### Still to verify (does not block packaging work) - โ˜ **Current Store revenue-share terms.** โš ๏ธ **Do not assume any percentage** โ€” the terms have moved more than once and smaller indie apps sometimes qualify for better rates. **Verify before setting a price.** Microsoft also requires that any IAP products and their pricing be declared in Partner Center. - โ˜ **Store IAP tier shape** โ€” โ›” **no annual tier, and that is now a settled constraint, not a preference:** Microsoft does not pro-rate, and *"monthly subscriptions and initial (pre-renewal) purchases aren't eligible for a prorated refund"*, so a first-year annual subscriber who cancels loses the remaining months in most countries **and the developer cannot refund it**. Declare a **monthly subscription**; a **lifetime** product (~$300, clean number, no `.99`) is the open question and the natural hedge. โ›” **No `.99` prices.** - โ˜ Individual vs Company Partner Center account (10.8.3 / 10.14 โ€” see `research-store-certification.md` ยง11 item 1). **Get this right before enrolling**; a Store+IAP product needing financial info for primary functionality would require Company, but a free product with a paid upgrade tier is the normal consumer shape and is fine on an individual account. --- ## 1. โ›” Bundle ffmpeg instead of downloading it โ€” **do this on EVERY route** **This is item 1 because it is genuinely first** โ€” it fixes a user-facing failure on its own and is a hard certification gate. `Services/Encoder/FfmpegLocator.cs:37-38` pins a GitHub release URL; `LocateAsync` downloads (line 69), extracts (line 73), and the encoder executes the result. On a cold cache the app downloads an unsigned executable from the internet and runs it. - **Store blocker:** policy 10.2.2 forbids dynamic code inclusion (download-and-execute). - **Route-independent bug class:** `FfmpegLocator.cs:30-35` records that the previous daily pin aged out of GitHub retention and **404'd on the first real recording attempt (2026-09-01)**. Bundling kills this permanently and removes the startup network dependency. **Do:** ship `ffmpeg.exe` + `ffprobe.exe` + the `libav*.dll` family inside the package; `FfmpegLocator` resolves PATH โ†’ package-local โ†’ cache, and never downloads. `IFfmpegLocator`'s contract, the `ExtractBinaries` staging discipline, and the existing `FfmpegLocatorTests` cold/cache/PATH coverage all still apply. **Licensing:** the LGPL-shared build was chosen for LGPL ยง6 compliance (dynamic linking = "license text + source offer"). That reasoning is **unaffected**. Confirm `THIRD-PARTY-NOTICES.txt` covers the bundled build. **Record the immutable tag** in `TASKS.md` per the licensing rule, and note the URL is now a provenance record rather than a runtime fetch. --- ## 2. โ˜ `Package.appxmanifest` โ€” full trust, camera, microphone There is currently **no `.manifest` file in the repo at all**, so this is purely additive. - `rescap:Capability Name="runFullTrust"` โ€” medium integrity, not AppContainer - `webcam` and `microphone` capabilities - `uap10:TrustLevel="mediumIL"` and `uap10:RuntimeBehavior="packagedClassicApp"` โ€” the latter is what allows launching package executables as child processes - Declare **English only** (10.7 โ€” otherwise the description must be localized into every declared language) - Block map SHA2-256, `StoreManifest`, under the 25 GB cap **Do NOT use `appContainer`** โ€” see the invariant in item 6. ## 3. โ˜ Remove Velopack โ€” 3 edit sites The Store handles updates, so this is simply **deleted**. Trivially removable; the code was written defensively for exactly this. - `ytLive.csproj:92` โ€” `` - `App.xaml.cs:3` โ€” `using Velopack;` - `App.xaml.cs:38-46` โ€” the sole call site, already try/caught and commented "(non-fatal) โ€ฆ when no URL is set, the check is a no-op" **Retires:** the pending "Velopack update URL" item (`TASKS.md` open items, `task-10-monetization.md:43`) and the self-hosted DigitalOcean droplet. ## 4. โ˜ Windows App Certification Kit Run before submission. Technical compliance is tested by WACK; it is not optional. Known relevant check: the app must start promptly, stay responsive, and shut down gracefully (10.4.2). ## 5. โ˜ Certification notes + the three documentation artifacts **In Partner Center (submission):** - โ˜ **The IARC age-rating questionnaire** (10.11.1) โ€” general audience, 12+ territory - โ˜ **Declare the IAP products and their pricing** in Partner Center (required for Store IAP). โ›” **No `.99` prices** โ€” creator ruling, and incoherent for a no-dark-patterns brand. Confirm the **net** after the revenue share, not the list. - โ˜ Note that the product is **general audience, not directed at under-13s** - โ˜ The 11.12 UGC position is **less load-bearing now that Polar is gone** โ€” the strong part of the original argument was "we render transiently, persist nothing, and provide no user-to-user communication surface," which is *unaffected*. State it in full anyway; the reasoning and the Q1-2026 YouTube enforcement numbers are in `research-store-certification.md` ยง9 - โ˜ The **YouTube age-gate argument** โ€” the operator is 13+ by construction (ยง8) **On `llamachile.shop`:** - โ˜ **Privacy policy** (10.5.1 โ€” mandatory for Win32/Desktop Bridge). Must state: camera/mic access is user-directed and OS-gated; **no retention** of chat or reward payloads; no viewer data collected; **ffmpeg is bundled and nothing is fetched at runtime** - โ˜ **Code of conduct + content guidelines** (11.12 / 11.15) - โ˜ Confirm `THIRD-PARTY-NOTICES.txt` covers the bundled LGPL ffmpeg build **In Partner Center (listing):** - โ˜ Title is exactly **`LlamaCasty`** โ€” 10.1.1 forbids marketing text or extraneous keywords - โ˜ Search terms: max 7, no pricing terms, and **no other product titles** (10.1.3 โ€” cannot list `OBS` or `StreamYard`) - โ˜ Real listing content โ€” 10.1.4 requires an active, substantive presence - โ˜ Review the **Individual vs Company** account question before enrolling ## 6. โ˜ The full-trust recording invariant โ€” **comment lands WITH this work** `ViewModels/MainViewModel.Recording.cs:173-179` (`DefaultRecordFolder()`) writes to `%USERPROFILE%\Downloads` or `SpecialFolder.MyVideos`; `ChooseRecordFolder()` (line 151) uses `Microsoft.Win32.OpenFolderDialog`; the temp-write-then-move lifecycle stays in one directory (line 131-132). **This works only because the package is full trust.** At `mediumIL`, user-profile writes pass through unvirtualized and `OpenFolderDialog` is an ordinary Win32 browser with no capability token. No `broadFileSystemAccess` needed. โš ๏ธ **If anyone flips the manifest to `appContainer`, recording silently breaks** โ€” `Directory.CreateDirectory` and `File.Move` start resolving to a per-package virtualized location and output vanishes. **Add a comment at `DefaultRecordFolder()` in this task, not before.** A comment describing a manifest that does not exist is worse than no comment. **Also verify on a real packaged build** before trusting it with recordings โ€” the docs say full trust passes writes through, but that is worth confirming with an actual package identity rather than an unpackaged run. ## 7. โ˜ Confirm the disqualifiers stay clear after packaging All four are currently clear (verified by grep 2026-09-27) โ€” re-verify once the packaging project exists: - No Windows driver installed (we consume DShow filters, we do not install one) - No per-user Windows service - No elevation / `requireAdministrator` / UAC manifest - No shell extension, no in-process module loading by outside processes, no jump list ## 8. โ˜ Pre-submission gates - โ˜ 0 warnings, full suite green - โ˜ **Vertical-recording path verified end-to-end** (compositor tier is proven by `Render_VerticalTier_Outputs_1080x1920_From_The_Center_Crop`; the *record* path has never been exercised โ€” `Services/Pump/FramePump.cs:645` flags off-size tiers as a known follow-up) - โ˜ **The real-output confirmation** the creator has been doing manually - โ˜ Camera/mic/wasapi capture verified **from the packaged build**, not just unpackaged - โ˜ Clean uninstall verified (10.2.7) - โ˜ Notification-disabled path leaves the app functional (10.9) --- ## Not in this task (deliberately) - **Velopack hardening / obfuscation / assembly splitting** โ€” stays a GA-time decision per TASK 36 item 6's agreed ceiling. Compile flags max at `HARDENED` + `MOCK_REWARDS`, additive-only. - **EULA draft/review and the THIRD-PARTY-NOTICES gate** โ€” stay in TASK 36 item 6. - **Vertical-canvas feature work** โ€” the feature exists; only the *record-path test* above is missing. - **macOS / Avalonia port** โ€” deferred; would be a second app, not a port.