Store policy 10.2.2 forbids dynamic code inclusion, and this was the only third-party binary the app ever downloaded and ran. It had already failed in production: the pin aged out of BtbN's 14-day retention and the download 404'd on the creator's first real recording attempt (2026-09-01). Bundling kills that failure class permanently and deletes the startup network dependency. - scripts/fetch-ffmpeg.sh populates a gitignored ./ffmpeg from a pinned month-end BtbN LGPL-shared artifact, SHA-256 verified before extraction, idempotent, FFMPEG_ZIP= override. 135 MB of DLLs in git would tax every commit on this 9p tree; the URL + hash are the audit trail LGPL §6 wants. - ytLive.csproj copies ffmpeg\** to output and publish (Always), and a GuardBundledFfmpeg target fails the BUILD if the folder is missing, so a fresh clone finds out before a take rather than during one. - FfmpegLocator loses the downloader, the zip extraction and the %APPDATA%\ytLlive\tools cache. Order is now bundle -> PATH, deliberately inverted: a PATH ffmpeg may be a different major version or a GPL build, and the bundle is what was pinned, license-vetted and tested. A missing bundle throws loudly; the old "found something on disk" path could satisfy a missing bundle with a stale copy, and silent drift is the worst failure mode for a dependency certification inspects. - ffplay.exe (18 MB) is not shipped; nothing calls it. - THIRD-PARTY-NOTICES.txt: "downloads" -> "bundled" throughout, and the stale build tag autobuild-2026-08-09-13-03 — the tag that 404'd — corrected to autobuild-2026-08-31-13-27 with the exact artifact name and archive hash. OpenH264 made precise: compiled INTO avcodec-63.dll, no separate binary. Checked whether other third-party binaries needed bundling: no. e_sqlite3.dll and WebView2Loader.dll already ship via NuGet into runtimes\win-x64\native\, OpenH264 is inside avcodec-63.dll, the rest are managed. Two gaps remain that bundling cannot fix, recorded in the map: the WebView2 Evergreen runtime is a machine prerequisite (TASK 48 item 2), and Velopack's updater downloads and executes, so its teardown is a compliance action. Tests: 16 locator facts, suite 379/379, 0 warnings. The integration test runs the bundled ffmpeg -version, which is the only assertion that proves the sibling libav*.dll resolved — presence checks pass with the DLLs missing and a shared build then fails to start. TheLocatorCarriesNoNetworkCapability keeps 10.2.2 from rotting back. Analysis: TASKS/research-store-certification.md §4.
17 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.
✅ 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:318recommended 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 inMyMistakes.mdstands: 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 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
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 byscripts/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 forCREDENTIALS.md— never commit those.
Next
- The packaging project +
Package.appxmanifest(TASK 48 items 2–3) — the next code unit. Full trust,webcam/microphone, English only; Velopack deleted. Add theDefaultRecordFolder()comment in the same unit, and the WebView2 framework dependency + graceful-failure path. - 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.xamlcopy. ⛔ The Individual-vs-Company account question is still unanswered — get it right before enrolling. - 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. - 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 (now literally true — the ffmpeg bundle is what makes that sentence defensible).
- 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 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".