Skip to content

Tenant-signup funnel: required-vs-optional field inventory (friction audit) #270

@perigrin

Description

@perigrin

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementEnhancement to existing featurefrontendFrontend/client-side developmentlowLow complexity implementationlow-impactLow business impact

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions