Skip to content

fix(billing): allow dropped license pools to transition - #2351

Merged
charlietlamb merged 1 commit into
devfrom
charlie/license-release-qa
Jul 22, 2026
Merged

fix(billing): allow dropped license pools to transition#2351
charlietlamb merged 1 commit into
devfrom
charlie/license-release-qa

Conversation

@charlietlamb

@charlietlamb charlietlamb commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • allow immediate transitions to drop license pools without blocking
  • retain the guard for ambiguous active-license mappings

This unblocks Team → Pro transitions where the incoming plan intentionally removes the team seat pool. Unrelated local work remains stashed.


Summary by cubic

Allow plan transitions that drop license pools by releasing active seat assignments during sync. This unblocks Team → Pro migrations while still blocking ambiguous 1:1 license mapping conflicts.

  • Bug Fixes
    • Release active assignments from dropped pools during the immediate sync phase and execute them in executeAutumnBillingPlan.
    • Attach handler now skips dropped pools; only ambiguous group mappings block with a clear error.
    • Add computeCustomerLicenseReleases and integrate into computeSyncImmediatePhase and computeSyncPlan.
    • Implement atomic licenseAssignmentRepo.releaseActiveAssignments to clear assignments and restore pool remaining.
    • Extend AutumnBillingPlan with releaseCustomerLicenseAssignments (schema in @autumn/shared).
    • Add integration test to confirm Team → Pro removes inherited team seats and releases assignments.

Written for commit 28637a9. Summary will update on new commits.

Review in cubic

Greptile Summary

This PR allows billing transitions to release assignments from dropped license pools. The main changes are:

  • Bug fixes: Allows dropped pools through immediate-transition validation.
  • Improvements: Computes and executes assignment releases during subscription sync.
  • API changes: Adds the assignment-release payload to the billing-plan schema.
  • Bug fixes: Adds integration tests for Team-to-Pro sync transitions.

Confidence Score: 4/5

Immediate attach can leave assignments active after their license pool is dropped.

  • The sync path creates and executes the required release action.
  • The shared attach guard now allows the same transition without adding that action.
  • Affected entities can retain stale assignments after the old product expires.

The dropped-license attach guard and immediate attach-plan computation.

Important Files Changed

Filename Overview
server/src/internal/billing/v2/actions/attach/errors/handleLicenseErrors/handleDroppedLicenseErrors.ts Allows dropped pools on immediate attach even though that planner does not create the new release action.
server/src/internal/billing/v2/actions/sync/compute/computeSyncImmediatePhase.ts Collects assignment releases for pools dropped during immediate sync replacements.
server/src/internal/billing/v2/compute/customerLicenseTransitions/computeCustomerLicenseReleases.ts Builds a release payload from outgoing pools with no successor.
server/src/internal/licenses/repos/licenseAssignmentRepo.ts Adds a set-based operation to release active assignments and update released pool balances.
server/src/internal/billing/v2/execute/executeAutumnBillingPlan.ts Executes the optional assignment-release action after license transitions.
shared/models/billingModels/plan/customerLicensePlan.ts Defines the customer and pool identifiers required by an assignment release.
server/tests/integration/billing/sync/sync-dropped-license-inheritance.test.ts Covers dropped-pool releases during sync but not the newly allowed immediate attach path.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Immediate plan transition] --> B{Transition path}
    B -->|Sync| C[Compute dropped-pool release]
    C --> D[Release active assignments]
    B -->|Attach| E[Allow dropped pool]
    E --> F[No release action created]
    F --> G[Old assignments remain active]
Loading
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
server/src/internal/billing/v2/actions/attach/errors/handleLicenseErrors/handleDroppedLicenseErrors.ts:29
**Attach Leaves Assignments Active**

An immediate attach that drops a pool with assigned seats now passes this guard, but only the sync planner creates `releaseCustomerLicenseAssignments`. The attach planner handles matched license transitions and has no release action for this unmatched pool, so the old product can expire while its entity assignments remain active.

Reviews (1): Last reviewed commit: "fix(billing): allow dropped license pool..." | Re-trigger Greptile

Greptile also left 1 inline comment on this PR.

Context used:

  • Context used - When generating the key changes section of the sum... (source)

@vercel

vercel Bot commented Jul 22, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
checkout Ignored Ignored Jul 22, 2026 12:11pm
landing-page Ignored Ignored Jul 22, 2026 12:11pm

Request Review

