이동 작업 실행 — 청소 정지부터 도착·구동부 정지까지
개요: 이동 명령을 보냈다고 이동 작업이 끝나지는 않는다
프레임워크는 명령 전송과 실제 완료를 구분해야 한다. 청소 중이라면 청소를 멈춘 사실을 먼저 확인하고, 이동 명령을 보낸 다음 목적지 도착과 구동부 정지를 순서대로 확인한다. 이 페이지는 전체 구조 Atlas의 AMR·Cleaning·Task 협력 부분을 확대한 것이다. 전체 모듈 지도나 Android 부팅 구조를 대체하지 않는다.
취소 이후의 별도 수명은 중단 관측의 서비스 소유권 상세도에서 설명한다. 실행 세션이 끝나도 원래 중단 응답을 기다리는 이유와 Task·자원 상태의 차이를 함께 볼 수 있다.
세션을 만들기 전, Cleaning이 구동 자원을 점유한 경우의 협력은 Cleaning → AMR 자원 인계 상세도에서 설명한다. 소유자의 정지·반환·재확인과 새 AMR 점유를 구분하며, 기존 prepare와 선택형 인계 진입점을 함께 유지한다.
범위: 내부 실행기와 Task·자원·AMR 작업 상태 권한 연결 / 제품 경로 미연결. 이 실행기가 존재한다고 현재 DeviceAgent 요청이 새 Task 경로를 통과하는 것은 아니다.
MovementTaskDispatcher가 발급된 실행권을 검사하고 구동 자원을 확보한 뒤 MovementTaskSession을 만든다. 세션은 현재 Task 권한·자원 점유·서비스 세대를 검사한다. 새 executeWithCompletion은 내부 Task 결과 반영까지 연결한다. 스케줄러의 실행 배정과 기기 관측 어댑터는 제품 연결 지점으로 남는다.
이 문서의 녹색 실선은 내부 코드의 제어 흐름이며, 제품 프로세스 간 연결 완료 표시가 아니다.
1. 책임과 폴더를 함께 보는 구조
skix-framework/base/
├── core/java/.../manager/SkixAmrManager.java 앱의 요청 API
└── services/core/java/.../server/
├── execution/task/TaskManagerService.java 작업 수명·취소 요청·실행 권한
├── control/resource/ResourceManagerService.java 자원 집합 점유·반환·재확인
└── domain/
├── amr/
│ ├── AmrManagerService.java AMR 서비스 수명과 요청 진입
│ ├── state/AmrStateAuthority.java 작업 토큰·요청 단계·관측 상태의 정본
│ ├── state/AmrStateSnapshot.java 요청 단계와 실제 관측을 분리한 읽기
│ ├── observation/AmrObservationEnvelope.java 원본 관측·순번·실행 귀속 주장
│ ├── observation/AmrObservationIngress.java 한 실행의 수신 검증; 제품 어댑터 미연결
│ ├── completion/MovementCompletionDisposition.java 완료 인계 가능 여부의 구분값
│ ├── completion/MovementCompletionSession.java 명령 전 등록·시도 결합·완료 전달·등록 정리
│ ├── completion/AmrAbortObservationService.java 취소 뒤 관측 수명·만료·서비스 종료 정리
│ └── execution/
│ ├── AmrOperations.java 기존 즉시 명령 경로
│ ├── MovementTaskInput.java 기본 이동·복귀의 불변 입력
│ ├── MovementTaskDispatcher.java 실행권 확인→구동 자원 확보→세션 생성
│ ├── MovementTaskSession.java Task·자원·서비스·AMR 작업 권한 검사
│ ├── ManagedMovementExecutor.java 청소 정지→이동→도착→구동부 정지
│ ├── MovementExecutionResult.java 정상 반환의 근거 구분 (Task 완료 아님)
│ ├── MovementExecutionContext.java 실제 Task·Runtime 수명에 묶인 세션 식별
│ ├── CorrelatedMovementRuntime.java 원본 순번·실행 귀속을 보존하는 공급자 계약
│ └── ManagedMovementRuntime.java 이번 실행의 관측·명령·중단 확인 계약
└── cleaning/execution/
└── CleaningTransitionPort.java 청소 중인지, 정지를 확인했는지
domain/amr/execution는 AMR의 실행 절차다. 작업 목록과 전체 Task 상태를 다시 만드는 폴더가 아니다.
domain/cleaning/execution는 청소 기능 자체의 소유권을 AMR에 넘기지 않고 필요한 협력 계약만 제공한다.
Task의 실행 시도를 인계받는 것과 구동 자원을 확보하는 것도 다르다. 세션은 ResourceManagerService가 이미 발급한 점유를 검사하며 자원을 새로 확보하거나 다른 소유자에게서 빼앗지 않는다.
기존 AmrOperations의 즉시 명령과 이번 완료 대기 실행기는 별도 경로다.
제품 요청을 새 경로에 연결할 때 이 두 경로를 동시에 실행하면 안 된다.
2. 목적지 이동의 판정 순서
완료 인계 준비 — 원본 근거와 현재 실행권
2026-09-11: AMR 취소 관측의 서비스 수명 연결
executeWithAbortObservation(runtime, cleaning)은 사전 구성된 AMR 관측 서비스에
원래 완료 등록·시도·수신기와 수락 결과를 인계한다. 실행 finally가 끝난 뒤에도
observe(handle, envelope) 또는 deliverAccepted(handle)로 원본 ABORTED를 검사한다.
현재 Task가 CANCELLING인 경우에만 공통 완료 조정자가 CANCELLED를 반영한다.
이전 세션의 완료/실패 변경은 거부하며 timeout·서비스 종료·세대 변경·중복 전달에서
단일 소유자가 정리한다. 새 명령을 보내거나 RECOVERY_REQUIRED 자원을 FREE로 바꾸지 않는다.
검증: 서버 604개·SDK 62개, 실패·오류·건너뜀 0과 release APK 빌드. 기존 미완 LLM/TTS 변경은 제외했다. 제품 경로 미연결·실기기 미검증이며, 기존 API의 제품 경로를 자동 교체하지 않는다. 폴더·수명·상태 흐름 상세 SVG.
2026-09-11: 수락한 관측을 원래 수신기에서 한 번만 인수
취소를 처리하는 동안 이미 받은 중단 관측을 다시 수신시키면 중복 순번으로 거부된다.
이를 피하기 위해 AmrObservationIngress.takeAccepted를 추가했다. 원본 순번을 초기화하지 않는다.
원래 수신기가 발급한 최신 수락 결과 하나만 보관하며, 동일 결과를 두 호출자가 동시에 인수해도 한 번만 성공한다.
다른 수신기의 결과·교체된 결과·이미 인수한 결과는 INVALID_RECEIPT로 거부한다.
인수 시 관측 나이와 현재 공급자 세대를 다시 검사한다. 원본 ID·중단 이유·관측 종류는 바꾸지 않는다.
당시 서비스 소유권 인계는 아직 미연결이었다. 이 API만으로 Task 취소 완료나 물리 정지가 증명되지는 않는다. 이후 위 서비스 수명 연결에서 기존 완료 등록과 수신기를 인계하고 만료·종료 정리를 구현했다. 제품 콜백을 추정 이벤트로 연결하지 않는 경계는 유지한다.
검증: 신규 12개 포함 서버 576개·SDK 62개 JVM 테스트, 실패·오류·건너뜀 0.
기존 LLM/TTS 미완 변경을 제외한 서버 스냅샷에서 전체 98개 빌드 작업 재실행 및 release APK 빌드를 통과했다.
제품 활성화·실기기·자원 FREE 전환 검증은 포함하지 않는다. 기존 전체 구조도와 이동 흐름 SVG는 유지한다.
2026-09-11: 사전 등록부터 Task 완료까지 연결
새 내부 선택형 API executeWithCompletion(runtime, cleaning, coordinator)가 다음 경로를 실행한다.
기존 execute·executeWithResult의 의미와 제품 경로는 바꾸지 않는다.
TaskManager 소유권 검사 → 정책·실행권 확인 → 완료 사전 등록
→ 실제 AMR 명령 → 반환된 실행 시도 결합 → 도착/충전·정지 확인
→ 자원 반납(UNVERIFIED) → 현재 실행권·원본 근거 재검사
→ 공통 완료 조정자 → Task SUCCEEDED 또는 보류/거부 → 소유 등록 정리
MovementCompletionSession은 AMR 한 실행의 등록을 소유한다. 내부 등록 키의 UUID는 제품 이벤트 ID가 아니다.
validateOwner는 동일 라이프사이클뿐 아니라 정확히 같은 TaskManager인지 실행권 소비 전에 확인한다.
다른 Task 저장소에 같은 ID가 있어도 명령을 보내거나 그 Task를 완료 처리하지 않는다.
| 상황 | 처리와 상태 |
|---|---|
| 사전 등록 충돌 | 명령 0회, FAILED_BEFORE_DISPATCH; 다른 실행의 등록은 유지 |
| 실제 명령 호출 뒤 오류·다른 대상 응답 | RECOVERY_REQUIRED; 물리 미전달·정지 완료로 단정하지 않음 |
| 유효한 원본 도착·충전 이벤트 | 원본 ID·순번으로 공통 완료 접수; 승인 시 Task SUCCEEDED |
| ID 누락·앞뒤 공백으로 변형될 ID | MISSING_EVENT_ID·NONCANONICAL_EVENT_ID; 성공 이벤트를 합성하지 않음 |
| 위치 안정·이미 충전소 | 근거 부족으로 보류; Task 성공으로 승격하지 않음 |
| 취소와 완료의 경합 | 공통 WAITING_FOR_ABORT 등 결과를 보존; 지속적인 실제 중단 관측은 후속 |
| Evidence 기록 실패 | 완료 조정자의 기록 실패 결과를 보존; 명령 재실행이나 Task 완료 되돌리기 없음 |
Task 성공과 자원 안전은 별개다. 정상 자원 반납 후에도 UNVERIFIED이며 FREE 확인은 별도다.
종료 시 등록 정리는 Task 취소나 물리 정지 처리가 아니다. 제품 공급자 연결·자동 실행 활성화·실기기 검증은 하지 않았다.
검증: 신규 10개 포함 서버 564개·SDK 62개, 실패·오류·건너뜀 0.
전체 98개 빌드 작업 재실행과 release APK 빌드, 폴더·레이어·API 경계 검사 통과.
기존 공통 validateOwner 보강만 의존성으로 포함하고 나머지 미완 LLM/TTS는 제외한 소스 격리 검증이다.
2026-09-11: 원본 이벤트 ID 보존
domain/amr/observation/AmrObservationEnvelope에 원본 식별자를 보존하는 eventId()를 추가했다.
기존 3인자 생성자는 그대로 유지하며 ID가 없다는 뜻의 null을 반환한다.
새 4인자 생성자는 공급자가 준 ID를 그대로 보관한다. 빈 값·앞뒤 공백·한글도 수정하거나 새 ID로 채우지 않는다.
수신 검증을 통과한 결과는 같은 봉투를 보존하고, 같은 순번의 재접수는 기존 규칙대로 거부한다.
이는 순번 검사이지 eventId 기반 중복 제거를 새로 구현했다는 뜻은 아니다.
명령 전 실행 등록은 위 후속 단계에서 내부 선택형 API로 구현했다. 이 절은 원본 필드 보존 단계의 이력이다. 공통 완료 입력은 앞뒤 공백을 제거하므로 새 연결에서는 원본 ID가 바뀌는 입력을 거부한다. 현재 봉투의 원본 보존 기능은 이러한 완료 승인 검사와 별개다. 제품 공급자 연결은 후속이며, 이 필드 추가로 Task 성공·자원 안전·하드웨어 출처 인증이 성립하지 않는다.
이번 검증: 신규 3개 포함 서버 554개·SDK 62개, 실패·오류·건너뜀 0. 집중 검사 27개와 전체 98개 빌드 작업 재실행·release APK 빌드·경계 검사 통과. 기존 미완 LLM/TTS 변경을 제외한 소스 격리 검증이며 APK 설치·실기기 검증은 수행하지 않았다.
2026-09-10 내부 구현 및 JVM 검증 이력. 실행 결과의 귀속과 현재 실행권을 확인하는 단계다. assessCompletion 자체는 공통 완료 조정자나 제품 콜백을 호출하지 않는다. 새 완료 API의 전달 동작은 위 절을 따른다.
| 순서·담당 폴더 | 하는 일 | 하지 않는 일 |
|---|---|---|
1. domain/amr/observation/ |
수신 검사를 통과한 원본 봉투를 그대로 보존 | 거부한 관측 노출, 없는 순번 생성 |
2. domain/amr/execution/ |
sourceEnvelope()로 실제 종료 근거의 봉투를 연결하고 MovementExecutionContext로 세션 귀속을 식별 |
같은 목적지라는 이유로 다른 실행의 결과 재사용 |
3. domain/amr/execution/MovementTaskSession |
assessCompletion(result)에서 현재 Task 실행권·서비스 수명·AMR 작업·공급자 세대·관측 시각을 다시 검사 |
정상 반납된 자원을 다시 점유하거나 기기에 명령 전송 |
4. domain/amr/completion/ |
MovementCompletionDisposition으로 판단 근거와 보류·거부 이유를 구분 |
Task 성공 확정, 자원 FREE 전환 |
5. execution/task/completion/ |
새 완료 API에서 원본 eventId와 실제 실행 등록을 연결한 뒤 공통 완료 접수 | 공급자가 제공하지 않은 eventId를 Task ID나 시각으로 대체 |
공급자 세대와 Runtime 서비스 세대는 다르다. 공급자의 Attempt.generation은 관측 공급자의 재시작을 구분한다. 컨텍스트의 runtimeGeneration은 실제 Runtime 서비스 수명을 나타낸다. 실제 Task permit에서 taskId를 가져오되, 이것을 공통 완료 등록용 TaskExecutionKey로 자동 변환하지 않는다. 컨텍스트의 숫자가 같다는 이유만으로 같은 세션이라고 판단하지 않는다.
| 판정값 | 읽는 방법 |
|---|---|
OBSERVATION_AVAILABLE |
이 검사 지점에서 현재 실행에 귀속된 원본 관측을 이용할 수 있음. Task 완료 승인은 아님 |
POSITION_ONLY / ALREADY_ON_STATION |
위치 안정 또는 이미 충전소인 호환 경로. 새 완료 이벤트를 만들지 않음 |
UNBOUND_RESULT |
단독 실행기나 다른 세션의 결과로, 이 세션의 실행권에 묶이지 않음 |
STALE_EXECUTION |
취소·서비스 재시작·작업 권한 교체 등으로 현재 실행권을 사용할 수 없음 |
STALE_PROVIDER / PROVIDER_UNAVAILABLE |
공급자 세대가 달라졌거나 현재 확인할 수 없음 |
INVALID_CLOCK / FUTURE_OBSERVATION / EXPIRED_OBSERVATION |
시계 또는 관측 시각이 허용 조건을 충족하지 않음 |
반복 조회는 상태를 바꾸지 않는다. 같은 결과가 다시 조회되는 것과 완료 이벤트가 중복 접수되는 것은 다른 문제다. 수신 단계는 중복 순번을 거부하지만 이 API는 완료 이벤트 전달·소비·중복 제거 API가 아니다.
검사는 서비스 사이의 원자적 트랜잭션이 아니다. OBSERVATION_AVAILABLE을 저장해 나중에 성공 허가증처럼 재사용하면 안 된다. 원본 eventId의 제품 공급、제품 관측 어댑터、실제 기기의 정지 확인은 후속이다. 기존 executeWithResult는 정상 반환 후 Task RUNNING·자원 UNVERIFIED를 유지한다. 새 executeWithCompletion의 Task 전이는 위 절에서 구분한다.
검증: 신규 10개 및 기존 검사 보강을 포함해 서버 551개, SDK 62개 JVM 테스트 실패·오류·건너뜀 0. 집중 검사 79개를 통과했고, 최종 검증에서 --rerun-tasks로 98개 빌드 작업을 재실행해 release APK를 생성했다. 미완 LLM/TTS 변경을 제외한 소스 격리 검증이다. API 미구현 실패와 선언만 있는 상태의 동작 실패를 먼저 확인했다. 제품 APK 설치·기기 관측·Task 종료·물리 안전 검증은 포함하지 않는다. 아래 541개 이하 수치는 이전 단계 이력이다.
실행 종료 근거 — Execution Result
2026-09-10 내부 구현, 제품 경로 미연결: 기존 이동 절차를 유지하면서 MovementExecutionResult로 정상 반환 근거를 구분한다. 이 결과는 Task 완료 증거가 아니다. 실행기가 단독 호출될 수도 있으므로 결과 타입만으로 관측 수신 검증을 통과했다고 주장하지 않는다.
| 종료 근거 | 한국어 의미 | 보존하는 정보 | 추가 판단이 필요한 부분 |
|---|---|---|---|
ARRIVAL_OBSERVED |
도착 응답을 확인하고 idle 대기를 마침 | 원본 Attempt·도착 Observation | idle snapshot의 실행 귀속·최신성 및 물리 안전 |
POSITION_STABLE |
목적지 위치가 안정적인 기존 호환 경로 | 원본 Attempt, 관측 이벤트 없음 | 이번 실행에 귀속된 도착 증거가 아님 |
STATION_CHARGING_OBSERVED |
충전소 충전 응답을 확인함 | 원본 Attempt·충전 Observation | 모든 구동부의 안전 정지를 뜻하지 않음 |
ALREADY_ON_STATION |
명령 전부터 충전소에 있음 | 기존 snapshot의 충전소 값 | 새 Attempt·이벤트 없음; 충전소 식별자가 없을 수도 있음 |
ManagedMovementExecutor.moveToWithResult()와 returnToStationWithResult()는 기존 대기·취소 확인을 마친 뒤 결과를 반환한다. 원본 관측 객체를 보존하며 새로운 관측 ID·순번·시각을 합성하지 않는다. 기존 void 호출도 유지한다.
MovementTaskSession.executeWithResult()는 기존 execute()와 같은 일회성 실행 제한을 공유한다. 최종 실행 권한 검사와 자원 반납이 성공한 뒤에만 결과를 반환한다. 정상 반환 후에도 Task는 RUNNING, 자원은 UNVERIFIED이며, 예외를 물리 정지 완료로 간주하지 않는다. 제품 콜백·공통 완료 조정자·Task terminal 연결은 후속 범위다.
검증: 신규 6개와 기존 회귀 검사 보강을 포함한 서버 541개, SDK 62개 JVM 테스트 실패·오류·건너뜀 0, release APK 빌드 통과. 기존 미완 LLM/TTS 변경을 제외한 소스 스냅샷으로 검사하고, --rerun-tasks로 전체 98개 빌드 작업을 다시 실행했다. 독립 설계 준수 리뷰 통과. 아래 535개·523개·500개·489개는 이전 단계 기록이며 합산하지 않는다.
관측 검증기와 이동 세션 연결
2026-09-10 내부 연결 구현, 제품 경로 미연결. 앞서 만든 검증기를 MovementTaskSession이 실제로 소비하도록 연결했다. 명시적 공급자 계약을 지원하지 않으면 물리 명령 전에 실패한다.
연결 경로는 MovementTaskSession → CorrelatedMovementRuntime → AmrObservationIngress → ManagedMovementExecutor다. 세션이 수신기의 수명을 소유하며, 공급자가 보존한 원본 순번·시각·실행 식별자를 검사한 관측만 실행기로 반환한다. 기존 raw Observation에 현재 실행 ID나 수신 순번을 붙여 신뢰할 수 있는 이벤트로 바꾸지 않는다.
| 검사 지점 | 세션의 책임 | 남는 한계 |
|---|---|---|
| 실행 시작 | 명시적인 관측 공급자 계약 확인; 미지원이면 물리 명령 전 거부 | 제품 공급자 미구현 |
| 명령 접수 뒤 | 반환된 Attempt를 한 번 등록, Task·서비스·자원 권한 재확인 | SoC 메시지 예약만으로 하드웨어 접수라고 주장할 수 없음 |
| 관측 읽기 전후 | 실행권과 현재 공급자 세대 확인; 빈 관측도 세대 검사 | 공급자 호출은 bounded/nonblocking이어야 함 |
| 관측 반환 직전 | 승인된 원본의 시각과 현재 권한 재확인 | 여러 서비스와 하드웨어를 하나의 원자적 트랜잭션으로 묶지 않음 |
| 실행 종료 | 정상·예외 모두 수신기 닫기 | 취소·서비스 종료는 다음 검사 지점에서 감지; blocking 호출 강제 중단 아님 |
검증 실패는 amr_observation_rejected와 도메인 거부 사유를 포함한 실패로 전달한다. 거부된 이벤트를 무시하고 위치 안정성 판정으로 우회하지 않는다. 다만 기존 MR6 위치 안정성 판정과 이미 스테이션 위인 경우의 빠른 반환 자체는 유지한다. 이 경로의 반환까지 실행 귀속 완료 증거가 되었다는 뜻은 아니다. 정상 반환도 Task 최종 완료·자원 FREE를 선언하지 않는다.
제품 연결에서 확인한 제약: 현재 SoC의 opSetTargetPosition2와 TargetPosition JSON에는 실행 요청 식별자가 없고, AllMovingInfo 모델 및 Binder 변환에도 보존되지 않는다. AmrPacket.currPart는 패킷 분할 번호이지 실행 ID가 아니다. AMR 펌웨어의 선택 확장 지원 여부는 별도 확인 대상이다.
검증: 신규 12개를 포함한 서버 535개, SDK 62개 JVM 테스트 실패·오류·건너뜀 0, release APK 빌드 통과. 기존 LLM/TTS 로컬 변경을 제외한 스냅샷으로 검증했다. 식별 불가·만료·중복 순번·같은 목적지의 다른 실행·미래 시각·명령 반환 직후와 빈 관측 중 세대 변경·관측 읽기 중 취소 및 AMR 재시작·소비 직전 만료·최종 시각 조회 중 공급자 세대 변경을 검사했다. 설계 준수 및 코드 품질 리뷰 통과. 아래 523개·500개·489개는 각각 이전 단계 기록이며 합산하지 않는다.
관측 수신 검증 — Observation
이전 단계 — 2026-09-10 구현, 제품 경로 미연결: domain/amr/observation/에 AMR 전용 수신 검증기를 추가했다. AmrObservationEnvelope는 원본 관측·순번·실행 귀속 정보를 보존하고, AmrObservationIngress는 등록한 한 번의 실행과 일치하는 관측만 반환한다. 당시에는 단독 검증기였으며 후속 이동 세션 연결 상태는 위 절에서 관리한다. 기존 DeviceAgent 콜백 연결은 별도다.
MR6 TaskCompletionStateStore는 대상 장소와 수신 시각으로 도착을 기록한다. MR7의 기존 MovingLocationManager.MovementCallback도 실행 시도·세대를 전달하지 않는다. 이런 이벤트에 현재 Task ID를 붙이면 같은 장소 재실행에서 이전 작업의 늦은 이벤트를 새 완료로 오인할 수 있다. 새 검증기는 빈 식별자를 현재 실행 정보로 보충하지 않는다.
| 검증 항목 | 거부하거나 유지하는 동작 |
|---|---|
| 실행 귀속 | 공급자가 원본 실행에 귀속하지 못한 이벤트는 UNCORRELATED_SOURCE로 거부 |
| 시도·세대·대상 | 등록한 실행과 정확히 일치해야 함; 같은 밀리초·같은 장소라도 이전 실행 ID는 거부 |
| 순번 | 양수이며 이전 승인 순번보다 커야 함; 잘못된 큰 순번을 거부해도 정상 이벤트는 계속 받을 수 있음 |
| 관측 시각 | 명령 접수 이후, 현재 단조 시각 이하, 명시된 유효기간 이내여야 함; 경계값 포함 |
| 수신 수명 | 한 번만 등록 가능; 종료 또는 호스트가 전달한 세대 변경 뒤에는 재사용 불가 |
| 반환 | 승인된 원본 Observation만 반환; 거부 결과에는 관측 없음; Task·AMR 상태·자원은 변경하지 않음 |
하드웨어 출처 인증은 아니다. 실행 귀속 정보는 신뢰된 공급자의 주장이다. 호스트는 실제 현재 세대·단조 시각을 전달하고 실행 권한 회수 시 수신기를 닫아야 한다. 자동 서비스 수명 구독은 없다. 반환 뒤 소비 시점의 권한 재검사도 필요하다. 실제 구동부 명령·콜백의 연결 보증은 제품 어댑터에서 별도로 입증해야 한다.
검증: 신규 23개를 포함한 서버 523개, SDK 62개 JVM 테스트 실패 0, release APK 빌드 및 폴더·계층·API 경계 검사 통과. 기존 미완 LLM/TTS 변경은 제외한 스냅샷으로 검증했다. 아래 500개·489개는 이전 단계 기록이다. 실제 기기 관측 공급·완료 증거 전달·제품 활성화는 아직 남아 있다.
AMR 작업 상태 연결
2026-09-10 구현: MovementTaskSession이 이제 AmrManagerService.beginStateOperation()을 호출한다. Task 실행권과 AMR 작업 토큰이 실제로 연결되며, dispatcher와 직접 세션 구성 모두 같은 서비스 수명에 속한 AMR 서비스와 양수 maxObservationAgeMs를 필수로 받는다. 관측이 없거나 허용 시간을 넘긴 IDLE이면 청소 정지·이동 명령 전에 거부한다.
| 시점 | 확인하는 상태와 처리 | 자동으로 선언하지 않는 것 |
|---|---|---|
| 실행 시작 | 최신 IDLE과 Task 실행권으로 AMR 작업 토큰 생성 | 접수 직후 상태는 ACCEPTED/UNKNOWN; 실제 이동·정지 아님 |
| 후속 검사 지점 | 같은 작업 토큰과 요청 단계 확인; STOPPING·FAULTED·토큰 교체 시 거부 |
정지 요청만으로 물리 정지 완료 아님 |
| 물리 명령 직전 | ACCEPTED만 명령 허용; QUIESCENT는 관측 읽기·정상 반환만 허용 |
이미 정지 관측된 작업에 새 이동 명령을 보내지 않음 |
| 이동 명령 이후 취소 | 후속 검사에서 중단; 실제 MOVING 관측은 유지 | Task 취소 완료·자원 해제 아님 |
| 세션 반환 | 정상은 자원 UNVERIFIED, 실패는 RECOVERY_REQUIRED |
Task terminal·자원 FREE 아님 |
MOVING·IDLE은 별도 상태 관측 API에 전달된 관측만 반영한다. 실행기의 Snapshot이나 ARRIVED를 상태 관측으로 변환하지 않는다. 검사와 물리 명령은 원자적이지 않으며, 관측 읽기·청소 정지 조회·대기 자체를 각각 잠그거나 중단시키는 구현도 아니다. 제품 어댑터에서 명령 직렬화와 콜백 출처 검증이 필요하다.
검증: 이번 11개 추가 검사 포함 서버 500개, SDK 62개 JVM 테스트 실패 0, release APK 빌드 성공. 미완 LLM/TTS 변경을 제외한 서버 스냅샷에 이번 AMR 변경만 적용했다. 이는 내부 실행 경로의 구현·검증이며 제품 콜백 연결, 스케줄러 실행 배정, 완료 증거 전달 및 실제 기기 검증은 남아 있다.
2026-09-10: 서비스 조립을 포함한 내부 통합 검증
MovementTaskChainTest는 개별 실행기만 호출하지 않는다. FrameworkRuntimeHost로 공통 서비스와 독립 AmrManagerService·CleaningManagerService를 시작한 뒤, 호출자별 AMR 접수 → Task 조회·실행권 발급 → 자원 확보 → 청소 정지 → 이동·복귀 관측을 이어서 검사한다. 기기 명령·관측만 테스트 제공자이며 Android Binder나 실제 하드웨어 왕복 검증은 아니다. 정책은 테스트용 명시적 허용이고 제품 안전 정책은 검증하지 않는다.
신규 10개 검사로 소유자별 조회·취소 격리, 두 작업의 구동 자원 경합, 시작 시 미확인 자원, 준비 전 취소, 청소 정지 직후 취소, Cleaning 장애, 전체 호스트 종료, 이전 세대 도착 관측을 확인했다. 전체 서버 489개, SDK 62개 JVM 테스트와 release APK 빌드가 통과했다. 테스트 추가에 따른 검증 범위 확대이지 제품 연결 구현률 증가가 아니다.
| 이어진 경계 | 이번에 확인한 결과 | 아직 자동 연결하지 않은 부분 |
|---|---|---|
| 앱 소유권 → Task 기록 | 다른 호출자는 조회·취소할 수 없음; 같은 요청 ID도 소유자별 분리 | 실제 Binder 호출자 신원·Parcel 왕복 |
| Task 실행권 → 구동 자원 | 먼저 점유한 작업만 준비 가능; 후속 작업의 실행권은 유지 | 스케줄러의 대기·재배정과 제품 자원 재확인 |
| 청소 정지 → 이동 | 정지 후 취소가 들어오면 이동 명령 없음 | 제품 Cleaning 정지 콜백의 출처·시점 검증 |
| 서비스 수명 → 실행권 | Cleaning 장애·호스트 종료 이후 기기 명령 없음 | 제품 프로세스 종료 시 물리 정지 확인 |
| 실행기 반환 → Task 상태 | 정상 반환은 RUNNING 유지, 취소 요청은 CANCELLING 유지 | 완료 증거 전달과 Task terminal 반영 |
이전 검증 단계(59909e8)의 결과: 당시 세션은 AMR 상태 시작 API를 호출하지 않아 상태 정본의 작업 식별자가 비어 있었다. 위의 AMR 작업 상태 연결에서 이 내부 간극을 해소했다. 489개·500개 수치는 이전 단계 기록이며 최신 상태는 위 실행 종료 근거 및 관측 검증기 연결 절을 참고한다. 서비스 내부 연결과 제품 경로 연결은 계속 구분한다.
- AMR 상태 정본 연결: 내부 세션 연결 완료. 수신 관측만 MOVING·IDLE 등 실제 상태를 바꾸고, 명령 접수를 IDLE/도착으로 만들지 않는다. 제품 관측 공급은 다음 항목이다.
- 제품 콜백 연결: 실제 Android/구동부 이벤트에서 시도 식별자·서비스 세대·대상·단조 시각을 검증한다. 현재 포트의 Snapshot은 제공자의 일관된 읽기를 전제로 하므로 제품 수신 계층 검증이 필요하다.
- 완료 증거 전달: 도착과 구동부 정지를 확인한 결과를 공통 완료 조정자로 전달한다. 취소·시간 초과·전송 오류를 물리 정지나 자원 FREE로 바꾸지 않는다. 실행 세션과 자원 확인, Task 종료는 서로 다른 판정이다.
MR6 task_manager 기준 d1d8dfb의 MovementTaskExecutor.executeReal()은 이동 도착·IDLE 확인 또는 스테이션 충전 확인 뒤 writeCompletion()으로 결과 필드를 쓴다. MR7의 MovementExecutionResult는 내부 실행 종료 근거를 구분하기 위한 계약이며, MR6 출력 Bundle 호환 어댑터나 Task 완료 전달의 이식과는 다르다. 방 이름/좌표 해석, 명시적 Bundle 플래그 보존도 별도 미이식 범위다. 보류 중인 LLM/TTS·공통 완료 조정자 변경은 이번 검증에 포함하지 않았다.
2026-09-10: 실행권에서 세션까지 연결
MovementTaskDispatcher.prepare(claim)은 실행권 검증 → 필수 정책 검사 → MOVEMENT_BASE 점유 → 세션 생성을 수행한다. 자원이 사용 중이거나 아직 확인되지 않았다면 빈 결과를 반환하고 실행권은 소비하지 않는다. 재시점 판단은 상위 스케줄러 책임이며 자동 재시도나 임의 자원 해제는 하지 않는다.
동일 실행권의 dispatcher·직접 세션 생성은 같은 잠금으로 직렬화한다. 세션 생성 전, 실행 시작 시, 청소 정지·주행 명령 직전에 정책과 현재 권한을 재검사한다. 이미 스테이션에 있어 명령 없이 끝나는 경로도 정책 검사를 생략하지 않는다. 정책 콜백은 신뢰된 내부 구현이며 짧게 끝나야 하고 실행 준비를 재귀 호출하면 안 된다. 서비스 간 검사와 물리 명령은 하나의 원자적 트랜잭션이 아니므로 제품 어댑터의 명령 직렬화는 별도로 필요하다.
실행 정상 반환 시 자원은 UNVERIFIED, 실패 시 RECOVERY_REQUIRED로 남는다. Task 완료나 물리 정지를 자동 선언하지 않는다. idle=true와 moving=true가 함께 관측되는 모순 상태도 정지 완료에서 제외했다.
검증: 신규 dispatcher 17개·모순 상태 회귀 1개 포함 서버 479개, SDK 62개 JVM 테스트 통과, release APK 빌드 성공. 기존 미완 LLM/TTS 변경을 제외한 서버 스냅샷에서 검증했다. 제품 정책·스케줄러·실제 구동부·공개 Binder·기기 검증은 미완이다.
| 단계 | 확인 또는 실행 | 다음 단계로 넘어가는 조건 |
|---|---|---|
| 실행 자격 확인 | 현재 실행이 취소·수명 종료 상태인지 확인 | 실행 가능한 시도라는 호스트 확인 |
| 청소 정리 | 청소 중이면 정지 요청 | 이번 정지 요청 이후의 정지 확인 |
| 이동 요청 | 해석을 마친 목적지로 명령 전송 | 실행 제공자가 명령을 수락 |
| 도착 확인 | 도착 이벤트 또는 현재 위치의 안정성 | 목적지 일치 상태가 2초 유지되고 이동 중이 아님 |
| 구동부 정지 | 도착 확인 이후 별도 확인 | 모터 IDLE, 이동·복귀·도킹 중이 아님 |
| 결과 반환 | 실행 자격 재확인 후 정상 반환 | 상위 Task 관리자가 결과를 반영해야 함 |
도착 이벤트는 위치 안정성 판정을 대신할 수 있지만 구동부 정지 확인을 생략하지 않는다. 현재 위치가 일치하는 경우에도, 출발 당시 이미 목적지에 있었거나 실제 이동/출발 위치 이탈을 관측한 경우에만 위치 기반 완료를 인정한다. 위치가 흔들리거나 이동 상태로 바뀌면 2초 안정성 시간을 다시 센다.
MR6 원본의 이동 기본 제한은 120초이며 요청/설정으로 달라질 수 있다. 내부 실행기는 호스트에서 계산한 제한을 받아 최소 1초를 적용한다. 도착 이후 구동부 정지 대기는 별도로 30초다. 관측 확인 간격은 500ms다. 청소 정지와 도착 대기에도 각각 제한이 있으므로 전체 처리 시간을 단일 이동 제한과 같다고 보면 안 된다. 경과 시간은 벽시계 변경의 영향을 피하도록 단조 증가 시계를 사용하는 계약이다.
3. 스테이션 복귀는 다른 완료 조건이다
이미 스테이션 위라면 청소 정지나 복귀 명령을 보내지 않는 원본의 빠른 완료 경로를 유지한다. 그 외에는 청소 정지 확인 후 복귀를 요청하고 스테이션 충전 상태 관측을 기다린다. 일반 목적지 이동의 도착·IDLE 판정을 스테이션 충전 확인 대신 사용하지 않는다.
4. 취소 요청 ≠ 물리 정지 ≠ Task 종료
취소 요청이나 서비스 수명 종료를 확인하면 실행기는 더 이상 다음 명령을 내리지 않고 대기를 중단한다. 하지만 대기를 끝냈다는 사실만으로 물리 정지나 Task 취소 완료를 선언하지 않는다. 인터럽트 역시 상위 호출자에 전달하며 정지 성공으로 변환하지 않는다.
제품 연결 시 상위 호스트가 별도의 정지 명령과 확인 관측을 처리하고, 그 뒤 Task 종료를 반영해야 한다. 실행기 안에서 임의로 자원을 해제하거나 실패를 성공으로 바꾸지 않는다. 관측 포트는 같은 Task 시도·서비스 세대·대상·요청 이후의 이벤트만 제공해야 한다. 이 계약을 실제 Android 콜백에 연결하고 검증하는 일은 아직 남아 있다.
내부 실행기는 전달받은 시도 식별자·세대·대상·수락 이후 시각을 관측값과 대조하고 미래 시각의 관측도 거부한다. 다만 그 값이 실제 기기 콜백에서 올바르게 만들어졌는지는 포트의 책임이다. 새 세션은 현재 서비스 세대가 바뀌었는지 추가 검사하지만, 식별자 비교 테스트를 실제 기기 관측의 진위 검증으로 해석하지 않는다.
실행 세션이 추가하는 권한 검사
- TaskManagerService가 실제 발급한 claim인지 확인하고 단일 실행 permit으로 소비한다.
- 같은 Task·요청에 대해 AMR이 MOVEMENT_BASE만 점유한 정확한 토큰인지 검사한다.
- Runtime·Task·AMR·Cleaning의 READY와 세대를 고정하고, 중단 확인 전후 및 물리 명령 직전에 다시 검사한다.
- 정상 반환 시 자원은 UNVERIFIED로 바뀐다. 새 관측 없이 FREE가 되지 않는다.
- 취소·장애 등 불확실한 실패는 해당 점유를 RECOVERY_REQUIRED로 제한한다. Task terminal은 여기서 결정하지 않는다.
같은 요청을 반환 후 재예약해도 이전 토큰은 새 예약에 영향을 주지 않는다. 동일 세션의 두 번째 동시 실행도 거부하며, 거부된 두 번째 호출이 첫 실행의 자원을 실패 처리해서는 안 된다. 서비스별 권한 검사는 물리 명령 전달까지의 원자적 트랜잭션이 아니다. 제품 adapter의 직렬화와 정책·정지 확인 연결은 여전히 필요하다. 전체 상태·자원 분리는 도메인별 소유권 지도에서 확인한다.
5. MR6에서 유지하는 것과 별도로 보강하는 것
원본: a1-packages-mr6, task_manager, d1d8dfbc4c807a32990f4350cf09c49f159f5848의
apps/DeviceAgent/app/src/main/java/com/sk/airbot/deviceagent/task/MovementTaskExecutor.java.
| 원본 책임 | 이식 위치 또는 경계 |
|---|---|
prepareMovementResource |
실행기의 순서 제어 + CleaningTransitionPort |
waitForMovementArrived |
AMR 내부 도착·위치 안정성 판정 |
waitForMovementIdle |
AMR의 도착 이후 별도 구동부 정지 확인 |
normalizeTargetArea |
기존 지도/Bundle 호환 계층에 유지; 이번 순수 실행기에서 재구현하지 않음 |
applyManagedMoveDefaults |
향후 실제 명령 어댑터가 기존 입력의 명시값과 기본값을 유지해야 함 |
writeCompletion |
향후 호환 결과 어댑터가 기존 출력 키를 유지해야 함 |
원본의 부분 이름 검색이나 좌표·POI 혼합 입력에는 별도 검토가 필요하다. 이번 실행기에서 이를 새 공개 API의 규칙으로 확대하지 않는다.
기존 즉시 명령은 appCall 계산 방식이 관리형 이동과 다르므로 어댑터를 단순 재사용하면 안 된다.
단조 시계, 명시적 실행 중단 확인은 이식 과정의 보강이며 원본 전체와 바이트 단위 호환을 주장하지 않는다.
이번 내부 실행기 진입점은 비어 있지 않은 목적지 ID를 가진 기본 관리형 이동으로 범위를 제한한다.
좌표만 있고 ID가 없는 MR6 입력은 직접 받을 수 없으며, 명시적 appCall/공기질 센서 옵션의 전체 호환도 아직 연결하지 않았다.
내부 기본 플래그는 둘 다 false다. 원본의 모든 이동 입력을 이 API로 대체했다는 뜻이 아니다.
포트는 짧게 반환하는 호출을 제공해야 한다. 장시간 막힌 기기 함수를 실행기가 강제로 중단할 수는 없다.
6. 검증과 다음 연결
이전 — Task·자원 실행 권한 연결
3a7aa902에서 단일 실행 permit, 일시정지·재개 세대 검사, 예약마다 구분되는 자원 토큰, MovementTaskSession을 추가했다.
커밋된 서버 소스만 추출해 전체 서버 385개, SDK 46개 테스트를 다시 실행했으며 실패·오류·건너뜀은 0이다. 서버 release Java 컴파일과 폴더·계층·API·Bundle 경계 검사 4종도 통과했다.
권한·자원·세션 집중 검사는 40개다. 동일 요청의 재예약 뒤 과거 토큰 사용, 중복 실행, 취소·재시작, pause→resume 이전 claim 재사용을 회귀 검사한다.
이는 내부 연결 검증이며 제품 어댑터 연결·APK 설치·실기기 동작 검증은 포함하지 않는다.
이전 — 이동 실행기 이식 검증 기록
구현 커밋은 4715e62, 최종 시간 판정 보강은 60ca9d1이다. 관리형 이동 테스트 23개를 추가했으며,
보류 TTS 수정이 섞이지 않도록 커밋된 서버 소스만 별도로 구성해 서버 362개·SDK 46개 테스트를 실행했다.
최종 리뷰에서 상태 조회 지연이 2초 안정성 시작 시각을 앞당기는 문제를 발견했다. 지연을 넣은 테스트의 실패를 확인하고 상태 조회 이후 시각으로 수정했으며, 재검토와 전체 회귀 검사를 통과했다.
실패·오류·건너뜀은 모두 0이며 서버 release Java 컴파일도 통과했다.
DeviceAgent 테스트 22개도 캐시 없이 재실행해 통과했고 제한된 소스 집합 검사와 release 빌드가 성공했다.
당시 domain/amr/task 코드는 DeviceAgent 소스 집합에 추가하지 않았으므로 당시 기존 APK 해시는 유지됐다. 2026-09-11에는 이 패키지를 domain/amr/execution으로 이동했다. 아래 해시는 과거 검증 기록이며 현재 빌드의 해시를 뜻하지 않는다.
이는 새 실행기의 제품 연결을 증명하는 빌드가 아니라 기존 경로의 회귀 확인이다.
실행기 단위 검증은 기록형 기기 포트와 제어 가능한 시계로 순서·시간 경계·중단을 재현한다. 이는 실제 모터·청소 장치·Android Binder 왕복을 검증한 것이 아니다. 실기기 검증은 미완료다.
내부 Task·자원·서비스 검사 연결 이후 남은 제품 연결 순서는 정책 평가와 자원 확보/양도 → 실행 시도별 관측 어댑터 → 실제 명령 수락의 직렬화 → 완료/중단 증거 반영이다. 안전한 제품 연결 전에 기존 DeviceAgent 경로를 제거하지 않는다.