docs(commercial): the claim page was never needed, signing waits for revenue, and support is a filter

Four things derived this session superseded what the plan said twenty-four hours earlier,
and the docs are corrected where each one died:

**Licensing needs no server.** Polar's customer-portal endpoints authenticate on the key
itself plus the public organization_id; the org access token is only needed to create
products. Channel binding rides in `conditions: {channel_id}`, re-checked on every
validate — so the "claim page" (a hosted OAuth flow that wrote the UC id onto the key)
and the key database it implied are deleted. Device management, key rotation and
subscription auto-revocation are Polar's; four states, one lease, no callback endpoint.

**Signing is deferred until two subscribers.** It never removes the day-one SmartScreen
warning (EV's instant-bypass died in 2024), so buying it before revenue buys only the
publisher line. Renamed to Azure Artifact Signing, $9.99/mo Basic, and it requires a
paid Azure subscription. Year-one fixed cost drops from ~$135 to ~$15.

**The Store account is free.** Microsoft waived the $19 individual fee and removed the
$99 company fee in May 2026, which makes the Store the only zero-cost route to a
Microsoft-signed install — the free answer to SmartScreen, noted beside the paid one.

**Support is a filter, not a promise.** Email, best effort, no SLA, refunds on request
without argument; no training service, no helpdesk. A repeated question becomes a
training video on @llamacasty, which is content and monetization instead of a ticket.
GitHub issues on the releases repo are the deliberate friction.

Also settled: binaries live in a second Gitea repo (`llamacasty-releases`) push-mirrored
to a public GitHub repo — issues and assets public, code private, one publish direction
(a git mirror carries refs, not GitHub Release assets); `@llamacasty` is
standard-verified with intermediate features, so live streaming, >15 min uploads and
custom thumbnails work today and no SIM or ID is needed; and OAuth comes out of Testing
mode by publishing the app (100-user lifetime cap, no 7-day token death).

TASKS.md now opens with the four-day surge slice — artifact shape, output gate,
entitlement seam, real-machine day — because those are what the first public release
waits on. HANDOFF.md rewritten. MyMistakes.md gains the Polar recipe and the
"verify means three different things" entry. MARCOM.md gains the support→content loop
and the surface roles.
This commit is contained in:
2026-10-31 10:08:05 -07:00
parent 0b41b7db67
commit 57ef9e7a5e
9 changed files with 322 additions and 111 deletions
+62 -29
View File
@@ -36,14 +36,46 @@ dotnet publish ytLive.csproj -c Release -r win-x64 --self-contained true \
settings in `.\data\` instead of `%APPDATA%\ytLlive` (hung off `LayoutPathOverride`). Same binary,
USB-stick behaviour, nothing to uninstall.
## 2. Release
## 2. Where releases live — the public handout repo
**The source repo stays private.** Releases live in a **second Gitea repo,
`llamacasty-releases`**, which is push-mirrored to a **public GitHub repo** (`llamacasty/releases`).
That public repo holds nothing but a README, an issue template, a known-issues page and the release
assets — so the only thing that can ever leak is content authored for publication.
Why it is shaped that way: GitHub has no "public issues on a private repo" switch, and issue
visibility follows repo visibility. An empty public repo gives a public issue tracker, a durable
binary URL for winget, and `releases/latest` for the update notice, without the code going anywhere.
**The mirror carries refs, not release assets.** Git mirrors branches and tags; GitHub Release
uploads live outside the object store, so the zip is uploaded separately.
**Setup, once:**
1. Gitea → new repo `llamacasty-releases` (private) with `README.md`,
`.github/ISSUE_TEMPLATE/bug.yml`, `known-issues.md`.
2. Gitea → that repo → **Settings → Mirror Settings → push mirror** →
`https://github.com/llamacasty/releases.git` + a GitHub PAT. One-way and force-updating, so the
public copy can never drift from what was authored.
3. Create the free GitHub **organisation** `llamacasty` so URLs aren't tied to a personal account.
4. Point the Steam store page's support URL at
`https://github.com/llamacasty/releases/issues/new/choose` — the template chooser, never the
open-issue list.
**Rejected:** a submodule (publishes a pointer, not files), `git subtree split` (three static files
don't justify the history surgery, and one stray private file in the prefix leaks forever), and
serving zips from the droplet (uses the VPS as a CDN and makes the update notice hand-rolled).
## 3. Release
```
publish → zip → SHA-256 → GitHub Release → winget manifest PR → update notice
publish → zip → SHA-256 → gh release create → winget manifest PR → update notice
```
1. **Tag and publish.** `v<major>.<minor>.<patch>` tag; attach `LlamaCasty-<version>-win-x64.zip`
to the **GitHub Release**. Binaries live there, never on the droplet — free CDN, durable URLs,
1. **Tag and publish.** `v<major>.<minor>.<patch>` tag; upload
`LlamaCasty-<version>-win-x64.zip` with
`gh release create v1.0.0 LlamaCasty-1.0.0-win-x64.zip --repo llamacasty/releases
--notes-file RELEASE.md`. Binaries live there, never on the droplet — free CDN, durable URLs,
release notes in the same place, and `wingetcreate` reads `releases/latest` natively.
2. **Record the hash.** `sha256sum` the zip; it goes in the winget manifest and in the release
notes. This is the anti-tamper wall: the official channel delivers exactly this artifact or
@@ -52,38 +84,38 @@ publish → zip → SHA-256 → GitHub Release → winget manifest PR → update
--submit` — it opens the PR against `microsoft/winget-pkgs`. Manifest shape:
`InstallerType: portable`, `InstallerSha256`, `NestedInstallerType` where the zip nests the exe.
First submission is `wingetcreate new`; the identifier is `LlamaCasty.LlamaCasty`.
4. **Update notice.** Write the current version into a small version file on the site (or read
`releases/latest` from the GitHub API). The app shows one line — *"v1.4 is out — `winget upgrade
LlamaCasty.LlamaCasty`"*. **Nothing downloads or executes**: that is Store policy 10.2.2 and a
permanent no (see `product.md`).
4. **Update notice.** Commit the current version into the releases repo (so it arrives in the same
push or a follow-up commit) or read `releases/latest` from the GitHub API. The app shows one
line — *"v1.4 is out — `winget upgrade LlamaCasty.LlamaCasty`"*. **Nothing downloads or
executes**: that is Store policy 10.2.2 and a permanent no (see `product.md`).
5. **Optional mirrors.** Scoop (a small JSON manifest hosted in a bucket) and Chocolatey (a nupkg
with a checksum) — ten minutes each, same audience.
6. **MSIX** stays buildable for the parked Store channel: `scripts/package-msix.sh` →
`artifacts/LlamaCasty.msix`, submitted nowhere until the Individual account exists.
`artifacts/LlamaCasty.msix`, submitted nowhere until the (now free) developer account exists.
## 3. Signing
## 4. Signing — deferred until revenue
**Azure Trusted Signing, ~$9.99/month** — Microsoft-issued, individuals eligible after identity
verification, and it accrues SmartScreen reputation without buying a certificate.
**Unsigned is the launch state.** Artifact Signing is bought at **two subscribers**, not before:
it never removes the day-one warning (EV's SmartScreen bypass died in March 2024), so the only
thing it changes early is the publisher line. Costs and posture:
[`docs/ai/commercial.md`](docs/ai/commercial.md).
```powershell
signtool sign /v /fd SHA256 /tr http://timestamp.acs.microsoft.com /td SHA256 `
/dlib "Azure.CodeSigning.Dlib.dll" /dmdf "metadata.json" LlamaCasty.exe
```
- **⛔ No EV certificate.** Microsoft removed the EV SmartScreen instant-bypass in March 2024, so
EV ($400+/yr) buys exactly what OV (~$150–300/yr) buys — and a bought cert is a 460-day recurring
line item whose key must live on an HSM or hardware token.
- **No certificate at all** is the interim state: ship unsigned and accept the "unknown publisher"
line. winget spares the browser prompt, not the publisher line, so signing is what makes the
channel feel legitimate — the reason it is in the year-one budget.
- **Signing never clears the warning on day one** — only download volume does. Plan for weeks.
- **Azure Artifact Signing** (renamed from "Trusted Signing") — **$9.99/month Basic**, 5,000
signatures, **requires a paid Azure subscription**. Individual identity validation is the slow
part (days to weeks) and must be renewed; start it when the purchase is decided.
- **⛔ No EV certificate, and no certificate with an HSM.** Microsoft removed the EV instant-bypass
in March 2024, so EV ($400+/yr) buys exactly what OV (~$150–300/yr) buys.
- **The free alternative is the Microsoft Store**: the individual developer fee was waived, and
Microsoft signs the MSIX, so a Store install never meets SmartScreen at all.
- **Unsigned costs a warning on every release**, because SmartScreen ties reputation to the file
hash. Signing accrues reputation to the **publisher** — the only exit from that loop.
- **Signing never clears the warning on day one either.** Plan for weeks.
- Secrets: the Trusted Signing profile lives in CI secrets; **never commit** a `.pfx` or
`metadata.json` with credentials. `MSIX_PFX`/`MSIX_PFX_PASSWORD` are for local test installs only.
- Verify every release: `signtool verify /pa /v <file>`, and confirm the hash in the manifest
matches the uploaded zip.
## 4. Release checklist
## 5. Release checklist
- Build clean, 0 warnings (`./scripts/verify.sh`), full test suite green.
- `signtool verify /pa` passes on the shipped exe.
@@ -95,7 +127,7 @@ signtool sign /v /fd SHA256 /tr http://timestamp.acs.microsoft.com /td SHA256 `
- Portable mode: `portable.txt` present → data lands in `.\data\`, deleting the folder deletes the
app's footprint.
## 5. Legal and takedown
## 6. Legal and takedown
- **`LICENSE`** (EULA) forbids redistribution, resale, reverse-engineering and circumvention, and
disclaims warranty. It is what makes a mirror site a licence breach rather than a grey area.
@@ -109,12 +141,13 @@ signtool sign /v /fd SHA256 /tr http://timestamp.acs.microsoft.com /td SHA256 `
hash-pinned delivery, short leases, a free tier good enough that a crack buys nothing, and a
licence ID in About. **Not obfuscation, not a native DLL, not hardware locking.**
## 6. Effort, once
## 7. Effort, once
| Step | Effort |
|---|---|
| Publish profile + portable mode | 0.5 day |
| GitHub Release + hash + version notice | 0.5 day |
| Releases repo + Gitea→GitHub push mirror | 0.5 day |
| `gh release` + hash + version notice | 0.5 day |
| First winget manifest (`wingetcreate new`) | 0.5 day, then ~15 min per release |
| Azure Trusted Signing profile + CI step | 0.5 day |
| Clean-machine + `winget` smoke test | 0.5 day |
| Artifact Signing profile + CI step (deferred) | 0.5 day, at two subscribers |
+22 -21
View File
@@ -1,35 +1,36 @@
# HANDOFF — 2026-10-31 (commercial plan settled in docs; next code unit is the entitlement seam)
# HANDOFF — 2026-10-31 (commercial settled; the surge week starts at the artifact, not the Text layer)
## State
- Branch `main`. Pushed through `c72b61b`; **`90d5140` + this commit are local only** (push at a
checkpoint, not per commit).
- **Commercial plan is settled and written down**: freemium (free = whole app at a 921,600 px /
30 fps profile, stream **and** recording), Pro = $10/mo or $400 lifetime through **Polar** (key
bound to the buyer's YouTube channel ID via a claim page, 2 activations, 7–14 day lease), one
portable zip + **winget** as the primary channel, **Steam and the Store parked**, Azure Trusted
Signing, Founding 200 promo. New single source of truth: [`docs/ai/commercial.md`](docs/ai/commercial.md).
- Docs consolidated this unit: `product.md` (money section → pointer), `decisions.md` (banners +
history), `index.md`, `ai.md`, `README.md`, `Distribution.md` (rewritten to release mechanics,
736 → 120 lines), `TASKS.md` (five new rows), `capture.md` (GPL line falsified by the proprietary
EULA), plus gitignored `MARCOM.md` / `MONETIZATION.md`. Site repo: `AGENTS.md`, `README.md`,
`build-this.md`.
- Scoreboard: built, unchanged from the last handoff (HTTP providers + goal inputs still missing).
- Branch `main`, level with `origin` through `7827d23` + `0b41b7d`; this doc unit is local.
- **Commercial is settled and written down**: freemium (free = whole app at a 921,600 px / 30 fps
profile, stream **and** recording), Pro $10/mo or $400 lifetime through **Polar with no server at
all** — the client calls the customer-portal API with the key, and channel binding rides in
`conditions: {channel_id}`. Signing is **deferred until two subscribers**; the Store account is
now free; binaries live in `llamacasty-releases` push-mirrored to public GitHub.
- **Support policy recorded**: email, best effort, no SLA, refunds on request; no training service;
a repeated question becomes a training video on `@llamacasty`. `MARCOM.md` holds the loop.
- `www.llamacasty.com` resolves and 301s to the apex (CNAME + YunoHost domain + LE cert + nginx
`.d` include). Recipe in `MyMistakes.md`.
## Next action
1. **Entitlement seam** — `IEntitlement` over Polar activate/validate/deactivate, channel-ID match,
2 activations, lease + offline grace, `--activate LCYT-…` CLI, fail closed. One Good Dog test.
2. Then the **OutputProfile gate** (one profile consumed by both the stream encoder and the
recorder; greyed tier entries; never downgrade mid-stream), the licence UI, and system messages.
3. Scoreboard HTTP providers + goal inputs whenever the creator queues them.
**Surge week, day 1: artifact shape.** `LlamaCasty.exe` rename (three pack URIs:
`MainWindow.xaml:14`, `OverlayHost.xaml:96`, `RenameRecordingDialog.xaml:5`), `scripts/publish.sh`
(self-contained win-x64, no test artifacts), `portable.txt` via `LayoutPathOverride`. It ends when
the zip runs on a clean profile.
Then: OutputProfile gate + TASK 53 geometry tests → entitlement seam + system messages → licence UI
and a real test broadcast. Day 5 if all four land: the visibility picker.
## Landmines
- **Nothing may gate a feature.** The only paid delta is the output profile. `ShowBrandFlash` stays
`false` and entitlement must never enable it.
- **The site publishes the model, never the numbers** — and its CTA ("Coming soon to the Microsoft
Store") stays until the creator says the download exists.
- **The org access token never ships.** `organization_id` is public; the token is not. No claim
page, no callback endpoint, no key database — `conditions` does the binding.
- **The site publishes the model, never the numbers**, and its CTA stays "Coming soon to the
Microsoft Store" until the creator says the download exists.
- **No self-updater, by policy**: `winget upgrade` + a version banner. `StorePackagingTests` asserts it.
- Pre-existing: the **full test suite hangs** (WASAPI teardown). Per-class runs are the way.
- Not every hex is a theme colour — `BorderColorPalette`/chat defaults are USER DATA; on-stream
+55
View File
@@ -111,6 +111,61 @@ survives YunoHost regenerating the `.conf`; never edit the `.conf` itself.
%{redirect_url}' https://www.llamacasty.com/privacy.html` → `301 https://llamacasty.com/privacy.html`;
the cert must read `CN=www.llamacasty.com`, issuer Let's Encrypt.
### LICENSING WITHOUT A SERVER — POLAR'S CUSTOMER-PORTAL API (RECIPE, 2026-10-31)
**Problem:** the plan carried a "claim page" — a hosted OAuth flow that wrote the buyer's channel ID
onto the licence key — plus an implied server to hold keys. Both were invented from an assumption
that binding needs a browser.
**It does not.** Polar's customer-portal endpoints authenticate on the **key itself** plus the
`organization_id` (a public identifier, safe in the binary). The **org access token is only needed
to create products** and must never ship.
```bash
# activate (label is required; conditions are re-checked on every validate)
POST https://api.polar.sh/v1/customer-portal/license-keys/activate
{ "key": "LCYT-…", "organization_id": "c05fb364-…", "label": "DESKTOP-NAME",
"conditions": { "channel_id": "UC…" } }
# validate on launch + daily, with the SAME conditions object
POST https://api.polar.sh/v1/customer-portal/license-keys/validate
{ "key": "LCYT-…", "organization_id": "c05fb364-…", "activation_id": "…",
"conditions": { "channel_id": "UC…" } }
```
- `limit_activations` on the benefit = the device cap (2). Polar's **customer portal** already does
deactivation, key rotation and "where is my key", so build no UI for it.
- A **subscription key is auto-revoked when the subscription ends** — that is what makes lapse a
real state; check `status`, not `expires_at`.
- ⚠️ `conditions` is **agreement, not cryptography**: a patched client can send someone else's ID.
Accepted — the free tier is the defence, not the check.
- ⚠️ There is **no push to a desktop app**: delivery is email or the customer portal. Auto-delivery
needs a webhook plus an endpoint *you* host, holding keys. Not worth it for one copy-paste.
### YOUTUBE "VERIFIED" — THREE DIFFERENT THINGS (2026-10-31)
`youtube.com/verify` saying "your account is verified" answers only the first tier. The tiers live
in Studio → Settings → Channel → **Feature eligibility**:
| Tier | Requires | Gives |
|---|---|---|
| Standard | nothing | uploads, playlists, posts (low daily limits) |
| **Intermediate** | phone verification | >15 min videos, **live streaming on a computer**, custom thumbnails |
| Advanced | **channel history** or ID/video verification | higher daily limits, clickable description links, chapters, pin comments, monetization application |
- **Live streaming is intermediate, not advanced** — a verified channel can stream; the API's
"live streaming not enabled" error means the *user's* channel is unverified, so the app must map
it to "verify at youtube.com/verify".
- **2 channels per phone number per year**, 365-day reset; VoIP/Google Voice numbers are refused.
That is the "used too many times" wall, and there is no appeal.
- Verification is **sticky** — it survives the phone being removed, which is why an old account can
read "verified" while no number on it will pass today.
- Advanced via **ID/video verification** is a privacy trade with a 24 h review; channel history is
free and usually sufficient within ~2 months of normal activity. Prefer history.
- ⚠️ Only the **primary owner** can do ID/video verification, and brand-account managers hit
*"Advanced YouTube features aren't available for this account"*. If ID verification is ever
wanted on a personal channel, do it **before** converting to a brand account.
### CONVERTING A MEDIAPIPE .tflite (WITH CUSTOM OPS) TO ONNX — AND PROVING IT (RECIPE, TASK 51)
**Problem:** MediaPipe segmentation models end in the custom op `Convolution2DTransposeBias`;
+25 -8
View File
@@ -7,11 +7,27 @@
## Next up
**TASK 3·17 — the Text layer.** The `TextOverlay` source type exists as a placeholder and nothing
else does: `AddSource` creates it 0×0, all four compositor paths `continue` past it, `FramePump`'s
hash skips it, it is not draggable, and it has no text/font/colour properties or persistence.
The unit: text content, font family/size/weight/style, colour, alignment, drag/resize, persisted;
renderer mirrors `Services/ChatBoxRenderer.cs`. One Good Dog test.
**The surge week (4 days, in this order).** Not every row below — this slice only, because it is
what the first public release is waiting on:
1. **Artifact shape** — `LlamaCasty.exe` rename (three pack URIs), `scripts/publish.sh`,
portable mode. Ends with a zip that runs on a clean machine.
2. **OutputProfile gate** — ≤921,600 px / ≤30 fps for the stream **and** the recorder, greyed tier
entries, plus the TASK 53 geometry tests that pin every tier.
3. **Entitlement seam** — `IEntitlement` against Polar's customer-portal API with a fake for tests,
`--activate` parsing, system messages.
4. **Real-machine day** — licence UI skeleton, a clean-profile install, a test broadcast.
Detail: [`commercial.md`](docs/ai/commercial.md) → Output profile / Entitlement;
[`Distribution.md`](Distribution.md). Day 5, if the four go well: the visibility picker (a hard
prerequisite for any *public* build).
**TASK 3·17 — the Text layer** stays the first product row after the surge. The `TextOverlay` source
type exists as a placeholder and nothing else does: `AddSource` creates it 0×0, all four compositor
paths `continue` past it, `FramePump`'s hash skips it, it is not draggable, and it has no
text/font/colour properties or persistence. The unit: text content, font family/size/weight/style,
colour, alignment, drag/resize, persisted; renderer mirrors `Services/ChatBoxRenderer.cs`. One Good
Dog test.
## Queue
@@ -33,11 +49,12 @@ renderer mirrors `Services/ChatBoxRenderer.cs`. One Good Dog test.
| 50 | ☐ | One-button publish of the local recording (resumable upload) | [`task-50`](TASKS/task-50-publish-recording.md) |
| 53 | ☐ | Output-format contract: pin every tier with a geometry test, prove 9:16 end-to-end, recorded file matches output | [`task-53`](TASKS/task-53-output-formats.md) |
| 54 | 🔶 | Freemium: EULA + privacy shipped; **no licence path built** — needs the entitlement seam, the output-profile cap and the licence UI below; site copy still says free forever | [`task-54`](TASKS/task-54-marcom-free-merch.md) |
| — | ☐ | **Entitlement seam** — `IEntitlement` over Polar activate/validate/deactivate, channel-ID match, 2 activations, 7–14 day lease, `--activate` CLI | [`commercial`](docs/ai/commercial.md) |
| — | ☐ | **Entitlement seam** — `IEntitlement` over Polar's customer-portal API (org ID public, org token never in the app), `conditions: {channel_id}`, 2 activations, 7–14 day lease, `--activate` CLI. **No claim page, no server** | [`commercial`](docs/ai/commercial.md) |
| — | ☐ | **OutputProfile gate** — ≤921,600 px / ≤30 fps for stream **and** recording, greyed tier entries, live re-resolve, never mid-stream | [`commercial`](docs/ai/commercial.md) |
| — | ☐ | **Licence UI** — claim page, four states, 7/1/0-day notices, portal link, licence ID in About | [`commercial`](docs/ai/commercial.md) |
| — | ☐ | **Licence UI** — key entry + four states, 7/1/0-day notices, portal link, licence ID in About | [`commercial`](docs/ai/commercial.md) |
| — | ☐ | **System messages** — `SystemMessage` + `MessageCenter`, strip above the dock, dedupe by key, short history, never composited | [`ui-surface`](docs/ai/ui-surface.md) |
| — | ☐ | **Packaging** — portable zip, GitHub Release, winget manifest, Azure Trusted Signing | [`Distribution.md`](Distribution.md) |
| — | ☐ | **Packaging** — rename, `scripts/publish.sh`, portable mode; then `llamacasty-releases` repo + Gitea→GitHub push mirror, `gh release create`, winget manifest. Signing **deferred** until two subscribers | [`Distribution.md`](Distribution.md) |
| — | ☐ | **Training-video series** — capture the first 8–10 recurring questions as videos on `@llamacasty`; each is documentation, channel content and monetization at once | MARCOM → support→content loop |
| 9·5 | ☐ | Webcam identity key reconciliation | [`task-09`](TASKS/task-09-live-stream-management.md) |
| 14 | 🔶 | Creator feedback batch — branch 3+ | [`task-14`](TASKS/task-14-creator-feedback-batch.md) |
| — | ☐ | Small: `StreamConfig` defaults (`TargetBitrate=6000`, `1920x1080`) are stale — the dropdown drives `StreamHealth` | this line |
+87 -37
View File
@@ -1,8 +1,10 @@
# Commercial: pricing, entitlement, channels
# Commercial: pricing, entitlement, channels, support
The single source of truth for what LlamaCasty charges, what a licence entitles, and how it
ships. Read before any pricing, licensing, distribution, storefront or promo work. Cause of death
for every retired path: [`decisions.md`](decisions.md).
The single source of truth for what LlamaCasty charges, what a licence entitles, how it ships and
what support it does and does not include. Read before any pricing, licensing, distribution,
storefront, support or promo work. Cause of death for every retired path:
[`decisions.md`](decisions.md). Release mechanics (build, zip, mirror, winget):
[`Distribution.md`](../../Distribution.md).
## Model
@@ -44,63 +46,111 @@ The cap is a pixel budget plus a frame rate, not the word "720p":
## Entitlement
**Polar is the merchant of record and the only source of truth.** It takes the money, remits
VAT/GST, handles refunds and chargebacks, and holds the keys. Nothing of ours sits in the middle.
VAT/GST, handles refunds and chargebacks, and holds the keys. **There is no server of ours in the
loop** — no claim page, no callback endpoint, no key database.
- **Key** — `LCYT-` prefix; org `c05fb364-…` and the endpoint are in `CREDENTIALS.md`. No
- **Key** — `LCYT-` prefix; org `c05fb364-…` and the endpoints are in `CREDENTIALS.md`. No
product is created yet.
- **Bound to the creator's YouTube channel ID** (`UC…` from `channels.list?mine=true`) — the
**active** brand account's channel, never the `@handle`. Written once through a **claim page**
after purchase (OAuth), not prefilled at checkout: the buyer's browser may be signed into a
different Google account, and that is manual reconciliation forever.
- **Two activations**, any machine, one channel. A second channel is a second key. Two keys for
one channel is never sold — it doubles the support surface and invites "give one to a friend".
- **The client talks to Polar directly.** The customer-portal endpoints authenticate on the **key
itself** plus the `organization_id` (a public identifier). The **org access token must never
ship in the app** — it is only needed to create products, not to activate them.
- **Bound to the buyer's YouTube channel ID** (`UC…` from `channels.list?mine=true` — the
signed-in channel, never the `@handle`) through Polar's **`conditions`**, not through a browser
flow: `activate` is called with `conditions: { "channel_id": "UC…" }`, and every later `validate`
passes the same object. Polar compares them server-side, so the binding is enforced without us
hosting anything.
- **Two activations**, any machine, one channel (`limit_activations: 2` on the benefit). A second
channel is a second key. Two keys for one channel is never sold — it doubles the support surface
and invites "give one to a friend".
- **Runtime rule:** Pro requires `licence.channelId == connected.channelId`. Equal → Pro.
Anything else → free tier, working normally, with *"This licence belongs to another channel"*
and a link to the Polar customer portal. Never an error wall, never a blocked stream.
- **Lease, not a permanent cache:** validate on launch and daily; **7–14 days offline grace**;
no network past grace = free tier until Polar answers. Never lock a paying user out on a DNS
failure.
- **Four states, and nothing else:** valid → Pro · expired → free with a banner · activation
limit reached → "manage devices" portal link · mismatch or offline → free, explained.
- **Device management is Polar's** — the customer portal already lists and deactivates
activations, so we build no UI for it. A subscription key is **auto-revoked when the
subscription ends**, which is what makes a lapse a real state on the monthly SKU.
- **Four states, and nothing else:** valid → Pro · expired/revoked → free with a banner ·
activation limit reached → "manage devices" portal link · mismatch or offline → free, explained.
- **CLI activation** — `--activate LCYT-…` so silent and fleet installs never meet a dialog.
Interactive entry in Settings is the secondary path.
- **Notice cadence** — 7 days out, 1 day out, and on the day, inline in the go-live panel with a
date and a portal link. Recording gets the banner only. Never modal, never blocking a Start,
never orange (orange is the dev-only label colour).
⛔ **`conditions` is agreement, not cryptography.** A patched client can send someone else's
channel ID. That is accepted: the free tier is the defence, the licence is a lease, and a
determined crack buys little. Do not propose a signed token, a hardware fingerprint or a server
to "fix" it.
## Channels
**One artifact, three ways to get it.** A self-contained single-file `LlamaCasty.exe` plus the
**One artifact, several ways to get it.** A self-contained single-file `LlamaCasty.exe` plus the
bundled `ffmpeg\` folder, zipped. No .NET install, no admin, no installer framework. Trimming
stays off (WPF reflection), so expect ~110–150 MB.
1. **Direct download** — `llamacasty.com` links the zip; the binaries live on **GitHub Releases**,
not the droplet (free CDN, durable URLs, release notes).
2. **winget — the primary channel.** Manifest in `microsoft/winget-pkgs`, `InstallerType:
1. **Direct download** — `llamacasty.com` links the zip. **Binaries live in a second Gitea repo
(`llamacasty-releases`), push-mirrored to a public GitHub repo**, so the source repo stays
private and the handout repo contains nothing but a README, an issue template and the release
assets. The mirror carries refs, not GitHub Release assets — the zip is uploaded with
`gh release create`. Mechanics: [`Distribution.md`](../../Distribution.md).
2. **winget — the primary install path.** Manifest in `microsoft/winget-pkgs`, `InstallerType:
portable`, pinned SHA-256. That hash pin is the anti-tamper wall: the official channel delivers
exactly the published artifact or fails. One `wingetcreate update` PR per release. Optional:
Scoop and Chocolatey manifests over the same zip.
3. **Steam — parked at $100.** Lifetime Pro as DLC only (Steam has no subscriptions), page public
two weeks before release, 30% cut, and price parity with the site.
4. **Microsoft Store — parked.** $19 Individual account; the only storefront that bills a
subscription natively and installs without a warning. Bans self-updaters, so a Store build
two weeks before release, 30% cut, and price parity with the site. It is the only channel with
discovery *and* an install that never meets SmartScreen, and it updates itself — which is why it
stays in the plan despite the cut.
4. **Microsoft Store — parked, and now free.** Microsoft waived the **$19** individual developer
fee (and removed the **$99** company fee in May 2026). It is the only storefront that bills a
subscription natively *and* the only zero-cost route to a Microsoft-signed install with no
SmartScreen warning, because Microsoft signs the MSIX. Bans self-updaters, so a Store build
ships without one.
**Not channels, ever:** Patreon or any subscription platform of our own, paywalled early access,
a paid beta, a Discord. Support is a public GitHub issue tracker. Content is the funnel; the
channel is only the checkout.
a paid beta, a Discord. Content is the funnel; the channel is only the checkout.
**Portable mode:** a `portable.txt` marker beside the exe keeps the layout DB and settings in
`.\data\` instead of `%APPDATA%\ytLlive` — the USB-stick case, hung off `LayoutPathOverride`.
It is the reason the zip survives even if Steam becomes the primary channel.
## Signing
## Signing — deferred until there is revenue
- **Azure Trusted Signing, ~$9.99/month**, is the intended certificate: Microsoft-issued, accrues
SmartScreen reputation, enrols individuals after identity verification.
- **No EV certificate** — Microsoft removed the SmartScreen instant-bypass in March 2024.
- Until it is in place, builds ship unsigned and the "unknown publisher" line is accepted. winget
spares the browser prompt, not the publisher warning.
- Every release: same signing pass, SHA-256 in the manifest, licence ID visible in About.
- **Not bought at launch.** Artifact Signing is listed in the costs as **deferred**: it is paid for
out of Pro revenue, and the trigger is **two subscribers**.
- **What it actually buys:** a named publisher line, fewer antivirus false positives on a ~150 MB
self-extracting exe, and tamper evidence for hand-downloaded zips. It does **not** remove the
SmartScreen warning on day one — Microsoft deleted the EV instant-bypass in March 2024, so a new
certificate has zero reputation and clears only with download volume.
- **Azure Artifact Signing** (renamed from "Trusted Signing"), **$9.99/month Basic**, up to 5,000
signatures, **requires a paid Azure subscription**. Individual identity validation exists and
must be renewed before it expires; it is the slow (days-to-weeks) part, so start it *when the
purchase is decided*, not before.
- **Reputation accrues to the publisher, not the file.** Unsigned means the warning on **every**
release, forever, because SmartScreen ties reputation to each file hash. Signing is the only exit
from that per-release tax.
- **The free alternative is the Store** (see Channels 4): a free developer account gets
Microsoft-signed installs with no certificate of ours.
- **No EV certificate, ever** — $400+/yr for what OV buys, and the bypass is gone.
- Every signed release: same pass, SHA-256 in the winget manifest, licence ID visible in About.
## Support
**Email. Best effort. No SLA. Refunds granted on request, without argument.**
- **There is no training service.** "I want people to succeed" is not "I will train them". The app
teaches itself — one-click go-live, five scenes, nothing to configure. A how-to question means
the app or its docs fell short; the answer is a fix or a video, not a conversation.
- **No helpdesk.** No ticketing system, no live chat, no onboarding calls, no hand-holding.
- **A repeated question is a video, not a ticket** — see `MARCOM.md` → support → content loop. The
training channel is the support surface.
- **The issue tracker is a filter, deliberately.** Bugs go to the public releases repo on GitHub,
which requires an account; Steam Discussions stay pinned and unread; Announcements carry known
issues and release notes. Steam refunds are Valve's own (2 h / 14 days) and cost us nothing.
- **Nobody is locked out over a licence.** A billing hiccup drops the app to the free profile with
a banner and a portal link — never a blocked stream, never a modal.
## Founding 200 promo
@@ -122,16 +172,16 @@ Cash cost **$0**; it is priced in support time and forgone list revenue.
| Item | Cost |
|---|---|
| Azure Trusted Signing | $9.99/month (~$120/yr) |
| Domain | ~$15/yr |
| Droplet | $0 — serves the existing site |
| GitHub Releases, winget, Scoop, Chocolatey | $0 |
| GitHub public repo (releases + issues), Gitea, winget, Scoop, Chocolatey | $0 |
| Polar | 5% + 50¢ per sale → $10 nets $9.00, $400 nets $379.50 |
| Azure Artifact Signing — **deferred** | $9.99/month (~$120/yr), bought at two subscribers |
| Steam Direct (optional) | $100 once, recoupable after $1,000 AGR |
| Microsoft Store (optional) | $19 once |
| Microsoft Store (optional) | **$0** — the individual fee was waived |
Fixed year one ≈ **$135**, and the same every year after. **Break-even is two subscribers**
(15 subscriber-months ≈ $135); one $400 lifetime sale covers nearly three years.
Fixed year one ≈ **$15** while unsigned, ≈ **$135** once signing starts. **Break-even is two
subscribers** (15 subscriber-months ≈ $135); one $400 lifetime sale covers nearly three years.
## Anti-piracy posture
@@ -140,8 +190,8 @@ pointless and a leak that is traceable — not an unbreakable binary.
- **The free tier is the real defence.** If the only delta is pixels above 921,600, a cracked
build buys almost nothing and forfeits updates, support and the publisher's signature.
- **Hash-pinned delivery + signed builds** — a patched build cannot arrive through winget, and a
forged one is not signed as us.
- **Hash-pinned delivery, signed once revenue exists** — a patched build cannot arrive through
winget, and a forged one is not signed as us. Until signing starts, the SHA-256 pin is the wall.
- **Short leases** — a revoked or refunded key dies inside the grace window.
- **Licence ID in About** — a leaked build traces back to a purchase.
+30 -2
View File
@@ -2,8 +2,36 @@
One dated line per decision that **no longer governs**, plus the reason it died. Read only when
you are about to re-open one of them. **Live commercial rules (prices, entitlement, channels,
signing, promo) are in [`commercial.md`](commercial.md)**; the other live rules live in the
subsystem files. This is the graveyard with cause of death.
signing, support, promo) are in [`commercial.md`](commercial.md)**; the other live rules live in
the subsystem files. This is the graveyard with cause of death.
## Commercial mechanics — died 2026-10-31 (same week they were written)
- **The licence "claim page" is dead.** The binding was going to be an OAuth web page that wrote
the buyer's `UC…` channel ID onto the key, because we assumed binding needed a browser. Polar's
**`conditions`** object does it natively: the client calls `activate` with
`conditions: { channel_id }` and repeats it on every `validate`. **No claim page, no hosted
endpoint, no key database, no OAuth redirect URI.**
- **"Signing is a year-one cost" is dead.** Artifact Signing is **deferred until two subscribers**
— it never removes the day-one SmartScreen warning, so buying it before revenue buys nothing but
the publisher line. It is also now called **Azure Artifact Signing** and **requires a paid Azure
subscription**.
- **"The Store developer account costs $19" is dead.** Microsoft waived the individual fee (and
removed the $99 company fee in May 2026), which makes the Store the **only zero-cost route to a
Microsoft-signed install** — the free answer to SmartScreen.
- **"Binaries on GitHub Releases" was half a plan.** There was no GitHub presence at all (the only
remote is Gitea). The route is a separate **`llamacasty-releases`** Gitea repo push-mirrored to a
public GitHub repo: issues and assets public, code private, one publish direction.
## Support — decided 2026-10-31
- **No training service, no helpdesk, no SLA.** Email, best effort, refunds on request without
argument. A repeated question becomes a training video on `@llamacasty`, which is content and
monetization rather than a ticket. See `commercial.md` → Support and `MARCOM.md` → support →
content loop.
- **Rejected as support surfaces:** a paid Discord, live chat, onboarding calls, a ticketing system.
GitHub issues on the releases repo is the deliberate filter — an account is enough friction to
keep the list short.
## Monetization
+1 -1
View File
@@ -14,7 +14,7 @@ Read the file whose row matches the task. There is no required reading order.
| [`output.md`](output.md) | The bundled `ffmpeg.exe` and the encoder subprocess (args, probe order, seams), local recording (REC/ON-AIR, filenames, private-broadcast close-out), and the WASAPI capture → filter → mix → named-pipe audio chain (TRAX, sync offset, ducking, limiter) | Touching ffmpeg args/bundling, encoder or recording behaviour, REC/ON-AIR, the audio mix and its meters, or loudness/sync problems |
| [`environment.md`](environment.md) | LAN hosts/IPs/SSH and the Gitea push recipe, the Windows `dotnet.exe` build + run commands, the test suite map, and the hermetic real-`MainWindow` test rule | Before building, running, adding a test, or reaching any box on the LAN |
| [`product.md`](product.md) | Brand/naming split, AI transparency, the licensing prohibitions, the freemium posture, auth/account assumptions, and packaging behaviour | Branding, naming, packaging, or a legal/third-party obligation |
| [`commercial.md`](commercial.md) | The live commercial policy: model and prices, the output-profile cap (stream **and** recording), Polar entitlement and channel binding, the channels (winget, Steam, Store), signing, the Founding 200 promo, costs, anti-piracy posture | Any question about money: pricing, licence keys, what free gets, how it ships, what a sale costs |
| [`commercial.md`](commercial.md) | The live commercial policy: model and prices, the output-profile cap (stream **and** recording), Polar entitlement via `conditions` (no server, no claim page), the channels (releases repo → winget → Steam → Store), signing (deferred), the Founding 200 promo, costs, **support policy**, anti-piracy posture | Any question about money or support: pricing, licence keys, what free gets, how it ships, what a sale costs |
| [`youtube-api.md`](youtube-api.md) | Go-live ordering + the broadcast-binding fix, the private-only lock, health parsing, end-stream pre-flight, Test mode, and chat-insert shape | Touching go-live, broadcast lifecycle, health polling, streaming close-out, or the chat service |
| [`overlays.md`](overlays.md) | The social bar, the native alert box (six animations, alert clip, ticker) and the chat box conventions — everything composited over the frame | Adding or changing anything drawn on top of the output |
| [`decisions.md`](decisions.md) | Retired paths with cause of death: the paid-tier history and pricing, the free-forever era, Store IAP, the Store ordering, rejected monetization and anti-piracy ideas, and the permanent out-of-product list | You are about to re-open a decision that was already settled |
+24 -13
View File
@@ -192,12 +192,15 @@ YouTube" button; going live is unreachable until an account is connected.
## Windows packaging & distribution (winget primary; Steam + Store parked)
Live channel policy, the artifact and signing costs: [`commercial.md`](commercial.md). Build
mechanics (publish flags, GitHub Releases, the winget manifest): [`Distribution.md`](../../Distribution.md).
Retired routes and cause of death: [`decisions.md`](decisions.md).
mechanics (publish flags, the releases repo mirror, the winget manifest):
[`Distribution.md`](../../Distribution.md). Retired routes and cause of death:
[`decisions.md`](decisions.md).
**The channels, in order:** direct download from `llamacasty.com` (binaries on GitHub Releases) →
**winget**, the primary install path → Steam (parked, $100, lifetime Pro as DLC only) → Microsoft
Store (parked, $19 Individual account — the only storefront that bills a subscription natively).
**The channels, in order:** direct download from `llamacasty.com` (binaries in a separate
`llamacasty-releases` repo, push-mirrored to public GitHub) → **winget**, the primary install path →
Steam (parked, $100, lifetime Pro as DLC only; the only channel with discovery *and* an install
that never meets SmartScreen) → Microsoft Store (parked, **free** account since the $19 individual
fee was waived — the only zero-cost route to a Microsoft-signed install).
**Consequences that are architecture, not packaging detail:**
@@ -205,8 +208,8 @@ Store (parked, $19 Individual account — the only storefront that bills a subsc
2026-10-22 (TASK 48 slice 3) because it downloaded *and executed*, which Store policy 10.2.2
forbids — and the same rule would close the Store for good if it came back. Updates arrive
through the package manager (`winget upgrade`) plus an in-app "newer version exists" banner read
from a version file on GitHub Releases. `ytLive.Tests/StorePackagingTests.cs` must keep asserting
that no self-updater returns.
from a version file in the releases repo. `ytLive.Tests/StorePackagingTests.cs` must keep
asserting that no self-updater returns.
- **The MSIX package stays buildable** 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
@@ -249,15 +252,23 @@ trust. No `broadFileSystemAccess` capability is needed, and none should be added
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 — Azure Trusted Signing, never EV
### Signing — deferred until revenue; Azure Artifact Signing, never EV
Microsoft **removed the SmartScreen instant-bypass for EV certificates in March 2024**, so EV
($400+/yr) buys what OV (~$150–300/yr) buys; both need an HSM or hardware token and renew inside
**460 days**. The 2026-10-31 ruling is **Azure Trusted Signing (~$9.99/month)**: Microsoft-issued,
individuals eligible after identity verification, and it accrues reputation without a certificate
purchase. Until the profile exists, builds ship unsigned and the "unknown publisher" line is
accepted. Signing never clears the warning on day one either way — only download volume does.
Signing steps: [`Distribution.md`](../../Distribution.md).
**460 days**. The 2026-10-31 ruling was **Azure Trusted Signing (~$9.99/month)**; the later ruling
the same week is that it is **deferred until two subscribers exist**, because it never removes the
warning on day one — only download volume does — and the storefront alternative is now free.
- **Renamed**: it is **Azure Artifact Signing** now. Basic is $9.99/month, 5,000 signatures, and it
**requires a paid Azure subscription**; individual identity validation exists and must be
renewed, and it is the slow (days-to-weeks) part — start it when the purchase is decided.
- **Unsigned is a per-release tax**: SmartScreen ties reputation to the file hash, so an unsigned
product warns on **every** release, forever. Signing is the only exit from that loop.
- **The free exit is the Microsoft Store** — the individual developer account fee was waived, and
Microsoft signs the MSIX, so a Store install never meets the wall.
- **Never EV, never a certificate with an HSM**, for this product.
Signing steps: [`Distribution.md`](../../Distribution.md). Costs: [`commercial.md`](commercial.md).
### Distribution and licensing are independent — the channel never decides billing
+16
View File
@@ -256,6 +256,22 @@ These are the hard facts behind every decision. Full list in `TASKS.md`.
## Auth & session (YouTube OAuth)
- **Publishing status, not phone verification, is what kills sessions.** An OAuth project in
**Testing** with user type *External* expires every authorization **and its refresh token after
7 days** — so every user re-signs in weekly. **Publish app** (→ *In production*) removes that
expiry. It does **not** require Google's verification review, but an unverified production app
shows the "unverified app" warning and carries a **100-new-user cap for the lifetime of the
project, which cannot be reset** — so publish when strangers are expected, not while testing.
Verification review only removes the warning and the cap. Source:
<https://support.google.com/cloud/answer/15549945>, and `commercial.md` → Entitlement is unrelated.
- **A channel must be verified before it can stream.** YouTube's feature tiers:
*standard* (nothing) → *intermediate* (**phone verification**: >15 min uploads, **live streaming
on a computer**, custom thumbnails) → *advanced* (channel history **or** ID/video verification:
higher daily limits, clickable description links, chapters, pin comments, monetization
application). Live streaming is **intermediate**, not advanced — a user without it gets a
live-streaming-disabled error from the API. Map that error in the go-live path to one sentence:
*"Verify your channel at youtube.com/verify, then try again — it takes a minute."* A raw Google
error here reads as "the app is broken". Source: <https://support.google.com/youtube/answer/9890437>.
- `Helpers/OAuthCredentials.cs` holds the real ClientId/ClientSecret. Auth is complete and the
session **persists via Windows DPAPI** (`Helpers/TokenStore.cs` → `%APPDATA%\ytLlive\ytLlive.auth`,
CurrentUser scope), reloaded best-effort at startup with a proactive refresh of a near-expiry