A default ladder's last tier can be removed while organizations hold it, leaving the ladder empty #214

Open
opened 2026-10-10 23:59:46 +00:00 by cgalo5758 · 0 comments
Owner

What happens

An organization type can name a plan ladder as its default. (A plan ladder is an ordered list of tiers, and rank 0 is the bottom tier.) New organizations of that type start on the ladder's rank-0 tier, and an organization of that type left with no plan is put back on it. Deleting the only tier of such a ladder is refused with "This ladder's only remaining tier; defaults new signups to it. Deleting it would leave those signups without a starting plan, so the removal is refused."

Only the plain delete runs that check, and the plain delete is only for a tier that nobody holds. A tier with holders is removed through Preview removal and Apply change, and neither of those checks whether the tier is the last one on a default ladder:

  • If the operator chooses keep, or every holder got the tier from another source, such as a grant or a subscription, so no choice is asked, the removal succeeds with " removed from the ladder: 1 position(s) on this ladder ended; delivery continues." The ladder now has no tiers and is still the organization type's default. The keep option's label promises "the current type default will still apply at their next restoration", and with no tier there is nothing to restore onto.
  • If the operator chooses migrate, the removal rolls back with "Removal failed and nothing was changed: could not apply the current default. Details are in the server logs." The tier stays, but the operator is told the removal failed when it should have been refused: moving the holders onto the default needs the rank-0 tier that the removal has just deleted. The log reads "reapply-defaults: resolve rank-0 tier of ladder …: sql: no rows in result set".

What follows from an empty default ladder:

  • When the ladder is the personal organization type's default, signup still completes, but with no plan, and logs "AUTO-PROVISIONING ALARM: default plan ladder has no rank-0 tier -- signup completed WITHOUT a plan".
  • Putting an organization of that type back on the default fails whenever it is left with no plan. An operator's revoke of a grant, a grant's expiry and a subscription's cancellation all call ReapplyDefaultsIfVacant, which returns an error when the default ladder has no rank-0 tier, so the whole act fails. #153 reports the same failure for a rank-0 product that confers nothing.

The plain delete's check can also skip itself. When orgTypesDefaultingToLadder cannot read the organization types, it logs the error and returns an empty list, and the delete goes ahead as though no type used the ladder as its default.

What should happen

Removing the last tier of a ladder that an organization type uses as its default is refused, whichever way the tier is removed, as the plan ladder management spec ("The last tier of a live default ladder cannot be deleted") and docs/models/plan-ladders-transitions.md say. Preview removal shows the refusal before a disposition is chosen. Apply change checks again inside its transaction, whatever disposition is posted, and uses the plain delete's wording. If the organization types cannot be read, the delete or removal is refused, not allowed.

Where

internal/server/operator_plan_ladders.go: DeletePlanLadderTier (has the check), buildTierRemovalPreview and CommitPlanLadderTierRemoval (lack it), orgTypesDefaultingToLadder. The consequences: ReapplyDefaultsForPool in internal/entitlements/reapply_defaults.go, and internal/provisioning/provisioning.go. The recorded answers in internal/server/testdata/plan_acts/remove-last-tier-keep.json and remove-last-tier-migrate.json show today's behaviour and will change with the fix.

Steps

  1. A ladder has one tier, and an organization type uses the ladder as its default.
  2. An organization of that type holds the tier.
  3. On the ladder's page, click Preview removal on the tier, choose keep, and click Apply change.
  4. The ladder shows no tiers, and the org types page still lists it as the type's default.

Why it matters

New organizations of that type start with no plan, and revoking, expiring or cancelling a plan fails for every organization of that type until an operator adds a tier back.

### What happens An organization type can name a plan ladder as its default. (A plan ladder is an ordered list of tiers, and rank 0 is the bottom tier.) New organizations of that type start on the ladder's rank-0 tier, and an organization of that type left with no plan is put back on it. Deleting the only tier of such a ladder is refused with "This ladder's only remaining tier; <org types> defaults new signups to it. Deleting it would leave those signups without a starting plan, so the removal is refused." Only the plain delete runs that check, and the plain delete is only for a tier that nobody holds. A tier with holders is removed through Preview removal and Apply change, and neither of those checks whether the tier is the last one on a default ladder: - If the operator chooses keep, or every holder got the tier from another source, such as a grant or a subscription, so no choice is asked, the removal succeeds with "<product> removed from the ladder: 1 position(s) on this ladder ended; delivery continues." The ladder now has no tiers and is still the organization type's default. The keep option's label promises "the current type default will still apply at their next restoration", and with no tier there is nothing to restore onto. - If the operator chooses migrate, the removal rolls back with "Removal failed and nothing was changed: could not apply the current default. Details are in the server logs." The tier stays, but the operator is told the removal failed when it should have been refused: moving the holders onto the default needs the rank-0 tier that the removal has just deleted. The log reads "reapply-defaults: resolve rank-0 tier of ladder …: sql: no rows in result set". What follows from an empty default ladder: - When the ladder is the personal organization type's default, signup still completes, but with no plan, and logs "AUTO-PROVISIONING ALARM: default plan ladder has no rank-0 tier -- signup completed WITHOUT a plan". - Putting an organization of that type back on the default fails whenever it is left with no plan. An operator's revoke of a grant, a grant's expiry and a subscription's cancellation all call `ReapplyDefaultsIfVacant`, which returns an error when the default ladder has no rank-0 tier, so the whole act fails. #153 reports the same failure for a rank-0 product that confers nothing. The plain delete's check can also skip itself. When `orgTypesDefaultingToLadder` cannot read the organization types, it logs the error and returns an empty list, and the delete goes ahead as though no type used the ladder as its default. ### What should happen Removing the last tier of a ladder that an organization type uses as its default is refused, whichever way the tier is removed, as the plan ladder management spec ("The last tier of a live default ladder cannot be deleted") and `docs/models/plan-ladders-transitions.md` say. Preview removal shows the refusal before a disposition is chosen. Apply change checks again inside its transaction, whatever disposition is posted, and uses the plain delete's wording. If the organization types cannot be read, the delete or removal is refused, not allowed. ### Where `internal/server/operator_plan_ladders.go`: `DeletePlanLadderTier` (has the check), `buildTierRemovalPreview` and `CommitPlanLadderTierRemoval` (lack it), `orgTypesDefaultingToLadder`. The consequences: `ReapplyDefaultsForPool` in `internal/entitlements/reapply_defaults.go`, and `internal/provisioning/provisioning.go`. The recorded answers in `internal/server/testdata/plan_acts/remove-last-tier-keep.json` and `remove-last-tier-migrate.json` show today's behaviour and will change with the fix. ### Steps 1. A ladder has one tier, and an organization type uses the ladder as its default. 2. An organization of that type holds the tier. 3. On the ladder's page, click Preview removal on the tier, choose keep, and click Apply change. 4. The ladder shows no tiers, and the org types page still lists it as the type's default. ### Why it matters New organizations of that type start with no plan, and revoking, expiring or cancelling a plan fails for every organization of that type until an operator adds a tier back.
cgalo5758 added the
kind
bug
area/entitlementsarea/catalog
labels 2026-10-10 23:59:46 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: wiki-cafe/member-console#214