상태·자원·동시 접근을 누가 책임지는가
현재 폴더 구조로 분리할 수 있다. 하지만 상태 enum과 ManagerService가 있다는 것만으로 동시 접근과 잘못된 전이가 차단되는 것은 아니다. 이 페이지는 이미 있는 기능과 추가로 연결해야 하는 부분을 구별한다. 전체 프레임워크 지도는 아키텍처 Atlas를 유지한다.
AMR 후속 상세: 요청과 실제 관측을 분리하는 서비스 상태 계약. AMR 내부 상태 소유자와 제품 콜백 연결을 구별한다. 다른 도메인의 전이 구현 완료를 뜻하지 않는다.
Cleaning 후속 상세: 팬 관측과 정지 근거의 소유권. 초기 RPM 0·정지 알림을 실제 수신 증거로 취급하지 않는 내부 계약이며, 이동 청소 전체 완료와 구분한다.
AMR 취소 수명 상세: 실행 종료 뒤 중단 확인의 소유자. 실행권·관측권·Task 결과·자원 복구를 별도 수명으로 설명한다.
1. 상태는 한 종류가 아니다
| 종류 | 단일 변경 주체 | 의미 | 혼동하면 생기는 문제 |
|---|---|---|---|
| 서비스 수명 | ServiceLifecycleSupervisor |
프로세스 내부 서비스가 STARTING/READY/FAILED/RECOVERING인지 | READY를 모터 IDLE로 잘못 판단 |
| 작업 상태 | TaskManagerService |
QUEUED/RUNNING/CANCELLING 및 terminal | 취소 요청만 받고 실제 모터가 멈췄다고 판단 |
| 도메인 동작 상태 | 각 도메인의 ManagerService 내부 상태 소유자 — AMR 내부 계약 구현, 다른 도메인 후속 | 이동·도킹·청소·관측 등 제품 동작 | 다른 도메인이 내부 상태를 직접 바꿔 불가능한 조합 발생 |
| 공유 자원 | ResourceManagerService |
UNVERIFIED/FREE/HELD/RECOVERY_REQUIRED | 작업이 끝났다는 이유로 아직 움직이는 구동부 재할당 |
| 관측값 사본 | StateManagerService |
출처·관측 시각·유효 기간이 있는 최신값 | 캐시를 실행 권한이나 기기의 실제 최신 상태로 간주 |
| 도메인 간 요구 조건 | PolicyManagerService + 각 도메인의 정책 정의 |
요구 조건 선택·충돌·억제 결과 | 각 앱이 자기 조건만 적용해 틸팅·이동 등 충돌 |
StateManagerService는 상태 조회를 모으는 곳이지 모든 도메인의 동작을 결정하는 만능 상태 머신이 아니다. 현재 publish는 타입과 관측 시각 순서만 검사한다. 문자열 source를 실제 발행 권한으로 인증하거나 도메인별 작성자를 제한하는 기능은 아직 없다.
2. 폴더와 변경 권한
다음은 skix-framework/base/services/core/java/com/sk/airbot/frameworkservice/server/ 아래의 책임 배치다.
control/
lifecycle/ 서비스 수명·의존 관계·세대
resource/ 공통 하드웨어/세션 자원의 배타적 점유
policy/ 도메인 요구 조건 간 결정과 충돌 이유
state/ 관측값 조회용 사본 — 동작 전이 권한이 아님
execution/
task/ 작업 수명·실행 claim·단일 실행 permit
domain/
amr/ 주행·도킹 상태 정의와 이동 정책
state/ AmrStateAuthority: 요청·관측 분리, 버전·세대·순번 검사
execution/ MovementTaskSession → ManagedMovementExecutor
observation/ AmrObservationIngress: 원본 귀속·순번·나이 검증, 수락 결과 일회 인수
completion/ MovementCompletionSession: 명령 전 등록·시도 결합·Task 완료 전달
AmrAbortObservationService: 취소 뒤 관측 수명·만료·종료 정리
cleaning/ 청소 상태 정의·청소 실행
state/ CleaningFanStateAuthority: 팬 관측·정지 요청 경계·최신성
execution/ CleaningTransitionPort: 정지 요청/확인 계약
vision/ 카메라·관측·처리 상태 정의
airquality/ 공기질 관측·매핑 상태 정의
ui/ 표시·외부 앱 세션 상태 정의
llm/ 음성/LLM 파이프라인 상태와 완료 처리
향후 도메인의 state/, policy/, execution/, port/를 세분화하더라도 공통 코어에 모든 제품 상태를 합치지 않는다. 그 폴더에 들어갈 실제 동작이 생길 때 분리하며, 빈 디렉터리를 만드는 것을 구현 진척으로 세지 않는다.
3. 도메인별 상태 정의와 현재 부족한 부분
| 도메인 | 실제 정의 파일/대표 값 | 공유 자원 연결 기준 | 현재 구분 |
|---|---|---|---|
| AMR | 기존 domain/amr/MobilityState.java 유지 + domain/amr/state/AmrStateSnapshot.java의 요청 단계·관측 상태 분리 |
MOVEMENT_BASE | 상태·자원·Task 권한 검사와 선택형 executeWithCompletion, 취소 후 executeWithAbortObservation의 서비스 수명 연결 구현. 제품 콜백은 미연결 |
| Cleaning | domain/cleaning/CleaningState.java: 전체 상태 정의. state/CleaningFanStateAuthority.java: 팬 요청·관측 변경 소유 |
이동 청소가 구동부를 쓸 때 MOVEMENT_BASE, 정지 협력 계약 필요 | 팬 상태·정지 근거의 내부 구현과 정지 포트가 있음. MCU 연결·전체 청소 상태 머신은 미완 |
| Vision | domain/vision/VisionState.java: ACQUIRING_RESOURCE/OBSERVING/PROCESSING 등 |
CAMERA, 필요 시 외부 앱 세션 | 내부 CameraSessionService의 허가·반환·종료·복구 수명 구현. 실제 자원 예약·영상 처리 상태 전이·모든 진입 경로의 강제는 미완 |
| AirQuality | domain/airquality/AirQualityState.java: MONITORING/MAPPING 등 |
단순 관측과 이동 매핑을 분리; 매핑 중 주행은 AMR 협력 | 관측 어댑터가 있음. 이동 매핑 전체 전이는 미완 |
| UI | domain/ui/UiSessionState.java |
FOREGROUND_DISPLAY/EXTERNAL_APP_SESSION | 세션 상태 정의와 모든 표시 경로 통제는 별개 |
| LLM | domain/llm/LlmState.java, LlmPipelineState.java |
TTS_PLAYBACK 등 실제 점유에 한해 예약 | TTS 관련 보류 변경은 이번 이동 작업에 포함하지 않음 |
위 자원 연결 기준 중 제품 경로에 아직 연결되지 않은 것은 설계 기준이다. ResourceId에 없는 자원을 이 표만 보고 등록되었다고 해석하면 안 된다. 현재 실제 enum에는 MOVEMENT_BASE, CAMERA, FOREGROUND_DISPLAY, EXTERNAL_APP_SESSION, SECURITY_SESSION, TTS_PLAYBACK이 있다.
4. 충돌을 막는 순서
- 요청을 typed Task 입력으로 확정한다. 우선순위와 queue는 admission 책임이다.
- 도메인 정책과 관측 상태를 평가한다. 충돌 시 거부·대기·명시적 조정 중 하나로 처리한다. 정책 결과만으로 기기를 명령하지 않는다.
- 필요한 자원을 하나의 집합으로 원자적으로 예약한다. 일부 자원만 잡은 채 다른 자원을 기다리는 교착을 피한다. 현재 ResourceManagerService의 acquire는 집합 전체의 배타적 예약을 지원한다.
- Task 실행 권한과 자원 토큰의 현재 유효성을 확인한다. 같은 Task claim의 중복 실행을 허용하지 않는다.
- 도메인 실행기가 기기 명령을 전달하고 해당 시도의 응답·상태 전이를 확인한다.
- 취소·재시작·점유 상실은 다음 명령을 막는다. 실제 정지 여부가 불명확하면 자원을 복구 필요 상태로 둔다.
- 작업 완료 증거와 자원 반환은 따로 처리한다. 반환 후에도 새로운 기기 상태 확인 전에는 FREE로 바꾸지 않는다.
현재 PolicyManagerService.resolve는 주어진 요구 조건을 비교하는 기능이다. 위 전체 순서를 자동으로 수행하거나 물리 명령까지 지속적으로 정책을 강제하는 기능은 아니다.
청소 중 주행 요청의 예
청소가 같은 구동부를 실제 점유하고 있다면 주행 요청이 그 소유권을 덮어쓰면 안 된다. 먼저 상위 조정자가 청소 소유자에게 정지/반환을 요청하고, 실제 정지 및 자원 재확인을 거쳐 주행에 새 토큰을 발급해야 한다. 새 토큰 없이 AMR이 청소 자원을 강제 해제하는 경로는 만들지 않는다.
이번 MovementTaskSession은 이미 AMR에 적법하게 예약된 토큰을 검증하는 내부 경계다. 청소 소유자에서 AMR 소유자로 넘기는 선점·양도 절차를 구현한 것은 아니다. 기존 이동 실행기의 청소 정지 확인도 자원 소유권 양도를 자동으로 뜻하지 않는다.
5. 자원 상태 전이
| 현재 → 다음 | 허용 근거 | 허용하지 않는 추정 |
|---|---|---|
| UNVERIFIED → FREE | 최신 reconciliation token에 연결된 가용 상태 확인 | 서비스가 READY니까 비어 있을 것 |
| FREE → HELD | 현재 세대 owner의 전체 자원 acquire 성공 | 한 번 읽은 isFree 값만 믿고 명령 |
| HELD → UNVERIFIED | 정확한 점유 token으로 release | release했다는 이유만으로 물리 정지 보장 |
| HELD → RECOVERY_REQUIRED | owner 실패·세대 변경·불확실한 실행 | 예외를 잡았으니 자원 자동 FREE |
| RECOVERY_REQUIRED → FREE | 새로운 상태 재확인 | 오래된 콜백·이전 세대 token 재사용 |
isHeld(token)은 이미 점유한 권한만 검사해야 한다. 이를 acquire 재호출로 대체하면 반환된 자원을 과거 실행이 다시 잡는 문제가 생길 수 있다.
같은 owner/request/resource로 반환 후 다시 예약해도 예약별 고유 식별자는 달라야 한다. 요청 ID와 서비스 세대만 비교하면 이전 토큰이 새 점유를 자기 것으로 오인할 수 있다. 이를 ABA 문제라고 하며, 오래된 토큰의 점유 검사뿐 아니라 release와 markOwnerFailed도 새 예약에 영향을 주지 않아야 한다.
6. 동시 접근에서 보장하는 범위와 남은 원자성 문제
각 서비스의 synchronized는 그 서비스 내부 데이터를 보호한다. Task 검사 → 자원 검사 → 기기 명령 전체를 하나의 트랜잭션으로 만들지는 않는다.
- 단일 claim 소비와 전체 자원 점유는 각각 소유 서비스에서 원자적으로 처리한다.
- 외부 포트 호출·대기 동안 Task/Resource의 전역 잠금을 잡지 않는다. 그래야 취소·장애 처리가 막히지 않는다.
- 실행 시도 ID, owner 세대, 토큰 epoch로 늦게 도착한 이전 실행과 현재 실행을 구별한다.
- 제품 adapter는 명령 전달을 직렬화하고, 최종 권한 확인과 명령 수락의 경계를 정의해야 한다. 현재 내부 체크포인트만으로 이 물리적 원자성을 주장하지 않는다.
- 도메인 상태 전이도 예상 이전 상태/버전과 실제 입력 이벤트를 검증해야 한다. 여러 writer가 같은 enum을 직접 대입하도록 만들지 않는다.
- 명령 요청 상태와 실제 관측 상태를 분리한다.
STOP_REQUESTED성격의 요청을IDLE관측으로 바꾸지 않는다.
7. 다음 이식의 완료 기준
전체 완성도를 높이는 연결 순서
아래는 클래스 개수가 아니라 실제 실행 경로를 기준으로 한 후속 순서다.
| 순서 | 묶어서 닫을 연결 | 완료로 판정할 근거 |
|---|---|---|
| 1 | AMR 취소 이후 관측 권한의 서비스 인계 — 내부 연결 검증 완료 | executeWithAbortObservation에서 원본 ABORTED와 단일 소유 정리 연결. 제품 공급자 활성화·실기기는 후속 |
| 2 | Cleaning 정지 협력과 청소 소유권 양도 — 내부 연결 검증 완료 | 팬 정지와 구동부 정지를 구분하고, 원래 소유자의 반환·재확인 없이 AMR에 새 점유를 주지 않음. 제품 공급자 연결은 후속 |
| 3 | Vision·UI의 관리형 실행 진입 | 카메라/화면/외부 앱 자원 집합 예약, 상태 전이, 취소·종료 정리, 직접 호출 우회 경계 검사 |
| 4 | 제품 공급자와 공개 Manager 연결 | 실제 원본 이벤트 귀속·권한·Binder 왕복·프로세스 재시작을 제품 경로로 검증 |
실행 권한과 관측 권한은 다르다. 취소된 Task는 새 명령을 보낼 수 없어도 자신의 중단 응답을 받을 필요가 있다. 그 관측 권한은 원래 시도·서비스 세대·시간 제한에 묶어 보존하며, 새 작업의 실행 permit으로 바꾸지 않는다. 이 표는 내부 연결 완료와 후속 작업을 구별하며 네 연결 전체가 구현되었다는 표시가 아니다.
Vision 후속 감사(2026-09-11): SkixCameraManager는 이미 카메라 계약 상수를 쓰지만,
DeviceAgent CameraCommandHandler는 static CameraAccessPolicy에 접근 상태를 저장한다.
후속으로 VisionManagerService가 독립적인 domain/vision/session/CameraSessionService를
소유하도록 구현했다. STREAM 공유·EXCLUSIVE 정책을 보존하며 세션 소유자·서비스 인스턴스·
READY 순번을 검사한다. 기존 static 경로의 제품 요청이 이 서비스로 전환된 것은 아니다.
공통 CAMERA enum이나 논리 세션 0을 실제 자원 예약·물리 해제 근거로 세지 않는다.
내부 세션 수명과 남은 제품 연결에서
검증 범위를 구분한다. 원본 감사 근거는 docs/refactor/skix-vision-camera-ownership-audit-2026-09-11.md다.
Cleaning → AMR 인계 구현 전 감사에서 확인한 연결 공백
이 절은 이식 공백을 확인한 원인 기록이다. 이를 연결하는 선택형 경로와 폴더·상태 흐름은 청소 소유 구동 자원의 안전한 인계에 분리했다. 기존 prepare 경로와 제품 조립 전체가 자동 전환되는 것은 아니다.
2026-09-11 소스 대조 기준으로 MovementTaskDispatcher.prepare()는 구동 자원을 먼저 확보하고 세션을 만든다.
청소 정지 요청은 그 세션의 ManagedMovementExecutor.quiesceCleaning() 안에서 수행한다.
따라서 이동 명령보다 청소 정지 확인이 먼저라는 순서는 있지만,
감사 당시 기존 prepare()에는 이미 구동 자원을 점유한 Cleaning owner에게 정지를 요청한 뒤 AMR이 이어받는 경로가 없었다.
Cleaning이 구동 자원을 점유하는 제품 조립에서는 AMR 준비 단계가 거부돼 세션 안의 정지 요청에 도달하지 못한다.
이는 코드 순서에서 확인한 이식 공백이며, 실기기 교착을 재현했다는 뜻은 아니다.
MR6 task_manager의 MovementTaskExecutor.prepareMovementResource()는 청소 active 판단 → STOP → cleaning.stopped 대기를 수행한다.
BaseCleaningManager의 기본/이동 청소 onStop()은 이 완료를 기록하지만,
CleaningAmpTaskExecutor.markStopCompletedIfAlreadyStopped()에는 기존 processing=false만으로 즉시 완료를 기록하는 경로도 있다.
이 의미를 팬의 실제 정지나 AMR 물리 정지 증거로 그대로 승격하지 않는다.
위 감사에서 찾은 기존 청소 owner의 정지 협력 → owner의 반환 → 새 상태 재확인 → 최신 Task/정책 검사 → AMR 획득 순서는 이제 선택형 내부 인계로 구현·검증했다. 실제 청소 점유 등록과 원본 공급자 연결은 여전히 후속이다.
다른 owner의 토큰을 해제하거나 강제로 FREE로 만드는 API로 우회하지 않는다.
소스 근거와 통합 합격 기준은 MR7 저장소의 docs/refactor/skix-cleaning-movement-handoff-audit-2026-09-11.md에 함께 기록했다.
도메인별로 다음 다섯 칸을 소스와 테스트로 채워야 한다. 공통 enum 하나 추가했다고 완료로 표시하지 않는다.
- 상태 정의: 제품 사양과 원본 이벤트의 대응.
- 전이 검사: 허용·거부 전이 표, 거부 이유, 복구 경로.
- 단일 변경 주체: 도메인 내부의 직렬 상태 소유자 및 관측 발행 권한.
- 자원 예약: 필요한 자원 집합, 충돌 시 대기/거부/선점 정책, 양도 시 정지 확인.
- 제품 연결: 모든 직접 명령 우회 경로 제거, callback 시도/세대 검증, 재시작·동시 요청 테스트.
MR6 task_manager의 TaskResourcePolicy는 CPU/RAM/thermal 입장 제한도 다룬다. 이것은 MOVEMENT_BASE 등의 배타적 하드웨어 점유와 다른 정책이다. 두 개를 같은 “자원 관리 완료”로 묶지 않는다.
도메인 전이 계약 — 구현 완료가 아닌 후속 적용 기준
각 도메인은 단순 setState(...) 대신 현재 상태 + 입력 이벤트 + 실행 권한 + 전이 조건을 함께 검사하는 진입점을 가져야 한다. 아래 이름은 설명용 계약이며, 공통 공개 API에 모두 추가되었다는 뜻이 아니다.
| 항목 | 필요한 이유 | 거부 또는 보류 사례 |
|---|---|---|
expectedVersion |
상태를 읽은 뒤 다른 요청이 먼저 바꾼 경우 구별 | 오래된 IDLE을 보고 시작한 이동 요청 |
eventId와 실행 시도 ID |
같은 이벤트의 재전송 및 이전 시도 콜백 구별 | 완료 콜백 중복 수신, 취소된 이전 이동의 도착 |
서비스 generation |
재시작 이전 작업과 현재 작업 구별 | 이전 서비스 인스턴스의 상태 보고 |
| 현재 Task permit·자원 token | 상태 전이가 현재 실행 주체의 권한에 속하는지 확인 | 취소된 작업의 추가 이동, 다른 소유자의 카메라 사용 |
| 도메인 전이표와 정책 조건 | 문법적으로 유효한 enum이라도 사양상 불가능한 전이 차단 | 정지 확인 없이 STOPPING에서 MOVING으로 전환 |
전이 판단과 도메인 내부 상태 변경은 해당 소유자 안에서 직렬 처리한다. 다른 도메인에 요청하거나 기기 응답을 기다릴 때는 공통 전역 잠금을 유지하지 않는다. 요청 수락 결과에는 거부·대기 이유를 남기고, 물리 상태는 해당 실행의 관측 이벤트가 확인된 뒤 별도로 갱신한다.
예를 들어 AMR의 STOPPING → IDLE은 정지 요청 전송이 아니라 실제 정지 관측으로 확정해야 한다. 관측이 없거나 제한 시간을 넘겼다면 정상 IDLE로 추정하지 않고 오류·복구 경로와 자원 RECOVERY_REQUIRED 처리를 연결한다. Cleaning·Vision·UI에도 같은 책임 원칙을 적용하되 상태 값과 전이 조건 자체는 각 도메인 사양으로 정의한다.
검증 범위
초기 실행 권한 연결 구현은 3a7aa902다. 당시 보류 TTS 수정이 섞이지 않도록 커밋된 서버 소스로 분리해 서버 385개·SDK 46개 테스트를 실행했고 실패·오류·건너뜀은 0이었다. 서버 release Java 컴파일과 구조 경계 검사 4종도 통과했다. 이후 상태 연결·원본 관측·선택형 Task 완료 전달 검증의 최신 결과는 진행 현황에서 관리한다. 과거 수치를 현재 전체 테스트 수나 구현률로 사용하지 않는다.
도메인 전체 전이 엔진·제품 어댑터·실기기 동작이 완성되었다는 의미는 아니다.