The 2026-09-27 entry sat under a "NEXT STEP" heading and framed the Google verification as the top priority. It was neither un-attempted nor un-blocked: the attempt had already failed on a Google-side bug and the creator had already ruled it unimportant for where we sit. A "next step" heading lends a dead end borrowed authority, and the next session would have opened the console instead of the editor. Records the ruling in the terms that change behaviour: attempted, died on Google's side, ruled unimportant, do not re-attempt. States the cost of tabling it honestly — an accepted 7-day refresh-token expiry while the consent screen sits in Testing, which is why the creator kept having to re-consent. The sensitive-vs-restricted classification was never read and is moot until a second real user or Store submission. The mechanism of the Google-side bug was never recorded and is left deliberately blank rather than reconstructed — a confident fiction in a memory map is the one sentence the next session would trust. The project number and console link stay in ai.md:233; they cost a hunt and are still true. The two code scope cleanups survive the tabling (neither needs verification to land): drop the redundant force-ssl scope, and move the uploads-playlist fetch off the user's session token. The channels?mine=true call on the same service legitimately needs the session — flagged so it is not "fixed" later. No code touched. Test state unchanged.
15 KiB
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:
- The sign-out ran first.
ChangeAccountAsyncwasawait SignOutYouTubeAsync(); await SignInAsync();.SignOutYouTubeAsync→TokenStore.Clear()→ deletesytLlive.auth. So the working account's token was destroyed before the browser even opened; cancelling the chooser left nothing. NowChangeAccountAsyncis justawait SignInAsync()— success overwrites the token via thesessionChangedhook, cancel/failure keeps the current session. No sign-out is needed at all. - The retry couldn't rebind the callback port. Abandoning the browser gives the app no signal, so
the attempt held
localhost:8765for its full 5-minute timeout. A newAuthenticateAsyncnow cancels and awaits the abandoned attempt before binding, serialised by aSemaphoreSlim(_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:18requestsauth/youtube+auth/youtube.force-ssl.force-sslis redundant — Google's liveChat docs listauth/youtubeas 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.liston 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/youtubeis forced byliveBroadcasts.*/liveStreams.listregardless) but that fetch should not need user credentials. Thechannels?mine=truecall on the same service does need the session — do not "fix" it.auth/youtubeis 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.
The first code unit: bundle ffmpeg (TASK 48 item 1)
Two real defects the research surfaced — neither is fixed yet, this was a docs unit:
- ⛔
FfmpegLocatordownloads an unsigned exe from GitHub and runs it. Store policy 10.2.2 (dynamic code inclusion) verbatim, and the root cause of the 2026-09-01 failure — the pin aged out of BtbN's 14-day retention and the download 404'd on the creator's first real recording attempt.FfmpegLocator.cs:30-35records the incident but still reads like a design choice. Fix: bundle it. Also deletes the startup network dependency. - ⛔
Distribution.md:318recommended a $400+/yr EV certificate for a SmartScreen bypass Microsoft removed in March 2024. Fixed last commit — 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 inMyMistakes.mdstands: a policy citation is a claim about scope, not just text.
⛔ Two invariants that break silently if touched
- Full trust, or recording breaks.
DefaultRecordFolder()writes to Downloads/MyVideos; that passes through unvirtualized only atmediumIL. Flip the manifest toappContainerand 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 andtask-36-gold-pass.mdstill shows items 1–6 unchecked. The line most likely means item 2 (branding flash goes live) shipped at9761b1d. 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) lockedbin/.../ytLive.exeand brokedotnet runwith MSB3027. Killed. If the build fails to copyytLive.exe, check for a running instance first. LayerReorderPersistenceTests.RealMouseDrag_OnTheLayerList_PersistsTheReorderis flaky by environment (needs an interactive desktop session; 2 fail / 1 pass in isolation). Run the suite with ytLive CLOSED. Not a regression.RealAppHost.RunAsyncexists for a reason — a frame-pump test MUSTawaitinside it. A blocking wait occupies the one shared STA thread and hangs the whole suite with no output. Always background avstestrun and poll the log; a foreground pipedvstestreturns 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
367/367 as of 938c5b3. Not re-run for this unit — docs only, no code touched.
Uncommitted / untracked
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 forCREDENTIALS.md— never commit those.
Next
- Bundle ffmpeg (TASK 48 item 1) — unblocked, first code unit, fixes a real user-facing failure and clears the one hard certification gate.
- Settle the price — one-time vs subscription, and the number. Then declare the IAP
products in Partner Center. Blocks: the Store listing, and the
OverlayHost.xamlcopy. - The packaging project +
Package.appxmanifest(TASK 48 items 2–3) — full trust,webcam/microphone, English only; Velopack deleted. Add theDefaultRecordFolder()comment in the same unit. - Polar teardown (TASK 48 item 0) —
PolarLicenseService,PolarLicense,MainViewModel.License.cs, theOverlayHost.xamlstring, thecsproj/App.xaml.csVelopack sites.IsPremium← Store entitlement. Do this as one unit — a half-torn-down licensing path is worse than either end state. - Verify the Store revenue-share rate — before step 2, not after.
- 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:645flags off-size tiers as a known follow-up. - 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.
- Post-session efficacy report — roll up
CurrentHealth(dropped frames, duration, health message) when a stream or recording ends.SessionTeardownTestsis the natural home. - Measure whether the Windows camera toggle gates DShow — gates all privacy-forward copy
(
MARCOM.mdguard). Not a certification risk; a trust risk. - 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 makes it disappear), 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 4).
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".