Skip to content

feat: operator console with sponsored trial invites - #104

Open
pythonlearner1025 wants to merge 2 commits into
mainfrom
feat/operator-console
Open

feat: operator console with sponsored trial invites#104
pythonlearner1025 wants to merge 2 commits into
mainfrom
feat/operator-console

Conversation

@pythonlearner1025

Copy link
Copy Markdown
Member

What this adds

Control plane

  • GET /admin/orgs — platform-operator route that lists every organization with members, invites, live workspaces, and entitlement state. This is the one deliberate exception to org-scoped queries; the gate is users.platform_operator, the same flag as /operator-tokens. Read-only operator tokens cannot reach it.
  • POST /admin/trial-orgs — seeds a sponsored trial: a fresh org with platform_compute = 1, a trial clock, and an admin invite link. A prospect follows the link and lands with machines they can create on the deployment's cloud credential — no BYOK setup.
  • Migration 0040: org_entitlements.trial_expires_at. A janitor sweep flips platform_compute off when the clock runs out (same downgrade contract as a billing cancellation: running workspaces stay alive, the next create is refused). A billing write clears the clock, so a converted trial becomes an ordinary paid org.
  • Core still never learns a plan name: the row holds integers and an instant.

Webapp

  • /admin console page, rail entry visible only to platform operators. Shows all orgs, members, invites, seats and VM slots against limits, and plan state (free / paid / trial until / trial ended). "Start a trial" form shows the invite link once, like InvitesPanel.

Defaults

Trial: 14 days (max 90), 5 seats, 2 VMs. Override per trial in the form.

Not in scope (follow-ups)

  • Payment truth (Stripe status, past_due) lives in the private billing worker's D1; the console shows what the control plane knows. A read API on the billing side can enrich this later.
  • Resource metering (server-hours, cost) does not exist anywhere yet; the console shows live counts and limits.

Test plan

  • npm run typecheck — clean.
  • npm run lint:gate — passes at baseline; max-lines warn list unchanged.
  • Control plane: 631 passing (new test/admin-console.test.ts: operator gate, cross-org listing, trial seeding + redeemable invite, trial expiry sweep, billing write superseding the trial clock, trial length cap).
  • Webapp: 345 passing (new test/admin-console.test.tsx: org rendering, trial post + one-time link, plan-state derivation, 403 refusal, rail gating).

🤖 Generated with Claude Code

pythonlearner1025 and others added 2 commits August 28, 2026 06:58
Two platform-operator routes. GET /admin/orgs lists every organization
with its members, invites, live workspaces, and entitlement state — the
one deliberate exception to org-scoped queries, gated on the same
users.platform_operator flag as /operator-tokens. POST /admin/trial-orgs
seeds a sponsored trial: a fresh org with platform_compute = 1, a trial
clock, and an admin invite link, so a prospect tries the product on the
deployment's cloud credential with no BYOK setup.

The trial clock is org_entitlements.trial_expires_at (migration 0040).
A janitor sweep flips platform_compute off when it runs out — one flag,
no provider calls, same downgrade contract as a billing cancellation.
A billing write clears the clock: the body states the org's whole
entitlement, and a paid org is not a trial.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
One page behind the operator flag: every organization with members,
invites, live workspaces, seats and VM slots against their limits, and
the plan state (free, paid, trial until, trial ended) derived from the
entitlement integers. A "Start a trial" form seeds a sponsored org and
shows its admin invite link once, the same way InvitesPanel does.

The rail entry renders only for platform operators; direct navigation
by anyone else renders the server's 403 in place — one refusal source.
A hard refresh on /admin gets the SPA shell because run_worker_first
derives /admin/* and never the bare path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@pythonlearner1025

Copy link
Copy Markdown
Member Author

TODO - update after merging the workspace udpate

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant