SKIX Blueprint 기준 MR7 리팩터링 정합성 감사 및 교정안
이 문서는 초기 기준의 감사 기록이다. 현재 팀 단위 Framework의 폴더·모듈·업무 경계는 2026-09-22 두 브랜치 비교를 참고한다.
결론부터: 무엇이 맞고 무엇을 바로잡아야 하는가
Blueprint가 목표 기준선이다. 분석 대상은
packages-mr7의 refactor/clean-architecture-prebuilt 브랜치, HEAD 5d0ee58이다.
MR7은 typed SDK, 공통 Binder 연결, 명령별 단일 domain owner, handler 분리까지는 Blueprint와
같은 방향이다. 그러나 이것은 목표 아키텍처의 Product Interface Layer와 Domain Framework
Plane 일부를 만든 것이지, Framework Control Plane이나 Managed Execution Plane을 구현한 것이
아니다. 특히 기존 StateManager, CmdPolicyManager, EventManager를 이름이 비슷하다는 이유로
각각 StateMirror, Policy Engine, Event and Evidence Fabric으로 간주하면 구현 방향이 틀어진다.
핵심 교정 문장: 이름이 비슷하다고 같은 책임이 아니다. 현재 리팩터링은 호출 외형과 도메인 분리를 잘 시작했지만, 상태·정책·자원·Task·완료 근거의 권위 원본은 아직 분리하지 않았다.
<a id="blueprint-work-area"></a>
Blueprint 전체에서 MR7이 작업된 영역
MR7 = Product Interface + Domain Framework 기반 구현
Framework Control Plane + Managed Execution Plane 미구현
아래 그림은 별도의 유사 구조도를 새로 그린 것이 아니다. Blueprint의 원본 도형을 단일 SVG 안에 그대로 포함하고, MR7 판정만 그 위에 얹었다. 대칭은 역할과 경계가 직접 대응함, 유사는 일부 대응, 다름은 이름·호출 형태보다 책임이 다름, 미구현은 목표 owner가 없음을 뜻한다.
주 구조도. Blueprint 원본 기반 MR7 작업 위치
02AManager와05Domain Execution은 대칭되는 기반이다.03은 유사하지만Bundle계약이고,04중앙 Product Framework Service는 다르거나 미구현이다. 빨간 점선이 현재의 중앙 authority 우회를 표시한다. 외부 SVG를 다시 불러오지 않는 독립형 파일이므로 위키<img>안에서도 원본과 판정이 함께 표시된다.
독립형 오버레이 SVG 열기
| Blueprint 영역 | MR7 현재 위치 | 이번 작업의 의미 |
|---|---|---|
| Experience and Consumer Layer | SKMLauncher2, AirClearWorkFragment, AirClearViewModel |
새 Product API의 사용처. MVVM 분석은 Experience/Consumer의 앱 내부 사용 사례 |
| Product Interface Layer | SkixManager, BaseManager, typed managers |
주 작업 ①: 문자열·Bundle 호출을 typed domain API로 감쌈 |
| Framework Control Plane | 목표 StateMirror, Policy Engine, Resource Arbiter, CapabilityRegistry 없음 |
미구현: 기존 유사 이름 클래스를 목표 authority로 간주하면 안 됨 |
| Managed Execution Plane | 정식 Task lifecycle, Workflow, Evidence, TaskState 없음 |
미구현: isDelivered()와 물리 완료를 분리해야 함 |
| Domain Framework Plane | ModuleCommandDispatcher, domain handlers |
주 작업 ②: 명령별 domain owner와 handler 분리 |
| Device Integration Plane | 기존 DeviceAgent·AMR/vendor 호출·callback | 기존 실행 경로 재사용, 이번 작업의 주 재설계 영역은 아님 |
현재는 Product Interface → IDeviceControl.sendModuleCommand(Bundle) → MainApi →
ModuleCommandDispatcher → domain handler로 내려간다. 즉 중앙 제어·관리형 실행 계층을 건너뛰는 현재 경로다.
이 브랜치는 Blueprint를 완성한 것이 아니라, 향후 Control Plane과 Managed Execution Plane을 사이에
삽입할 수 있도록 위쪽 typed 호출 창구와 아래쪽 domain executor 경계를 만든 단계다.
목표 구조의 정의와 각 계층 책임은 원래 Blueprint의 현재 MR7 위치 설명을 먼저 보고, 이 문서에서는 해당 판정의 코드 근거와 교정 순서를 확인하면 된다.
앱 사용 사례: MVVM 기준 1:1 대칭 분석
아래 그림은 전체 리팩터링 범위를 뜻하지 않는다. Experience and Consumer Layer의
SKMLauncher2가 Product Interface를 사용하는 방식을 확대해서 보는 앱 내부 상세도다. 기준 문서의
MVVM과 단방향 상태 흐름에 있는 원본 SVG와
원본 도형을 단일 SVG 안에 그대로 포함했다. 새 구조를 같은 좌표로 다시 그린 것이 아니라, 원본의 세 섹션과
11개 슬롯 위에 MR7 AirClear 대응 외곽선과 배지만 얹었으므로 같은 박스에서 차이를 바로 읽으면 된다.
그림 2. MVVM·UDF 원본 기반 MR7 1:1 대응
초록 대칭, 주황 유사, 빨강 다름, 회색 점선 미구현이다. 아래쪽 빨간 설명은stopAirClear()가 transport ACK를 UI 완료로 승격하는 책임 차이를 표시한다. 외부 SVG를 다시 불러오지 않는 독립형 파일이므로 위키<img>안에서도 원본과 판정이 함께 표시된다.
독립형 오버레이 SVG 열기
| 원본 MVVM 슬롯 | MR7 실제 대응 | 정합성 판정 | 구현자가 바꿔야 할 방향 |
|---|---|---|---|
| View | AirClearWorkFragment |
부분 정렬: uiState, uiEvent 관찰 |
버튼의 ViewModel 직접 호출을 UiAction 입력으로 통일 — 현재는 UiAction 미도입 |
| FeatureViewModel | AirClearViewModel |
의미 교정: ViewModel은 있으나 mutable AirClearUiState, Reducer 없음 | 불변 FeatureUiState와 일회성 UiEffect로 분리 |
| UseCase · Repository | UseCase 없음 · AirCleanerRepository |
부분 정렬: typed manager 호출은 맞음 | command 반환과 Flow<TaskState> 기반 완료 관찰 분리 |
| SkixUiManager | SkixAirCleanerManager |
잘못 매칭하면 안 됨: SkixUiManager 미구현 |
domain command facade는 유지하고 UI session facade를 별도 도입 |
| Binder · AIDL | IDeviceControl.sendModuleCommand(Bundle) |
전환 adapter | 새 공개 계약은 typed/versioned request로 제한 |
| SkixUiService · UiSessionController | 대응 없음 | SkixUiService 미구현 | session lifecycle, foreground, restore 권위 신설 |
| TaskSessionBinder | 대응 없음 | TaskSessionBinder 미구현 | taskId, observe, cancel, terminal 결과 계약 신설 |
| Android · Device Domain | DeviceAgent → Dispatcher → handler → vendor callback | 부분 정렬 | callback을 공통 evidence stream으로 정규화 |
| FeatureUiState | AirClearUiState |
의미 교정: setter가 같은 객체를 변경 | immutable snapshot으로 교체 |
| UiSessionState | 대응 없음 | UiSessionState 미구현 | 화면 session 수명의 권위 원본 신설 |
| TaskState | 대응 없음 | TaskState 미구현 | 실행 evidence 기반 terminal 상태 신설 |
현재 STOP 흐름은 btnAirClearStop → AirClearViewModel.stopAirClear() →
AirCleanerRepository.stopAirClear() → SkixResult.isDelivered() → fetchAirCleanerState() →
AIR_CLEAR_STOP이다. 여기서 isDelivered()는 Binder 전달 성공인데, 곧바로 화면의 완료성 이벤트를
발행하므로 transport ACK를 UI 완료로 승격한다. 목표 구조에서는 ACCEPTED와 물리 동작의
SUCCEEDED/FAILED를 TaskState로 분리해야 한다. 이 판정은 정적 코드 근거이며 실제 청소 종료 완료는
실기기 미검증이다.
보조 그림. Blueprint 목표 위의 MR7 구현 정합성
초록은 유지할 기반, 노랑은 전환 중인 기반, 빨강은 의미를 다시 분리해야 하는 부분, 회색은 아직 없는 부분이다. 원본 SVG 열기
보조 구조도: 제품 Framework 계층과 Runtime 상세
위 MVVM 1:1 대칭도가 UI 구조 판정의 기준이다. 아래 그림들은 UI 비교를 대체하지 않으며, Blueprint의 전체 구조, AOSP 위에서의 위치, Android Manager / Service 분리 관점에서 하위 runtime과 제품 Framework 경계를 더 자세히 보여 주는 보조 자료다.
그림 A. 전체 구조 matched view
Product Interface와 Domain Framework에는 유지할 구현이 있지만, 그 사이의 Control/Managed Execution authority는 비어 있거나 기존 대형 클래스에 섞여 있다. 원본 SVG 열기
- 답하는 질문: MR7 전체에서 SKIX가 앱과 실제 실행부 사이 어디에 놓이는가?
- 읽는 순서: 앱 기능 창구→Android 보호→공통 Runtime→도메인 서비스→실제 실행부 순서다.
- 검증 범위: 구조·JVM 기준이며 제품 포함과 실기기 동작은 별도다.
그림 B. AOSP position matched view
MR7 DeviceAgent는system_server의 SystemService가 아니라/system/priv-app의 platform-signed, persistent 제품 Runtime이다. 논리 Service를 나누되 초기 단계부터 APK·프로세스를 쪼개지 않는다. 원본 SVG 열기
- 답하는 질문: Android와 SKIX, 제품 실행부는 각각 무엇을 책임지는가?
- 읽는 순서: 시작·권한·복구→공통 운영→제품과 기기 실행 순서다.
- 검증 범위: AOSP 배치 목표이며 product makefile·SELinux·실기기는 미검증이다.
그림 C. Manager / Service contract matched view
SkixManager와 typed domain manager는 호출 창구로 유지한다. 상태·정책·자원·Task·완료의 authority는 DeviceAgent 프로세스 안의 논리 Service로 새로 분리해야 한다. 원본 SVG 열기
- 답하는 질문: Manager, ManagerService와 Agent/provider는 무엇이 다른가?
- 읽는 순서: 앱의 기능 창구→상태와 규칙의 책임자→실제 장치 실행자 순서다.
- 검증 범위: API·서비스 골격 계약 기준이며 도메인별 provider 실연결은 후속이다.
실제 요청과 callback을 끝까지 따라간 결과
그림 D. Runtime trace
typed 호출이 legacy Binder로 들어가고 callback이 상태·UI·IoT side effect로 퍼지는 실제 경로다. 원본 SVG 열기
- 답하는 질문: 요청이 멈췄을 때 어느 단계에서 문제인지 어떻게 찾는가?
- 읽는 순서: requestId→taskId→정책 판단→도메인 실행→실행부 결과→Evidence를 따라간다.
- 검증 범위: 필요한 추적 필드 계약이며 제품 로그에서 전 구간 상관관계 확인은 후속이다.
| 추적 지점 | MR7 정적 증거 | Blueprint 판정 | 교정 |
|---|---|---|---|
| SDK 진입 | SkixManager → typed manager → BaseManager |
부분 정렬 | Product API seed로 유지 |
| Binder ingress | IDeviceControl.sendModuleCommand(Bundle) |
전환 adapter | 신규 공개 계약은 typed/versioned request로 제한 |
| 호출자 신뢰 | Binder.getCallingPid()는 death monitor에 쓰고, routing 이름은 전달받은 classname 사용 |
방향 수정 필요 | UID·package·certificate를 Binder에서 파생하고 capability allowlist 적용 |
| callback 해제 | unregisterCallback() 본문이 주석 처리되고 항상 0 반환, 즉 no-op |
P0 결함 후보 | callback/name/death recipient를 원자적으로 해제하고 leak 계측 |
| 선행 gate | MainApi가 special mode와 StateManager.checkBlockedStatus를 직접 분기 |
정책 계약 미분리 | side-effect-free PolicyDecision을 shadow로 먼저 도입 |
| 도메인 routing | 31 registered handlers, 검증 결과 272 methods / 272 unique owners | 정렬됨 | dispatcher를 executor adapter 경계로 유지 |
| 호출 반환 | DeviceControlService가 Binder 반환 직후 SkixResult.delivered(output) 생성 |
transport ACK | ACCEPTED로만 해석하고 SUCCEEDED는 evidence 대기 |
| callback 처리 | MainService가 decode·state·UI·IoT·제품 action을 한 흐름에서 수행 |
책임 혼재 | Connector → Decoder → State Writer/Evidence Adapter → Subscriber 분리 |
classname은 callback routing metadata일 수는 있지만 보안 identity가 아니다. Proxy에서 stack trace나
호출자 입력으로 만들어 서비스에 넘기는 문자열을 권한 판단에 사용하면 안 된다. 또한
unregisterCallback()의 no-op은 등록 수명과 해제 수명이 비대칭임을 뜻한다. 이 두 항목은 공개
Manager 확대보다 먼저 닫아야 한다.
이름 변경이 아니라 책임 추출
그림 E. Responsibility extraction
StateManager,CmdPolicyManager,EventManager,MainService를 목표 이름으로 rename하지 않고 observe → shadow → route → own 순서로 책임을 추출한다. 원본 SVG 열기
- 답하는 질문: DeviceAgent에서 어떤 책임을 공통·Task·도메인·provider로 꺼내야 하는가?
- 읽는 순서: 현재 집중 구조에서 오른쪽으로 책임별 목적지를 따라간다.
- 검증 범위: 코드 책임 분석과 목표 배치이며 모든 명령 이식 완료를 뜻하지 않는다.
새로 확인된 TaskExecutor 의미 충돌
그림 F. TaskExecutor semantic gap
현재 구현은 유용한 직렬 queue seed지만 Task lifecycle authority가 아니며, 공유 숫자 ID 때문에 도메인 간 간섭 위험이 있다. 원본 SVG 열기
- 답하는 질문: Task 골격이 있는데도 왜 실제 로봇 작업 완료가 아닌가?
- 읽는 순서: 골격 있음→실제 명령 연결 필요→물리 완료 검증 필요 순서다.
- 검증 범위: JVM 골격과 남은 연결 차이를 구분하며 실기기 완료는 미검증이다.
TaskExecutor는 단일 HandlerThread에 ITask.execute(): void를 넣고 Message.what 숫자로 대기·실행
여부와 삭제를 관리한다. 그런데 WSS_COMMAND_ID_BASE = 2000과
STREAMING_COMMAND_ID_BASE = 2000이 동일하다. 따라서 예를 들어 WSS START와 Streaming START는
모두 2001이며, hasMessages(2001) 또는 removeMessages(2001)가 상대 도메인의 작업을 대기 중으로
오판하거나 함께 지우는 교차 취소 가능성이 있다. 이는 정적 코드에서 확인되는 구조 위험이며
실제 재현 여부는 동시 요청 실기기 테스트가 필요하다.
더 중요한 차이는 Task ID가 업무 식별자가 아니라 command type 숫자라는 점이다. request source,
idempotency, timeout, cancellation reason, checkpoint, EvidenceRecord, terminal TaskSnapshot이 없다.
따라서 이 클래스를 TaskManager Core로 rename하지 말고, 우선 domain namespace를 분리한 뒤 기존
queue를 WSS/Streaming executor 내부 구현으로 한정한다.
버리지 말고 공통 계약으로 승격할 vertical slice
그림 G. Reusable vertical slices
현재 구현에서 이미 목표 성질을 가진 작은 단위를 골라 공통 계약으로 승격한다. 원본 SVG 열기
- 답하는 질문: 기능 하나를 어느 구간까지 연결해야 구현 완료라고 할 수 있는가?
- 읽는 순서: Manager 요청부터 정책·Task·도메인·provider·Evidence까지 왼쪽에서 오른쪽으로 본다.
- 검증 범위: 기능별 완료 기준이며 각 기능은 AOSP·실기기 증거를 따로 채워야 한다.
| 현재 seed | 같은 성질 | 승격할 때 반드시 추가할 것 |
|---|---|---|
DeviceStateSnapshot |
immutable state, source, observed time, stale 판정 | domain aggregate, sequence, freshness policy, read-only subscription |
CameraAccessPolicy |
client lease, reference count, exclusive/shared 판단 | trusted caller, resource descriptor, TTL, fence, release evidence |
| KVS session 계약 | sessionId, fence, lease/command expiry, correlationId, replyTo, heartbeat/result |
common task/evidence schema와 sequence; liveview의 session authority는 유지 |
| Dispatcher owner registry | command별 단일 owner | version, availability, constraints를 갖춘 CapabilityRegistry projection |
이 네 seed는 Blueprint 전체가 구현됐다는 증거가 아니다. 각각 상태·자원·evidence·capability의 한 도메인 vertical slice일 뿐이다. 하지만 전면 재작성 대신 이 계약들을 일반화하면 기존 실행 owner를 보존하면서 목표 구조로 이동할 수 있다.
구현·릴리스 Gate
그림 H. Release gates
안전 경계부터 닫고 관찰·shadow를 거친 뒤 한 기능의 authority만 옮긴다. 원본 SVG 열기
- 답하는 질문: 어떤 검증을 통과해야 제품 출시 가능이라고 말할 수 있는가?
- 읽는 순서: 계약→JVM→AOSP 포함→권한·SELinux→실기기 순서다.
- 검증 범위: 현재는 앞쪽 계약·JVM 중심이며 오른쪽 제품 관문은 후속이다.
| 단계 | 구현 범위 | 다음 단계 진입 기준 |
|---|---|---|
| P0 | Binder 인증, callback 정상 해제, TaskExecutor ID namespace 분리 | 미승인 caller 거부, callback 잔존 0, WSS/Streaming 교차 취소 0 |
| P1 | read-only StateMirror, pure policy shadow, callback evidence adapter | 기존/신규 판정 diff 수렴, 기존 executor side effect 정확히 1회 |
| P2 | 한 기능의 Capability + ResourceLease + Task lifecycle | ACCEPTED와 SUCCEEDED 분리, timeout/cancel terminal 1회, evidence 기반 성공 |
| P3 | Schedule/UI/IoT subscriber와 domain별 authority 순차 전환 | reboot/rebind/callback-loss 복구 및 이동·도킹·청정 실기기 Gate 통과 |
추천 첫 slice는 Camera 또는 Docking이다. Camera는 기존 lease seed가 있고, Docking은 Binder 접수와 물리 도착 완료를 분리하기 쉬워 Task/evidence 계약의 수용 기준이 명확하다.
구현자에게 먼저 전달할 교정 지시
| 우선순위 | 지시 | 이유 |
|---|---|---|
| 즉시 중단 | StateManager를 이름만 바꿔 StateMirror로 선언하는 작업 |
현재 클래스는 상태 저장뿐 아니라 차단 검사와 LCD·SoundFlow·Vision·청정 제어까지 수행한다. |
| 즉시 중단 | CmdPolicyManager를 그대로 공통 Policy Engine으로 승격 |
정책 판정과 stopAllAction, pause/resume, returnToStation 물리 실행이 결합돼 있다. 정책이 실행을 직접 소유하면 안 된다. |
| 즉시 중단 | ModuleCommandDispatcher 또는 domain handler를 TaskManager로 부르기 |
현재 dispatcher는 문자열 명령의 owner를 고를 뿐, Task ID·대기열·취소·시간 제한·완료 근거를 소유하지 않는다. |
| 즉시 중단 | typed manager 수를 늘리는 것만으로 Framework 완성도를 판단 | 도메인별 편의 API 증가는 Control Plane의 상태·정책·자원·기능 명세 부재를 해결하지 않는다. |
| 즉시 유지 | SkixManager의 단일 연결, BaseManager의 legacy 격리, typed request/result 모델 |
Product Interface Layer의 올바른 전환 기반이다. |
| 즉시 유지 | ModuleCommandDispatcher와 명령별 단일 handler owner |
Domain Framework Plane의 실행 변환 경계로 재사용할 수 있다. |
| 즉시 착수 | 기존 owner를 읽는 read-only StateMirror projection |
새 상태 원본을 하나 더 만들지 않고 출처·관측 시각·최신성을 표준화한다. |
| 즉시 착수 | CmdPolicyManager에서 순수 판정과 실행 side effect 분리 |
shadow decision 비교 후 기능별로 실행 owner를 옮길 수 있게 한다. |
| 즉시 착수 | callback을 EvidenceRecord로 변환하는 adapter와 correlation ID |
Binder 응답과 실제 도착·도킹·청정 종료를 구분한다. |
| 즉시 착수 | Runtime Binder 공통 authorization gate | exported privileged service를 signature permission, UID·package·certificate, capability로 보호한다. |
판정 기준과 증거 경계
| 판정 | 의미 |
|---|---|
| 정렬됨 | Blueprint의 책임 경계와 MR7 구현 책임이 실질적으로 일치하며 다음 계층의 기반으로 유지 가능 |
| 부분 정렬 | 같은 방향의 구조가 있으나 계약·권위 원본·범위가 목표보다 좁음 |
| 미구현 | 대응하는 권위 원본이나 lifecycle 계약이 현재 브랜치에 없음 |
| 방향 수정 필요 | 이름이나 외형은 유사하지만 실제 책임 결합이 Blueprint 원칙과 충돌 |
이 문서는 소스, Git, Manifest와 로컬 정적 구조를 근거로 한다. AOSP 통합, OTA, 동일 서명 APK 교체, 실제 Binder 호출자 권한, 로봇의 이동·도킹·청정 물리 완료는 실기기 미검증이다.
6개 계층 종합 판정
| Blueprint 계층 | MR7 대응 구현 | 판정 | 유지할 성질 | 반드시 보완할 것 |
|---|---|---|---|---|
| Experience and Consumer Layer | SKMLauncher2·PUI·IoT 등 호출 앱의 SDK 채택 | 부분 정렬 | 앱이 raw proxy 대신 typed API로 이동 | 앱 내부 UDF/MVVM, UI session·Task 관찰 계약은 별도 검증 필요 |
| Product Interface Layer | SkixManager, SkixDeviceManagers, typed managers/models, DeviceConnectionService |
부분 정렬 | 단일 연결과 typed facade, legacy adapter 격리 | State·Capability·Policy·Task·Health의 횡단 Manager/Service 계약 부재 |
| Framework Control Plane | StateManager, CmdPolicyManager, EventManager, camera access policy |
방향 수정 필요 | 기존 안전 규칙과 도메인 상태는 회귀 기준선 | StateMirror·순수 Policy Engine·Resource Arbiter·CapabilityRegistry·Health 분리 |
| Managed Execution Plane | schedule handler와 기존 직접 실행, 별도 donor 후보 | 미구현 | 예약 저장·도메인 실행 경험 | Task lifecycle, workflow, queue, timeout, cancellation, evidence authority |
| Domain Framework Plane | ModuleCommandDispatcher, 31개 handler, 272개 단일 command owner |
정렬됨 | 기존 장치 동작을 typed 요청과 연결하는 adapter 경계 | handler 내부의 횡단 정책·상태·완료 판단을 Control/Execution Plane으로 이동 |
| Device Integration Plane | platform-signed DeviceAgent, AMR·IoT·vendor/HAL 연결, MainService callback |
부분 정렬 | 실제 하드웨어·연동 Agent 연결의 기존 owner 유지 | callback decode·상태 변경·UI·IoT 전달·evidence 방출 책임 분리, 공통 Binder 인증 |
1. Experience and Consumer Layer
Blueprint 목표
시스템 앱·PUI·IoT·승인된 파트너가 제품 기능을 직접 Binder 문자열로 조합하지 않고 Repository/UseCase를 거쳐 typed Manager를 사용한다. 앱 화면 상태는 앱이 소유하고, 제품 실행 상태는 Runtime Service와 TaskManager가 소유한다.
MR7의 같은 방향
- 앱 호출면이
SkixManager.getInstance(context)와 도메인 manager로 모인다. SkixDeviceManagers는 air cleaner, AMR, camera, schedule 등 도메인 탐색 창구를 제공한다.- 여러 typed request와 snapshot이 raw
Bundle을 앱 밖으로 밀어내는 방향으로 추가됐다.
차이와 수정 방향
MR7 소스만으로 모든 consumer가 UiAction → ViewModel → Repository → Manager를 따르고 있는지,
화면과 기기 Task의 lifecycle을 분리했는지는 증명되지 않는다. 따라서 “앱 API 교체 완료”를
“Experience Layer 완료”로 쓰면 안 된다. 소비 앱별로 direct proxy/Binder/Bundle 호출 잔존을 검사하고,
장기 동작은 callback을 곧바로 화면 완료로 해석하지 말고 Task snapshot으로 투영해야 한다.
2. Product Interface Layer
Blueprint 목표
Manager는 호출 편의·연결 대리이고, Service는 상태·정책·실행 lifecycle의 최종 책임자다. 첫 배치는 각 기능을 별도 APK로 쪼개는 것이 아니라 DeviceAgent 프로세스의 하나의 Runtime Binder 진입점 뒤에 논리 모듈로 둔다. 신규 공개 계약은 버전이 붙은 typed request/result/event를 사용한다.
MR7의 같은 방향: 유지해야 할 기반
| MR7 코드 | 확인된 책임 | Blueprint와 같은 성질 | 판정 |
|---|---|---|---|
SkixManager |
한 앱 프로세스의 단일 facade, 연결·환경·업데이트·도메인 manager 제공 | SkixFramework Client Core의 출발점 |
부분 정렬 |
DeviceConnectionService |
legacy adapter 한 개의 연결·해제·연결 상태·event 전달 | Manager별 bind 중복 방지 | 부분 정렬 |
BaseManager |
typed method를 legacy sendModuleCommand로 변환 |
Branch by Abstraction의 호환 adapter | 정렬됨 |
DeviceStateSnapshot |
source, observed time, stale 판정 포함 | StateMirror 공개 snapshot의 계약 씨앗 | 부분 정렬 |
| typed domain managers | 앱에서 명령 문자열과 Bundle 형식을 숨김 | 명확한 Product API로 이동 | 부분 정렬 |
아직 목표가 아닌 부분
BaseManager의 주석과 구현은 typed call이 최종적으로 legacy Bundle과
sendModuleCommand(indata, outdata)로 변환됨을 명시한다. 이것은 올바른 전환 경계지만 새로운
Runtime Service 계약 자체는 아니다. 또한 MR7에는 다음 Blueprint 횡단 API가 없다.
SkixDeviceStateManagerSkixCapabilityManagerSkixExecutionPolicyManagerSkixExecutionEventManagerSkixFrameworkHealthManagerSkixExecutionResourceManagerSkixTaskManagerSkixUiManager
현재 SkixEventManager는 이름과 달리 screen/sub-state와 EyeLED scene 명령을 보내는 도메인 manager다.
이를 Blueprint의 SkixExecutionEventManager로 확장하지 말고, SkixUiManager 또는 UI domain adapter로
명확히 이름과 위치를 정리해야 한다.
3. Framework Control Plane
그림 2. 이름 유사성 때문에 생기는 가장 큰 오해
왼쪽의 기존 class를 오른쪽 목표 모듈로 단순 rename하지 않고, 읽기 projection·순수 판정·evidence adapter로 분리한다. 원본 SVG 열기
3.1 StateManager는 아직 StateMirror가 아니다
StateManager는 707줄 enum singleton이다. main/sub state, wizard·mapping·ICT 등 제품 상태를 보관하는
동시에 checkBlockedStatus로 문자열 method를 차단하고, 상태 전환 과정에서 LCD, SoundFlow, Vision,
청정과 CmdPolicyManager pause/resume까지 호출한다.
Blueprint의 StateMirror는 기존 owner를 대체해 명령을 실행하는 객체가 아니라, 여러 도메인의 권위
상태를 출처·관측 시각·최신성과 함께 읽어 주는 공통 최신본이다. 첫 단계는 기존 write owner를
유지하고 read-only projection을 붙이는 것이다.
3.2 CmdPolicyManager는 아직 순수 Policy Engine이 아니다
CmdPolicyManager는 1,581줄이며 조건 판정뿐 아니라 stopAllAction, 이동 pause/resume,
AMRMotor.returnToStation, InterScheduleManager 제어를 직접 수행한다. 이 상태로 공통 Policy Engine이
되면 정책을 조회하는 순간 장치가 실행되거나, TaskManager와 이중 실행 owner가 생길 수 있다.
교정 순서는 다음과 같다.
- 기존 method별 입력 상태와 allow/deny/reason을 반환하는 side-effect-free evaluator를 추출한다.
- 기존 경로가 한 번만 실행하는 동안 새 evaluator는 shadow decision만 기록한다.
- 차이를 검수한 기능만
PolicyDecision → ResourceLease → Domain Executor순으로 연결한다. - 최종 실행은 handler/adapter 한 곳만 소유하고 Policy Engine에는 장치 제어 호출을 두지 않는다.
3.3 EventManager는 아직 Event and Evidence Fabric이 아니다
현재 EventManager 414줄은 main/sub state와 EyeLED scene을 중심으로 제품 표시 상태를 조정한다.
요청 ID, Task ID, sequence, event type, physical completion evidence를 공통 계약으로 연결하지 않는다.
따라서 기존 이름을 유지한 채 범용 event bus 역할을 추가하지 말고 UI scene 책임과 실행 evidence를
분리해야 한다.
3.4 부분적으로 재사용할 수 있는 구조
카메라의 CameraAccessPolicy는 client별 lease와 reference count, exclusive/shared 상태를 관리한다.
이것은 Resource Arbiter와 비슷한 성질을 가진 좋은 vertical slice다. 다만 카메라 전용 in-memory
policy이므로 이동·화면·오디오까지 포괄하는 공통 ResourceLease 권위 원본이라고 부를 수 없다.
카메라 동작을 그대로 일반화하기보다 resource descriptor, owner, mode, TTL/fence, release evidence를
공통 계약으로 추출하고 카메라 adapter가 이를 사용하게 한다.
4. Managed Execution Plane
현재 판정: 미구현
현재 브랜치에는 Blueprint가 요구하는 TaskManager Core, workflow, queue, timeout, cancellation,
checkpoint, TaskSnapshot, 완료 근거 기반 최종 상태의 권위 원본이 없다.
SkixScheduleManager가 typed schedule 요청을 만들고 ScheduleCommandHandler가 예약 동작을 처리하는
것은 Schedule API의 일부로는 맞다. 그러나 handler가 CmdPolicyManager.stopAllAction,
InterScheduleManager.start/pause/resume, returnToStation을 직접 호출하므로 예약 시작이 곧
managed task 제출은 아니다.
다음과 같이 고쳐야 한다.
Schedule trigger
→ TaskSubmission(scheduleId, capabilityId, inputs)
→ PolicyDecision
→ ResourceLease
→ Domain Executor Adapter
→ EvidenceRecord
→ TaskSnapshot(SUCCEEDED | FAILED | CANCELLED | TIMED_OUT)
TaskManager는 handler를 대체해 장치 코드를 다시 구현하지 않는다. 기존 handler 또는 더 작은 executor를
호출하고 lifecycle만 소유한다. Binder transport ACK는 ACCEPTED일 뿐 SUCCEEDED가 아니다.
5. Domain Framework Plane
가장 강하게 정렬된 부분
MainApi는 ModuleCommandDispatcher에 alert, air quality, camera, cleaning, manage, provisioning,
setting, system, map, OTA, interaction, LLM, movement, schedule, test 등을 포함한 31개 handler를 등록한다.
검증 스크립트 기준 272개 method가 272개 unique owner에 연결된다. 이것은 거대한 switch에서
도메인별 executor adapter로 나누려는 Blueprint 방향과 정확히 맞는다.
그림 3. typed 외형부터 기존 실행까지의 실제 연결
Product Interface와 Domain Framework는 형성됐지만 그 사이 Control/Execution Plane이 아직 비어 있음을 보여준다. 원본 SVG 열기
- 답하는 질문: 요청 한 건이 실제 결과와 근거로 돌아오기까지 어디를 지나는가?
- 읽는 순서: 기능 요청부터 결과·근거 반환까지 번호 순서로 읽고 아래 반환선을 본다.
- 검증 범위: 요청·Task 계약 기준이며 모든 DeviceAgent 명령의 물리 실행 연결은 진행 중이다.
이 구조를 잘못 확장하지 않는 규칙
ModuleCommandDispatcher는 command owner router로 유지한다. 정책·queue·Task state를 넣지 않는다.- handler는 도메인 입력 변환과 executor 호출에 집중한다.
- handler 내부의 공통 차단·공유 자원·재시도·완료 판정은 Control/Execution Plane으로 올린다.
- 문자열 method와 Bundle은 compatibility adapter 안에만 남기고 신규 공개 계약으로 역류시키지 않는다.
- owner 단일성은 method 기준을 넘어 실제 물리 행위 기준으로도 검증한다.
6. Device Integration Plane
현재 위치
DeviceAgent는 Android.mk에서 $(PRODUCT_OUT)/system/priv-app, LOCAL_CERTIFICATE := platform으로
배치된다. Android system_server의 SystemService가 아니라 플랫폼 서명 privileged app 내부의
제품 Runtime이다. 이 배치는 Blueprint의 초기 “하나의 Runtime Binder 진입점 뒤 논리 모듈”과
양립한다. 논리 책임을 나누기 위해 곧바로 APK나 프로세스를 여러 개 만들 필요는 없다.
MainService의 집중 책임
MainService 640줄의 AMR callback은 callback Bundle 해석, StateManager 조회·변경, ICT 분기,
IoT callback 전달, PUI/표시 반응, 오류와 product action을 한 흐름에서 처리한다. Device Integration
Plane의 connector가 Control Plane의 상태와 Experience Layer의 반응까지 소유하는 형태다.
다음 네 경계로 나눈다.
- Connector: AMR/IoT service bind와 연결 lifecycle만 소유
- Callback Decoder: legacy Bundle을 typed domain event로 변환
- State Writer / Evidence Adapter: 기존 state owner 갱신과 물리 완료 근거 발행
- Subscriber: IoT, UI, Task가 권한과 관심 이벤트에 따라 소비
또한 .DeviceAgent가 exported인 반면 전체 Runtime service에 일관된 signature permission 관문이
확인되지 않는다. 공개 Manager를 확장하기 전에 Binder.getCallingUid() 기반 UID·package·certificate
검증과 method/capability allowlist를 진입점 한 곳에서 적용해야 한다. payload의 clientName/source는
인증 근거가 아니다.
현재 경로를 목표 경로로 고치는 한 장의 설계
그림 4. 유지·분리·추가·금지 경계
기존 executor는 유지하고, 그 앞에 상태·정책·자원·Task 책임을 순서대로 삽입한다. 원본 SVG 열기
| 구분 | 현재 | 교정 목표 | 금지할 지름길 |
|---|---|---|---|
| 호출 API | typed manager → legacy adapter | 횡단 manager + typed domain API | manager를 God facade로 확대 |
| 상태 | mutable singleton + side effect | read-only StateMirror + 기존 단일 writer | 복제 상태 DB를 새 권위 원본으로 생성 |
| 정책 | 판정과 실행 결합 | pure PolicyDecision + executor 분리 |
canExecute()가 모터·화면을 제어 |
| 자원 | 도메인별 busy/lease | 공통 Resource Arbiter |
boolean busy만으로 선점·복구 처리 |
| 실행 | handler의 즉시 호출 | Task lifecycle이 domain executor를 호출 | TaskManager가 장치 구현을 복제 |
| 완료 | callback별 임의 해석 | correlation ID가 있는 EvidenceRecord |
Binder return을 물리 완료로 판정 |
| 보안 | 부분별 개별 검사 | Runtime Binder 공통 gate | clientName/source 문자열 신뢰 |
구현 순서: 무엇을 언제 옮길 것인가
그림 5. 한 번에 갈아엎지 않는 Blueprint 수렴 순서
각 단계는 기존 물리 실행 owner를 하나로 유지하고, 검증된 기능만 다음 단계로 넘긴다. 원본 SVG 열기
- 답하는 질문: 기존 기능을 중복 실행 없이 어떤 순서로 옮기는가?
- 읽는 순서: 목록화→계약 분리→관측 비교→책임 전환→기존 경로 제거 순서다.
- 검증 범위: 안전한 이행 순서이며 실기기에서 책임 전환이 끝났다는 증거는 아니다.
| 단계 | 구현 | 통과 조건 | rollback |
|---|---|---|---|
| 0 | 272 command의 caller·입력·차단·callback·물리 완료 기준 동결 | command ownership verifier + 대표 회귀 기준선 | 변경 없음 |
| 1 | DeviceStateSnapshot을 확장한 read-only StateMirror adapter |
source·timestamp·freshness와 기존 state 값 동등 | projection 비활성화 |
| 2 | callback decoder와 Event/Evidence 이중 기록 |
누락·중복·순서·correlation 검증 | 신규 event 발행 중단 |
| 3 | CmdPolicyManager 규칙의 pure evaluator + shadow decision |
allow/deny/reason 차이 승인 | 새 판정 무시 |
| 4 | CapabilityRegistry에 입력·정책·자원·executor·evidence 연결 |
대표 capability descriptor 계약 시험 | registry 조회 비활성화 |
| 5 | camera부터 공통 Resource Arbiter vertical slice |
exclusive/shared/timeout/release/fence 시험 | 기존 camera policy 복귀 |
| 6 | 이동 또는 복귀 한 기능을 TaskManager Core로 전환 |
중복 물리 실행 0, 취소·timeout·실제 완료 확인 | 기능 flag로 직접 handler 복귀 |
| 7 | schedule이 direct action 대신 Workflow/Task 제출 | reboot 복구·중복 방지·예약 완료 evidence | 기존 schedule 실행 경로 복귀 |
| 8 | UI session·추가 도메인 확장 | 실기기 Gate, 성능·장기·권한·rollback drill | 기능별 owner switch 복귀 |
Blueprint 핵심 구성요소별 최종 점검표
| 목표 구성요소 | MR7의 씨앗 | 현재 판정 | 다음 코드 단위 |
|---|---|---|---|
| SkixFramework Client Core | SkixManager, DeviceConnectionService |
부분 정렬 | version/capability/health와 typed event 연결 |
| StateMirror | DeviceStateSnapshot, 기존 StateManager 값 |
부분 정렬 | read-only projection; writer와 side effect 유지 분리 |
| Policy Engine | CmdPolicyManager 규칙 |
방향 수정 필요 | pure evaluator와 reason code 추출 |
| Resource Arbiter | CameraAccessPolicy |
부분 정렬 | 공통 resource descriptor/lease/fence 계약 |
| CapabilityRegistry | domain manager + handler owner 목록 | 미구현 | typed input부터 evidence까지 descriptor로 연결 |
| Lifecycle Supervisor | connection auto-rebind 일부 | 부분 정렬 | component READY/DEGRADED/RECOVERING/FAILED 모델 |
| TaskManager Core | schedule/direct action, 별도 donor 후보 | 미구현 | task state authority와 한 vertical slice |
| Evidence Adapter | AMR/IoT callback | 미구현 | transport ACK와 physical completion 분리 |
| Event and Evidence Fabric | ModuleCallbackRouter |
부분 정렬 | correlation, sequence, access scope, bounded replay |
| Domain Framework Plane | dispatcher + domain handlers | 정렬됨 | executor adapter로 유지하고 횡단 책임 제거 |
구현 리뷰에서 바로 사용할 질문
다음 질문에 “예”라고 답할 수 없으면 Blueprint에 수렴한 구현이 아니다.
- 이 클래스는 Manager인가 Service인가, 최종 상태 owner는 어디인가?
- 상태 snapshot에 source, observed time, freshness가 있는가?
- 정책 판정 함수가 장치 실행 없이 같은 입력에 같은 결정을 반환하는가?
- 충돌 자원에 명시적인 owner, lease, release/expiry가 있는가?
- 장기 동작에 Task ID, 상태 전이, 취소, timeout이 있는가?
ACCEPTED와 물리 완료SUCCEEDED를 다른 evidence로 판정하는가?- command router와 Task scheduler의 책임을 섞지 않았는가?
- 같은 물리 행위를 실행하는 owner가 전환 중에도 정확히 하나인가?
- exported Binder 진입점이 UID·package·certificate·capability를 공통 검사하는가?
- 기능 flag로 새 판단 또는 실행 owner만 안전하게 되돌릴 수 있는가?
부록: 기존 TaskManager 분석의 올바른 위치
별도 TaskManager 작업본의 lifecycle, workflow, scheduler, completion test는 Managed Execution Plane을 구현할 때 참고할 donor다. 그러나 MR7 전체와 직접 병합하거나 Blueprint 전체의 중심으로 삼지 않는다. MR7의 올바른 기준선은 현재 domain owner와 제품 동작이며, donor에서는 다음 책임만 선택 이식한다.
- Task state reducer와 terminal-state 불변식
- queue·timeout·cancel·retry·checkpoint
- workflow dependency와 단계 상대 지연
- completion evidence adapter 계약과 테스트
직접 Git 병합, 일괄 패키지 이식, handler 내부 TaskManager 복제는 선택하지 않는다. 이 부록의 목적은 Blueprint의 Managed Execution Plane을 채우는 것이며 문서의 목표 구조를 바꾸는 것이 아니다.