기록 측정 단위 칼로리(cal) 원자 신설 — 어썰트바이크류 컨디셔닝의 다섯 번째 원자 (2026-08-31)
- 기간: 2026-08-30 ~ 2026-08-31 (1 세션 — 이슈 #932: 실사용자 스크린샷 "어썰트바이크+박스점프오버+백스쿼트 90kg×(12+10+5)" 분석에서 출발한 복합 혼합 트랙의 잔여. 혼합 모델 자체는 병렬 트랙 #947이 선반영, 이 트랙은 칼로리 원자만 담당)
- 랜딩: PR #973 (
9fe02e9b, 묶음 1 PR) — 마이그레이션20260831150000_calories_atom_v1.sqlProduction 적용(check:remote-schemamissing 0), Vercel 배포 success + 배포 청크 마커 실측(WorkoutFlow 청크workout.set.calories) - 후속 랜딩(D3 이행): PR #990 — 프로필 폭 상한 2→3 + 유산소 기계 5종
{거리,시간,칼로리}지정(20260831170000Production 적용·5종 any 트리 실측) - 설계서: 없음 — 분석·Phase 계획은 이슈 #932 쓰레드(08-30 게시, 08-31 범위 재조정)
- 정본:
src/react/types/recording.ts(원자 어휘·유연 집합) ·src/react/ui/shared/measureSemantics.ts(M_FIELD·단위cal) · 서버20260831150000(정본 함수 21종+plan structure_core 재발행) - 도구:
npm run migrations:renumber·landing:lock— 랜딩 직렬화 절차(#966)의 첫 실전 소비자 - 게이트: 클라 스위트 1,980(관통 테스트 신설 2건), pgTAP 센티널 반전+4케이스, Production 드라이런 원자 롤백 4회(DRYRUN-OK-932-A~D), 풀 CI 1회 그린(마지막 재번호 커밋은 Actions 결제 차단으로 CI 불능 — 아래 "적용 결과" 참조)
- 버그리포트: 없음 — 이 트랙이 직접 수리한 오너 보고 결함 없음(선행 결함 2건은 #936·#947이 수리, BUG-041·042)
- 계약:
docs/data/app-screen-rpc-contract.md원자 열거 2곳 갱신
1. 배경
08-30 오너가 실사용자의 복합종목 기록을 공유하며 "측정 단위가 다른 종목도 자유롭게 묶자"고 방향을 냈다(#932). 조사 결과 어썰트바이크의 12는 칼로리를 횟수로 우겨넣은 값이었고, 원자 어휘(load/reps/distance/duration)에 칼로리가 없어 어떤 경로로도 cal을 기록할 수 없었다. 혼합 복합 모델은 병렬 트랙 #947이 parts로 선반영했고("칼로리는 별도 트랙" 명시 제외, 센티널 테스트까지 심어 둠), 이 트랙이 그 잔여인 칼로리 원자를 전층으로 신설했다.
2. 문제 제기
- 원자 어휘가 4종으로 봉인되어 있었다: 카탈로그 CHECK·서버 검증기 화이트리스트·required_inputs 문법·클라 타입·표시·입력 전층이
{load,reps,distance,duration}을 하드코딩. - 세트 저장 컬럼도 4원자뿐(
exercise_sets·planned_sets) — 값을 실을 자리 자체가 없었다. - "하나만 채우면 됨" 유연 판정(any)이
{distance,duration}부분집합으로 정의되어, 칼로리를 더해도 유연성이 따라오지 않는 구조였다.
3. 해결 방안
원칙 (오너 확정 2026-08-31)
- D1 표시 단위 =
cal(박스 관행). - D2 복합 토큰 내부의 거리+시간 =
500m·1:30축약(단일 종목의in표기는 불변). - (08-30 선행 확정) 표시는 A안 한 줄 시퀀스 + 동질 축약 유지.
접근
- 다섯 번째 원자, 규칙은 그대로: 프로필 상한(1..2원자)·정본 정렬·판정 구조를 바꾸지 않고 어휘만 넓힌다. 유연(any) 집합을
{distance,duration,calories}로 확장하되 기존 프로필의 판정은 전부 불변(pgTAP·클라 동치 스위프로 잠금). - 정본 추출-치환 빌더: 서버 함수 21종 재발행은 schema.sql의 마지막(정본) 정의를 기계로 추출해 치환(각 치환 앵커 count 단언) — 병렬 트랙이 정본을 갈아치울 때마다(#950 load_lb) 재추출로 따라갔다.
- 읽기 관대, 쓰기 엄격: 읽기 어댑터는 calories 키 부재를 허용(서버 재발행 전 구간 무파손), 쓰기는 프로필 밖 값 즉시 거부.
- 랜딩은 새 직렬화 절차(#966)로: 잠금 →
migrations:renumber→ CI → verify → 머지 → db push. 이 절차의 첫 실전 소비자가 됐다.
4. 적용한 내용
- 서버(
20260831150000):exercise_sets.calories·planned_sets.caloriesnumeric(8,2)+CHECK(>0, ≤100000) / recording_fields 원자 CHECK 3벌·measurement_type enum 확장 / sort·canonical·역매핑 3종 / required_inputs 문법·기본 트리 / apply·scrub·rescrub 4종+트리거 / 완료 v4·계획 v3(+structure_core)·보드 검증기 / 카탈로그 생성 3종 / 읽기 5종(session_detail·planned_detail·calendar decorate·presentation mains·board day sessions) / postcheck(임시 종목으로 v4 칼로리 관통·프로필 밖 거부·보드 에코 실측, 순변화 0). - 클라: 원자 어휘·유연 집합·논리식 미러 / 직렬화·검증 전층(완료·계획·플로우·드래프트·복합 parts·보드) / 읽기 투영(세션·계획·달력·피드·보드) / 입력(모바일 세트행·빠른 추가·수정 시트·복합 동작별 칸, 데스크톱 플랜 에디터
칼로리분기, 커스텀 등록 픽커 2벌, 관리자 단위cal·라벨·논리식 안내) / 표시(12cal토큰·합계 칩·보드 GbSeq·D2 축약).
주요 결정과 그 근거
- 칼로리를 유산소류(any) 원자로 편입 — 어썰트바이크가 "시간 또는 칼로리 중 하나"로 기록되는 실사용 관행. load·reps가 낀 프로필은 종전대로 전 원자 필수.
- 정수 강제 없이 양수 numeric — 콘솔 표기(정수)가 일반이나 소수 입력을 막을 이유가 없음.
- e2e 신규 CASE 미작성 — 신설 원자는 기존 CASE-021/022가 잠근 경로 위를 지나며, 단위 관통 테스트 2건+pgTAP+postcheck 드라이런이 층별 유실을 잠근다.
작업 중 드러난 것
- 하루 4트랙 경합의 실황: 번호 선점 2회(#950의 12만, #923의 13만)·main 전진 5회·머지 충돌 3회를 지나며 15만으로 랜딩 — 도중에 랜딩 직렬화 절차(#966)가 랜딩되어 후반은 그 절차(잠금·자동 재번호)로 진행했고, 그 와중에도 절차 밖 세션이 14만을 선점해 절차가 예고한 "드문 경우(재번호+CI 1회)"를 그대로 겪었다.
- GitHub Actions 결제 차단: 마지막 재번호 커밋의 CI가 러너 기동 단계에서 결제 실패로 거부됨("recent account payments have failed or your spending limit needs to be increased"). 직전 sha의 풀 레인 그린+재번호 전용 로컬 게이트+Production 드라이런을 근거로 admin 머지로 진행 — 오너 조치 필요(GitHub Billing).
- check:remote-schema의 낡은 토큰 핀: 볼륨 오버뷰 RPC 3종에 옛 마이그레이션 번호 토큰을 요구하는 release_contract 검사가 로컬 정본·Production 양쪽 부재로 상시 빨간불 — 이 랜딩과 무관(머지 전 체크아웃에서 동일 재현), 이슈 #982로 분리.
- 마이그레이션 전문 드라이런 기법의 확장: DO-raise 원자 롤백을 postcheck까지 포함한 2,700줄 마이그레이션 전체에 적용 — CI 이전에 Production 스키마 기준으로 4회 통검했고, 이것이 결제 차단 국면에서 머지 근거의 한 축이 됐다.
5. 적용 결과
| 항목 | 전 → 후 |
|---|---|
| 칼로리 기록 | 원자 없음(횟수로 우겨넣기) → 단일 종목·이종 복합(parts)·계획·그룹 보드 전 경로에서 cal 기록·왕복 |
| 유연 판정 | any = {거리,시간} → {거리,시간,칼로리} (기존 프로필 판정 전부 불변 — 동치 스위프·pgTAP) |
| 표시 | — → 세트 토큰 12cal·합계 칩 총 칼로리·보드 토큰, D2 500m·1:30(복합 내부) |
| 검증 | 클라 1,980/1,980 · pgTAP plan 26→30 · Production 드라이런 4회 · 배포 청크 마커 실측 |
| 미검증 | 최종 재번호 커밋의 CI 1회(Actions 결제 차단 — 직전 sha 풀 레인 그린으로 갈음) · 실기기 입력 UX(오너 확인 게이트 폐지 방침에 따라 생략) |
6. 이번 개선으로 향상된 것
사용자: 어썰트바이크를 어썰트바이크답게 기록한다
카탈로그(또는 커스텀)에 칼로리 프로필을 지정하면 세트 입력칸이 cal로 뜨고, "12cal + 10회 + 90kg×5회" 같은 이종 복합도 동작별로 제 단위로 저장·표시된다.
구조적으로 남는 것
- 원자 신설의 전층 체크리스트가 실코드로 남음 — 여섯 번째 원자는 이 마이그레이션·이 diff를 따라가면 된다.
- 정본 추출-치환 빌더(스크래치패드) — 대량 함수 재발행을 병렬 정본 변경에 안전하게 따라가게 한 방식.
- 랜딩 직렬화 절차(#966)의 첫 실전 통과 기록.
후속 — D3 이행: 폭 상한 3과 5종 지정 (같은 날, PR #990)
오너 확정 "5종 다 cal 있어야함"에 대해 로잉머신의 거리 기록이 45%(59/132세트)임을 실측으로 제시하자, 오너가 폭 상한 2→3 확장을 택했다(그리고 정확한 반문 — "저 상한은 and 상한이고 이건 or인데 왜 문제냐" — 가 이 선택의 논거 그 자체다: 폭 상한은 기록할 수 있는 원자 수이고, 채움 판정은 required_inputs가 or로 별도 담당하므로 넓혀도 부담이 늘지 않는다).
- 서버(
20260831170000): 원자 CHECK 3벌·byte 묶음 CHECK 2벌 cardinality≤3(중복 방지는 pairwise — CHECK엔 서브쿼리 불가), 완료 v4·계획 v3·카탈로그 생성 2종 원자 수 경계 1..3, canonical limit 3, 5종 데이터 지정. - 함정: required_inputs 트리거는 기존 트리가 새 프로필에서 유효하면 재유도하지 않는다 — any{거리,시간}이 남아 칼로리 단독이 거부됨. 프로필 갱신 시
required_inputs = null동시 기입으로 기본 트리 재유도를 강제(드라이런 1차 postcheck가 적발). - 클라: slice 3·maxRecordingFields 3(계약+예산)·어댑터 경계 4곳·픽커 상한 라벨, 한도 정본 장부 "기록 방식 원자 수 3" + 3층 게이트 갱신.
- 검증: 클라 스위트 1,990/1,990 · Production 드라이런 원자 롤백(DRYRUN-OK-CAP3-B) · 적용 후 5종
{distance,duration,calories}+any 트리 실측 · missing 0. CI는 Actions 결제 차단으로 로컬 check 전 체인(#975 확립 기준)으로 갈음.
후속 2 — or 프로필 기록 항목 선택기 → on/off 토글 (같은 날, PR #993·#996)
오너 지시 2단: ① or 프로필은 원자 칸 셋을 나란히 강제하지 말고 골라서 열 것(#993 — 단일 선택기) → ② 단일 선택이 아니라 on/off 토글로, 여러 개를 켜서 같이 적을 수 있게 하고 켠 칸들의 연결은 그냥 ×(#996 — 최종형). 기록 화면 옵션바·수정 시트에 "기록 항목" 토글 세그(거리|시간|칼로리 — 값이 실린 원자는 점 표시, 최소 1개 유지, 꺼도 값 보존), 복합 동작은 단위 자리 토글 묶음. 입력 칸의 " in " 구분자는 은퇴(표시행 "5km in 30:00"은 불변), 채움 판정(any)·저장 계약 무변경. 원자 한글 라벨 정본 M_LABEL 신설, 마커 workout.set.measurePick 등재. DOM 회귀 재구성(양쪽 켜기·한쪽 켜기·복합 병기) 포함 스위트 1,995 그린, 배포 청크 마커 실측. 데스크톱 플랜 에디터(광폭 테이블)는 범위 밖 — 요청 시 후속.
남은 것
- 오너 조치: GitHub Actions 결제/지출 한도(전 트랙 CI 차단 중).
- 별건: check:remote-schema 토큰 핀 수리(#982), 구형 복합 소급(#953).