Files
LlamaCasty/HANDOFF.md
T
gramps cb750660c3 fix(ypp): refresh 403'd on every real call — drop the auditDetails part (needs a partner scope)
Creator: 'when I attempt to refresh my YPP page, I get an error about not being
able to reach YouTube. Seriously?' Real log: channels.list failed (403) x3.

Root cause #1 (the 403): channels.list?mine=true&part=statistics,auditDetails,
contentDetails returns 403 insufficientPermissions when the token lacks the
youtubepartner-channel-audit scope — which the auditDetails part ALONE requires,
per the docs ('A request that retrieves the auditDetails part ... must provide an
authorization token that contains the youtubepartner-channel-audit scope'). That
scope is MCN partner tooling with a 2-week token-revocation rule; the app must not
hold it. TASK-39's 'current scopes suffice, no re-consent' slice-1 claim was wrong
for this part; mock-fake tests never touched the real API, so it shipped green and
403'd every refresh since 2026-09-22.
https://developers.google.com/youtube/v3/docs/channels/list

Fix: part=statistics,contentDetails only; standing flags removed from
ChannelStatsService -> YppStatSnapshot surface -> YppTrackerViewModel -> drawer,
replaced by an honest deep-link row ('Channel standing isn't exposed to YouTube
apps — check the Earn page'). YppSnapshot standing columns stay (schema-stable,
always false). channels.list failures now log the response BODY — the bare code
could not name insufficientPermissions, which is what made this undiagnosable.

Root cause #2 (found by the new Good Dog, masked by the 403): statistics come back
as JSON STRINGS ('350'); raw GetInt64() throws. Tolerant ReadInt64 (ValueKind-first;
JsonElement.TryGetInt64 THROWS on strings — type-in, not try-type).

Good Dog: ChannelStatsServiceTests.CaptureCurrent_RequestsNoAuditDetails_AndStillParsesTheSnapshot
(URL asserts no auditDetails + snapshot parses); YppPullOutTests fixture updated.
Recipes for both 403/scope and statistics-strings entered in MyMistakes.md.

Also shipped in the same commit (shared PreviewPane.xaml + ai.md): the audio-sync
status dot removal from the #77 feedback round (creator: 'what is the point of the
status light? Lose it') — IntToSyncBrushConverter deleted with it.

verify.sh gate: 0 warnings, 316/316 pass, scope-check clean.
2026-09-23 16:45:56 -07:00

6.8 KiB
Raw Blame History

HANDOFF — 2026-09-23 (build #77 feedback round; LOG fixes all shipped; app likely running)

Branch / Commit State

main HEAD = d72949e (build-77 top-bar feedback: avatar/gear/cluster). Backlog: ac6e67a (end close-out), 18ab553 (health-poll), e69db4d (TASK 42), bb5dcb4 (TASK 41). Pushed state at 197ee81 (36 commits ahead locally — push only at a sub-milestone / on the user's say-so).

Working tree DIRTY — two finished hotfix work units from the live build #77 session:

 M Controls/PreviewPane.xaml             (audio-sync status dot removed + YPP standing row)
 M Themes/Controls.xaml                  (IntToSyncBrush converter resource removed)
 D Helpers/IntToSyncBrushConverter.cs    (converter dead — deleted)
 M Services/ChannelStatsService.cs       (YPP auditDetails part dropped; 403 body logged)
 M Services/YppTrackerViewModel.cs       (standing flags removed; honest deep-link text)
 M ytLive.Tests/YppPullOutTests.cs       (fixture loses the standing flags)
?? ytLive.Tests/ChannelStatsServiceTests.cs  (NEW Good Dog: no auditDetails in URL + parses)
 M ai.md, TASKS/task-22, TASKS/task-39, TASKS.md, MyMistakes.md, HANDOFF.md

⚠️ The app is RUNNING, and that's the story of this round

Ac6e67a could not be verified through the normal gate because the user was looking at the running app (PID 17752) while it happened. verify.sh (clean build 0-warnings + full suite + scope check) cannot run until the app is closed — an in-place build fails the apphost.exe copy MSB3021/MSB3027 while the exe is locked. Compile gate used instead: dotnet build reaches the copy stage with XAML/markup compiling clean (only the 2 copy errors → code + XAML are valid).

What the user asked this round (live-launch feedback on #77, all XAML-only)

  1. Avatar much bigger — now 40×40 (was 26), CornerRadius 20, initial 20pt. Shows the streaming account at a glance (113% bigger than the old 26 read).
  2. Gear moved further from the brand — margin 6 → 18 (a couple more "places").
  3. Uniform 32-height cluster in the top center — segments host Border, Start/Go Live, and Test all Height="32" so the REC|ON-AIR switch + its actions read as ONE family ("so the user can intuit their purpose"). Running world (reality line) intentionally untouched. [COMMITTED d72949e]
  4. Audio-sync status light LOST — "AUDIO SYNC" slider keeps its numeric tooltip; the green/ amber 8×8 dot was "what is the point" dead weight. IntToSyncBrushConverter + its resource + TASK-22's "visual feedback" decision all retired with it, both docs updated in-place.
  5. YPP refresh bug (this turn, "Seriously?") — channels.list?mine=true&part=statistics, auditDetails,contentDetails 403'd insufficientPermissions on EVERY real refresh since slice 1 shipped: the auditDetails part alone requires the youtubepartner-channel-audit scope (MCN partner tooling the app must never hold; the "no re-consent / scopes suffice" slice-1 claim was wrong). Fix: part list is now statistics,contentDetails; standing flags dropped from snapshot→VM→drawer in favour of an honest "not exposed to apps — check the Earn page" row; channels.list failures now log the response body's error.reason (the bare code hid this for days); new Good Dog ChannelStatsServiceTests guards no-auditDetails + still-parses — which it then used to catch a SECOND latent bug the 403 masked (statistics are JSON strings; GetInt64 throws → now the tolerant ReadInt64, ValueKind-first). Docs: ai.md / TASK-39 / TASKS.md / MyMistakes all corrected. Cite: https://developers.google.com/youtube/v3/docs/channels/list

What shipped before this round (all committed)

  • ac6e67a end-of-stream close-out fix — every 2026-09-22 stop logged transition(complete) 403 invalidTransition; the blind POST raced YouTube/autoStop marking the broadcast complete (enableAutoStop=true always set). EndBroadcastAsync now pre-flights liveBroadcasts.list→status.lifeCycleStatus and skips the POST when already complete/revoked; inconclusive pre-check still posts (backstopped); never throws. Good Dog: EndBroadcast_Verifies_LifeCycle_Then_Transitions_Complete_Never_Throws (3 halves: live→GET+POST, complete→skip, 403→error-string). Cite: youtube liveBroadcasts/transition errors table. Full suite 315/315 (audio-timing flake Mix_HonorsProviderGains… cleared on rerun); verify.sh clean pass predates this round's XAML-only edits.
  • 18ab553 health-poll fix — status.healthStatus is an OBJECT, not a string; every session start logged requires an element of type 'String'. Nested shape parsed (flat tolerated), banner alive again.
  • TASK 42 (e69db4d) + TASK 41 (bb5dcb4) — one-surface top bar, Test Stream drawer.

Around the task (carried facts)

  • RealMouseDrag test no-ops while a game/fullscreen window steals the mouse (POE 2 seen 2026-09-22) — close games before full-suite runs. AudioPipeline timing flake similar (load-dependent).
  • Test-env trap: saved OAuth session loads synchronously → force vm.IsConnected in signed-in tests.
  • subscriberCount YPP slice 2 needs re-consent — do not merge with other units. Polar $99/yr must become one-time before launch. MARCOM.md/MONETIZATION.md gitignored — never commit.
  • The YppSnapshot.OverallGoodStanding etc. columns now serialize false forever (the API path no longer populates them; schema kept stable for the analytics slice). Don't "restore" the standing flags via auditDetails — see the MyMistakes recipe.
  • TASK 40 App Settings round: units A (camera) → C (defaults) → D (accent) remain; A's SharedReadOnly control-write question = only genuine uncertainty.

Next step

  1. Close the running app (PID 17752 holds the exe), then run the full gate — ./scripts/verify.sh with the full declared scope (audio-dot unit + YPP fix): clean build 0-warnings + full suite + scope check. Then commit BOTH units — they share PreviewPane.xaml / ai.md, so a single hotfix commit ("build-77 polish + YPP refresh 403") is the honest shape, or two commits if the user prefers strict splits.

    ⚠️ The YPP Good Dog (ChannelStatsServiceTests) and the updated YppPullOutTests cannot even BUILD until the app closes (the app csproj copy fails on the locked exe) — they were written but not yet executed.

  2. After the gate: push decisions pending (user's call). TASK 40 Unit A next.

Critical working rules (unchanged, still binding)

  • Good Dog = ONE integration test per change. Scope lock + ./scripts/scope-check.sh before commit.
  • Windows dotnet host for all WSL builds (/mnt/c/Program Files/dotnet/dotnet.exe, quoted paths).
  • 0 warnings on real clean builds (verify.sh only, never incremental). One runtime model.