Problem
A newly created organization has no subscription until something explicitly calls POST /v1/billing/free-plan. Until that happens the org is plan-less, and the ONBOARDING_STEP_ID_CHOOSE_PLAN step stays incomplete because subscriptionCompletesOnboarding has no subscription to evaluate.
That makes the free path a client responsibility, so every caller — the React controller, the headless OnboardingBackend, a future CLI or API consumer — has to remember the extra call, and any that forgets leaves an org in a state the product does not really support.
Organizations are created from two places today (Service.CreateOrganization, and the signup path inside ensureOrg), which is exactly the kind of duplication that lets one of them drift.
Scope
- Attach the free plan as part of organization creation, in the same transaction, so an org is never plan-less.
- Apply it to both creation paths.
- Keep
POST /v1/billing/free-plan for explicit selection, and keep it idempotent — it must remain safe to call on an org that already has the free plan.
- Confirm the intended interaction with
plan_entitlements, seeded by migration, so entitlement checks are correct immediately at creation rather than after a follow-up call.
- Do not change behaviour for orgs created on a paid plan.
Testing surface
- pipeline: a newly created org has the free plan and
CHOOSE_PLAN is already satisfied
- pipeline: an org created through the signup path gets the same treatment as one created through
CreateOrganization
- pipeline: calling
/v1/billing/free-plan on an org that already has it is a no-op, not a duplicate subscription
- pipeline: entitlement checks pass immediately after creation without a follow-up call
- pipeline: upgrading away from free, then re-calling the endpoint, does not silently downgrade
pure: subscriptionCompletesOnboarding accepts the free plan (there is existing coverage for usable plans — extend it)
Dependencies
None — fully parallelisable.
Design detail: module/docs/IDENTITY_ACCESS_PLAN.md.
Problem
A newly created organization has no subscription until something explicitly calls
POST /v1/billing/free-plan. Until that happens the org is plan-less, and theONBOARDING_STEP_ID_CHOOSE_PLANstep stays incomplete becausesubscriptionCompletesOnboardinghas no subscription to evaluate.That makes the free path a client responsibility, so every caller — the React controller, the headless
OnboardingBackend, a future CLI or API consumer — has to remember the extra call, and any that forgets leaves an org in a state the product does not really support.Organizations are created from two places today (
Service.CreateOrganization, and the signup path insideensureOrg), which is exactly the kind of duplication that lets one of them drift.Scope
POST /v1/billing/free-planfor explicit selection, and keep it idempotent — it must remain safe to call on an org that already has the free plan.plan_entitlements, seeded by migration, so entitlement checks are correct immediately at creation rather than after a follow-up call.Testing surface
CHOOSE_PLANis already satisfiedCreateOrganization/v1/billing/free-planon an org that already has it is a no-op, not a duplicate subscriptionpure:subscriptionCompletesOnboardingaccepts the free plan (there is existing coverage for usable plans — extend it)Dependencies
None — fully parallelisable.
Design detail:
module/docs/IDENTITY_ACCESS_PLAN.md.