소스가 Describe 때 한 번 선언하는 설계도의 §A2 제안형 — 실제 자료구조(JSON) 그대로입니다. channels는 객체의 배열이고, 배열 순서가 곧 values[] 인덱스(우측 // [i]). 필드명은 흰색, 값 색이 출처를 뜻합니다: 청록=소스 메타에서 옴(예: key=실제 컬럼명) / 주황=우리 추론(예: unit). null인 필드(rot_type/group/range)는 객체에서 생략됩니다. (색은 표시용일 뿐 데이터에 *_src 같은 필드는 없습니다.)
robot0.action.state.joint_positions[3]). SDK가 중간을 깎지 않습니다(.state. 제거 금지) — 원본 복구가 보장돼야 하니까요. observation/action 구분은 이름에 이미 있으니 거기엔 role을 안 씁니다. 대신 role은 로봇 경계를 실제로 넘었는지로 2가지로 나눕니다 — robot(로봇에서 나온 측정 상태 또는 로봇에 실제 전달된 명령) / aux(그 외 곁다리: 실행 안 된 후보 액션·action_source·inference latency·prev_·success 등). 판별: "이 값이 로봇으로 실제 들어갔거나 로봇에서 나왔나?" 이름만으론 모르니 가치 있는 부가 필드입니다. 그 외 이름으로 복구 불가라 유용한 건 unit·rot_type. group은 선택적 묶음 태그. role·rot_type은 닫힌 집합(proto enum)이라 정해진 값 외엔 거부됩니다(role: robot/aux, rot_type: none/quaternion/euler/rotation_6d). unit은 권장 집합 + 그 외는 경고(단위는 무한하므로). 중복·불필요 채널(예: action.state.*가 observation.state.*와 동일, 한 명령의 여러 표현)은 권고로 줄이되 강제로 제거하지 않습니다 — 클라이언트가 로깅 목적으로 보낼 수 있으니까요.euler.실제 에피소드(프랑카·베가=recorder, G1=LeRobot/GR00T)에서 추출. unit/rot_type은 best-effort 추론이며 실제 계약은 클라이언트가 선언합니다. docs/spec_discrepancies.md §A2.