@vercel
vercel Bot temporarily deployed to Preview – autumn-vite July 22, 2026 12:07 Inactive
@capy-ai

capy-ai Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Capy auto-review is paused for this organization because the usage-cycle auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews.

});

for (const { outgoingCustomerLicense, reason, group } of unmatched) {
if (reason === "dropped") continue;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Attach Leaves Assignments Active

An immediate attach that drops a pool with assigned seats now passes this guard, but only the sync planner creates releaseCustomerLicenseAssignments. The attach planner handles matched license transitions and has no release action for this unmatched pool, so the old product can expire while its entity assignments remain active.

Prompt To Fix With AI
This is a comment left during a code review.
Path: server/src/internal/billing/v2/actions/attach/errors/handleLicenseErrors/handleDroppedLicenseErrors.ts
Line: 29

Comment:
**Attach Leaves Assignments Active**

An immediate attach that drops a pool with assigned seats now passes this guard, but only the sync planner creates `releaseCustomerLicenseAssignments`. The attach planner handles matched license transitions and has no release action for this unmatched pool, so the old product can expire while its entity assignments remain active.

How can I resolve this? If you propose a fix, please make it concise.

@charlietlamb
charlietlamb force-pushed the charlie/license-release-qa branch from 965bf75 to 28637a9 Compare July 22, 2026 12:11
@vercel
vercel Bot temporarily deployed to Preview – autumn-vite July 22, 2026 12:11 Inactive

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found and verified against the latest diff

Confidence score: 3/5

  • In server/src/internal/billing/v2/actions/attach/errors/handleLicenseErrors/handleDroppedLicenseErrors.ts, the new if (reason === "dropped") continue path appears to let dropped pools with active assignments proceed in attach while the downstream release path (computeCustomerLicenseReleases/executeCustomerLicenseReleases) does not account for dropped pools, which can leave assignments/licenses out of sync for customers after merge. Align the attach guard with the release logic (or add explicit dropped-pool handling there) and cover it with a regression test before merging.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="server/src/internal/billing/v2/actions/attach/errors/handleLicenseErrors/handleDroppedLicenseErrors.ts">

<violation number="1" location="server/src/internal/billing/v2/actions/attach/errors/handleLicenseErrors/handleDroppedLicenseErrors.ts:29">
P1: This guard now allows dropped pools with active assignments to pass through in the attach flow (`if (reason === "dropped") continue`), but the compensating release logic (`computeCustomerLicenseReleases` / `executeCustomerLicenseAssignmentReleases`) is only wired into the sync planner. When an immediate attach drops a pool that has assigned seats, the old product will expire while its entity assignments remain active — effectively orphaning them. Either the attach planner needs its own release step for dropped pools, or this guard should remain for the attach path.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

});

for (const { outgoingCustomerLicense, reason, group } of unmatched) {
if (reason === "dropped") continue;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: This guard now allows dropped pools with active assignments to pass through in the attach flow (if (reason === "dropped") continue), but the compensating release logic (computeCustomerLicenseReleases / executeCustomerLicenseAssignmentReleases) is only wired into the sync planner. When an immediate attach drops a pool that has assigned seats, the old product will expire while its entity assignments remain active — effectively orphaning them. Either the attach planner needs its own release step for dropped pools, or this guard should remain for the attach path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At server/src/internal/billing/v2/actions/attach/errors/handleLicenseErrors/handleDroppedLicenseErrors.ts, line 29:

<comment>This guard now allows dropped pools with active assignments to pass through in the attach flow (`if (reason === "dropped") continue`), but the compensating release logic (`computeCustomerLicenseReleases` / `executeCustomerLicenseAssignmentReleases`) is only wired into the sync planner. When an immediate attach drops a pool that has assigned seats, the old product will expire while its entity assignments remain active — effectively orphaning them. Either the attach planner needs its own release step for dropped pools, or this guard should remain for the attach path.</comment>

<file context>
@@ -28,20 +26,18 @@ export const handleDroppedLicenseErrors = ({
 	});
 
 	for (const { outgoingCustomerLicense, reason, group } of unmatched) {
+		if (reason === "dropped") continue;
 		const used = customerLicenseToUsage({
 			customerLicense: outgoingCustomerLicense,
</file context>

@charlietlamb
charlietlamb merged commit 7d8f975 into dev Jul 22, 2026
15 checks passed
@charlietlamb
charlietlamb deleted the charlie/license-release-qa branch July 22, 2026 12:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant