운동 종목 데이터 모델 정책
- 정책 ID:
lift-guild.exercise-model - 정책 버전:
2.0.0 - 결정 상태: accepted
- 시행 상태: partial (
enforced_since미정) - 결정일: 2026-08-21 (최초 결정 2026-07-27)
- 구현 감사 기준 리비전:
b9a76ce9ce7563ae793f8cf3620257b39bbfc8b0 - 기계 판독 정본:
docs/policies/exercise-model/policy.json - 불변 버전 스냅샷:
docs/policies/exercise-model/versions/2.0.0
2.0.0 (2026-08-21, breaking — 별칭 계약 개정) — 카탈로그 synonym 컷오버(Phase 0~3, PR #526·#541·#544 + 구층 은퇴 마이그레이션
20260821250000)로 별칭 계약을 3층 트리로 바꿨다: 종목(exercises.id) → synonym(exercise_synonyms, 동등한 표기, 종목당 default 정확히 하나, 자체 정체성 없음) → 교정 별칭(synonym 소속, 검색 전용). 기록은session_exercises.synonym_id로 고른 표기를 참조하고(표시 전용·nullable), 구 컬럼exercises.aliases는 은퇴했다. 종목 정체성·통계 귀속·기록 사실의 의미는 변하지 않는다 — 바뀐 것은/contract/aliases서브트리뿐이며, breaking_paths에 속하므로 major다. 위 감사 기준 리비전은 Phase 2(쓰기 컷오버) 머지 시점의 main이다.1.0.1 (2026-08-21, documentation patch) — 옛 브랜드 개명에 맞춰 conformance의 구현 경계 경로를 실제 구현 모듈(
barbelicMappers.ts·barbelicViewMappers.ts)로 갱신하고 경로 유지용 shim(liftGuildMappers.ts·liftGuildViewMappers.ts)을 은퇴했다. 계약·지문·통계 의미 변경 없음. 아래 구현 감사표는 1.0.0 당시 리비전 (ed32f681f183cedaf00f02fa8edfe4c01082a46f) 기준 그대로이며, 위 감사 기준 리비전은 경계 경로를 재확인한 리비전이다.
목적
이 정책은 다음 세 개념을 영구적으로 분리한다.
- PR과 통계가 귀속되는 canonical 종목
- 여러 종목을 한 세트에서 순서대로 수행하는 연속 동작 세트(훈련 컴플렉스)
- 파워리프팅·역도처럼 여러 기록을 한 화면에 모으는 기록 표시 묶음
클린 앤 저크와 1 Clean + 2 Jerk를 같은 종류의 객체로 취급하지 않는 것이 이 정책의 핵심이다. 전자는 관습과 경기 규칙상 하나의 기록 단위이고, 후자는 Clean과 Jerk를 특정 순서와 반복수로 수행하는 훈련 구조다.
용어와 정체성
Canonical 종목
Canonical 종목은 public.exercises.id 하나를 갖는 독립 기록 단위다.
id는 DB가 발급한 UUID 형식의 불투명한 내부 식별자다.slug, 한국어·영문 이름, 별칭, archetype, provider ID는 식별자가 아니다.- 이름·번역·slug·활성 상태가 바뀌어도
id는 바뀌지 않는다. origin은system,user,external을 구분한다.- 사용자 종목은 DB에서 먼저 생성하고, 소유자 범위의 UUID를 받은 뒤에만 기록에 사용할 수 있다.
- 참조된 종목은 삭제 대신 비활성화한다. 병합·분리는 원천 사실을 이관한 뒤 모든 파생 통계를 replay하는 유지보수 작업이다.
Canonical 종목의 수행 의미는 다음처럼 분류한다.
standalone: 백스쿼트, Clean, Jerk처럼 그 종목 자체로 수행과 기록을 판단한다.conventional_combined: Clean & Jerk처럼 여러 단계를 포함해도 관습적으로 하나의 수행·채점·PR 단위인 종목이다.
여기서 standalone은 생체역학적으로 단순하다는 뜻이 아니다. 오직 제품에서 하나의 기록 단위로 취급한다는 뜻이다. opaque나 unresolved는 수행 종류가 아니라 외부 해석의 상태다.
사용자 custom 종목은 DB-first owner-scoped UUID를 발급하고 기본적으로 standalone으로 생성한다. 사용자가 여러 동작을 묶고 싶다면 새 canonical 종목을 만드는 대신 훈련 컴플렉스 정의를 만든다. 이름 표기는 3층 트리다: 종목은 동등한 표기인 synonym을 여러 개 가질 수 있고(그중 하나가 default로 exercises.name_ko/name_en과 동기화된다), 각 synonym은 자기 밑에 교정 별칭(검색 전용)을 둔다. synonym은 종목의 id를 공유하며 자체 정체성을 갖지 않고, 기록은 session_exercises.synonym_id로 고른 표기만 참조한다(표시 전용). 별칭 충돌을 근거로 종목을 자동 병합하지 않는다.
정식 결합 종목
클린 앤 저크는 conventional_combined canonical 종목이다.
- Clean, Jerk, Clean & Jerk는 서로 다른 세 UUID다.
- Clean & Jerk 직접 기록은 Clean & Jerk의 볼륨·NRM·e1RM·PR만 갱신한다.
- 설명과 검색을 위해 Clean 단계와 Jerk 단계의 구성 관계를 보유할 수 있지만, 그 관계를 근거로 Clean·Jerk 통계를 자동 갱신하지 않는다.
- 같은 중량의 Clean 다음 Jerk를 했다는 사실만으로 Clean & Jerk 기록으로 승격하지 않는다. 사용자가 Clean & Jerk를 직접 선택했거나, 감사된 provider 매핑이 그 수행을 Clean & Jerk로 확정해야 한다.
연속 동작 세트(훈련 컴플렉스)
1 Clean + 2 Jerk, 1 High Clean + 1 Squat Clean + 1 Jerk는 canonical 종목이 아니다. 다음으로 구성되는 세션 내 수행 구조다.
- 순서가 있는 canonical 종목 구성원
- 한 sequence 단위에서 각 구성원이 수행하는 반복수
- sequence의 라운드 수
- 공통 또는 구성원별 중량
- 공통 또는 구성원별 성공 여부와 체감 난이도
- 휴식과 메모
훈련 컴플렉스 자체에는 exercise_id, 일반 NRM, e1RM, 측정 PR이 없다. 자주 쓰는 컴플렉스는 별도 정의 UUID를 가질 수 있지만 그 UUID는 exercise_id가 아니며 종목 상세·즐겨찾기·PR 카탈로그에 섞이지 않는다. 정의의 revision은 불변이며, 과거 세션과 예정 운동은 사용한 revision 또는 당시 구성 snapshot을 고정해 이후 편집으로 의미가 바뀌지 않게 한다.
슈퍼세트와 서킷도 세션 구조다. 같은 부하를 공유하고 순서가 기록 의미인 training_complex, 구성원별 부하가 독립적인 superset, 여러 라운드와 시간을 포함하는 circuit을 같은 종목으로 합치지 않는다.
기록 표시 묶음
스쿼트 + 데드리프트 + 벤치프레스 + 오버헤드 프레스, 스내치 + 클린 앤 저크와 같은 묶음은 읽기 전용 화면 구성이다.
- 자체 운동 정체성·세션 세트·PR·통계를 갖지 않는다.
- 구성원은 canonical UUID로만 참조한다.
- 묶음 점수나 합계는 화면용 파생값이며 원래 종목 기록을 대체하지 않는다.
원천 사실과 통계 귀속
| 입력 | 저장 의미 | 볼륨·빈도 | 측정 NRM·PR | e1RM |
|---|---|---|---|---|
| canonical 종목 직접 수행 | 선택한 UUID의 직접 사실 | 해당 종목 | 해당 종목 | 해당 종목 |
| Clean & Jerk 직접 수행 | 결합 종목 UUID의 직접 사실 | Clean & Jerk만 | Clean & Jerk만 | Clean & Jerk만 |
| 명시적으로 구조화된 훈련 컴플렉스 | 구성원·순서·반복·라운드가 있는 세션 구조 | 확인된 구성원에 귀속 | 구성원 완료 반복이 명시된 경우에만 해당 구간을 후보로 사용 | 구성원별 load/reps가 명시된 경우만 계산; 난이도가 없으면 e1RM 미입력 경로의 저신뢰 대표값만 허용 |
| 이름만 해석한 외부 컴플렉스 후보 | 검토 전 추정 | 없음 | 없음 | 없음 |
| opaque external | 원본 보존·검토 대기 | 유효한 단위·부하·반복은 세션 전체 합계에만 보존하고 canonical 종목에는 귀속하지 않음 | 없음 | 없음 |
| 기록 표시 묶음 | presentation | 없음 | 없음 | 없음 |
Canonical 종목의 e1RM은 정량 부하, 지원되는 측정 방식, track_pr, 정책상 지원 반복수 조건을 모두 만족할 때만 계산한다. Clean & Jerk의 1회는 Clean 단계와 Jerk 단계의 합이 아니라 완료된 parent lift cycle 1회다. 따라서 100kg × 1 직접 기록의 세션 볼륨은 Clean & Jerk 100kg이고 빈도도 parent에만 1회 귀속된다. 반면 구조화된 1 Clean + 1 Jerk @ 100kg 1라운드는 두 구성 동작의 완료와 부하가 각각 검증됐을 때 세션 component-work 볼륨 200kg으로 합산한다. 이때 block 수와 구성원 수행 fact 수는 서로 다른 지표다.
훈련 컴플렉스의 라운드 수를 한 종목의 연속 반복수로 합치지 않는다. 예를 들어 1 Clean + 2 Jerk를 3라운드 수행해도 Clean 3RM이나 Jerk 6RM으로 만들지 않는다. 완료가 확인된 한 sequence 안의 Clean 1회, Jerk 2회만 각각의 수행 구간으로 다룬다.
그룹 전체의 체감 난이도를 모든 구성원의 RIR로 복사하지 않는다. 구성원별 난이도가 없으면 e1RM 정책의 missing-RIR 경로를 사용한다. 지원 반복 범위 안에서는 대표값을 계산할 수 있지만 신뢰도는 낮고 유한한 상한 추정값은 만들지 않는다. 성공 여부가 불명확하거나 일부 실패한 컴플렉스는 완료된 구성원과 반복수를 구조적으로 알 수 있을 때만 통계에 반영한다.
외부 provider 해석
Provider 정체성은 다음 경계를 갖는다.
(provider, scope, owner, raw provider exercise id)
|
v
감사된 interpretation
/ \
canonical exercise training-complex definition외부 interpretation은 대상 종류와 검토 상태를 별도 필드로 보존한다.
target_kind:canonical_exercise또는training_complex; 후보가 없으면nullresolution_status:unresolved,proposed,reviewed,ignored,rejected
reviewed일 때만 exercise UUID와 complex definition revision 중 정확히 하나를 target으로 강제한다. proposed는 parser나 정규화 규칙이 만든 검토 후보일 뿐 원천 사실을 materialize하거나 파생 통계를 만들 수 없다. ignored와 rejected는 대상 FK를 갖지 않는다. unresolved는 원본을 보존하고 검토를 기다린다.
이 문서의 opaque external은 이런 미해석 provider 표시를 뜻하며 canonical 종목의 수행 kind가 아니다. 목표 모델에서는 검토 전 external movement 때문에 public.exercises 행을 새로 만들지 않는다.
이름 parser는 매핑 후보만 만든다. 이름만으로 canonical 종목을 병합하거나 원천 사실을 쓰기 해서는 안 된다. 숫자가 반복수인지, 이름의 일부인지, WodUp 세트의 reps가 라운드 수인지 총 동작 수인지 확인되지 않으면 unknown으로 남긴다.
WodUp 예시
- 공식
Clean and Jerkprovider ID- 감사된 매핑으로 canonical Clean & Jerk UUID 하나에 연결한다.
- Clean과 Jerk로 fan-out하지 않는다.
1 Clean 1 Jerk- Clean & Jerk 후보일 수 있지만 이름만으로 자동 확정하지 않는다.
- pause, hang, power, squat, touch-and-go 같은 modifier와 실제 채점 의미를 검토한다.
1 Clean 2 Jerk{Clean × 1, Jerk × 2}훈련 컴플렉스 해석 후보를 만든다.- provider의 구조화 데이터 또는 감사된 interpretation이 확인할 때만 materialize한다.
- 검토 전에는
unresolved이며 canonical Clean & Jerk 기록을 갱신하지 않는다.
2 Clean 1 Jerk,Clean + 2 Jerks- 동일하게 훈련 컴플렉스 후보만 만들고, 검토 전에는
unresolved로 둔다. - 확정할 때 순서와
reps_per_sequence를 보존한다.
- 동일하게 훈련 컴플렉스 후보만 만들고, 검토 전에는
- 해석 실패
unresolved인opaque_external로 표시하고 원본 및 provider lineage를 보존한다.
WodUp 세트에는 아래 메타데이터가 필요하다.
provider_repsrepetition_semantics:rounds,total_reps,member_reps,unknowninterpretation_versioninterpretation_source: structured provider data, curated mapping, parser proposalconfidence
반복 의미는 다음처럼 고정한다.
rounds:provider_reps가 완전한 sequence의 수행 횟수다.total_reps: 모든 구성 동작의 합계다. 분배 근거가 없으면 구성원별로 귀속하지 않는다.member_reps: 하나로 식별된 구성원의 해당 행 반복수다.unknown: 전개하거나 구성원 통계를 만들지 않는다.
repetition_semantics = rounds가 감사된 경우에만 다음처럼 전개한다.
component_completed_reps = provider_reps × component.reps_per_sequence
component_volume = reviewed_component_load × component_completed_reps부하 의미가 확인되지 않으면 완료 반복수만 보존하고 구성원 볼륨은 만들지 않는다.
목표 관계형 모델
물리 테이블 이름은 이식 과정에서 조정할 수 있지만, 아래 관계는 JSON 표시 메타데이터가 아닌 DB 제약으로 표현해야 한다.
exercises
id (canonical UUID)
origin / owner_user_id
exercise_kind
exercise_synonyms -- 2.0.0: 구현됨 (20260821060000~210000)
id / exercise_id / name_ko / name_en
is_default (종목당 정확히 하나) / aliases (교정 별칭, 검색 전용)
session_exercises.synonym_id -- 2.0.0: 구현됨, 표시 전용·nullable FK
exercise_composition_components
parent_exercise_id -- 예: Clean & Jerk
component_exercise_id -- 예: Clean, Jerk
position / reps
relation_kind = phase
attribution = parent_only
training_complex_definitions
id / immutable_revision -- exercise_id가 아님
origin / owner_user_id / source lineage
name / version / load_semantics
training_complex_components
definition_id
component_exercise_id
position / reps_per_sequence
external_exercise_interpretations
provider identity
resolution_status / proposed interpretation
target_kind -- reviewed일 때만 사용
exercise_id xor complex_definition_id
repetition_semantics / version / review metadata
session_blocks
session_id / position
kind = single | training_complex | superset | circuit | note
session_block_movements
block_id / component_exercise_id / position
session_block_rounds and performed component facts
round / load / reps / result / effort / rest / source lineage
planned_session_blocks / movements / rounds
performed block과 같은 의미 모델
stable row IDs / definition revision 또는 component snapshot
mutation receipts
client_mutation_id / aggregate version / idempotent result정식 결합 종목의 exercise_composition_components는 설명 관계다. 세션 원천 사실을 자동 분해하거나 통계 귀속을 바꾸는 규칙이 아니다.
Block 전체 저장은 하나의 transaction과 idempotency receipt로 처리하고 CAS version으로 동시 수정을 검출한다. UI block 개수 제한과 저장 시 펼쳐지는 component fact 개수 제한은 별도로 둔다. legacy composite_meta가 손상됐거나 구성원이 일부 누락된 경우 그룹은 quarantine하되 살아 있는 원자 기록을 반드시 표시한다. 손상을 이유로 기록을 조용히 숨기지 않는다.
현재 구현 감사 — 2026-07-27
감사 기준 리비전의 deterministic cutover 결과는 서로 다른 세 정체성을 만든다.
| 의미 | canonical UUID |
|---|---|
| Clean | 6f810cf0-78d1-53ec-82ad-222b69f84d4b |
| Jerk | c54e666e-b7b5-5d2f-8b38-a545566e19f8 |
| Clean & Jerk | dc142fec-787d-5bb7-8462-0f0c4b1c1b14 |
| 영역 | 상태 | 현재 동작과 근거 |
|---|---|---|
| 단일 내부 ID | 준수 | exercise-identity-hard-cutover.md와 UUID/FK 제약이 exercises.id 하나를 강제한다. |
| system/user/external 소유권 | 준수 | 사용자 종목은 DB-first UUID와 owner scope를 사용하고 provider placeholder는 별도 origin을 사용한다. |
| Clean / Jerk / Clean & Jerk 분리 | 준수 | 세 종목은 독립 UUID이며 Clean & Jerk strength standard와 PR read model도 별도다. |
| custom 종목 | 부분 준수 | owner-scoped UUID는 강제하지만 수행 kind와 “다중 동작은 complex definition” 규칙은 DB에 없다. |
| 별칭·검색 | 준수 (2.0.0) | exercise_synonyms 트리가 정본이다: 카탈로그 v4 payload·검색 3표면·SQL 매칭 경로(임포트 스코어링·컴플렉스 파서·관리자 매핑 검색)가 트리를 읽고, 별칭 편집·기록 참조(session_exercises.synonym_id)·커스텀 가드가 트리에 쓴다. 구 컬럼 exercises.aliases는 20260821250000에서 드롭. catalog 응답의 별칭 수 제한(2×48)은 유지된다. |
| 종목의 수행 종류 | 미구현 | exercises에 exercise_kind가 없고 DB는 Clean & Jerk의 단계 관계를 모른다. |
| 데스크톱 연속 동작 입력 | 부분 준수 | compositeExerciseModel.ts와 mapper가 구성원 UUID별 행으로 fan-out하고 composite_meta로 복원한다. 자체 가짜 exercise ID는 만들지 않는다. |
| 연속 동작 관계 무결성 | 미준수 | DB는 composite_meta가 JSON object이고 크기 제한 안이라는 것만 검사한다. 구성원 완전성·순서·라운드·개별 부하/결과를 관계형으로 강제하지 않는다. 손상 그룹이 복원 기준을 못 채우면 원자 행까지 화면에서 사라질 수 있다. |
| 연속 동작 통계 | 부분 준수 | 현재는 같은 중량·난이도·성공 상태를 모든 구성원 행에 복사하고 각 구성원의 PR/e1RM에도 바로 참여시킨다. 구성원별 evidence와 그룹 난이도를 구분하지 않는다. |
| 모바일 연속 동작 입력 | 미구현 | 데스크톱과 동등한 구조화 입력·수정 경로가 없다. |
| 기록 표시 묶음 | 준수 | prBenchmarkSets.ts와 business-role membership은 presentation 구성만 제공하고 가짜 종목 통계를 만들지 않는다. |
| WodUp complex 인식 | 미준수 | normalizer는 이름과 provider ID 하나를 session exercise 하나로 만들며 isComplex와 이름 속 scheme을 의미 필드로 사용하지 않는다. |
| WodUp 외부 매핑 target | 미준수 | exercise_external_mappings는 exercise UUID 하나만 대상으로 하므로 training-complex 정의를 가리킬 수 없다. |
| WodUp 다중 movement | 위험 | set에 movementId가 없으면 같은 set이 모든 movement로 복제될 수 있다. 모호한 경우 fail-closed가 필요하다. |
| WodUp provider key | 위험 | repo의 normalizer는 scoped key 예시(wodup:global:41)를 만들지만 seed 형식에는 raw ID 예시(41)가 있다. 환경별 실제 매핑을 감사하기 전까지 canonical 매핑을 놓치고 placeholder를 만들 가능성을 배제할 수 없다. |
| WodUp 권위 원본 보존 | 미준수 | wodup_import_batches와 wodup-imports storage object를 소유자가 update/delete할 수 있다. source fact가 존재하는 동안 content-addressed 원본을 불변 보존하고 replay 전 digest를 재검증하는 제약이 없다. |
| unresolved external 통계 | 미준수 | 현재 placeholder도 일반 종목처럼 track_pr가 켜지고 통계 파이프라인에 참여할 수 있다. |
| 기존 매핑 변경 | 위험 | mapping row 변경만으로 이미 저장된 원천 사실과 파생 통계가 이동하지 않는다. placeholder→canonical처럼 fact shape가 같은 변경은 원자적으로 참조를 다시 쓰고 전량 replay해야 한다. opaque→complex처럼 fact shape가 바뀌는 변경은 WodUp 업로드 원본에서 재materialize한 뒤 전량 replay해야 한다. |
현재 화면의 복합 종목 세트라는 문구는 정식 결합 종목과 혼동된다. 사용자 문구는 여러 동작을 한 세트로 묶기 또는 연속 동작 세트로 바꾸고, 내부 코드의 composite 명칭은 구현 세부로만 유지하는 편이 안전하다.
현재 WodUp에서 1 Clean 2 Jerk가 잘못 처리되는 지점
현재 흐름은 다음과 같다.
WodUp movement "1 Clean 2 Jerk"
-> provider scoped key 한 개
-> canonical UUID 매핑 또는 external placeholder UUID 한 개
-> session_exercises 한 행
-> 일반 종목의 reps/load 세트
-> 그 placeholder 자체의 NRM/PR/e1RM 후보재처리의 권위 원본은 batch가 참조하는 WodUp 업로드 파일이다. staging과 session raw payload에도 복구 정보가 있지만, session raw만으로 movement 정의까지 완전하다고 가정하지 않는다. canonical 세션 행에는 Clean/Jerk 구성과 반복 의미가 없으므로 올바른 모델을 넣은 뒤 업로드 원본에서 다시 materialize해야 한다.
권위 원본은 ingest 시 SHA-256 content address로 봉인하고, 참조하는 source fact가 남아 있는 동안 소유자도 수정·삭제할 수 없게 보존한다. ingest와 replay 때 digest를 다시 검증한다. 현재 DB의 batch 및 wodup-imports storage RLS는 소유자 update/delete를 허용하므로 이 보존 계약을 아직 충족하지 않는다.
현재 normalizer 회귀 테스트도 2 Clean 1 Jerk를 하나의 provider exercise와 일반 60kg × 3, 65kg × 2로 기대한다. 이는 identity 안정성 테스트로는 맞지만 운동 의미 계약으로는 교체되어야 한다.
이식 순서
- writer·WodUp worker·통계 worker를 fence하고 provider identity 중복을 감사한다.
exercise_kind, 정식 결합 종목의 설명 관계, training-complex 정의·구성원, provider interpretation 구조를 추가한다.- normalizer가 structured component와 원본 반복 의미를 보존하도록 바꾼다.
- 공식 Clean & Jerk provider ID는 canonical Clean & Jerk로 매핑한다.
- 감사된
1 Clean 2 Jerk계열은 training-complex로 분류하고, 모호한 행은unresolved로 둔다. - 외부 placeholder의 PR/e1RM 참여를 중단한다.
- native 연속 동작 세트를 관계형 block 모델로 이식하고
composite_meta를 표시 호환 용도로만 남긴다. - fact shape가 바뀌는 WodUp 기록은 batch가 참조하는 업로드 원본에서, native 기록은 관계형 원천 사실에서 세션을 재materialize한다. identity만 바뀌는 행은 하나의 transaction에서 모든 원천 참조를 다시 쓴다.
- NRM/PR/e1RM/기간 통계/Home/Volume/달력을 전량 replay한다.
- source count, provider lineage, 화면 round-trip, projection generation을 검증한 뒤 writer를 연다.
금지 규칙
- 이름·slug·archetype·provider ID를
exercise_id로 저장하지 않는다. - 이름이 비슷하다는 이유로 종목이나 컴플렉스를 자동 병합하지 않는다.
1 Clean + 2 Jerk를 Clean & Jerk로 저장하지 않는다.- Clean & Jerk를 설명용 구성 관계 때문에 Clean/Jerk 통계로 자동 분해하지 않는다.
- 컴플렉스 라운드 수를 구성 종목의 연속 NRM 반복수로 합산하지 않는다.
- 그룹 난이도를 모든 구성원의 RIR로 복사하지 않는다.
- unresolved external 종목으로 PR/e1RM을 만들지 않는다.
composite_meta만으로 원천 관계 무결성을 보장하지 않는다.- 기존 provider mapping을 바꾸고 파생 통계를 부분 수정하지 않는다. identity 변경은 원자 참조 rewrite와 전량 replay, fact shape 변경은 권위 원본 재materialize와 전량 replay를 수행한다.
버전 정책
- identity/equivalence, canonical 수행 종류, 정식 결합 종목 귀속, 컴플렉스 구성·귀속, provider interpretation 의미를 바꾸면 major 버전을 올린다.
- 기존 원천 사실의 의미나 통계 결과가 바뀌는 major 변경은 전량 replay가 필요하다.
- contract fingerprint를 바꾸지 않고 구현만
partial → enforced로 전환하면activationpatch로 기록한다. 이 변경은 migration 경로, 전량 replay, 모든 시행 전 conformance test와enforced_since를 반드시 포함한다. - 호환 가능한 metadata·taxonomy 추가는 기존 breaking subtree 안에 끼워 넣지 않고,
breaking_paths밖의 새 비행동 contract subtree로 분리한 경우에만 minor다. - 의미를 바꾸지 않는 설명·링크·오탈자 수정은 patch다.
- 각 버전은
versions/<full-semver>/에 사람용 문서와 manifest를 불변 보존한다. - contract뿐 아니라 breaking path와 replay 규칙 자체도 semantic fingerprint에 포함한다.
accepted는 제품 결정을 확정했다는 뜻이다.enforcement_status = partial인 동안은 감사 표의 미준수 항목이 남아 있다는 뜻이며enforced_since는null이다.
Conformance gate
- 현재 정본과 최신 불변 스냅샷은 canonical UTF-8/LF 기준으로 같아야 한다.
- changelog의 문서·manifest SHA-256과 semantic fingerprint가 실제 파일과 같아야 한다.
- CI는 base revision에 이미 존재하는
versions/<full-semver>/의 수정·삭제·파일 추가를 거부하고 새 버전 디렉터리만 허용한다. - changelog의 모든 버전을 검증하며, breaking fingerprint 변경은 major와 전량 replay, 시행 전환은 동일 fingerprint의 activation patch와 replay migration, semantic-only 변경은 minor, 문서만 바뀐 변경은 replay 없는 patch를 강제한다.
- 모든 영속
exercise_id는 UUID FK여야 한다. - Clean, Jerk, Clean & Jerk는 서로 다른 canonical UUID여야 한다.
- native 연속 동작 세트는 가짜 exercise ID를 만들지 않아야 한다.
- provider interpretation은 exercise와 training complex를 구분해야 한다.
- 모호한 외부 기록은 통계를 만들지 않고 원본을 보존해야 한다.
- 손상된 legacy complex는 quarantine하더라도 살아 있는 원자 기록을 숨기지 않아야 한다.
- 종목 merge/split과 interpretation 변경 후에는 원천 수량 대조와 전량 replay가 자동 검증되어야 한다.
현재 policy contract test는 정본·스냅샷·버전 규칙과 선언을 검증한다. 기존 mapper와 WodUp normalizer 테스트는 현행 동작을 재현하는 감사 근거이며 목표 정책 준수를 뜻하지 않는다. 관계형 complex round-trip, ambiguous fail-closed, reviewed attribution, replay 테스트가 모두 구현된 뒤에만 enforcement_status를 enforced로 올린다.