Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/audit/hosted.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ Be adversarial, and go past the `FAIL IF` list; a bare section name is `docs/spe
- **Can a Hosted login become terminal access, or an account become someone else's?** Trace linking, a revoked login's callback, an unused or unknown provider credential, and every path from a login toward a Burrow ACL grant ("Account boundary"; the relay Worker's cookie rule is "Relay boundary"). Pocket and `/connect/` share the relay origin; check what each page's policy lets it reach of the other.
- **Can one account's relay socket reach another's?** Trace an upgrade through `relaySocketRoutes` (`hosted/server/relay-sockets.ts`) into `RelayRoom` (`hosted/server/relay-room.ts`) against "Relay boundary". Look for routing state kept in memory that a hibernated object would lose, a socket torn down twice or routed after its close began, a frame bounded in characters rather than bytes, a `ct` touched outside the shared frame layer's field copy, a Client cap one socket can evict past, a session that outlives its alarm, a ping that wakes the object, a Burrow socket accepted on a row removed after the token check, and a removed or de-entitled Burrow's socket outliving the sweep.
- **Can the rendezvous become more than a handshake pipe?** Trace a frame through `OneTimeRoom` against "Rendezvous boundary". Look for a second phone admitted across an await or a hibernation, a room that outlives its alarm, a web page that can mint a room, a join from another origin, a room id a caller can choose, and a limit a caller can step around. The relay is same-site with the account, so account cookies can ride the phone's upgrade.
- **Can billing grant what Stripe did not sell?** Trace checkout, confirm, the webhook, and the cron resync in `hosted/server/billing-routes.ts` against "Billing boundary": a browser-chosen Price, owner, or return URL, an unsigned or replayed event, a 2xx before the commit, a subscription that lapsed but still admits, and a cohort answer that names more than an opted-in founder chose to show.
- **Does anything from the test or preview build reach production?** Trace the production entries and the preview configuration and workers against "Deployment boundary". Preview credentials and the cleanup checkout are `ci-and-secrets`' (`docs/specs/security-ci.md` -> "Hosted Deployments").

Does the shipped code still match what the spec and this section claim? Spec drift is a finding; say which side is wrong.
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ A spec is the accurate reference for the current code: it states the invariants
- **`docs/specs/one-time.md`** — One-time connection: the link a laptop shows, its Settings panel and Baseboard indicator, the Hosted rendezvous wire that carries only its handshake, the phone page Hosted serves, and the direct-only session; no account, nothing saved.
- **`docs/specs/security-hosted.md`** — Hosted account origin, identity, and deployment security checks.
- **`SELF_HOST.md`** (repo root) — Self-host deployment: the assistant-run install runbook plus the Installer contract that `docs/specs/security-remote.md`'s `FAIL IF` lines and `scripts/deploy-lint.mjs` audit.
- **`docs/specs/pricing.md`** — Pricing (design-stage): the tiers and founding ladder, what the Individual plan grants, how a desktop proves membership, the managed-voice boundary, and the `/hosted` page contract.
- **`docs/specs/pricing.md`** — Pricing: the tiers and founding ladder, what the Individual plan grants, checkout and the subscription entitlement, how a desktop proves membership, the managed-voice boundary, and the `/hosted` page contract.
- **`docs/specs/pocket-app.md`** — Pocket: the remote session is a `PlatformAdapter` (`RemotePtyAdapter`), so Pocket is auth screens plus the mobile composition; owns the same-origin deployment rule.
- **`docs/specs/deploy.md`** — Release process: artifact matrix, release checklist, two-stage sign-and-release pipeline, updater manifest, changelog flow.
- **`docs/specs/security.md`** — The guarantees Dormouse makes, what it does not defend, the known gaps, and how it is all checked; published at `/security`, rows split by audience. Read first for anything security. It alone states each known gap and accepted risk; other security specs and the audit preamble point at it. Root `SECURITY.md` is the GitHub policy pointer at it.
Expand Down
47 changes: 39 additions & 8 deletions docs/specs/hosted.md

Large diffs are not rendered by default.

51 changes: 27 additions & 24 deletions docs/specs/pricing.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
> - See `docs/specs/glossary.md` for Burrow / Client / Relay and Pane / Session vocabulary.
> - **Owns:** the plans, the founding ladder, what a plan grants, how a desktop proves membership, the managed-voice boundary, and the content contract of the Hosted page.
> - **Defers:** page chrome, rail, and link obligations to `docs/specs/website-docs.md` -> "Reference page chrome"; the Hosted Relay's accounts, enrollment, and entitlement, and managed voice's routes, to `docs/specs/hosted.md`; the cloud-hosted trust boundary to `docs/specs/security-remote.md` -> "Cloud-hosted mode"; alarm delivery to `docs/specs/alert.md` -> "Spoken alarms".
> - **Status:** the Hosted page publishes the plans and the FAQ, and a desktop signs in to Hosted for managed voice (`docs/specs/hosted.md` -> "Managed voice"); everything that takes money — checkout, the subscription as the entitlement, the hosted Relay — is under [Future](#future).
> - **Status:** the Hosted page publishes the plans and the FAQ, a desktop signs in to Hosted for managed voice (`docs/specs/hosted.md` -> "Managed voice"), and checkout, the account pages, and the subscription as the entitlement are built and off until Stripe is configured ([Checkout and entitlement](#checkout-and-entitlement)); turning billing on is under [Future](#future).

## The Hosted page

Expand All @@ -13,7 +13,7 @@

**Content, in order:** the plan cards, directly under the title and anchored `#pricing`; what a member gets, as prose; "Self-hosting stays free"; and a short FAQ — refunds and cancellation, the founding lock, who appears in the founders row, what happens if Hosted shuts down, and that team pricing goes by email to `teams@dormouse.sh`.

