Entitlements
Whether the workspace has paid for a module, and what each state allows.
An entitlement records whether a workspace has a module. There is one per module. Each records the package it was bought on (Essentials, Operations, Professional, Complete or Enterprise; a pack records the package its Pro module count needs; Safety Vision is its own line, safety-vision, on the cameras module; orders from before 20 October 2026 keep Lite, Base, Standard or Advanced), the SKU, a state, and an enabled switch the owner controls.
States
| State | Highest level anyone gets | When |
|---|---|---|
locked | none | Never bought, or lapsed past day 30. New entitlements start here. |
active | as granted | Paid and current. |
grace | as granted | Payment failed. Full access for 7 days while it is fixed. |
read_only | view | Day 8 to day 30 after a failed payment. You can read and export, not change or send. |
After the lock, data is kept for 90 days.
If the owner switches a module off (enabled = false), everyone gets none for it, whatever the state. A locked module cannot be switched on.
Who can change what
- Only the store can change the package, the SKU, the state or the period end. In this build that is the demo checkout on the site, which sends a signed message to the app.
- The owner can only flip
enabled, and only when the module is not locked. - No user, the owner included, can unlock a module by editing the database. The columns are not writable for signed-in users.
The store message
The site's demo checkout builds a body like this:
{ "orderId": "PV-…", "workspaceSlug": "aurora-marine", "sku": "tanker", "modules": ["noon", "emissions", "documents", "pms", "insurance"], "vessels": 4, "state": "active", "annualUsd": 57800, "oneOffUsd": 19944, "firstChargeUsd": 77744, "consent": { … }, "term": { … }, "trial": { … }, "chargeTodayUsd": 0, "payment": { "provider": "demo", "reference": "pm_demo_visa_4242", … } }It signs timestamp.body with HMAC-SHA256 using a shared secret and sends the timestamp and signature in the x-pv-timestamp and x-pv-signature headers to /api/entitlements on the app. The app rejects a bad signature and anything older than 5 minutes.
The secret is PRUVA_ENTITLEMENTS_SECRET. In demo mode outside production a fixed local value is used when it is not set. Local database mode writes with the service role, so the app requires a real secret there: set the same PRUVA_ENTITLEMENTS_SECRET for the app and the site. In production the site refuses to sign without it.
Access levels
none, view, edit and publish, set per person and per module. Ship and shore are not split by vessel in this build.
Billing & renewal
The 1-month trial after a 10% deposit, the 3-year contract with a year-1 exit, annual billing, renewal, Cancel trial, Cancel renewal, no refunds for the year already started, and what each order stores.