From c8fb5a2705bea6289170feaaf35e238d79f1b1f9 Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Mon, 26 Oct 2026 03:00:29 -0700 Subject: [PATCH] fix(pricing): remove landing claims that code does not back (TASK-015) Walked the Pricing section against main and removed or scoped the claims the code does not back: - "10 hosted agent seats" and "coming soon": the default is 1 hosted agent per user on every plan, and hosted agents are live. - "SSO/SAML" and "federation across instances": #2856 (bafad4060) already changed both in the pricing copy on main. There is no SSO code, and federation is cross-pod inside one instance. - "30 days of message history" is a ceiling, not a guarantee. - "Pro adds hosted agents": Cloud free gets hosted agents too. Pro gets a higher turn cap (200 vs 30 a day per agent). - "Pro adds unlimited history" now says "in pods you create": the pod's creator governs retention, not any member. The enterprise line keeps main's "SSO/SAML is planned" (#2856). The guard allows that phrase and no other SSO/SAML copy. Adds guard tests for the scoped history line and the hosted-agent turn caps (200 and 30 a day per agent, both locales), and Pricing verdict rows in docs/audits/claim-table.md. The zh-CN wording is a draft for GTM review. Co-authored-by: wren Agent-Model: claude/claude-haiku-5-5/max Co-Authored-By: Claude Haiku 5.5 --- docs/audits/claim-table.md | 18 ++++++++- frontend/src/i18n/locales/en.json | 6 +-- frontend/src/i18n/locales/zh-CN.json | 6 +-- .../__tests__/pricing-copy-invariants.test.ts | 38 +++++++++++++++++++ 4 files changed, 60 insertions(+), 8 deletions(-) diff --git a/docs/audits/claim-table.md b/docs/audits/claim-table.md index a7817684e4..0ef84238bd 100644 --- a/docs/audits/claim-table.md +++ b/docs/audits/claim-table.md @@ -111,10 +111,24 @@ Owner: TASK-013. Source: `docs-site/deployment/index.mdx`, `docker.mdx`, `kubern Owner: TASK-015. The docs site has no pricing page. `commonly.me/pricing` redirects to the landing page's pricing section (`frontend/nginx.conf:36`), which renders at `frontend/src/v2/landing/V2LandingPage.tsx:827` with its copy under `landing.pricing` in -`frontend/src/i18n/locales/en.json:981`. +`frontend/src/i18n/locales/en.json:978`. + +Evidence below is `main` at `75d80918e`. Rows marked "Removed here" or "Now" are fixed in the PR that +adds them. Rows that name another PR were already fixed on main. | Page | Claim | Verdict | Evidence | Date | |---|---|---|---|---| +| Landing pricing | "10 hosted agent seats" | FALSE | `backend/services/hostedRuntimeService.ts:36` `DEFAULT_AGENTS_PER_USER = 1`, the same on every plan (`:58`). Removed here. | 2026-10-26 | +| Landing pricing | "coming soon, included when it lands" (hosted) | FALSE | Hosted agents run now; per-plan caps are wired in `backend/services/hostedRuntimeService.ts:58-61`. Removed here. | 2026-10-26 | +| Landing pricing | "SSO/SAML" | FALSE | No code: `git grep -i saml origin/main -- backend frontend/src` matches only the copy (`en.json:1021`, `zh-CN.json:1020`). Main already reads "SSO/SAML is planned" (#2856, `bafad4060`). This PR keeps that wording, and the guard allows that phrase and no other SSO/SAML copy. | 2026-10-26 | +| Landing pricing | "federation across instances" | FALSE | Federation is cross-pod inside one instance: `backend/server.ts:271`, `backend/services/federationService.ts:1-5`. `federationProtocol` is only declared in the schema (`backend/models/Installable.ts:242`, `:482`). Removed on main by #2856 (`bafad4060`). | 2026-10-26 | +| Landing pricing | "30 days of message history" (as a flat promise) | FALSE | `backend/services/pgRetentionService.ts:47` (30-day default), `:51` (1-day floor), `:267-296` (steps down under disk pressure). Now "Up to 30 days". | 2026-10-26 | +| Landing pricing | "Pro adds hosted agents" | FALSE | Cloud free gets hosted agents too: the agent cap ignores plan (`backend/services/hostedRuntimeService.ts:58`); the plan only changes turns (`:60-61`). Now "a higher hosted-agent turn cap". | 2026-10-26 | +| Landing pricing | "Pro adds unlimited history" (unscoped) | FALSE | Only pods the buyer created: `backend/services/pgRetentionService.ts:125-150` (the pod's creator governs), `:166` (Pro flag read on the creator). Now "in pods you create". | 2026-10-26 | +| Landing pricing | "Up to 30 days of message history" (Cloud free) | TRUE | `backend/services/pgRetentionService.ts:47,51,267-296`. A ceiling: disk pressure can shorten it. | 2026-10-26 | +| Landing pricing | "Unlimited history in pods you create" (Pro) | TRUE | `backend/services/pgRetentionService.ts:125-150,166`. | 2026-10-26 | +| Landing pricing | "Hosted agents: 200 turns a day per agent, against 30 on Cloud free" | TRUE | Code defaults `backend/services/hostedRuntimeService.ts:38-39`, plan switch `:60-61`, per agent per UTC day `:11-13`. Live env not read: no `HOSTED_TURNS*` override in repo k8s or docs. | 2026-10-26 | +| Landing pricing | "Community listing" (Pro) | TRUE | `backend/routes/pods.ts:768-770`: a non-admin pod creator without `entitlements.pro` gets 402 `pro_required`. | 2026-10-26 | ## Other customer surfaces @@ -125,7 +139,7 @@ the FAQ, and `CONTRIBUTING.md`. The section is complete when every surface in th | Page | Claim | Verdict | Evidence | Date | |---|---|---|---|---| | In-app Plan (`billing.proNote`) | "…and hosted agent seats when they land." | FALSE | Hosted agents have shipped. `backend/services/hostedRuntimeService.ts:36` gives a free account one agent (`DEFAULT_AGENTS_PER_USER = 1`). The key has no call site, so the sentence is removed with it. | 2026-10-26 | -| In-app Plan (`billing.freeNote`) | "Unlimited agents you bring, and 30 days of message history. …" | CUT | No call site in `frontend/src` or `backend`. The only other match is a fixture label at `frontend/src/i18n/__tests__/zhNumberUnitSpacing.test.ts:254`, which reads no catalog value. The key is removed. | 2026-10-26 | +| In-app Plan (`billing.freeNote`) | "Unlimited agents you bring, and 30 days of message history. …" | CUT | No call site in `frontend/src` or `backend`. The only other match is a fixture label at `frontend/src/i18n/__tests__/zhNumberUnitSpacing.test.ts:256`, which reads no catalog value. The key is removed. | 2026-10-26 | | Landing, open-source list (`landing.openSource.items.federation`) | "Federation on the roadmap. Agents on different instances will interact — ActivityPub for agents." | CUT | `git grep -i activitypub` finds nothing under `backend`, `frontend/src` or `docs-site`. The federation code that exists is cross-pod queries (`backend/services/federationService.ts:1-5`). The bullet is removed; it can return with a shipped track or a walk. | 2026-10-26 | | CONTRIBUTING.md | "pick up an open issue labeled `good first issue` on GitHub" (the label-query link on main before this PR) | FALSE | `gh issue list -R Team-Commonly/commonly --label "good first issue" --state open` returned 0 on 2026-10-26, so the link opened an empty list. The earlier task-board link, `app-dev.commonly.me/pods/team`, was already dead (`CLAUDE.md`, Quick Start). This PR replaces the link with the open-issues list, which had 138 open issues on 2026-10-27. | 2026-10-27 | | CONTRIBUTING.md | "you can install it into the public Commonly instance" (OpenClaw) | TRUE | The provider route exists: `backend/routes/providers/moltbot.ts:4`. This row checks the code path only, not a contributor install on commonly.me. | 2026-10-26 | diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 3348b9eba5..6d468cdb1b 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -978,7 +978,7 @@ "pricing": { "kicker": "Pricing", "title": "Humans are seats. Agents never are.", - "sub": "Self-host free forever under Apache-2.0. On the hosted plans, agents you bring connect free and unlimited — on every tier, forever. Pro adds unlimited history, Community listing and hosted agents. Never per agent.", + "sub": "Self-host free forever under Apache-2.0. On the hosted plans, agents you bring connect free and unlimited — on every tier, forever. Pro adds unlimited history in pods you create, Community listing and a higher hosted-agent turn cap. Never per agent.", "zero": "$0", "selfHost": { "name": "Self-host", @@ -998,7 +998,7 @@ "items": { "unlimited": "Unlimited BYO agents — they run on your machines, connect free", "private": "Private and invited pods", - "history": "30 days of message history", + "history": "Up to 30 days of message history", "connect": "Connect via webhook, CLI wrapper, or MCP", "card": "No credit card" } @@ -1012,7 +1012,7 @@ "everything": "Everything in Cloud free", "history": "Unlimited history in pods you create — nothing expires at 30 days", "community": "List your pods in Community so your team can find them", - "hosted": "10 hosted agent seats — our compute, zero setup (coming soon, included when it lands)", + "hosted": "Hosted agents: 200 turns a day per agent, against 30 on Cloud free — our compute, zero setup", "support": "Priority support" } }, diff --git a/frontend/src/i18n/locales/zh-CN.json b/frontend/src/i18n/locales/zh-CN.json index 7fb4816699..1fdd560fa3 100644 --- a/frontend/src/i18n/locales/zh-CN.json +++ b/frontend/src/i18n/locales/zh-CN.json @@ -977,7 +977,7 @@ "pricing": { "kicker": "定价", "title": "人类占席位,智能体永远不占。", - "sub": "基于 Apache-2.0,自托管永久免费。托管版中,你自带的智能体在所有档位都免费且不限数量——永远如此。Pro 增加无限历史记录、社区收录与托管智能体。绝不按智能体收费。", + "sub": "基于 Apache-2.0,自托管永久免费。托管版中,你自带的智能体在所有档位都免费且不限数量——永远如此。Pro 增加你创建的 Pod 内无限历史记录、社区收录,以及更高的托管智能体每日轮数上限。绝不按智能体收费。", "zero": "$0", "selfHost": { "name": "自托管", @@ -997,7 +997,7 @@ "items": { "unlimited": "不限数量的自带智能体 —— 跑在你自己的机器上,免费接入", "private": "私密 Pod 与邀请制 Pod", - "history": "30天消息历史", + "history": "最长30天消息历史", "connect": "通过 webhook、CLI wrapper 或 MCP 接入", "card": "无需信用卡" } @@ -1011,7 +1011,7 @@ "everything": "包含 Cloud free 的全部功能", "history": "你创建的 Pod 内消息历史无限 —— 不再有30天过期", "community": "把你的 Pod 收录进「社区」,方便团队找到", - "hosted": "10个托管智能体席位 —— 我们的算力,零配置(即将推出,上线即包含)", + "hosted": "托管智能体:每个智能体每天200轮,云端免费版为30轮 —— 我们的算力,零配置", "support": "优先支持" } }, diff --git a/frontend/src/v2/__tests__/pricing-copy-invariants.test.ts b/frontend/src/v2/__tests__/pricing-copy-invariants.test.ts index 1cfdc8ae06..c366b36e87 100644 --- a/frontend/src/v2/__tests__/pricing-copy-invariants.test.ts +++ b/frontend/src/v2/__tests__/pricing-copy-invariants.test.ts @@ -69,3 +69,41 @@ describe('landing pricing copy', () => { expect(zhCN.landing.pricing.pro.price).toBe(en.landing.pricing.pro.price); }); }); + +/** + * Claims the TASK-015 walk checked against code and found false. Each is a + * promise a buyer would hold us to: SSO/SAML and cross-instance federation have + * no code behind them, the hosted caps are 1 agent per user on every plan, and + * hosted agents shipped, so "coming soon" is wrong. The history line keeps its + * "up to": disk pressure can shorten retention below 30 days. Unlimited history + * is a Pro promise only for pods the buyer created: the pod's creator governs + * retention, not any member. + */ +describe('landing pricing claims that code does not back', () => { + Object.entries(LOCALES).forEach(([locale, bundle]) => { + test(`${locale}: no SSO/SAML, federation, hosted-seat, or coming-soon promise`, () => { + // "SSO/SAML is planned" is the one allowed mention (main, #2856). Strip it + // first, so any other SSO/SAML copy still fails. + const pricing = JSON.stringify(bundle.landing.pricing).replace(/SSO\/SAML (is planned|已在计划中)/g, ''); + expect(pricing).not.toMatch(/SSO|SAML|federation across|跨实例联邦/); + expect(pricing).not.toMatch(/10 hosted agent seats|10个托管智能体席位|coming soon|即将推出/); + }); + }); + + test('message history is stated as a ceiling, not a guarantee', () => { + expect(en.landing.pricing.cloud.items.history).toMatch(/^Up to 30 days/); + expect(zhCN.landing.pricing.cloud.items.history).toMatch(/^最长30天/); + }); + + test('Pro unlimited history is scoped to pods the buyer creates', () => { + expect(en.landing.pricing.sub).toMatch(/unlimited history in pods you create/); + expect(zhCN.landing.pricing.sub).toMatch(/你创建的 Pod 内无限历史记录/); + }); + + test('hosted-agent turn caps are stated per agent per day, at the plan defaults', () => { + // 200 and 30 are the plan defaults in hostedRuntimeService.ts. Pinning both + // numbers and the per-agent scope fails if either limit or the scope changes. + expect(en.landing.pricing.pro.items.hosted).toMatch(/200 turns a day per agent, against 30 on Cloud free/); + expect(zhCN.landing.pricing.pro.items.hosted).toMatch(/每个智能体每天200轮,云端免费版为30轮/); + }); +});