**Prices, inclusions, and the FAQ are prerendered text**, and the page emits `Product` / `Offer` JSON-LD carrying one `Offer` per paid plan at its current price, so an assistant fetching the page can quote it. **Offers stay `PreOrder` while checkout is unbuilt.**
**Prices, inclusions, and the FAQ are prerendered text**, and the page emits `Product` / `Offer` JSON-LD carrying one `Offer` per paid plan at its current price, so an assistant fetching the page can quote it. **Offers are `PreOrder` until `CHECKOUT_OPEN`, then `InStock`.**

**Every price on the site has one owner**: the page, the structured data, and the tests read `website/src/lib/hosted-pricing.ts` rather than restating a number.

Expand All @@ -31,17 +31,19 @@ Three cards — Free, Hosted, Founding — side by side from `md` up, stacked in

- **Mark the Hosted card with the accent border, never a surface of its own**, which would be a tint no docs token is derived against.
- **The toggle defaults to Monthly**, the prerendered state; switching swaps the price, the billing line, and the buy target in place.
- **A buy button opens the unbuilt-checkout notice** — the plan's name, that nothing was charged and no seat taken, and the devlog. **Never render a buy button that silently does nothing.**
- **A buy button links to the account origin's `/checkout?plan=` once `CHECKOUT_OPEN`**, by checkout's plan names; until then it opens the unbuilt-checkout notice — the plan's name, that nothing was charged and no seat taken, and the devlog. **Never render a buy button that silently does nothing.**

### The founding card's live half

Seats left in the open cohort and the founders row load after hydration from one endpoint; the price beside them is prerendered.

