Sign-up plan choice and approval
## Original requirement (epic, verbatim)
Fold Trial mode into Billing plans:
- Trial becomes a regular Plan, that is pre-seeded but may be changed by Operator as they please
- All functions of Trial become configurable parameters of the plan
- Trial node: each plan may have multiple products that are coming with the plan. One of them is a Jaah-provided node - operator can pick what kind of node - same as with Trial now and the same controls (reuse) as with user adding products. They may be arbitrary number of products attached
- When user is signing up he is picking the plan from offered plans that are self-served. Operator can change this plan to any other configured in the system that are active/offered, including those that are not Self-served. E.g. Operator may choose a different flavor of a Trial, such as one without a node
- Operator can override Plan parameters per organization, instance of the plan
- One of the goals is to make it more flexible with less specialized configuration knobs
Settled inputs: BA PRD (Task 5248: R1–R9, D-1…D-9, A-1…A-11, NC-1…NC-9) and Architecture (Task 5249: DA-1…DA-6, §1–§13). Use `task_read` on 5248/5249 for the full documents; this body carries everything this child needs. This child is C5 of 9 (C1…C9) of epic "Fold Trial Mode Into Billing Plans". It builds on C2 (plan_terms), C3 (pending clears on entitlement) and C4 (approval no longer calls trial_node; bundles provision via the reconciler).
Binding operator decisions: D-1 bundled products included in plan price · D-2 leaving a plan → today's trial-node keep-or-stop rule generalized · D-3 paid sign-up plan paid after approval · D-4 old knobs move onto plans and are deleted; empty plan value = unlimited/off, not platform default · D-5 attachable products: Shared node (tier), Dedicated node (size), Jaah AI · D-6 per-org overrides: limits, duration/end date, bundled products, price · D-7 any plan with a duration pauses work like Trial when it ends · D-8 operator-chosen Default plan knob (seeded Trial); any plan retirable except the current default · D-9 Jaah AI bundle credits at plan start + every billing month, may be zero, no rollover · DA-1 paid subscription ends → Default plan · DA-2 plan products edit applies to all members without a products override · DA-3 duration edit is live: end = plan_started_at + duration_days unless overridden · DA-4 plan move clears ALL overrides incl. caps · DA-5 operator-created orgs get bundled products · DA-6 returning to a plan is fresh every time.
Quality bar: every UI string follows docs/standards/ui-copy-standards.md; plan cards must be readable and comparable at a glance (price or "Free", duration, bundled products in words). A literally-compliant but confusing result fails this child. Everything is live on dev.jaah.ai after merge — no feature flag (AC9.1, R29).
## This child — scope (HOW, from Architecture Task 5249 §6 + §5.3; API and web)
- `GET /api/auth/signup/plans` (public): active ∧ self_serve plans in catalogue order → `{key, name, blurb, price: {amount, currency, interval} | null, duration_days, products: [plan_terms.describe(line)], is_default}`.
- `ApplicationIn.plan_key?` → `org_signups.plan_key` (validated against that list; stale/absent ⇒ default plan).
- Approval (`org_approval._after_approval`, operator and auto-approve alike): free choice ⇒ `plan_terms.assign(org, choice)`; priced choice ⇒ stay on default plan, set `orgs.pending_plan_key`; then `wake_bundles()`. While pending, the org runs on the default plan's caps/end but receives NONE of its bundled products or credit (Architecture flagged decision §4.1 — C4's desired set already excludes pending orgs).
- `POST /api/orgs/{id}/approve` body gains `plan_key?` (operator override at approval); `ApproveOrgDialog` shows "Applied for: <plan>" + a select of every active plan.
- `SignupApplyPage`: plan cards (name, blurb, price or "Free", duration, bundled products in words; hidden when the list is empty; default preselected if listed); no card field (D-3). `SignupSubmittedPage` copy follows the chosen plan. `signup_auto_approve_domains` help text loses "trial node".
- Payment of the pending plan → entitlement → C3's on_entitlement moves the org and clears pending_plan_key (nothing new here; C8 renders the "Complete checkout" prompt and runs the priced end-to-end oracle).
## Acceptance criteria (PRD, verbatim) carried by this child
- AC5.1 The sign-up form lists every active, self-serve plan (A-5) with name, blurb, price (or "Free"), duration and bundled products in readable words; the default plan is preselected if it is among them. None listed → no picker, default plan applies. Verify: on dev, as a signed-out visitor open sign-up; observe the cards; mark Team self-serve in Settings, reload → Team appears; unmark all → picker gone.
- AC5.2 The choice is stored on the application and shown to the operator on the approval page ("Applied for: <plan>"). Verify: submit an application choosing a non-default plan; open it on the operator approval page.
- AC5.3 (part: approval side) Free choice: on approval the org is on that plan and its products provision. Priced choice: on approval the org is on the default plan and its owner sees "Complete checkout for <plan>" leading to checkout; on payment the org moves to the chosen plan (A-6, D-3). Card is never asked for during sign-up. Verify: both flows end to end on dev with Stripe test mode. (While pending: no bundled products/credit — Architecture §4.1. Prompt UI + priced end-to-end = C8.)
- AC5.4 Auto-approved domains (`signup_auto_approve_domains`) follow the same rule; its help text no longer says "trial node". Verify: read the knob's help in Settings; sign up from an auto-approved domain → plan as chosen.
- AC6.2 The operator can change the plan at approval time (before the org exists as active). Verify: on the approval page, change "Applied for" and approve → org on the operator's choice.
## Out of scope:
Do NOT change the approval policy itself (manual review, auto-approve domains and their daily cap — NC-6) or other sign-up knobs (NC-4). Do NOT collect payment during sign-up (D-3). Do NOT build the tenant "Complete checkout" prompt (C8) or org-page Plan tab (C7). No Alembic revisions.
Epic out of scope (PRD §5, verbatim):
- NC-1 Stripe money model: monthly USD, Stripe owns the price; no new intervals/currencies, no proration redesign. `billing/plans.py::push_price` mechanics unchanged except generalizing the per-org custom price beyond Enterprise.
- NC-2 The hosting price catalogue (`rental_prices`), tiers/sizes and their editor — read by bundles, not changed.
- NC-3 Placement/isolation of Shared nodes (`pick_shared_host`, ADR-0130) — reused as is.
- NC-4 Abuse quotas, cron minimum period, sign-up knobs, `rental_grace_days`/`rental_retention_days`, hosting offering switches (A-9) stay global knobs.
- NC-5 Jaah AI wallet/top-up/refund mechanics (`billing/jaah_credit.py`, `token_billing`) — bundles only add a monthly credit entry; no change to top-ups, auto-recharge or refunds.
- NC-6 No change to the approval policy itself (manual review, auto-approve domains and their daily cap).
- NC-7 No new product types beyond D-5 (no storage-only, GitHub runner, or self-hosted products as bundles).
- NC-8 Operator orgs (org zero / own machines) keep their exemptions; bundles never provision into them.
- NC-9 No forge/Slack/notification-channel redesign; reuse existing billing notices for warnings.
Deferred (do NOT build): abuse quotas as plan parameters; Jaah AI credit rollover; annual plans; a configurable bundle provision throttle; tenant self-service switching between free plans.
Done means:
- API tests: signup plans list = active∧self_serve only; stale plan_key ⇒ default; free choice ⇒ assigned at approval; priced ⇒ default + pending_plan_key and no bundles; operator `plan_key` at approve wins; auto-approve path follows the same rule.
- On dev: signed-out sign-up shows plan cards; toggling Team self-serve shows/hides it; all unmarked → no picker; submit a non-default choice → approval page reads "Applied for: <plan>"; change it and approve → org on operator's choice; free choice with a Shared bundle → org on that plan, node provisions; auto-approve knob help has no "trial node" — cite observations.
- `./dc check full` green; PR merged into develop; the change is live on dev.jaah.ai with no flag (AC9.1).
- Rendered report cites file:line per AC above and the observed output of each oracle.
Outcome
Sign-up plan cards + plan choice at approval: free choice assigned, priced → Default + pending_plan_key (no bundles); operator can override. Full gate green; #4646 merged. Dev oracles run locally (dev unreachable from node).
Artifacts
Activity
- Draft
- Ready
- Runner started
- Runner ended (done)