# 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 β€” VERIFIED 15%, subscriptions included.** Read from the ADA v8.10 PDF (downloaded and text-extracted), Β§6(b): 15% for Apps and their In-App Products *not listed in* 6(b)(iii); 12% is Games-only; 30% is Xbox console / Xbox non-subscription IAP / Windows 8. LlamaCasty is a Windows PC App, so **6(b)(i) 15% governs** β‡’ `$8.50/mo` net, `$340` lifetime net. The agreement's own changelog (v8.0, Oct 26 2017) states it plainly: *"implement the 85/15 revenue share for non-Game subscriptions."* Smaller-indie better rates were **not** found in the standard terms. Microsoft also requires that any IAP products and their pricing be declared in Partner Center. - β˜‘ **Store IAP tier shape β€” βœ… DECIDED 2026-09-27: `$10/mo` subscription + `$400` lifetime, no annual.** Declare **two** products in Partner Center: one auto-renewing monthly subscription, one non-expiring lifetime IAP. β›” **No `.99` prices** (settled). β›” **No annual product** β€” Microsoft does not pro-rate: *"monthly subscriptions and initial (pre-renewal) purchases aren't eligible for a prorated refund"*, so an annual sub cancelled in year 1 forfeits the remainder **and the developer cannot refund it**. Net after the 15% Store Fee (verified, Β§6(b)(i) β€” applies to non-Game subscriptions too): **`$8.50/mo` and `$340`**. Note ADA Β§6(h): we must fulfil the subscription for the whole period, and **raising the price disables auto-renew** β€” so `$10` is effectively locked for the product's life. β›” A discounted subscriberβ†’lifetime upgrade SKU is **not** approved; do not declare one. - ☐ 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 - ☐ **A working YouTube demo account** (10.3.1) β€” the app is useless to a reviewer without one. Must be a real, maintained account able to sign in and start a stream; a dead or credentials-lapsed account is a rejection. β›” **This was accidentally dropped from this list** during the Store-IAP edit β€” it is in `research-store-certification.md` Β§4 and must be done. - ☐ **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.