Invalid Formula: Dentaku::AST::Multiplication has too many operands (given 3, expected 2)
RCA: Invalid Formula: Dentaku::AST::Multiplication has too many operands (given 3, expected 2)
Overview#
What Happened#
2026-07-28 18:20:15 KST 에 cupixworks-api 의 PUT /api/v1/phase_metrics/249 요청 1건이 Cupix::Errors::Parameter (ARG10001) 로 400 응답을 반환했다. 원인은 사용자가 저장한 phase metric formula 를 Dentaku 로 파싱하는 과정에서 Dentaku::AST::Multiplication has too many operands (given 3, expected 2) 예외가 발생했기 때문이며, 이는 formula 문자열에 곱셈 노드 하나에 대해 피연산자가 3개인 잘못된 표현식(예: 연속된 dimension token 사이에 연산자 누락)이 포함되었기 때문이다. 같은 endpoint 는 동일 리소스(id=249)에 대해 12초 전(18:20:03)과 12초 후(18:20:27) 요청 모두 200 으로 성공했다.
Quick Facts#
| Field | Value |
|---|---|
| exception.class | Cupix::Errors::Parameter |
| exception.message | Invalid Formula: Dentaku::AST::Multiplication has too many operands (given 3, expected 2) |
| top_frame | lib/cupix/util/formula_parser.rb:22 (Dentaku::Calculator#evaluate!) |
| logged_by | Cupix::Util::FormulaParser.calculate! (lib/cupix/util/formula_parser.rb:24) |
| endpoint | PUT /api/v1/phase_metrics/249 |
| env | production, us-west-2 |
| tenant | cupix |
| error_code | ARG10001 |
Affected Teams#
| Team / Domain | Error Count | Impact |
|---|---|---|
| phase metrics (formula authoring) | 1 | 단일 요청 400 반환; 동일 유저의 이후 요청은 성공 — 사용자 영향 없음 |
Timeline#
- 2026-07-28 18:17:46 KST —
PUT /api/v1/phase_metrics/249성공 (200) - 2026-07-28 18:20:03 KST —
PUT /api/v1/phase_metrics/249성공 (200) - 2026-07-28 18:20:15 KST —
PUT /api/v1/phase_metrics/249실패 (400,Invalid Formula: Dentaku::AST::Multiplication has too many operands).Cupix::Util::FormulaParser.calculate!가Cupix::Logger.error로 기록 - 2026-07-28 18:20:27 KST —
PUT /api/v1/phase_metrics/249성공 (200) — 사용자가 formula 를 수정해 재제출한 것으로 보임
Error Log#
Invalid Formula: Dentaku::AST::Multiplication has too many operands (given 3, expected 2)
Impact#
- Service:
cupixworks-api - 발생 횟수: 1
- 최초 발생: 2026-07-28 18:20:15 KST
- 최근 발생: 2026-07-28 18:20:15 KST
Root Cause Summary#
Cupix::Util::FormulaParser.calculate! 는 Dentaku::Calculator#evaluate! 로 formula 문자열을 평가하고, 실패 시 모든 StandardError 를 rescue 하여 Cupix::Logger.error 로 로깅한 뒤 Cupix::Errors::Parameter 를 raise 한다. 이번 사건에서 Dentaku 파서는 곱셈 AST 노드 하나에 피연산자가 3개인 표현식을 만나 Dentaku::AST::Multiplication has too many operands (given 3, expected 2) 예외를 던졌으며, 이는 사용자가 저장한 formula 에 연산자 없이 인접한 dimension/property token(예: !{length}!{weight}*!{volume} 형태)이 포함되어 있었기 때문이다. Controller/repository 는 이 예외를 400 응답으로 정상 매핑했다(error.class: Cupix::Errors::Parameter, error.code: ARG10001). 즉 서버 로직 결함이 아니라 입력 검증이 정상 동작한 사례이며, 유일한 실질적 이슈는 사용자 입력 오류가 error 레벨 로그로 기록되어 에러 클러스터로 승격되었다는 점이다.
Technical Analysis#
Code Path#
Entry point: app/controllers/api/v1/phase_metrics_controller.rb:24 (Api::V1::PhaseMetricsController#update)
def update
@model = repository_instance.update(params)
super
end
Repository update 는 set_parameters 를 호출해 formula 를 검증한다:
def update(params = {})
super
set_parameters(params)
begin
@model.save!
rescue StandardError => e
raise Cupix::Errors::Parameter.new(code: 'ARG10001', reason: 'Invalid argument', message: e.message)
end
@model
end
Parameter::PhaseMetric concern 이 set_parameters 안에서 FormulaParser.validate! 를 호출:
def set_parameters(params = {})
set_formula(params) if params[:formula].present?
super
end
def set_formula(params)
Cupix::Util::FormulaParser.validate!(params[:formula], @model.facility)
@model.formula = params[:formula]
end
Failure point: lib/cupix/util/formula_parser.rb:19-28 — validate! 가 dimension/custom_property/constant 토큰을 1 로 치환한 뒤 calculate! 로 Dentaku 파싱을 수행. 사용자 입력이 곱셈 노드에 3개 피연산자를 만들면 여기서 예외 발생:
def calculate!(expression)
calculator = ::Dentaku::Calculator.new
begin
calculator.evaluate!(expression).to_f!.round(5)
rescue StandardError => e
Cupix::Logger.error("Invalid Formula: #{e.message}", class: self.name, function: __method__, formula: expression)
raise Cupix::Errors::Parameter.new(code: 'ARG10001', reason: "Invalid Formula: #{e.message}")
end
end
validate! 는 위 로직을 사용자 formula 에 그대로 적용한다:
def validate!(formula, facility)
calculate!(formula.gsub(/!\{([^}]*)\}/) { |m| '1 ' if validate_dimension!(Regexp.last_match(1)) }
.gsub(/\$\{([^}]*)\}/) { |m| '1 ' if validate_custom_property!(Regexp.last_match(1), facility) }
.gsub(/@\{([^}]*)\}/) { |m| '1 ' if validate_constant!(Regexp.last_match(1)) })
end
Spec 파일이 정확히 이 실패 케이스를 명시적으로 커버한다 — 즉, "operator 개수가 잘못된 formula" 는 의도된 검증 실패 경로이다:
it 'return false with invalid number of operands & operators' do
invalids = []
invalids << '!{length}!{weight}+!{volume}*!{volume}!{volume}'
invalids << '!{area}!{area}!{area}!{area}!{area}!{area}!{area}!{area}!{area}!{area}!{area}*!{volume}*!{area}'
invalids << '!{length}++!{weight}'
invalids << '!{length}+*!{weight}'
invalids.each do |exp|
expect { described_class.validate!(exp, facility) }.to raise_error(Cupix::Errors::Parameter)
end
end
기대 동작 vs 실제 동작: 잘못된 formula 를 클라이언트가 보내면 validate! → calculate! 가 Dentaku 예외를 잡아 Cupix::Errors::Parameter(ARG10001) 로 400 응답을 반환하는 것이 설계된 흐름이다. 실제로도 400 이 반환되었고 곧이어 사용자가 formula 를 수정해 200 성공했다. 유일한 부작용은 calculate! 안의 Cupix::Logger.error 호출이 사용자 입력 실패까지 error 레벨로 기록하여 error-sweeper 클러스터에 포집된다는 점이다.
Log Evidence#
Datadog query (재현용):
service:cupixworks-api "phase_metrics/249"
시간대별 요청/에러 로그:
{
"timestamp": "2026-07-28 18:17:46 KST",
"status": "info",
"message": "[200] PUT /api/v1/phase_metrics/249 (Api::V1::PhaseMetricsController#update)"
}
{
"timestamp": "2026-07-28 18:20:03 KST",
"status": "info",
"message": "[200] PUT /api/v1/phase_metrics/249 (Api::V1::PhaseMetricsController#update)"
}
{
"timestamp": "2026-07-28 18:20:15 KST",
"status": "info",
"message": "[400] PUT /api/v1/phase_metrics/249 (Api::V1::PhaseMetricsController#update)",
"error": {
"reason": "Invalid Formula: Dentaku::AST::Multiplication has too many operands (given 3, expected 2)",
"code": "ARG10001",
"message": "Invalid Formula: Dentaku::AST::Multiplication has too many operands (given 3, expected 2)",
"class": "Cupix::Errors::Parameter"
}
}
{
"timestamp": "2026-07-28 18:20:15 KST",
"status": "error",
"message": "Invalid Formula: Dentaku::AST::Multiplication has too many operands (given 3, expected 2)",
"class": "Cupix::Util::FormulaParser",
"function": "calculate!"
}
{
"timestamp": "2026-07-28 18:20:27 KST",
"status": "info",
"message": "[200] PUT /api/v1/phase_metrics/249 (Api::V1::PhaseMetricsController#update)"
}
추가 확인: 최근 14일간 service:cupixworks-api "Dentaku" 검색은 이 사건 1건 외 결과 없음(service:cupixworks-api "FormulaParser" 0건). 재발/광범위 영향 징후 없음.
Hypotheses Considered#
| # | Hypothesis | Evidence for | Evidence against | Verdict |
|---|---|---|---|---|
| H1 | 사용자가 저장한 formula 문자열이 Dentaku 문법에 어긋나(곱셈 노드에 3-피연산자) 발생한 입력 검증 실패 | 로그의 error.class: Cupix::Errors::Parameter, error.code: ARG10001, HTTP 400 응답; formula_parser.rb:19-28 의 rescue → raise 흐름; spec 이 동일 케이스(!{length}!{weight}+...)를 커버(formula_parser_spec.rb:54-63); 12초 후 동일 endpoint 200 성공 |
— | Confirmed |
| H2 | Dentaku gem 버그 또는 라이브러리 회귀로 인한 서버측 결함 | — | 최근 14일간 동일 서비스에서 Dentaku/FormulaParser 관련 에러 총 1건, 동일 리소스(id=249)에 대해 앞뒤 요청 성공. 회귀였다면 유사 로그가 다수 축적되어야 함 |
Rejected |
| H3 | 부분 저장/데이터 손상으로 phase_metric 자체가 이상 상태에 빠짐 | — | repository.update 는 set_parameters 에서 검증 실패 시 예외를 raise 하므로 @model.save! 에 도달하지 않음(phase_metric_repository.rb:50-62). 이후 200 성공으로 정상 저장 확인 |
Rejected |
| H4 | Cupix::Logger.error 호출이 클러스터 승격의 실질 이슈 |
formula_parser.rb:24 가 사용자 입력 오류를 무조건 error 레벨로 기록 → error-sweeper 가 클러스터화. 그러나 재발 빈도 낮음(1건) |
로그 레벨 문제는 root cause 가 아니라 signal 노이즈 | Confirmed(부수 이슈) |
Fix Recommendation#
즉시 조치 (Critical)#
- 없음. 사용자 입력 검증이 설계대로 동작해 400 을 반환했다. 코드 변경 불필요.
단기 개선 (1주 이내)#
lib/cupix/util/formula_parser.rb:24의Cupix::Logger.error를warn레벨로 낮출지 검토. 이 로그는calculate!내부에서 Dentaku 예외를 잡을 때 항상 찍히는데,validate!경로에서는 곧바로ARG10001로 400 응답이 나가는 정상 흐름이다. 사용자 입력 오류(정상 400)와 시스템 오류(진짜 버그)를 구분하지 못해 error-sweeper 클러스터로 승격되어 노이즈를 만든다. 대안:validate!호출 컨텍스트(사용자 입력 검증)에서는warn, 내부에서 이미 정상화된 표현식을 계산하는 컨텍스트(예:parse!이후 계산)에서는error로 분기.- 또는 rescue 대상을
Dentaku::ParseError/Dentaku::TokenizerError(및 유사 문법 오류) 로 좁혀warn, 그 외StandardError는 기존대로error유지. (Sorbet/의존성 확인 필요 — memory 노트 참고)
- 근거: MEMORY.md "Assess error severity during RCA" 및 "Scope warn-level downgrades to the specific exception class" 원칙을 따라, 광범위 rescue 를 유지하되 문법 오류만 downgrade.
장기 개선 (재발 방지)#
- 프런트엔드 formula 편집기에 실시간 문법 검증기(Dentaku 와 동일 규칙) 를 도입해 서버 400 왕복 없이 사용자에게 즉시 피드백 제공. 서버 로그에 잡히는 사용자 입력 오류 자체를 줄여 관측 노이즈 감소.
Monitoring#
error레벨로 남은 formula 검증 실패는 error-sweeper 가 자동 클러스터화하므로 별도 알림은 불필요. 단기 개선으로warn이동 시, 다음 쿼리로 volume 을 모니터링:
service:cupixworks-api status:warn "Invalid Formula"
- 실제 서버측 회귀(Dentaku 업그레이드 등)를 조기 감지하려면 아래 쿼리로 급증 확인:
service:cupixworks-api status:error @class:Cupix\:\:Util\:\:FormulaParser
Risk Assessment#
- Risk level: low
- 예상 복잡도: trivial (단기 개선 채택 시 단일 파일 로그 레벨 조정)