Files
LlamaCasty/docs/ai/product.md
T
gramps a311c8feb1 docs(ai): partition ai.md into five subsystem files (verbatim moves)
ai.md 1,308 → 228 lines. No prose rewritten: every block was moved as-is so
nothing could be lost in translation, then replaced by a pointer.

New files:
- docs/ai/environment.md — LAN/SSH + Gitea push recipe, Windows dotnet
  build/run, test-suite map, hermetic real-MainWindow test rule
- docs/ai/product.md — brand/naming, AI transparency, GPL licensing
  prohibitions, free-forever monetization, auth/account assumptions,
  packaging & distribution
- docs/ai/youtube-api.md — go-live ordering, the broadcast-binding fix, the
  private-only lock, health parsing, end-stream pre-flight, Test mode,
  chat insert shape
- docs/ai/overlays.md — social bar, native alert box (animations, clip,
  ticker), chat box conventions
- docs/ai/ui-surface.md — control surface, scene strip, preview rail,
  hotkeys, broadcast drawer, toasts, transitions, brand logo, launch
  geometry, theme/properties conventions

ai.md keeps the response/derivation rules, the architecture table, the four
existing invariant stubs, and one pointer per moved subsystem.
docs/ai/index.md routes every file with a "read when" column.

References falsified by the move were fixed in the same commit: AGENTS.md
build note → environment.md → Run, and the "Related" footers in
frame-pipeline.md / output.md / capture.md now point at the owning files.

Docs-only unit: scope-check run, no build (no source touched).
2026-10-29 11:05:53 -07:00

30 KiB
Raw Blame History

Product: brand, licensing, monetization & distribution

What the product is called, what it promises, what it may never do legally, and how it ships. Read before branding, marketing, pricing, licensing or packaging work.

Brand

Brand palette and official assets live in MARCOM.md (gitignored — read it for hex values, asset file names, and marketing copy). The brand red #e94560 is the primary accent used throughout the UI.

Product name vs repo/assembly branding (2026-09-14)

The product is llamacasty (llamacasty.com, llamachile.tube, llgit.llamachile.tube). The repo path, csproj AssemblyName/RootNamespace, DB/log paths (%APPDATA%\ytLlive\...), and most internal namings are the legacy ytLive/ytLlive — a re-brand that never renamed the internals. Treat the two as separate: external/user-facing language says "llamacasty", code/assembly/repo names stay ytLive. If a full internal re-brand is ever done, this note and the UI locator (Premium/wordmark) are the checklists; it is NOT a goal pre-1.0.

Say "LlamaCasty" when reporting to the creator — never "ytLive" in prose. ytLive is the internal repo/assembly/namespace name only. Exact current split:

surface name note
product / site / marketing LlamaCasty llamacasty.com, llamachile.tube
repo dir, csproj, RootNamespace ytLive 230 files: namespace ytLive.*
AssemblyName → shipped exe ytLive ytLive.exe
app data dir %APPDATA%\ytLlive capital L, drifts from ytLive
Distribution.md tells customers LlamaCasty.exe ⚠ mismatch vs the real ytLive.exe

Consequences, so nobody re-hunts them:

  • Renaming <AssemblyName> to LlamaCasty is the cheap win (the customer-visible exe name matches what Distribution.md already promises; namespaces can stay ytLive.*). It is 4 lines, but it hard-breaks three pack URIs that spell the assembly name: MainWindow.xaml:14 (the window icon — taskbar/Alt-Tab), OverlayHost.xaml:96, RenameRecordingDialog.xaml:5. Miss those and you ship a broken icon. Queued in TASKS.md under the shipping task, not done.
  • Renaming the %APPDATA%\ytLlive folder is breaking — it would orphan every existing user's layout DB, Assets and settings. Never do it casually; needs a migration.
  • No code compares its own assembly name (no GetExecutingAssembly().GetName().Name checks), so the assembly name is otherwise free to change.

AI transparency

This project is built through AI-assisted pair-programming. All code is generated under the supervision of an experienced developer with decades of coding experience. Architecture decisions, product direction, and quality gates are human-owned; AI accelerates implementation. This is a testimonial to successful human-AI collaboration, not autonomous code generation.

Licensing — do not violate (GPL-3.0 open source since 2026-10-09; see LICENSE, ADDITIONAL-TERMS.md, THIRD-PARTY-NOTICES.txt)

LlamaCasty is free software under GPL-3.0-or-later (creator rulings 2026-10-09 — free forever, public repo, "GPL-3 with attribution"). Copyright (C) 2026 Gramps. ADDITIONAL-TERMS.md carries the three §7 terms: 7(b) every copy keeps "Based on LlamaCasty by Gramps — https://llamacasty.com" in its About screen; 7(c) modified versions are marked and renamed; 7(e) no trademark rights. The icon + logo files (Assets/llamacasty-icon.png/.ico, Assets/llamacasty-logo.jpg) are all rights reserved, outside the GPL — verbatim copies may carry them, forks must replace them (this is what keeps the merch brand protected). The scene backgrounds + default alert video are AI-generated placeholders (creator: made "with the intent of individual creators to immediately replace with their own images") — they go out under the GPL with the code, no reservation. LICENSE + ADDITIONAL-TERMS.md ship beside the exe (csproj None items); the About panel shows THIRD-PARTY-NOTICES.txt, whose header carries the LlamaCasty license notice (GPL §5(d) "Appropriate Legal Notices"). Source offer (§6): https://llgit.llamachile.tube/gramps/LlamaCasty (the Gitea repo; creator toggles it public — the old gramps/ytLlive path 301-redirects there), recorded in the notices header + README.

Every bundled component must stay GPL-3.0-compatible (MIT/BSD/Apache-2.0/LGPL/public domain all are; GPL-2.0-only and proprietary are not; Apache-2.0 is fine with GPL-3 but NOT with GPL-2). The rules below predate the GPL switch; where their reason was "proprietary product" it is noted, but the rule itself still stands for its other reasons. NEVER:

  • Swap the FFmpeg pin casually — the BtbN lgpl-shared pin is the verified build. A GPL FFmpeg build would now be license-compatible (the old "GPL contaminates the paid product" reason is gone), but changing builds means re-verifying encoders, and libx264 carries no H.264 patent grant (Cisco's covers OpenH264). Keep lgpl-shared unless a task says otherwise.
  • Distribute the static lgpl build — LGPLv2.1 §6 wants relinkable object files for static linking. The shared (dynamic-DLL) build sidesteps that: compliance is "license text + source offer + unmodified binaries". The pin is lgpl-shared; when the pin is refreshed, keep the shared variant.
  • Use BtbN's nonfree variant — it adds fdk-aac (Fraunhofer code licensing). The native FFmpeg AAC encoder is fine (no Fraunhofer code) but grants no AAC patent license — accepted low-risk posture for RTMP→YouTube, since encoder vendors cover their implementations (Cisco OpenH264, NVIDIA NVENC, Intel QSV, AMD AMF).
  • Link FFmpeg into the app — it stays a separate subprocess fed frames over a pipe; that separation keeps the app's own code out of LGPL reach.
  • Drop THIRD-PARTY-NOTICES.txt from the shipped app or the About screen, or alter the FFmpeg copyright/LGPL notices inside the downloaded binaries. Automating the download counts as distribution — the obligations are not optional.
  • Pin to a moving target — the latest BtbN release tag floats. Only immutable autobuild tags give a reproducible source offer. Record the tag + variant beside the URL (TASKS.md) every time the pin moves.
  • Use non-CC0 icon art — the social bar's bundled SVG logo path data comes from Simple Icons (CC0 1.0, public domain — see THIRD-PARTY-NOTICES.txt). Replacing or adding logos must stay CC0 or another public-domain source; a logo asset under an incompatible or attribution-heavy license would complicate every redistribution. The app's icon + logo (Assets/llamacasty-icon.*, Assets/llamacasty-logo.jpg) are the creator's own art — all rights reserved, outside the GPL (ADDITIONAL-TERMS.md).
  • Ship an ML model without clean provenance — the background-effects model (TASK 51) is Google's official MediaPipe Selfie Segmenter download (Apache-2.0, model card), converted by our own script. Model-zoo re-hosts (e.g. PINTO #109 = ML Kit's model, extracted) are off-limits. Apache-2.0 §4 obligations (license notice + "we converted it") live in THIRD-PARTY-NOTICES.txt.
  • Forget the v1 license-texts gate — THIRD-PARTY-NOTICES.txt links the canonical license texts; at v1 (GA) the full texts of every license it names MUST ship alongside it (TASK 4 requirement 9 is the release blocker). Queued early is wrong; the release pass owns it.
  • Break the in-app About requirement — since TASK 11 the About overlay is the app's in-app creator hub: the real logo, the creator links (YouTube channel, Mastodon, Buy me a coffee, and the greyed "Unlock Premium" seam whose billing URL is tabled), and a Licenses & legal sub-panel that loads THIRD-PARTY-NOTICES.txt from the executable directory and renders it in a scrolling panel — never the OS viewer, no Notepad, no browser tab. The premium/coffee link constants live in MainViewModel (PremiumUrl, CoffeeUrl, ChannelUrl, MastodonUrl); filling in the billing URL lights up the premium button. Do not reintroduce an "open the notices in Notepad" button.

Design Principle

This software is so intuitive that even the most right-brained person can easily intuit and use it.

Apply this to every UI decision:

  • One-click go-live with working defaults
  • Prefilled YouTube defaults (RTMP URL, bitrate, resolution, latency)
  • Visual/drag-and-drop scene building over property panels
  • Every action produces a visible outcome — no dead ends
  • "We're not them" (2026-09-01): assume the creator's hardware is mediocre, because it is. Every feature spends the machine's budget once — never twice for the same result (record OR stream, not both; one reusable stream; one webcam; two mixer inputs). If a feature only sings on a high-end chassis, it doesn't ship — the OBS escape hatch is open by design.

Monetization (design decision — the branding flash is the sword)

⛔ Superseded 2026-10-08/09 — free forever, no branding on the stream. The credit is now OFF by default: MainViewModel.ShowBrandFlash (internal static, default false) is the master switch, and BrandFlashEnabled => ShowBrandFlash && !IsPremium feeds the presenter at construction and on every licence change. The code (BrandFlashPresenter, the FramePump brandFlash: seam, the preview layer) is kept, not deleted — creator ruling: "don't delete it, just make it conditionally displayed". BrandFlashOutputTests opt in by setting the switch before constructing a MainWindow; Credit_IsOffByDefault_EvenOnTheFreeTier guards the default. Everything below is the historical paid-tier design.

Full pricing and the Year 1 revenue forecast in MONETIZATION.md (gitignored). ⚠️ No Polar product, discount code, or license key exists any more — nothing bills today; Polar is dormant. Brand palette and assets in MARCOM.md (gitignored). This section covers the in-app model.

