setup-guides

Cornerstone Plus — launch runbook

Status: freemium gate + Stripe checkout BUILT, shipping OFF (CORNERSTONE_PLUS_ENFORCED unset → nothing paywalled). Sibling of FAMILY_WALL_PLUS_LAUNCH.md; same shape, same subscriptions table (plan_key = 'cornerstone').

The model — own-the-key custody stays free; pay for the recurring-cost layer

  • Free — the full encrypted vault (notes, photos, documents), the console, and the device mesh (each device keeps a local encrypted replica). This is the honest-custody core — you hold the key, we store only ciphertext — and it costs us nothing marginal, so it's free.
  • Plus — the off-site encrypted backup (Glacier): a second encrypted copy we hold on a separate provider, so a house fire or a lost-every-device event isn't the end. It's the same ciphertext (we still can't read it) and it's a real recurring storage cost — the one thing it's honest to charge for. (Expanded storage quota + more mesh devices are the natural v2 gates.)

What's gated (service cornerstone-plus.ts, no-op until enforced): POST /api/cornerstone/backup/run and POST /api/cornerstone/backup/restorerequireCornerstonePlus → 402 with upgrade:true. GET /api/cornerstone/backup/status returns { plus, enforced } so the vault page shows the upsell.

One-time setup

1. Create two recurring Stripe prices

On the "Cornerstone Plus" product: a monthly and an annual price (pick the anchor — e.g. $8/mo · $79/yr — higher than Family Wall since it carries real per-family storage cost).

2. Set the price env vars (Railway backend)

STRIPE_PRICE_CORNERSTONE=price_...            # monthly
STRIPE_PRICE_CORNERSTONE_ANNUAL=price_...     # annual

Until set, /api/payment/cornerstone/config reports available:false and the vault page's Get-Plus button self-hides — nothing breaks.

3. Grandfather early families (do this BEFORE flipping the flag)

Comp everyone who already built a vault, so the flip never yanks their off-site backup. No Stripe charge — insert active subscription rows directly.

Grandfather everyone with a vault item (the "already using it" cohort):

insert into subscriptions (user_id, plan_key, status, current_period_end, cancel_at_period_end)
select distinct v.owner_id, 'cornerstone', 'active', now() + interval '10 years', false
from cornerstone_vault v
where not exists (
  select 1 from subscriptions s
  where s.user_id = v.owner_id and s.plan_key = 'cornerstone'
);

current_period_end far out + cancel_at_period_end = false = a comp that doesn't lapse; these rows have no stripe_subscription_id, which is fine (never touched by the webhook; isCornerstonePlus() only checks status + period).

4. Flip the master switch

Set CORNERSTONE_PLUS_ENFORCED=true on the backend. Off-site backup + restore now require Plus; the vault, console, and device mesh stay free for everyone.

5. Verify

A non-Plus test account hits the upsell when it taps "Back up off-site now"; a grandfathered (or subscribed) account backs up normally.

How activation works (no new code)

The same POST /api/payment/webhook that handles every other tier upserts a subscriptions row with plan_key = 'cornerstone' on checkout.session.completed (price → plan_key map in payment.ts). Cancellation flows through the shared, plan-key-aware path.

Rollback

Unset CORNERSTONE_PLUS_ENFORCED → the gate no-ops and everything is free again instantly.

CORNERSTONE PLUS LAUNCH — Docs | HiveJournal