b2036a38e8
The creator raised it as instinct: "who hasn't been screwed over cancelling a sub early to be told that the extra six months remaining are your loss?" Checking the mechanism confirms it, and worse than expected. Microsoft's general position: "Digital goods like apps, add-on content, subscriptions... aren't refundable unless the offer or applicable law states that you're eligible for a refund." Pro-rated refunds on cancel exist only in Canada, Denmark, France, Israel, Korea, Turkey (all lengths) and Finland, Germany, Netherlands, Poland, Portugal (renewals only). Critically: "monthly subscriptions and initial (pre-renewal) purchases aren't eligible for a prorated refund." A first-year annual subscriber who cancels in month 2 loses months 3-12, in most countries, and the developer cannot refund it. That is verbatim the trap the creator named, so we must not build it. => monthly only. Max loss $10, max grievance a rounding error, one fewer product to declare, less support surface. The chargeback argument gets stronger, not weaker: a customer down $89 on an annual cliff disputes through their bank, which is frozen funds and account risk against a solo dev. Monthly bounds that at $10. Also records two rulings: - No .99 prices. "Let's stop with the x.99 stuff - I find that irritating. Just say: ten bucks a month." The convention only makes a price look cheaper, which is incoherent for a brand sold on honesty and no-dark-patterns. - No feature gating, now argued as revenue rather than taste: the free tier's reach is the funnel and the branding flash is an ad running inside other people's content. Pricing model itself stays OPEN in TASKS.md — $10/mo is the lean, and a lifetime product (~$300, the hedge against the no-moat renewal problem) is undecided. The Store revenue share is still unverified: confirm the net, not the list. MyMistakes.md records the actual error: I had offered "just refund anyone who asks" as a mitigation without checking who holds the authority to execute it. Store refunds run through Microsoft, not the developer. The check that followed is what produced this constraint. MONETIZATION.md got the full analysis but is gitignored, so it stayed local.
224 lines
12 KiB
Markdown
224 lines
12 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.** ⚠️ **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` — `<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
|
|
- ☐ **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.
|