Skip to content

Premium-tier feature gating mechanism + cross-cutting Expert Mode #424

@Salem874

Description

@Salem874

Summary

Establish two cross-cutting mechanisms used by multiple Phase 9 features:

  1. Premium-tier gating — infrastructure for marking features as paid-tier-only, validating licence entitlement at runtime, gracefully degrading in the free tier, and bypassing the gate for developers and internal testers.
  2. Expert Mode — a per-user setting that unlocks override paths on safety floors (confidence thresholds, drift-rate caps, minimum-duration cutoffs, etc.) across the app. Toggling Expert Mode ON triggers an explicit warning dialog the first time.

These two mechanisms are filed in one issue because they will likely share UI surface (both live in Settings) and because both need to interact with the audio-sync features (#421, #422) and potentially other future Phase 9 features (#419 OFX, #420 OCIO) that may also be premium-gated.

Why one issue for both

  • They share Settings UI: a top-level "Account & Capabilities" panel with subsections
  • They share the entitlement check pattern: both feature-tier and expert-mode gates use a consistent "is this user permitted to do X" API
  • They're both cross-cutting and low-priority on their own — bundling avoids two separate small-scope issues each waiting on a separate sprint slot
  • They share documentation surface — the user-facing docs about "what's free, what's paid, what's expert" should be coherent

If implementation pressure ever pulls them apart, easy to split. For now, one issue.

Premium-tier gating — design

Entitlement model

A feature is either free (everyone) or premium (paid customers + developers + internal testers). No multi-tier ladder. No per-feature à la carte. One paid tier, one entitlement check.

Gate enforcement

Every premium feature wraps its entry point in an entitlement check:

guard Entitlement.shared.canUse(.audioSyncDrift) else {
    presentUpgradePrompt(forFeature: .audioSyncDrift)
    return
}
// ... feature implementation ...

The check returns true when ANY of:

  1. User has a valid premium licence active (LicenceManager.isPremium)
  2. The build is a developer / internal build (compile-time flag MEEDYACONVERTER_DEV_BUILD)
  3. A developer-bypass key is present in the user's keychain (set during dev setup; never shipped in release binaries)

Open questions: licence delivery mechanism, validation cadence, offline tolerance. See below.

What features are gated?

Initial premium feature set (proposed, subject to product decision):

Feature Issue Tier
Intelligent audio offset detection (#421) Premium Locks behind paid tier
Audio drift correction + cuts + spatial (#422) Premium Locks behind paid tier
OpenFX plugin host (#419) TBD — likely premium given engineering cost
OpenColorIO integration (#420) TBD — possibly free (industry standard, smaller scope)
Everything else currently in MeedyaConverter Free Unchanged

Final tier assignments are a separate product decision; this issue is about the mechanism, not the per-feature placement.

Upgrade prompt UX

When a free user attempts a premium feature:

  • Modal dialog: "Intelligent Audio Sync is a premium feature. [Learn More] [Cancel]"
  • Learn More opens a Settings → Premium panel listing the premium features, their benefits, and the upgrade path
  • No nagging in non-feature contexts — banner ads, upsell pop-ups outside the feature flow, etc. are explicitly out of scope. The gate is silent until the user actively tries to use a premium feature.
  • Dev / internal builds: the gate is silently bypassed; no UI ever appears

Free-tier graceful degradation

For premium features that have a free-tier analogue (e.g. #421 audio sync vs. existing manual MKVToolNix-style offset), the upgrade prompt must include "what's available in the free tier instead". For genuinely premium-only features (e.g. #420 OCIO), the prompt is upgrade-or-nothing.

Licence delivery — open question

Three plausible delivery models (need product decision before implementing):

Model Pros Cons
App Store in-app purchase (StoreKit on macOS) Native, Apple handles billing + refunds, trusted by users App Store-only path; Direct distribution can't share licences without a separate path
Direct licence file (signed JWT delivered post-purchase via email / portal) Works for Direct distribution AND App Store; portable across machines via licence-file copy Requires us to operate licence issuance + signing infrastructure; harder UX
Both (StoreKit for App Store, signed file for Direct) Best for each distribution channel More implementation work; needs licence-system abstraction

Recommendation: Both, with a LicenceManager abstraction so the rest of the code doesn't care which delivery mechanism is active.

Validation cadence and offline tolerance

Premium features must work offline. Validation cadence should be lenient — premium check on app startup, cache the result, re-validate every 30 days. If validation fails (network down, server unreachable), continue to honour the most recent successful validation for at least 30 days before degrading.

This is the "respect the customer" approach — common in pro software (Adobe Creative Cloud allows 30 days offline, etc.).

Expert Mode — design

What it unlocks

A per-user toggle in Settings that bypasses safety floors across the app. Specifically:

Feature Safety floor Expert Mode behaviour
Audio sync confidence threshold (#421, #422) Default 0.85 hard-reject Allow user to lower or disable
Drift-rate ceiling (#422) Default 5% max drift correction Allow user to override
Minimum file duration for drift detection (#422) Default 5 minutes Allow user to override
Drift-linearity threshold (#422) Default 0.85 Allow user to override
Spatial-audio policy (#422) Default: prompt before lossy operations Allow batch-default to lossy
Cut-detection thresholds (#422) Default safe values Allow user to set arbitrary thresholds
(Future) Any future safety floor in the app Same pattern

Activation UX

  • Toggle in Settings → Advanced → "Enable Expert Mode"
  • First-time activation triggers a modal warning:

    Expert Mode disables safety checks

    Expert Mode allows you to override confidence thresholds, drift-rate limits, and other safety gates that protect against producing low-quality output.

    Use Expert Mode if:

    • You understand what these limits mean
    • You have tested your inputs and know they're suitable
    • You're willing to verify output quality manually

    Do NOT use Expert Mode if you're producing output for someone else without their explicit knowledge that these checks have been bypassed.

    [Cancel] [I Understand — Enable Expert Mode]

  • After activation, a small "Expert" badge appears in the Settings nav (subtle reminder)
  • Expert Mode persists across sessions
  • Disabling Expert Mode does NOT show a warning (re-enabling triggers the warning again — encourages thoughtful re-entry)

What Expert Mode is NOT

  • Not a developer mode. Developer / internal builds have their own bypass (see Premium gating section); Expert Mode is for end users.
  • Not a premium feature. Expert Mode is available to all users including the free tier — the whole point is to let advanced users tune things.
  • Not a debug mode. No verbose logging, no inspector panels, no internal telemetry. Just unlocks safety overrides on existing features.

Per-feature opt-in

A feature using Expert Mode reads ExpertMode.isEnabled at the point where it would normally enforce a safety floor:

let confidenceFloor = ExpertMode.isEnabled
    ? userSetting(.audioSyncMinConfidence, default: 0.5)  // user can go as low as 0.5
    : 0.85  // hard-coded safety floor in non-expert mode

The free-tier user can therefore use Expert Mode to tweak audio-sync thresholds if they have access to audio sync at all. (For premium features, both gates need to pass: premium entitlement AND expert mode for any threshold-lowering operation.)

Settings UX (consolidated)

A new Settings → Advanced subsection:

Setting Default Notes
Premium account status (display only) Shows licence state, expiry, last validation
Enter licence key (action) For Direct distribution licence files
Expert Mode OFF Master toggle with first-time warning
Reset all advanced settings to defaults (action) Convenient escape hatch

And in each affected feature's Settings panel, threshold sliders get a clarifying caption:

"Confidence threshold — minimum 0.85 (Expert Mode required to lower)"

Scope (phased)

Phase A — Entitlement infrastructure

  • Entitlement API: Entitlement.shared.canUse(.featureKey) -> Bool
  • LicenceManager abstraction with two implementations: StoreKit-backed and licence-file-backed
  • Developer build detection via MEEDYACONVERTER_DEV_BUILD compile-time flag
  • Developer keychain bypass for non-dev builds during local development
  • Unit tests covering: free user → false, premium user → true, dev build → true, expired licence → false (with grace period)

Phase B — Premium gating UX

  • Upgrade prompt modal dialog
  • Settings → Premium panel showing feature list + upgrade path
  • No-network grace period handling (30-day tolerance)
  • Test paths: hit upgrade prompt, dismiss, hit again from a different feature

Phase C — Expert Mode infrastructure

Phase D — Licence delivery infrastructure (mechanism-only; not the back-end)

  • StoreKit integration for App Store distribution
  • Licence-file format spec (signed JWT with feature claims + expiry)
  • Licence-file import UX
  • Validation against a public key embedded in the app
  • Periodic re-validation logic

Phase E — Documentation

  • User-facing docs: what's free, what's paid, how to upgrade, how to enter licence file, what Expert Mode does
  • Developer docs: how to mark a feature as premium-gated, how to add an Expert Mode override
  • Privacy docs: what data leaves the device during validation (minimal — anonymous licence-validation call only)

Acceptance criteria

Technical / security notes

  1. Licence-file signing: standard JWT with RS256 (RSA + SHA256) signature, project-controlled signing key. Public key shipped in the app binary. Compromised signing key requires a binary update to rotate — accept this constraint.
  2. No phone-home telemetry: validation is opt-in network call; no anonymous usage data, no feature-use tracking, no analytics beacons. The licence validation call itself transmits only the licence ID and returns an entitlement object.
  3. No DRM on the converted output: this issue is about feature gating, not output DRM. MeedyaConverter's output files contain no DRM, no watermarks, no "this was created in the free tier" markers.
  4. Dev bypass leak prevention: the developer-bypass key is stored in ~/.config/meedyaconverter/dev-key on the developer's machine; NEVER checked into any repository, NEVER shipped in any binary. Detection at build time (CI grep for the magic constant) catches accidental commits.
  5. Per-machine vs per-user licensing: licence files are per-licence-purchase, transferable to a new machine by copying the file. No machine fingerprinting, no activation limits in the initial implementation (revisit if abuse emerges).
  6. Refund handling: StoreKit refunds are handled by Apple. For Direct distribution refunds, we mark the licence ID as revoked in our validation API; on next periodic validation, the user's entitlement drops.

Open questions

  1. StoreKit + Direct licence file: both, or pick one? Recommendation: both, behind LicenceManager abstraction.
  2. Licence pricing: out of scope for this issue (product decision), but the mechanism needs to accommodate one-time purchases AND/OR subscriptions. Recommendation: design for both, default to one-time perpetual purchase.
  3. Trial period: should premium features be usable for N days before requiring a licence? Recommendation: not in initial implementation. Add later if conversion rate justifies the complexity.
  4. Refund / revocation: how do we handle a customer who refunded purchasing through Direct? Recommendation: revocation list checked at periodic re-validation; user gets a clear "your licence is no longer active" message rather than a silent feature-disable.
  5. Education / non-profit pricing: out of scope but the licence-file format should accommodate flagged-licence-types so we can issue special licences later without app changes.

References

Effort estimate

  • Phase A: 2-3 weeks (entitlement infrastructure + tests)
  • Phase B: 1-2 weeks (premium gating UX)
  • Phase C: 1-2 weeks (Expert Mode infrastructure + UX)
  • Phase D: 3-4 weeks (StoreKit + licence file delivery)
  • Phase E: 1 week (documentation)

Conservative total: 8-12 weeks of focused work — most of it in Phase A and Phase D. Doesn't gate the implementation of #421/#422/#419/#420 themselves, but DOES gate their public release as premium features.

Related issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions