Correcting the handoff against the repo, per the rule that a stale map is worse than no map. Three facts were wrong or missing, and the first two would have made the next session's first action wrong. 1. PUSH STATE. "Last pushed: e7cded6" with two commits listed as unpushed. main was actually 9 commits ahead of origin/main. The line under-reported unpushed work by 7 commits, which is the optimistic-by-construction failure MyMistakes.md already names for a handoff whose heading promises the work is live. Now recorded as caught up. 2. TASK 50 WAS ABSENT, AND IT CARRIES A LIVE BUG, NOT A QUEUE ITEM. The task was carved 2026-09-28 and its mechanism section was CORRECTED the same day against the creator's own channel: 20+ private videos, all content-less placeholders. That is not a deferred feature, it is the real explanation of the 2026-09-26 "I cannot get private test recordings saved" report the map had been carrying as a closed item. liveBroadcasts.insert succeeds and creates the placeholder; the RTMP push / LIVE transition / archive chain is what fails, so no video content is ever produced. Added as an OPEN entry with the single diagnostic question that localises it (does ffmpeg reach a.rtmp.youtube.com, and does the broadcast reach LIVE), plus the sequencing constraint: do NOT flip enableAutomaticArchiving before TASK 50 ships, since archiving is currently the only route a recording takes to YouTube. It also carries an open 11.12 re-check — a publish feature makes the app PUSH creator content to a third party, so the IARC answers must not describe an app that cannot publish. That is a certification-ordering constraint, not a code detail, and it belongs where the next session will read it. 3. TEST COUNT WAS STALE. Recorded 379; the suite is 380 (the force-ssl scope test added one). Re-verified on a clean test-project build. Also recorded a new flake honestly rather than leaving it for the next session to rediscover as a regression: MasterLimiter_CapsTheLiveMix_OnThePipe failed once on a full-suite run with Expected 0.891 / Actual 0, then passed 27/27 as a class run and 380/380 on an immediate re-run. It reads the live mix off the named pipe, so a ZERO is the pipe not being fed yet, not the limiter misbehaving — the same whole-suite scheduling family as the RealAppHost STA landmine. Recorded with "re-run before believing it". Next list: the RTMP diagnostic sorts first because it is an observation rather than a build and it gates a queued task; the packaging unit remains the next CODE unit, which is not a reordering of the creator's priorities. verify.sh: clean build, 0 warnings, 380/380, scope check passed.
316 lines
22 KiB
Markdown
316 lines
22 KiB
Markdown
# HANDOFF — current state
|
||
|
||
**Branch:** `main` (pre-1.0, no feature branches — creator ruling 2026-08-24).
|
||
**Caught up 2026-09-28:** `main` is pushed to `origin/main` (was 9 commits ahead — the handoff
|
||
line below used to under-report the unpushed work by 7 commits, which is exactly the failure
|
||
`MyMistakes.md` warns about: a stale handoff makes the reader's next action wrong).
|
||
|
||
## 🚨 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.
|
||
|
||
**One of these two survives the tabling, and the other was a bad idea (corrected 2026-09-28):**
|
||
- ✅ **DONE — `Services/YouTubeAuthService.cs` no longer requests `auth/youtube.force-ssl`.**
|
||
It is **redundant**: its grant is *"see, edit, and permanently delete your YouTube videos,
|
||
ratings, comments and captions"*, a strict subset of `auth/youtube`'s *"Manage your YouTube
|
||
account"*, which is the scope we keep. Google's own `liveBroadcasts.insert` reference lists
|
||
`auth/youtube` as an accepted **alternative** to `force-ssl`, so every endpoint this app calls
|
||
(`channels`, `liveBroadcasts`, `liveStreams`, `liveChat`, `playlistItems`) is covered.
|
||
Two consent lines became one. **No forced re-consent** — a refresh token keeps the scopes it
|
||
was originally granted, so existing sessions are untouched; the reduced set applies at the
|
||
next fresh sign-in. Removing a scope cannot narrow capability when the one kept is broader.
|
||
- ❌ **NOT doing this, and the earlier note recommending it was wrong: moving
|
||
`playlistItems.list` off the user's session token.** It was written during the verification
|
||
hunt on the theory that "public data should not ride on user credentials". Walking the code
|
||
kills it: `ChannelStatsService` is built as `new ChannelStatsService(_youtubeAuth)` with **one**
|
||
`HttpClient`, and `channels?mine=true` (same service) **genuinely needs the session**. To split
|
||
it you would need a second unauthenticated client **plus a Google API key**, because every
|
||
YouTube Data API request needs a credential — without a key the call 403s. So the "fix" means
|
||
**shipping a new extractable credential in the binary, subject to quota abuse, to replace a
|
||
token the app already holds.** It buys **zero** verification-surface reduction (the data is
|
||
public; no scope is involved) and zero correctness. Strictly worse. ⛔ Do not "fix" this later
|
||
without a new ruling.
|
||
|
||
## ✅ 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 at that point** (380 now — see Test
|
||
state); 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.
|
||
|
||
## 🚨 OPEN 2026-09-28 — the RTMP push never lands: recordings reach YouTube as EMPTY placeholders
|
||
|
||
**This is a live bug, not a queued feature**, and it reframes a complaint the map previously
|
||
treated as closed. It was found on 2026-09-28 while scoping TASK 50, and the handoff had no
|
||
entry for it at all — corrected here.
|
||
|
||
**What the creator observed:** the channel carries **20+ private videos, all content-less
|
||
placeholders** (creator-inspected, 2026-09-28). This is the real explanation of the
|
||
2026-09-26 report "I still cannot get private test recordings saved" — it was never true that
|
||
the app could not *find* an archived VOD. **No video content is ever produced on the channel.**
|
||
|
||
**Mechanism (verified against the code AND the channel):** `liveBroadcasts.insert` succeeds and
|
||
creates the placeholder; everything after it is what fails. Content only exists if all three of
|
||
these land, and at least one does not:
|
||
1. broadcast reaches **LIVE** via `liveBroadcasts/transition`,
|
||
2. ffmpeg actually pushes RTMP to the bound stream key,
|
||
3. the broadcast ends and YouTube transcodes it into a VOD.
|
||
|
||
`enableAutomaticArchiving` is **never set**, so YouTube's `true` default applies — the archiving
|
||
route exists but has nothing to archive. Broadcasts are inserted `private`
|
||
(`Services/YouTubeStreamService.cs:49`), so the orphans are not public, and the app has **no
|
||
delete path** — the creator clears each one by hand in Studio.
|
||
|
||
**⛔ The one diagnostic question, worth answering before anything else queued:** during a real
|
||
test stream, **does ffmpeg reach `a.rtmp.youtube.com`, and does the broadcast reach LIVE?**
|
||
That single observation localises the failure to one of three steps. Do not go hunting beyond
|
||
it until it is answered.
|
||
|
||
**Sequencing constraint — do not flip the flag early.** `enableAutomaticArchiving: false` is the
|
||
correct end state (TASK 50 will upload the local file deliberately), but archiving is
|
||
**currently the only route a recording takes to YouTube** — flipping it before TASK 50 ships
|
||
would leave test streams with nothing at all and re-create the 2026-09-26 complaint in a worse
|
||
form. Ship the upload, flip the flag in the same task, never before.
|
||
|
||
Detail and the open 11.12 re-check: [`TASKS/task-50-publish-recording.md`](TASKS/task-50-publish-recording.md).
|
||
|
||
## 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
|
||
|
||
**380/380, 0 warnings, re-verified 2026-09-28 on a clean test-project build** (the count is 380,
|
||
not the 379 recorded earlier — the `force-ssl` scope test added one). Build the **test** project,
|
||
not just the app project, or VSTest runs a stale DLL.
|
||
|
||
**⚠️ New flake, do not chase it as a regression:** on the *first* full-suite run,
|
||
`AudioPipelineTests.MasterLimiter_CapsTheLiveMix_OnThePipe` failed with `Expected 0.891,
|
||
Actual 0` — a **zero**, not a slightly-off value, which is the signature of the pipe not being
|
||
fed yet rather than the limiter misbehaving. It passed 27/27 as a class run and 380/380 on an
|
||
immediate full-suite re-run. It reads the live mix **off the named pipe**, so it is a
|
||
whole-suite scheduling artifact of the same family as the `RealAppHost` STA landmine below —
|
||
**if it ever goes red, re-run before believing it.**
|
||
|
||
## 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. **Answer the RTMP diagnostic — an observation, not a build** (see the OPEN entry above).
|
||
One test stream, then read `startup.log` for whether ffmpeg reached YouTube and whether the
|
||
broadcast went LIVE. **Cheap, and it gates a queued task**, which is why it sorts first. It
|
||
does not reorder anything below — the packaging unit is still the next *code* unit.
|
||
2. **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.
|
||
3. **TASK 50 — one-button "Publish this video"** (`TASKS/task-50-publish-recording.md`). Queued
|
||
behind the Store submission by its own precedence note ("MSIX first, publish second — unless
|
||
the creator reverses that"). Scoping is done; the build is not. ⛔ Do **not** flip
|
||
`enableAutomaticArchiving` until it ships. It also carries an **open 11.12 re-check**: a
|
||
publish feature makes the app *push* creator content to a third party, so the IARC answers
|
||
must not go to certification describing an app that cannot publish.
|
||
4. **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.
|
||
5. **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.
|
||
6. **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.
|
||
7. **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).
|
||
8. **Post-session efficacy report** — roll up `CurrentHealth` (dropped frames, duration, health
|
||
message) when a stream or recording ends. `SessionTeardownTests` is the natural home.
|
||
9. **Measure whether the Windows camera toggle gates DShow** — gates all privacy-forward copy
|
||
(`MARCOM.md` guard). Not a certification risk; a trust risk.
|
||
10. **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".
|