- **Count seats on the server** from the billing provider behind a cache of at most 60 seconds, never on the client and never stored. **Never show a count for a closed cohort.**
- **Count seats on the server** from the billing provider behind a cache of at most 60 seconds, never on the client and never stored. **Never show a count for a closed cohort**: the endpoint names the cohort its seats belong to, and the page drops the seats unless that is the cohort its price was prerendered at.
- **Show a founder only if they opted in at checkout**; the box starts unticked and the account can untick it. Every other founder counts toward the `+N` that ends the row, as does everyone past the row's cap.
- **Must serve avatars from this origin, never the OAuth provider**, so loading the page tells no provider about the reader. The client draws an initial for any avatar that is not a same-origin path, or that fails to load.
- **Never send an OAuth provider's avatar**, so loading the page tells no provider about the reader: the endpoint sends names only, and the client draws an initial for any avatar that is not a same-origin path, or that fails to load. Reserved: avatars proxied onto this origin ([Future](#future)).
- **The page prerenders without the endpoint**: the seats line is reserved and the row absent; an unreachable endpoint, a non-2xx, or a malformed field drops only that field, never an error. **A cohort closing raises the price at the next deploy.**

The endpoint and its cache: `docs/specs/hosted.md` -> "Billing".

**Every existing link keeps working unchanged**: the `linkedFrom` obligations, the root README, `vscode-ext/README.md`, the Settings dialog's voice link, and the hosting notice all already point at `/hosted`. `docs/specs/website-docs.md` -> "Reference page chrome" owns the mechanics.

### Published prices
Expand All @@ -59,17 +61,30 @@ Prices in USD, and the merchant of record adds or includes tax by jurisdiction.
- **The step is $10 per cohort of 100, fixed**, and the ladder's last step is the one below list — reaching list closes founding.
- **Show the current price, the struck list price, and the seats left at that price — never the next step or how many cohorts remain.**

Source of truth: `tiersOnSale`, `foundingTier`, and `pricingJsonLd` in `website/src/lib/hosted-pricing.ts`; `fetchCohort` in `website/src/lib/hosted-cohorts.ts`; `website/src/pages/Hosted.tsx`; the `/pricing` rule in `website/public/_redirects`, pinned by `checkPricingRedirect` in `scripts/public-docs-lint.mjs`. `website/src/pages/Hosted.test.tsx` pins the page contract.
Source of truth: `tiersOnSale`, `foundingTier`, `CHECKOUT_OPEN`, and `pricingJsonLd` in `website/src/lib/hosted-pricing.ts`; `fetchCohort` in `website/src/lib/hosted-cohorts.ts`; `website/src/pages/Hosted.tsx`; the `/pricing` rule in `website/public/_redirects`, pinned by `checkPricingRedirect` in `scripts/public-docs-lint.mjs`. `website/src/pages/Hosted.test.tsx` pins the page contract.

## Checkout and entitlement

Built on the account Worker and off until Stripe is configured; routes, bindings, and the cohort count: `docs/specs/hosted.md` -> "Billing".

- **Stripe Managed Payments runs checkout, subscriptions, and the customer portal as merchant of record, through `@pgstencil/stripe`**, so tax is Stripe's. Dormouse never stores card data. A founding lock is a per-cohort Price; every past cohort's Price keeps granting the plan.
- **Checkout belongs to a signed-in Hosted account**, so the subscription belongs to an account from its first event. A buy link lands on the account origin's `/checkout`, which asks for sign-in first, then shows the plan and its price now before handing off to Stripe. The browser names a plan, never a Price.
- **No trial**: the first payment is taken at checkout, and the 30-day refund is the trial.
- **The entitlement is the account's subscription, read on the server on every voice and Relay request.** No licence, no offline verification, and no grace past what the subscription grants; a lapsed member's voices fall back to the system voice and its Burrows to `not-entitled`.
- **One account covers every machine the member uses.** No device count, no seat count, no activation limit.
- **Sign-in is the only account surface in the free client** (`docs/specs/hosted.md` -> "Managed voice").
- **A refund or chargeback ends the subscription**, so the next request is refused, and the seat returns to its cohort.
- **Stripe's return shows the founders-row opt-in, unticked, to a founder**; the account page's Plan section can withdraw it at any time, beside Manage billing.
- **The return also asks the four Van Westendorp questions**, optional and unsent until answered: too expensive to consider, too cheap to trust, expensive but would consider, a bargain. Their answers inform later list changes.

## Future

**Scope: hosted-sales** — what remains, in staged order:

