Files
zpan/docs/v2.6-release-notes.md
T
Jasper Van c85e60f200 fix(analytics): enforce trustworthy offline statistics (#510)
* fix(analytics): enforce trustworthy offline statistics

Separate immutable counters from point-in-time snapshots, expose incomplete coverage instead of synthetic zeroes, and keep browser analytics result-only.

Restore finite Free quota baselines, fail closed for invalid storage quota, reconcile traffic reports fairly, and add production-safe backfill and data-quality diagnostics.

* fix(analytics): preserve global backfill totals

Group generated hourly backfill rows by their projected values so SQLite cannot resolve output aliases to source organization columns and overwrite cross-organization totals.
2026-07-20 10:23:22 -04:00

14 KiB
Raw Blame History

ZPan v2.6 Release Notes

What's New

ZPan v2.6 introduces ZPan Pro — a paid tier that unlocks operator-grade features for people running ZPan as a platform for others. Free self-hosted personal use remains supported, with the operator-grade gates noted below.

v2.6 also adds the Pro quota store. This is a scoped storage purchase and gift-card credit redemption flow for Pro operators; it does not move generic Pro activation into ZPan. Cloud remains the merchant of record and owns Stripe, subscriptions, membership redemption, and payment operations.


ZPan Pro

Cloud Account Binding

Connect your ZPan instance to a ZPan Cloud account using a device-code pairing flow — no callback URL required, so it works on home labs, internal networks, and NATted VPS hosts.

  1. Go to Settings → Billing and click Connect to ZPan Cloud.
  2. ZPan displays a short code. Visit cloud.zpan.space/pair on any device, sign in, and enter the code.
  3. ZPan detects confirmation and shows your plan within seconds.

Entitlement Certificates

Pro status is verified locally using an Ed25519-signed certificate fetched from cloud.zpan.space every 6 hours. No network call happens per request — the certificate is cached in your database. ZPan tolerates up to 24 hours of cloud downtime before feature gates fall back to Free.

White-Label (first Pro feature)

Customize the look of your ZPan instance:

  • Upload a custom logo and favicon
  • Set a custom wordmark text (replaces "ZPan" in titles and copy)
  • Hide the "Powered by ZPan" footer badge

Manage in Settings → Branding.


Pro Quota Store

The quota store lets a Pro instance operator sell monthly storage packages and metered download traffic capacity to terminal users on that ZPan instance.

Direction and Ownership

  • Cloud is merchant of record in v2.6. Stripe Checkout, Stripe subscriptions, Stripe webhooks, Cloud order state, credit grants, gift-card redemption, and usage debits run through ZPan Cloud.
  • The ZPan operator defines the package catalog per instance. Admins create packages in ZPan with name, description, bytes, price, currency, active state, and sort order.
  • Terminal buyers do not need Cloud accounts. ZPan creates binding-authenticated checkout requests through the Pro Cloud binding; Cloud uses those requests to create orders.
  • Purchased storage and traffic are delivered as Cloud entitlements in v2.6. Every organization keeps a Free baseline plan. An active paid plan overrides that baseline while grants add to it; expiry or revocation therefore falls back to Free automatically. Missing or zero effective storage entitlement fails closed and never means unlimited storage.
  • The scheduled quota reconciler restores missing or previously revoked Free baseline rows before analytics snapshots, so installations affected by the old paid-plan replacement logic heal automatically after upgrade.
  • Monthly storage package subscriptions are Cloud-owned. ZPan does not run subscription billing locally. Cloud owns Stripe subscriptions and sends authenticated order.quota_changed deliveries only for entitlement-changing subscription states: initial entitlement, positive create/update deltas, renewals that replace the current entitlement period, and terminal reversals. ZPan records deliveries idempotently under a stable subscription entitlement id such as stripe_subscription:<subscriptionId>:<orgId> and recomputes the effective quota from active entitlements.
  • Metered traffic billing is credit-backed and usage-event based. Credits are unitless internal ZPan Cloud units, not USD and not Stripe Customer Balance. ZPan increments local monthly traffic usage before issuing a presigned download URL, reports traffic_egress usage to Cloud with an idempotency key, and denies the request if Cloud returns an insufficient-credit decision. ZPan does not price or debit credits.
  • Subscriptions and credits are separate. Stripe subscriptions remain normal USD subscriptions and may include credits per billing period. Free or fully couponed subscriptions still grant included credits when the Cloud subscription or invoice state qualifies. Credits can also come from top-up purchases, gift-card redemption, and admin grants, but credits only pay usage-based charges. They do not pay subscriptions or fixed package purchases.
  • Generic Pro activation stays Cloud-owned. Subscription purchase, membership redemption, and Pro entitlement activation remain in the Cloud dashboard.

Admin Setup

  1. Bind the ZPan instance to a Pro Cloud entitlement.
  2. Open the admin quota store settings.
  3. Enable the store.
  4. Create active packages and confirm their Cloud sync status is synced.

Quota store communication is automatic through the Pro Cloud binding. ZPan resolves Cloud from ZPAN_CLOUD_URL or the built-in default and sends catalog sync and checkout requests with Authorization: Bearer <refresh_token> plus JSON bodies that include boundLicenseId and the required business fields. Forwarded headers are not trusted for Cloud-facing store URLs.

Cloud order quota-change webhook attempts must include x-zpan-cloud-event-token, a Cloud-signed PASETO event token. ZPan verifies the token with the built-in Cloud public keys and requires type: "zpan.cloud.event", purpose: "quota_store.delivery", the current Cloud issuer, an audience equal to the target instance/license id, the bound license id claim, the order quota-change event id, a raw-body SHA-256 payload hash, and a short expiry. Missing, expired, wrong-purpose, wrong-audience, wrong-hash, or mismatched-event tokens are rejected with 401.

The user-facing store remains unavailable while disabled, while Pro is missing, or while the Cloud binding is unavailable.

Buyer Flow

  1. A terminal user opens Store from the quota meter.
  2. The user chooses a personal or team target org.
  3. Checkout calls ZPan, then Cloud creates Stripe Checkout from the package snapshot associated with the bound instance.
  4. After Stripe marks the initial subscription checkout paid, or after Cloud accepts a positive subscription update delta, Cloud sends one authenticated order quota-change webhook to ZPan.
  5. ZPan records the webhook idempotently under the stable subscription entitlement id, stores storage and traffic as Cloud entitlements, and refreshes the quota meter and order history.
  6. Normal subscription renewals are billed in Cloud only; when they change the entitlement period, Cloud sends a replacement delivery that cannot stack quota.

Users who previously hit quota can retry confirm/upload while the draft still exists after the quota change arrives.

Metered Traffic Caveats

  • ZPan meters download traffic at presigned URL issuance time. S3 serves the object directly afterward, so ZPan cannot observe partial downloads, canceled transfers, range requests, cache hits, or repeated use of an already-issued URL.
  • Cloud credit denial happens before ZPan returns the presigned URL. If Cloud rejects usage with insufficient_credits, ZPan refunds the local traffic counter for that attempt and returns 402.
  • Local monthly traffic quota is still enforced before Cloud reporting. Cloud metering is for credit-backed usage billing, not a replacement for ZPan's local quota checks.

Gift Cards

Gift card checkout is separate from Cloud membership redemption:

  • Users redeem gift cards into Cloud credits against a selected personal or team org.
  • Cloud validates the gift card, grants credits, and sends quota-change webhooks only when a separate package purchase or subscription fulfillment changes ZPan storage or traffic entitlements.
  • Membership redemption remains on Cloud and does not share this flow.

Verification Notes

The merged implementation covers:

  • Pro gating for quota store admin and self-service APIs
  • Package validation, Cloud catalog sync, and Cloud sync error surfacing
  • Target org access checks for checkout
  • Anonymous Cloud checkout handoff from binding-authenticated ZPan Store requests
  • Stripe-paid Cloud order quota change through authenticated callback
  • Initial subscription-shaped Cloud quota-change delivery for both storage and traffic entitlements using a stable subscription source id
  • Duplicate Cloud quota-change webhook returning success without applying quota twice
  • Bad Cloud webhook auth returning 401
  • Admin base quota edits staying separate from Cloud entitlements
  • Upload confirm quota checks using effective quota
  • Cloud metered traffic usage reporting, idempotency, insufficient-credit denial, and local counter refund behavior
  • Normal subscription renewals not stacking quota by contract: Cloud replacement deliveries update the stable subscription entitlement instead of adding a second entitlement, and ZPan duplicate-delivery coverage verifies stable entitlement replays do not apply twice
  • Gift-card credit redemption using credit endpoints, not membership redemption

Manual preview evidence required before merge:

  1. Open admin quota-store settings, enable the store, then create an active monthly storage package and confirm Cloud sync status.
    • Screenshot: /tmp/zpan-metered-storage-preview/admin-package-store.png
  2. Open Storage, confirm the quota panel shows base storage, Cloud storage entitlement, included traffic, current-period traffic, and credit balance.
    • Screenshot: /tmp/zpan-metered-storage-preview/storage-quota-panel.png
  3. Open the terminal-user Store from the quota meter without a Cloud account, select a personal or team target org, and confirm active packages render.
    • Screenshot: /tmp/zpan-metered-storage-preview/user-store-packages.png
  4. Start checkout and confirm ZPan opens the Cloud/Stripe checkout URL returned by the binding-authenticated Cloud request.
    • Screenshot: /tmp/zpan-metered-storage-preview/checkout-launch.png
  5. Deliver a Cloud quota-change event, then confirm order history plus the quota panel refresh without changing the admin base quota.
    • Screenshot: /tmp/zpan-metered-storage-preview/order-history-entitlement.png
  6. Force storage over local quota or traffic usage with insufficient Cloud credits and confirm the storage quota card or traffic usage card shows the blocked state.
    • Screenshot: /tmp/zpan-metered-storage-preview/over-cap-state.png

Backend verification covers duplicate Cloud quota-change webhooks not applying quota twice, bad webhook auth returning 401, admin base quota separation, stable subscription-shaped entitlement delivery, Cloud metered traffic reporting, Cloud insufficient-credit denial, and legacy one-time quota reversal compatibility.


Retroactive Pro Gates

Three features from earlier versions have moved to Pro in v2.6. These affect only operators running ZPan as a platform for unknown third parties.

Open Registration (open_registration)

Registration modes closed and invite-only remain Free. Open registration (anyone can sign up without an invite) requires Pro.

If you were using open registration mode, set up a Pro license before upgrading or your instance will fall back to invite-only.

Team Count > 1 Extra Team (teams_unlimited)

Free instances may have 1 personal workspace + up to 1 team. Creating a 2nd team requires Pro. Existing teams are never deleted; the gate only prevents creating new ones beyond the limit.

Storage Backend Count > 3 (storages_unlimited)

Free instances may configure up to 3 storage backends. Adding a 4th storage requires Pro.


Feature Gate Overview

Feature Free Pro
All v2.0v2.5 features
Open registration mode
Teams beyond 1 extra team
Unlimited storages Up to 3
White-label branding
Quota store

Upgrading

  1. Pull the latest image: docker pull ghcr.io/saltbo/zpan:latest
  2. Restart your container — migrations run automatically.
  3. If you use open registration mode, bind a Pro license before restarting (see above).
  4. Optional: go to Settings → Billing to connect your cloud account and activate Pro.
  5. Optional for Pro operators: enable Quota Store after binding Pro; Cloud communication is handled automatically through that binding.

For Cloudflare Workers deployments, trigger the Deploy to Cloudflare Workers workflow from your fork's Actions tab.


Known Issues

None at release time.


Changelog Summary

  • feat(licensing): Ed25519 entitlement verification layer (server/licensing/)
  • feat(licensing): Cloud account binding via device-code flow
  • feat(licensing): 6h background entitlement refresh with manual "Refresh now" button
  • feat(ui): Settings → Billing page (bound / unbound states)
  • feat(ui): <ProBadge /> and <UpgradeHint /> frontend primitives
  • feat(branding): White-label settings page (logo, favicon, wordmark, footer toggle)
  • feat(quota-store): Pro-gated admin package catalog and store settings with Cloud sync status
  • feat(quota-store): Terminal-user storage checkout, target org selection, gift-card credit redemption, and order history
  • feat(quota-store): Authenticated Cloud quota-change webhook with idempotent order quota changes
  • feat(quota-store): Effective quota now combines admin base quota with Cloud storage and traffic entitlements
  • feat(gates): Open registration gated behind open_registration Pro feature
  • feat(gates): Team creation blocked after 1 extra team for Free (teams_unlimited required for the 3rd org)
  • feat(gates): Storage creation blocked at 3 for Free (storages_unlimited required for the 4th storage)