Skip to content

fix(usage): create rollover periods from the current Box attribution #1207

Description

@ltstriker

Summary

closeAndReopenUsagePeriods() currently creates the next usage period by cloning the previous period through BoxUsagePeriod.fromUsagePeriod().

The clone carries forward the previous period's organizationId and region; rollover only replaces the resources. If the Box attribution has changed since the previous period was
opened, the newly created period can retain stale attribution.

This behavior predates #1104 and already exists on main. It was identified while reviewing that PR.

Current flow

closeAndReopenUsagePeriods()
→ read current Box outside the period transaction
→ close the previous period
→ clone the previous period
  ← BUG: copies the previous organizationId and region
→ replace resources from the current Box state
→ save the new period

Simply querying the Box again before saving would not fully solve the problem because an external Box update could still commit between the query and the insert.

Expected behavior

When rolling over a usage period:

  • The closed period keeps its original organizationId and region.
  • The new period uses the current Box's organizationId, region, and resources.
  • The new period starts at exactly the same timestamp at which the old period ends.
  • Closing the old period and creating the new one are atomic.
  • Concurrent Box updates must not result in a newly opened period using a stale Box snapshot.
  • There must remain at most one open period per Box.

Proposed direction

Within one database transaction:

lock current Box (FOR UPDATE)
→ re-query and lock the eligible open period
→ choose one transition timestamp
→ close the old period without changing its attribution
→ create the new period from the locked Box
→ commit

The new period should be constructed through the existing Box-based creation path rather than fromUsagePeriod(). The creation helper may need to accept an explicit startAt and
transactional EntityManager.

Use a consistent lock order (Box before BoxUsagePeriod) and roll back the entire transition if locking or insertion fails.

Acceptance criteria

  • A rollover test starts with an old period attributed to organization/region A and a current Box attributed to B.
  • After rollover, the closed period remains attributed to A.
  • The new open period is attributed to B.
  • A PostgreSQL concurrency test verifies that a concurrent Box update cannot produce a stale newly opened period.
  • Failure to create the new period rolls back closing the old period.

Notes

The warm-pool sentinel organization is currently excluded from the rollover query, so this is a pre-existing consistency risk rather than a regression introduced by #1104.

If real organization-to-organization reassignment is supported, the reassignment writer should eventually update the Box and cut over its usage period in the same database transaction.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions