Context
The Alex journey tests walk the tenant-signup funnel with realistic data (Leg 1: `t/user-journeys/alex/01-acquire-tenant.t`) and with minimal data (Leg 3: `t/user-journeys/alex/03-platform-billing.t`). During those walks a per-step inventory of required vs. accepted-empty fields was recorded. This issue tracks the friction each optional-but-requested field creates for the signup user, collects the downstream consumers for each field, and frames the per-field removal decisions that are product calls.
See spec: `docs/superpowers/specs/2026-06-11-alex-user-journeys-design.md`
Per-step field inventory
landing
No fields required or collected. POST with empty body advances. Friction: none.
profile
| Field |
Required? |
Downstream consumers |
| `name` |
Effectively yes (falls back to `'Organization'` without it in `_provision_tenant`) |
`TenantPayment::_provision_tenant` (lib/Registry/DAO/WorkflowSteps/TenantPayment.pm:407): used as org name in provision call; `TenantPayment::create_setup_intent` (line 166): used in Stripe customer creation |
| `description` |
No — accepted empty, not consumed by any provisioning path |
`TenantSignupReview::prepare_template_data` (lib/Registry/DAO/WorkflowSteps/TenantSignupReview.pm:18): displayed on review page only |
| `billing_email` |
No — accepted empty; provisioning proceeds without it |
`TenantSignupReview` (line 19): displayed on review page; `TenantPayment::create_setup_intent` (line 165): used for Stripe customer creation only on the real-Stripe path (not on the seti_test path); `RegisterTenant::prepare_completion_data` (lib/Registry/DAO/WorkflowSteps/RegisterTenant.pm:90): shown on the complete page; `PricingPlanSelection::get_organization_preview` (lib/Registry/DAO/WorkflowSteps/PricingPlanSelection.pm:161): fallback for admin_email preview |
Friction: `description` and `billing_email` are shown on-screen but neither gates provisioning. Removing them simplifies the profile step; each is a separate product call.
users
| Field |
Required? |
Downstream consumers |
| `admin_name` |
No — accepted empty by the step |
`TenantPayment::_provision_tenant` (line 378): used as the user's `name` when creating/finding the admin user; `TenantSignupReview` (line 23): shown on review; `RegisterTenant` (line 88): shown on complete page |
| `admin_email` |
No — accepted empty; the admin user is created/found by username |
`TenantPayment::_provision_tenant` (line 379): passed as `email` to `find_or_create`; `TenantSignupReview` (line 24, via admin => { email }): shown on review; `PricingPlanSelection::get_organization_preview` (line 161): shown in pricing preview |
| `admin_username` |
No — accepted empty; `find_or_create` proceeds without a unique constraint hit on empty string |
`TenantPayment::_provision_tenant` (line 380): used as the lookup key in `User->find`; empty username produces a degenerate user row |
| `admin_user_type` |
Effectively yes — must be `'admin'` to avoid the `invite_pending warn()` path (pristine-output hazard; see spec) |
`TenantPayment::_provision_tenant` (line 381): determines whether the user is marked `invite_pending` |
Friction: empty `admin_username` produces a degenerate user. `admin_name` and `admin_email` are display-only on the review/complete pages and Stripe customer creation. Each is a separate removal decision.
pricing
| Field |
Required? |
Downstream consumers |
| `selected_plan_id` |
Yes (must be a valid UUID for an active platform relationship) — absent silently skips the step without advancing (issue #268 guards this) |
`PricingPlanSelection::process` (lib/Registry/DAO/WorkflowSteps/PricingPlanSelection.pm:18): existence check; stored in run data as `selected_pricing_plan` for display and billing-field writing |
Friction: none beyond the required selection.
review
| Field |
Required? |
Downstream consumers |
| `terms_accepted` |
Yes — the controller (`lib/Registry/Controller/Workflows.pm) validates this **before** calling \step->process`; absent causes an error page |
Gate in `Workflows.pm`; the step itself accepts empty body |
Friction: none beyond the required checkbox.
payment
| Field |
Required? |
Downstream consumers |
| `collect_payment_method` |
Yes (with `setup_intent_id`) — triggers the provision path |
`TenantPayment::process` dispatch (line 43) |
| `setup_intent_id` |
Yes — must be present to trigger `handle_setup_completion`; `seti_test` prefix activates the test-mode path |
`TenantPayment::process` (line 45) and `handle_setup_completion` (line 284) |
Friction: none beyond the required payment-method + intent pair.
Framing
Each optional-but-requested field above represents signup friction: it's shown to the user (rendered as a form input) but either not consumed by provisioning or only consumed for display purposes. Removing any individual field is a separate product call decided field-by-field — this issue tracks the inventory and provides the downstream-consumer evidence to make those decisions.
The Alex journey tests permanently guard the minimal-data path (admin_user_type => 'admin', selected_plan_id seeded, terms_accepted), preventing required-field creep from silently breaking the funnel.
Related issues
Context
The Alex journey tests walk the tenant-signup funnel with realistic data (Leg 1: `t/user-journeys/alex/01-acquire-tenant.t`) and with minimal data (Leg 3: `t/user-journeys/alex/03-platform-billing.t`). During those walks a per-step inventory of required vs. accepted-empty fields was recorded. This issue tracks the friction each optional-but-requested field creates for the signup user, collects the downstream consumers for each field, and frames the per-field removal decisions that are product calls.
See spec: `docs/superpowers/specs/2026-06-11-alex-user-journeys-design.md`
Per-step field inventory
landing
No fields required or collected. POST with empty body advances. Friction: none.
profile
lib/Registry/DAO/WorkflowSteps/TenantPayment.pm:407): used as org name in provision call; `TenantPayment::create_setup_intent` (line 166): used in Stripe customer creationlib/Registry/DAO/WorkflowSteps/TenantSignupReview.pm:18): displayed on review page onlylib/Registry/DAO/WorkflowSteps/RegisterTenant.pm:90): shown on the complete page; `PricingPlanSelection::get_organization_preview` (lib/Registry/DAO/WorkflowSteps/PricingPlanSelection.pm:161): fallback for admin_email previewFriction: `description` and `billing_email` are shown on-screen but neither gates provisioning. Removing them simplifies the profile step; each is a separate product call.
users
admin => { email }): shown on review; `PricingPlanSelection::get_organization_preview` (line 161): shown in pricing previewFriction: empty `admin_username` produces a degenerate user. `admin_name` and `admin_email` are display-only on the review/complete pages and Stripe customer creation. Each is a separate removal decision.
pricing
lib/Registry/DAO/WorkflowSteps/PricingPlanSelection.pm:18): existence check; stored in run data as `selected_pricing_plan` for display and billing-field writingFriction: none beyond the required selection.
review
) validates this **before** calling \step->process`; absent causes an error pageFriction: none beyond the required checkbox.
payment
Friction: none beyond the required payment-method + intent pair.
Framing
Each optional-but-requested field above represents signup friction: it's shown to the user (rendered as a form input) but either not consumed by provisioning or only consumed for display purposes. Removing any individual field is a separate product call decided field-by-field — this issue tracks the inventory and provides the downstream-consumer evidence to make those decisions.
The Alex journey tests permanently guard the minimal-data path (
admin_user_type => 'admin',selected_plan_idseeded,terms_accepted), preventing required-field creep from silently breaking the funnel.Related issues