The DESIGN.md type standard needs the fonts actually present — the machine's installed faces are not a design system. Four Inter weights (400/500/600/700) and two JetBrains Mono weights (500/600) ship as WPF resources from Assets/Fonts; the token families resolve them with the `/Assets/Fonts/#Family` pack form, so nothing depends on an install or a network fetch. Licensing (recorded in docs/ai/product.md): OFL-1.1 is a free licence but is NOT GPL-compatible in the linking sense. These are separate .ttf files loaded through WPF's font stack — an aggregate, not linked code — so they coexist with the GPL-3.0 app. Obligations carried: copyright line in THIRD-PARTY-NOTICES.txt (new items 13-14), the OFL text beside the files (Assets/Fonts/OFL-*.txt), no modification, no reserved-name reuse. Guard: BundledFontsTests — files exist and are real binaries, the csproj ships them, the theme uses pack URIs (a bare family name would silently fall back), the notice and both OFL texts are present, and no third font family sneaks in. Pending visual confirmation: the first launch should render panel text in Inter and telemetry in JetBrains Mono.
363 lines
26 KiB
Markdown
363 lines
26 KiB
Markdown
# 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
|
||
|
||
**The UI design standard is [`DESIGN.md`](../../DESIGN.md)** — colours, type scale, spacing, radii
|
||
and elevation, all normative, implemented by `Themes/Tokens.xaml`. Accent blue `#38bdf8` is the only
|
||
interactive accent; red `#ff5451` means live/record and nothing else. The old brand red `#e94560` is
|
||
retired from app chrome (the wordmark/logo artwork is unchanged).
|
||
|
||
Marketing palette, official assets and copy live in `MARCOM.md` (gitignored — read it before any
|
||
branding or marketing work).
|
||
|
||
### 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 a font whose licence was not checked.** The two bundled families — **Inter** and
|
||
**JetBrains Mono** — are **SIL Open Font License 1.1** (approved 2026-10-30 with the DESIGN.md
|
||
standard). OFL is a free licence but is **not** GPL-compatible in the linking sense; they ship as
|
||
separate `.ttf` files loaded through WPF's font stack, which is aggregation, so the two coexist.
|
||
Obligations: keep the copyright line in `THIRD-PARTY-NOTICES.txt`, ship the `OFL-*.txt` text
|
||
beside the files in `Assets/Fonts/`, and never modify the fonts or reuse their reserved family
|
||
names for a modified version. Any new typeface needs this same check before it ships.
|
||
- **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 — free forever, no watermark
|
||
|
||
**All features are free and ungated** (creator ruling 2026-10-08, TASK 54): no feature lock, no paid
|
||
tier, no watermark. Money comes from gratuity and merch, not gating. **No licence chatter reaches a
|
||
user** — no keys, no expiry, no licence UI. Support is email + GitHub issues; an in-app bug reporter
|
||
is out of product. `IsPremium` gates nothing in v1.
|
||
|
||
**The branding credit is OFF by default.** `MainViewModel.ShowBrandFlash` (internal static, default
|
||
`false`) is the master switch; `BrandFlashEnabled => ShowBrandFlash && !IsPremium` feeds the
|
||
presenter. The code is kept, not deleted — creator ruling: *"don't delete it, just make it
|
||
conditionally displayed."* `BrandFlashOutputTests` opts in by setting the switch before constructing
|
||
a `MainWindow`; `Credit_IsOffByDefault_EvenOnTheFreeTier` guards the default.
|
||
|
||
If it is ever enabled, the mechanics that matter:
|
||
|
||
- Full-frame neon "made with LlamaCasty!" at a **random position per presentation**, held **2s**
|
||
(500ms in / 1000ms full / 500ms out), first ~5s after start then every `rand(30s)+30s`.
|
||
- Published to the frame pump and the preview from the **same** `VideoFrame`, so preview and
|
||
broadcast cannot drift. Deliberately **not** folded into `SceneGraph.GetBakedBase` — the bake
|
||
cache compares element ids and would freeze the credit in place.
|
||
- **App-lifetime cadence:** the presenter is `Start()`ed once in the `MainViewModel` ctor, not per
|
||
go-live, because go-live and local recording share one `FramePump`. `IsPremium`'s setter therefore
|
||
restarts the cadence timer when re-enabling (`IsCadenceTimerEnabled` is the test seam).
|
||
- **It composites into the recording and the stream** (reversed 2026-09-26 — the free tier must be
|
||
honest advertising). Test arithmetic trap: `Advance(5.1)` in one call blows through the 2s window
|
||
(it both opens and ages the presentation) — step in 1/30s increments like `BrandFlashOutputTests`.
|
||
- **A mid-credit downgrade** cuts the advertisement on the next tick: the presenter re-checks its own
|
||
`Enabled` gate every frame rather than letting the credit finish.
|
||
|
||
Pricing, billing, the Store IAP plan and the rejected alternatives are history →
|
||
[`decisions.md`](decisions.md).
|
||
|
||
|
||
**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.
|
||
|
||
|
||
## 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 (direct + Steam; Store dropped 2026-10-22)
|
||
|
||
**The Microsoft Store route is dead** — a brand on the publisher line needs a Company account and
|
||
a legal entity. Cause of death and the abandoned routes: [`decisions.md`](decisions.md).
|
||
|
||
**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 [`output.md`](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."
|
||
|