-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Summary
Pre-release builds (alpha, beta) should have a time-limited expiry window so that outdated versions don't remain in use indefinitely. After expiry, the app should prompt the user to update rather than silently continuing to run stale code.
This is distinct from freemium tier gating (#631) — expiry applies to ALL users of a pre-release build regardless of tier. It's about build lifecycle, not feature access.
Proposed Design
Expiry Mechanism
Embed a build timestamp and expiry window at compile time:
// build.rs or main.rs
const BUILD_TIMESTAMP: &str = env!("BUILD_TIMESTAMP"); // Set by CI or build script
const EXPIRY_DAYS: u64 = 90; // Alpha/beta builds expire after 90 days
const BUILD_CHANNEL: &str = env!("BUILD_CHANNEL"); // "alpha", "beta", "release"On app startup:
- Parse
BUILD_TIMESTAMP, addEXPIRY_DAYS - Compare against current system time
- If expired → show expiry banner/modal
- If within 14 days of expiry → show warning banner
Expiry Behaviour (NOT a hard block)
Soft expiry (recommended for alpha/beta):
- App still launches and functions
- Persistent banner at top of window: "This alpha build expired on {date}. Please update to the latest version."
- Banner includes "Download Update" link and "Dismiss" (dismisses for current session only, returns on next launch)
- Optional: disable certain features (e.g., LLM chat) after expiry to encourage update without breaking core mapping functionality
Hard expiry (alternative, more aggressive):
- App launches to a full-screen update prompt
- Core daemon functionality continues (daemon has no expiry — it runs headless)
- GUI becomes non-functional until updated
Recommendation: soft expiry for alpha, graduating to either approach for beta depending on product needs.
Build Channels
| Channel | Expiry | Auto-update | Version suffix |
|---|---|---|---|
alpha |
90 days | Check only (manual download) | v4.37.0-alpha |
beta |
180 days | Prompt to update | v4.37.0-beta |
release |
Never | Optional check | v4.37.0 |
Implementation
Build Script (build.rs)
fn main() {
// Embed build timestamp as compile-time env var
let timestamp = chrono::Utc::now().to_rfc3339();
println!("cargo:rustc-env=BUILD_TIMESTAMP={}", timestamp);
// Channel from env or default to "alpha"
let channel = std::env::var("BUILD_CHANNEL").unwrap_or_else(|_| "alpha".to_string());
println!("cargo:rustc-env=BUILD_CHANNEL={}", channel);
}Tauri Command
#[tauri::command]
fn get_build_info() -> BuildInfo {
BuildInfo {
version: env!("CARGO_PKG_VERSION"),
channel: env!("BUILD_CHANNEL"),
build_date: env!("BUILD_TIMESTAMP"),
expires_at: calculate_expiry(),
is_expired: check_expired(),
days_remaining: days_until_expiry(),
}
}Frontend Banner Component
<!-- ExpiryBanner.svelte -->
{#if expired}
<div class="expiry-banner error">
This {channel} build expired on {expiresAt}.
<a href={updateUrl}>Download latest version</a>
</div>
{:else if daysRemaining <= 14}
<div class="expiry-banner warning">
This {channel} build expires in {daysRemaining} days.
<a href={updateUrl}>Check for updates</a>
<button on:click={dismiss}>✕</button>
</div>
{/if}Clock Manipulation Protection
Users could set their system clock back to avoid expiry. Mitigations:
- Light protection: On each launch, store the latest observed system time in
localStorage. If current time is earlier than stored time, treat as suspicious and use the stored time for expiry calculation. - No network requirement: Expiry must work offline. Don't depend on NTP or server-side validation.
- Pragmatic stance: If a user deliberately manipulates their clock to use an expired alpha build, that's acceptable. This isn't DRM — it's a nudge to stay current.
Daemon Independence
The daemon has no expiry. It runs headless, may be managed by launchd/systemd, and should never stop processing MIDI because a build date passed. Expiry is GUI-only, consistent with the "daemon + TOML is self-contained" principle (#559).
What This Issue Does NOT Cover
- Auto-update mechanism (downloading and installing updates)
- License key expiry (that's feat: Freemium tier gating framework for GUI capabilities #631 tier gating territory)
- Telemetry or usage reporting on expired builds
Related Issues
- feat: Freemium tier gating framework for GUI capabilities #631 — Freemium tier gating (separate concern — feature access vs build lifecycle)
- Epic: Settings Persistence Architecture (ADR-017) #559 — State persistence boundaries (daemon has no expiry, GUI-only concern)
Priority
P4 — Not urgent while in active development with direct user contact, but should be in place before any public alpha/beta distribution.
Acceptance Criteria
- Build timestamp embedded at compile time via
build.rs - Build channel (alpha/beta/release) configurable via environment variable
- Alpha builds expire after 90 days, beta after 180 days, release never
- Expiry banner shown in GUI when build is expired or within 14 days of expiry
- "Download Update" link in banner
- Expired builds still function (soft expiry) — banner is persistent but dismissable per session
- Daemon has no expiry awareness
- Light clock-manipulation protection via stored high-water-mark timestamp
- Release builds have no expiry check
🤖 Generated with Claude Code