[Quote][KJPGP20RV6] Quote application failed: Active billing team found
RCA: [Quote][KJPGP20RV6] Quote application failed: Active billing team found
Overview#
What Happened#
2026-05-26 15:34 UTC에 cupixworks-api 서비스에서 관리자(philip.hall@cupix.com)가 Quote 8228(번호 KJPGP20RV6)을 Workspace 5646에 적용하려 시도했으나, 해당 Workspace의 상위 Team이 이미 active billing 상태여서 BILL2100 에러와 함께 HTTP 400으로 실패했다. 단일 발생이며 비즈니스 로직 검증 에러이다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Cupix::Errors::Billing |
| exception.message | Active billing team found |
| top_frame | app/models/concerns/billable/workspace.rb:45 |
| deploy | production-us-west-2-20260526T0443Z0-dd7bd097-cupixworks |
| env | production, us-west-2 |
Timeline#
- 2026-05-26T15:34:00.277Z — Quote KJPGP20RV6 apply 시작, 중간 단계 진행 후
check_on_billing_activation!에서 실패 - 2026-05-26T15:34:00.328Z — API 응답 HTTP 400 반환 (303ms 소요)
- 2026-05-27 — RCA 수행
Error Log#
[Quote][KJPGP20RV6] Quote application failed: Active billing team found
Impact#
- Service:
cupixworks-api - 발생 횟수: 1
- 최초 발생: 2026-05-26T15:34:00.277Z
- 최근 발생: 2026-05-26T15:34:00.277Z
단일 admin 사용자의 quote apply 요청 실패. 최종 사용자 영향 없음. Quote 미적용 상태 유지.
Root Cause Summary#
Workspace 5646에 Quote를 apply하는 과정에서, 상태 머신의 before_transition to: :active 훅이 check_on_billing_activation!을 호출한다. 이 메서드는 Workspace의 상위 Team이 이미 billing_state_active?인지 확인하며, active 상태일 경우 BILL2100 에러를 발생시킨다. 이는 하나의 billing 계층(Team ↔ Workspace)에서 중복 billing activation을 방지하는 의도적 비즈니스 검증 로직이다. 에러 자체는 정상적인 비즈니스 규칙 위반 감지이나, error 레벨로 로깅되어 모니터링에 잡힌 것이 문제이다.
Technical Analysis#
Code Path#
- Entry point:
app/controllers/api/v1/admin/quotes_controller.rb—applyaction - Repository:
app/repositories/admin/quote_repository.rb:48—@model.apply!호출 - Quote model:
app/models/quote.rb:78-151—apply!메서드 - Failure point:
app/models/concerns/billable/workspace.rb:45—check_on_billing_activation!
실행 흐름:
- Admin이
PUT /api/v1/admin/quotes/8228/apply호출 QuoteRepository#apply가 권한 확인 후@model.apply!호출
def apply
raise Cupix::Errors::Billing.new(code: 'BILL10000', reason: 'Permission denied operation: Only accounting deparment can apply the quote') unless Pundit.policy(self.current_user, @model).apply?
@model.apply!
@model
end
Quote#apply!가billable.reset_billing!으로 기존 billing 초기화 후, 제품 추가 및 billing 정보 업데이트 진행
def apply!
Cupix::Logger.info("[Quote][#{number}] Quote application begins #{billable.class.name} #{billable.id}")
check_appliable
billable.reset_billing!
Cupix::Logger.info("[Quote][#{number}] Deactivate existing products")
- 마지막 단계에서
billable.active_billing_state!호출 시 상태 머신의before_transition훅 실행
Cupix::Logger.info("[Quote][#{number}] Activate billing state: #{_billing_started_at} to #{_billing_expires_at} for #{_contract_months} months")
if _billing_started_at > Date.current
billable.scheduled_billing_state!
else
billable.active_billing_state!
end
- 상태 머신 훅이
check_on_billing_activation!호출
before_transition from: any, to: :active do |billable, transition|
billable.check_on_billing_activation!
end
- Workspace의 구현이 상위 Team의 billing 상태 확인 후 에러 발생
def check_on_billing_activation!
if team.billing_state_active?
raise Cupix::Errors::Billing.new(code: 'BILL2100', reason: 'Active billing team found')
end
end
Quote#apply!의 rescue 블록이 에러를error레벨로 로깅 후 re-raise
rescue StandardError => e
Cupix::Logger.error("[Quote][#{number}] Quote application failed: #{e.message}")
raise e
기대 동작: Workspace의 상위 Team이 active billing이 아닌 경우에만 quote가 적용되어야 한다.
실제 동작: Team이 이미 active billing 상태이므로 검증에서 정상적으로 거부됨. 비즈니스 규칙 위반은 정확히 감지되었으나, error 레벨 로깅이 과도하다.
Log Evidence#
사용한 Datadog 쿼리:
service:cupixworks-api @environment:production "KJPGP20RV6"
실행 로그 (시간순):
[Quote][KJPGP20RV6] Quote application begins Workspace 5646
[Quote][KJPGP20RV6] Deactivate existing products
[Quote][KJPGP20RV6] Add team products
[Quote][KJPGP20RV6] Update billing_info to Workspace 5646
[Quote][KJPGP20RV6] Activate billing state: 2026-05-22 to 2027-06-01 for 12 months
[Quote][KJPGP20RV6] Quote application failed: Active billing team found
HTTP 요청 로그:
[400] PUT /api/v1/admin/quotes/8228/apply (Api::V1::Admin::QuotesController#apply)
Error Code: BILL2100
Error Class: Cupix::Errors::Billing
Error Reason: Active billing team found
User: philip.hall@cupix.com (ID: 4568)
Duration: 303.81ms
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | Workspace의 상위 Team이 이미 active billing이라 비즈니스 규칙에 의해 정당하게 거부됨 | 코드 workspace.rb:44에서 team.billing_state_active? 체크, 로그에서 모든 중간 단계 정상 진행 후 마지막 activation 단계에서만 실패 |
— | Confirmed |
| H2 | reset_billing!이 Team 레벨의 billing state도 초기화했어야 하나 Workspace만 초기화함 |
reset_billing!은 해당 billable(Workspace)만 초기화 (resetable/billable.rb:19-27), Team의 billing은 별도 |
reset_billing!의 설계 의도가 자신의 billing만 초기화하는 것이므로 버그 아님 |
Rejected |
| H3 | Transaction이 없어 부분 적용(제품 추가, billing_info 업데이트)이 롤백되지 않는 데이터 정합성 문제 | Quote#apply!와 controller에 transaction 블록 없음, grep 결과 확인 |
이 에러 자체의 root cause는 아님. 별도의 잠재적 문제 | Inconclusive |
Fix Recommendation#
즉시 조치 (Critical)#
이 에러는 의도된 비즈니스 검증 로직의 정상 작동이다. error 레벨로 로깅하는 것이 과도하므로, Quote#apply!의 rescue 블록에서 Cupix::Errors::Billing 예외를 별도 처리하여 warn 레벨로 다운그레이드하는 것을 권장한다.
- 파일:
app/models/quote.rb:145-147 - 방향:
Cupix::Errors::Billing예외는warn레벨로 로깅, 그 외StandardError는 기존대로error레벨 유지
단기 개선 (1주 이내)#
check_on_billing_activation!검증을apply!메서드 초반(check_appliable직후)으로 이동하여, 부분적 상태 변경(제품 추가, billing_info 업데이트) 전에 조기 실패하도록 개선- 또는
apply!전체를 DB transaction으로 감싸서 실패 시 롤백 보장
장기 개선 (재발 방지)#
- Quote apply 프로세스에 pre-validation 단계를 추가하여, 실제 적용 전에 모든 조건을 사전 검증하는 dry-run 모드 구현
- billing state 관련 비즈니스 에러를
error대신warn으로 로깅하는 정책 수립 (모든Cupix::Errors::Billing예외에 대해)
Monitoring#
- 비즈니스 로직 에러를 error에서 분리하는 쿼리:
service:cupixworks-api status:error "Quote application failed" -"Active billing team found" -"Active billing workspaces found"
- Quote apply 실패율 추적:
service:cupixworks-api "Quote application failed" | stats count by @error.code
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial
단일 발생이며, 비즈니스 규칙이 정상 작동한 결과이다. 사용자 영향 없음. 로그 레벨 조정만으로 해결 가능.