When to use this runbook: smoke-checking the M1 self-serve activation funnel after any change touching the four sliced systems (foundation, ProCloudProvider, billing, frontend polish).
- Prerequisites
- When to use this
- Pre-flight
- Procedure — happy path
- Negative-path Coverage
- Verification
- Rollback
- Troubleshooting
- Cross-references
- Local dev DB up-to-date (apply pending migrations — see Pre-flight)
- Vultr API credentials registered on the canonical Pro Cloud provider via Settings → Cloud Credentials
- Stripe test mode available (test card
4242 4242 4242 4242) - Worker service running
businessandsystemextensions enabled (M1 funnel is extension-gated)
- After any change to the M1 funnel (
extensions/system/server/services/system/account_bootstrap_service*, ProCloudProvider, billing meter / quota guard, frontend provisioning polish). - Before promoting a release branch carrying M1 changes to
master. - After config edits to the
pro_cloudSaasPlan limits.
-
Apply pending migrations on the dev DB:
cd server && bundle exec rails db:migrate
-
Refresh stale plan rows.
extensions/business/server/db/seeds/saas_plans_seed.rbusesfind_or_create_by!, which does NOT update existing rows to add the newlimitskeys introduced by Slice C. Without this, free-tierQuotaGuardtreats all accounts as unlimited and the paywall step below will not fire.cd server && bundle exec rails runner " ::SaasPlan.find_each do |p| limits = case p.slug when 'free' then { 'max_node_instances' => 1, 'max_monthly_provisioning_usd' => 20 } when 'pro_cloud' then { 'max_node_instances' => 50, 'max_monthly_provisioning_usd' => 500 } else p.limits end p.update!(limits: limits) if p.limits != limits end "
-
Vultr API credentials. Slice B reads from
System::ProviderCredential(account-scoped, with platform-pool fallback). For the platform-pool fallback to work in dev, register a Vultr API key on the canonicalPro Cloudprovider via Settings → Cloud Credentials before the test. For account-isolation testing, register a separate cred under the test account. -
Stripe test card. Checkout uses the standard
4242 4242 4242 4242test card. -
Worker rollup endpoint.
worker/app/jobs/billing_provisioning_meter_job.rbPOSTs to/api/v1/internal/billing/provisioning/meter/rollupwhich does NOT yet exist server-side. Daily rollup fails silently. Live-spend display in step 6 uses on-the-fly aggregation overBilling::ProvisioningUsageRecord, which works without the rollup endpoint. Track this as a follow-up.
-
Sign up → checkout → land on
/new-
Visit
/auth/registeras a fresh email. -
Complete the multi-step signup (email + password + name) and the embedded Stripe checkout (free plan card).
-
Confirm post-checkout redirect to
/new(the provisioning chat). -
Verify
Account.after_create_commitran:rails runner "a = Account.last; puts \"providers=#{System::Provider.where(account: a).count} regions=#{System::ProviderRegion.where(account: a).count} types=#{System::ProviderInstanceType.where(account: a).count} templates=#{System::NodeTemplate.where(account: a).count}\""Expected:
providers=1 regions=2 types=3 templates=7.
-
-
Type Discord-bot brief → Brief Card populates
- In the chat input, paste:
I want to run my Discord moderation bot 24/7 on a small VM. It's a side project, I don't need anything fancy — just $5/mo on a 1 vCPU box.
- Confirm the AI response captures intent="Run my Discord bot 24/7", use_case mentions "Discord bot", scale.initial=1, regions=["us-east-1"], budget_cap_usd_monthly ≈ 10.
- The Brief Card on the right pane should populate with these values.
- In the chat input, paste:
-
Approve → 1-step plan with $5/mo cost estimate
-
Click "Open Plan" once it surfaces.
-
Plan modal should show:
- 1 step in the topology (
provision_full_stack) — Slice D step-collapse pass merged 3 redundant sequential steps into 1. - Cost estimate $5.04/mo (one Pro Cloud
vc2-1c-1gbinus-east-1). - Risk severity: low.
- 1 step in the topology (
-
Verify via API:
curl -H "Authorization: Bearer $TOKEN" /api/v1/ai/missions/$MISSION_ID | jq '.data.plan.dag.step_count'
Expected:
1.
-
-
Execute → instance running on Vultr within ~90s
-
Click "Approve & Provision".
-
Watch the bottom-right ExecutionPill; the step transitions
pending → running → completed. -
Confirm a Vultr instance was created:
curl https://api.vultr.com/v2/instances -H "Authorization: Bearer $VULTR_KEY" | jq '.instances | length'
Or check the dev DB:
rails runner "puts System::NodeInstance.last.attributes.slice('id','status','provider_instance_id','created_at')"Expected:
status='running'within ~90s of the approve click. -
Verify a
Billing::ProvisioningUsageRecordwas created:rails runner "puts ::Billing::ProvisioningUsageRecord.last.attributes.slice('event','metered_at','status')"Expected:
event='running',status='pending'(rolled up later).
-
-
Try a second instance → UpgradeRequiredCard renders
- Back to
/new, type a second prompt:Spin up another bot host alongside the first one.
- Drive through brief → plan review → Approve.
- On click, the response should be
402 upgrade_required. The frontend should renderUpgradeRequiredCard(Slice D component) instead of the execution view, with copy referencing free-tier limits and an upgrade CTA.
- Back to
-
Subscription's daily spend reflects usage
- Navigate to Settings → Billing.
- Confirm the live spend tracker shows ≈$5.04/mo accumulating (or the pro-rated daily share) for the running instance. Source: on-the-fly aggregation over
Billing::ProvisioningUsageRecordsince the daily rollup endpoint is not yet wired (see pre-flight item 5). - The cost-cap progress bar in the Brief Card should reflect the same value (Slice D
CostCapGuardreads the same source).
- Missing / invalid credential. Disable the Vultr API in
System::ProviderCredentialand approve a plan — provisioning should fail-closed and surface a friendly "credentials missing or invalid" toast (no half-billed records). - Quota guard. Force
Billing::ProvisioningQuotaGuardto returnover_limit(e.g. manually setcurrent_usage.node_instance_count = 1for a free account) — Approve should immediately renderUpgradeRequiredCardwithout contacting the cloud API. - Step-collapse boundary. Trigger a non-redundant 3-step plan (compute + db + network) — collapse should NOT merge them; the review modal should show 3 separate steps.
After the checklist passes:
- Final state: one running
System::NodeInstance, oneBilling::ProvisioningUsageRecordwithevent='running'. - No 500s in
journalctl -u powernode-backend@default --since "10 minutes ago". - Sidekiq dashboard shows no failed
Billing::ProvisioningMeterJobitems (silent rollup failures are acceptable; see pre-flight item 5).
If the funnel breaks mid-test:
- Destroy the Vultr instance manually via the Vultr API or admin UI.
- Delete the
System::NodeInstanceand correspondingBilling::ProvisioningUsageRecordin dev:rails runner "System::NodeInstance.find_by(provider_instance_id: '...').destroy" - Reset the dev account's spend cap by clearing
Billing::ProvisioningUsageRecordfor the account. - Re-run pre-flight steps before retrying.
| Symptom | Likely cause | First action |
|---|---|---|
| Brief Card never populates | AI provider quota exhausted | Check Ai::Provider.find_by(name: 'pro_cloud').current_quota_usage |
| Approve produces 5xx | Worker not running | sudo systemctl status powernode-worker@default |
| Vultr instance never created | Provider credentials missing | Re-register Vultr API key per pre-flight item 3 |
| UpgradeRequiredCard never fires | Stale plan limits | Re-run pre-flight item 2 |
- Foundation slice (Account hook + bootstrap):
extensions/system/server/spec/services/system/account_bootstrap_service_spec.rb - ProCloudProvider (Vultr adapter):
extensions/system/server/spec/services/system/providers/pro_cloud_provider_spec.rb - Billing (UsageRecord + QuotaGuard + MeterService):
extensions/business/server/spec/services/billing/ - Frontend polish (CostCapGuard + step-collapse + UpgradeRequiredCard):
frontend/src/features/ai/provisioning/ - Activation-funnel UI flow e2e:
e2e/provisioning/discord-bot-activation.spec.ts
- production-deployment.md — Initial platform install
- ai-operations.md — Mission and worker procedures
- data-sources.md — External API quota patterns
docs/m1_selfserve_acceptance.md
Last verified: 2026-05-17