# HANDOFF β€” current state **Branch:** `main` (pre-1.0, no feature branches β€” creator ruling 2026-08-24). **Last pushed:** `e7cded6` (pricing/Store fee docs). **Local, unpushed:** `536676f` (recording filename ms) and the Change Account fix in this unit β€” ask before pushing. ## 🚨 FIXED 2026-09-27 β€” "Change Account" no longer signs you out before the browser opens **Reported as:** *"I clicked on my signed-in account pic, then clicked sign-in to another account. It opened the browser page to login under a different account and I canceled the request. But when returned to the app, I see logged out β€” and should not have happened! Cannot sign-in because the callback url was not released."* Also reported as hitting **both** running instances. **Two independent defects, and the second one was masking a race:** 1. **The sign-out ran first.** `ChangeAccountAsync` was `await SignOutYouTubeAsync(); await SignInAsync();`. `SignOutYouTubeAsync` β†’ `TokenStore.Clear()` β†’ **deletes** `ytLlive.auth`. So the working account's token was destroyed *before the browser even opened*; cancelling the chooser left nothing. Now `ChangeAccountAsync` is just `await SignInAsync()` β€” success overwrites the token via the `sessionChanged` hook, cancel/failure keeps the current session. No sign-out is needed at all. 2. **The retry couldn't rebind the callback port.** Abandoning the browser gives the app no signal, so the attempt held `localhost:8765` for its full 5-minute timeout. A new `AuthenticateAsync` now cancels and awaits the abandoned attempt before binding, **serialised by a `SemaphoreSlim`** (`_authGate`) β€” without the gate, the takeover window let a third caller see a free port and race to bind it, i.e. the takeover reproduced the original failure. My own test caught this. **The cross-instance claim was NOT the cause** and is still open as a *separate* question: no code path can sign out a second instance (`TokenStore` is per-`InstanceProfile.DataRoot`, `Clear()` is a local file delete with no server-side revoke, and no second instance had a token at inspection time). Verify both processes carried distinct `YTLIVE_INSTANCE` values and ran Debug builds before chasing it. Test: `YouTubeAuthServiceTests.RetryingAfterAnAbandonedAttempt_ReleasesTheCallbackPort`. Full suite **370/370**, 0 warnings (build `ytLive.Tests.csproj`, not just the app project, or VSTest runs a stale test DLL). Added an `openBrowser` ctor seam to `YouTubeAuthService` so tests never launch a real browser. **Coverage gap:** the *ordering* fix (defect 1) has no automated test β€” `_youtubeAuth` is hardcoded in `MainViewModel` and `ChangeAccountAsync` is private, so a real test would shell out to the browser. Wiring an auth seam into `MainViewModel` is a deliberate follow-up, not a bug-fix extra. ## πŸ—„ PARKED β€” Google OAuth verification: attempted, hit a Google-side bug, tabled by ruling **Creator ruling: unimportant for where we currently sit.** Verification is *not* a live blocker and **must not be re-attempted, re-derived, or re-researched** without a new ruling. The 2026-09-27 entry that preceded this one wrongly sat at `⏭ NEXT STEP` and framed it as the top priority β€” that was the plan going in, not the outcome, and the outcome was never written down. Corrected 2026-09-28. **What happened:** the verification attempt was made and failed on a bug **on Google's side** β€” not something we can fix, work around, or retry our way past. β›” **The exact mechanism was never recorded and is deliberately left blank** rather than reconstructed from memory: a confidently wrong note here is worse than none, because the next session would trust it. Do not go looking for the cause. **Owning account (creator-confirmed): `llamachile.shop@gmail.com` ("gramps").** Cloud project number `198374416003` (from the ClientId) β€” banked in `ai.md:233`, **do not re-hunt it.** The one-hop console link is `https://console.cloud.google.com/apis/credentials?project=198374416003` (picker defaults to **"Recent"** β€” switch to **"All"**). β›” Not the same account as the YouTube channel signed into the app. **What tabling costs, stated honestly:** if the consent screen is still in **Testing**, the 100-user cap and the **7-day refresh-token expiry** both stand β€” that expiry is why the creator kept having to re-consent. Parking verification does not fix that; it accepts it for the solo-use phase. It becomes real work the moment a *second real user* needs to sign in, or at Store submission (TASK 48). The sensitive-vs-restricted classification was never read and is moot until then. **These two survive the tabling** β€” neither needs verification to land, and both shrink the surface if it ever comes back: - `Services/YouTubeAuthService.cs:18` requests `auth/youtube` **+ `auth/youtube.force-ssl`**. `force-ssl` is **redundant** β€” Google's liveChat docs list `auth/youtube` as an acceptable alternative, and it is the broader of the two ("Manage your YouTube account" βŠƒ "see/edit/delete your videos, ratings, comments, captions"). Drop it: one fewer scope to justify, one fewer consent line. - `Services/ChannelStatsService.cs:104,107` β€” `playlistItems.list` on the creator's own uploads playlist (`UU…`) is **public data needing no OAuth scope at all** (API key suffices); it currently rides on the user's session token. Does not change the requested scope (`auth/youtube` is forced by `liveBroadcasts.*`/`liveStreams.list` regardless) but that fetch should not need user credentials. The `channels?mine=true` call on the same service **does** need the session β€” do not "fix" it. - `auth/youtube` is **unavoidable**: no narrower scope covers live broadcast create/transition. ## βœ… DECIDED 2026-09-27 β€” pricing: `$10/mo` subscription, or `$400` lifetime, **no annual** **Creator's ruling, verbatim: *"Let's just go with $10 monthly recurring subscription for option A and, for option B, a lifetime sub for $400. No annual."* β›” **No `.99` prices** (earlier ruling: *"Let's stop with the x.99 stuff - I find that irritating. Just say: ten bucks a month"*). This **supersedes** the 2026-09-21 one-time `$29 β†’ $49` model and the old `$99/yr` sub. No feature gating: free gets everything, the branding flash is the only paid delta. **βœ… Store revenue share VERIFIED β€” 15%, and subscriptions are NOT penalised.** Read from the ADA v8.10 PDF itself (downloaded, text-extracted β€” not a search excerpt). Β§6(b)(i) charges **15%** on "any Apps (and any In-App Products in such Apps) that are **not listed in Section 6(b)(iii)**"; 12% is Games-only; 30% covers Xbox console apps/games, Xbox non-subscription IAP, and Windows 8/Phone 8. LlamaCasty is a Windows PC App β€” not a Game, not Xbox, not Win8 β€” so **6(b)(i) governs both tiers.** The agreement's own changelog (**v8.0, Oct 26 2017**) says it outright: *"implement the **85/15 revenue share for non-Game subscriptions**."* β‡’ **`$10/mo` nets `$8.50/mo` (~$102/yr). `$400` lifetime nets `$340`.** Also confirmed: the 15% applies *after* VAT/GST (Net Receipts), payouts are monthly above a **$50** threshold, and no better small-indie rate was found in the standard terms. **β›” Two obligations Β§6(h) attaches to the recurring tier** (why lifetime is the hedge): (1) we must **fulfil the subscription for the entire period as marketed**, and on breach Microsoft may refund the customer *"the full amount, plus taxes... in Microsoft's sole discretion"*; (2) **raising the price disables auto-renew** β€” so `$10` is effectively locked for the product's life absent Microsoft's price-change process. **⏳ Still open:** the `$50` subscriberβ†’lifetime upgrade-discount SKU was discussed and is **not approved** β€” do not declare it. Individual vs Company Partner Center account still unanswered (get it right before enrolling). ## βœ… DECIDED 2026-09-27 β€” distribution is the Microsoft Store: MSIX + Store IAP **Creator's criteria, verbatim: "zero headaches, minimal maintenance (for me) while still providing accountability and a reasonably easy upgrade flow."** Route A is the only option where all four are solved by handing the work to Microsoft rather than to a certificate vendor: **$0/yr, no certificate, no HSM, no annual renewal, no SmartScreen ramp**, plus Store auto-update, Store-side payments/entitlements/refunds/support, and Microsoft review as the accountability layer. The rejected options and their reasons are in `TASKS/research-store-certification.md` Β§3 β€” **read that before reopening the question, not before re-deriving it.** **The big consequence: the whole licensing backend is deleted.** `PolarLicenseService`, `PolarLicense`, `MainViewModel.License.cs` (`PremiumUrl`, customer portal, the `OfflineGracePeriod = 14 days` subscription-era artifact, renewal/lapse copy) and the wrong "Polar unlocks alerts" string all become dead code. `IsPremium` comes from the **Store entitlement** instead of an HTTP call β€” which also deletes the entire "network flaky β†’ app thinks I'm expired" bug class. **Velopack, the update URL and the DO droplet go too.** **What does not change: the entitlement.** Free gets everything; the branding flash stays the ONLY paid delta. Store IAP changes how `IsPremium` is *obtained*, never what it *gates*. **β›” The old "still open: the price" note is retired β€” the price is settled, see the top of this file.** The old "~$69 floor" and "don't break the launch-price promise" notes refer to the old Polar storefront and are **not** current. ### βœ… DONE 2026-09-28 β€” TASK 48 item 1: bundle ffmpeg (was the "first code unit") The app no longer downloads an executable at runtime. `scripts/fetch-ffmpeg.sh` populates a gitignored `ffmpeg/` from a **SHA-256-verified** pinned month-end BtbN artifact; `ytLive.csproj` copies it to output *and publish* and **fails the build** if it's missing; `FfmpegLocator` lost its downloader, its zip extraction and the `%APPDATA%\ytLlive\tools` cache, and now resolves **bundle β†’ PATH** with a loud, actionable failure. The pin that 404'd is gone, so the 2026-09-01 failure class cannot recur. Suite **379/379**, 0 warnings; 16 locator facts including an integration test that **executes the bundled binary** (which is what proves the sibling DLLs resolve). `THIRD-PARTY-NOTICES.txt` corrected β€” it still named the *stale* tag `autobuild-2026-08-09-13-03`, i.e. the one that 404'd. **Fresh-clone cost, deliberately accepted:** `./scripts/fetch-ffmpeg.sh` once (~65 MB). 135 MB of DLLs in git would tax every commit on this 9p tree; the URL + SHA-256 are the audit trail. **Answering "other 3p binaries needing bundling?" β€” no, verified.** ffmpeg was the only download-and-execute. `e_sqlite3.dll` + `WebView2Loader.dll` already ship via NuGet (`runtimes\win-x64\native\`), OpenH264 is compiled *into* `avcodec-63.dll`, the rest are managed. Two gaps remain that bundling cannot fix, and they are the next real risks: the **WebView2 Evergreen runtime** is a machine prerequisite (item 2's manifest dependency + a graceful-failure path), and **Velopack's updater downloads and executes**, so its teardown in step 3 is a compliance action. ### The next code unit: the packaging project + `Package.appxmanifest` (TASK 48 items 2–3) Full trust, `webcam`/`microphone`, English only; Velopack deleted. Add the `DefaultRecordFolder()` comment in the same unit (the mediumIL invariant below becomes load-bearing the moment the manifest lands). Full-trust writes under a real package identity are still **unverified** β€” confirm on a real packaged build before trusting them with recordings. Carried over from the research unit, still open: - **β›” `Distribution.md:318` recommended a $400+/yr EV certificate for a SmartScreen bypass Microsoft removed in March 2024.** Fixed then β€” had it shipped, it would have cost $400+/yr to buy what $150 buys. Now moot anyway (MSIX is signed by Microsoft), but the lesson in `MyMistakes.md` stands: a policy citation is a claim about **scope**, not just text. - **WebView2 Evergreen runtime** is a machine prerequisite the app will hard-fail without. The manifest needs the framework dependency, and WebView2 init needs a graceful error instead of a crash. New, found while bundling ffmpeg. ### β›” Two invariants that break silently if touched - **Full trust, or recording breaks.** `DefaultRecordFolder()` writes to Downloads/MyVideos; that passes through unvirtualized **only** at `mediumIL`. Flip the manifest to `appContainer` and output vanishes with no error. A comment is owed there **when the manifest lands** (TASK 48 item 6) β€” not before. - **Chat is rendered, never stored.** That non-persistence half is the load-bearing part of the 11.12 certification answer. No chat history, moderation log, analytics, or crash payload may be added without re-arguing 11.12 first. ## Landmines - **Stale fact inside `TASKS.md` (left alone, flagged here per the Scope Lock).** The "1.0 gates" section asserts "**TASK 36 is now shipped**", but the catalog row 36 reads **☐ Queued** and `task-36-gold-pass.md` still shows items 1–6 unchecked. The line most likely means *item 2* (branding flash goes live) shipped at `9761b1d`. **Needs a one-line fix, not a code change** β€” flagging rather than fixing because it is not this unit's job. - **A stale `ytLive.exe` (PID 2544) locked `bin/.../ytLive.exe`** and broke `dotnet run` with MSB3027. Killed. **If the build fails to copy `ytLive.exe`, check for a running instance first.** - **`LayerReorderPersistenceTests.RealMouseDrag_OnTheLayerList_PersistsTheReorder` is flaky by environment** (needs an interactive desktop session; 2 fail / 1 pass in isolation). **Run the suite with ytLive CLOSED.** Not a regression. - **`RealAppHost.RunAsync` exists for a reason** β€” a frame-pump test MUST `await` inside it. A blocking wait occupies the one shared STA thread and **hangs the whole suite with no output.** Always background a `vstest` run and poll the log; a foreground piped `vstest` returns nothing in this shell even on success. - **`GlobalHotkeys`: two instances registering the SAME global hotkey** β€” Windows refuses the second. Left alone deliberately. - **MSIX writes under a real package identity are unverified.** The docs say full trust passes user-profile writes through, but confirm on a real packaged build before trusting it with recordings (TASK 48 item 6). ## Test state **379/379, 0 warnings, as of the ffmpeg-bundling unit (2026-09-28).** Build the **test** project, not just the app project, or VSTest runs a stale DLL. ## Uncommitted / untracked - `ffmpeg/` β€” the 9 bundled binaries (~135 MB), **gitignored by design**. Populated by `scripts/fetch-ffmpeg.sh`. Do not commit it. - `MARCOM.md`, `MONETIZATION.md` β€” both edited (privacy-copy guard; the Polar-obsolete banner and the re-opened-price note) and both **gitignored by design** (confidential business files, `.gitignore:10-11`). They stay local, as intended. Same for `CREDENTIALS.md` β€” **never commit those.** ## Next 1. **The packaging project + `Package.appxmanifest`** (TASK 48 items 2–3) β€” the next code unit. Full trust, `webcam`/`microphone`, English only; Velopack deleted. Add the `DefaultRecordFolder()` comment in the same unit, and the WebView2 framework dependency + graceful-failure path. 2. **Declare the IAP products in Partner Center** β€” the price is **settled** ($10/mo, $400 lifetime, no annual, 15% verified), so this is unblocked. **Blocks:** the Store listing and the `OverlayHost.xaml` copy. β›” The Individual-vs-Company account question is still unanswered β€” get it right before enrolling. 3. **Polar teardown** (TASK 48 item 0) β€” `PolarLicenseService`, `PolarLicense`, `MainViewModel.License.cs`, the `OverlayHost.xaml` string, the `csproj`/`App.xaml.cs` Velopack sites. `IsPremium` ← Store entitlement. **Do this as one unit** β€” a half-torn-down licensing path is worse than either end state. 4. **Vertical recording verification** β€” the compositor tier is proven by `Render_VerticalTier_Outputs_1080x1920_From_The_Center_Crop`; the *record* path has never been run. `FramePump.cs:645` flags off-size tiers as a known follow-up. 5. **WACK + certification notes + the privacy policy** (TASK 48 items 4–5) β€” the policy must state camera/mic is OS-gated, nothing is retained, and **nothing is fetched at runtime** (now literally true β€” the ffmpeg bundle is what makes that sentence defensible). 6. **Post-session efficacy report** β€” roll up `CurrentHealth` (dropped frames, duration, health message) when a stream or recording ends. `SessionTeardownTests` is the natural home. 7. **Measure whether the Windows camera toggle gates DShow** β€” gates all privacy-forward copy (`MARCOM.md` guard). Not a certification risk; a trust risk. 8. **TASK 36:212 stale "shipped" line** β€” one-line fix, needs a hand. **Dropped from the list** because the ruling made them moot: multi-instance's ffmpeg tools-dir race (item 1 deleted the tools dir entirely), `scripts/publish.sh` + the `LlamaCasty.exe` rename (the Store packages and delivers β€” *revisit only if we ever ship outside the Store*), and the alert-gating copy (the wrong string dies with the Polar teardown in step 3). **Everything else in the v1 queue:** stream resilience (32), bandwidth step-down (33), scheduled streams (34), scene-linked audio (35), the master limiter (12), text source (3.16), reward events (3.20), the media picker (21), webcam identity (9.5), settings units A/C/D (40), and the defaults split (37). Ship-checklist items live in `TASKS.md` -> "1.0 gates".