Files
LlamaCasty/TASKS/task-48-distribution-msix.md
T
gramps e7cded6879 docs: pricing ruled — $10/mo + $400 lifetime, and the 15% fee is verified for subscriptions
Reconcile the pending doc edits with the creator's actual ruling, and close the
revenue-share question that was blocking the monthly price.

Verified from the App Developer Agreement v8.10 PDF itself (downloaded and
text-extracted, not a search excerpt). Section 6(b) has only three tiers:
6(b)(i) 15% for Apps and their In-App Products *not listed in* 6(b)(iii);
6(b)(ii) 12% Games-only; 6(b)(iii) 30% for Xbox console apps/games, Xbox
non-subscription IAP, and Windows 8/Phone 8. LlamaCasty is a Windows PC App,
so 6(b)(i) governs BOTH tiers -- there is no subscription surcharge. The
agreement's own changelog (v8.0, Oct 26 2017) states it outright: "implement
the 85/15 revenue share for non-Game subscriptions."

  => $10/mo nets $8.50 (~$102/yr); $400 lifetime nets $340.

Also confirmed: the 15% applies after VAT/GST (Net Receipts definition),
payouts are monthly above a $50 threshold, and no better small-indie rate
exists in the standard terms.

Creator ruling recorded: $10/mo subscription or $400 lifetime, no annual, no
.99. This supersedes the 2026-09-21 one-time $29 -> $49 model.

Two obligations ADA 6(h) attaches to the recurring tier, recorded because they
change its risk profile and are the reason lifetime is the hedge: we must
fulfil the subscription for the entire period as marketed (on breach Microsoft
may refund the full amount plus taxes in its sole discretion), and raising the
price disables auto-renew -- so $10 is effectively locked for the product's life.

Stale facts fixed in the same change rather than appended:
- research-store-certification.md: IARC was 11.11.1/11.11.2 in one table and
  10.11.1 in another; corrected to 10.11.x.
- research-store-certification.md: policy 10.8.1/10.8.2 still asserted "Polar
  explicitly permitted" and "tick the third-party purchase box". Void now that
  Store IAP is the route and Polar is being torn down.
- task-48: the working YouTube demo account (10.3.1) was accidentally dropped
  from the certification list during the Store-IAP edit. Restored -- a reviewer
  cannot use the app without one.
- MyMistakes.md: the verified-fact-vs-decided-outcome lesson now closes its
  loop (the creator did rule the way the research pointed), and records the
  over-correction that followed.

Docs-only. No code, no build, no tests.
2026-09-27 15:02:25 -07:00

236 lines
13 KiB
Markdown

# 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` — `<PackageReference Include="Velopack" Version="1.2.0" />`
- `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.