Summary
Establish two cross-cutting mechanisms used by multiple Phase 9 features:
- 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.
- 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:
- User has a valid premium licence active (
LicenceManager.isPremium)
- The build is a developer / internal build (compile-time flag
MEEDYACONVERTER_DEV_BUILD)
- 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
Phase B — Premium gating UX
Phase C — Expert Mode infrastructure
Phase D — Licence delivery infrastructure (mechanism-only; not the back-end)
Phase E — Documentation
Acceptance criteria
Technical / security notes
- 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.
- 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.
- 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.
- 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.
- 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).
- 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
- StoreKit + Direct licence file: both, or pick one? Recommendation: both, behind
LicenceManager abstraction.
- 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.
- 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.
- 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.
- 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
Summary
Establish two cross-cutting mechanisms used by multiple Phase 9 features:
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
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:
The check returns
truewhen ANY of:LicenceManager.isPremium)MEEDYACONVERTER_DEV_BUILD)Open questions: licence delivery mechanism, validation cadence, offline tolerance. See below.
What features are gated?
Initial premium feature set (proposed, subject to product decision):
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:
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):
Recommendation: Both, with a
LicenceManagerabstraction 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:
Activation UX
What Expert Mode is NOT
Per-feature opt-in
A feature using Expert Mode reads
ExpertMode.isEnabledat the point where it would normally enforce a safety floor: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:
And in each affected feature's Settings panel, threshold sliders get a clarifying caption:
Scope (phased)
Phase A — Entitlement infrastructure
EntitlementAPI:Entitlement.shared.canUse(.featureKey) -> BoolLicenceManagerabstraction with two implementations: StoreKit-backed and licence-file-backedMEEDYACONVERTER_DEV_BUILDcompile-time flagPhase B — Premium gating UX
Phase C — Expert Mode infrastructure
ExpertMode.isEnabledAPIPhase D — Licence delivery infrastructure (mechanism-only; not the back-end)
Phase E — Documentation
Acceptance criteria
Technical / security notes
~/.config/meedyaconverter/dev-keyon 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.Open questions
LicenceManagerabstraction.References
Effort estimate
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