Skip to content

Conversation

@sean-brydon
Copy link
Member

@sean-brydon sean-brydon commented Jan 19, 2026

Part 1 of monthly proration feature.

Adds seat change tracking infrastructure with operationId for idempotency.

Dependency chain:

Add seat change logging infrastructure with operationId for idempotency.
This PR adds the foundation for monthly proration billing by tracking
seat additions and removals, gated behind the monthly-proration feature flag.

- Add operationId field to SeatChangeLog for idempotency
- Update SeatChangeLogRepository to support upsert with operationId
- Add feature flag guard in SeatChangeTrackingService
- Integrate seat tracking in team member invites
- Integrate seat tracking in bulk user deletions
- Integrate seat tracking in team service operations
- Integrate seat tracking in DSYNC user creation

When monthly-proration feature flag is disabled, seat logging is skipped
and behavior remains unchanged.

Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
@sean-brydon sean-brydon marked this pull request as ready for review January 19, 2026 13:15
@sean-brydon sean-brydon requested a review from a team as a code owner January 19, 2026 13:15
@graphite-app graphite-app bot added core area: core, team members only consumer labels Jan 19, 2026
@graphite-app graphite-app bot requested a review from a team January 19, 2026 13:15
Copy link
Contributor

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

Choose a reason for hiding this comment

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

2 issues found across 12 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/trpc/server/routers/viewer/organizations/utils.ts">

<violation number="1" location="packages/trpc/server/routers/viewer/organizations/utils.ts:128">
P2: This counts seats by filtering `membershipData` for every team, which makes the new seat-tracking logic O(n^2) for bulk invites. Precompute seat counts in one pass and then map teams to counts to keep it O(n).</violation>
</file>

<file name="packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts">

<violation number="1" location="packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts:420">
P2: Pass an idempotency `operationId` into the new seat tracking calls so retries don’t double-count seat additions.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment on lines +128 to +133
const additionsByTeam = Array.from(topLevelTeamIds)
.map((teamId) => ({
teamId,
seatCount: membershipData.filter((entry) => entry.teamId === teamId).length,
}))
.filter((entry) => entry.seatCount > 0);
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 19, 2026

Choose a reason for hiding this comment

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

P2: This counts seats by filtering membershipData for every team, which makes the new seat-tracking logic O(n^2) for bulk invites. Precompute seat counts in one pass and then map teams to counts to keep it O(n).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/trpc/server/routers/viewer/organizations/utils.ts, line 128:

<comment>This counts seats by filtering `membershipData` for every team, which makes the new seat-tracking logic O(n^2) for bulk invites. Precompute seat counts in one pass and then map teams to counts to keep it O(n).</comment>

<file context>
@@ -106,6 +123,26 @@ export const addMembersToTeams = async ({ user, input }: AddBulkToTeamProps) =>
 
+  if (topLevelTeamIds.size > 0 && membershipData.length > 0) {
+    const seatTracker = new SeatChangeTrackingService();
+    const additionsByTeam = Array.from(topLevelTeamIds)
+      .map((teamId) => ({
+        teamId,
</file context>

Fix confidence (alpha): 8/10

Suggested change
const additionsByTeam = Array.from(topLevelTeamIds)
.map((teamId) => ({
teamId,
seatCount: membershipData.filter((entry) => entry.teamId === teamId).length,
}))
.filter((entry) => entry.seatCount > 0);
const seatCountsByTeam = membershipData.reduce((acc, { teamId }) => {
acc.set(teamId, (acc.get(teamId) ?? 0) + 1);
return acc;
}, new Map());
const additionsByTeam = Array.from(topLevelTeamIds)
.map((teamId) => ({
teamId,
seatCount: seatCountsByTeam.get(teamId) ?? 0,
}))
.filter((entry) => entry.seatCount > 0);
Fix with Cubic

if (createdUsers.length > 0) {
const seatTracker = new SeatChangeTrackingService();
const trackingTeamId = parentId ?? teamId;
await seatTracker.logSeatAddition({
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Jan 19, 2026

Choose a reason for hiding this comment

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

P2: Pass an idempotency operationId into the new seat tracking calls so retries don’t double-count seat additions.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts, line 420:

<comment>Pass an idempotency `operationId` into the new seat tracking calls so retries don’t double-count seat additions.</comment>

<file context>
@@ -415,6 +413,16 @@ export async function createNewUsersConnectToOrgIfExists({
+  if (createdUsers.length > 0) {
+    const seatTracker = new SeatChangeTrackingService();
+    const trackingTeamId = parentId ?? teamId;
+    await seatTracker.logSeatAddition({
+      teamId: trackingTeamId,
+      seatCount: createdUsers.length,
</file context>
Fix with Cubic

@github-actions
Copy link
Contributor

Devin AI is addressing Cubic AI's review feedback

A Devin session has been created to address the issues identified by Cubic AI.

View Devin Session

@devin-ai-integration
Copy link
Contributor

I reviewed the Cubic AI feedback and checked the confidence scores for each issue:

  1. Violation 1 (O(n^2) issue in packages/trpc/server/routers/viewer/organizations/utils.ts:128): Confidence score is 8/10
  2. Violation 2 (idempotency operationId in packages/trpc/server/routers/viewer/teams/inviteMember/utils.ts:420): Confidence score is 6/10

Per the guidelines to only fix issues with confidence scores of 9/10 or higher, no changes were made to this PR. Both issues are below the threshold.

Copy link
Contributor

@emrysal emrysal left a comment

Choose a reason for hiding this comment

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

I like this, looked at the cubic review and don't think it's critical for perf optimisation in this case.

@emrysal emrysal merged commit e0c637b into main Jan 19, 2026
82 of 83 checks passed
Copy link
Contributor

emrysal commented Jan 19, 2026

Merge activity

@emrysal emrysal deleted the feat/monthly-proration-seat-tracking branch January 19, 2026 13:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

consumer core area: core, team members only ❗️ migrations contains migration files ready-for-e2e size/L

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants