Skip to content

Commit 413ffc9

Browse files
fix(pricing): strict-≥80%-margin redesign — Team is finite, not "unlimited" (#188)
CEO directive (2026-06-05, LOCKED): retire every "unlimited" tier claim and show the real finite plans.yaml caps. Hard-caps only. Team stays GATED (this PR does NOT touch the checkout/CTA gate) — copy-only. - PricingPage.tsx: Team column was `unlimited` on every limit; now renders the finite caps (50 GB PG/100 conn, 1.5 GB Redis, 40 GB Mongo/50 conn, 40 GB queue, 30 GB vector, 300 GB storage, 100k webhooks, 100 deploys, 1000 vault). Pro queue 10 GB → 5 GB, anon/hobby queue 1 GB/5 GB → 64 MB/2 GB. Removed the `UNLIMITED` cell helper. Growth blurb de-"unlimited"-ed. - PricingGrid.tsx (dashboard upsell) + MarketingPage.tsx: Team feature bullets changed from "unlimited X" to the finite caps + "need more? contact sales". - BillingPage.tsx LIMITS fallback: team Infinity → finite; growth mongodb/webhooks Infinity → finite; growth deploys 5 → 50 (display-accuracy). - public/llms.txt: anon NATS 1024→64 MB; Team "unlimited everything" → finite caps + Enterprise-above-Team. Keeps the "not yet a self-serve tier" gate marker (llmsContract.test.ts). NOTE (pre-existing, out of scope): MarketingPage Team CTA still points at the self-serve /app/checkout?plan=team path which the server gate 400s — flagged in a code comment for a follow-up gate-reconciliation PR (not changed here). Rule-22 synchronized pricing redesign (strict-80%, retire unlimited) — see docs/sessions/2026-06-04/PRICING-MARGIN-MODEL-AND-TEAM-REDESIGN.md. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 4cf37ca commit 413ffc9

5 files changed

Lines changed: 68 additions & 57 deletions

File tree

public/llms.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ Pick a descriptive name per resource (e.g. `"prod-db"`, `"sessions-cache"`, `"ev
6161
- Postgres: 10 MB storage, 2 simultaneous connections.
6262
- Redis: 5 MB total.
6363
- MongoDB: 5 MB storage, 2 connections per token (shared pod cap up to 20 across all anonymous tokens).
64-
- NATS: 1024 MB JetStream storage.
64+
- NATS: 64 MB JetStream storage.
6565
- S3-compatible storage: 10 MB bucket prefix.
6666
- Webhook: keeps last 100 received payloads.
6767
- Deploy: not available on anonymous tier (requires Hobby or above — plans.yaml deployments_apps=0 for anonymous).
@@ -100,7 +100,7 @@ on `/api/v1/capabilities` for agent introspection.
100100

101101
> **Upgrading auto-promotes in-flight deployment TTLs.** When a team upgrades to any paid tier (Hobby / Hobby Plus / Pro / Growth / Team), the Razorpay subscription.charged webhook flips the team's `default_deployment_ttl_policy` from `auto_24h` to `permanent` (so every future `POST /deploy/new` defaults to no TTL) AND promotes every existing `auto_24h` non-terminal deploy to permanent (clearing `expires_at`). Per-deploy `ttl_policy='custom'` and `ttl_policy='permanent'` rows are never touched — only the `auto_24h` class is rolled forward. To restore the 24h-default behaviour after an upgrade, `PATCH /api/v1/team/settings {"default_deployment_ttl_policy":"auto_24h"}`.
102102
- **Pro**: $49/mo. 10 GB Postgres, 512 MB Redis, 5 GB Mongo, 50 GB storage, 10 apps.
103-
- **Team**: available soon — not yet self-serve. Planned at $199/mo with unlimited everything (Postgres / Redis / Mongo / queues / storage / deployments / vault entries), 50 custom domains, 90-day backups with self-serve restore, RBAC + audit log; SSO/SAML and a 99.9% SLA are also planned. Team cannot be purchased or claimed today — contact support@instanode.dev for onboarding.
103+
- **Team**: available soon — not yet self-serve. Planned at $199/mo with high finite limits (50 GB Postgres, 1.5 GB Redis, 40 GB Mongo, 40 GB queues, 300 GB storage, 30 GB vector, 100 deployments, 1000 vault entries, 100k webhooks), 50 custom domains, 90-day backups with self-serve restore, RBAC + audit log; SSO/SAML and a 99.9% SLA are also planned. Capacity beyond these caps (or dedicated/isolated infra, multi-region, or compliance such as SOC2/BAA/SSO/SLA/DPA) is Enterprise — contact sales. Team cannot be purchased or claimed today — contact support@instanode.dev for onboarding.
104104

105105
## Conventions an LLM should follow when scripting against the platform
106106

src/components/PricingGrid.tsx

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,11 @@ export const PRICING_GRID_TIERS: TierDefinition[] = [
168168
upgradesTo: 'team',
169169
},
170170
{
171-
// Team — launched 2026-05-20 (DOC-REALITY-DELTA sweep). plans.yaml:375
172-
// has team at $199/mo, $1990/yr, every limit -1 (unlimited).
173-
// CTA goes via buildCtaLabel('team') → "Contact sales" until the
174-
// assisted-Razorpay self-serve flow ships.
171+
// Team — $199/mo, $1990/yr. strict-80% margin redesign (2026-06-05):
172+
// every Team limit is now a finite plans.yaml cap (was -1/unlimited).
173+
// Above these caps = Enterprise (contact sales). CTA goes via
174+
// buildCtaLabel('team') → "Contact sales" until the assisted-Razorpay
175+
// self-serve flow ships.
175176
key: 'team',
176177
label: 'Team',
177178
monthly: { price: '$199', sub: '/mo' },
@@ -182,11 +183,12 @@ export const PRICING_GRID_TIERS: TierDefinition[] = [
182183
yearlyTotal: '$1990/yr',
183184
},
184185
features: [
185-
{ text: 'unlimited Postgres · Redis · MongoDB · queues · storage' },
186-
{ text: 'unlimited deployments · 50 custom domains' },
187-
{ text: 'unlimited vault entries · multi-env' },
186+
{ text: '50 GB Postgres · 1.5 GB Redis · 40 GB MongoDB' },
187+
{ text: '40 GB queues · 300 GB object storage' },
188+
{ text: '100 deployments · 50 custom domains' },
189+
{ text: '1 000 vault entries · multi-env' },
188190
{ text: '90-day backups · self-serve restore' },
189-
{ text: 'RBAC + audit log' },
191+
{ text: 'RBAC + audit log · need more? contact sales' },
190192
{ text: 'SSO / SAML (coming soon)' },
191193
{ text: '99.9% SLA (coming soon)' },
192194
],

src/pages/BillingPage.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -119,22 +119,24 @@ const LIMITS: Record<string, { label: string; limits: PlanLimits; nextTier?: Tie
119119
},
120120
// D5 (2026-05-18): growth row was missing — a growth-tier team fell
121121
// through to `LIMITS.hobby`, painting red over-quota bars on a plan they
122-
// were well within. Numbers mirror api/plans.yaml growth: postgres 20480
123-
// MB, redis 1024 MB, mongodb / webhooks unlimited (-1 in plans.yaml →
124-
// Infinity here), 5 deployments, 10 team_members.
122+
// were well within. strict-80% margin redesign (2026-06-05): growth's
123+
// formerly-unlimited (-1) mongodb/webhooks are now finite; deployments
124+
// corrected 5 → 50. Numbers mirror api/plans.yaml growth.
125125
growth: {
126126
label: 'Growth',
127127
nextTier: 'team',
128128
limits: {
129-
postgres_mb: 20480, redis_mb: 1024, mongodb_mb: Infinity,
130-
deployments: 5, webhooks: Infinity, team_seats: 10,
129+
postgres_mb: 20480, redis_mb: 1024, mongodb_mb: 20480,
130+
deployments: 50, webhooks: 100000, team_seats: 10,
131131
},
132132
},
133+
// strict-80% margin redesign (2026-06-05): every Team limit is now finite
134+
// (was Infinity, mirroring the retired -1 sentinel). Mirrors api/plans.yaml.
133135
team: {
134136
label: 'Team',
135137
limits: {
136-
postgres_mb: Infinity, redis_mb: Infinity, mongodb_mb: Infinity,
137-
deployments: Infinity, webhooks: Infinity, team_seats: Infinity,
138+
postgres_mb: 51200, redis_mb: 1536, mongodb_mb: 40960,
139+
deployments: 100, webhooks: 100000, team_seats: 25,
138140
},
139141
},
140142
}

src/pages/MarketingPage.tsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -200,23 +200,25 @@ const PLANS: Plan[] = [
200200
cta: { label: 'Start pro →', href: ROUTES.signin, variant: 'primary' },
201201
},
202202
{
203-
// Team tier — launched 2026-05-20 (DOC-REALITY-DELTA sweep).
204-
// plans.yaml:375 has team at $199/mo, every limit -1 (unlimited).
205-
// DOG-1: CTA is self-serve via /app/checkout?plan=team (Razorpay plan IDs
206-
// are configured server-side as of api#168 + dashboard #106). Previous
207-
// mailto Team CTA contradicted the "Self-serve at every tier. No sales
208-
// call." H2 directly above this tile and leaked mid-funnel conversions
209-
// on the $199/mo AOV path.
203+
// Team tier — $199/mo. strict-80% margin redesign (2026-06-05): every
204+
// Team limit is now a finite plans.yaml cap (was -1/unlimited). Above
205+
// these caps = Enterprise (contact sales).
206+
// NOTE (pre-existing, out of scope here): this CTA still points at the
207+
// self-serve /app/checkout?plan=team path, which the server-side Team
208+
// gate rejects with 400 tier_not_yet_available. The PricingPage Team CTA
209+
// already uses a contact-sales mailto. This MarketingPage CTA should be
210+
// reconciled with the gate in a follow-up (not changed here — the
211+
// strict-margin task is explicitly copy-only and must not touch the gate).
210212
id: 'team',
211213
name: 'Team',
212214
tagline: 'For the engineering org. Dedicated infra, RBAC + audit, with SSO & SLA on the way.',
213215
price: '$199',
214216
freq: '/ mo',
215217
features: [
216-
'unlimited Postgres · Redis · MongoDB',
217-
'unlimited deployments · 50 custom domains',
218+
'50 GB Postgres · 1.5 GB Redis · 40 GB MongoDB',
219+
'100 deployments · 50 custom domains',
218220
'90-day backups · self-serve restore · RBAC + audit',
219-
'SSO/SAML · 99.9% SLA (coming soon)',
221+
'Need more? Enterprise — contact sales',
220222
],
221223
cta: {
222224
label: 'Start team →',

src/pages/PricingPage.tsx

Lines changed: 35 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,11 @@ const TIERS: {
8585
ctaHrefYearly: '/app/checkout?plan=pro&frequency=yearly',
8686
highlighted: true,
8787
},
88-
// Team tier — $199/mo, unlimited across the board; the upsell vs Pro is
89-
// dedicated infra + 90-day backup retention + SLA + RBAC + SAML. Per
90-
// api/plans.yaml:375 ($199/mo, $1990/yr).
88+
// Team tier — $199/mo, high finite limits across the board (NOT unlimited
89+
// as of the 2026-06-05 strict-margin redesign); the upsell vs Pro is
90+
// dedicated infra + 90-day backup retention + SLA + RBAC + SAML + far
91+
// higher caps. Anyone needing more than the finite Team caps is routed to
92+
// Enterprise (contact sales). Per api/plans.yaml ($199/mo, $1990/yr).
9193
{
9294
key: 'team',
9395
name: 'Team',
@@ -119,50 +121,52 @@ type Cell =
119121
// lock-step with the TIERS array above.
120122
type Row = { label: string; sub?: string; values: [Cell, Cell, Cell, Cell] }
121123

122-
// UNLIMITED — Team-tier marker. plans.yaml uses -1 sentinel for "no cap"
123-
// on every Team limit; we render this as the visual "unlimited" cell.
124-
// Kept as its own helper so a future redesign can swap in a richer
125-
// visualisation (e.g. lightning-bolt icon) without rewriting every row.
126-
const UNLIMITED: Cell = 'unlimited'
124+
// strict-80% margin redesign (2026-06-05): Team is no longer "unlimited" —
125+
// every Team limit is now a finite plans.yaml cap rendered directly in the
126+
// Team column below. The former `UNLIMITED` cell helper was removed; anyone
127+
// needing MORE than the finite Team caps is routed to Enterprise (contact
128+
// sales), surfaced in the Team tier description + the section copy.
127129

128130
// Each row has 4 cells: [Anonymous, Hobby, Pro, Team]. Numbers come from
129131
// api/plans.yaml. 2026-05-15: Hobby Plus column removed (the tier exists
130132
// for upsell flows but is not part of the public ladder); Pro storage
131133
// bumped per PRICING-AUDIT-2026-05-15.md (Postgres 5→10 GB, Redis
132-
// 256→512 MB, Mongo 2→5 GB, object 10→50 GB). 2026-05-20: Team tier
133-
// launched — every limit -1 in plans.yaml becomes 'unlimited' here.
134+
// 256→512 MB, Mongo 2→5 GB, object 10→50 GB).
135+
// strict-80% margin redesign (2026-06-05): every Team -1 ("unlimited")
136+
// replaced with the finite plans.yaml cap (Postgres 50 GB / 100 conn,
137+
// Redis 1.5 GB, Mongo 40 GB / 50 conn, Queue 40 GB, Vector 30 GB, Storage
138+
// 300 GB, Webhooks 100k, Deploy apps 100, Vault 1000). Pro queue trimmed
139+
// 10 GB → 5 GB in the same pass. Above Team = Enterprise (contact sales).
134140
const ROWS: Row[] = [
135-
{ label: 'Postgres', values: ['10 MB / 2 conn / 24h TTL', '1 GB / 8 conn', '10 GB / 20 conn', UNLIMITED] },
136-
{ label: 'Redis', values: ['5 MB / 24h TTL', '50 MB', '512 MB', UNLIMITED] },
137-
{ label: 'MongoDB', values: ['5 MB / 2 conn / 24h TTL', '100 MB / 5 conn', '5 GB / 20 conn', UNLIMITED] },
141+
{ label: 'Postgres', values: ['10 MB / 2 conn / 24h TTL', '1 GB / 8 conn', '10 GB / 20 conn', '50 GB / 100 conn'] },
142+
{ label: 'Redis', values: ['5 MB / 24h TTL', '50 MB', '512 MB', '1.5 GB'] },
143+
{ label: 'MongoDB', values: ['5 MB / 2 conn / 24h TTL', '100 MB / 5 conn', '5 GB / 20 conn', '40 GB / 50 conn'] },
138144
// FIX-G (2026-05-14): the column used to advertise "1 000 / 5 000 / 100k
139145
// msg/d" but there's no backing queue_messages_per_day field on the
140-
// plans.yaml side — quota enforcement is on queue_storage_mb. Shipping
141-
// a per-day-msg counter is real scope and not in flight, so the copy
142-
// moves to the field we actually enforce. Numbers mirror plans.yaml
143-
// queue_storage_mb (anonymous=1024, hobby=5120, pro=10240).
144-
{ label: 'Queue', sub: 'NATS storage', values: ['1 GB / 24h TTL', '5 GB', '10 GB', UNLIMITED] },
145-
// Vector — added 2026-05-20 to surface /vector/new alongside the other
146-
// services. plans.yaml vector_storage_mb: anon=10, hobby=500, pro=10240,
147-
// team=-1 (unlimited).
148-
{ label: 'Vector', sub: 'pgvector', values: ['10 MB / 24h TTL', '500 MB', '10 GB', UNLIMITED] },
146+
// plans.yaml side — quota enforcement is on queue_storage_mb. Numbers
147+
// mirror plans.yaml queue_storage_mb (anon=64 MB, hobby=2 GB, pro=5 GB,
148+
// team=40 GB) after the 2026-06-05 strict-margin trim.
149+
{ label: 'Queue', sub: 'NATS storage', values: ['64 MB / 24h TTL', '2 GB', '5 GB', '40 GB'] },
150+
// Vector — plans.yaml vector_storage_mb: anon=10 MB, hobby=500 MB,
151+
// pro=10 GB, team=30 GB (strict-margin 2026-06-05).
152+
{ label: 'Vector', sub: 'pgvector', values: ['10 MB / 24h TTL', '500 MB', '10 GB', '30 GB'] },
149153
// Anonymous storage: plans.yaml storage_storage_mb=10 (anonymous tier).
150154
// PB04 P1 (2026-05-21): cell used to render '—' which contradicted the
151155
// shipped backend — anonymous /storage/new returns a real 10 MB bucket.
152-
{ label: 'Storage', values: ['10 MB / 24h TTL', '512 MB', '50 GB', UNLIMITED] },
153-
{ label: 'Webhook stored', values: ['100', '1 000', '10k', UNLIMITED] },
156+
{ label: 'Storage', values: ['10 MB / 24h TTL', '512 MB', '50 GB', '300 GB'] },
157+
{ label: 'Webhook stored', values: ['100', '1 000', '10k', '100k'] },
154158
// 2026-05-20: dropped "small / medium" pod-size adjectives — there is no
155159
// deployment_size field on api/internal/handlers/deploy.go. Numbers map
156-
// to plans.yaml deployments_apps (hobby=1, pro=10, team=-1 → unlimited).
157-
{ label: 'Deploy apps', values: [{ mark: 'dash' }, '1', '10', UNLIMITED] },
160+
// to plans.yaml deployments_apps (hobby=1, pro=10, team=100 finite).
161+
{ label: 'Deploy apps', values: [{ mark: 'dash' }, '1', '10', '100'] },
158162
{ label: 'Domains', values: [{ mark: 'dash' }, '*.deployment.instanode.dev', 'custom domain', '50 custom domains'] },
159163
// Multi-env workflows (stack promotion + vault copy across envs) is a
160164
// shipped Pro-tier feature: POST /api/v1/stacks/:slug/promote and
161165
// POST /api/v1/vault/copy are live (RETRO-2026-05-12 §10.17). Hobby is
162166
// single-env (production only).
163167
{ label: 'Multi-env workflows', sub: 'stack promotion + vault copy', values: [{ mark: 'dash' }, { mark: 'dash' }, 'dev / staging / prod', 'dev / staging / prod'] },
164168
{ label: 'RBAC + audit', values: [{ mark: 'dash' }, { mark: 'dash' }, { mark: 'dash' }, { mark: 'check' }] },
165-
{ label: 'Vault entries', values: [{ mark: 'dash' }, '20', '200', UNLIMITED] },
169+
{ label: 'Vault entries', values: [{ mark: 'dash' }, '20', '200', '1 000'] },
166170
{ label: 'Vault envs', values: [{ mark: 'dash' }, 'production only', 'multi-env', 'multi-env'] },
167171
{ label: 'Backups', values: [{ mark: 'dash' }, '7-day · no restore', '30-day · 1-click restore', '90-day · self-serve restore'] },
168172
// SSO/SAML + SLA have no backend yet — shown as not-yet-available everywhere
@@ -467,9 +471,10 @@ export function PricingPage() {
467471
"outgrew Hobby's Mongo" step.
468472
</li>
469473
<li data-testid="intermediate-tier-growth">
470-
<strong>Growth · $99/mo.</strong> Unlimited MongoDB, unlimited webhooks, 1 GB Redis,
471-
20 GB Postgres. Pro-tier supporting services without committing to Pro's deployment
472-
ladder. Yearly billing not yet offered on this tier.
474+
<strong>Growth · $99/mo.</strong> 20 GB MongoDB, 100k webhook receivers, 1 GB Redis,
475+
20 GB Postgres, 20 GB queue, 150 GB object storage. Pro-tier supporting services
476+
without committing to Pro's deployment ladder. Yearly billing not yet offered on
477+
this tier.
473478
</li>
474479
</ul>
475480
</section>

0 commit comments

Comments
 (0)