Documentation — Architecture

# Licensing & Deployment Models

A license system for software that runs entirely on someone else's network has one hard constraint: it must never grant unlimited access simply because it can't reach home. This page describes exactly how TuneVault avoids that trap.

## Cloud vs self-hosted: same enforcement idea, different source of truth

| Cloud | Self-hosted
--- | --- | ---
Where limits live | `user_credits.plan_tier`, checked per-request | A license key, validated against `tunevault.app`
Enforcement point | `tier-enforce.js` middleware on connection creation and health-check runs | The same middleware, reading caps resolved from the license instead of a plan row
What happens over cap | HTTP 402 with a structured upgrade message | HTTP 402, same shape — expired trials get an expiry-specific message

## The offline-grace problem, and why it matters for self-hosted specifically

Self-hosted TuneVault's core audience is enterprises whose firewalls block outbound connections by default — the exact ICP this product exists for. That creates an uncomfortable tension: if the license server is unreachable, do you brick a legitimate paying customer's install, or do you grant unlimited access because you can't check?

Neither. TuneVault caches the result of every successful validation, keyed to the license key that earned it:

The offline decision tree
Validated recently (within 30 days) → use cached caps, cached expiry still enforced Cache exists but grace expired → minimal caps (1 connection), loud warning Never validated successfully → minimal caps — the exploit this closes Cached license itself has expired → minimal caps regardless of grace

The critical line is the third one. A cache keyed only on "does a row exist" would let someone swap in a forged key, block outbound access, and inherit whatever caps happened to be cached — so the cache also records which key earned it, and a mismatched key is treated as never-validated.

A never-validated key never receives unlimited caps, under any circumstance. Unlimited is a privilege earned by a real, successful check-in — never a default.

## Expiry is enforced continuously, not just at boot

A container started once and never restarted would otherwise keep its boot-time caps forever — meaning a 30-day trial license would never actually expire in practice. Caps are instead resolved fresh at the point of use, via a shared helper checked by every enforcement site, rather than read once from a value set at startup.

Once a license's `expires_at` has passed, existing connections and their historical reports and tickets stay fully readable — nothing is deleted or hidden. What stops is new health checks, which is where the actual ongoing value of the product lives. This is a deliberate design choice: an expired trial should make its absence felt, not disappear the evidence of what it already found.

## The free trial, concretely

Property | Value
--- | ---
Connections | 1
Health checks | Unlimited
Duration | 30 days from issue
Cost to issue | None — self-hosted runs on your hardware with your own optional AI keys, so a trial connection costs TuneVault nothing to support

One connection with unlimited checks was a deliberate choice over a small number of checks with more connections: the value of TuneVault only becomes visible when someone watches it catch something real on their own database over time, and a check-count limit would cut that observation short before it happens.

See Architecture Overview for how the license-validation call fits the rest of the connectivity model, or download self-hosted to get a trial key.