TASK 48 slice 3: the self-updater is deleted - the Store updates the app

The creator's ruling (2026-09-27) is Store MSIX + Store IAP, so Microsoft delivers
the updates and the in-app updater is gone rather than parked. This is a
COMPLIANCE action, not cleanup: the Velopack bootstrap downloaded and executed a
payload, which Store policy 10.2.2 does not permit.

Three edit sites, as planned: the PackageReference in ytLive.csproj, 'using
Velopack;' in App.xaml.cs, and the bootstrap block. The self-hosted DigitalOcean
update-URL droplet dies with it.

Guard: ytLive.Tests/StorePackagingTests.cs - the ONE test asserts the app project
references no updater package (Velopack/Squirrel/NetSparkle/AutoUpdater/WinSparkle)
and that no app source file uses the updater API. The check matches CODE (a using
directive, a type usage), not a bare word, so a file's prose may still name what it
deliberately does not do.

Smoke-tested after the removal: MainWindow loaded, 0 exceptions in startup.log.
Full suite 437/437, 0 warnings.
This commit is contained in:
2026-10-22 06:57:34 -07:00
parent b1d6034662
commit 20cfadca0c
9 changed files with 109 additions and 36 deletions
+2 -14
View File
@@ -1,6 +1,5 @@
using System.Windows; using System.Windows;
using System.Windows.Threading; using System.Windows.Threading;
using Velopack;
using ytLive.Helpers; using ytLive.Helpers;
namespace ytLive; namespace ytLive;
@@ -31,19 +30,8 @@ public partial class App : Application
// the per-build GUID is forensic only and lands here. // the per-build GUID is forensic only and lands here.
AppLog.Write($"Build {ytLive.Helpers.BuildStamp.Id} (compiled {ytLive.Helpers.BuildStamp.BuiltLocal})"); AppLog.Write($"Build {ytLive.Helpers.BuildStamp.Id} (compiled {ytLive.Helpers.BuildStamp.BuiltLocal})");
// Velopack auto-update bootstrap — checks for updates on startup and // There is deliberately NO self-update bootstrap here: the Microsoft Store updates the
// applies them (the app restarts automatically if an update is found). // app (TASK 48 — Store MSIX route), and a second updater would fight it.
// Update URL is configured per-deployment (self-hosted on DO droplet);
// when no URL is set, the check is a no-op.
try
{
VelopackApp.Build()
.Run();
}
catch (Exception ex)
{
AppLog.Write($"Velopack bootstrap failed (non-fatal): {ex.Message}");
}
base.OnStartup(e); base.OnStartup(e);
AppLog.Write("App OnStartup end"); AppLog.Write("App OnStartup end");
+6 -4
View File
@@ -409,10 +409,12 @@ Set-AuthenticodeSignature -FilePath "LlamaCasty.exe" `
### 4.4 Update Mechanism — ⛔ OBSOLETE (2026-09-27) ### 4.4 Update Mechanism — ⛔ OBSOLETE (2026-09-27)
> **Superseded by Store updates.** Microsoft delivers and updates the app from the listing, so > **Superseded by Store updates.** Microsoft delivers and updates the app from the listing, so
> there is nothing to check and no manifest to host. The Velopack updater currently in > there is nothing to check and no manifest to host. The Velopack updater that used to sit in
> `App.xaml.cs` **downloads *and executes***, which Store policy **10.2.2** does not permit — its > `App.xaml.cs` **downloaded *and executed***, which Store policy **10.2.2** does not permit — its
> removal is a **compliance action**, not optional cleanup (TASK 48 item 3). The Polar-native design > removal was a **compliance action**, not optional cleanup, and it is **done** (TASK 48 slice 3,
> below is retained as a record of the approach that was rejected; it is not an option. > 2026-10-22: the package reference and the bootstrap are gone; `ytLive.Tests/StorePackagingTests.cs`
> guards it). The Polar-native design below is retained as a record of the approach that was
> rejected; it is not an option.
**Polar-native approach (no custom infrastructure):** **Polar-native approach (no custom infrastructure):**
+14 -4
View File
@@ -77,6 +77,16 @@
unchanged; the seams are two `internal` properties). Guard: unchanged; the seams are two `internal` properties). Guard:
`ytLive.Tests/AppLogRotationTests.cs`. Slices 2 (temp alert-clip cleanup) and 3 (low-disk `ytLive.Tests/AppLogRotationTests.cs`. Slices 2 (temp alert-clip cleanup) and 3 (low-disk
guard before recording) are still open. guard before recording) are still open.
- **TASK 48 slice 3 — the self-updater is gone (shipped 2026-10-22).** The Store route (creator
ruling 2026-09-27) means the Store updates the app, so the Velopack package + bootstrap were
**deleted** (`ytLive.csproj`, `App.xaml.cs`) — a compliance action, not cleanup: the updater
downloaded *and executed*, which Store policy 10.2.2 does not permit. The DO update-URL droplet
dies with it. Guard: `ytLive.Tests/StorePackagingTests.cs` (no updater package, no updater API
in any app source file). The app was smoke-tested after the removal: `MainWindow loaded`, no
exceptions. **Still open on TASK 48:** slice 2 (the `Package.appxmanifest` + packaging project),
slice 6 (the full-trust recording invariant comment), slice 7 (disqualifier re-check), slice 8
(the pre-submission gates) — and slices 4/5 are the creator's (Certification Kit, Partner
Center listing, the demo account).
- **The previous session (HA lights) produced NO ytLive code** — it was spent on the creator's Home - **The previous session (HA lights) produced NO ytLive code** — it was spent on the creator's Home
Assistant light alert system (off-repo, `192.168.50.145`). See `MyMistakes.md` → 2026-10-21: the plan Assistant light alert system (off-repo, `192.168.50.145`). See `MyMistakes.md` → 2026-10-21: the plan
was one tweak to a verified-working script; the script was rewritten twice, the alert path broke, and was one tweak to a verified-working script; the script was rewritten twice, the alert path broke, and
@@ -270,10 +280,10 @@ Zombie entities deleted from the registry: `script.leak_strobe`, `script.doorbel
Galaxy `cef.log` at 2.7 GB is NOT ours.) Galaxy `cef.log` at 2.7 GB is NOT ours.)
## Tests ## Tests
- Last full run: **436/436 passed, 0 warnings** (2026-10-22 — after TASK 55 slice 1's - Last full run: **437/437 passed, 0 warnings** (2026-10-22 — after TASK 48 slice 3's
`AppLogRotationTests`; the run before was 435/435 after TASK 63, and the runs before that were `StorePackagingTests`; the run before was 436/436 after TASK 55 slice 1's
435/435 after TASK 61+62, 433/433 after TASK 60, 432/432 after TASK 59 and 431/431 after `AppLogRotationTests`, and the one before that 435/435 after TASK 63). The one failure seen
TASK 58). The one failure seen earlier in the earlier in the
session was `LayerReorderPersistenceTests.RealMouseDrag_OnTheLayerList_PersistsTheReorder`, session was `LayerReorderPersistenceTests.RealMouseDrag_OnTheLayerList_PersistsTheReorder`,
which **passes in isolation** (re-run: 3/3) — the known real-mouse flake, not a regression. which **passes in isolation** (re-run: 3/3) — the known real-mouse flake, not a regression.
- Known flaky-by-environment (all pass in isolation): `AudioPipelineTests.Mix_HonorsProviderGains…`, - Known flaky-by-environment (all pass in isolation): `AudioPipelineTests.Mix_HonorsProviderGains…`,
+2 -2
View File
@@ -63,7 +63,7 @@
| 45 | TEST-tab chat fix #2: insert body must declare `snippet.type` (400 MISSING_REQUIRED_FIELD) | ✅ Done (2026-09-25) | [`TASKS/task-45-chat-insert-type.md`](TASKS/task-45-chat-insert-type.md) | | 45 | TEST-tab chat fix #2: insert body must declare `snippet.type` (400 MISSING_REQUIRED_FIELD) | ✅ Done (2026-09-25) | [`TASKS/task-45-chat-insert-type.md`](TASKS/task-45-chat-insert-type.md) |
| 46 | Drawers: click outside the rail closes whichever is open — TEST added to the existing Stream Settings + YPP dismiss behavior | ✅ Done (2026-09-25) | [`TASKS/task-46-drawer-click-outside-close.md`](TASKS/task-46-drawer-click-outside-close.md) | | 46 | Drawers: click outside the rail closes whichever is open — TEST added to the existing Stream Settings + YPP dismiss behavior | ✅ Done (2026-09-25) | [`TASKS/task-46-drawer-click-outside-close.md`](TASKS/task-46-drawer-click-outside-close.md) |
| 47 | Alert box video: built-in/custom alert clip (+ six-animation fallback) with read-time fade in/out + ticker (now in the preview too, 3 display methods) + alert volume in the live mix | ✅ Done (2026-09-26) | [`TASKS/task-47-alert-videos.md`](TASKS/task-47-alert-videos.md) | | 47 | Alert box video: built-in/custom alert clip (+ six-animation fallback) with read-time fade in/out + ticker (now in the preview too, 3 display methods) + alert volume in the live mix | ✅ Done (2026-09-26) | [`TASKS/task-47-alert-videos.md`](TASKS/task-47-alert-videos.md) |
| 48 | Distribution & packaging: **MSIX + Store IAP** | 🔶 In progress — **✅ route decided 2026-09-27 (Store MSIX + Store IAP)**; **item 1 (bundle ffmpeg) SHIPPED 2026-09-28** | [`TASKS/task-48-distribution-msix.md`](TASKS/task-48-distribution-msix.md) | | 48 | Distribution & packaging: **MSIX + Store IAP** | 🔶 In progress — **✅ route decided 2026-09-27 (Store MSIX + Store IAP)**; **item 1 (bundle ffmpeg) SHIPPED 2026-09-28**; **item 3 (remove the Velopack updater) SHIPPED 2026-10-22** — the Store updates the app | [`TASKS/task-48-distribution-msix.md`](TASKS/task-48-distribution-msix.md) |
| 49 | Chat profanity filter (local, opt-in, non-persistent, user word list) | ☐ Queued (2026-09-27) | [`TASKS/task-49-chat-profanity-filter.md`](TASKS/task-49-chat-profanity-filter.md) | | 49 | Chat profanity filter (local, opt-in, non-persistent, user word list) | ☐ Queued (2026-09-27) | [`TASKS/task-49-chat-profanity-filter.md`](TASKS/task-49-chat-profanity-filter.md) |
| 50 | One-button "Publish this video" (resumable upload of the local recording) | ☐ Queued (2026-09-28) | [`TASKS/task-50-publish-recording.md`](TASKS/task-50-publish-recording.md) | | 50 | One-button "Publish this video" (resumable upload of the local recording) | ☐ Queued (2026-09-28) | [`TASKS/task-50-publish-recording.md`](TASKS/task-50-publish-recording.md) |
| 51 | **Webcam background effects: Remove + Blur** | ✅ **Built 2026-10-09** (v1 requirement) — bundled MediaPipe Selfie Segmenter → ONNX (verified vs Google's runtime), Remove + Blur with strength, per-scene config persisted, one processing point in CameraManager, radio-pill UI in the webcam's Layers properties. Creator check 2026-10-09: **Remove jitter FIXED** (temporal mask smoothing 0.75/0.25, OBS/Jitsi-derived) and **Blur "heavy" FIXED** (quadratic radius 1→6 small px, default 0.5 = 2, StreamFX-consensus); recheck on the real camera pending | [`TASKS/task-51-webcam-bg-effects.md`](TASKS/task-51-webcam-bg-effects.md) | | 51 | **Webcam background effects: Remove + Blur** | ✅ **Built 2026-10-09** (v1 requirement) — bundled MediaPipe Selfie Segmenter → ONNX (verified vs Google's runtime), Remove + Blur with strength, per-scene config persisted, one processing point in CameraManager, radio-pill UI in the webcam's Layers properties. Creator check 2026-10-09: **Remove jitter FIXED** (temporal mask smoothing 0.75/0.25, OBS/Jitsi-derived) and **Blur "heavy" FIXED** (quadratic radius 1→6 small px, default 0.5 = 2, StreamFX-consensus); recheck on the real camera pending | [`TASKS/task-51-webcam-bg-effects.md`](TASKS/task-51-webcam-bg-effects.md) |
@@ -129,7 +129,7 @@ second encoder path needing a "redirect". Record+simulcast is one ffmpeg with tw
`DroppedFrames`/`StreamDuration`; `SessionTeardownTests` is the natural home for the roll-up. `DroppedFrames`/`StreamDuration`; `SessionTeardownTests` is the natural home for the roll-up.
- **TASK 3** — items 16 (Text source) + 20 (RewardEvent capture → SQLite, Alerts' persistence half) open; **17 (Alerts) is DONE via TASK 43 (2026-09-24) — native six-event alert box** - **TASK 3** — items 16 (Text source) + 20 (RewardEvent capture → SQLite, Alerts' persistence half) open; **17 (Alerts) is DONE via TASK 43 (2026-09-24) — native six-event alert box**
- **TASK 9** — item 5 open (webcam identity key reconciliation) - **TASK 9** — item 5 open (webcam identity key reconciliation)
- **TASK 10** — Velopack update URL pending → **now owned by TASK 48** (retires if the Store handles updates) - **TASK 10** — the Velopack update URL is **RETIRED (2026-10-22)**: the updater was deleted (TASK 48 slice 3) and the Store updates the app
- **Shipping / release build (creator-queued 2026-09-26)** — no publish config exists yet: - **Shipping / release build (creator-queued 2026-09-26)** — no publish config exists yet:
the csproj has only `OutputType=WinExe` + `TargetFramework`, so a plain `dotnet publish` the csproj has only `OutputType=WinExe` + `TargetFramework`, so a plain `dotnet publish`
is **framework-dependent** (customer needs the .NET 8 Desktop Runtime preinstalled). is **framework-dependent** (customer needs the .NET 8 Desktop Runtime preinstalled).
+4 -4
View File
@@ -68,7 +68,7 @@ licensing path is being deleted because Store IAP supplies the same bit from the
handles purchase. Paid users see zero license UI. handles purchase. Paid users see zero license UI.
7. ⛔ **Licensing model / renewal-lapse path** — already dead before the Store ruling (a perpetual 7. ⛔ **Licensing model / renewal-lapse path** — already dead before the Store ruling (a perpetual
key never expires). Now superseded again: **Store IAP has no keys at all.** key never expires). Now superseded again: **Store IAP has no keys at all.**
8. 🔶 **Velopack auto-updates** — NuGet packages + bootstrap in `App.xaml.cs`. **TEARDOWN is a 8. ✅ **Velopack auto-updates** — NuGet package + bootstrap in `App.xaml.cs`. **DELETED
compliance action, not cleanup:** the updater downloads *and executes*, and Store policy 10.2.2 2026-10-22 (TASK 48 slice 3)** — a compliance action, not cleanup: the updater downloaded
does not permit it. Tracked as [`TASKS/task-48-distribution-msix.md`](task-48-distribution-msix.md) *and executed*, and Store policy 10.2.2 does not permit it. The Store updates the app
item 3; the Store updates the app instead. instead. Guard: `ytLive.Tests/StorePackagingTests.cs`.
+11 -5
View File
@@ -158,15 +158,21 @@ There is currently **no `.manifest` file in the repo at all**, so this is purely
**Do NOT use `appContainer`** — see the invariant in item 6. **Do NOT use `appContainer`** — see the invariant in item 6.
## 3. ☐ Remove Velopack — 3 edit sites ## 3. ✅ Remove Velopack — 3 edit sites (DONE 2026-10-22)
The Store handles updates, so this is simply **deleted**. Trivially removable; the code was The Store handles updates, so this is simply **deleted**. Trivially removable; the code was
written defensively for exactly this. written defensively for exactly this.
- `ytLive.csproj:92` — `<PackageReference Include="Velopack" Version="1.2.0" />` - `ytLive.csproj:92` — `<PackageReference Include="Velopack" Version="1.2.0" />` — **REMOVED**
- `App.xaml.cs:3` — `using Velopack;` - `App.xaml.cs:3` — `using Velopack;` — **REMOVED**
- `App.xaml.cs:38-46` — the sole call site, already try/caught and commented - `App.xaml.cs:38-46` — the sole call site (already try/caught and commented
"(non-fatal) … when no URL is set, the check is a no-op" "(non-fatal) … when no URL is set, the check is a no-op") — **REMOVED**
**Guard:** `ytLive.Tests/StorePackagingTests.cs` — the ONE test asserts the app project
references no updater package (Velopack/Squirrel/NetSparkle/AutoUpdater/WinSparkle) and that no
app source file uses the updater API (matched as CODE — a `using` directive, a type usage — not
as a bare word, so a file's prose may still name what it deliberately does not do). The app was
smoke-tested after the removal: `MainWindow loaded`, no exceptions.
**Retires:** the pending "Velopack update URL" item (`TASKS.md` open items, **Retires:** the pending "Velopack update URL" item (`TASKS.md` open items,
`task-10-monetization.md:43`) and the self-hosted DigitalOcean droplet. `task-10-monetization.md:43`) and the self-hosted DigitalOcean droplet.
+4 -2
View File
@@ -2583,8 +2583,10 @@ before re-deriving it. Executable checklist: `TASKS/task-48-distribution-msix.md
**Consequences that are architecture, not packaging detail:** **Consequences that are architecture, not packaging detail:**
- **⛔ Velopack and the update URL are deleted** — the Store auto-updates. 3 edit sites, - **⛔ Velopack and the update URL are deleted** — the Store auto-updates. **DONE 2026-10-22**
`ytLive.csproj:92` + `App.xaml.cs:3,38-46`. The self-hosted DO droplet dies with it. (TASK 48 slice 3): the `PackageReference` in `ytLive.csproj` and the bootstrap in
`App.xaml.cs` are gone, and `ytLive.Tests/StorePackagingTests.cs` guards that no updater
package or updater API comes back. The self-hosted DO droplet dies with it.
- **⛔ The Polar licensing subsystem is deleted** — `PolarLicenseService`, `PolarLicense`, - **⛔ The Polar licensing subsystem is deleted** — `PolarLicenseService`, `PolarLicense`,
`MainViewModel.License.cs` (`PremiumUrl`, customer portal, the `MainViewModel.License.cs` (`PremiumUrl`, customer portal, the
`OfflineGracePeriod = 14 days` subscription-era artifact, renewal/lapse copy), and the `OfflineGracePeriod = 14 days` subscription-era artifact, renewal/lapse copy), and the
+66
View File
@@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Linq;
using Xunit;
namespace ytLive.Tests;
/// <summary>TASK 48, slice 3 — the creator's ruling (2026-09-27): the app ships as a
/// **Microsoft Store MSIX** and the **Store updates it**. The self-update subsystem that
/// existed for the abandoned self-hosted route (a Velopack bootstrap plus the DigitalOcean
/// update-URL droplet) is therefore deleted, not parked: a second updater inside a
/// Store-installed package fights the Store's own updater.
///
/// <para>This test is the guard: the app project references no updater package and no app
/// source file uses the updater API — so the deleted subsystem cannot creep back in, and
/// the "the Store updates it" decision stays the only update path.</para></summary>
public class StorePackagingTests
{
/// <summary>Known self-update packages. If one of these comes back, the Store route has
/// been quietly reversed.</summary>
private static readonly string[] UpdaterPackages =
{
"Velopack", "Squirrel", "NetSparkle", "AutoUpdater", "WinSparkle"
};
[Fact]
public void The_app_ships_no_self_update_subsystem()
{
// 1. The app project references no updater package.
var project = XDocument.Load(SourceAssert.RepoPath("ytLive.csproj"));
var packages = project.Descendants()
.Where(e => e.Name.LocalName == "PackageReference")
.Select(e => (string?)e.Attribute("Include"))
.Where(name => name != null)
.ToHashSet(StringComparer.OrdinalIgnoreCase);
foreach (var updater in UpdaterPackages)
Assert.DoesNotContain(updater, packages);
// 2. No app source file uses the updater API. Matched as CODE (a using directive,
// a type usage), not as a bare word — a file's prose is allowed to name what it
// deliberately does not do.
var root = Path.GetDirectoryName(SourceAssert.RepoPath("ytLive.csproj"))!;
foreach (var file in AppSourceFiles(root))
{
var text = File.ReadAllText(file);
Assert.False(
Regex.IsMatch(text, @"^\s*using\s+Velopack\s*;", RegexOptions.Multiline),
$"{Path.GetFileName(file)} still imports the Velopack updater — the Store updates this app.");
Assert.False(
Regex.IsMatch(text, @"\bVelopackApp\b"),
$"{Path.GetFileName(file)} still calls the Velopack updater — the Store updates this app.");
}
}
/// <summary>Every C# file of the app project (not the test project, not build output).</summary>
private static IEnumerable<string> AppSourceFiles(string root)
{
return Directory.EnumerateFiles(root, "*.cs", SearchOption.AllDirectories)
.Where(path => !path.Contains($"{Path.DirectorySeparatorChar}ytLive.Tests{Path.DirectorySeparatorChar}"))
.Where(path => !path.Contains($"{Path.DirectorySeparatorChar}obj{Path.DirectorySeparatorChar}"))
.Where(path => !path.Contains($"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}"));
}
}
-1
View File
@@ -125,7 +125,6 @@
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.12" /> <PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.1.12" />
<PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" /> <PackageReference Include="System.Security.Cryptography.ProtectedData" Version="8.0.0" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2903.40" /> <PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2903.40" />
<PackageReference Include="Velopack" Version="1.2.0" />
</ItemGroup> </ItemGroup>
</Project> </Project>