Operator org plan and bundles page
## 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 C7 of 9 (C1…C9) of epic "Fold Trial Mode Into Billing Plans".
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; an overridden value must be unmistakable next to the plan's own value. 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 §8 C7)
Consumes C2's `GET/PUT /api/orgs/{id}/plan[?dry_run=1]`, C3's price override + roster `no_subscription` field, C4's bundles API, and C6's products line editor + HostingPicker (import, never copy).
- Org page Plan tab: plan select listing every active plan (self-serve or not) with a dry-run confirm before applying (shows provisions/releases/pausing and the overrides that will clear — DA-4 note "Moving plan clears all overrides").
- One row per override — End (plan / date / never), Hosting allowed, Products (C6's line editor; whole-list override), Price (any sold plan; disabled with the server sentence on unsold) — each rendered as "value · Overridden — plan: X · Reset"; Reset sends the null/plan mode. Caps stay on the Limits tab.
- `OrgTrialNode` → `OrgBundles`: list BundleOut rows with the statuses provisioning, failed, ready, removed, stopping, stopped, kept, terminated; operator sentence; Retry on failed/removed only.
- Limits tab chips: "Plan" / "This organization" (AC2.2 wording) only — no "Default".
- OrgsPage trial column → "Ends" from `plan_ends_at` ("Never" when null).
- Billing roster: "No subscription" chip from C3's field.
- Plan history visible on the Plan tab (override clears/resets, Stripe pushes).
## Acceptance criteria (PRD, verbatim) carried by this child
- AC7.1 On the org page the operator can override, per org: each cap, the end date/duration, the bundled product list (add/remove/change lines and quantities), the price (any priced plan, generalizing the Enterprise custom amount), and hosting allowed (D-6, A-8). Verify: on dev override each on a test org; the org page marks each overridden value ("Overridden — plan: X") with a Reset; Reset restores the plan value.
- AC4.3 (part: UI) Failure is visible and retryable by the operator exactly like today's trial node (operator sentence on the org page, tenant-safe sentence for members, Retry from `failed`/`not provisioned` only); one product failing does not block the org's approval or the other products. Verify: on dev, with no Shared capacity offered, approve an org with a Shared bundle → org approved, product shows failed with the "No Shared pool host has room…" sentence; offer capacity, Retry → ready.
- AC6.1 (part: UI) The operator's plan picker (org page Plan tab, approval page, billing desk) lists every active plan, self-serve or not, and Trial-flavoured plans; changing it applies that plan's parameters and products (R4) and, when priced, the existing Stripe push. Verify: on dev change a test org from Trial to "Trial lite" → its bundled node enters the D-2 lifecycle, caps/end date follow the new plan; to Enterprise → Stripe push recorded in plan history.
- AC2.2 (part: chips) Org Limits tab on dev shows each cap's source as "Plan" or "This organization".
- AC7.3 (part: UI) history row visible on the Plan tab.
## Out of scope:
Do NOT change backend contracts (C2/C3/C4 own them) — if a field is missing, name it in the PR body rather than redesigning. Do NOT build tenant surfaces (C8) or the plan editor (C6). Do NOT copy the line editor or HostingPicker — import C6's.
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:
- On dev: override end, hosting, products, price and one cap on a test org → each shows "Overridden — plan: X" with Reset; Reset restores plan value; move Trial → "Trial lite" shows the dry-run confirm listing overrides to clear, then the bundled node shows `stopping`; move to Enterprise → history row with Stripe push — cite observations.
- AC4.3 run as stated: no Shared capacity → approve an org with a Shared bundle → org approved, bundle failed with the operator sentence → offer capacity → Retry → ready; double-click Retry → one node.
- Limits tab chips read "Plan" / "This organization" only.
- Web tests for OrgBundles statuses and override rows pass.
- `./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
Plan tab: overrides with Reset and a dry-run plan move; OrgBundles replaces the trial panel; Limits chips, roster Ends, No-subscription chip. Full gate green; #4647 merged. Dev not reachable, checked locally. NodeVault (5284) made develop's API ref stale.
Artifacts
Activity
- Draft
- Ready
- Runner started
- Runner ended (done)