1. **The cohort endpoint** the page already calls: the open cohort's seats and the opted-in founders, avatars proxied onto this origin.
2. **Checkout and entitlement**: purchase, the subscription as the account's entitlement, revocation.
3. **Managed voice for members**: the subscription replacing the admin-only entitlement (`docs/specs/hosted.md` -> "Entitlement"), one voice per Pane.
4. **Hosted Relay inclusion**: the subscription as the Relay's entitlement (`docs/specs/hosted.md` -> "Relay"), gated on the independent review `docs/specs/security-remote.md` -> "Cloud-hosted mode" requires.
5. **Renewal, cancellation, and refund** paths.
1. **Managed voice for members**: one voice per Pane.
2. **Turning billing on**: the Stripe products, Prices, portal, and webhook, then the bindings (`docs/specs/hosted.md` -> "Billing"), then `CHECKOUT_OPEN`. The subscription admits members to the Hosted Relay (`docs/specs/hosted.md` -> "Relay"), so this is gated on the independent review `docs/specs/security-remote.md` -> "Cloud-hosted mode" requires.
3. **Founder avatars** proxied onto this origin.
4. **Renewal, cancellation, and refund** paths.

Team and enterprise tiers are never sold through this page. A free hosted tier is undecided — see [Open questions](#open-questions).

Expand Down Expand Up @@ -97,20 +112,9 @@ What each plan grants once checkout can sell it; [Published prices](#published-p
| Founding badge | live for founding |

- **The plan never grants team or enterprise capability** — org accounts, SSO, SCIM, BYOT, audit export.
- **The hosted Relay is part of the plan, never a second purchase.** Reserved: the **hosted-sales** scope reads the plan from the Hosted account's subscription ("Checkout and entitlement"), so a member never signs up twice.
- **The hosted Relay is part of the plan, never a second purchase**: the Relay reads the same subscription ([Checkout and entitlement](#checkout-and-entitlement)), so a member never signs up twice.
- **Nothing shipped free is ever gated**: the terminal, `dor`, browser panes, the notepad, alerts with the system voice, the self-host Relay, and Pocket over a self-hosted Relay stay free, with no login.

### Checkout and entitlement

- **Stripe Managed Payments runs checkout, subscriptions, and the customer portal as merchant of record, through `@pgstencil/stripe`**, so tax is Stripe's. Dormouse never stores card data. A founding lock is a per-cohort Price; cohort counts come from the billing provider's completed subscriptions.
- **Checkout starts from a Hosted account**: a buy button lands on the account origin, which asks for sign-in first, so the subscription belongs to an account from its first event.
- **Founding checkout offers the founders-row opt-in, unticked**; the account can withdraw it at any time.
- **The success page asks the four Van Westendorp questions**, optional and unsent until answered: too expensive to consider, too cheap to trust, expensive but would consider, a bargain. Their answers inform later list changes.
- **The entitlement is the account's subscription, read on the server on every voice and Relay request.** No licence, no offline verification, and no grace past what the subscription grants; a lapsed member's voices fall back to the system voice and its Burrows to `not-entitled`.
- **Sign-in is the only account surface in the free client** (`docs/specs/hosted.md` -> "Managed voice").
- **One account covers every machine the member uses.** No device count, no seat count, no activation limit.
- **A refund or chargeback ends the subscription**, so the next request is refused, and the seat returns to its cohort.

### Managed voice

What is built — the endpoint, the disclosure, the clip cache, the daily cap, and the fallback — is `docs/specs/hosted.md` -> "Managed voice" and `docs/specs/alert.md` -> "Managed voice".
Expand All @@ -124,7 +128,6 @@ What is built — the endpoint, the disclosure, the clip cache, the daily cap, a
- **30-day refund on every plan.** A refund revokes.
- **A failed founding renewal gets 30 days of grace before the lock is lost.**
- **A subscription is personal and non-transferable.**
- **No trial**: the 30-day refund is the trial.

### Open questions

Expand Down
Loading
Loading