Free forever: all features unlocked for everyone — no feature lock, no paid tier, and since 2026-10-08 no watermark either (the credit is OFF by default; see the banner above). No creator will tolerate a watermark, and as a good-guy developer we give everyone complete access so no one can call us crooked. Money comes from gratuity/merch, not gating (TASK 54).

  • Free: a periodic branding credit — "made with LlamaCasty!" in full-frame neon (white core, feathered red/blue halo with the channels split in opposite directions), at a random position inside the frame on every presentation (single pre-measured line, so it never wraps; travel range is 0 … 1920−textWidth), held 2s (500ms fade in, 1000ms full, 500ms fade out), first credit ~5s after go-live then every rand(30s)+30s (30–60s), on the live output and local recordings. An always-on watermark can be cropped or covered; an intermittent full-frame flash can't be cropped and is impractical to edit around on a live feed. The flash is also the free tier's billboard — every free stream advertises LlamaCasty to its own viewers; the free tier is distribution, not compromise. Implemented as Services/Compositor/BrandFlashPresenter.cs (raster + envelope + placement + licence gate), published to both the frame pump (FramePump(brandFlash:) → the compositor's per-frame flashFrame slot) and the preview (BrandFlashElement, bound to BrandFlashImageSource) from the same VideoFrame, so the preview cannot drift from the broadcast. Deliberately NOT the flashFrame parameter of SceneGraph.GetBakedBase — SceneRegion.Matches compares element ids only, so a credit folded into the cached bake would freeze there and never expire. Pre-GA posture — REVERSED 2026-09-26 (TASK 36 shipped). This used to render in the preview only, never on the broadcast, so test VODs stayed clean. The creator ruled that the free tier must be honest advertising: it now composites into the recording and the stream. Test VODs from an unlicensed instance carry the credit — use a license key or the paid build for clean captures. Escalation model (2026-09-01, creator decision): the cadence is obnoxiously self-promoting. License activation still flips exactly one bit: IsPremium → flash off. Nothing else changes between free and paid, ever. Cadence is APP-LIFETIME, not go-live (creator ruling 2026-09-26): the presenter is Start()ed once in the MainViewModel ctor, not from UpdateLiveVisuals(). It used to be gated on IsLive, which meant a recording made without ever going live carried no credit — the creator's ruling: "the made with llamacasty flash should appear in all scenes, not just live" and "should also appear in recordings". One start now covers every scene, the preview, the stream and the recording, because go-live and local recording are the same FramePump (Streaming.Operations.cs:68 and :199 both call StartAsync with the same brandFlash: delegate) — verified, not assumed; there is no second encoder path needing a "redirect". The licence gate needs no live branch: IsPremium's setter pushes Enabled from anywhere. Consequence of app-lifetime — the premium edge restarts the timer. Enabled = false stops the DispatcherTimer (cutting the advertisement mid-credit). Because Start() is no longer called per go-live, nothing would ever restart it, so a key entered mid-session would leave the credit dead until the process restarted. The setter therefore restarts it when re-enabling while _running (IsCadenceTimerEnabled is the test seam). Test-arithmetic trap, hit again 2026-09-26: Advance(5.1) in ONE call cannot observe a credit — Advance both opens the presentation and ages it by the same delta, so a single 5.1s step blows clean through the 2s window. Always step in 1/30s increments like the real timer (the Step helpers in BrandFlashOutputTests).
  • Paid: branding flash removed. BrandFlashEnabled is now a derived, non-assignable !IsPremium and the presenter's own Enabled gate is re-checked on every frame, so the entitlement changing mid-credit cuts the advertisement on the next tick rather than letting it finish. That's it. No feature gating. Alerts, social bar slots, voice filters, TRAX, recording — everything is free. How IsPremium is obtained is a billing concern and has changed twice; what it gates has not.
  • Pricing: ⛔ SUPERSEDED 2026-10-08 by the free-forever ruling (TASK 54). Retained as reasoning only: $10/mo subscription or $400 lifetime, no annual, ruled 2026-09-27 and overtaken ten days later. If a paid tier ever returns, these are the numbers to argue from. How IsPremium would be obtained is a billing concern; what it gates is not.
  • Billing: ⏸ dormant — no paid tier exists (free-forever ruling 2026-10-08, TASK 54). The Store IAP plan of 2026-09-27 was never implemented (Windows.Services.Store appears nowhere in the repo) and was withdrawn with the Store route on 2026-10-22. If a paid tier ever returns, the provider is Polar — merchant of record for VAT/sales tax, 5% + $0.50 Starter, $50 already paid; Distribution.md §4.2/§4.5 holds the design. ⚠️ No Polar product, org ID, key prefix, or discount code exists (d105dfa1-…, c05fb364-…, LCYT-, LLAMAFOUNDER/ LLAMA50, the customer-portal validate endpoint, and the 14-day offline-grace machine are all gone).
  • Support (creator's model, corrected 2026-09-01): support = email + GitHub issues — the once-planned in-app bug-reporter is out of product (TASKS.md → closed list). Most queries are how-tos / feature requests / manual-skimmers. Maintenance cadence = "when I get around to it" with emergency patches; not a 24/7 service promise. There is no licence chatter at all — the app is free, there are no keys, and no user sees licence UI. TASK 36 item 5 (expiry reminder) is struck.

Monetization awareness (built-in, ungated, free — 2026-09-01; v1 SCOPE per the complete-v1 ruling — build order: capture → report → journey → Alerts): the app is monetization-aware by design, for every creator, free and ungated (the only subscriber swap remains the branding-flash removal above). It has three layers, all free — this is the product's differentiator, not a paid tier:

  • Reward-event capture (the data foundation). The live-chat poll (YouTubeChatService.Poll) receives every reward event over liveChat/messages and — since TASK 43 (2026-09-24) — decodes all six types, not two: superChatEvent, superStickerEvent, newSponsorEvent, membershipGiftingEvent, giftMembershipReceivedEvent, memberMilestoneChatEvent land as ChatMessage.Kind (ChatEventKind) with their payload fields (display string, sticker description, gift count, gifter name, milestone months); free subscriptions emit NO event (API fact — a sub mention is a plain text row). The remaining half of the original capture plan stays open (TASK 3 item 20): persist each event to the canonical SQLite RewardEvents table (broadcastId, type, timestamp, amountMicros, currency, tier...) + superChatEvents.list (30-day lookback) backfill — that persistence is what the session report and the journey tracker read. Alerts did not wait on it: the shipped alert path (TASK 43) renders directly from the in-memory ChatMessage feed.
  • Session / broadcast report. A session is created at Go Live; the poll + End roll that session's reward totals (new members, Super Chat , stickers , gifts, milestones). This is the in-app replacement for the emailed external "stream activity report" — the incumbents (Streamlabs et al.) email near-useless post-stream summaries; we track the same data natively and maintain it.
  • Journey tracker (TASK 39, slice 1 shipped 2026-09-22). A "YPP" tab below the Stream Settings pull-out (Live screen right edge) shows the connected creator's position toward each YPP tier threshold. Slice 1 = current-scope data only: channels.list?mine=true &part=statistics,contentDetails (subs/views/videos + uploads-playlist id) + playlistItems.list for 90-day public uploads. Every refresh appends a YppSnapshot to SQLite (schema v10) — the history a later slice needs for velocity/ETA. The auditDetails part is OUT (fixed 2026-09-23): readding it 403s the WHOLE request — that part alone requires the youtubepartner-channel-audit scope no app for normal creators should request (the standing flags sat unreadable since slice 1 shipped; the app's youtube + youtube.force-ssl scopes were never sufficient for them — the "no re-consent" slice-1 claim was wrong for that part). Channel standing now deep-links to the Earn page; API stats come from statistics,contentDetails only. YPP/API failures log the response body's error.reason. YPP thresholds are versioned, date-aware DATA, never constants — the Tier-2 bar doubles for new applicants on 2027-02-01 (long-form 4,000 → 8,000 qualified hrs / 365d; Shorts 10M → 20M / 90d), while the Tier-1 fan-funding bar (500 subs / 3,000 hrs / 3M Shorts) is unchanged. Hardcoding the pre-2027 numbers would ship the wrong ETA. Watch-hours / Shorts / velocity / ETA are slice 2: Analytics-API scopes (yt-analytics.readonly, additive yt-analytics-monetary.readonly) behind the IChannelStatsProvider seam (additive capability, requires an OAuth re-consent). Compliance items no API exposes (2FA, AdSense linkage) are in-app self-reported checkboxes with deep links; channel standing (exposed only via the partner-audit scope) deep-links to the Earn page. Slice 2's once-daily refresh gate + snapshot history were baked into slice 1.

What was rejected: always-on watermark (obscurable — replaced by the flash), hard stream-time cutoffs (the worst dead end — a stream dying mid-broadcast reads as broken, and YouTube streams routinely run 2-4 hours), soft-limit nagging, freemium feature tiers, and donation-only (relies on the kindness of strangers). Resolution/quality ceilings stay rejected (fixed 2026-09-01, no longer "deferred"): the free tier never loses quality — auto step-down (TASK 33) is a protect the stream feature, never a monetization penalty.

Auth gates Go Live, but not exploration

The app is fully usable without authentication: users can build scenes, add sources, compose previews, and audition the software with zero commitment. But going live requires authentication — it's the one capability gated behind YouTube sign-in. The sign-in should never pressure the user ("sign in (optional)", not a modal wall): the two-state top bar shows Start Stream (offline) / End Stream (live), and the Start Stream dialog hosts the account — a saved session appears as the default with "Change Account"; with none saved, a "Sign in to YouTube" button starts OAuth and the Start button stays disabled until signed in.

Account assumption (do not build an account setup flow)

Connecting uses Google OAuth ("Sign in with Google") to link an existing YouTube creator account. ytLlive never creates or sets up accounts — that is YouTube's job. If the creator has no YouTube channel, they go to YouTube first. This assumption is explicit and must never be silently replaced by an in-app account-creation step. Zero state = the Start Stream dialog's "Sign in to YouTube" button; going live is unreachable until an account is connected.

Windows packaging & distribution (⛔ REVERSED 2026-10-22 — direct + Steam; Store dropped)

The Microsoft Store route is dead. A brand name on the Store's publisher line requires a Company account, which requires a registered legal entity (an LLC — ~$85 New Mexico with a ~2-week SOS backlog, ~$143 Wyoming). Partner Center does not convert an Individual account to a Company account later, and an Individual account publishes under the verified legal name. The creator will not fund an entity for a nameplate, so the 2026-09-27 Store ruling was withdrawn on 2026-10-22. Store IAP was never implemented either — Windows.Services.Store appears nowhere in the repo.

The route now:

  1. Direct download from llamacasty.com — the app is free forever (TASK 54), so there is nothing to bill in v1.
  2. Steam when $100 exists — Steam Direct is $100 per product (recoupable after $1,000 AGR), accepted categories include Audio/Video Production / Player Tools (OBS Studio is the precedent), the developer/publisher name is self-chosen so the brand needs no entity, 5+ screenshots at 1920×1080 + capsule art, a build Valve runs, and a 2-week live page before release. Valve takes 30%.
  3. Polar is dormant, not deleted-in-principle — chosen checkout/licence provider if a paid tier ever returns; $50 already paid. Distribution.md §4.2/§4.5 holds the design.
  4. The Microsoft Store: not pursued. The MSIX package stays as a working artifact and is submitted nowhere. History: TASKS/research-store-certification.md, TASKS/task-48-distribution-msix.md.

Consequences that are architecture, not packaging detail:

  • ⛔ The updater is now MISSING, not deleted-by-design. Velopack and its bootstrap were removed on 2026-10-22 (TASK 48 slice 3) because the Store was going to deliver updates — that premise is gone. An update path must come back, and ytLive.Tests/StorePackagingTests.cs (which asserts no updater returns) must be rewritten with the chosen updater rather than left to veto one.
  • The MSIX package still builds via scripts/package-msix.sh (TASK 48 slice 2) — publish Release -r win-x64 --self-contained into artifacts/msix-stage, copy Packaging/Package.appxmanifest in as AppxManifest.xml + Packaging/Assets/, then makeappx pack → artifacts/LlamaCasty.msix (159 MB, gitignored; MSIX_PFX signs it for a local install). The manifest is a full-trust packagedClassicApp at mediumIL with runFullTrust + webcam + microphone. Square310x310Logo/Wide150x150Logo traps are in MyMistakes.md. Logos come from the app's own artwork via scripts/make-store-logos.py.
  • ⛔ Bundle ffmpeg — no runtime downloads (was Store policy 10.2.2; still right for a direct download, and it fixes the 2026-09-01 404). See docs/ai/output.md → "FFmpeg locator".
  • ⛔ 64-bit Windows only (creator ruling 2026-10-09). Both csproj files set <PlatformTarget>x64</PlatformTarget> (the test project too — an AnyCPU test project referencing an x64 app raises MSB3270 and breaks the 0-warning gate). Why: the bundled ffmpeg is the win64 build, ONNX Runtime 1.31 ships no win-x86 native (only win-x64/win-arm64), and a 32-bit address space can't hold the 1080p frame buffers. Windows 11 is 64-bit-only anyway. Public copy says "64-bit Windows 10 or 11".

What does NOT change: the monetization posture. The app is free forever with no watermark (TASK 54, 2026-10-08), so IsPremium gates nothing in v1 and the branding flash is OFF by default. Monetization above governs.

⛔ Durable invariant: full trust, or recording silently breaks

MSIX packages run at one of two trust levels. A packaged WPF app declaring rescap:Capability Name="runFullTrust" runs at mediumIL — the same permissions as a standard desktop app, NOT AppContainer — and may spawn child processes, which is why bundling ffmpeg is legal. Writes to user-profile locations (AppData, Downloads, MyVideos) pass through unvirtualized at mediumIL; the package install folder is read-only.

ViewModels/MainViewModel.Recording.cs writes recordings to %USERPROFILE%\Downloads / SpecialFolder.MyVideos (DefaultRecordFolder(), ChooseRecordFolder() via Microsoft.Win32.OpenFolderDialog), and the whole temp-write-then-move lifecycle stays in one directory. That works only because we are full trust. No broadFileSystemAccess capability is needed, and none should be added.

⚠️ If anyone flips the manifest to appContainer, Directory.CreateDirectory and File.Move start resolving to a per-package virtualized location and output vanishes with no error. A comment at DefaultRecordFolder() is owed when the manifest lands (TASK 48 item 6) — not before; a comment describing a manifest that doesn't exist is worse than none.

Signing — do not buy EV

Microsoft removed the SmartScreen instant-bypass for EV certificates in March 2024. An EV-signed file now accrues reputation exactly like an OV-signed one, so EV ($400+/yr) buys what OV (~$150–300/yr) buys. Signing is not instant regardless — a valid cert stops the malware warning immediately, but "unrecognized publisher" clears only as SmartScreen accrues reputation from real download volume. Cert validity is capped at 460 days (recurring line item) and private keys must live on an HSM or hardware token. The 2026-10-22 ruling is to ship unsigned and accept the "unrecognized publisher" warning until download reputation accrues — buying a certificate is deferred, not rejected. Distribution.md §4.3 was corrected 2026-09-27 (it previously recommended EV).

Distribution and licensing are independent — and the channel decision does not decide licensing

A Store-distributed app may still verify licences with Polar (Store policy 10.8.1 explicitly permits a secure third-party purchase API for non-game PC products), so "which channel?" never implied "which billing?" — route B was a real option. The 2026-10-22 reversal proves the point in the other direction: dropping the Store did not by itself restart Polar, because the 2026-10-08 free-forever ruling (TASK 54) had already removed the paid tier. Polar is therefore dormant, ready if a paid tier ever returns, and nothing in v1 depends on it. The lesson worth keeping: the licensing choice rides on a monetization decision, not on a distribution decision.

⛔ "Only when the user says so" is enforced by Windows, not by us

Camera/mic consent is layered: Store policy 10.6 forbids circumventing OS checks; the Windows desktop-app camera/microphone toggle (Settings → Privacy & security, since Win10 1903) is the gate; hardware lights and tray indicators are the OS's disclosure mechanism. So route everything through the standard MF/DShow/WASAPI paths and we never write that certification ourselves — the OS does it and gets neither our credit nor our blame.

⚠️ Untested: whether that toggle reliably gates a DShow webcam and a capture card. Microsoft's own support page admits desktop apps "might still be able to access your camera or microphone even when these settings are turned off." This is not a certification failure (we declare webcam honestly and use supported APIs) but it is a real user-trust issue — so no privacy-forward marketing copy until it is measured. Guard note added to MARCOM.md.

⛔ Durable invariant: chat is rendered, never stored

TASK 49's profanity filter is local, on-device, opt-in, non-persistent, with a user-supplied word list — never a hardcoded slur list baked into the binary (unpleasant artifact, false positives across dialects, extractable). It must never match the SuperChat amount or any reward field — those are financial data under policy 10.5.5.

The non-persistence half is not just hygiene: it is the load-bearing half of the 11.12 UGC certification answer (mirrored YouTube chat is moderated at industrial scale upstream; ~1.6B comments removed in Q1 2026 alone, child safety the #2 reason at 124.6M). No future feature — chat history, moderation logs, analytics, crash payloads — may quietly break that assumption. If one is ever proposed, the 11.12 position must be re-argued first.

Minors

The operator is 13+ by construction, not by promise: LlamaCasty requires a YouTube account to stream, and YouTube's ToS sets the account floor at 13 (regional variants reach 14; 13–17 need parent/guardian permission; under-13s are on supervised/YouTube Kids accounts). So a COPPA theory premised on collecting PI from under-13s has no purchase against the operator. The residual is viewers — still possible on supervised family accounts — and it is weak: live chat is restricted there, YouTube moderates before we see it, and we persist nothing. COPPA is very unlikely to bind (not child-directed), but note the enforcement trend (FTC Sept 2025 Apitor: operators are responsible for what third-party components collect) — so the answer is a documented inquiry, and ours is "no third-party analytics or ad SDKs, no persisted chat or reward payloads."